kcl_lib/std/
constraints.rs

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