kcl_lib/std/
constraints.rs

1use anyhow::Result;
2
3use crate::{
4    errors::{KclError, KclErrorDetails},
5    execution::{
6        AbstractSegment, ConstrainablePoint2d, ExecState, KclValue, SegmentRepr, SketchConstraint,
7        SketchConstraintKind, SketchVarId, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind,
8        normalize_to_solver_unit,
9        types::{ArrayLen, PrimitiveType, RuntimeType},
10    },
11    front::{LineCtor, Point2d, PointCtor},
12    std::Args,
13};
14#[cfg(feature = "artifact-graph")]
15use crate::{
16    execution::ArtifactId,
17    front::{Coincident, Constraint, Horizontal, LinesEqualLength, Object, ObjectId, ObjectKind, Parallel, Vertical},
18};
19
20pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21    let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
22    if at.len() != 2 {
23        return Err(KclError::new_semantic(KclErrorDetails::new(
24            "at must be a 2D point".to_owned(),
25            vec![args.source_range],
26        )));
27    }
28    let at_x_value = at.first().unwrap().clone();
29    let Some(at_x) = at_x_value.as_unsolved_expr() else {
30        return Err(KclError::new_semantic(KclErrorDetails::new(
31            "at x must be a number or sketch var".to_owned(),
32            vec![args.source_range],
33        )));
34    };
35    let at_y_value = at.get(1).unwrap().clone();
36    let Some(at_y) = at_y_value.as_unsolved_expr() else {
37        return Err(KclError::new_semantic(KclErrorDetails::new(
38            "at y must be a number or sketch var".to_owned(),
39            vec![args.source_range],
40        )));
41    };
42    let ctor = PointCtor {
43        position: Point2d {
44            x: at_x_value.to_sketch_expr().ok_or_else(|| {
45                KclError::new_semantic(KclErrorDetails::new(
46                    "unable to convert numeric type to suffix".to_owned(),
47                    vec![args.source_range],
48                ))
49            })?,
50            y: at_y_value.to_sketch_expr().ok_or_else(|| {
51                KclError::new_semantic(KclErrorDetails::new(
52                    "unable to convert numeric type to suffix".to_owned(),
53                    vec![args.source_range],
54                ))
55            })?,
56        },
57    };
58    let segment = UnsolvedSegment {
59        object_id: exec_state.next_object_id(),
60        kind: UnsolvedSegmentKind::Point {
61            position: [at_x, at_y],
62            ctor: Box::new(ctor),
63        },
64        meta: vec![args.source_range.into()],
65    };
66    #[cfg(feature = "artifact-graph")]
67    let optional_constraints = {
68        let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
69
70        let mut optional_constraints = Vec::new();
71        if exec_state.segment_ids_edited_contains(&object_id) {
72            if let Some(at_x_var) = at_x_value.as_sketch_var() {
73                let x_initial_value = at_x_var.initial_value_to_solver_units(
74                    exec_state,
75                    args.source_range,
76                    "edited segment fixed constraint value",
77                )?;
78                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
79                    at_x_var.id.to_constraint_id(args.source_range)?,
80                    x_initial_value.n,
81                ));
82            }
83            if let Some(at_y_var) = at_y_value.as_sketch_var() {
84                let y_initial_value = at_y_var.initial_value_to_solver_units(
85                    exec_state,
86                    args.source_range,
87                    "edited segment fixed constraint value",
88                )?;
89                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
90                    at_y_var.id.to_constraint_id(args.source_range)?,
91                    y_initial_value.n,
92                ));
93            }
94        }
95        optional_constraints
96    };
97
98    // Save the segment to be sent to the engine after solving.
99    let Some(sketch_state) = exec_state.sketch_block_mut() else {
100        return Err(KclError::new_semantic(KclErrorDetails::new(
101            "line() can only be used inside a sketch block".to_owned(),
102            vec![args.source_range],
103        )));
104    };
105    sketch_state.needed_by_engine.push(segment.clone());
106
107    #[cfg(feature = "artifact-graph")]
108    sketch_state.solver_optional_constraints.extend(optional_constraints);
109
110    let meta = segment.meta.clone();
111    let abstract_segment = AbstractSegment {
112        repr: SegmentRepr::Unsolved { segment },
113        meta,
114    };
115    Ok(KclValue::Segment {
116        value: Box::new(abstract_segment),
117    })
118}
119
120pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
121    let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
122    // TODO: make this optional and add midpoint.
123    let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
124    if start.len() != 2 {
125        return Err(KclError::new_semantic(KclErrorDetails::new(
126            "start must be a 2D point".to_owned(),
127            vec![args.source_range],
128        )));
129    }
130    if end.len() != 2 {
131        return Err(KclError::new_semantic(KclErrorDetails::new(
132            "end must be a 2D point".to_owned(),
133            vec![args.source_range],
134        )));
135    }
136    // SAFETY: checked length above.
137    let start_x_value = start.first().unwrap().clone();
138    let Some(start_x) = start_x_value.as_unsolved_expr() else {
139        return Err(KclError::new_semantic(KclErrorDetails::new(
140            "start x must be a number or sketch var".to_owned(),
141            vec![args.source_range],
142        )));
143    };
144    let start_y_value = start.get(1).unwrap().clone();
145    let Some(start_y) = start_y_value.as_unsolved_expr() else {
146        return Err(KclError::new_semantic(KclErrorDetails::new(
147            "start y must be a number or sketch var".to_owned(),
148            vec![args.source_range],
149        )));
150    };
151    let end_x_value = end.first().unwrap().clone();
152    let Some(end_x) = end_x_value.as_unsolved_expr() else {
153        return Err(KclError::new_semantic(KclErrorDetails::new(
154            "end x must be a number or sketch var".to_owned(),
155            vec![args.source_range],
156        )));
157    };
158    let end_y_value = end.get(1).unwrap().clone();
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        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            if let Some(start_x_var) = start_x_value.as_sketch_var() {
219                let x_initial_value = start_x_var.initial_value_to_solver_units(
220                    exec_state,
221                    args.source_range,
222                    "edited segment fixed constraint value",
223                )?;
224                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
225                    start_x_var.id.to_constraint_id(args.source_range)?,
226                    x_initial_value.n,
227                ));
228            }
229            if let Some(start_y_var) = start_y_value.as_sketch_var() {
230                let y_initial_value = start_y_var.initial_value_to_solver_units(
231                    exec_state,
232                    args.source_range,
233                    "edited segment fixed constraint value",
234                )?;
235                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
236                    start_y_var.id.to_constraint_id(args.source_range)?,
237                    y_initial_value.n,
238                ));
239            }
240        }
241        if exec_state.segment_ids_edited_contains(&end_object_id) {
242            if let Some(end_x_var) = end_x_value.as_sketch_var() {
243                let x_initial_value = end_x_var.initial_value_to_solver_units(
244                    exec_state,
245                    args.source_range,
246                    "edited segment fixed constraint value",
247                )?;
248                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
249                    end_x_var.id.to_constraint_id(args.source_range)?,
250                    x_initial_value.n,
251                ));
252            }
253            if let Some(end_y_var) = end_y_value.as_sketch_var() {
254                let y_initial_value = end_y_var.initial_value_to_solver_units(
255                    exec_state,
256                    args.source_range,
257                    "edited segment fixed constraint value",
258                )?;
259                optional_constraints.push(kcl_ezpz::Constraint::Fixed(
260                    end_y_var.id.to_constraint_id(args.source_range)?,
261                    y_initial_value.n,
262                ));
263            }
264        }
265        optional_constraints
266    };
267
268    // Save the segment to be sent to the engine after solving.
269    let Some(sketch_state) = exec_state.sketch_block_mut() else {
270        return Err(KclError::new_semantic(KclErrorDetails::new(
271            "line() can only be used inside a sketch block".to_owned(),
272            vec![args.source_range],
273        )));
274    };
275    sketch_state.needed_by_engine.push(segment.clone());
276
277    #[cfg(feature = "artifact-graph")]
278    sketch_state.solver_optional_constraints.extend(optional_constraints);
279
280    let meta = segment.meta.clone();
281    let abstract_segment = AbstractSegment {
282        repr: SegmentRepr::Unsolved { segment },
283        meta,
284    };
285    Ok(KclValue::Segment {
286        value: Box::new(abstract_segment),
287    })
288}
289
290pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
291    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
292        "points",
293        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
294        exec_state,
295    )?;
296    if points.len() != 2 {
297        return Err(KclError::new_semantic(KclErrorDetails::new(
298            "must have two input points".to_owned(),
299            vec![args.source_range],
300        )));
301    }
302
303    let range = args.source_range;
304    match (&points[0], &points[1]) {
305        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
306            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
307                return Err(KclError::new_semantic(KclErrorDetails::new(
308                    "first point must be an unsolved segment".to_owned(),
309                    vec![args.source_range],
310                )));
311            };
312            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
313                return Err(KclError::new_semantic(KclErrorDetails::new(
314                    "second point must be an unsolved segment".to_owned(),
315                    vec![args.source_range],
316                )));
317            };
318            match (&unsolved0.kind, &unsolved1.kind) {
319                (
320                    UnsolvedSegmentKind::Point { position: pos0, .. },
321                    UnsolvedSegmentKind::Point { position: pos1, .. },
322                ) => {
323                    let p0_x = &pos0[0];
324                    let p0_y = &pos0[1];
325                    match (p0_x, p0_y) {
326                        (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
327                            let p1_x = &pos1[0];
328                            let p1_y = &pos1[1];
329                            match (p1_x, p1_y) {
330                                (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
331                                    let constraint = kcl_ezpz::Constraint::PointsCoincident(
332                                        kcl_ezpz::datatypes::DatumPoint::new_xy(
333                                            p0_x.to_constraint_id(range)?,
334                                            p0_y.to_constraint_id(range)?,
335                                        ),
336                                        kcl_ezpz::datatypes::DatumPoint::new_xy(
337                                            p1_x.to_constraint_id(range)?,
338                                            p1_y.to_constraint_id(range)?,
339                                        ),
340                                    );
341                                    #[cfg(feature = "artifact-graph")]
342                                    let constraint_id = exec_state.next_object_id();
343                                    // Save the constraint to be used for solving.
344                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
345                                        return Err(KclError::new_semantic(KclErrorDetails::new(
346                                            "coincident() can only be used inside a sketch block".to_owned(),
347                                            vec![args.source_range],
348                                        )));
349                                    };
350                                    sketch_state.solver_constraints.push(constraint);
351                                    #[cfg(feature = "artifact-graph")]
352                                    {
353                                        let constraint = crate::front::Constraint::Coincident(Coincident {
354                                            points: vec![unsolved0.object_id, unsolved1.object_id],
355                                        });
356                                        sketch_state.sketch_constraints.push(constraint_id);
357                                        track_constraint(constraint_id, constraint, exec_state, &args);
358                                    }
359                                    Ok(KclValue::none())
360                                }
361                                (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
362                                    let p1_x = KclValue::Number {
363                                        value: p1_x.n,
364                                        ty: p1_x.ty,
365                                        meta: vec![args.source_range.into()],
366                                    };
367                                    let p1_y = KclValue::Number {
368                                        value: p1_y.n,
369                                        ty: p1_y.ty,
370                                        meta: vec![args.source_range.into()],
371                                    };
372                                    let (constraint_x, constraint_y) =
373                                        coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
374
375                                    #[cfg(feature = "artifact-graph")]
376                                    let constraint_id = exec_state.next_object_id();
377                                    // Save the constraint to be used for solving.
378                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
379                                        return Err(KclError::new_semantic(KclErrorDetails::new(
380                                            "coincident() can only be used inside a sketch block".to_owned(),
381                                            vec![args.source_range],
382                                        )));
383                                    };
384                                    sketch_state.solver_constraints.push(constraint_x);
385                                    sketch_state.solver_constraints.push(constraint_y);
386                                    #[cfg(feature = "artifact-graph")]
387                                    {
388                                        let constraint = crate::front::Constraint::Coincident(Coincident {
389                                            points: vec![unsolved0.object_id, unsolved1.object_id],
390                                        });
391                                        sketch_state.sketch_constraints.push(constraint_id);
392                                        track_constraint(constraint_id, constraint, exec_state, &args);
393                                    }
394                                    Ok(KclValue::none())
395                                }
396                                (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
397                                | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
398                                    // TODO: sketch-api: unimplemented
399                                    Err(KclError::new_semantic(KclErrorDetails::new(
400                                        "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(),
401                                        vec![args.source_range],
402                                    )))
403                                }
404                            }
405                        }
406                        (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
407                            let p1_x = &pos1[0];
408                            let p1_y = &pos1[1];
409                            match (p1_x, p1_y) {
410                                (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
411                                    let p0_x = KclValue::Number {
412                                        value: p0_x.n,
413                                        ty: p0_x.ty,
414                                        meta: vec![args.source_range.into()],
415                                    };
416                                    let p0_y = KclValue::Number {
417                                        value: p0_y.n,
418                                        ty: p0_y.ty,
419                                        meta: vec![args.source_range.into()],
420                                    };
421                                    let (constraint_x, constraint_y) =
422                                        coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
423
424                                    #[cfg(feature = "artifact-graph")]
425                                    let constraint_id = exec_state.next_object_id();
426                                    // Save the constraint to be used for solving.
427                                    let Some(sketch_state) = exec_state.sketch_block_mut() else {
428                                        return Err(KclError::new_semantic(KclErrorDetails::new(
429                                            "coincident() can only be used inside a sketch block".to_owned(),
430                                            vec![args.source_range],
431                                        )));
432                                    };
433                                    sketch_state.solver_constraints.push(constraint_x);
434                                    sketch_state.solver_constraints.push(constraint_y);
435                                    #[cfg(feature = "artifact-graph")]
436                                    {
437                                        let constraint = crate::front::Constraint::Coincident(Coincident {
438                                            points: vec![unsolved0.object_id, unsolved1.object_id],
439                                        });
440                                        sketch_state.sketch_constraints.push(constraint_id);
441                                        track_constraint(constraint_id, constraint, exec_state, &args);
442                                    }
443                                    Ok(KclValue::none())
444                                }
445                                (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
446                                    if *p0_x != *p1_x || *p0_y != *p1_y {
447                                        return Err(KclError::new_semantic(KclErrorDetails::new(
448                                            "Coincident constraint between two fixed points failed since coordinates differ"
449                                                .to_owned(),
450                                            vec![args.source_range],
451                                        )));
452                                    }
453                                    Ok(KclValue::none())
454                                }
455                                (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
456                                | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
457                                    // TODO: sketch-api: unimplemented
458                                    Err(KclError::new_semantic(KclErrorDetails::new(
459                                        "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(),
460                                        vec![args.source_range],
461                                    )))
462                                }
463                            }
464                        }
465                        (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
466                        | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
467                            // The segment is a point with one sketch var.
468                            Err(KclError::new_semantic(KclErrorDetails::new(
469                                "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(),
470                                vec![args.source_range],
471                            )))
472                        }
473                    }
474                }
475                _ => Err(KclError::new_semantic(KclErrorDetails::new(
476                    format!(
477                        "both inputs of coincident must be points; found {:?} and {:?}",
478                        &unsolved0.kind, &unsolved1.kind
479                    ),
480                    vec![args.source_range],
481                ))),
482            }
483        }
484        _ => Err(KclError::new_semantic(KclErrorDetails::new(
485            "All inputs must be points, created from point(), line(), or another sketch function".to_owned(),
486            vec![args.source_range],
487        ))),
488    }
489}
490
491#[cfg(feature = "artifact-graph")]
492fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
493    exec_state.add_scene_object(
494        Object {
495            id: constraint_id,
496            kind: ObjectKind::Constraint { constraint },
497            label: Default::default(),
498            comments: Default::default(),
499            artifact_id: ArtifactId::constraint(),
500            source: args.source_range.into(),
501        },
502        args.source_range,
503    );
504}
505
506/// Order of points has been erased when calling this function.
507fn coincident_constraints_fixed(
508    p0_x: SketchVarId,
509    p0_y: SketchVarId,
510    p1_x: &KclValue,
511    p1_y: &KclValue,
512    exec_state: &mut ExecState,
513    args: &Args,
514) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
515    let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
516    let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
517    let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
518        let message = format!(
519            "Expected number after coercion, but found {}",
520            p1_x_number_value.human_friendly_type()
521        );
522        debug_assert!(false, "{}", &message);
523        return Err(KclError::new_internal(KclErrorDetails::new(
524            message,
525            vec![args.source_range],
526        )));
527    };
528    let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
529        let message = format!(
530            "Expected number after coercion, but found {}",
531            p1_y_number_value.human_friendly_type()
532        );
533        debug_assert!(false, "{}", &message);
534        return Err(KclError::new_internal(KclErrorDetails::new(
535            message,
536            vec![args.source_range],
537        )));
538    };
539    let constraint_x = kcl_ezpz::Constraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
540    let constraint_y = kcl_ezpz::Constraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
541    Ok((constraint_x, constraint_y))
542}
543
544pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
545    let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
546        "points",
547        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
548        exec_state,
549    )?;
550    if points.len() != 2 {
551        return Err(KclError::new_semantic(KclErrorDetails::new(
552            "must have two input points".to_owned(),
553            vec![args.source_range],
554        )));
555    }
556
557    match (&points[0], &points[1]) {
558        (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
559            let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
560                return Err(KclError::new_semantic(KclErrorDetails::new(
561                    "first point must be an unsolved segment".to_owned(),
562                    vec![args.source_range],
563                )));
564            };
565            let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
566                return Err(KclError::new_semantic(KclErrorDetails::new(
567                    "second point must be an unsolved segment".to_owned(),
568                    vec![args.source_range],
569                )));
570            };
571            match (&unsolved0.kind, &unsolved1.kind) {
572                (
573                    UnsolvedSegmentKind::Point { position: pos0, .. },
574                    UnsolvedSegmentKind::Point { position: pos1, .. },
575                ) => {
576                    // Both segments are points. Create a distance constraint
577                    // between them.
578                    match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
579                        (
580                            UnsolvedExpr::Unknown(p0_x),
581                            UnsolvedExpr::Unknown(p0_y),
582                            UnsolvedExpr::Unknown(p1_x),
583                            UnsolvedExpr::Unknown(p1_y),
584                        ) => {
585                            // All coordinates are sketch vars. Proceed.
586                            let sketch_constraint = SketchConstraint {
587                                kind: SketchConstraintKind::Distance {
588                                    points: [
589                                        ConstrainablePoint2d {
590                                            vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
591                                            object_id: unsolved0.object_id,
592                                        },
593                                        ConstrainablePoint2d {
594                                            vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
595                                            object_id: unsolved1.object_id,
596                                        },
597                                    ],
598                                },
599                                meta: vec![args.source_range.into()],
600                            };
601                            Ok(KclValue::SketchConstraint {
602                                value: Box::new(sketch_constraint),
603                            })
604                        }
605                        _ => Err(KclError::new_semantic(KclErrorDetails::new(
606                            "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
607                            vec![args.source_range],
608                        ))),
609                    }
610                }
611                _ => Err(KclError::new_semantic(KclErrorDetails::new(
612                    "distance() arguments must be unsolved points".to_owned(),
613                    vec![args.source_range],
614                ))),
615            }
616        }
617        _ => Err(KclError::new_semantic(KclErrorDetails::new(
618            "distance() arguments must be point segments".to_owned(),
619            vec![args.source_range],
620        ))),
621    }
622}
623
624pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
625    let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
626        "lines",
627        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
628        exec_state,
629    )?;
630    if lines.len() != 2 {
631        return Err(KclError::new_semantic(KclErrorDetails::new(
632            "must have two input lines".to_owned(),
633            vec![args.source_range],
634        )));
635    }
636
637    let KclValue::Segment { value: segment0 } = &lines[0] else {
638        return Err(KclError::new_semantic(KclErrorDetails::new(
639            "line argument must be a Segment".to_owned(),
640            vec![args.source_range],
641        )));
642    };
643    let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
644        return Err(KclError::new_internal(KclErrorDetails::new(
645            "line must be an unsolved Segment".to_owned(),
646            vec![args.source_range],
647        )));
648    };
649    let UnsolvedSegmentKind::Line {
650        start: start0,
651        end: end0,
652        ..
653    } = &unsolved0.kind
654    else {
655        return Err(KclError::new_semantic(KclErrorDetails::new(
656            "line argument must be a line, no other type of Segment".to_owned(),
657            vec![args.source_range],
658        )));
659    };
660    let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
661        return Err(KclError::new_semantic(KclErrorDetails::new(
662            "line's start x coordinate must be a var".to_owned(),
663            vec![args.source_range],
664        )));
665    };
666    let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
667        return Err(KclError::new_semantic(KclErrorDetails::new(
668            "line's start y coordinate must be a var".to_owned(),
669            vec![args.source_range],
670        )));
671    };
672    let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
673        return Err(KclError::new_semantic(KclErrorDetails::new(
674            "line's end x coordinate must be a var".to_owned(),
675            vec![args.source_range],
676        )));
677    };
678    let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
679        return Err(KclError::new_semantic(KclErrorDetails::new(
680            "line's end y coordinate must be a var".to_owned(),
681            vec![args.source_range],
682        )));
683    };
684    let KclValue::Segment { value: segment1 } = &lines[1] else {
685        return Err(KclError::new_semantic(KclErrorDetails::new(
686            "line argument must be a Segment".to_owned(),
687            vec![args.source_range],
688        )));
689    };
690    let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
691        return Err(KclError::new_internal(KclErrorDetails::new(
692            "line must be an unsolved Segment".to_owned(),
693            vec![args.source_range],
694        )));
695    };
696    let UnsolvedSegmentKind::Line {
697        start: start1,
698        end: end1,
699        ..
700    } = &unsolved1.kind
701    else {
702        return Err(KclError::new_semantic(KclErrorDetails::new(
703            "line argument must be a line, no other type of Segment".to_owned(),
704            vec![args.source_range],
705        )));
706    };
707    let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
708        return Err(KclError::new_semantic(KclErrorDetails::new(
709            "line's start x coordinate must be a var".to_owned(),
710            vec![args.source_range],
711        )));
712    };
713    let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
714        return Err(KclError::new_semantic(KclErrorDetails::new(
715            "line's start y coordinate must be a var".to_owned(),
716            vec![args.source_range],
717        )));
718    };
719    let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
720        return Err(KclError::new_semantic(KclErrorDetails::new(
721            "line's end x coordinate must be a var".to_owned(),
722            vec![args.source_range],
723        )));
724    };
725    let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
726        return Err(KclError::new_semantic(KclErrorDetails::new(
727            "line's end y coordinate must be a var".to_owned(),
728            vec![args.source_range],
729        )));
730    };
731
732    let range = args.source_range;
733    let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
734        line0_p0_x.to_constraint_id(range)?,
735        line0_p0_y.to_constraint_id(range)?,
736    );
737    let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
738        line0_p1_x.to_constraint_id(range)?,
739        line0_p1_y.to_constraint_id(range)?,
740    );
741    let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
742    let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
743        line1_p0_x.to_constraint_id(range)?,
744        line1_p0_y.to_constraint_id(range)?,
745    );
746    let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
747        line1_p1_x.to_constraint_id(range)?,
748        line1_p1_y.to_constraint_id(range)?,
749    );
750    let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
751    let constraint = kcl_ezpz::Constraint::LinesEqualLength(solver_line0, solver_line1);
752    #[cfg(feature = "artifact-graph")]
753    let constraint_id = exec_state.next_object_id();
754    // Save the constraint to be used for solving.
755    let Some(sketch_state) = exec_state.sketch_block_mut() else {
756        return Err(KclError::new_semantic(KclErrorDetails::new(
757            "equalLength() can only be used inside a sketch block".to_owned(),
758            vec![args.source_range],
759        )));
760    };
761    sketch_state.solver_constraints.push(constraint);
762    #[cfg(feature = "artifact-graph")]
763    {
764        let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
765            lines: vec![unsolved0.object_id, unsolved1.object_id],
766        });
767        sketch_state.sketch_constraints.push(constraint_id);
768        track_constraint(constraint_id, constraint, exec_state, &args);
769    }
770    Ok(KclValue::none())
771}
772
773pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
774    let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
775        "lines",
776        &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
777        exec_state,
778    )?;
779    if lines.len() != 2 {
780        return Err(KclError::new_semantic(KclErrorDetails::new(
781            "must have two input lines".to_owned(),
782            vec![args.source_range],
783        )));
784    }
785
786    let KclValue::Segment { value: segment0 } = &lines[0] else {
787        return Err(KclError::new_semantic(KclErrorDetails::new(
788            "line argument must be a Segment".to_owned(),
789            vec![args.source_range],
790        )));
791    };
792    let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
793        return Err(KclError::new_internal(KclErrorDetails::new(
794            "line must be an unsolved Segment".to_owned(),
795            vec![args.source_range],
796        )));
797    };
798    let UnsolvedSegmentKind::Line {
799        start: start0,
800        end: end0,
801        ..
802    } = &unsolved0.kind
803    else {
804        return Err(KclError::new_semantic(KclErrorDetails::new(
805            "line argument must be a line, no other type of Segment".to_owned(),
806            vec![args.source_range],
807        )));
808    };
809    let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
810        return Err(KclError::new_semantic(KclErrorDetails::new(
811            "line's start x coordinate must be a var".to_owned(),
812            vec![args.source_range],
813        )));
814    };
815    let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
816        return Err(KclError::new_semantic(KclErrorDetails::new(
817            "line's start y coordinate must be a var".to_owned(),
818            vec![args.source_range],
819        )));
820    };
821    let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
822        return Err(KclError::new_semantic(KclErrorDetails::new(
823            "line's end x coordinate must be a var".to_owned(),
824            vec![args.source_range],
825        )));
826    };
827    let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
828        return Err(KclError::new_semantic(KclErrorDetails::new(
829            "line's end y coordinate must be a var".to_owned(),
830            vec![args.source_range],
831        )));
832    };
833    let KclValue::Segment { value: segment1 } = &lines[1] else {
834        return Err(KclError::new_semantic(KclErrorDetails::new(
835            "line argument must be a Segment".to_owned(),
836            vec![args.source_range],
837        )));
838    };
839    let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
840        return Err(KclError::new_internal(KclErrorDetails::new(
841            "line must be an unsolved Segment".to_owned(),
842            vec![args.source_range],
843        )));
844    };
845    let UnsolvedSegmentKind::Line {
846        start: start1,
847        end: end1,
848        ..
849    } = &unsolved1.kind
850    else {
851        return Err(KclError::new_semantic(KclErrorDetails::new(
852            "line argument must be a line, no other type of Segment".to_owned(),
853            vec![args.source_range],
854        )));
855    };
856    let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
857        return Err(KclError::new_semantic(KclErrorDetails::new(
858            "line's start x coordinate must be a var".to_owned(),
859            vec![args.source_range],
860        )));
861    };
862    let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
863        return Err(KclError::new_semantic(KclErrorDetails::new(
864            "line's start y coordinate must be a var".to_owned(),
865            vec![args.source_range],
866        )));
867    };
868    let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
869        return Err(KclError::new_semantic(KclErrorDetails::new(
870            "line's end x coordinate must be a var".to_owned(),
871            vec![args.source_range],
872        )));
873    };
874    let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
875        return Err(KclError::new_semantic(KclErrorDetails::new(
876            "line's end y coordinate must be a var".to_owned(),
877            vec![args.source_range],
878        )));
879    };
880
881    let range = args.source_range;
882    let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
883        line0_p0_x.to_constraint_id(range)?,
884        line0_p0_y.to_constraint_id(range)?,
885    );
886    let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
887        line0_p1_x.to_constraint_id(range)?,
888        line0_p1_y.to_constraint_id(range)?,
889    );
890    let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
891    let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
892        line1_p0_x.to_constraint_id(range)?,
893        line1_p0_y.to_constraint_id(range)?,
894    );
895    let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
896        line1_p1_x.to_constraint_id(range)?,
897        line1_p1_y.to_constraint_id(range)?,
898    );
899    let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
900    let constraint =
901        kcl_ezpz::Constraint::LinesAtAngle(solver_line0, solver_line1, kcl_ezpz::datatypes::AngleKind::Parallel);
902    #[cfg(feature = "artifact-graph")]
903    let constraint_id = exec_state.next_object_id();
904    // Save the constraint to be used for solving.
905    let Some(sketch_state) = exec_state.sketch_block_mut() else {
906        return Err(KclError::new_semantic(KclErrorDetails::new(
907            "equalLength() can only be used inside a sketch block".to_owned(),
908            vec![args.source_range],
909        )));
910    };
911    sketch_state.solver_constraints.push(constraint);
912    #[cfg(feature = "artifact-graph")]
913    {
914        let constraint = crate::front::Constraint::Parallel(Parallel {
915            lines: vec![unsolved0.object_id, unsolved1.object_id],
916        });
917        sketch_state.sketch_constraints.push(constraint_id);
918        track_constraint(constraint_id, constraint, exec_state, &args);
919    }
920    Ok(KclValue::none())
921}
922
923pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
924    let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
925    let KclValue::Segment { value: segment } = line else {
926        return Err(KclError::new_semantic(KclErrorDetails::new(
927            "line argument must be a Segment".to_owned(),
928            vec![args.source_range],
929        )));
930    };
931    let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
932        return Err(KclError::new_internal(KclErrorDetails::new(
933            "line must be an unsolved Segment".to_owned(),
934            vec![args.source_range],
935        )));
936    };
937    let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
938        return Err(KclError::new_semantic(KclErrorDetails::new(
939            "line argument must be a line, no other type of Segment".to_owned(),
940            vec![args.source_range],
941        )));
942    };
943    let p0_x = &start[0];
944    let p0_y = &start[1];
945    let p1_x = &end[0];
946    let p1_y = &end[1];
947    match (p0_x, p0_y, p1_x, p1_y) {
948        (
949            UnsolvedExpr::Unknown(p0_x),
950            UnsolvedExpr::Unknown(p0_y),
951            UnsolvedExpr::Unknown(p1_x),
952            UnsolvedExpr::Unknown(p1_y),
953        ) => {
954            let range = args.source_range;
955            let solver_p0 =
956                kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
957            let solver_p1 =
958                kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
959            let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
960            let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
961            #[cfg(feature = "artifact-graph")]
962            let constraint_id = exec_state.next_object_id();
963            // Save the constraint to be used for solving.
964            let Some(sketch_state) = exec_state.sketch_block_mut() else {
965                return Err(KclError::new_semantic(KclErrorDetails::new(
966                    "horizontal() can only be used inside a sketch block".to_owned(),
967                    vec![args.source_range],
968                )));
969            };
970            sketch_state.solver_constraints.push(constraint);
971            #[cfg(feature = "artifact-graph")]
972            {
973                let constraint = crate::front::Constraint::Horizontal(Horizontal {
974                    line: unsolved.object_id,
975                });
976                sketch_state.sketch_constraints.push(constraint_id);
977                track_constraint(constraint_id, constraint, exec_state, &args);
978            }
979            Ok(KclValue::none())
980        }
981        _ => Err(KclError::new_semantic(KclErrorDetails::new(
982            "line's x and y coordinates of both start and end must be vars".to_owned(),
983            vec![args.source_range],
984        ))),
985    }
986}
987
988pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
989    let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
990    let KclValue::Segment { value: segment } = line else {
991        return Err(KclError::new_semantic(KclErrorDetails::new(
992            "line argument must be a Segment".to_owned(),
993            vec![args.source_range],
994        )));
995    };
996    let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
997        return Err(KclError::new_internal(KclErrorDetails::new(
998            "line must be an unsolved Segment".to_owned(),
999            vec![args.source_range],
1000        )));
1001    };
1002    let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1003        return Err(KclError::new_semantic(KclErrorDetails::new(
1004            "line argument must be a line, no other type of Segment".to_owned(),
1005            vec![args.source_range],
1006        )));
1007    };
1008    let p0_x = &start[0];
1009    let p0_y = &start[1];
1010    let p1_x = &end[0];
1011    let p1_y = &end[1];
1012    match (p0_x, p0_y, p1_x, p1_y) {
1013        (
1014            UnsolvedExpr::Unknown(p0_x),
1015            UnsolvedExpr::Unknown(p0_y),
1016            UnsolvedExpr::Unknown(p1_x),
1017            UnsolvedExpr::Unknown(p1_y),
1018        ) => {
1019            let range = args.source_range;
1020            let solver_p0 =
1021                kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
1022            let solver_p1 =
1023                kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
1024            let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
1025            let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1026            #[cfg(feature = "artifact-graph")]
1027            let constraint_id = exec_state.next_object_id();
1028            // Save the constraint to be used for solving.
1029            let Some(sketch_state) = exec_state.sketch_block_mut() else {
1030                return Err(KclError::new_semantic(KclErrorDetails::new(
1031                    "vertical() can only be used inside a sketch block".to_owned(),
1032                    vec![args.source_range],
1033                )));
1034            };
1035            sketch_state.solver_constraints.push(constraint);
1036            #[cfg(feature = "artifact-graph")]
1037            {
1038                let constraint = crate::front::Constraint::Vertical(Vertical {
1039                    line: unsolved.object_id,
1040                });
1041                sketch_state.sketch_constraints.push(constraint_id);
1042                track_constraint(constraint_id, constraint, exec_state, &args);
1043            }
1044            Ok(KclValue::none())
1045        }
1046        _ => Err(KclError::new_semantic(KclErrorDetails::new(
1047            "line's x and y coordinates of both start and end must be vars".to_owned(),
1048            vec![args.source_range],
1049        ))),
1050    }
1051}