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