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