kcl_lib/std/
constraints.rs

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