Skip to main content

kcl_lib/std/
constraints.rs

1use anyhow::Result;
2use kcl_ezpz::{
3    Constraint as SolverConstraint,
4    datatypes::{
5        AngleKind,
6        inputs::{DatumCircularArc, DatumLineSegment, DatumPoint},
7    },
8};
9
10use crate::{
11    errors::{KclError, KclErrorDetails},
12    execution::{
13        AbstractSegment, ConstrainablePoint2d, ExecState, KclValue, SegmentRepr, SketchConstraint,
14        SketchConstraintKind, SketchVarId, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind,
15        normalize_to_solver_unit,
16        types::{ArrayLen, PrimitiveType, RuntimeType},
17    },
18    front::{ArcCtor, LineCtor, Point2d, PointCtor},
19    std::Args,
20};
21#[cfg(feature = "artifact-graph")]
22use crate::{
23    execution::ArtifactId,
24    front::{
25        Coincident, Constraint, Horizontal, LinesEqualLength, Object, ObjectId, ObjectKind, Parallel, Perpendicular,
26        Vertical,
27    },
28};
29
30pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
31    let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
32    let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
33        KclError::new_semantic(KclErrorDetails::new(
34            "at must be a 2D point".to_owned(),
35            vec![args.source_range],
36        ))
37    })?;
38    let Some(at_x) = at_x_value.as_unsolved_expr() else {
39        return Err(KclError::new_semantic(KclErrorDetails::new(
40            "at x must be a number or sketch var".to_owned(),
41            vec![args.source_range],
42        )));
43    };
44    let Some(at_y) = at_y_value.as_unsolved_expr() else {
45        return Err(KclError::new_semantic(KclErrorDetails::new(
46            "at y must be a number or sketch var".to_owned(),
47            vec![args.source_range],
48        )));
49    };
50    let ctor = PointCtor {
51        position: Point2d {
52            x: at_x_value.to_sketch_expr().ok_or_else(|| {
53                KclError::new_semantic(KclErrorDetails::new(
54                    "unable to convert numeric type to suffix".to_owned(),
55                    vec![args.source_range],
56                ))
57            })?,
58            y: at_y_value.to_sketch_expr().ok_or_else(|| {
59                KclError::new_semantic(KclErrorDetails::new(
60                    "unable to convert numeric type to suffix".to_owned(),
61                    vec![args.source_range],
62                ))
63            })?,
64        },
65    };
66    let segment = UnsolvedSegment {
67        object_id: exec_state.next_object_id(),
68        kind: UnsolvedSegmentKind::Point {
69            position: [at_x, at_y],
70            ctor: Box::new(ctor),
71        },
72        meta: vec![args.source_range.into()],
73    };
74    #[cfg(feature = "artifact-graph")]
75    let optional_constraints = {
76        let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
77
78        let mut optional_constraints = Vec::new();
79        if exec_state.segment_ids_edited_contains(&object_id) {
80            if let Some(at_x_var) = at_x_value.as_sketch_var() {
81                let x_initial_value = at_x_var.initial_value_to_solver_units(
82                    exec_state,
83                    args.source_range,
84                    "edited segment fixed constraint value",
85                )?;
86                optional_constraints.push(SolverConstraint::Fixed(
87                    at_x_var.id.to_constraint_id(args.source_range)?,
88                    x_initial_value.n,
89                ));
90            }
91            if let Some(at_y_var) = at_y_value.as_sketch_var() {
92                let y_initial_value = at_y_var.initial_value_to_solver_units(
93                    exec_state,
94                    args.source_range,
95                    "edited segment fixed constraint value",
96                )?;
97                optional_constraints.push(SolverConstraint::Fixed(
98                    at_y_var.id.to_constraint_id(args.source_range)?,
99                    y_initial_value.n,
100                ));
101            }
102        }
103        optional_constraints
104    };
105
106    // Save the segment to be sent to the engine after solving.
107    let Some(sketch_state) = exec_state.sketch_block_mut() else {
108        return Err(KclError::new_semantic(KclErrorDetails::new(
109            "line() can only be used inside a sketch block".to_owned(),
110            vec![args.source_range],
111        )));
112    };
113    sketch_state.needed_by_engine.push(segment.clone());
114
115    #[cfg(feature = "artifact-graph")]
116    sketch_state.solver_optional_constraints.extend(optional_constraints);
117
118    let meta = segment.meta.clone();
119    let abstract_segment = AbstractSegment {
120        repr: SegmentRepr::Unsolved { segment },
121        meta,
122    };
123    Ok(KclValue::Segment {
124        value: Box::new(abstract_segment),
125    })
126}
127
128pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
129    let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
130    // TODO: make this optional and add midpoint.
131    let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
132    let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
133    let construction: bool = construction_opt.unwrap_or(false);
134    let construction_ctor = construction_opt;
135    let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
136        KclError::new_semantic(KclErrorDetails::new(
137            "start must be a 2D point".to_owned(),
138            vec![args.source_range],
139        ))
140    })?;
141    let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
142        KclError::new_semantic(KclErrorDetails::new(
143            "end must be a 2D point".to_owned(),
144            vec![args.source_range],
145        ))
146    })?;
147    let Some(start_x) = start_x_value.as_unsolved_expr() else {
148        return Err(KclError::new_semantic(KclErrorDetails::new(
149            "start x must be a number or sketch var".to_owned(),
150            vec![args.source_range],
151        )));
152    };
153    let Some(start_y) = start_y_value.as_unsolved_expr() else {
154        return Err(KclError::new_semantic(KclErrorDetails::new(
155            "start y must be a number or sketch var".to_owned(),
156            vec![args.source_range],
157        )));
158    };
159    let Some(end_x) = end_x_value.as_unsolved_expr() else {
160        return Err(KclError::new_semantic(KclErrorDetails::new(
161            "end x must be a number or sketch var".to_owned(),
162            vec![args.source_range],
163        )));
164    };
165    let Some(end_y) = end_y_value.as_unsolved_expr() else {
166        return Err(KclError::new_semantic(KclErrorDetails::new(
167            "end y must be a number or sketch var".to_owned(),
168            vec![args.source_range],
169        )));
170    };
171    let ctor = LineCtor {
172        start: Point2d {
173            x: start_x_value.to_sketch_expr().ok_or_else(|| {
174                KclError::new_semantic(KclErrorDetails::new(
175                    "unable to convert numeric type to suffix".to_owned(),
176                    vec![args.source_range],
177                ))
178            })?,
179            y: start_y_value.to_sketch_expr().ok_or_else(|| {
180                KclError::new_semantic(KclErrorDetails::new(
181                    "unable to convert numeric type to suffix".to_owned(),
182                    vec![args.source_range],
183                ))
184            })?,
185        },
186        end: Point2d {
187            x: end_x_value.to_sketch_expr().ok_or_else(|| {
188                KclError::new_semantic(KclErrorDetails::new(
189                    "unable to convert numeric type to suffix".to_owned(),
190                    vec![args.source_range],
191                ))
192            })?,
193            y: end_y_value.to_sketch_expr().ok_or_else(|| {
194                KclError::new_semantic(KclErrorDetails::new(
195                    "unable to convert numeric type to suffix".to_owned(),
196                    vec![args.source_range],
197                ))
198            })?,
199        },
200        construction: construction_ctor,
201    };
202    // Order of ID generation is important.
203    let start_object_id = exec_state.next_object_id();
204    let end_object_id = exec_state.next_object_id();
205    let line_object_id = exec_state.next_object_id();
206    let segment = UnsolvedSegment {
207        object_id: line_object_id,
208        kind: UnsolvedSegmentKind::Line {
209            start: [start_x, start_y],
210            end: [end_x, end_y],
211            ctor: Box::new(ctor),
212            start_object_id,
213            end_object_id,
214            construction,
215        },
216        meta: vec![args.source_range.into()],
217    };
218    #[cfg(feature = "artifact-graph")]
219    let optional_constraints = {
220        let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
221        let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
222        let line_object_id = exec_state.add_placeholder_scene_object(line_object_id, args.source_range);
223
224        let mut optional_constraints = Vec::new();
225        if exec_state.segment_ids_edited_contains(&start_object_id)
226            || exec_state.segment_ids_edited_contains(&line_object_id)
227        {
228            if let Some(start_x_var) = start_x_value.as_sketch_var() {
229                let x_initial_value = start_x_var.initial_value_to_solver_units(
230                    exec_state,
231                    args.source_range,
232                    "edited segment fixed constraint value",
233                )?;
234                optional_constraints.push(SolverConstraint::Fixed(
235                    start_x_var.id.to_constraint_id(args.source_range)?,
236                    x_initial_value.n,
237                ));
238            }
239            if let Some(start_y_var) = start_y_value.as_sketch_var() {
240                let y_initial_value = start_y_var.initial_value_to_solver_units(
241                    exec_state,
242                    args.source_range,
243                    "edited segment fixed constraint value",
244                )?;
245                optional_constraints.push(SolverConstraint::Fixed(
246                    start_y_var.id.to_constraint_id(args.source_range)?,
247                    y_initial_value.n,
248                ));
249            }
250        }
251        if exec_state.segment_ids_edited_contains(&end_object_id)
252            || exec_state.segment_ids_edited_contains(&line_object_id)
253        {
254            if let Some(end_x_var) = end_x_value.as_sketch_var() {
255                let x_initial_value = end_x_var.initial_value_to_solver_units(
256                    exec_state,
257                    args.source_range,
258                    "edited segment fixed constraint value",
259                )?;
260                optional_constraints.push(SolverConstraint::Fixed(
261                    end_x_var.id.to_constraint_id(args.source_range)?,
262                    x_initial_value.n,
263                ));
264            }
265            if let Some(end_y_var) = end_y_value.as_sketch_var() {
266                let y_initial_value = end_y_var.initial_value_to_solver_units(
267                    exec_state,
268                    args.source_range,
269                    "edited segment fixed constraint value",
270                )?;
271                optional_constraints.push(SolverConstraint::Fixed(
272                    end_y_var.id.to_constraint_id(args.source_range)?,
273                    y_initial_value.n,
274                ));
275            }
276        }
277        optional_constraints
278    };
279
280    // Save the segment to be sent to the engine after solving.
281    let Some(sketch_state) = exec_state.sketch_block_mut() else {
282        return Err(KclError::new_semantic(KclErrorDetails::new(
283            "line() can only be used inside a sketch block".to_owned(),
284            vec![args.source_range],
285        )));
286    };
287    sketch_state.needed_by_engine.push(segment.clone());
288
289    #[cfg(feature = "artifact-graph")]
290    sketch_state.solver_optional_constraints.extend(optional_constraints);
291
292    let meta = segment.meta.clone();
293    let abstract_segment = AbstractSegment {
294        repr: SegmentRepr::Unsolved { segment },
295        meta,
296    };
297    Ok(KclValue::Segment {
298        value: Box::new(abstract_segment),
299    })
300}
301
302pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
303    let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
304    let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
305    // TODO: make this optional and add interior.
306    let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
307    let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
308    let construction: bool = construction_opt.unwrap_or(false);
309    let construction_ctor = construction_opt;
310
311    let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
312        KclError::new_semantic(KclErrorDetails::new(
313            "start must be a 2D point".to_owned(),
314            vec![args.source_range],
315        ))
316    })?;
317    let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
318        KclError::new_semantic(KclErrorDetails::new(
319            "end must be a 2D point".to_owned(),
320            vec![args.source_range],
321        ))
322    })?;
323    let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
324        KclError::new_semantic(KclErrorDetails::new(
325            "center must be a 2D point".to_owned(),
326            vec![args.source_range],
327        ))
328    })?;
329
330    let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
331        return Err(KclError::new_semantic(KclErrorDetails::new(
332            "start x must be a sketch var".to_owned(),
333            vec![args.source_range],
334        )));
335    };
336    let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
337        return Err(KclError::new_semantic(KclErrorDetails::new(
338            "start y must be a sketch var".to_owned(),
339            vec![args.source_range],
340        )));
341    };
342    let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
343        return Err(KclError::new_semantic(KclErrorDetails::new(
344            "end x must be a sketch var".to_owned(),
345            vec![args.source_range],
346        )));
347    };
348    let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
349        return Err(KclError::new_semantic(KclErrorDetails::new(
350            "end y must be a sketch var".to_owned(),
351            vec![args.source_range],
352        )));
353    };
354    let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
355        return Err(KclError::new_semantic(KclErrorDetails::new(
356            "center x must be a sketch var".to_owned(),
357            vec![args.source_range],
358        )));
359    };
360    let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
361        return Err(KclError::new_semantic(KclErrorDetails::new(
362            "center y must be a sketch var".to_owned(),
363            vec![args.source_range],
364        )));
365    };
366
367    let ctor = ArcCtor {
368        start: Point2d {
369            x: start_x_value.to_sketch_expr().ok_or_else(|| {
370                KclError::new_semantic(KclErrorDetails::new(
371                    "unable to convert numeric type to suffix".to_owned(),
372                    vec![args.source_range],
373                ))
374            })?,
375            y: start_y_value.to_sketch_expr().ok_or_else(|| {
376                KclError::new_semantic(KclErrorDetails::new(
377                    "unable to convert numeric type to suffix".to_owned(),
378                    vec![args.source_range],
379                ))
380            })?,
381        },
382        end: Point2d {
383            x: end_x_value.to_sketch_expr().ok_or_else(|| {
384                KclError::new_semantic(KclErrorDetails::new(
385                    "unable to convert numeric type to suffix".to_owned(),
386                    vec![args.source_range],
387                ))
388            })?,
389            y: end_y_value.to_sketch_expr().ok_or_else(|| {
390                KclError::new_semantic(KclErrorDetails::new(
391                    "unable to convert numeric type to suffix".to_owned(),
392                    vec![args.source_range],
393                ))
394            })?,
395        },
396        center: Point2d {
397            x: center_x_value.to_sketch_expr().ok_or_else(|| {
398                KclError::new_semantic(KclErrorDetails::new(
399                    "unable to convert numeric type to suffix".to_owned(),
400                    vec![args.source_range],
401                ))
402            })?,
403            y: center_y_value.to_sketch_expr().ok_or_else(|| {
404                KclError::new_semantic(KclErrorDetails::new(
405                    "unable to convert numeric type to suffix".to_owned(),
406                    vec![args.source_range],
407                ))
408            })?,
409        },
410        construction: construction_ctor,
411    };
412
413    // Order of ID generation is important.
414    let start_object_id = exec_state.next_object_id();
415    let end_object_id = exec_state.next_object_id();
416    let center_object_id = exec_state.next_object_id();
417    let arc_object_id = exec_state.next_object_id();
418    let segment = UnsolvedSegment {
419        object_id: arc_object_id,
420        kind: UnsolvedSegmentKind::Arc {
421            start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
422            end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
423            center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
424            ctor: Box::new(ctor),
425            start_object_id,
426            end_object_id,
427            center_object_id,
428            construction,
429        },
430        meta: vec![args.source_range.into()],
431    };
432    #[cfg(feature = "artifact-graph")]
433    let optional_constraints = {
434        let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
435        let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
436        let center_object_id = exec_state.add_placeholder_scene_object(center_object_id, args.source_range);
437        let arc_object_id = exec_state.add_placeholder_scene_object(arc_object_id, args.source_range);
438
439        let mut optional_constraints = Vec::new();
440        if exec_state.segment_ids_edited_contains(&start_object_id)
441            || exec_state.segment_ids_edited_contains(&arc_object_id)
442        {
443            if let Some(start_x_var) = start_x_value.as_sketch_var() {
444                let x_initial_value = start_x_var.initial_value_to_solver_units(
445                    exec_state,
446                    args.source_range,
447                    "edited segment fixed constraint value",
448                )?;
449                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
450                    start_x_var.id.to_constraint_id(args.source_range)?,
451                    x_initial_value.n,
452                ));
453            }
454            if let Some(start_y_var) = start_y_value.as_sketch_var() {
455                let y_initial_value = start_y_var.initial_value_to_solver_units(
456                    exec_state,
457                    args.source_range,
458                    "edited segment fixed constraint value",
459                )?;
460                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
461                    start_y_var.id.to_constraint_id(args.source_range)?,
462                    y_initial_value.n,
463                ));
464            }
465        }
466        if exec_state.segment_ids_edited_contains(&end_object_id)
467            || exec_state.segment_ids_edited_contains(&arc_object_id)
468        {
469            if let Some(end_x_var) = end_x_value.as_sketch_var() {
470                let x_initial_value = end_x_var.initial_value_to_solver_units(
471                    exec_state,
472                    args.source_range,
473                    "edited segment fixed constraint value",
474                )?;
475                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
476                    end_x_var.id.to_constraint_id(args.source_range)?,
477                    x_initial_value.n,
478                ));
479            }
480            if let Some(end_y_var) = end_y_value.as_sketch_var() {
481                let y_initial_value = end_y_var.initial_value_to_solver_units(
482                    exec_state,
483                    args.source_range,
484                    "edited segment fixed constraint value",
485                )?;
486                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
487                    end_y_var.id.to_constraint_id(args.source_range)?,
488                    y_initial_value.n,
489                ));
490            }
491        }
492        if exec_state.segment_ids_edited_contains(&center_object_id)
493            || exec_state.segment_ids_edited_contains(&arc_object_id)
494        {
495            if let Some(center_x_var) = center_x_value.as_sketch_var() {
496                let x_initial_value = center_x_var.initial_value_to_solver_units(
497                    exec_state,
498                    args.source_range,
499                    "edited segment fixed constraint value",
500                )?;
501                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
502                    center_x_var.id.to_constraint_id(args.source_range)?,
503                    x_initial_value.n,
504                ));
505            }
506            if let Some(center_y_var) = center_y_value.as_sketch_var() {
507                let y_initial_value = center_y_var.initial_value_to_solver_units(
508                    exec_state,
509                    args.source_range,
510                    "edited segment fixed constraint value",
511                )?;
512                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
513                    center_y_var.id.to_constraint_id(args.source_range)?,
514                    y_initial_value.n,
515                ));
516            }
517        }
518        optional_constraints
519    };
520
521    // Build the implicit arc constraint.
522    let range = args.source_range;
523    let constraint = kcl_ezpz::Constraint::Arc(kcl_ezpz::datatypes::inputs::DatumCircularArc {
524        center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
525            center_x.to_constraint_id(range)?,
526            center_y.to_constraint_id(range)?,
527        ),
528        start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
529            start_x.to_constraint_id(range)?,
530            start_y.to_constraint_id(range)?,
531        ),
532        end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
533            end_x.to_constraint_id(range)?,
534            end_y.to_constraint_id(range)?,
535        ),
536    });
537
538    let Some(sketch_state) = exec_state.sketch_block_mut() else {
539        return Err(KclError::new_semantic(KclErrorDetails::new(
540            "arc() can only be used inside a sketch block".to_owned(),
541            vec![args.source_range],
542        )));
543    };
544    // Save the segment to be sent to the engine after solving.
545    sketch_state.needed_by_engine.push(segment.clone());
546    // Save the constraint to be used for solving.
547    sketch_state.solver_constraints.push(constraint);
548    // The constraint isn't added to scene objects since it's implicit in the
549    // arc segment. You cannot have an arc without it.
550
551    #[cfg(feature = "artifact-graph")]
552    sketch_state.solver_optional_constraints.extend(optional_constraints);
553
554    let meta = segment.meta.clone();
555    let abstract_segment = AbstractSegment {
556        repr: SegmentRepr::Unsolved { segment },
557        meta,
558    };
559    Ok(KclValue::Segment {
560        value: Box::new(abstract_segment),
561    })
562}
563
564pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
565    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
566        "points",
567        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
568        exec_state,
569    )?;
570    let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
571        KclError::new_semantic(KclErrorDetails::new(
572            "must have two input points".to_owned(),
573            vec![args.source_range],
574        ))
575    })?;
576
577    let range = args.source_range;
578    match (&point0, &point1) {
579        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
580            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
581                return Err(KclError::new_semantic(KclErrorDetails::new(
582                    "first point must be an unsolved segment".to_owned(),
583                    vec![args.source_range],
584                )));
585            };
586            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
587                return Err(KclError::new_semantic(KclErrorDetails::new(
588                    "second point must be an unsolved segment".to_owned(),
589                    vec![args.source_range],
590                )));
591            };
592            match (&unsolved0.kind, &unsolved1.kind) {
593                (
594                    UnsolvedSegmentKind::Point { position: pos0, .. },
595                    UnsolvedSegmentKind::Point { position: pos1, .. },
596                ) => {
597                    let p0_x = &pos0[0];
598                    let p0_y = &pos0[1];
599                    match (p0_x, p0_y) {
600                        (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
601                            let p1_x = &pos1[0];
602                            let p1_y = &pos1[1];
603                            match (p1_x, p1_y) {
604                                (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
605                                    let constraint = SolverConstraint::PointsCoincident(
606                                        kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
607                                            p0_x.to_constraint_id(range)?,
608                                            p0_y.to_constraint_id(range)?,
609                                        ),
610                                        kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
611                                            p1_x.to_constraint_id(range)?,
612                                            p1_y.to_constraint_id(range)?,
613                                        ),
614                                    );
615                                    #[cfg(feature = "artifact-graph")]
616                                    let constraint_id = exec_state.next_object_id();
617                                    // Save the constraint to be used for solving.
618                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
619                                        return Err(KclError::new_semantic(KclErrorDetails::new(
620                                            "coincident() can only be used inside a sketch block".to_owned(),
621                                            vec![args.source_range],
622                                        )));
623                                    };
624                                    sketch_state.solver_constraints.push(constraint);
625                                    #[cfg(feature = "artifact-graph")]
626                                    {
627                                        let constraint = crate::front::Constraint::Coincident(Coincident {
628                                            segments: vec![unsolved0.object_id, unsolved1.object_id],
629                                        });
630                                        sketch_state.sketch_constraints.push(constraint_id);
631                                        track_constraint(constraint_id, constraint, exec_state, &args);
632                                    }
633                                    Ok(KclValue::none())
634                                }
635                                (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
636                                    let p1_x = KclValue::Number {
637                                        value: p1_x.n,
638                                        ty: p1_x.ty,
639                                        meta: vec![args.source_range.into()],
640                                    };
641                                    let p1_y = KclValue::Number {
642                                        value: p1_y.n,
643                                        ty: p1_y.ty,
644                                        meta: vec![args.source_range.into()],
645                                    };
646                                    let (constraint_x, constraint_y) =
647                                        coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
648
649                                    #[cfg(feature = "artifact-graph")]
650                                    let constraint_id = exec_state.next_object_id();
651                                    // Save the constraint to be used for solving.
652                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
653                                        return Err(KclError::new_semantic(KclErrorDetails::new(
654                                            "coincident() can only be used inside a sketch block".to_owned(),
655                                            vec![args.source_range],
656                                        )));
657                                    };
658                                    sketch_state.solver_constraints.push(constraint_x);
659                                    sketch_state.solver_constraints.push(constraint_y);
660                                    #[cfg(feature = "artifact-graph")]
661                                    {
662                                        let constraint = crate::front::Constraint::Coincident(Coincident {
663                                            segments: vec![unsolved0.object_id, unsolved1.object_id],
664                                        });
665                                        sketch_state.sketch_constraints.push(constraint_id);
666                                        track_constraint(constraint_id, constraint, exec_state, &args);
667                                    }
668                                    Ok(KclValue::none())
669                                }
670                                (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
671                                | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
672                                    // TODO: sketch-api: unimplemented
673                                    Err(KclError::new_semantic(KclErrorDetails::new(
674                                        "Unimplemented: When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
675                                        vec![args.source_range],
676                                    )))
677                                }
678                            }
679                        }
680                        (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
681                            let p1_x = &pos1[0];
682                            let p1_y = &pos1[1];
683                            match (p1_x, p1_y) {
684                                (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
685                                    let p0_x = KclValue::Number {
686                                        value: p0_x.n,
687                                        ty: p0_x.ty,
688                                        meta: vec![args.source_range.into()],
689                                    };
690                                    let p0_y = KclValue::Number {
691                                        value: p0_y.n,
692                                        ty: p0_y.ty,
693                                        meta: vec![args.source_range.into()],
694                                    };
695                                    let (constraint_x, constraint_y) =
696                                        coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
697
698                                    #[cfg(feature = "artifact-graph")]
699                                    let constraint_id = exec_state.next_object_id();
700                                    // Save the constraint to be used for solving.
701                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
702                                        return Err(KclError::new_semantic(KclErrorDetails::new(
703                                            "coincident() can only be used inside a sketch block".to_owned(),
704                                            vec![args.source_range],
705                                        )));
706                                    };
707                                    sketch_state.solver_constraints.push(constraint_x);
708                                    sketch_state.solver_constraints.push(constraint_y);
709                                    #[cfg(feature = "artifact-graph")]
710                                    {
711                                        let constraint = crate::front::Constraint::Coincident(Coincident {
712                                            segments: vec![unsolved0.object_id, unsolved1.object_id],
713                                        });
714                                        sketch_state.sketch_constraints.push(constraint_id);
715                                        track_constraint(constraint_id, constraint, exec_state, &args);
716                                    }
717                                    Ok(KclValue::none())
718                                }
719                                (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
720                                    if *p0_x != *p1_x || *p0_y != *p1_y {
721                                        return Err(KclError::new_semantic(KclErrorDetails::new(
722                                            "Coincident constraint between two fixed points failed since coordinates differ"
723                                                .to_owned(),
724                                            vec![args.source_range],
725                                        )));
726                                    }
727                                    Ok(KclValue::none())
728                                }
729                                (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
730                                | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
731                                    // TODO: sketch-api: unimplemented
732                                    Err(KclError::new_semantic(KclErrorDetails::new(
733                                        "Unimplemented: When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
734                                        vec![args.source_range],
735                                    )))
736                                }
737                            }
738                        }
739                        (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
740                        | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
741                            // The segment is a point with one sketch var.
742                            Err(KclError::new_semantic(KclErrorDetails::new(
743                                "When given points, input point at index 0 must be a sketch var for both x and y coordinates to constrain as coincident".to_owned(),
744                                vec![args.source_range],
745                            )))
746                        }
747                    }
748                }
749                // Point-Line or Line-Point case: create perpendicular distance constraint with distance 0
750                (
751                    UnsolvedSegmentKind::Point {
752                        position: point_pos, ..
753                    },
754                    UnsolvedSegmentKind::Line {
755                        start: line_start,
756                        end: line_end,
757                        ..
758                    },
759                )
760                | (
761                    UnsolvedSegmentKind::Line {
762                        start: line_start,
763                        end: line_end,
764                        ..
765                    },
766                    UnsolvedSegmentKind::Point {
767                        position: point_pos, ..
768                    },
769                ) => {
770                    let point_x = &point_pos[0];
771                    let point_y = &point_pos[1];
772                    match (point_x, point_y) {
773                        (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
774                            // Extract line start and end coordinates
775                            let (start_x, start_y) = (&line_start[0], &line_start[1]);
776                            let (end_x, end_y) = (&line_end[0], &line_end[1]);
777
778                            match (start_x, start_y, end_x, end_y) {
779                                (
780                                    UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
781                                    UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
782                                ) => {
783                                    let point = DatumPoint::new_xy(
784                                        point_x.to_constraint_id(range)?,
785                                        point_y.to_constraint_id(range)?,
786                                    );
787                                    let line_segment = DatumLineSegment::new(
788                                        DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
789                                        DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
790                                    );
791                                    let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
792
793                                    #[cfg(feature = "artifact-graph")]
794                                    let constraint_id = exec_state.next_object_id();
795
796                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
797                                        return Err(KclError::new_semantic(KclErrorDetails::new(
798                                            "coincident() can only be used inside a sketch block".to_owned(),
799                                            vec![args.source_range],
800                                        )));
801                                    };
802                                    sketch_state.solver_constraints.push(constraint);
803                                    #[cfg(feature = "artifact-graph")]
804                                    {
805                                        let constraint = crate::front::Constraint::Coincident(Coincident {
806                                            segments: vec![unsolved0.object_id, unsolved1.object_id],
807                                        });
808                                        sketch_state.sketch_constraints.push(constraint_id);
809                                        track_constraint(constraint_id, constraint, exec_state, &args);
810                                    }
811                                    Ok(KclValue::none())
812                                }
813                                _ => Err(KclError::new_semantic(KclErrorDetails::new(
814                                    "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
815                                    vec![args.source_range],
816                                ))),
817                            }
818                        }
819                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
820                            "Point coordinates must be sketch variables for point-segment coincident constraint"
821                                .to_owned(),
822                            vec![args.source_range],
823                        ))),
824                    }
825                }
826                // Point-Arc or Arc-Point case: create PointArcCoincident constraint
827                (
828                    UnsolvedSegmentKind::Point {
829                        position: point_pos, ..
830                    },
831                    UnsolvedSegmentKind::Arc {
832                        start: arc_start,
833                        end: arc_end,
834                        center: arc_center,
835                        ..
836                    },
837                )
838                | (
839                    UnsolvedSegmentKind::Arc {
840                        start: arc_start,
841                        end: arc_end,
842                        center: arc_center,
843                        ..
844                    },
845                    UnsolvedSegmentKind::Point {
846                        position: point_pos, ..
847                    },
848                ) => {
849                    let point_x = &point_pos[0];
850                    let point_y = &point_pos[1];
851                    match (point_x, point_y) {
852                        (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
853                            // Extract arc center, start, and end coordinates
854                            let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
855                            let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
856                            let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
857
858                            match (center_x, center_y, start_x, start_y, end_x, end_y) {
859                                (
860                                    UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
861                                    UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
862                                    UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
863                                ) => {
864                                    let point = DatumPoint::new_xy(
865                                        point_x.to_constraint_id(range)?,
866                                        point_y.to_constraint_id(range)?,
867                                    );
868                                    let circular_arc = DatumCircularArc {
869                                        center: DatumPoint::new_xy(
870                                            cx.to_constraint_id(range)?,
871                                            cy.to_constraint_id(range)?,
872                                        ),
873                                        start: DatumPoint::new_xy(
874                                            sx.to_constraint_id(range)?,
875                                            sy.to_constraint_id(range)?,
876                                        ),
877                                        end: DatumPoint::new_xy(
878                                            ex.to_constraint_id(range)?,
879                                            ey.to_constraint_id(range)?,
880                                        ),
881                                    };
882                                    let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
883
884                                    #[cfg(feature = "artifact-graph")]
885                                    let constraint_id = exec_state.next_object_id();
886
887                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
888                                        return Err(KclError::new_semantic(KclErrorDetails::new(
889                                            "coincident() can only be used inside a sketch block".to_owned(),
890                                            vec![args.source_range],
891                                        )));
892                                    };
893                                    sketch_state.solver_constraints.push(constraint);
894                                    #[cfg(feature = "artifact-graph")]
895                                    {
896                                        let constraint = crate::front::Constraint::Coincident(Coincident {
897                                            segments: vec![unsolved0.object_id, unsolved1.object_id],
898                                        });
899                                        sketch_state.sketch_constraints.push(constraint_id);
900                                        track_constraint(constraint_id, constraint, exec_state, &args);
901                                    }
902                                    Ok(KclValue::none())
903                                }
904                                _ => Err(KclError::new_semantic(KclErrorDetails::new(
905                                    "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
906                                    vec![args.source_range],
907                                ))),
908                            }
909                        }
910                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
911                            "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
912                            vec![args.source_range],
913                        ))),
914                    }
915                }
916                // Line-Line case: create parallel constraint and perpendicular distance of zero
917                (
918                    UnsolvedSegmentKind::Line {
919                        start: line0_start,
920                        end: line0_end,
921                        ..
922                    },
923                    UnsolvedSegmentKind::Line {
924                        start: line1_start,
925                        end: line1_end,
926                        ..
927                    },
928                ) => {
929                    // Extract line coordinates
930                    let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
931                    let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
932                    let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
933                    let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
934
935                    match (
936                        line0_start_x,
937                        line0_start_y,
938                        line0_end_x,
939                        line0_end_y,
940                        line1_start_x,
941                        line1_start_y,
942                        line1_end_x,
943                        line1_end_y,
944                    ) {
945                        (
946                            UnsolvedExpr::Unknown(l0_sx),
947                            UnsolvedExpr::Unknown(l0_sy),
948                            UnsolvedExpr::Unknown(l0_ex),
949                            UnsolvedExpr::Unknown(l0_ey),
950                            UnsolvedExpr::Unknown(l1_sx),
951                            UnsolvedExpr::Unknown(l1_sy),
952                            UnsolvedExpr::Unknown(l1_ex),
953                            UnsolvedExpr::Unknown(l1_ey),
954                        ) => {
955                            // Create line segments for the solver
956                            let line0_segment = DatumLineSegment::new(
957                                DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
958                                DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
959                            );
960                            let line1_segment = DatumLineSegment::new(
961                                DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
962                                DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
963                            );
964
965                            // Create parallel constraint
966                            let parallel_constraint =
967                                SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
968
969                            // Create perpendicular distance constraint from first line to start point of second line
970                            let point_on_line1 =
971                                DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
972                            let distance_constraint =
973                                SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
974
975                            #[cfg(feature = "artifact-graph")]
976                            let constraint_id = exec_state.next_object_id();
977
978                            let Some(sketch_state) = exec_state.sketch_block_mut() else {
979                                return Err(KclError::new_semantic(KclErrorDetails::new(
980                                    "coincident() can only be used inside a sketch block".to_owned(),
981                                    vec![args.source_range],
982                                )));
983                            };
984                            // Push both constraints to achieve collinearity
985                            sketch_state.solver_constraints.push(parallel_constraint);
986                            sketch_state.solver_constraints.push(distance_constraint);
987                            #[cfg(feature = "artifact-graph")]
988                            {
989                                let constraint = crate::front::Constraint::Coincident(Coincident {
990                                    segments: vec![unsolved0.object_id, unsolved1.object_id],
991                                });
992                                sketch_state.sketch_constraints.push(constraint_id);
993                                track_constraint(constraint_id, constraint, exec_state, &args);
994                            }
995                            Ok(KclValue::none())
996                        }
997                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
998                            "Line segment endpoints must be sketch variables for line-line coincident constraint"
999                                .to_owned(),
1000                            vec![args.source_range],
1001                        ))),
1002                    }
1003                }
1004                _ => Err(KclError::new_semantic(KclErrorDetails::new(
1005                    format!(
1006                        "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
1007                        &unsolved0.kind, &unsolved1.kind
1008                    ),
1009                    vec![args.source_range],
1010                ))),
1011            }
1012        }
1013        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1014            "All inputs must be segments (points or lines), created from point(), line(), or another sketch function"
1015                .to_owned(),
1016            vec![args.source_range],
1017        ))),
1018    }
1019}
1020
1021#[cfg(feature = "artifact-graph")]
1022fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
1023    exec_state.add_scene_object(
1024        Object {
1025            id: constraint_id,
1026            kind: ObjectKind::Constraint { constraint },
1027            label: Default::default(),
1028            comments: Default::default(),
1029            artifact_id: ArtifactId::constraint(),
1030            source: args.source_range.into(),
1031        },
1032        args.source_range,
1033    );
1034}
1035
1036/// Order of points has been erased when calling this function.
1037fn coincident_constraints_fixed(
1038    p0_x: SketchVarId,
1039    p0_y: SketchVarId,
1040    p1_x: &KclValue,
1041    p1_y: &KclValue,
1042    exec_state: &mut ExecState,
1043    args: &Args,
1044) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
1045    let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
1046    let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
1047    let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
1048        let message = format!(
1049            "Expected number after coercion, but found {}",
1050            p1_x_number_value.human_friendly_type()
1051        );
1052        debug_assert!(false, "{}", &message);
1053        return Err(KclError::new_internal(KclErrorDetails::new(
1054            message,
1055            vec![args.source_range],
1056        )));
1057    };
1058    let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
1059        let message = format!(
1060            "Expected number after coercion, but found {}",
1061            p1_y_number_value.human_friendly_type()
1062        );
1063        debug_assert!(false, "{}", &message);
1064        return Err(KclError::new_internal(KclErrorDetails::new(
1065            message,
1066            vec![args.source_range],
1067        )));
1068    };
1069    let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
1070    let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
1071    Ok((constraint_x, constraint_y))
1072}
1073
1074pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1075    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1076        "points",
1077        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1078        exec_state,
1079    )?;
1080    let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1081        KclError::new_semantic(KclErrorDetails::new(
1082            "must have two input points".to_owned(),
1083            vec![args.source_range],
1084        ))
1085    })?;
1086
1087    match (&point0, &point1) {
1088        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1089            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1090                return Err(KclError::new_semantic(KclErrorDetails::new(
1091                    "first point must be an unsolved segment".to_owned(),
1092                    vec![args.source_range],
1093                )));
1094            };
1095            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1096                return Err(KclError::new_semantic(KclErrorDetails::new(
1097                    "second point must be an unsolved segment".to_owned(),
1098                    vec![args.source_range],
1099                )));
1100            };
1101            match (&unsolved0.kind, &unsolved1.kind) {
1102                (
1103                    UnsolvedSegmentKind::Point { position: pos0, .. },
1104                    UnsolvedSegmentKind::Point { position: pos1, .. },
1105                ) => {
1106                    // Both segments are points. Create a distance constraint
1107                    // between them.
1108                    match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1109                        (
1110                            UnsolvedExpr::Unknown(p0_x),
1111                            UnsolvedExpr::Unknown(p0_y),
1112                            UnsolvedExpr::Unknown(p1_x),
1113                            UnsolvedExpr::Unknown(p1_y),
1114                        ) => {
1115                            // All coordinates are sketch vars. Proceed.
1116                            let sketch_constraint = SketchConstraint {
1117                                kind: SketchConstraintKind::Distance {
1118                                    points: [
1119                                        ConstrainablePoint2d {
1120                                            vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1121                                            object_id: unsolved0.object_id,
1122                                        },
1123                                        ConstrainablePoint2d {
1124                                            vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1125                                            object_id: unsolved1.object_id,
1126                                        },
1127                                    ],
1128                                },
1129                                meta: vec![args.source_range.into()],
1130                            };
1131                            Ok(KclValue::SketchConstraint {
1132                                value: Box::new(sketch_constraint),
1133                            })
1134                        }
1135                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1136                            "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1137                            vec![args.source_range],
1138                        ))),
1139                    }
1140                }
1141                _ => Err(KclError::new_semantic(KclErrorDetails::new(
1142                    "distance() arguments must be unsolved points".to_owned(),
1143                    vec![args.source_range],
1144                ))),
1145            }
1146        }
1147        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1148            "distance() arguments must be point segments".to_owned(),
1149            vec![args.source_range],
1150        ))),
1151    }
1152}
1153
1154pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1155    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1156        "points",
1157        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1158        exec_state,
1159    )?;
1160    let [p1, p2] = points.as_slice() else {
1161        return Err(KclError::new_semantic(KclErrorDetails::new(
1162            "must have two input points".to_owned(),
1163            vec![args.source_range],
1164        )));
1165    };
1166    match (p1, p2) {
1167        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1168            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1169                return Err(KclError::new_semantic(KclErrorDetails::new(
1170                    "first point must be an unsolved segment".to_owned(),
1171                    vec![args.source_range],
1172                )));
1173            };
1174            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1175                return Err(KclError::new_semantic(KclErrorDetails::new(
1176                    "second point must be an unsolved segment".to_owned(),
1177                    vec![args.source_range],
1178                )));
1179            };
1180            match (&unsolved0.kind, &unsolved1.kind) {
1181                (
1182                    UnsolvedSegmentKind::Point { position: pos0, .. },
1183                    UnsolvedSegmentKind::Point { position: pos1, .. },
1184                ) => {
1185                    // Both segments are points. Create a horizontal distance constraint
1186                    // between them.
1187                    match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1188                        (
1189                            UnsolvedExpr::Unknown(p0_x),
1190                            UnsolvedExpr::Unknown(p0_y),
1191                            UnsolvedExpr::Unknown(p1_x),
1192                            UnsolvedExpr::Unknown(p1_y),
1193                        ) => {
1194                            // All coordinates are sketch vars. Proceed.
1195                            let sketch_constraint = SketchConstraint {
1196                                kind: SketchConstraintKind::HorizontalDistance {
1197                                    points: [
1198                                        ConstrainablePoint2d {
1199                                            vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1200                                            object_id: unsolved0.object_id,
1201                                        },
1202                                        ConstrainablePoint2d {
1203                                            vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1204                                            object_id: unsolved1.object_id,
1205                                        },
1206                                    ],
1207                                },
1208                                meta: vec![args.source_range.into()],
1209                            };
1210                            Ok(KclValue::SketchConstraint {
1211                                value: Box::new(sketch_constraint),
1212                            })
1213                        }
1214                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1215                            "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
1216                                .to_owned(),
1217                            vec![args.source_range],
1218                        ))),
1219                    }
1220                }
1221                _ => Err(KclError::new_semantic(KclErrorDetails::new(
1222                    "horizontalDistance() arguments must be unsolved points".to_owned(),
1223                    vec![args.source_range],
1224                ))),
1225            }
1226        }
1227        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1228            "horizontalDistance() arguments must be point segments".to_owned(),
1229            vec![args.source_range],
1230        ))),
1231    }
1232}
1233
1234pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1235    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1236        "points",
1237        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1238        exec_state,
1239    )?;
1240    let [p1, p2] = points.as_slice() else {
1241        return Err(KclError::new_semantic(KclErrorDetails::new(
1242            "must have two input points".to_owned(),
1243            vec![args.source_range],
1244        )));
1245    };
1246    match (p1, p2) {
1247        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1248            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1249                return Err(KclError::new_semantic(KclErrorDetails::new(
1250                    "first point must be an unsolved segment".to_owned(),
1251                    vec![args.source_range],
1252                )));
1253            };
1254            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1255                return Err(KclError::new_semantic(KclErrorDetails::new(
1256                    "second point must be an unsolved segment".to_owned(),
1257                    vec![args.source_range],
1258                )));
1259            };
1260            match (&unsolved0.kind, &unsolved1.kind) {
1261                (
1262                    UnsolvedSegmentKind::Point { position: pos0, .. },
1263                    UnsolvedSegmentKind::Point { position: pos1, .. },
1264                ) => {
1265                    // Both segments are points. Create a vertical distance constraint
1266                    // between them.
1267                    match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1268                        (
1269                            UnsolvedExpr::Unknown(p0_x),
1270                            UnsolvedExpr::Unknown(p0_y),
1271                            UnsolvedExpr::Unknown(p1_x),
1272                            UnsolvedExpr::Unknown(p1_y),
1273                        ) => {
1274                            // All coordinates are sketch vars. Proceed.
1275                            let sketch_constraint = SketchConstraint {
1276                                kind: SketchConstraintKind::VerticalDistance {
1277                                    points: [
1278                                        ConstrainablePoint2d {
1279                                            vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1280                                            object_id: unsolved0.object_id,
1281                                        },
1282                                        ConstrainablePoint2d {
1283                                            vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1284                                            object_id: unsolved1.object_id,
1285                                        },
1286                                    ],
1287                                },
1288                                meta: vec![args.source_range.into()],
1289                            };
1290                            Ok(KclValue::SketchConstraint {
1291                                value: Box::new(sketch_constraint),
1292                            })
1293                        }
1294                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1295                            "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
1296                                .to_owned(),
1297                            vec![args.source_range],
1298                        ))),
1299                    }
1300                }
1301                _ => Err(KclError::new_semantic(KclErrorDetails::new(
1302                    "verticalDistance() arguments must be unsolved points".to_owned(),
1303                    vec![args.source_range],
1304                ))),
1305            }
1306        }
1307        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1308            "verticalDistance() arguments must be point segments".to_owned(),
1309            vec![args.source_range],
1310        ))),
1311    }
1312}
1313
1314pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1315    let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1316        "lines",
1317        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1318        exec_state,
1319    )?;
1320    let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1321        KclError::new_semantic(KclErrorDetails::new(
1322            "must have two input lines".to_owned(),
1323            vec![args.source_range],
1324        ))
1325    })?;
1326
1327    let KclValue::Segment { value: segment0 } = &line0 else {
1328        return Err(KclError::new_semantic(KclErrorDetails::new(
1329            "line argument must be a Segment".to_owned(),
1330            vec![args.source_range],
1331        )));
1332    };
1333    let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1334        return Err(KclError::new_internal(KclErrorDetails::new(
1335            "line must be an unsolved Segment".to_owned(),
1336            vec![args.source_range],
1337        )));
1338    };
1339    let UnsolvedSegmentKind::Line {
1340        start: start0,
1341        end: end0,
1342        ..
1343    } = &unsolved0.kind
1344    else {
1345        return Err(KclError::new_semantic(KclErrorDetails::new(
1346            "line argument must be a line, no other type of Segment".to_owned(),
1347            vec![args.source_range],
1348        )));
1349    };
1350    let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1351        return Err(KclError::new_semantic(KclErrorDetails::new(
1352            "line's start x coordinate must be a var".to_owned(),
1353            vec![args.source_range],
1354        )));
1355    };
1356    let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1357        return Err(KclError::new_semantic(KclErrorDetails::new(
1358            "line's start y coordinate must be a var".to_owned(),
1359            vec![args.source_range],
1360        )));
1361    };
1362    let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1363        return Err(KclError::new_semantic(KclErrorDetails::new(
1364            "line's end x coordinate must be a var".to_owned(),
1365            vec![args.source_range],
1366        )));
1367    };
1368    let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1369        return Err(KclError::new_semantic(KclErrorDetails::new(
1370            "line's end y coordinate must be a var".to_owned(),
1371            vec![args.source_range],
1372        )));
1373    };
1374    let KclValue::Segment { value: segment1 } = &line1 else {
1375        return Err(KclError::new_semantic(KclErrorDetails::new(
1376            "line argument must be a Segment".to_owned(),
1377            vec![args.source_range],
1378        )));
1379    };
1380    let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1381        return Err(KclError::new_internal(KclErrorDetails::new(
1382            "line must be an unsolved Segment".to_owned(),
1383            vec![args.source_range],
1384        )));
1385    };
1386    let UnsolvedSegmentKind::Line {
1387        start: start1,
1388        end: end1,
1389        ..
1390    } = &unsolved1.kind
1391    else {
1392        return Err(KclError::new_semantic(KclErrorDetails::new(
1393            "line argument must be a line, no other type of Segment".to_owned(),
1394            vec![args.source_range],
1395        )));
1396    };
1397    let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1398        return Err(KclError::new_semantic(KclErrorDetails::new(
1399            "line's start x coordinate must be a var".to_owned(),
1400            vec![args.source_range],
1401        )));
1402    };
1403    let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1404        return Err(KclError::new_semantic(KclErrorDetails::new(
1405            "line's start y coordinate must be a var".to_owned(),
1406            vec![args.source_range],
1407        )));
1408    };
1409    let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1410        return Err(KclError::new_semantic(KclErrorDetails::new(
1411            "line's end x coordinate must be a var".to_owned(),
1412            vec![args.source_range],
1413        )));
1414    };
1415    let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1416        return Err(KclError::new_semantic(KclErrorDetails::new(
1417            "line's end y coordinate must be a var".to_owned(),
1418            vec![args.source_range],
1419        )));
1420    };
1421
1422    let range = args.source_range;
1423    let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1424        line0_p0_x.to_constraint_id(range)?,
1425        line0_p0_y.to_constraint_id(range)?,
1426    );
1427    let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1428        line0_p1_x.to_constraint_id(range)?,
1429        line0_p1_y.to_constraint_id(range)?,
1430    );
1431    let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1432    let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1433        line1_p0_x.to_constraint_id(range)?,
1434        line1_p0_y.to_constraint_id(range)?,
1435    );
1436    let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1437        line1_p1_x.to_constraint_id(range)?,
1438        line1_p1_y.to_constraint_id(range)?,
1439    );
1440    let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1441    let constraint = SolverConstraint::LinesEqualLength(solver_line0, solver_line1);
1442    #[cfg(feature = "artifact-graph")]
1443    let constraint_id = exec_state.next_object_id();
1444    // Save the constraint to be used for solving.
1445    let Some(sketch_state) = exec_state.sketch_block_mut() else {
1446        return Err(KclError::new_semantic(KclErrorDetails::new(
1447            "equalLength() can only be used inside a sketch block".to_owned(),
1448            vec![args.source_range],
1449        )));
1450    };
1451    sketch_state.solver_constraints.push(constraint);
1452    #[cfg(feature = "artifact-graph")]
1453    {
1454        let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
1455            lines: vec![unsolved0.object_id, unsolved1.object_id],
1456        });
1457        sketch_state.sketch_constraints.push(constraint_id);
1458        track_constraint(constraint_id, constraint, exec_state, &args);
1459    }
1460    Ok(KclValue::none())
1461}
1462
1463#[derive(Debug, Clone, Copy)]
1464pub(crate) enum LinesAtAngleKind {
1465    Parallel,
1466    Perpendicular,
1467}
1468
1469impl LinesAtAngleKind {
1470    pub fn to_function_name(self) -> &'static str {
1471        match self {
1472            LinesAtAngleKind::Parallel => "parallel",
1473            LinesAtAngleKind::Perpendicular => "perpendicular",
1474        }
1475    }
1476
1477    fn to_solver_angle(self) -> kcl_ezpz::datatypes::AngleKind {
1478        match self {
1479            LinesAtAngleKind::Parallel => kcl_ezpz::datatypes::AngleKind::Parallel,
1480            LinesAtAngleKind::Perpendicular => kcl_ezpz::datatypes::AngleKind::Perpendicular,
1481        }
1482    }
1483
1484    #[cfg(feature = "artifact-graph")]
1485    fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
1486        match self {
1487            LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
1488            LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
1489        }
1490    }
1491}
1492
1493pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1494    lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
1495}
1496pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1497    lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
1498}
1499
1500async fn lines_at_angle(
1501    angle_kind: LinesAtAngleKind,
1502    exec_state: &mut ExecState,
1503    args: Args,
1504) -> Result<KclValue, KclError> {
1505    let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1506        "lines",
1507        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1508        exec_state,
1509    )?;
1510    let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1511        KclError::new_semantic(KclErrorDetails::new(
1512            "must have two input lines".to_owned(),
1513            vec![args.source_range],
1514        ))
1515    })?;
1516
1517    let KclValue::Segment { value: segment0 } = &line0 else {
1518        return Err(KclError::new_semantic(KclErrorDetails::new(
1519            "line argument must be a Segment".to_owned(),
1520            vec![args.source_range],
1521        )));
1522    };
1523    let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1524        return Err(KclError::new_internal(KclErrorDetails::new(
1525            "line must be an unsolved Segment".to_owned(),
1526            vec![args.source_range],
1527        )));
1528    };
1529    let UnsolvedSegmentKind::Line {
1530        start: start0,
1531        end: end0,
1532        ..
1533    } = &unsolved0.kind
1534    else {
1535        return Err(KclError::new_semantic(KclErrorDetails::new(
1536            "line argument must be a line, no other type of Segment".to_owned(),
1537            vec![args.source_range],
1538        )));
1539    };
1540    let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1541        return Err(KclError::new_semantic(KclErrorDetails::new(
1542            "line's start x coordinate must be a var".to_owned(),
1543            vec![args.source_range],
1544        )));
1545    };
1546    let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1547        return Err(KclError::new_semantic(KclErrorDetails::new(
1548            "line's start y coordinate must be a var".to_owned(),
1549            vec![args.source_range],
1550        )));
1551    };
1552    let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1553        return Err(KclError::new_semantic(KclErrorDetails::new(
1554            "line's end x coordinate must be a var".to_owned(),
1555            vec![args.source_range],
1556        )));
1557    };
1558    let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1559        return Err(KclError::new_semantic(KclErrorDetails::new(
1560            "line's end y coordinate must be a var".to_owned(),
1561            vec![args.source_range],
1562        )));
1563    };
1564    let KclValue::Segment { value: segment1 } = &line1 else {
1565        return Err(KclError::new_semantic(KclErrorDetails::new(
1566            "line argument must be a Segment".to_owned(),
1567            vec![args.source_range],
1568        )));
1569    };
1570    let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1571        return Err(KclError::new_internal(KclErrorDetails::new(
1572            "line must be an unsolved Segment".to_owned(),
1573            vec![args.source_range],
1574        )));
1575    };
1576    let UnsolvedSegmentKind::Line {
1577        start: start1,
1578        end: end1,
1579        ..
1580    } = &unsolved1.kind
1581    else {
1582        return Err(KclError::new_semantic(KclErrorDetails::new(
1583            "line argument must be a line, no other type of Segment".to_owned(),
1584            vec![args.source_range],
1585        )));
1586    };
1587    let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1588        return Err(KclError::new_semantic(KclErrorDetails::new(
1589            "line's start x coordinate must be a var".to_owned(),
1590            vec![args.source_range],
1591        )));
1592    };
1593    let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1594        return Err(KclError::new_semantic(KclErrorDetails::new(
1595            "line's start y coordinate must be a var".to_owned(),
1596            vec![args.source_range],
1597        )));
1598    };
1599    let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1600        return Err(KclError::new_semantic(KclErrorDetails::new(
1601            "line's end x coordinate must be a var".to_owned(),
1602            vec![args.source_range],
1603        )));
1604    };
1605    let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1606        return Err(KclError::new_semantic(KclErrorDetails::new(
1607            "line's end y coordinate must be a var".to_owned(),
1608            vec![args.source_range],
1609        )));
1610    };
1611
1612    let range = args.source_range;
1613    let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1614        line0_p0_x.to_constraint_id(range)?,
1615        line0_p0_y.to_constraint_id(range)?,
1616    );
1617    let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1618        line0_p1_x.to_constraint_id(range)?,
1619        line0_p1_y.to_constraint_id(range)?,
1620    );
1621    let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1622    let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1623        line1_p0_x.to_constraint_id(range)?,
1624        line1_p0_y.to_constraint_id(range)?,
1625    );
1626    let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1627        line1_p1_x.to_constraint_id(range)?,
1628        line1_p1_y.to_constraint_id(range)?,
1629    );
1630    let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1631    let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
1632    #[cfg(feature = "artifact-graph")]
1633    let constraint_id = exec_state.next_object_id();
1634    // Save the constraint to be used for solving.
1635    let Some(sketch_state) = exec_state.sketch_block_mut() else {
1636        return Err(KclError::new_semantic(KclErrorDetails::new(
1637            format!(
1638                "{}() can only be used inside a sketch block",
1639                angle_kind.to_function_name()
1640            ),
1641            vec![args.source_range],
1642        )));
1643    };
1644    sketch_state.solver_constraints.push(constraint);
1645    #[cfg(feature = "artifact-graph")]
1646    {
1647        let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
1648        sketch_state.sketch_constraints.push(constraint_id);
1649        track_constraint(constraint_id, constraint, exec_state, &args);
1650    }
1651    Ok(KclValue::none())
1652}
1653
1654pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1655    let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1656    let KclValue::Segment { value: segment } = line else {
1657        return Err(KclError::new_semantic(KclErrorDetails::new(
1658            "line argument must be a Segment".to_owned(),
1659            vec![args.source_range],
1660        )));
1661    };
1662    let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1663        return Err(KclError::new_internal(KclErrorDetails::new(
1664            "line must be an unsolved Segment".to_owned(),
1665            vec![args.source_range],
1666        )));
1667    };
1668    let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1669        return Err(KclError::new_semantic(KclErrorDetails::new(
1670            "line argument must be a line, no other type of Segment".to_owned(),
1671            vec![args.source_range],
1672        )));
1673    };
1674    let p0_x = &start[0];
1675    let p0_y = &start[1];
1676    let p1_x = &end[0];
1677    let p1_y = &end[1];
1678    match (p0_x, p0_y, p1_x, p1_y) {
1679        (
1680            UnsolvedExpr::Unknown(p0_x),
1681            UnsolvedExpr::Unknown(p0_y),
1682            UnsolvedExpr::Unknown(p1_x),
1683            UnsolvedExpr::Unknown(p1_y),
1684        ) => {
1685            let range = args.source_range;
1686            let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1687                p0_x.to_constraint_id(range)?,
1688                p0_y.to_constraint_id(range)?,
1689            );
1690            let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1691                p1_x.to_constraint_id(range)?,
1692                p1_y.to_constraint_id(range)?,
1693            );
1694            let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1695            let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
1696            #[cfg(feature = "artifact-graph")]
1697            let constraint_id = exec_state.next_object_id();
1698            // Save the constraint to be used for solving.
1699            let Some(sketch_state) = exec_state.sketch_block_mut() else {
1700                return Err(KclError::new_semantic(KclErrorDetails::new(
1701                    "horizontal() can only be used inside a sketch block".to_owned(),
1702                    vec![args.source_range],
1703                )));
1704            };
1705            sketch_state.solver_constraints.push(constraint);
1706            #[cfg(feature = "artifact-graph")]
1707            {
1708                let constraint = crate::front::Constraint::Horizontal(Horizontal {
1709                    line: unsolved.object_id,
1710                });
1711                sketch_state.sketch_constraints.push(constraint_id);
1712                track_constraint(constraint_id, constraint, exec_state, &args);
1713            }
1714            Ok(KclValue::none())
1715        }
1716        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1717            "line's x and y coordinates of both start and end must be vars".to_owned(),
1718            vec![args.source_range],
1719        ))),
1720    }
1721}
1722
1723pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1724    let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1725    let KclValue::Segment { value: segment } = line else {
1726        return Err(KclError::new_semantic(KclErrorDetails::new(
1727            "line argument must be a Segment".to_owned(),
1728            vec![args.source_range],
1729        )));
1730    };
1731    let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1732        return Err(KclError::new_internal(KclErrorDetails::new(
1733            "line must be an unsolved Segment".to_owned(),
1734            vec![args.source_range],
1735        )));
1736    };
1737    let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1738        return Err(KclError::new_semantic(KclErrorDetails::new(
1739            "line argument must be a line, no other type of Segment".to_owned(),
1740            vec![args.source_range],
1741        )));
1742    };
1743    let p0_x = &start[0];
1744    let p0_y = &start[1];
1745    let p1_x = &end[0];
1746    let p1_y = &end[1];
1747    match (p0_x, p0_y, p1_x, p1_y) {
1748        (
1749            UnsolvedExpr::Unknown(p0_x),
1750            UnsolvedExpr::Unknown(p0_y),
1751            UnsolvedExpr::Unknown(p1_x),
1752            UnsolvedExpr::Unknown(p1_y),
1753        ) => {
1754            let range = args.source_range;
1755            let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1756                p0_x.to_constraint_id(range)?,
1757                p0_y.to_constraint_id(range)?,
1758            );
1759            let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1760                p1_x.to_constraint_id(range)?,
1761                p1_y.to_constraint_id(range)?,
1762            );
1763            let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1764            let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1765            #[cfg(feature = "artifact-graph")]
1766            let constraint_id = exec_state.next_object_id();
1767            // Save the constraint to be used for solving.
1768            let Some(sketch_state) = exec_state.sketch_block_mut() else {
1769                return Err(KclError::new_semantic(KclErrorDetails::new(
1770                    "vertical() can only be used inside a sketch block".to_owned(),
1771                    vec![args.source_range],
1772                )));
1773            };
1774            sketch_state.solver_constraints.push(constraint);
1775            #[cfg(feature = "artifact-graph")]
1776            {
1777                let constraint = crate::front::Constraint::Vertical(Vertical {
1778                    line: unsolved.object_id,
1779                });
1780                sketch_state.sketch_constraints.push(constraint_id);
1781                track_constraint(constraint_id, constraint, exec_state, &args);
1782            }
1783            Ok(KclValue::none())
1784        }
1785        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1786            "line's x and y coordinates of both start and end must be vars".to_owned(),
1787            vec![args.source_range],
1788        ))),
1789    }
1790}