1use anyhow::Result;
2use ezpz::CircleSide;
3use ezpz::Constraint as SolverConstraint;
4use ezpz::LineSide;
5use ezpz::datatypes::AngleKind;
6use ezpz::datatypes::inputs::DatumCircle;
7use ezpz::datatypes::inputs::DatumCircularArc;
8use ezpz::datatypes::inputs::DatumDistance;
9use ezpz::datatypes::inputs::DatumLineSegment;
10use ezpz::datatypes::inputs::DatumPoint;
11use kcl_api::UnitAngle;
12use kcl_api::UnitLength;
13use kittycad_modeling_cmds as kcmc;
14
15use crate::errors::KclError;
16use crate::errors::KclErrorDetails;
17use crate::execution::AbstractSegment;
18use crate::execution::Artifact;
19use crate::execution::CodeRef;
20use crate::execution::ConstrainableLine2d;
21use crate::execution::ConstrainablePoint2d;
22use crate::execution::ConstrainablePoint2dOrOrigin;
23use crate::execution::ConstraintKey;
24use crate::execution::ConstraintState;
25use crate::execution::ExecState;
26use crate::execution::KclValue;
27use crate::execution::SegmentRepr;
28use crate::execution::SketchBlockConstraint;
29use crate::execution::SketchBlockConstraintType;
30use crate::execution::SketchConstraint;
31use crate::execution::SketchConstraintKind;
32use crate::execution::SketchVarId;
33use crate::execution::TangencyMode;
34use crate::execution::UnsolvedExpr;
35use crate::execution::UnsolvedSegment;
36use crate::execution::UnsolvedSegmentKind;
37use crate::execution::normalize_to_solver_distance_unit;
38use crate::execution::solver_numeric_type;
39use crate::execution::types::ArrayLen;
40use crate::execution::types::NumericType;
41use crate::execution::types::NumericTypeExt;
42use crate::execution::types::PrimitiveType;
43use crate::execution::types::RuntimeType;
44use crate::execution::types::UnitType;
45use crate::front::ArcCtor;
46use crate::front::CircleCtor;
47use crate::front::Coincident;
48use crate::front::Constraint;
49use crate::front::ControlPointSplineCtor;
50use crate::front::EqualRadius;
51use crate::front::Horizontal;
52use crate::front::LineCtor;
53use crate::front::LinesEqualLength;
54use crate::front::Midpoint;
55use crate::front::Number;
56use crate::front::Object;
57use crate::front::ObjectId;
58use crate::front::ObjectKind;
59use crate::front::Parallel;
60use crate::front::Perpendicular;
61use crate::front::Point2d;
62use crate::front::PointCtor;
63use crate::front::SourceRef;
64use crate::front::Symmetric;
65use crate::front::Tangent;
66use crate::front::Vertical;
67use crate::frontend::sketch::ConstraintSegment;
68use crate::std::Args;
69use crate::std::args::FromKclValue;
70use crate::std::args::TyF64;
71
72fn point2d_is_origin(point2d: &KclValue) -> bool {
73 let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point2d) else {
74 return false;
75 };
76 if x.ty.as_length().is_none() || y.ty.as_length().is_none() {
79 return false;
80 }
81 x.n == 0.0 && y.n == 0.0
84}
85
86fn numeric_suffix_to_type(suffix: crate::pretty::NumericSuffix, exec_state: &ExecState) -> NumericType {
87 match suffix {
88 crate::pretty::NumericSuffix::None => NumericType::Default {
89 len: exec_state.length_unit(),
90 angle: exec_state.angle_unit(),
91 },
92 crate::pretty::NumericSuffix::Count => NumericType::Known(UnitType::Count),
93 crate::pretty::NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
94 crate::pretty::NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
95 crate::pretty::NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
96 crate::pretty::NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
97 crate::pretty::NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
98 crate::pretty::NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
99 crate::pretty::NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
100 crate::pretty::NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
101 crate::pretty::NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
102 crate::pretty::NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
103 crate::pretty::NumericSuffix::Unknown => NumericType::Unknown,
104 }
105}
106
107fn number_to_solver_distance(
108 number: Number,
109 exec_state: &mut ExecState,
110 source_range: crate::SourceRange,
111 description: &str,
112) -> Result<f64, KclError> {
113 let value = ty_f64_to_kcl_value(
114 TyF64::new(number.value, numeric_suffix_to_type(number.units, exec_state)),
115 source_range,
116 );
117 let normalized = normalize_to_solver_distance_unit(&value, source_range, exec_state, description)?;
118 let Some(n) = normalized.as_ty_f64() else {
119 return Err(KclError::new_internal(KclErrorDetails::new(
120 format!("{description} did not normalize to a number"),
121 vec![source_range],
122 )));
123 };
124 Ok(n.n)
125}
126
127fn drag_anchor_target_to_solver_units(
128 target: Point2d<Number>,
129 exec_state: &mut ExecState,
130 source_range: crate::SourceRange,
131) -> Result<[f64; 2], KclError> {
132 Ok([
133 number_to_solver_distance(target.x, exec_state, source_range, "drag anchor x")?,
134 number_to_solver_distance(target.y, exec_state, source_range, "drag anchor y")?,
135 ])
136}
137
138struct FixedDragAnchorPoint {
139 point: DatumPoint,
140 fixed_constraints: [SolverConstraint; 2],
141}
142
143fn fixed_drag_anchor_point(
144 exec_state: &mut ExecState,
145 range: crate::SourceRange,
146 target: Point2d<Number>,
147) -> Result<FixedDragAnchorPoint, KclError> {
148 let [target_x, target_y] = drag_anchor_target_to_solver_units(target, exec_state, range)?;
149 let solver_ty = solver_numeric_type(exec_state);
150 let Some(sketch_state) = exec_state.sketch_block_mut() else {
151 return Err(KclError::new_semantic(KclErrorDetails::new(
152 "drag anchors can only be used inside a sketch block".to_owned(),
153 vec![range],
154 )));
155 };
156
157 let anchor_x_id = sketch_state.next_sketch_var_id();
158 sketch_state.sketch_vars.push(KclValue::SketchVar {
159 value: Box::new(crate::execution::SketchVar {
160 id: anchor_x_id,
161 initial_value: target_x,
162 ty: solver_ty,
163 node_path: None,
164 meta: Vec::new(),
165 }),
166 });
167
168 let anchor_y_id = sketch_state.next_sketch_var_id();
169 sketch_state.sketch_vars.push(KclValue::SketchVar {
170 value: Box::new(crate::execution::SketchVar {
171 id: anchor_y_id,
172 initial_value: target_y,
173 ty: solver_ty,
174 node_path: None,
175 meta: Vec::new(),
176 }),
177 });
178
179 let point = DatumPoint::new_xy(
180 anchor_x_id.to_constraint_id(range)?,
181 anchor_y_id.to_constraint_id(range)?,
182 );
183 Ok(FixedDragAnchorPoint {
184 point,
185 fixed_constraints: [
186 SolverConstraint::Fixed(point.x_id, target_x),
187 SolverConstraint::Fixed(point.y_id, target_y),
188 ],
189 })
190}
191
192fn fixed_origin_datum_point(
193 exec_state: &mut ExecState,
194 range: crate::SourceRange,
195 constraint_name: &str,
196) -> Result<(DatumPoint, [SolverConstraint; 2]), KclError> {
197 let sketch_var_ty = solver_numeric_type(exec_state);
198 let Some(sketch_state) = exec_state.sketch_block_mut() else {
199 return Err(KclError::new_semantic(KclErrorDetails::new(
200 format!("{constraint_name}() can only be used inside a sketch block"),
201 vec![range],
202 )));
203 };
204
205 let origin_x_id = sketch_state.next_sketch_var_id();
206 sketch_state.sketch_vars.push(KclValue::SketchVar {
207 value: Box::new(crate::execution::SketchVar {
208 id: origin_x_id,
209 initial_value: 0.0,
210 ty: sketch_var_ty,
211 node_path: None,
213 meta: vec![],
214 }),
215 });
216
217 let origin_y_id = sketch_state.next_sketch_var_id();
218 sketch_state.sketch_vars.push(KclValue::SketchVar {
219 value: Box::new(crate::execution::SketchVar {
220 id: origin_y_id,
221 initial_value: 0.0,
222 ty: sketch_var_ty,
223 node_path: None,
225 meta: vec![],
226 }),
227 });
228
229 let origin_x = origin_x_id.to_constraint_id(range)?;
230 let origin_y = origin_y_id.to_constraint_id(range)?;
231
232 Ok((
233 DatumPoint::new_xy(origin_x, origin_y),
234 [
235 SolverConstraint::Fixed(origin_x, 0.0),
236 SolverConstraint::Fixed(origin_y, 0.0),
237 ],
238 ))
239}
240
241#[derive(Debug, Clone, Copy)]
242struct LineVars {
243 start: [SketchVarId; 2],
244 end: [SketchVarId; 2],
245}
246
247#[derive(Debug, Clone, Copy)]
248struct ArcVars {
249 center: [SketchVarId; 2],
250 start: [SketchVarId; 2],
251 end: Option<[SketchVarId; 2]>,
252}
253
254fn make_line_arc_tangency_key(line: LineVars, arc: ArcVars) -> ConstraintKey {
255 let [a0, a1, a2, a3] = flatten_line_vars(line);
256 let [b0, b1, b2, b3, b4, b5] = flatten_arc_vars(arc);
257 ConstraintKey::LineCircle([a0, a1, a2, a3, b0, b1, b2, b3, b4, b5])
258}
259
260fn make_arc_arc_tangency_key(arc_a: ArcVars, arc_b: ArcVars) -> ConstraintKey {
261 let flat_a = flatten_arc_vars(arc_a);
262 let flat_b = flatten_arc_vars(arc_b);
263 let (lhs, rhs) = if flat_a <= flat_b {
264 (flat_a, flat_b)
265 } else {
266 (flat_b, flat_a)
267 };
268 let [a0, a1, a2, a3, a4, a5] = lhs;
269 let [b0, b1, b2, b3, b4, b5] = rhs;
270 ConstraintKey::CircleCircle([a0, a1, a2, a3, a4, a5, b0, b1, b2, b3, b4, b5])
271}
272
273fn flatten_line_vars(line: LineVars) -> [usize; 4] {
274 [line.start[0].0, line.start[1].0, line.end[0].0, line.end[1].0]
275}
276
277fn flatten_arc_vars(arc: ArcVars) -> [usize; 6] {
278 let end = arc.end.unwrap_or([SketchVarId::INVALID; 2]);
279 [
280 arc.center[0].0,
281 arc.center[1].0,
282 arc.start[0].0,
283 arc.start[1].0,
284 end[0].0,
285 end[1].0,
286 ]
287}
288
289fn infer_line_tangent_side(
290 sketch_vars: &[KclValue],
291 line: LineVars,
292 circle_center: [SketchVarId; 2],
293 exec_state: &mut ExecState,
294 range: crate::SourceRange,
295) -> Result<LineSide, KclError> {
296 let [sx, sy] = point_initial_position(sketch_vars, line.start, exec_state, range)?;
297 let [ex, ey] = point_initial_position(sketch_vars, line.end, exec_state, range)?;
298 let [cx, cy] = point_initial_position(sketch_vars, circle_center, exec_state, range)?;
299 let cross = (ex - sx) * (cy - sy) - (ey - sy) * (cx - sx);
300 Ok(if cross >= 0.0 { LineSide::Left } else { LineSide::Right })
301}
302
303fn infer_arc_tangent_side(
304 sketch_vars: &[KclValue],
305 arc_a: ArcVars,
306 arc_b: ArcVars,
307 exec_state: &mut ExecState,
308 range: crate::SourceRange,
309) -> Result<CircleSide, KclError> {
310 let rad_a = arc_initial_radius(sketch_vars, arc_a, exec_state, range)?;
311 let rad_b = arc_initial_radius(sketch_vars, arc_b, exec_state, range)?;
312 infer_circle_tangent_side(sketch_vars, arc_a.center, arc_b.center, rad_a, rad_b, exec_state, range)
313}
314
315fn infer_circle_tangent_side(
316 sketch_vars: &[KclValue],
317 center_a: [SketchVarId; 2],
318 center_b: [SketchVarId; 2],
319 radius_a: f64,
320 radius_b: f64,
321 exec_state: &mut ExecState,
322 range: crate::SourceRange,
323) -> Result<CircleSide, KclError> {
324 let dist = points_initial_distance(sketch_vars, center_a, center_b, exec_state, range)?;
325 let r_int = ((radius_a - radius_b).abs() - dist).abs();
326 let r_ext = (radius_a + radius_b - dist).abs();
327 Ok(if r_int < r_ext {
328 CircleSide::Interior
329 } else {
330 CircleSide::Exterior
331 })
332}
333
334fn point_initial_position(
335 sketch_vars: &[KclValue],
336 point: [SketchVarId; 2],
337 exec_state: &mut ExecState,
338 range: crate::SourceRange,
339) -> Result<[f64; 2], KclError> {
340 Ok([
341 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
342 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
343 ])
344}
345
346fn points_initial_distance(
347 sketch_vars: &[KclValue],
348 point_a: [SketchVarId; 2],
349 point_b: [SketchVarId; 2],
350 exec_state: &mut ExecState,
351 range: crate::SourceRange,
352) -> Result<f64, KclError> {
353 let [a_x, a_y] = point_initial_position(sketch_vars, point_a, exec_state, range)?;
354 let [b_x, b_y] = point_initial_position(sketch_vars, point_b, exec_state, range)?;
355 Ok(libm::hypot(a_x - b_x, a_y - b_y))
356}
357
358fn arc_initial_radius(
359 sketch_vars: &[KclValue],
360 arc: ArcVars,
361 exec_state: &mut ExecState,
362 range: crate::SourceRange,
363) -> Result<f64, KclError> {
364 points_initial_distance(sketch_vars, arc.center, arc.start, exec_state, range)
365}
366
367fn constrainable_point_from_unsolved_segment(
368 segment: &UnsolvedSegment,
369 function_name: &str,
370 range: crate::SourceRange,
371) -> Result<ConstrainablePoint2d, KclError> {
372 let UnsolvedSegmentKind::Point { position, .. } = &segment.kind else {
373 return Err(KclError::new_semantic(KclErrorDetails::new(
374 format!("{function_name}() expected a point segment"),
375 vec![range],
376 )));
377 };
378
379 match (&position[0], &position[1]) {
380 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(ConstrainablePoint2d {
381 vars: crate::front::Point2d { x: *x, y: *y },
382 object_id: segment.object_id,
383 }),
384 _ => Err(KclError::new_semantic(KclErrorDetails::new(
385 format!("unimplemented: {function_name}() point arguments must be sketch vars in all coordinates"),
386 vec![range],
387 ))),
388 }
389}
390
391fn constrainable_line_from_unsolved_segment(
392 segment: &UnsolvedSegment,
393 function_name: &str,
394 range: crate::SourceRange,
395) -> Result<ConstrainableLine2d, KclError> {
396 let UnsolvedSegmentKind::Line { start, end, .. } = &segment.kind else {
397 return Err(KclError::new_semantic(KclErrorDetails::new(
398 format!("{function_name}() expected a line segment"),
399 vec![range],
400 )));
401 };
402
403 match (&start[0], &start[1], &end[0], &end[1]) {
404 (
405 UnsolvedExpr::Unknown(start_x),
406 UnsolvedExpr::Unknown(start_y),
407 UnsolvedExpr::Unknown(end_x),
408 UnsolvedExpr::Unknown(end_y),
409 ) => Ok(ConstrainableLine2d {
410 vars: [
411 crate::front::Point2d {
412 x: *start_x,
413 y: *start_y,
414 },
415 crate::front::Point2d { x: *end_x, y: *end_y },
416 ],
417 object_id: segment.object_id,
418 }),
419 _ => Err(KclError::new_semantic(KclErrorDetails::new(
420 format!("unimplemented: {function_name}() line arguments must be sketch vars in all coordinates"),
421 vec![range],
422 ))),
423 }
424}
425
426fn constrainable_point_from_exprs(
427 position: &[UnsolvedExpr; 2],
428 object_id: ObjectId,
429 function_name: &str,
430 range: crate::SourceRange,
431 description: &str,
432) -> Result<ConstrainablePoint2d, KclError> {
433 match (&position[0], &position[1]) {
434 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(ConstrainablePoint2d {
435 vars: crate::front::Point2d { x: *x, y: *y },
436 object_id,
437 }),
438 _ => Err(KclError::new_semantic(KclErrorDetails::new(
439 format!("unimplemented: {function_name}() {description} must be sketch vars in all coordinates"),
440 vec![range],
441 ))),
442 }
443}
444
445fn constrainable_circular_from_unsolved_segment(
446 segment: &UnsolvedSegment,
447 function_name: &str,
448 range: crate::SourceRange,
449) -> Result<(ConstrainablePoint2d, ConstrainablePoint2d, Option<ConstrainablePoint2d>), KclError> {
450 match &segment.kind {
451 UnsolvedSegmentKind::Arc {
452 center,
453 start,
454 end,
455 center_object_id,
456 start_object_id,
457 end_object_id,
458 ..
459 } => Ok((
460 constrainable_point_from_exprs(center, *center_object_id, function_name, range, "arc center")?,
461 constrainable_point_from_exprs(start, *start_object_id, function_name, range, "arc start")?,
462 Some(constrainable_point_from_exprs(
463 end,
464 *end_object_id,
465 function_name,
466 range,
467 "arc end",
468 )?),
469 )),
470 UnsolvedSegmentKind::Circle {
471 center,
472 start,
473 center_object_id,
474 start_object_id,
475 ..
476 } => Ok((
477 constrainable_point_from_exprs(center, *center_object_id, function_name, range, "circle center")?,
478 constrainable_point_from_exprs(start, *start_object_id, function_name, range, "circle start")?,
479 None,
480 )),
481 _ => Err(KclError::new_semantic(KclErrorDetails::new(
482 format!("{function_name}() expected an arc or circle segment"),
483 vec![range],
484 ))),
485 }
486}
487
488fn extract_point_component(
494 value: &KclValue,
495 exec_state: &mut ExecState,
496 range: crate::SourceRange,
497 function_name: &str,
498 description: &str,
499) -> Result<(SketchVarId, Option<SolverConstraint>), KclError> {
500 match value.as_unsolved_expr() {
501 None => Err(KclError::new_semantic(KclErrorDetails::new(
502 format!("{description} must be a number or sketch var"),
503 vec![range],
504 ))),
505 Some(UnsolvedExpr::Unknown(var_id)) => Ok((var_id, None)),
506 Some(UnsolvedExpr::Known(_)) => {
507 let value_in_solver_units = normalize_to_solver_distance_unit(value, range, exec_state, description)?;
508 let Some(normalized_value) = value_in_solver_units.as_ty_f64() else {
509 return Err(KclError::new_internal(KclErrorDetails::new(
510 "Expected number after coercion".to_owned(),
511 vec![range],
512 )));
513 };
514
515 let Some(sketch_state) = exec_state.sketch_block_mut() else {
516 return Err(KclError::new_semantic(KclErrorDetails::new(
517 format!("{function_name}() can only be used inside a sketch block"),
518 vec![range],
519 )));
520 };
521 let var_id = sketch_state.next_sketch_var_id();
522 sketch_state.sketch_vars.push(KclValue::SketchVar {
523 value: Box::new(crate::execution::SketchVar {
524 id: var_id,
525 initial_value: normalized_value.n,
526 ty: normalized_value.ty,
527 node_path: None,
529 meta: vec![],
530 }),
531 });
532
533 Ok((
534 var_id,
535 Some(SolverConstraint::Fixed(
536 var_id.to_constraint_id(range)?,
537 normalized_value.n,
538 )),
539 ))
540 }
541 }
542}
543
544fn coincident_segments_for_segment_and_point2d(
545 segment_id: ObjectId,
546 point2d: &KclValue,
547 segment_first: bool,
548) -> Vec<ConstraintSegment> {
549 if !point2d_is_origin(point2d) {
550 return vec![segment_id.into()];
551 }
552
553 if segment_first {
554 vec![segment_id.into(), ConstraintSegment::ORIGIN]
555 } else {
556 vec![ConstraintSegment::ORIGIN, segment_id.into()]
557 }
558}
559
560pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
561 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
562 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
563 KclError::new_semantic(KclErrorDetails::new(
564 "at must be a 2D point".to_owned(),
565 vec![args.source_range],
566 ))
567 })?;
568 let Some(at_x) = at_x_value.as_unsolved_expr() else {
569 return Err(KclError::new_semantic(KclErrorDetails::new(
570 "at x must be a number or sketch var".to_owned(),
571 vec![args.source_range],
572 )));
573 };
574 let Some(at_y) = at_y_value.as_unsolved_expr() else {
575 return Err(KclError::new_semantic(KclErrorDetails::new(
576 "at y must be a number or sketch var".to_owned(),
577 vec![args.source_range],
578 )));
579 };
580 let ctor = PointCtor {
581 position: Point2d {
582 x: at_x_value.to_sketch_expr().ok_or_else(|| {
583 KclError::new_semantic(KclErrorDetails::new(
584 "unable to convert numeric type to suffix".to_owned(),
585 vec![args.source_range],
586 ))
587 })?,
588 y: at_y_value.to_sketch_expr().ok_or_else(|| {
589 KclError::new_semantic(KclErrorDetails::new(
590 "unable to convert numeric type to suffix".to_owned(),
591 vec![args.source_range],
592 ))
593 })?,
594 },
595 };
596 let segment = UnsolvedSegment {
597 id: exec_state.next_uuid(),
598 object_id: exec_state.next_object_id(),
599 kind: UnsolvedSegmentKind::Point {
600 position: [at_x, at_y],
601 ctor: Box::new(ctor),
602 },
603 tag: None,
604 node_path: args.node_path.clone(),
605 meta: vec![args.source_range.into()],
606 };
607 let optional_constraints = {
608 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range, args.node_path);
609
610 let mut optional_constraints = Vec::new();
611 if exec_state.segment_ids_edited_contains(&object_id) {
612 if let Some(at_x_var) = at_x_value.as_sketch_var() {
613 let x_initial_value = at_x_var.initial_value_to_solver_units(
614 exec_state,
615 args.source_range,
616 "edited segment fixed constraint value",
617 )?;
618 optional_constraints.push(SolverConstraint::Fixed(
619 at_x_var.id.to_constraint_id(args.source_range)?,
620 x_initial_value.n,
621 ));
622 }
623 if let Some(at_y_var) = at_y_value.as_sketch_var() {
624 let y_initial_value = at_y_var.initial_value_to_solver_units(
625 exec_state,
626 args.source_range,
627 "edited segment fixed constraint value",
628 )?;
629 optional_constraints.push(SolverConstraint::Fixed(
630 at_y_var.id.to_constraint_id(args.source_range)?,
631 y_initial_value.n,
632 ));
633 }
634 }
635 optional_constraints
636 };
637 let Some(sketch_state) = exec_state.sketch_block_mut() else {
639 return Err(KclError::new_semantic(KclErrorDetails::new(
640 "point() can only be used inside a sketch block".to_owned(),
641 vec![args.source_range],
642 )));
643 };
644 sketch_state.needed_by_engine.push(segment.clone());
645
646 sketch_state.solver_optional_constraints.extend(optional_constraints);
647
648 let meta = segment.meta.clone();
649 let abstract_segment = AbstractSegment {
650 repr: SegmentRepr::Unsolved {
651 segment: Box::new(segment),
652 },
653 meta,
654 };
655 Ok(KclValue::Segment {
656 value: Box::new(abstract_segment),
657 })
658}
659
660pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
661 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
662 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
663 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
664 let construction: bool = construction_opt.unwrap_or(false);
665 let construction_ctor = construction_opt;
666 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
667 KclError::new_semantic(KclErrorDetails::new(
668 "start must be a 2D point".to_owned(),
669 vec![args.source_range],
670 ))
671 })?;
672 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
673 KclError::new_semantic(KclErrorDetails::new(
674 "end must be a 2D point".to_owned(),
675 vec![args.source_range],
676 ))
677 })?;
678 let Some(start_x) = start_x_value.as_unsolved_expr() else {
679 return Err(KclError::new_semantic(KclErrorDetails::new(
680 "start x must be a number or sketch var".to_owned(),
681 vec![args.source_range],
682 )));
683 };
684 let Some(start_y) = start_y_value.as_unsolved_expr() else {
685 return Err(KclError::new_semantic(KclErrorDetails::new(
686 "start y must be a number or sketch var".to_owned(),
687 vec![args.source_range],
688 )));
689 };
690 let Some(end_x) = end_x_value.as_unsolved_expr() else {
691 return Err(KclError::new_semantic(KclErrorDetails::new(
692 "end x must be a number or sketch var".to_owned(),
693 vec![args.source_range],
694 )));
695 };
696 let Some(end_y) = end_y_value.as_unsolved_expr() else {
697 return Err(KclError::new_semantic(KclErrorDetails::new(
698 "end y must be a number or sketch var".to_owned(),
699 vec![args.source_range],
700 )));
701 };
702 let ctor = LineCtor {
703 start: Point2d {
704 x: start_x_value.to_sketch_expr().ok_or_else(|| {
705 KclError::new_semantic(KclErrorDetails::new(
706 "unable to convert numeric type to suffix".to_owned(),
707 vec![args.source_range],
708 ))
709 })?,
710 y: start_y_value.to_sketch_expr().ok_or_else(|| {
711 KclError::new_semantic(KclErrorDetails::new(
712 "unable to convert numeric type to suffix".to_owned(),
713 vec![args.source_range],
714 ))
715 })?,
716 },
717 end: Point2d {
718 x: end_x_value.to_sketch_expr().ok_or_else(|| {
719 KclError::new_semantic(KclErrorDetails::new(
720 "unable to convert numeric type to suffix".to_owned(),
721 vec![args.source_range],
722 ))
723 })?,
724 y: end_y_value.to_sketch_expr().ok_or_else(|| {
725 KclError::new_semantic(KclErrorDetails::new(
726 "unable to convert numeric type to suffix".to_owned(),
727 vec![args.source_range],
728 ))
729 })?,
730 },
731 construction: construction_ctor,
732 };
733 let line_var_ids = (start_x.var(), start_y.var(), end_x.var(), end_y.var());
734 let start_object_id = exec_state.next_object_id();
736 let end_object_id = exec_state.next_object_id();
737 let line_object_id = exec_state.next_object_id();
738 let segment = UnsolvedSegment {
739 id: exec_state.next_uuid(),
740 object_id: line_object_id,
741 kind: UnsolvedSegmentKind::Line {
742 start: [start_x, start_y],
743 end: [end_x, end_y],
744 ctor: Box::new(ctor),
745 start_object_id,
746 end_object_id,
747 construction,
748 },
749 tag: None,
750 node_path: args.node_path.clone(),
751 meta: vec![args.source_range.into()],
752 };
753 let mut optional_constraints = {
754 let start_object_id =
755 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
756 let end_object_id =
757 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
758 let line_object_id =
759 exec_state.add_placeholder_scene_object(line_object_id, args.source_range, args.node_path.clone());
760
761 let mut optional_constraints = Vec::new();
762 if exec_state.segment_ids_edited_contains(&start_object_id)
763 || exec_state.segment_ids_edited_contains(&line_object_id)
764 {
765 if let Some(start_x_var) = start_x_value.as_sketch_var() {
766 let x_initial_value = start_x_var.initial_value_to_solver_units(
767 exec_state,
768 args.source_range,
769 "edited segment fixed constraint value",
770 )?;
771 optional_constraints.push(SolverConstraint::Fixed(
772 start_x_var.id.to_constraint_id(args.source_range)?,
773 x_initial_value.n,
774 ));
775 }
776 if let Some(start_y_var) = start_y_value.as_sketch_var() {
777 let y_initial_value = start_y_var.initial_value_to_solver_units(
778 exec_state,
779 args.source_range,
780 "edited segment fixed constraint value",
781 )?;
782 optional_constraints.push(SolverConstraint::Fixed(
783 start_y_var.id.to_constraint_id(args.source_range)?,
784 y_initial_value.n,
785 ));
786 }
787 }
788 if exec_state.segment_ids_edited_contains(&end_object_id)
789 || exec_state.segment_ids_edited_contains(&line_object_id)
790 {
791 if let Some(end_x_var) = end_x_value.as_sketch_var() {
792 let x_initial_value = end_x_var.initial_value_to_solver_units(
793 exec_state,
794 args.source_range,
795 "edited segment fixed constraint value",
796 )?;
797 optional_constraints.push(SolverConstraint::Fixed(
798 end_x_var.id.to_constraint_id(args.source_range)?,
799 x_initial_value.n,
800 ));
801 }
802 if let Some(end_y_var) = end_y_value.as_sketch_var() {
803 let y_initial_value = end_y_var.initial_value_to_solver_units(
804 exec_state,
805 args.source_range,
806 "edited segment fixed constraint value",
807 )?;
808 optional_constraints.push(SolverConstraint::Fixed(
809 end_y_var.id.to_constraint_id(args.source_range)?,
810 y_initial_value.n,
811 ));
812 }
813 }
814 optional_constraints
815 };
816 let mut required_constraints = Vec::new();
817 if let Some(target) = exec_state.drag_anchor_target(&line_object_id).cloned()
818 && let (Some(start_x), Some(start_y), Some(end_x), Some(end_y)) = line_var_ids
819 {
820 let anchor = fixed_drag_anchor_point(exec_state, args.source_range, target)?;
821 required_constraints.push(SolverConstraint::PointLineDistance(
822 anchor.point,
823 DatumLineSegment::new(
824 DatumPoint::new_xy(
825 start_x.to_constraint_id(args.source_range)?,
826 start_y.to_constraint_id(args.source_range)?,
827 ),
828 DatumPoint::new_xy(
829 end_x.to_constraint_id(args.source_range)?,
830 end_y.to_constraint_id(args.source_range)?,
831 ),
832 ),
833 0.0,
834 ));
835 optional_constraints.extend(anchor.fixed_constraints);
836 }
837
838 let Some(sketch_state) = exec_state.sketch_block_mut() else {
840 return Err(KclError::new_semantic(KclErrorDetails::new(
841 "line() can only be used inside a sketch block".to_owned(),
842 vec![args.source_range],
843 )));
844 };
845 sketch_state.needed_by_engine.push(segment.clone());
846
847 sketch_state.solver_constraints.extend(required_constraints);
848 sketch_state.solver_optional_constraints.extend(optional_constraints);
849
850 let meta = segment.meta.clone();
851 let abstract_segment = AbstractSegment {
852 repr: SegmentRepr::Unsolved {
853 segment: Box::new(segment),
854 },
855 meta,
856 };
857 Ok(KclValue::Segment {
858 value: Box::new(abstract_segment),
859 })
860}
861
862pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
863 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
864 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
865 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
867 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
868 let construction: bool = construction_opt.unwrap_or(false);
869 let construction_ctor = construction_opt;
870
871 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
872 KclError::new_semantic(KclErrorDetails::new(
873 "start must be a 2D point".to_owned(),
874 vec![args.source_range],
875 ))
876 })?;
877 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
878 KclError::new_semantic(KclErrorDetails::new(
879 "end must be a 2D point".to_owned(),
880 vec![args.source_range],
881 ))
882 })?;
883 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
884 KclError::new_semantic(KclErrorDetails::new(
885 "center must be a 2D point".to_owned(),
886 vec![args.source_range],
887 ))
888 })?;
889
890 let (start_x, start_x_fixed) =
891 extract_point_component(&start_x_value, exec_state, args.source_range, "arc", "start x")?;
892 let (start_y, start_y_fixed) =
893 extract_point_component(&start_y_value, exec_state, args.source_range, "arc", "start y")?;
894 let (end_x, end_x_fixed) = extract_point_component(&end_x_value, exec_state, args.source_range, "arc", "end x")?;
895 let (end_y, end_y_fixed) = extract_point_component(&end_y_value, exec_state, args.source_range, "arc", "end y")?;
896 let (center_x, center_x_fixed) =
897 extract_point_component(¢er_x_value, exec_state, args.source_range, "arc", "center x")?;
898 let (center_y, center_y_fixed) =
899 extract_point_component(¢er_y_value, exec_state, args.source_range, "arc", "center y")?;
900 let arc_fixed_constraints = [
903 start_x_fixed,
904 start_y_fixed,
905 end_x_fixed,
906 end_y_fixed,
907 center_x_fixed,
908 center_y_fixed,
909 ]
910 .into_iter()
911 .flatten();
912
913 let ctor = ArcCtor {
914 start: Point2d {
915 x: start_x_value.to_sketch_expr().ok_or_else(|| {
916 KclError::new_semantic(KclErrorDetails::new(
917 "unable to convert numeric type to suffix".to_owned(),
918 vec![args.source_range],
919 ))
920 })?,
921 y: start_y_value.to_sketch_expr().ok_or_else(|| {
922 KclError::new_semantic(KclErrorDetails::new(
923 "unable to convert numeric type to suffix".to_owned(),
924 vec![args.source_range],
925 ))
926 })?,
927 },
928 end: Point2d {
929 x: end_x_value.to_sketch_expr().ok_or_else(|| {
930 KclError::new_semantic(KclErrorDetails::new(
931 "unable to convert numeric type to suffix".to_owned(),
932 vec![args.source_range],
933 ))
934 })?,
935 y: end_y_value.to_sketch_expr().ok_or_else(|| {
936 KclError::new_semantic(KclErrorDetails::new(
937 "unable to convert numeric type to suffix".to_owned(),
938 vec![args.source_range],
939 ))
940 })?,
941 },
942 center: Point2d {
943 x: center_x_value.to_sketch_expr().ok_or_else(|| {
944 KclError::new_semantic(KclErrorDetails::new(
945 "unable to convert numeric type to suffix".to_owned(),
946 vec![args.source_range],
947 ))
948 })?,
949 y: center_y_value.to_sketch_expr().ok_or_else(|| {
950 KclError::new_semantic(KclErrorDetails::new(
951 "unable to convert numeric type to suffix".to_owned(),
952 vec![args.source_range],
953 ))
954 })?,
955 },
956 construction: construction_ctor,
957 };
958
959 let start_object_id = exec_state.next_object_id();
961 let end_object_id = exec_state.next_object_id();
962 let center_object_id = exec_state.next_object_id();
963 let arc_object_id = exec_state.next_object_id();
964 let segment = UnsolvedSegment {
965 id: exec_state.next_uuid(),
966 object_id: arc_object_id,
967 kind: UnsolvedSegmentKind::Arc {
968 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
969 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
970 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
971 ctor: Box::new(ctor),
972 start_object_id,
973 end_object_id,
974 center_object_id,
975 construction,
976 },
977 tag: None,
978 node_path: args.node_path.clone(),
979 meta: vec![args.source_range.into()],
980 };
981 let optional_constraints = {
982 let start_object_id =
983 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
984 let end_object_id =
985 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
986 let center_object_id =
987 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
988 let arc_object_id =
989 exec_state.add_placeholder_scene_object(arc_object_id, args.source_range, args.node_path.clone());
990
991 let mut optional_constraints = Vec::new();
992 if exec_state.segment_ids_edited_contains(&start_object_id)
993 || exec_state.segment_ids_edited_contains(&arc_object_id)
994 {
995 if let Some(start_x_var) = start_x_value.as_sketch_var() {
996 let x_initial_value = start_x_var.initial_value_to_solver_units(
997 exec_state,
998 args.source_range,
999 "edited segment fixed constraint value",
1000 )?;
1001 optional_constraints.push(ezpz::Constraint::Fixed(
1002 start_x_var.id.to_constraint_id(args.source_range)?,
1003 x_initial_value.n,
1004 ));
1005 }
1006 if let Some(start_y_var) = start_y_value.as_sketch_var() {
1007 let y_initial_value = start_y_var.initial_value_to_solver_units(
1008 exec_state,
1009 args.source_range,
1010 "edited segment fixed constraint value",
1011 )?;
1012 optional_constraints.push(ezpz::Constraint::Fixed(
1013 start_y_var.id.to_constraint_id(args.source_range)?,
1014 y_initial_value.n,
1015 ));
1016 }
1017 }
1018 if exec_state.segment_ids_edited_contains(&end_object_id)
1019 || exec_state.segment_ids_edited_contains(&arc_object_id)
1020 {
1021 if let Some(end_x_var) = end_x_value.as_sketch_var() {
1022 let x_initial_value = end_x_var.initial_value_to_solver_units(
1023 exec_state,
1024 args.source_range,
1025 "edited segment fixed constraint value",
1026 )?;
1027 optional_constraints.push(ezpz::Constraint::Fixed(
1028 end_x_var.id.to_constraint_id(args.source_range)?,
1029 x_initial_value.n,
1030 ));
1031 }
1032 if let Some(end_y_var) = end_y_value.as_sketch_var() {
1033 let y_initial_value = end_y_var.initial_value_to_solver_units(
1034 exec_state,
1035 args.source_range,
1036 "edited segment fixed constraint value",
1037 )?;
1038 optional_constraints.push(ezpz::Constraint::Fixed(
1039 end_y_var.id.to_constraint_id(args.source_range)?,
1040 y_initial_value.n,
1041 ));
1042 }
1043 }
1044 if exec_state.segment_ids_edited_contains(¢er_object_id)
1045 || exec_state.segment_ids_edited_contains(&arc_object_id)
1046 {
1047 if let Some(center_x_var) = center_x_value.as_sketch_var() {
1048 let x_initial_value = center_x_var.initial_value_to_solver_units(
1049 exec_state,
1050 args.source_range,
1051 "edited segment fixed constraint value",
1052 )?;
1053 optional_constraints.push(ezpz::Constraint::Fixed(
1054 center_x_var.id.to_constraint_id(args.source_range)?,
1055 x_initial_value.n,
1056 ));
1057 }
1058 if let Some(center_y_var) = center_y_value.as_sketch_var() {
1059 let y_initial_value = center_y_var.initial_value_to_solver_units(
1060 exec_state,
1061 args.source_range,
1062 "edited segment fixed constraint value",
1063 )?;
1064 optional_constraints.push(ezpz::Constraint::Fixed(
1065 center_y_var.id.to_constraint_id(args.source_range)?,
1066 y_initial_value.n,
1067 ));
1068 }
1069 }
1070 optional_constraints
1071 };
1072 let range = args.source_range;
1074 let mut required_constraints = Vec::with_capacity(7);
1075 required_constraints.extend(arc_fixed_constraints);
1076 required_constraints.push(ezpz::Constraint::Arc(ezpz::datatypes::inputs::DatumCircularArc {
1077 center: ezpz::datatypes::inputs::DatumPoint::new_xy(
1078 center_x.to_constraint_id(range)?,
1079 center_y.to_constraint_id(range)?,
1080 ),
1081 start: ezpz::datatypes::inputs::DatumPoint::new_xy(
1082 start_x.to_constraint_id(range)?,
1083 start_y.to_constraint_id(range)?,
1084 ),
1085 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
1086 end_x.to_constraint_id(range)?,
1087 end_y.to_constraint_id(range)?,
1088 ),
1089 }));
1090 let drag_anchor = exec_state
1091 .drag_anchor_target(&arc_object_id)
1092 .cloned()
1093 .map(|target| fixed_drag_anchor_point(exec_state, range, target))
1094 .transpose()?;
1095
1096 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1097 return Err(KclError::new_semantic(KclErrorDetails::new(
1098 "arc() can only be used inside a sketch block".to_owned(),
1099 vec![args.source_range],
1100 )));
1101 };
1102 if let Some(anchor) = drag_anchor {
1103 required_constraints.push(ezpz::Constraint::PointArcCoincident(
1104 DatumCircularArc {
1105 center: DatumPoint::new_xy(center_x.to_constraint_id(range)?, center_y.to_constraint_id(range)?),
1106 start: DatumPoint::new_xy(start_x.to_constraint_id(range)?, start_y.to_constraint_id(range)?),
1107 end: DatumPoint::new_xy(end_x.to_constraint_id(range)?, end_y.to_constraint_id(range)?),
1108 },
1109 anchor.point,
1110 ));
1111 sketch_state
1112 .solver_optional_constraints
1113 .extend(anchor.fixed_constraints);
1114 }
1115 sketch_state.needed_by_engine.push(segment.clone());
1117 sketch_state.solver_constraints.extend(required_constraints);
1119 sketch_state.solver_optional_constraints.extend(optional_constraints);
1123
1124 let meta = segment.meta.clone();
1125 let abstract_segment = AbstractSegment {
1126 repr: SegmentRepr::Unsolved {
1127 segment: Box::new(segment),
1128 },
1129 meta,
1130 };
1131 Ok(KclValue::Segment {
1132 value: Box::new(abstract_segment),
1133 })
1134}
1135
1136pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1137 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
1138 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
1139 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
1140 let construction: bool = construction_opt.unwrap_or(false);
1141 let construction_ctor = construction_opt;
1142
1143 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
1144 KclError::new_semantic(KclErrorDetails::new(
1145 "start must be a 2D point".to_owned(),
1146 vec![args.source_range],
1147 ))
1148 })?;
1149 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
1150 KclError::new_semantic(KclErrorDetails::new(
1151 "center must be a 2D point".to_owned(),
1152 vec![args.source_range],
1153 ))
1154 })?;
1155
1156 let (start_x, start_x_fixed) =
1161 extract_point_component(&start_x_value, exec_state, args.source_range, "circle", "start x")?;
1162 let (start_y, start_y_fixed) =
1163 extract_point_component(&start_y_value, exec_state, args.source_range, "circle", "start y")?;
1164 let (center_x, center_x_fixed) =
1165 extract_point_component(¢er_x_value, exec_state, args.source_range, "circle", "center x")?;
1166 let (center_y, center_y_fixed) =
1167 extract_point_component(¢er_y_value, exec_state, args.source_range, "circle", "center y")?;
1168 let circle_fixed_constraints = [start_x_fixed, start_y_fixed, center_x_fixed, center_y_fixed]
1170 .into_iter()
1171 .flatten();
1172
1173 let ctor = CircleCtor {
1174 start: Point2d {
1175 x: start_x_value.to_sketch_expr().ok_or_else(|| {
1176 KclError::new_semantic(KclErrorDetails::new(
1177 "unable to convert numeric type to suffix".to_owned(),
1178 vec![args.source_range],
1179 ))
1180 })?,
1181 y: start_y_value.to_sketch_expr().ok_or_else(|| {
1182 KclError::new_semantic(KclErrorDetails::new(
1183 "unable to convert numeric type to suffix".to_owned(),
1184 vec![args.source_range],
1185 ))
1186 })?,
1187 },
1188 center: Point2d {
1189 x: center_x_value.to_sketch_expr().ok_or_else(|| {
1190 KclError::new_semantic(KclErrorDetails::new(
1191 "unable to convert numeric type to suffix".to_owned(),
1192 vec![args.source_range],
1193 ))
1194 })?,
1195 y: center_y_value.to_sketch_expr().ok_or_else(|| {
1196 KclError::new_semantic(KclErrorDetails::new(
1197 "unable to convert numeric type to suffix".to_owned(),
1198 vec![args.source_range],
1199 ))
1200 })?,
1201 },
1202 construction: construction_ctor,
1203 };
1204
1205 let start_object_id = exec_state.next_object_id();
1207 let center_object_id = exec_state.next_object_id();
1208 let circle_object_id = exec_state.next_object_id();
1209 let segment = UnsolvedSegment {
1210 id: exec_state.next_uuid(),
1211 object_id: circle_object_id,
1212 kind: UnsolvedSegmentKind::Circle {
1213 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
1214 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
1215 ctor: Box::new(ctor),
1216 start_object_id,
1217 center_object_id,
1218 construction,
1219 },
1220 tag: None,
1221 node_path: args.node_path.clone(),
1222 meta: vec![args.source_range.into()],
1223 };
1224 let mut optional_constraints = {
1225 let start_object_id =
1226 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
1227 let center_object_id =
1228 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
1229 let circle_object_id =
1230 exec_state.add_placeholder_scene_object(circle_object_id, args.source_range, args.node_path.clone());
1231
1232 let mut optional_constraints = Vec::new();
1233 if exec_state.segment_ids_edited_contains(&start_object_id)
1234 || exec_state.segment_ids_edited_contains(&circle_object_id)
1235 {
1236 if let Some(start_x_var) = start_x_value.as_sketch_var() {
1237 let x_initial_value = start_x_var.initial_value_to_solver_units(
1238 exec_state,
1239 args.source_range,
1240 "edited segment fixed constraint value",
1241 )?;
1242 optional_constraints.push(ezpz::Constraint::Fixed(
1243 start_x_var.id.to_constraint_id(args.source_range)?,
1244 x_initial_value.n,
1245 ));
1246 }
1247 if let Some(start_y_var) = start_y_value.as_sketch_var() {
1248 let y_initial_value = start_y_var.initial_value_to_solver_units(
1249 exec_state,
1250 args.source_range,
1251 "edited segment fixed constraint value",
1252 )?;
1253 optional_constraints.push(ezpz::Constraint::Fixed(
1254 start_y_var.id.to_constraint_id(args.source_range)?,
1255 y_initial_value.n,
1256 ));
1257 }
1258 }
1259 if exec_state.segment_ids_edited_contains(¢er_object_id)
1260 || exec_state.segment_ids_edited_contains(&circle_object_id)
1261 {
1262 if let Some(center_x_var) = center_x_value.as_sketch_var() {
1263 let x_initial_value = center_x_var.initial_value_to_solver_units(
1264 exec_state,
1265 args.source_range,
1266 "edited segment fixed constraint value",
1267 )?;
1268 optional_constraints.push(ezpz::Constraint::Fixed(
1269 center_x_var.id.to_constraint_id(args.source_range)?,
1270 x_initial_value.n,
1271 ));
1272 }
1273 if let Some(center_y_var) = center_y_value.as_sketch_var() {
1274 let y_initial_value = center_y_var.initial_value_to_solver_units(
1275 exec_state,
1276 args.source_range,
1277 "edited segment fixed constraint value",
1278 )?;
1279 optional_constraints.push(ezpz::Constraint::Fixed(
1280 center_y_var.id.to_constraint_id(args.source_range)?,
1281 y_initial_value.n,
1282 ));
1283 }
1284 }
1285 optional_constraints
1286 };
1287 let mut required_constraints = Vec::new();
1288 required_constraints.extend(circle_fixed_constraints);
1289 if let Some(target) = exec_state.drag_anchor_target(&circle_object_id).cloned() {
1290 let anchor = fixed_drag_anchor_point(exec_state, args.source_range, target)?;
1291 let center = DatumPoint::new_xy(
1292 center_x.to_constraint_id(args.source_range)?,
1293 center_y.to_constraint_id(args.source_range)?,
1294 );
1295 required_constraints.push(SolverConstraint::LinesEqualLength(
1296 DatumLineSegment::new(center, anchor.point),
1297 DatumLineSegment::new(
1298 center,
1299 DatumPoint::new_xy(
1300 start_x.to_constraint_id(args.source_range)?,
1301 start_y.to_constraint_id(args.source_range)?,
1302 ),
1303 ),
1304 ));
1305 optional_constraints.extend(anchor.fixed_constraints);
1306 }
1307
1308 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1309 return Err(KclError::new_semantic(KclErrorDetails::new(
1310 "circle() can only be used inside a sketch block".to_owned(),
1311 vec![args.source_range],
1312 )));
1313 };
1314 sketch_state.needed_by_engine.push(segment.clone());
1316
1317 sketch_state.solver_constraints.extend(required_constraints);
1318 sketch_state.solver_optional_constraints.extend(optional_constraints);
1319
1320 let meta = segment.meta.clone();
1321 let abstract_segment = AbstractSegment {
1322 repr: SegmentRepr::Unsolved {
1323 segment: Box::new(segment),
1324 },
1325 meta,
1326 };
1327 Ok(KclValue::Segment {
1328 value: Box::new(abstract_segment),
1329 })
1330}
1331
1332pub async fn control_point_spline(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1333 let points: Vec<KclValue> = args.get_kw_arg(
1334 "points",
1335 &RuntimeType::Array(Box::new(RuntimeType::point2d()), ArrayLen::Minimum(3)),
1336 exec_state,
1337 )?;
1338 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
1339 let construction = construction_opt.unwrap_or(false);
1340
1341 if points.len() < 3 {
1342 return Err(KclError::new_semantic(KclErrorDetails::new(
1343 "controlPointSpline requires at least 3 control points".to_owned(),
1344 vec![args.source_range],
1345 )));
1346 }
1347
1348 let degree = usize::min(3, points.len() - 1) as u32;
1349 let mut ctor_points = Vec::with_capacity(points.len());
1350 let mut control_values = Vec::with_capacity(points.len());
1351 let mut controls = Vec::with_capacity(points.len());
1352 let mut control_object_ids = Vec::with_capacity(points.len());
1353 let mut control_polygon_edge_object_ids = Vec::with_capacity(points.len().saturating_sub(1));
1354
1355 for point in points {
1356 let KclValue::HomArray { value, .. } = point else {
1357 return Err(KclError::new_semantic(KclErrorDetails::new(
1358 "each control point must be a 2D point".to_owned(),
1359 vec![args.source_range],
1360 )));
1361 };
1362 let [x_value, y_value]: [KclValue; 2] = value.try_into().map_err(|_| {
1363 KclError::new_semantic(KclErrorDetails::new(
1364 "each control point must be a 2D point".to_owned(),
1365 vec![args.source_range],
1366 ))
1367 })?;
1368 let Some(x) = x_value.as_unsolved_expr() else {
1369 return Err(KclError::new_semantic(KclErrorDetails::new(
1370 "control point x must be a number or sketch var".to_owned(),
1371 vec![args.source_range],
1372 )));
1373 };
1374 let Some(y) = y_value.as_unsolved_expr() else {
1375 return Err(KclError::new_semantic(KclErrorDetails::new(
1376 "control point y must be a number or sketch var".to_owned(),
1377 vec![args.source_range],
1378 )));
1379 };
1380 ctor_points.push(Point2d {
1381 x: x_value.to_sketch_expr().ok_or_else(|| {
1382 KclError::new_semantic(KclErrorDetails::new(
1383 "unable to convert numeric type to suffix".to_owned(),
1384 vec![args.source_range],
1385 ))
1386 })?,
1387 y: y_value.to_sketch_expr().ok_or_else(|| {
1388 KclError::new_semantic(KclErrorDetails::new(
1389 "unable to convert numeric type to suffix".to_owned(),
1390 vec![args.source_range],
1391 ))
1392 })?,
1393 });
1394 control_values.push([x_value, y_value]);
1395 controls.push([x, y]);
1396 control_object_ids.push(exec_state.next_object_id());
1397 }
1398 for _ in 0..controls.len().saturating_sub(1) {
1399 control_polygon_edge_object_ids.push(exec_state.next_object_id());
1400 }
1401
1402 let spline_object_id = exec_state.next_object_id();
1403 let ctor = ControlPointSplineCtor {
1404 points: ctor_points,
1405 construction: construction_opt,
1406 };
1407 let segment = UnsolvedSegment {
1408 id: exec_state.next_uuid(),
1409 object_id: spline_object_id,
1410 kind: UnsolvedSegmentKind::ControlPointSpline {
1411 controls,
1412 ctor: Box::new(ctor),
1413 control_object_ids: control_object_ids.clone(),
1414 control_polygon_edge_object_ids: control_polygon_edge_object_ids.clone(),
1415 degree,
1416 construction,
1417 },
1418 tag: None,
1419 node_path: args.node_path.clone(),
1420 meta: vec![args.source_range.into()],
1421 };
1422
1423 let optional_constraints = {
1424 let placeholder_control_ids = control_object_ids
1425 .iter()
1426 .map(|control_object_id| {
1427 exec_state.add_placeholder_scene_object(*control_object_id, args.source_range, args.node_path.clone())
1428 })
1429 .collect::<Vec<_>>();
1430 control_polygon_edge_object_ids.iter().for_each(|edge_object_id| {
1431 exec_state.add_placeholder_scene_object(*edge_object_id, args.source_range, args.node_path.clone());
1432 });
1433 let spline_object_id =
1434 exec_state.add_placeholder_scene_object(spline_object_id, args.source_range, args.node_path.clone());
1435
1436 let mut optional_constraints = Vec::new();
1437 for (index, [x_value, y_value]) in control_values.iter().enumerate() {
1438 let control_object_id = placeholder_control_ids[index];
1439 if !(exec_state.segment_ids_edited_contains(&control_object_id)
1440 || exec_state.segment_ids_edited_contains(&spline_object_id))
1441 {
1442 continue;
1443 }
1444
1445 if let Some(x_var) = x_value.as_sketch_var() {
1446 let x_initial_value = x_var.initial_value_to_solver_units(
1447 exec_state,
1448 args.source_range,
1449 "edited segment fixed constraint value",
1450 )?;
1451 optional_constraints.push(SolverConstraint::Fixed(
1452 x_var.id.to_constraint_id(args.source_range)?,
1453 x_initial_value.n,
1454 ));
1455 }
1456
1457 if let Some(y_var) = y_value.as_sketch_var() {
1458 let y_initial_value = y_var.initial_value_to_solver_units(
1459 exec_state,
1460 args.source_range,
1461 "edited segment fixed constraint value",
1462 )?;
1463 optional_constraints.push(SolverConstraint::Fixed(
1464 y_var.id.to_constraint_id(args.source_range)?,
1465 y_initial_value.n,
1466 ));
1467 }
1468 }
1469 optional_constraints
1470 };
1471
1472 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1473 return Err(KclError::new_semantic(KclErrorDetails::new(
1474 "controlPointSpline() can only be used inside a sketch block".to_owned(),
1475 vec![args.source_range],
1476 )));
1477 };
1478 sketch_state.needed_by_engine.push(segment.clone());
1479
1480 sketch_state.solver_optional_constraints.extend(optional_constraints);
1481
1482 let meta = segment.meta.clone();
1483 let abstract_segment = AbstractSegment {
1484 repr: SegmentRepr::Unsolved {
1485 segment: Box::new(segment),
1486 },
1487 meta,
1488 };
1489 Ok(KclValue::Segment {
1490 value: Box::new(abstract_segment),
1491 })
1492}
1493
1494pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1495 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1496 "points",
1497 &RuntimeType::Array(
1498 Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
1499 ArrayLen::Minimum(2),
1500 ),
1501 exec_state,
1502 )?;
1503 if points.len() > 2 {
1504 return coincident_points(points, exec_state, args);
1505 }
1506 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1507 KclError::new_semantic(KclErrorDetails::new(
1508 "must have two input points".to_owned(),
1509 vec![args.source_range],
1510 ))
1511 })?;
1512
1513 let range = args.source_range;
1514 match (&point0, &point1) {
1515 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1516 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1517 return Err(KclError::new_semantic(KclErrorDetails::new(
1518 "first point must be an unsolved segment".to_owned(),
1519 vec![args.source_range],
1520 )));
1521 };
1522 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1523 return Err(KclError::new_semantic(KclErrorDetails::new(
1524 "second point must be an unsolved segment".to_owned(),
1525 vec![args.source_range],
1526 )));
1527 };
1528 match (&unsolved0.kind, &unsolved1.kind) {
1529 (
1530 UnsolvedSegmentKind::Point { position: pos0, .. },
1531 UnsolvedSegmentKind::Point { position: pos1, .. },
1532 ) => {
1533 let p0_x = &pos0[0];
1534 let p0_y = &pos0[1];
1535 match (p0_x, p0_y) {
1536 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
1537 let p1_x = &pos1[0];
1538 let p1_y = &pos1[1];
1539 match (p1_x, p1_y) {
1540 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
1541 let constraint = SolverConstraint::PointsCoincident(
1542 ezpz::datatypes::inputs::DatumPoint::new_xy(
1543 p0_x.to_constraint_id(range)?,
1544 p0_y.to_constraint_id(range)?,
1545 ),
1546 ezpz::datatypes::inputs::DatumPoint::new_xy(
1547 p1_x.to_constraint_id(range)?,
1548 p1_y.to_constraint_id(range)?,
1549 ),
1550 );
1551 let constraint_id = exec_state.next_object_id();
1552 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1554 return Err(KclError::new_semantic(KclErrorDetails::new(
1555 "coincident() can only be used inside a sketch block".to_owned(),
1556 vec![args.source_range],
1557 )));
1558 };
1559 sketch_state.solver_constraints.push(constraint);
1560 let constraint = crate::front::Constraint::Coincident(Coincident {
1561 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1562 });
1563 sketch_state.sketch_constraints.push(constraint_id);
1564 track_constraint(constraint_id, constraint, exec_state, &args);
1565 Ok(KclValue::none())
1566 }
1567 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1568 let p1_x = KclValue::Number {
1569 value: p1_x.n,
1570 ty: p1_x.ty,
1571 meta: vec![args.source_range.into()],
1572 };
1573 let p1_y = KclValue::Number {
1574 value: p1_y.n,
1575 ty: p1_y.ty,
1576 meta: vec![args.source_range.into()],
1577 };
1578 let (constraint_x, constraint_y) =
1579 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
1580
1581 let constraint_id = exec_state.next_object_id();
1582 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1584 return Err(KclError::new_semantic(KclErrorDetails::new(
1585 "coincident() can only be used inside a sketch block".to_owned(),
1586 vec![args.source_range],
1587 )));
1588 };
1589 sketch_state.solver_constraints.push(constraint_x);
1590 sketch_state.solver_constraints.push(constraint_y);
1591 let constraint = crate::front::Constraint::Coincident(Coincident {
1592 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1593 });
1594 sketch_state.sketch_constraints.push(constraint_id);
1595 track_constraint(constraint_id, constraint, exec_state, &args);
1596 Ok(KclValue::none())
1597 }
1598 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1599 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1600 Err(KclError::new_semantic(KclErrorDetails::new(
1602 "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(),
1603 vec![args.source_range],
1604 )))
1605 }
1606 }
1607 }
1608 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
1609 let p1_x = &pos1[0];
1610 let p1_y = &pos1[1];
1611 match (p1_x, p1_y) {
1612 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
1613 let p0_x = KclValue::Number {
1614 value: p0_x.n,
1615 ty: p0_x.ty,
1616 meta: vec![args.source_range.into()],
1617 };
1618 let p0_y = KclValue::Number {
1619 value: p0_y.n,
1620 ty: p0_y.ty,
1621 meta: vec![args.source_range.into()],
1622 };
1623 let (constraint_x, constraint_y) =
1624 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
1625
1626 let constraint_id = exec_state.next_object_id();
1627 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1629 return Err(KclError::new_semantic(KclErrorDetails::new(
1630 "coincident() can only be used inside a sketch block".to_owned(),
1631 vec![args.source_range],
1632 )));
1633 };
1634 sketch_state.solver_constraints.push(constraint_x);
1635 sketch_state.solver_constraints.push(constraint_y);
1636 let constraint = crate::front::Constraint::Coincident(Coincident {
1637 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1638 });
1639 sketch_state.sketch_constraints.push(constraint_id);
1640 track_constraint(constraint_id, constraint, exec_state, &args);
1641 Ok(KclValue::none())
1642 }
1643 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1644 if *p0_x != *p1_x || *p0_y != *p1_y {
1645 return Err(KclError::new_semantic(KclErrorDetails::new(
1646 "Coincident constraint between two fixed points failed since coordinates differ"
1647 .to_owned(),
1648 vec![args.source_range],
1649 )));
1650 }
1651 Ok(KclValue::none())
1652 }
1653 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1654 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1655 Err(KclError::new_semantic(KclErrorDetails::new(
1657 "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(),
1658 vec![args.source_range],
1659 )))
1660 }
1661 }
1662 }
1663 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1664 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1665 Err(KclError::new_semantic(KclErrorDetails::new(
1667 "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(),
1668 vec![args.source_range],
1669 )))
1670 }
1671 }
1672 }
1673 (
1675 UnsolvedSegmentKind::Point {
1676 position: point_pos, ..
1677 },
1678 UnsolvedSegmentKind::Line {
1679 start: line_start,
1680 end: line_end,
1681 ..
1682 },
1683 )
1684 | (
1685 UnsolvedSegmentKind::Line {
1686 start: line_start,
1687 end: line_end,
1688 ..
1689 },
1690 UnsolvedSegmentKind::Point {
1691 position: point_pos, ..
1692 },
1693 ) => {
1694 let point_x = &point_pos[0];
1695 let point_y = &point_pos[1];
1696 match (point_x, point_y) {
1697 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1698 let (start_x, start_y) = (&line_start[0], &line_start[1]);
1700 let (end_x, end_y) = (&line_end[0], &line_end[1]);
1701
1702 match (start_x, start_y, end_x, end_y) {
1703 (
1704 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1705 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1706 ) => {
1707 let point = DatumPoint::new_xy(
1708 point_x.to_constraint_id(range)?,
1709 point_y.to_constraint_id(range)?,
1710 );
1711 let line_segment = DatumLineSegment::new(
1712 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
1713 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
1714 );
1715 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
1716
1717 let constraint_id = exec_state.next_object_id();
1718
1719 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1720 return Err(KclError::new_semantic(KclErrorDetails::new(
1721 "coincident() can only be used inside a sketch block".to_owned(),
1722 vec![args.source_range],
1723 )));
1724 };
1725 sketch_state.solver_constraints.push(constraint);
1726 let constraint = crate::front::Constraint::Coincident(Coincident {
1727 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1728 });
1729 sketch_state.sketch_constraints.push(constraint_id);
1730 track_constraint(constraint_id, constraint, exec_state, &args);
1731 Ok(KclValue::none())
1732 }
1733 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1734 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
1735 vec![args.source_range],
1736 ))),
1737 }
1738 }
1739 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1740 "Point coordinates must be sketch variables for point-segment coincident constraint"
1741 .to_owned(),
1742 vec![args.source_range],
1743 ))),
1744 }
1745 }
1746 (
1748 UnsolvedSegmentKind::Point {
1749 position: point_pos, ..
1750 },
1751 UnsolvedSegmentKind::Arc {
1752 start: arc_start,
1753 end: arc_end,
1754 center: arc_center,
1755 ..
1756 },
1757 )
1758 | (
1759 UnsolvedSegmentKind::Arc {
1760 start: arc_start,
1761 end: arc_end,
1762 center: arc_center,
1763 ..
1764 },
1765 UnsolvedSegmentKind::Point {
1766 position: point_pos, ..
1767 },
1768 ) => {
1769 let point_x = &point_pos[0];
1770 let point_y = &point_pos[1];
1771 match (point_x, point_y) {
1772 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1773 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
1775 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
1776 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
1777
1778 match (center_x, center_y, start_x, start_y, end_x, end_y) {
1779 (
1780 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
1781 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1782 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1783 ) => {
1784 let point = DatumPoint::new_xy(
1785 point_x.to_constraint_id(range)?,
1786 point_y.to_constraint_id(range)?,
1787 );
1788 let circular_arc = DatumCircularArc {
1789 center: DatumPoint::new_xy(
1790 cx.to_constraint_id(range)?,
1791 cy.to_constraint_id(range)?,
1792 ),
1793 start: DatumPoint::new_xy(
1794 sx.to_constraint_id(range)?,
1795 sy.to_constraint_id(range)?,
1796 ),
1797 end: DatumPoint::new_xy(
1798 ex.to_constraint_id(range)?,
1799 ey.to_constraint_id(range)?,
1800 ),
1801 };
1802 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
1803
1804 let constraint_id = exec_state.next_object_id();
1805
1806 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1807 return Err(KclError::new_semantic(KclErrorDetails::new(
1808 "coincident() can only be used inside a sketch block".to_owned(),
1809 vec![args.source_range],
1810 )));
1811 };
1812 sketch_state.solver_constraints.push(constraint);
1813 let constraint = crate::front::Constraint::Coincident(Coincident {
1814 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1815 });
1816 sketch_state.sketch_constraints.push(constraint_id);
1817 track_constraint(constraint_id, constraint, exec_state, &args);
1818 Ok(KclValue::none())
1819 }
1820 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1821 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
1822 vec![args.source_range],
1823 ))),
1824 }
1825 }
1826 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1827 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
1828 vec![args.source_range],
1829 ))),
1830 }
1831 }
1832 (
1835 UnsolvedSegmentKind::Point {
1836 position: point_pos, ..
1837 },
1838 UnsolvedSegmentKind::Circle {
1839 start: circle_start,
1840 center: circle_center,
1841 ..
1842 },
1843 )
1844 | (
1845 UnsolvedSegmentKind::Circle {
1846 start: circle_start,
1847 center: circle_center,
1848 ..
1849 },
1850 UnsolvedSegmentKind::Point {
1851 position: point_pos, ..
1852 },
1853 ) => {
1854 let point_x = &point_pos[0];
1855 let point_y = &point_pos[1];
1856 match (point_x, point_y) {
1857 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1858 let (center_x, center_y) = (&circle_center[0], &circle_center[1]);
1860 let (start_x, start_y) = (&circle_start[0], &circle_start[1]);
1861
1862 match (center_x, center_y, start_x, start_y) {
1863 (
1864 UnsolvedExpr::Unknown(cx),
1865 UnsolvedExpr::Unknown(cy),
1866 UnsolvedExpr::Unknown(sx),
1867 UnsolvedExpr::Unknown(sy),
1868 ) => {
1869 let point_radius_line = DatumLineSegment::new(
1870 DatumPoint::new_xy(
1871 cx.to_constraint_id(range)?,
1872 cy.to_constraint_id(range)?,
1873 ),
1874 DatumPoint::new_xy(
1875 point_x.to_constraint_id(range)?,
1876 point_y.to_constraint_id(range)?,
1877 ),
1878 );
1879 let circle_radius_line = DatumLineSegment::new(
1880 DatumPoint::new_xy(
1881 cx.to_constraint_id(range)?,
1882 cy.to_constraint_id(range)?,
1883 ),
1884 DatumPoint::new_xy(
1885 sx.to_constraint_id(range)?,
1886 sy.to_constraint_id(range)?,
1887 ),
1888 );
1889 let constraint =
1890 SolverConstraint::LinesEqualLength(point_radius_line, circle_radius_line);
1891
1892 let constraint_id = exec_state.next_object_id();
1893
1894 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1895 return Err(KclError::new_semantic(KclErrorDetails::new(
1896 "coincident() can only be used inside a sketch block".to_owned(),
1897 vec![args.source_range],
1898 )));
1899 };
1900 sketch_state.solver_constraints.push(constraint);
1901 let constraint = crate::front::Constraint::Coincident(Coincident {
1902 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1903 });
1904 sketch_state.sketch_constraints.push(constraint_id);
1905 track_constraint(constraint_id, constraint, exec_state, &args);
1906 Ok(KclValue::none())
1907 }
1908 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1909 "Circle start and center points must be sketch variables for point-circle coincident constraint".to_owned(),
1910 vec![args.source_range],
1911 ))),
1912 }
1913 }
1914 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1915 "Point coordinates must be sketch variables for point-circle coincident constraint"
1916 .to_owned(),
1917 vec![args.source_range],
1918 ))),
1919 }
1920 }
1921 (
1923 UnsolvedSegmentKind::Line {
1924 start: line0_start,
1925 end: line0_end,
1926 ..
1927 },
1928 UnsolvedSegmentKind::Line {
1929 start: line1_start,
1930 end: line1_end,
1931 ..
1932 },
1933 ) => {
1934 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
1936 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
1937 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
1938 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
1939
1940 match (
1941 line0_start_x,
1942 line0_start_y,
1943 line0_end_x,
1944 line0_end_y,
1945 line1_start_x,
1946 line1_start_y,
1947 line1_end_x,
1948 line1_end_y,
1949 ) {
1950 (
1951 UnsolvedExpr::Unknown(l0_sx),
1952 UnsolvedExpr::Unknown(l0_sy),
1953 UnsolvedExpr::Unknown(l0_ex),
1954 UnsolvedExpr::Unknown(l0_ey),
1955 UnsolvedExpr::Unknown(l1_sx),
1956 UnsolvedExpr::Unknown(l1_sy),
1957 UnsolvedExpr::Unknown(l1_ex),
1958 UnsolvedExpr::Unknown(l1_ey),
1959 ) => {
1960 let line0_segment = DatumLineSegment::new(
1962 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
1963 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
1964 );
1965 let line1_segment = DatumLineSegment::new(
1966 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
1967 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
1968 );
1969
1970 let parallel_constraint =
1972 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
1973
1974 let point_on_line1 =
1976 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
1977 let distance_constraint =
1978 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
1979
1980 let constraint_id = exec_state.next_object_id();
1981
1982 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1983 return Err(KclError::new_semantic(KclErrorDetails::new(
1984 "coincident() can only be used inside a sketch block".to_owned(),
1985 vec![args.source_range],
1986 )));
1987 };
1988 sketch_state.solver_constraints.push(parallel_constraint);
1990 sketch_state.solver_constraints.push(distance_constraint);
1991 let constraint = crate::front::Constraint::Coincident(Coincident {
1992 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1993 });
1994 sketch_state.sketch_constraints.push(constraint_id);
1995 track_constraint(constraint_id, constraint, exec_state, &args);
1996 Ok(KclValue::none())
1997 }
1998 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1999 "Line segment endpoints must be sketch variables for line-line coincident constraint"
2000 .to_owned(),
2001 vec![args.source_range],
2002 ))),
2003 }
2004 }
2005 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2006 format!(
2007 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
2008 &unsolved0.kind, &unsolved1.kind
2009 ),
2010 vec![args.source_range],
2011 ))),
2012 }
2013 }
2014 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2017 let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
2018 return Err(KclError::new_semantic(KclErrorDetails::new(
2019 "Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
2020 vec![args.source_range],
2021 )));
2022 };
2023 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2024 return Err(KclError::new_semantic(KclErrorDetails::new(
2025 "segment must be an unsolved segment".to_owned(),
2026 vec![args.source_range],
2027 )));
2028 };
2029 match &unsolved.kind {
2030 UnsolvedSegmentKind::Point { position, .. } => {
2031 let p_x = &position[0];
2032 let p_y = &position[1];
2033 match (p_x, p_y) {
2034 (UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
2035 let pt_x = KclValue::Number {
2036 value: pt[0].n,
2037 ty: pt[0].ty,
2038 meta: vec![args.source_range.into()],
2039 };
2040 let pt_y = KclValue::Number {
2041 value: pt[1].n,
2042 ty: pt[1].ty,
2043 meta: vec![args.source_range.into()],
2044 };
2045 let (constraint_x, constraint_y) =
2046 coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
2047
2048 let constraint_id = exec_state.next_object_id();
2049 let coincident_segments = coincident_segments_for_segment_and_point2d(
2050 unsolved.object_id,
2051 point2d,
2052 matches!((&point0, &point1), (KclValue::Segment { .. }, _)),
2053 );
2054 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2055 return Err(KclError::new_semantic(KclErrorDetails::new(
2056 "coincident() can only be used inside a sketch block".to_owned(),
2057 vec![args.source_range],
2058 )));
2059 };
2060 sketch_state.solver_constraints.push(constraint_x);
2061 sketch_state.solver_constraints.push(constraint_y);
2062 let constraint = crate::front::Constraint::Coincident(Coincident {
2063 segments: coincident_segments,
2064 });
2065 sketch_state.sketch_constraints.push(constraint_id);
2066 track_constraint(constraint_id, constraint, exec_state, &args);
2067 Ok(KclValue::none())
2068 }
2069 (UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
2070 let pt_x_val = normalize_to_solver_distance_unit(
2071 &KclValue::Number {
2072 value: pt[0].n,
2073 ty: pt[0].ty,
2074 meta: vec![args.source_range.into()],
2075 },
2076 args.source_range,
2077 exec_state,
2078 "coincident constraint value",
2079 )?;
2080 let pt_y_val = normalize_to_solver_distance_unit(
2081 &KclValue::Number {
2082 value: pt[1].n,
2083 ty: pt[1].ty,
2084 meta: vec![args.source_range.into()],
2085 },
2086 args.source_range,
2087 exec_state,
2088 "coincident constraint value",
2089 )?;
2090 let Some(pt_x) = pt_x_val.as_ty_f64() else {
2091 return Err(KclError::new_semantic(KclErrorDetails::new(
2092 "Expected number for Point2d x coordinate".to_owned(),
2093 vec![args.source_range],
2094 )));
2095 };
2096 let Some(pt_y) = pt_y_val.as_ty_f64() else {
2097 return Err(KclError::new_semantic(KclErrorDetails::new(
2098 "Expected number for Point2d y coordinate".to_owned(),
2099 vec![args.source_range],
2100 )));
2101 };
2102 let known_x_val = normalize_to_solver_distance_unit(
2103 &KclValue::Number {
2104 value: known_x.n,
2105 ty: known_x.ty,
2106 meta: vec![args.source_range.into()],
2107 },
2108 args.source_range,
2109 exec_state,
2110 "coincident constraint value",
2111 )?;
2112 let Some(known_x_f) = known_x_val.as_ty_f64() else {
2113 return Err(KclError::new_semantic(KclErrorDetails::new(
2114 "Expected number for known x coordinate".to_owned(),
2115 vec![args.source_range],
2116 )));
2117 };
2118 let known_y_val = normalize_to_solver_distance_unit(
2119 &KclValue::Number {
2120 value: known_y.n,
2121 ty: known_y.ty,
2122 meta: vec![args.source_range.into()],
2123 },
2124 args.source_range,
2125 exec_state,
2126 "coincident constraint value",
2127 )?;
2128 let Some(known_y_f) = known_y_val.as_ty_f64() else {
2129 return Err(KclError::new_semantic(KclErrorDetails::new(
2130 "Expected number for known y coordinate".to_owned(),
2131 vec![args.source_range],
2132 )));
2133 };
2134 if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
2135 return Err(KclError::new_semantic(KclErrorDetails::new(
2136 "Coincident constraint between two fixed points failed since coordinates differ"
2137 .to_owned(),
2138 vec![args.source_range],
2139 )));
2140 }
2141 Ok(KclValue::none())
2142 }
2143 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2144 "Point coordinates must have consistent known/unknown status for coincident constraint"
2145 .to_owned(),
2146 vec![args.source_range],
2147 ))),
2148 }
2149 }
2150 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2151 "A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
2152 vec![args.source_range],
2153 ))),
2154 }
2155 }
2156 _ => {
2158 let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
2159 let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
2160 match (pt0, pt1) {
2161 (Some(a), Some(b)) => {
2162 let a_x = normalize_to_solver_distance_unit(
2164 &KclValue::Number {
2165 value: a[0].n,
2166 ty: a[0].ty,
2167 meta: vec![args.source_range.into()],
2168 },
2169 args.source_range,
2170 exec_state,
2171 "coincident constraint value",
2172 )?;
2173 let a_y = normalize_to_solver_distance_unit(
2174 &KclValue::Number {
2175 value: a[1].n,
2176 ty: a[1].ty,
2177 meta: vec![args.source_range.into()],
2178 },
2179 args.source_range,
2180 exec_state,
2181 "coincident constraint value",
2182 )?;
2183 let b_x = normalize_to_solver_distance_unit(
2184 &KclValue::Number {
2185 value: b[0].n,
2186 ty: b[0].ty,
2187 meta: vec![args.source_range.into()],
2188 },
2189 args.source_range,
2190 exec_state,
2191 "coincident constraint value",
2192 )?;
2193 let b_y = normalize_to_solver_distance_unit(
2194 &KclValue::Number {
2195 value: b[1].n,
2196 ty: b[1].ty,
2197 meta: vec![args.source_range.into()],
2198 },
2199 args.source_range,
2200 exec_state,
2201 "coincident constraint value",
2202 )?;
2203 if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
2204 || a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
2205 {
2206 return Err(KclError::new_semantic(KclErrorDetails::new(
2207 "Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
2208 vec![args.source_range],
2209 )));
2210 }
2211 Ok(KclValue::none())
2212 }
2213 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2214 "All inputs must be Segments or Point2d values".to_owned(),
2215 vec![args.source_range],
2216 ))),
2217 }
2218 }
2219 }
2220}
2221
2222fn coincident_points(
2223 point_values: Vec<KclValue>,
2224 exec_state: &mut ExecState,
2225 args: Args,
2226) -> Result<KclValue, KclError> {
2227 if point_values.len() < 2 {
2228 return Err(KclError::new_semantic(KclErrorDetails::new(
2229 "coincident() point list must contain at least two points".to_owned(),
2230 vec![args.source_range],
2231 )));
2232 }
2233
2234 let points = point_values
2236 .iter()
2237 .map(|point| extract_multi_coincident_point(point, args.source_range))
2238 .collect::<Result<Vec<_>, _>>()?;
2239
2240 let constraint_segments = points.iter().map(|point| point.constraint_segment).collect::<Vec<_>>();
2241
2242 let mut variable_points = Vec::new();
2243 let mut fixed_points = Vec::new();
2244 for point in points {
2245 match point.point {
2246 PointToAlign::Variable { x, y } => variable_points.push([x, y]),
2247 PointToAlign::Fixed { x, y } => fixed_points.push([x, y]),
2248 }
2249 }
2250
2251 let mut solver_constraints = Vec::with_capacity(point_values.len().saturating_sub(1) * 2);
2252 if let Some((anchor_fixed, remaining_fixed_points)) = fixed_points.split_first() {
2253 if remaining_fixed_points
2255 .iter()
2256 .any(|point| !fixed_points_match(point, anchor_fixed))
2257 {
2258 return Err(KclError::new_semantic(KclErrorDetails::new(
2259 "coincident() with more than two inputs can include at most one fixed point location".to_owned(),
2260 vec![args.source_range],
2261 )));
2262 }
2263
2264 let anchor_x = ty_f64_to_kcl_value(anchor_fixed[0].clone(), args.source_range);
2265 let anchor_y = ty_f64_to_kcl_value(anchor_fixed[1].clone(), args.source_range);
2266 for point in variable_points {
2267 let (constraint_x, constraint_y) =
2268 coincident_constraints_fixed(point[0], point[1], &anchor_x, &anchor_y, exec_state, &args)?;
2269 solver_constraints.push(constraint_x);
2270 solver_constraints.push(constraint_y);
2271 }
2272 } else {
2273 let mut points = variable_points.into_iter();
2275 let first_point = points.next().ok_or_else(|| {
2276 KclError::new_semantic(KclErrorDetails::new(
2277 "coincident() point list must contain at least two points".to_owned(),
2278 vec![args.source_range],
2279 ))
2280 })?;
2281 let anchor = datum_point(first_point, args.source_range)?;
2282 for point in points {
2283 let solver_point = datum_point(point, args.source_range)?;
2284 solver_constraints.push(SolverConstraint::PointsCoincident(anchor, solver_point));
2285 }
2286 }
2287
2288 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2289 return Err(KclError::new_semantic(KclErrorDetails::new(
2290 "coincident() can only be used inside a sketch block".to_owned(),
2291 vec![args.source_range],
2292 )));
2293 };
2294 sketch_state.solver_constraints.extend(solver_constraints);
2295
2296 let constraint_id = exec_state.next_object_id();
2298 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2299 debug_assert!(false, "Constraint created outside a sketch block");
2300 return Ok(KclValue::none());
2301 };
2302 sketch_state.sketch_constraints.push(constraint_id);
2303 let constraint = Constraint::Coincident(Coincident {
2304 segments: constraint_segments,
2305 });
2306 track_constraint(constraint_id, constraint, exec_state, &args);
2307
2308 Ok(KclValue::none())
2309}
2310
2311fn extract_multi_coincident_point(
2312 input: &KclValue,
2313 source_range: crate::SourceRange,
2314) -> Result<CoincidentPointInput, KclError> {
2315 match input {
2317 KclValue::Segment { value: segment } => {
2318 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2319 return Err(KclError::new_semantic(KclErrorDetails::new(
2320 "coincident() with more than two inputs only supports unsolved points or ORIGIN".to_owned(),
2321 vec![source_range],
2322 )));
2323 };
2324 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2325 return Err(KclError::new_semantic(KclErrorDetails::new(
2326 format!(
2327 "coincident() with more than two inputs only supports points or ORIGIN, but one item is {}",
2328 unsolved.kind.human_friendly_kind_with_article()
2329 ),
2330 vec![source_range],
2331 )));
2332 };
2333 match (&position[0], &position[1]) {
2334 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(CoincidentPointInput {
2335 point: PointToAlign::Fixed {
2336 x: x.to_owned(),
2337 y: y.to_owned(),
2338 },
2339 constraint_segment: unsolved.object_id.into(),
2340 }),
2341 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(CoincidentPointInput {
2342 point: PointToAlign::Variable { x: *x, y: *y },
2343 constraint_segment: unsolved.object_id.into(),
2344 }),
2345 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..))
2347 | (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => Err(KclError::new_semantic(
2348 KclErrorDetails::new(
2349 "coincident() with more than two inputs requires each point to be fully fixed or fully variable"
2350 .to_owned(),
2351 vec![source_range],
2352 ),
2353 )),
2354 }
2355 }
2356 point if point2d_is_origin(point) => {
2357 let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point) else {
2358 debug_assert!(false, "Origin literal should coerce to Point2d");
2359 return Err(KclError::new_internal(KclErrorDetails::new(
2360 "Origin literal could not be converted to a point".to_owned(),
2361 vec![source_range],
2362 )));
2363 };
2364 Ok(CoincidentPointInput {
2365 point: PointToAlign::Fixed { x, y },
2366 constraint_segment: ConstraintSegment::ORIGIN,
2367 })
2368 }
2369 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2370 "coincident() with more than two inputs only supports points and ORIGIN".to_owned(),
2371 vec![source_range],
2372 ))),
2373 }
2374}
2375
2376#[derive(Debug, Clone)]
2377struct CoincidentPointInput {
2378 point: PointToAlign,
2379 constraint_segment: ConstraintSegment,
2380}
2381
2382fn fixed_points_match(a: &[TyF64; 2], b: &[TyF64; 2]) -> bool {
2383 a[0].to_mm() == b[0].to_mm() && a[1].to_mm() == b[1].to_mm()
2384}
2385
2386fn ty_f64_to_kcl_value(value: TyF64, source_range: crate::SourceRange) -> KclValue {
2387 KclValue::Number {
2388 value: value.n,
2389 ty: value.ty,
2390 meta: vec![source_range.into()],
2391 }
2392}
2393
2394fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
2395 let sketch_id = {
2396 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2397 debug_assert!(false, "Constraint created outside a sketch block");
2398 return;
2399 };
2400 sketch_state.sketch_id
2401 };
2402 let Some(sketch_id) = sketch_id else {
2403 debug_assert!(false, "Constraint created without a sketch id");
2404 return;
2405 };
2406 let artifact_id = exec_state.next_artifact_id();
2407 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2408 id: artifact_id,
2409 sketch_id,
2410 constraint_id,
2411 constraint_type: SketchBlockConstraintType::from(&constraint),
2412 code_ref: CodeRef::placeholder(args.source_range),
2413 }));
2414 exec_state.add_scene_object(
2415 Object {
2416 id: constraint_id,
2417 kind: ObjectKind::Constraint { constraint },
2418 label: Default::default(),
2419 comments: Default::default(),
2420 artifact_id,
2421 source: SourceRef::new(args.source_range, args.node_path.clone()),
2422 },
2423 args.source_range,
2424 );
2425}
2426
2427fn coincident_constraints_fixed(
2429 p0_x: SketchVarId,
2430 p0_y: SketchVarId,
2431 p1_x: &KclValue,
2432 p1_y: &KclValue,
2433 exec_state: &mut ExecState,
2434 args: &Args,
2435) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
2436 let p1_x_number_value =
2437 normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
2438 let p1_y_number_value =
2439 normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
2440 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
2441 let message = format!(
2442 "Expected number after coercion, but found {}",
2443 p1_x_number_value.human_friendly_type()
2444 );
2445 debug_assert!(false, "{}", &message);
2446 return Err(KclError::new_internal(KclErrorDetails::new(
2447 message,
2448 vec![args.source_range],
2449 )));
2450 };
2451 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
2452 let message = format!(
2453 "Expected number after coercion, but found {}",
2454 p1_y_number_value.human_friendly_type()
2455 );
2456 debug_assert!(false, "{}", &message);
2457 return Err(KclError::new_internal(KclErrorDetails::new(
2458 message,
2459 vec![args.source_range],
2460 )));
2461 };
2462 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
2463 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
2464 Ok((constraint_x, constraint_y))
2465}
2466
2467pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2468 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2469 "points",
2470 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2471 exec_state,
2472 )?;
2473 let label_position = get_constraint_label_position(exec_state, &args, "distance")?;
2474 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
2475 KclError::new_semantic(KclErrorDetails::new(
2476 "must have two input points".to_owned(),
2477 vec![args.source_range],
2478 ))
2479 })?;
2480
2481 match (&point0, &point1) {
2482 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2483 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2484 return Err(KclError::new_semantic(KclErrorDetails::new(
2485 "first point must be an unsolved segment".to_owned(),
2486 vec![args.source_range],
2487 )));
2488 };
2489 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2490 return Err(KclError::new_semantic(KclErrorDetails::new(
2491 "second point must be an unsolved segment".to_owned(),
2492 vec![args.source_range],
2493 )));
2494 };
2495 match (&unsolved0.kind, &unsolved1.kind) {
2496 (
2497 UnsolvedSegmentKind::Point { position: pos0, .. },
2498 UnsolvedSegmentKind::Point { position: pos1, .. },
2499 ) => {
2500 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2503 (
2504 UnsolvedExpr::Unknown(p0_x),
2505 UnsolvedExpr::Unknown(p0_y),
2506 UnsolvedExpr::Unknown(p1_x),
2507 UnsolvedExpr::Unknown(p1_y),
2508 ) => {
2509 let sketch_constraint = SketchConstraint {
2511 kind: SketchConstraintKind::Distance {
2512 points: [
2513 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2514 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2515 object_id: unsolved0.object_id,
2516 }),
2517 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2518 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2519 object_id: unsolved1.object_id,
2520 }),
2521 ],
2522 label_position,
2523 },
2524 meta: vec![args.source_range.into()],
2525 };
2526 Ok(KclValue::SketchConstraint {
2527 value: Box::new(sketch_constraint),
2528 })
2529 }
2530 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2531 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
2532 vec![args.source_range],
2533 ))),
2534 }
2535 }
2536 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Line { .. })
2537 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Point { .. }) => {
2538 let (point_segment, line_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2539 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Line { .. }) => (unsolved0, unsolved1),
2540 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Point { .. }) => (unsolved1, unsolved0),
2541 _ => {
2542 return Err(KclError::new_semantic(KclErrorDetails::new(
2543 "distance() expected a point-line segment pair".to_owned(),
2544 vec![args.source_range],
2545 )));
2546 }
2547 };
2548 let point =
2549 constrainable_point_from_unsolved_segment(point_segment, "distance", args.source_range)?;
2550 let line = constrainable_line_from_unsolved_segment(line_segment, "distance", args.source_range)?;
2551
2552 Ok(KclValue::SketchConstraint {
2553 value: Box::new(SketchConstraint {
2554 kind: SketchConstraintKind::PointLineDistance {
2555 point: ConstrainablePoint2dOrOrigin::Point(point),
2556 line,
2557 input_object_ids: [Some(unsolved0.object_id), Some(unsolved1.object_id)],
2558 label_position,
2559 },
2560 meta: vec![args.source_range.into()],
2561 }),
2562 })
2563 }
2564 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Arc { .. })
2565 | (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Circle { .. })
2566 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Point { .. })
2567 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Point { .. }) => {
2568 let (point_segment, circular_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2569 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Arc { .. })
2570 | (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2571 (unsolved0, unsolved1)
2572 }
2573 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Point { .. })
2574 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Point { .. }) => {
2575 (unsolved1, unsolved0)
2576 }
2577 _ => {
2578 return Err(KclError::new_semantic(KclErrorDetails::new(
2579 "distance() expected a point-arc or point-circle segment pair".to_owned(),
2580 vec![args.source_range],
2581 )));
2582 }
2583 };
2584 let point =
2585 constrainable_point_from_unsolved_segment(point_segment, "distance", args.source_range)?;
2586 let (center, start, end) =
2587 constrainable_circular_from_unsolved_segment(circular_segment, "distance", args.source_range)?;
2588
2589 Ok(KclValue::SketchConstraint {
2590 value: Box::new(SketchConstraint {
2591 kind: SketchConstraintKind::PointCircularDistance {
2592 point: ConstrainablePoint2dOrOrigin::Point(point),
2593 center,
2594 start,
2595 end,
2596 input_object_ids: [Some(unsolved0.object_id), Some(unsolved1.object_id)],
2597 label_position,
2598 },
2599 meta: vec![args.source_range.into()],
2600 }),
2601 })
2602 }
2603 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Arc { .. })
2604 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Circle { .. })
2605 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Line { .. })
2606 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Line { .. }) => {
2607 let (line_segment, circular_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2608 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Arc { .. })
2609 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2610 (unsolved0, unsolved1)
2611 }
2612 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Line { .. })
2613 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Line { .. }) => {
2614 (unsolved1, unsolved0)
2615 }
2616 _ => {
2617 return Err(KclError::new_semantic(KclErrorDetails::new(
2618 "distance() expected a line-arc or line-circle segment pair".to_owned(),
2619 vec![args.source_range],
2620 )));
2621 }
2622 };
2623 let line = constrainable_line_from_unsolved_segment(line_segment, "distance", args.source_range)?;
2624 let (center, start, end) =
2625 constrainable_circular_from_unsolved_segment(circular_segment, "distance", args.source_range)?;
2626
2627 Ok(KclValue::SketchConstraint {
2628 value: Box::new(SketchConstraint {
2629 kind: SketchConstraintKind::LineCircularDistance {
2630 line,
2631 center,
2632 start,
2633 end,
2634 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2635 label_position,
2636 },
2637 meta: vec![args.source_range.into()],
2638 }),
2639 })
2640 }
2641 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Arc { .. })
2642 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Circle { .. })
2643 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Arc { .. })
2644 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2645 let (center0, start0, end0) =
2646 constrainable_circular_from_unsolved_segment(unsolved0, "distance", args.source_range)?;
2647 let (center1, start1, end1) =
2648 constrainable_circular_from_unsolved_segment(unsolved1, "distance", args.source_range)?;
2649
2650 Ok(KclValue::SketchConstraint {
2651 value: Box::new(SketchConstraint {
2652 kind: SketchConstraintKind::CircularCircularDistance {
2653 center0,
2654 start0,
2655 end0,
2656 center1,
2657 start1,
2658 end1,
2659 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2660 label_position,
2661 },
2662 meta: vec![args.source_range.into()],
2663 }),
2664 })
2665 }
2666 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Line { .. }) => {
2667 let line0 = constrainable_line_from_unsolved_segment(unsolved0, "distance", args.source_range)?;
2668 let line1 = constrainable_line_from_unsolved_segment(unsolved1, "distance", args.source_range)?;
2669
2670 Ok(KclValue::SketchConstraint {
2671 value: Box::new(SketchConstraint {
2672 kind: SketchConstraintKind::LineLineDistance {
2673 line0,
2674 line1,
2675 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2676 label_position,
2677 },
2678 meta: vec![args.source_range.into()],
2679 }),
2680 })
2681 }
2682 (UnsolvedSegmentKind::ControlPointSpline { .. }, _)
2683 | (_, UnsolvedSegmentKind::ControlPointSpline { .. }) => {
2684 Err(KclError::new_semantic(KclErrorDetails::new(
2685 "distance() does not yet support control point spline segments".to_owned(),
2686 vec![args.source_range],
2687 )))
2688 }
2689 }
2690 }
2691 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2693 if !point2d_is_origin(point2d) {
2694 return Err(KclError::new_semantic(KclErrorDetails::new(
2695 "distance() Point2d arguments must be ORIGIN".to_owned(),
2696 vec![args.source_range],
2697 )));
2698 }
2699
2700 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2701 return Err(KclError::new_semantic(KclErrorDetails::new(
2702 "segment must be an unsolved segment".to_owned(),
2703 vec![args.source_range],
2704 )));
2705 };
2706 let segment_first = matches!((&point0, &point1), (KclValue::Segment { .. }, _));
2707 let input_object_ids = if segment_first {
2708 [Some(unsolved.object_id), None]
2709 } else {
2710 [None, Some(unsolved.object_id)]
2711 };
2712 match &unsolved.kind {
2713 UnsolvedSegmentKind::Point { position, .. } => match (&position[0], &position[1]) {
2714 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2715 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2716 vars: crate::front::Point2d {
2717 x: *point_x,
2718 y: *point_y,
2719 },
2720 object_id: unsolved.object_id,
2721 });
2722 let points = if segment_first {
2723 [point, ConstrainablePoint2dOrOrigin::Origin]
2724 } else {
2725 [ConstrainablePoint2dOrOrigin::Origin, point]
2726 };
2727 Ok(KclValue::SketchConstraint {
2728 value: Box::new(SketchConstraint {
2729 kind: SketchConstraintKind::Distance { points, label_position },
2730 meta: vec![args.source_range.into()],
2731 }),
2732 })
2733 }
2734 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2735 "unimplemented: distance() point arguments must be sketch vars in all coordinates".to_owned(),
2736 vec![args.source_range],
2737 ))),
2738 },
2739 UnsolvedSegmentKind::Line { .. } => {
2740 let line = constrainable_line_from_unsolved_segment(unsolved, "distance", args.source_range)?;
2741 Ok(KclValue::SketchConstraint {
2742 value: Box::new(SketchConstraint {
2743 kind: SketchConstraintKind::PointLineDistance {
2744 point: ConstrainablePoint2dOrOrigin::Origin,
2745 line,
2746 input_object_ids,
2747 label_position,
2748 },
2749 meta: vec![args.source_range.into()],
2750 }),
2751 })
2752 }
2753 UnsolvedSegmentKind::Arc { .. } | UnsolvedSegmentKind::Circle { .. } => {
2754 let (center, start, end) =
2755 constrainable_circular_from_unsolved_segment(unsolved, "distance", args.source_range)?;
2756 Ok(KclValue::SketchConstraint {
2757 value: Box::new(SketchConstraint {
2758 kind: SketchConstraintKind::PointCircularDistance {
2759 point: ConstrainablePoint2dOrOrigin::Origin,
2760 center,
2761 start,
2762 end,
2763 input_object_ids,
2764 label_position,
2765 },
2766 meta: vec![args.source_range.into()],
2767 }),
2768 })
2769 }
2770 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_semantic(KclErrorDetails::new(
2771 "distance() does not yet support control point spline segments".to_owned(),
2772 vec![args.source_range],
2773 ))),
2774 }
2775 }
2776 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2777 "distance() arguments must be point segments or ORIGIN".to_owned(),
2778 vec![args.source_range],
2779 ))),
2780 }
2781}
2782
2783fn get_constraint_label_position(
2784 exec_state: &mut ExecState,
2785 args: &Args,
2786 constraint_name: &str,
2787) -> Result<Option<Point2d<Number>>, KclError> {
2788 let label_position = args.get_kw_arg_opt::<[TyF64; 2]>("labelPosition", &RuntimeType::point2d(), exec_state)?;
2789
2790 label_position
2791 .map(|label| {
2792 TyF64::to_point2d(&label).map_err(|_| {
2793 KclError::new_internal(KclErrorDetails::new(
2794 format!("Could not convert {constraint_name} label position to a Point2d"),
2795 vec![args.source_range],
2796 ))
2797 })
2798 })
2799 .transpose()
2800}
2801
2802fn create_circular_radius_constraint(
2805 segment: KclValue,
2806 constraint_kind: impl Fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
2807 source_range: crate::SourceRange,
2808) -> Result<SketchConstraint, KclError> {
2809 let dummy_constraint = constraint_kind([
2811 ConstrainablePoint2d {
2812 vars: crate::front::Point2d {
2813 x: SketchVarId(0),
2814 y: SketchVarId(0),
2815 },
2816 object_id: ObjectId(0),
2817 },
2818 ConstrainablePoint2d {
2819 vars: crate::front::Point2d {
2820 x: SketchVarId(0),
2821 y: SketchVarId(0),
2822 },
2823 object_id: ObjectId(0),
2824 },
2825 ]);
2826 let function_name = dummy_constraint.name();
2827
2828 let KclValue::Segment { value: seg } = segment else {
2829 return Err(KclError::new_semantic(KclErrorDetails::new(
2830 format!("{}() argument must be a segment", function_name),
2831 vec![source_range],
2832 )));
2833 };
2834 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2835 return Err(KclError::new_semantic(KclErrorDetails::new(
2836 "segment must be unsolved".to_owned(),
2837 vec![source_range],
2838 )));
2839 };
2840 match &unsolved.kind {
2841 UnsolvedSegmentKind::Arc {
2842 center,
2843 start,
2844 center_object_id,
2845 start_object_id,
2846 ..
2847 }
2848 | UnsolvedSegmentKind::Circle {
2849 center,
2850 start,
2851 center_object_id,
2852 start_object_id,
2853 ..
2854 } => {
2855 match (¢er[0], ¢er[1], &start[0], &start[1]) {
2857 (
2858 UnsolvedExpr::Unknown(center_x),
2859 UnsolvedExpr::Unknown(center_y),
2860 UnsolvedExpr::Unknown(start_x),
2861 UnsolvedExpr::Unknown(start_y),
2862 ) => {
2863 let sketch_constraint = SketchConstraint {
2865 kind: constraint_kind([
2866 ConstrainablePoint2d {
2867 vars: crate::front::Point2d {
2868 x: *center_x,
2869 y: *center_y,
2870 },
2871 object_id: *center_object_id,
2872 },
2873 ConstrainablePoint2d {
2874 vars: crate::front::Point2d {
2875 x: *start_x,
2876 y: *start_y,
2877 },
2878 object_id: *start_object_id,
2879 },
2880 ]),
2881 meta: vec![source_range.into()],
2882 };
2883 Ok(sketch_constraint)
2884 }
2885 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2886 format!(
2887 "unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
2888 function_name
2889 ),
2890 vec![source_range],
2891 ))),
2892 }
2893 }
2894 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2895 format!("{}() argument must be an arc or circle segment", function_name),
2896 vec![source_range],
2897 ))),
2898 }
2899}
2900
2901pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2902 let segment: KclValue =
2903 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
2904 let label_position = get_constraint_label_position(exec_state, &args, "radius")?;
2905
2906 create_circular_radius_constraint(
2907 segment,
2908 |points| SketchConstraintKind::Radius {
2909 points,
2910 label_position: label_position.clone(),
2911 },
2912 args.source_range,
2913 )
2914 .map(|constraint| KclValue::SketchConstraint {
2915 value: Box::new(constraint),
2916 })
2917}
2918
2919pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2920 let segment: KclValue =
2921 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
2922 let label_position = get_constraint_label_position(exec_state, &args, "diameter")?;
2923
2924 create_circular_radius_constraint(
2925 segment,
2926 |points| SketchConstraintKind::Diameter {
2927 points,
2928 label_position: label_position.clone(),
2929 },
2930 args.source_range,
2931 )
2932 .map(|constraint| KclValue::SketchConstraint {
2933 value: Box::new(constraint),
2934 })
2935}
2936
2937pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2938 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2939 "points",
2940 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2941 exec_state,
2942 )?;
2943 let label_position = get_constraint_label_position(exec_state, &args, "horizontalDistance")?;
2944 let [p1, p2] = points.as_slice() else {
2945 return Err(KclError::new_semantic(KclErrorDetails::new(
2946 "must have two input points".to_owned(),
2947 vec![args.source_range],
2948 )));
2949 };
2950 match (p1, p2) {
2951 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2952 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2953 return Err(KclError::new_semantic(KclErrorDetails::new(
2954 "first point must be an unsolved segment".to_owned(),
2955 vec![args.source_range],
2956 )));
2957 };
2958 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2959 return Err(KclError::new_semantic(KclErrorDetails::new(
2960 "second point must be an unsolved segment".to_owned(),
2961 vec![args.source_range],
2962 )));
2963 };
2964 match (&unsolved0.kind, &unsolved1.kind) {
2965 (
2966 UnsolvedSegmentKind::Point { position: pos0, .. },
2967 UnsolvedSegmentKind::Point { position: pos1, .. },
2968 ) => {
2969 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2972 (
2973 UnsolvedExpr::Unknown(p0_x),
2974 UnsolvedExpr::Unknown(p0_y),
2975 UnsolvedExpr::Unknown(p1_x),
2976 UnsolvedExpr::Unknown(p1_y),
2977 ) => {
2978 let sketch_constraint = SketchConstraint {
2980 kind: SketchConstraintKind::HorizontalDistance {
2981 points: [
2982 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2983 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2984 object_id: unsolved0.object_id,
2985 }),
2986 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2987 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2988 object_id: unsolved1.object_id,
2989 }),
2990 ],
2991 label_position,
2992 },
2993 meta: vec![args.source_range.into()],
2994 };
2995 Ok(KclValue::SketchConstraint {
2996 value: Box::new(sketch_constraint),
2997 })
2998 }
2999 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3000 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
3001 .to_owned(),
3002 vec![args.source_range],
3003 ))),
3004 }
3005 }
3006 (
3007 UnsolvedSegmentKind::Point { .. },
3008 UnsolvedSegmentKind::Line { .. },
3009 )
3010 | (
3011 UnsolvedSegmentKind::Line { .. },
3012 UnsolvedSegmentKind::Point { .. },
3013 ) => Err(KclError::new_semantic(KclErrorDetails::new(
3014 "horizontalDistance() between a point and a line is invalid because the constraint is under-specified".to_owned(),
3015 vec![args.source_range],
3016 ))),
3017 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3018 "horizontalDistance() arguments must be unsolved points".to_owned(),
3019 vec![args.source_range],
3020 ))),
3021 }
3022 }
3023 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
3025 if !point2d_is_origin(point2d) {
3026 return Err(KclError::new_semantic(KclErrorDetails::new(
3027 "horizontalDistance() Point2d arguments must be ORIGIN".to_owned(),
3028 vec![args.source_range],
3029 )));
3030 }
3031
3032 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
3033 return Err(KclError::new_semantic(KclErrorDetails::new(
3034 "segment must be an unsolved segment".to_owned(),
3035 vec![args.source_range],
3036 )));
3037 };
3038 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3039 return Err(KclError::new_semantic(KclErrorDetails::new(
3040 "horizontalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
3041 vec![args.source_range],
3042 )));
3043 };
3044 match (&position[0], &position[1]) {
3045 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
3046 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3047 vars: crate::front::Point2d {
3048 x: *point_x,
3049 y: *point_y,
3050 },
3051 object_id: unsolved.object_id,
3052 });
3053 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
3054 [point, ConstrainablePoint2dOrOrigin::Origin]
3055 } else {
3056 [ConstrainablePoint2dOrOrigin::Origin, point]
3057 };
3058 Ok(KclValue::SketchConstraint {
3059 value: Box::new(SketchConstraint {
3060 kind: SketchConstraintKind::HorizontalDistance { points, label_position },
3061 meta: vec![args.source_range.into()],
3062 }),
3063 })
3064 }
3065 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3066 "unimplemented: horizontalDistance() point arguments must be sketch vars in all coordinates"
3067 .to_owned(),
3068 vec![args.source_range],
3069 ))),
3070 }
3071 }
3072 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3073 "horizontalDistance() arguments must be point segments or ORIGIN".to_owned(),
3074 vec![args.source_range],
3075 ))),
3076 }
3077}
3078
3079pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3080 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
3081 "points",
3082 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3083 exec_state,
3084 )?;
3085 let label_position = get_constraint_label_position(exec_state, &args, "verticalDistance")?;
3086 let [p1, p2] = points.as_slice() else {
3087 return Err(KclError::new_semantic(KclErrorDetails::new(
3088 "must have two input points".to_owned(),
3089 vec![args.source_range],
3090 )));
3091 };
3092 match (p1, p2) {
3093 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
3094 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
3095 return Err(KclError::new_semantic(KclErrorDetails::new(
3096 "first point must be an unsolved segment".to_owned(),
3097 vec![args.source_range],
3098 )));
3099 };
3100 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
3101 return Err(KclError::new_semantic(KclErrorDetails::new(
3102 "second point must be an unsolved segment".to_owned(),
3103 vec![args.source_range],
3104 )));
3105 };
3106 match (&unsolved0.kind, &unsolved1.kind) {
3107 (
3108 UnsolvedSegmentKind::Point { position: pos0, .. },
3109 UnsolvedSegmentKind::Point { position: pos1, .. },
3110 ) => {
3111 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
3114 (
3115 UnsolvedExpr::Unknown(p0_x),
3116 UnsolvedExpr::Unknown(p0_y),
3117 UnsolvedExpr::Unknown(p1_x),
3118 UnsolvedExpr::Unknown(p1_y),
3119 ) => {
3120 let sketch_constraint = SketchConstraint {
3122 kind: SketchConstraintKind::VerticalDistance {
3123 points: [
3124 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3125 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
3126 object_id: unsolved0.object_id,
3127 }),
3128 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3129 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
3130 object_id: unsolved1.object_id,
3131 }),
3132 ],
3133 label_position,
3134 },
3135 meta: vec![args.source_range.into()],
3136 };
3137 Ok(KclValue::SketchConstraint {
3138 value: Box::new(sketch_constraint),
3139 })
3140 }
3141 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3142 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
3143 .to_owned(),
3144 vec![args.source_range],
3145 ))),
3146 }
3147 }
3148 (
3149 UnsolvedSegmentKind::Point { .. },
3150 UnsolvedSegmentKind::Line { .. },
3151 )
3152 | (
3153 UnsolvedSegmentKind::Line { .. },
3154 UnsolvedSegmentKind::Point { .. },
3155 ) => Err(KclError::new_semantic(KclErrorDetails::new(
3156 "verticalDistance() between a point and a line is invalid because the constraint is under-specified".to_owned(),
3157 vec![args.source_range],
3158 ))),
3159 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3160 "verticalDistance() arguments must be unsolved points".to_owned(),
3161 vec![args.source_range],
3162 ))),
3163 }
3164 }
3165 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
3166 if !point2d_is_origin(point2d) {
3167 return Err(KclError::new_semantic(KclErrorDetails::new(
3168 "verticalDistance() Point2d arguments must be ORIGIN".to_owned(),
3169 vec![args.source_range],
3170 )));
3171 }
3172
3173 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
3174 return Err(KclError::new_semantic(KclErrorDetails::new(
3175 "segment must be an unsolved segment".to_owned(),
3176 vec![args.source_range],
3177 )));
3178 };
3179 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3180 return Err(KclError::new_semantic(KclErrorDetails::new(
3181 "verticalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
3182 vec![args.source_range],
3183 )));
3184 };
3185 match (&position[0], &position[1]) {
3186 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
3187 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3188 vars: crate::front::Point2d {
3189 x: *point_x,
3190 y: *point_y,
3191 },
3192 object_id: unsolved.object_id,
3193 });
3194 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
3195 [point, ConstrainablePoint2dOrOrigin::Origin]
3196 } else {
3197 [ConstrainablePoint2dOrOrigin::Origin, point]
3198 };
3199 Ok(KclValue::SketchConstraint {
3200 value: Box::new(SketchConstraint {
3201 kind: SketchConstraintKind::VerticalDistance { points, label_position },
3202 meta: vec![args.source_range.into()],
3203 }),
3204 })
3205 }
3206 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3207 "unimplemented: verticalDistance() point arguments must be sketch vars in all coordinates"
3208 .to_owned(),
3209 vec![args.source_range],
3210 ))),
3211 }
3212 }
3213 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3214 "verticalDistance() arguments must be point segments or ORIGIN".to_owned(),
3215 vec![args.source_range],
3216 ))),
3217 }
3218}
3219
3220#[derive(Debug, Clone, Copy)]
3221enum MidpointPointVars {
3222 Segment {
3223 coords: [SketchVarId; 2],
3224 constraint_segment: ConstraintSegment,
3225 },
3226 Origin,
3227}
3228
3229impl MidpointPointVars {
3230 fn constraint_segment(self) -> ConstraintSegment {
3231 match self {
3232 Self::Segment { constraint_segment, .. } => constraint_segment,
3233 Self::Origin => ConstraintSegment::ORIGIN,
3234 }
3235 }
3236}
3237
3238#[derive(Debug, Clone, Copy)]
3239enum MidpointTargetVars {
3240 Line {
3241 start: [SketchVarId; 2],
3242 end: [SketchVarId; 2],
3243 object_id: ObjectId,
3244 },
3245 Arc {
3246 center: [SketchVarId; 2],
3247 start: [SketchVarId; 2],
3248 end: [SketchVarId; 2],
3249 object_id: ObjectId,
3250 },
3251}
3252
3253impl MidpointTargetVars {
3254 fn object_id(self) -> ObjectId {
3255 match self {
3256 Self::Line { object_id, .. } | Self::Arc { object_id, .. } => object_id,
3257 }
3258 }
3259}
3260
3261fn extract_midpoint_point(segment_value: &KclValue, range: crate::SourceRange) -> Result<MidpointPointVars, KclError> {
3262 if point2d_is_origin(segment_value) {
3263 return Ok(MidpointPointVars::Origin);
3264 }
3265
3266 let KclValue::Segment { value: segment } = segment_value else {
3267 return Err(KclError::new_semantic(KclErrorDetails::new(
3268 format!(
3269 "midpoint() point must be a point Segment or ORIGIN, but found {}",
3270 segment_value.human_friendly_type()
3271 ),
3272 vec![range],
3273 )));
3274 };
3275 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3276 return Err(KclError::new_semantic(KclErrorDetails::new(
3277 "midpoint() point must be an unsolved point Segment".to_owned(),
3278 vec![range],
3279 )));
3280 };
3281 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3282 return Err(KclError::new_semantic(KclErrorDetails::new(
3283 "midpoint() point must be a point Segment".to_owned(),
3284 vec![range],
3285 )));
3286 };
3287 let (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) = (&position[0], &position[1]) else {
3288 return Err(KclError::new_semantic(KclErrorDetails::new(
3289 "midpoint() point coordinates must be sketch vars".to_owned(),
3290 vec![range],
3291 )));
3292 };
3293
3294 Ok(MidpointPointVars::Segment {
3295 coords: [*point_x, *point_y],
3296 constraint_segment: unsolved.object_id.into(),
3297 })
3298}
3299
3300fn extract_midpoint_target(
3301 segment_value: &KclValue,
3302 range: crate::SourceRange,
3303) -> Result<MidpointTargetVars, KclError> {
3304 let KclValue::Segment { value: segment } = segment_value else {
3305 return Err(KclError::new_semantic(KclErrorDetails::new(
3306 format!(
3307 "midpoint() target must be a line or arc Segment, but found {}",
3308 segment_value.human_friendly_type()
3309 ),
3310 vec![range],
3311 )));
3312 };
3313 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3314 return Err(KclError::new_semantic(KclErrorDetails::new(
3315 "midpoint() target must be an unsolved line or arc Segment".to_owned(),
3316 vec![range],
3317 )));
3318 };
3319 match &unsolved.kind {
3320 UnsolvedSegmentKind::Line { start, end, .. } => {
3321 let (
3322 UnsolvedExpr::Unknown(start_x),
3323 UnsolvedExpr::Unknown(start_y),
3324 UnsolvedExpr::Unknown(end_x),
3325 UnsolvedExpr::Unknown(end_y),
3326 ) = (&start[0], &start[1], &end[0], &end[1])
3327 else {
3328 return Err(KclError::new_semantic(KclErrorDetails::new(
3329 "midpoint() line coordinates must be sketch vars".to_owned(),
3330 vec![range],
3331 )));
3332 };
3333
3334 Ok(MidpointTargetVars::Line {
3335 start: [*start_x, *start_y],
3336 end: [*end_x, *end_y],
3337 object_id: unsolved.object_id,
3338 })
3339 }
3340 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3341 let (
3342 UnsolvedExpr::Unknown(center_x),
3343 UnsolvedExpr::Unknown(center_y),
3344 UnsolvedExpr::Unknown(start_x),
3345 UnsolvedExpr::Unknown(start_y),
3346 UnsolvedExpr::Unknown(end_x),
3347 UnsolvedExpr::Unknown(end_y),
3348 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3349 else {
3350 return Err(KclError::new_semantic(KclErrorDetails::new(
3351 "midpoint() arc center/start/end coordinates must be sketch vars".to_owned(),
3352 vec![range],
3353 )));
3354 };
3355
3356 Ok(MidpointTargetVars::Arc {
3357 center: [*center_x, *center_y],
3358 start: [*start_x, *start_y],
3359 end: [*end_x, *end_y],
3360 object_id: unsolved.object_id,
3361 })
3362 }
3363 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3364 "midpoint() target must be a line or circular arc Segment".to_owned(),
3365 vec![range],
3366 ))),
3367 }
3368}
3369
3370pub async fn midpoint(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3371 let target: KclValue =
3372 args.get_unlabeled_kw_arg("input", &RuntimeType::Primitive(PrimitiveType::Segment), exec_state)?;
3373 let point: KclValue = args.get_kw_arg(
3374 "point",
3375 &RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()]),
3376 exec_state,
3377 )?;
3378 let range = args.source_range;
3379
3380 let point = extract_midpoint_point(&point, range)?;
3381 let target = extract_midpoint_target(&target, range)?;
3382
3383 let (solver_point, origin_constraints) = match point {
3384 MidpointPointVars::Segment { coords, .. } => (datum_point(coords, range)?, None),
3385 MidpointPointVars::Origin => {
3386 let (origin_point, origin_constraints) = fixed_origin_datum_point(exec_state, range, "midpoint")?;
3387 (origin_point, Some(origin_constraints))
3388 }
3389 };
3390
3391 let constraint_id = exec_state.next_object_id();
3392 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3393 return Err(KclError::new_semantic(KclErrorDetails::new(
3394 "midpoint() can only be used inside a sketch block".to_owned(),
3395 vec![range],
3396 )));
3397 };
3398
3399 if let Some(origin_constraints) = origin_constraints {
3400 sketch_state.solver_constraints.extend(origin_constraints);
3401 }
3402
3403 match target {
3404 MidpointTargetVars::Line { start, end, .. } => {
3405 sketch_state.solver_constraints.push(SolverConstraint::Midpoint(
3406 DatumLineSegment::new(datum_point(start, range)?, datum_point(end, range)?),
3407 solver_point,
3408 ));
3409 }
3410 MidpointTargetVars::Arc { center, start, end, .. } => {
3411 sketch_state
3412 .solver_constraints
3413 .extend(SolverConstraint::point_bisects_arc(
3414 DatumCircularArc {
3415 center: datum_point(center, range)?,
3416 start: datum_point(start, range)?,
3417 end: datum_point(end, range)?,
3418 },
3419 solver_point,
3420 ));
3421 }
3422 }
3423
3424 let constraint = Constraint::Midpoint(Midpoint {
3425 point: point.constraint_segment(),
3426 segment: target.object_id(),
3427 });
3428 sketch_state.sketch_constraints.push(constraint_id);
3429 track_constraint(constraint_id, constraint, exec_state, &args);
3430
3431 Ok(KclValue::none())
3432}
3433
3434pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3435 #[derive(Clone, Copy)]
3436 struct ConstrainableLine {
3437 solver_line: DatumLineSegment,
3438 object_id: ObjectId,
3439 }
3440
3441 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
3442 "lines",
3443 &RuntimeType::Array(
3444 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
3445 ArrayLen::Minimum(2),
3446 ),
3447 exec_state,
3448 )?;
3449 let range = args.source_range;
3450 let constrainable_lines: Vec<ConstrainableLine> = lines
3451 .iter()
3452 .map(|line| {
3453 let KclValue::Segment { value: segment } = line else {
3454 return Err(KclError::new_semantic(KclErrorDetails::new(
3455 "line argument must be a Segment".to_owned(),
3456 vec![args.source_range],
3457 )));
3458 };
3459 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3460 return Err(KclError::new_internal(KclErrorDetails::new(
3461 "line must be an unsolved Segment".to_owned(),
3462 vec![args.source_range],
3463 )));
3464 };
3465 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
3466 return Err(KclError::new_semantic(KclErrorDetails::new(
3467 "line argument must be a line, no other type of Segment".to_owned(),
3468 vec![args.source_range],
3469 )));
3470 };
3471 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
3472 return Err(KclError::new_semantic(KclErrorDetails::new(
3473 "line's start x coordinate must be a var".to_owned(),
3474 vec![args.source_range],
3475 )));
3476 };
3477 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
3478 return Err(KclError::new_semantic(KclErrorDetails::new(
3479 "line's start y coordinate must be a var".to_owned(),
3480 vec![args.source_range],
3481 )));
3482 };
3483 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
3484 return Err(KclError::new_semantic(KclErrorDetails::new(
3485 "line's end x coordinate must be a var".to_owned(),
3486 vec![args.source_range],
3487 )));
3488 };
3489 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
3490 return Err(KclError::new_semantic(KclErrorDetails::new(
3491 "line's end y coordinate must be a var".to_owned(),
3492 vec![args.source_range],
3493 )));
3494 };
3495
3496 let solver_line_p0 =
3497 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
3498 let solver_line_p1 =
3499 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
3500
3501 Ok(ConstrainableLine {
3502 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
3503 object_id: unsolved.object_id,
3504 })
3505 })
3506 .collect::<Result<_, _>>()?;
3507
3508 let constraint_id = exec_state.next_object_id();
3509 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3511 return Err(KclError::new_semantic(KclErrorDetails::new(
3512 "equalLength() can only be used inside a sketch block".to_owned(),
3513 vec![args.source_range],
3514 )));
3515 };
3516 let first_line = constrainable_lines[0];
3517 for line in constrainable_lines.iter().skip(1) {
3518 sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
3519 first_line.solver_line,
3520 line.solver_line,
3521 ));
3522 }
3523 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
3524 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
3525 });
3526 sketch_state.sketch_constraints.push(constraint_id);
3527 track_constraint(constraint_id, constraint, exec_state, &args);
3528 Ok(KclValue::none())
3529}
3530
3531fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
3532 Ok(DatumPoint::new_xy(
3533 coords[0].to_constraint_id(range)?,
3534 coords[1].to_constraint_id(range)?,
3535 ))
3536}
3537
3538fn sketch_var_initial_value(
3539 sketch_vars: &[KclValue],
3540 id: SketchVarId,
3541 exec_state: &mut ExecState,
3542 range: crate::SourceRange,
3543) -> Result<f64, KclError> {
3544 sketch_vars
3545 .get(id.0)
3546 .and_then(KclValue::as_sketch_var)
3547 .map(|sketch_var| {
3548 sketch_var
3549 .initial_value_to_solver_units(exec_state, range, "equalRadius() hidden shared radius initial value")
3550 .map(|value| value.n)
3551 })
3552 .transpose()?
3553 .ok_or_else(|| {
3554 KclError::new_internal(KclErrorDetails::new(
3555 format!("Missing sketch variable initial value for id {}", id.0),
3556 vec![range],
3557 ))
3558 })
3559}
3560
3561fn radius_guess(
3562 sketch_vars: &[KclValue],
3563 center: [SketchVarId; 2],
3564 point: [SketchVarId; 2],
3565 exec_state: &mut ExecState,
3566 range: crate::SourceRange,
3567) -> Result<f64, KclError> {
3568 let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
3569 - sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
3570 let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
3571 - sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
3572 Ok(libm::hypot(dx, dy))
3573}
3574
3575fn reflect_point_across_line(point: [f64; 2], axis_start: [f64; 2], axis_end: [f64; 2]) -> [f64; 2] {
3576 let [px, py] = point;
3577 let [ax, ay] = axis_start;
3578 let [bx, by] = axis_end;
3579 let dx = bx - ax;
3580 let dy = by - ay;
3581 let axis_len_sq = dx * dx + dy * dy;
3582 if axis_len_sq <= f64::EPSILON {
3583 return point;
3584 }
3585
3586 let point_from_axis = [px - ax, py - ay];
3587 let projection_scale = (point_from_axis[0] * dx + point_from_axis[1] * dy) / axis_len_sq;
3588 let projected = [ax + projection_scale * dx, ay + projection_scale * dy];
3589
3590 [2.0 * projected[0] - px, 2.0 * projected[1] - py]
3591}
3592
3593fn symmetric_hidden_point_guess(
3596 sketch_vars: &[KclValue],
3597 point: [SketchVarId; 2],
3598 axis: SymmetricLineVars,
3599 exec_state: &mut ExecState,
3600 range: crate::SourceRange,
3601) -> Result<[f64; 2], KclError> {
3602 let point = [
3603 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
3604 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
3605 ];
3606 let axis_start = [
3607 sketch_var_initial_value(sketch_vars, axis.start[0], exec_state, range)?,
3608 sketch_var_initial_value(sketch_vars, axis.start[1], exec_state, range)?,
3609 ];
3610 let axis_end = [
3611 sketch_var_initial_value(sketch_vars, axis.end[0], exec_state, range)?,
3612 sketch_var_initial_value(sketch_vars, axis.end[1], exec_state, range)?,
3613 ];
3614
3615 Ok(reflect_point_across_line(point, axis_start, axis_end))
3616}
3617
3618fn create_hidden_point(
3619 exec_state: &mut ExecState,
3620 initial_position: [f64; 2],
3621 range: crate::SourceRange,
3622) -> Result<[SketchVarId; 2], KclError> {
3623 let sketch_var_ty = solver_numeric_type(exec_state);
3624 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3625 return Err(KclError::new_semantic(KclErrorDetails::new(
3626 "symmetric() can only be used inside a sketch block".to_owned(),
3627 vec![range],
3628 )));
3629 };
3630
3631 let x_id = sketch_state.next_sketch_var_id();
3632 sketch_state.sketch_vars.push(KclValue::SketchVar {
3633 value: Box::new(crate::execution::SketchVar {
3634 id: x_id,
3635 initial_value: initial_position[0],
3636 ty: sketch_var_ty,
3637 node_path: None,
3639 meta: vec![],
3640 }),
3641 });
3642
3643 let y_id = sketch_state.next_sketch_var_id();
3644 sketch_state.sketch_vars.push(KclValue::SketchVar {
3645 value: Box::new(crate::execution::SketchVar {
3646 id: y_id,
3647 initial_value: initial_position[1],
3648 ty: sketch_var_ty,
3649 node_path: None,
3651 meta: vec![],
3652 }),
3653 });
3654
3655 Ok([x_id, y_id])
3656}
3657
3658pub async fn equal_radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3659 #[derive(Debug, Clone, Copy)]
3660 struct RadiusInputVars {
3661 center: [SketchVarId; 2],
3662 start: [SketchVarId; 2],
3663 end: Option<[SketchVarId; 2]>,
3664 }
3665
3666 #[derive(Debug, Clone, Copy)]
3667 enum EqualRadiusInput {
3668 Radius(RadiusInputVars),
3669 }
3670
3671 fn extract_equal_radius_input(
3672 segment_value: &KclValue,
3673 range: crate::SourceRange,
3674 ) -> Result<(EqualRadiusInput, ObjectId), KclError> {
3675 let KclValue::Segment { value: segment } = segment_value else {
3676 return Err(KclError::new_semantic(KclErrorDetails::new(
3677 format!(
3678 "equalRadius() arguments must be segments but found {}",
3679 segment_value.human_friendly_type()
3680 ),
3681 vec![range],
3682 )));
3683 };
3684 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3685 return Err(KclError::new_semantic(KclErrorDetails::new(
3686 "equalRadius() arguments must be unsolved segments".to_owned(),
3687 vec![range],
3688 )));
3689 };
3690 match &unsolved.kind {
3691 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3692 let (
3693 UnsolvedExpr::Unknown(center_x),
3694 UnsolvedExpr::Unknown(center_y),
3695 UnsolvedExpr::Unknown(start_x),
3696 UnsolvedExpr::Unknown(start_y),
3697 UnsolvedExpr::Unknown(end_x),
3698 UnsolvedExpr::Unknown(end_y),
3699 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3700 else {
3701 return Err(KclError::new_semantic(KclErrorDetails::new(
3702 "arc center/start/end coordinates must be sketch vars for equalRadius()".to_owned(),
3703 vec![range],
3704 )));
3705 };
3706 Ok((
3707 EqualRadiusInput::Radius(RadiusInputVars {
3708 center: [*center_x, *center_y],
3709 start: [*start_x, *start_y],
3710 end: Some([*end_x, *end_y]),
3711 }),
3712 unsolved.object_id,
3713 ))
3714 }
3715 UnsolvedSegmentKind::Circle { center, start, .. } => {
3716 let (
3717 UnsolvedExpr::Unknown(center_x),
3718 UnsolvedExpr::Unknown(center_y),
3719 UnsolvedExpr::Unknown(start_x),
3720 UnsolvedExpr::Unknown(start_y),
3721 ) = (¢er[0], ¢er[1], &start[0], &start[1])
3722 else {
3723 return Err(KclError::new_semantic(KclErrorDetails::new(
3724 "circle center/start coordinates must be sketch vars for equalRadius()".to_owned(),
3725 vec![range],
3726 )));
3727 };
3728 Ok((
3729 EqualRadiusInput::Radius(RadiusInputVars {
3730 center: [*center_x, *center_y],
3731 start: [*start_x, *start_y],
3732 end: None,
3733 }),
3734 unsolved.object_id,
3735 ))
3736 }
3737 other => Err(KclError::new_semantic(KclErrorDetails::new(
3738 format!(
3739 "equalRadius() currently supports only arc and circle segments, you provided {}",
3740 other.human_friendly_kind_with_article()
3741 ),
3742 vec![range],
3743 ))),
3744 }
3745 }
3746
3747 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
3748 "input",
3749 &RuntimeType::Array(
3750 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
3751 ArrayLen::Minimum(2),
3752 ),
3753 exec_state,
3754 )?;
3755 let range = args.source_range;
3756
3757 let extracted_input = input
3758 .iter()
3759 .map(|segment_value| extract_equal_radius_input(segment_value, range))
3760 .collect::<Result<Vec<_>, _>>()?;
3761 let radius_inputs: Vec<RadiusInputVars> = extracted_input
3762 .iter()
3763 .map(|(equal_radius_input, _)| match equal_radius_input {
3764 EqualRadiusInput::Radius(radius_input) => *radius_input,
3765 })
3766 .collect();
3767 let input_object_ids: Vec<ObjectId> = extracted_input.iter().map(|(_, object_id)| *object_id).collect();
3768
3769 let sketch_var_ty = solver_numeric_type(exec_state);
3770 let constraint_id = exec_state.next_object_id();
3771
3772 let sketch_vars = {
3773 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3774 return Err(KclError::new_semantic(KclErrorDetails::new(
3775 "equalRadius() can only be used inside a sketch block".to_owned(),
3776 vec![range],
3777 )));
3778 };
3779 sketch_state.sketch_vars.clone()
3780 };
3781
3782 let radius_initial_value = radius_guess(
3783 &sketch_vars,
3784 radius_inputs[0].center,
3785 radius_inputs[0].start,
3786 exec_state,
3787 range,
3788 )?;
3789
3790 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3791 return Err(KclError::new_semantic(KclErrorDetails::new(
3792 "equalRadius() can only be used inside a sketch block".to_owned(),
3793 vec![range],
3794 )));
3795 };
3796 let radius_id = sketch_state.next_sketch_var_id();
3797 sketch_state.sketch_vars.push(KclValue::SketchVar {
3798 value: Box::new(crate::execution::SketchVar {
3799 id: radius_id,
3800 initial_value: radius_initial_value,
3801 ty: sketch_var_ty,
3802 node_path: None,
3804 meta: vec![],
3805 }),
3806 });
3807 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
3808
3809 for radius_input in radius_inputs {
3810 let center = datum_point(radius_input.center, range)?;
3811 let start = datum_point(radius_input.start, range)?;
3812 sketch_state
3813 .solver_constraints
3814 .push(SolverConstraint::DistanceVar(start, center, radius));
3815 if let Some(end) = radius_input.end {
3816 let end = datum_point(end, range)?;
3817 sketch_state
3818 .solver_constraints
3819 .push(SolverConstraint::DistanceVar(end, center, radius));
3820 }
3821 }
3822
3823 let constraint = crate::front::Constraint::EqualRadius(EqualRadius {
3824 input: input_object_ids,
3825 });
3826 sketch_state.sketch_constraints.push(constraint_id);
3827 track_constraint(constraint_id, constraint, exec_state, &args);
3828
3829 Ok(KclValue::none())
3830}
3831
3832pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3833 let Some(Some(sketch_id)) = exec_state.sketch_block().map(|sb| sb.sketch_id) else {
3834 return Err(KclError::new_semantic(KclErrorDetails::new(
3835 "tangent() cannot be used outside a sketch block".to_owned(),
3836 vec![args.source_range],
3837 )));
3838 };
3839
3840 #[derive(Debug, Clone)]
3841 enum TangentInput {
3842 Line(LineVars),
3843 Circular(ArcVars),
3844 }
3845
3846 fn extract_tangent_input(
3847 segment_value: &KclValue,
3848 range: crate::SourceRange,
3849 ) -> Result<(TangentInput, ObjectId), KclError> {
3850 let KclValue::Segment { value: segment } = segment_value else {
3851 return Err(KclError::new_semantic(KclErrorDetails::new(
3852 "tangent() arguments must be segments".to_owned(),
3853 vec![range],
3854 )));
3855 };
3856 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3857 return Err(KclError::new_semantic(KclErrorDetails::new(
3858 "tangent() arguments must be unsolved segments".to_owned(),
3859 vec![range],
3860 )));
3861 };
3862 match &unsolved.kind {
3863 UnsolvedSegmentKind::Line { start, end, .. } => {
3864 let (
3865 UnsolvedExpr::Unknown(start_x),
3866 UnsolvedExpr::Unknown(start_y),
3867 UnsolvedExpr::Unknown(end_x),
3868 UnsolvedExpr::Unknown(end_y),
3869 ) = (&start[0], &start[1], &end[0], &end[1])
3870 else {
3871 return Err(KclError::new_semantic(KclErrorDetails::new(
3872 "line coordinates must be sketch vars for tangent()".to_owned(),
3873 vec![range],
3874 )));
3875 };
3876 Ok((
3877 TangentInput::Line(LineVars {
3878 start: [*start_x, *start_y],
3879 end: [*end_x, *end_y],
3880 }),
3881 unsolved.object_id,
3882 ))
3883 }
3884 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3885 let (
3886 UnsolvedExpr::Unknown(center_x),
3887 UnsolvedExpr::Unknown(center_y),
3888 UnsolvedExpr::Unknown(start_x),
3889 UnsolvedExpr::Unknown(start_y),
3890 UnsolvedExpr::Unknown(end_x),
3891 UnsolvedExpr::Unknown(end_y),
3892 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3893 else {
3894 return Err(KclError::new_semantic(KclErrorDetails::new(
3895 "arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
3896 vec![range],
3897 )));
3898 };
3899 Ok((
3900 TangentInput::Circular(ArcVars {
3901 center: [*center_x, *center_y],
3902 start: [*start_x, *start_y],
3903 end: Some([*end_x, *end_y]),
3904 }),
3905 unsolved.object_id,
3906 ))
3907 }
3908 UnsolvedSegmentKind::Circle { center, start, .. } => {
3909 let (
3910 UnsolvedExpr::Unknown(center_x),
3911 UnsolvedExpr::Unknown(center_y),
3912 UnsolvedExpr::Unknown(start_x),
3913 UnsolvedExpr::Unknown(start_y),
3914 ) = (¢er[0], ¢er[1], &start[0], &start[1])
3915 else {
3916 return Err(KclError::new_semantic(KclErrorDetails::new(
3917 "circle center/start coordinates must be sketch vars for tangent()".to_owned(),
3918 vec![range],
3919 )));
3920 };
3921 Ok((
3922 TangentInput::Circular(ArcVars {
3923 center: [*center_x, *center_y],
3924 start: [*start_x, *start_y],
3925 end: None,
3926 }),
3927 unsolved.object_id,
3928 ))
3929 }
3930 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3931 "tangent() supports only line, arc, and circle segments".to_owned(),
3932 vec![range],
3933 ))),
3934 }
3935 }
3936
3937 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
3938 "input",
3939 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3940 exec_state,
3941 )?;
3942 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
3943 KclError::new_semantic(KclErrorDetails::new(
3944 "tangent() requires exactly 2 input segments".to_owned(),
3945 vec![args.source_range],
3946 ))
3947 })?;
3948 let range = args.source_range;
3949 let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
3950 let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
3951
3952 enum TangentCase {
3953 LineCircular(LineVars, ArcVars),
3954 CircularCircular(ArcVars, ArcVars),
3955 }
3956 let tangent_case = match (input0, input1) {
3957 (TangentInput::Line(line), TangentInput::Circular(circular))
3958 | (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
3959 (TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
3960 TangentCase::CircularCircular(circular0, circular1)
3961 }
3962 (TangentInput::Line(_), TangentInput::Line(_)) => {
3963 return Err(KclError::new_semantic(KclErrorDetails::new(
3964 "tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
3965 vec![range],
3966 )));
3967 }
3968 };
3969
3970 let sketch_var_ty = solver_numeric_type(exec_state);
3971 let constraint_id = exec_state.next_object_id();
3972
3973 let sketch_vars = {
3974 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3975 return Err(KclError::new_semantic(KclErrorDetails::new(
3976 "tangent() can only be used inside a sketch block".to_owned(),
3977 vec![range],
3978 )));
3979 };
3980 sketch_state.sketch_vars.clone()
3981 };
3982
3983 match tangent_case {
3985 TangentCase::LineCircular(line, circular) => {
3986 let tangency_key = make_line_arc_tangency_key(line, circular);
3987 let tangency_side = match exec_state.constraint_state(sketch_id, &tangency_key) {
3988 Some(ConstraintState::Tangency(TangencyMode::LineCircle(side))) => side,
3989 _ => {
3990 let side = infer_line_tangent_side(&sketch_vars, line, circular.center, exec_state, range)?;
3991 exec_state.set_constraint_state(
3992 sketch_id,
3993 tangency_key,
3994 ConstraintState::Tangency(TangencyMode::LineCircle(side)),
3995 );
3996 side
3997 }
3998 };
3999 let line_p0 = datum_point(line.start, range)?;
4000 let line_p1 = datum_point(line.end, range)?;
4001 let line_datum = DatumLineSegment::new(line_p0, line_p1);
4002
4003 let center = datum_point(circular.center, range)?;
4004 let circular_start = datum_point(circular.start, range)?;
4005 let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
4006 let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
4007 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4008 return Err(KclError::new_semantic(KclErrorDetails::new(
4009 "tangent() can only be used inside a sketch block".to_owned(),
4010 vec![range],
4011 )));
4012 };
4013 let radius_id = sketch_state.next_sketch_var_id();
4014 sketch_state.sketch_vars.push(KclValue::SketchVar {
4015 value: Box::new(crate::execution::SketchVar {
4016 id: radius_id,
4017 initial_value: radius_initial_value,
4018 ty: sketch_var_ty,
4019 node_path: None,
4021 meta: vec![],
4022 }),
4023 });
4024 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
4025 let circle = DatumCircle { center, radius };
4026
4027 sketch_state
4032 .solver_constraints
4033 .push(SolverConstraint::DistanceVar(circular_start, center, radius));
4034 if let Some(circular_end) = circular_end {
4035 sketch_state
4036 .solver_constraints
4037 .push(SolverConstraint::DistanceVar(circular_end, center, radius));
4038 }
4039 sketch_state
4040 .solver_constraints
4041 .push(SolverConstraint::LineTangentToCircle(line_datum, circle, tangency_side));
4042 }
4043 TangentCase::CircularCircular(circular0, circular1) => {
4044 let tangency_key = make_arc_arc_tangency_key(circular0, circular1);
4045 let tangency_side = match exec_state.constraint_state(sketch_id, &tangency_key) {
4046 Some(ConstraintState::Tangency(TangencyMode::CircleCircle(side))) => side,
4047 _ => {
4048 let side = infer_arc_tangent_side(&sketch_vars, circular0, circular1, exec_state, range)?;
4049 exec_state.set_constraint_state(
4050 sketch_id,
4051 tangency_key,
4052 ConstraintState::Tangency(TangencyMode::CircleCircle(side)),
4053 );
4054 side
4055 }
4056 };
4057 let center0 = datum_point(circular0.center, range)?;
4058 let start0 = datum_point(circular0.start, range)?;
4059 let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
4060 let radius0_initial_value =
4061 radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
4062 let center1 = datum_point(circular1.center, range)?;
4063 let start1 = datum_point(circular1.start, range)?;
4064 let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
4065 let radius1_initial_value =
4066 radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
4067 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4068 return Err(KclError::new_semantic(KclErrorDetails::new(
4069 "tangent() can only be used inside a sketch block".to_owned(),
4070 vec![range],
4071 )));
4072 };
4073 let radius0_id = sketch_state.next_sketch_var_id();
4074 sketch_state.sketch_vars.push(KclValue::SketchVar {
4075 value: Box::new(crate::execution::SketchVar {
4076 id: radius0_id,
4077 initial_value: radius0_initial_value,
4078 ty: sketch_var_ty,
4079 node_path: None,
4081 meta: vec![],
4082 }),
4083 });
4084 let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
4085 let circle0 = DatumCircle {
4086 center: center0,
4087 radius: radius0,
4088 };
4089
4090 let radius1_id = sketch_state.next_sketch_var_id();
4091 sketch_state.sketch_vars.push(KclValue::SketchVar {
4092 value: Box::new(crate::execution::SketchVar {
4093 id: radius1_id,
4094 initial_value: radius1_initial_value,
4095 ty: sketch_var_ty,
4096 node_path: None,
4098 meta: vec![],
4099 }),
4100 });
4101 let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
4102 let circle1 = DatumCircle {
4103 center: center1,
4104 radius: radius1,
4105 };
4106
4107 sketch_state
4112 .solver_constraints
4113 .push(SolverConstraint::DistanceVar(start0, center0, radius0));
4114 if let Some(end0) = end0 {
4115 sketch_state
4116 .solver_constraints
4117 .push(SolverConstraint::DistanceVar(end0, center0, radius0));
4118 }
4119 sketch_state
4120 .solver_constraints
4121 .push(SolverConstraint::DistanceVar(start1, center1, radius1));
4122 if let Some(end1) = end1 {
4123 sketch_state
4124 .solver_constraints
4125 .push(SolverConstraint::DistanceVar(end1, center1, radius1));
4126 }
4127 sketch_state
4128 .solver_constraints
4129 .push(SolverConstraint::CircleTangentToCircle(circle0, circle1, tangency_side));
4130 }
4131 }
4132
4133 let constraint = crate::front::Constraint::Tangent(Tangent {
4134 input: vec![input0_object_id, input1_object_id],
4135 });
4136 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4137 return Err(KclError::new_semantic(KclErrorDetails::new(
4138 "tangent() can only be used inside a sketch block".to_owned(),
4139 vec![range],
4140 )));
4141 };
4142 sketch_state.sketch_constraints.push(constraint_id);
4143 track_constraint(constraint_id, constraint, exec_state, &args);
4144
4145 Ok(KclValue::none())
4146}
4147
4148#[derive(Debug, Clone, Copy)]
4149struct SymmetricPointVars {
4150 coords: [SketchVarId; 2],
4151 object_id: ObjectId,
4152}
4153
4154#[derive(Debug, Clone, Copy)]
4156struct SymmetricLineVars {
4157 start: [SketchVarId; 2],
4158 end: [SketchVarId; 2],
4159 object_id: ObjectId,
4160}
4161
4162#[derive(Debug, Clone, Copy)]
4163struct SymmetricArcVars {
4164 center: [SketchVarId; 2],
4165 start: [SketchVarId; 2],
4166 end: [SketchVarId; 2],
4167 object_id: ObjectId,
4168}
4169
4170#[derive(Debug, Clone, Copy)]
4171struct SymmetricCircleVars {
4172 center: [SketchVarId; 2],
4173 start: [SketchVarId; 2],
4174 object_id: ObjectId,
4175}
4176
4177#[derive(Debug, Clone, Copy)]
4178enum SymmetricInput {
4179 Point(SymmetricPointVars),
4180 Line(SymmetricLineVars),
4181 Arc(SymmetricArcVars),
4182 Circle(SymmetricCircleVars),
4183}
4184
4185impl SymmetricInput {
4186 fn type_name(self) -> &'static str {
4187 match self {
4188 SymmetricInput::Point(_) => "points",
4189 SymmetricInput::Line(_) => "lines",
4190 SymmetricInput::Arc(_) => "arcs",
4191 SymmetricInput::Circle(_) => "circles",
4192 }
4193 }
4194
4195 fn object_id(self) -> ObjectId {
4196 match self {
4197 SymmetricInput::Point(point) => point.object_id,
4198 SymmetricInput::Line(line) => line.object_id,
4199 SymmetricInput::Arc(arc) => arc.object_id,
4200 SymmetricInput::Circle(circle) => circle.object_id,
4201 }
4202 }
4203}
4204
4205fn extract_symmetric_input(segment_value: &KclValue, range: crate::SourceRange) -> Result<SymmetricInput, KclError> {
4206 let KclValue::Segment { value: segment } = segment_value else {
4207 return Err(KclError::new_semantic(KclErrorDetails::new(
4208 format!(
4209 "symmetric() arguments must be point, line, arc, or circle segments, but found {}",
4210 segment_value.human_friendly_type()
4211 ),
4212 vec![range],
4213 )));
4214 };
4215 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4216 return Err(KclError::new_semantic(KclErrorDetails::new(
4217 "symmetric() arguments must be unsolved segments".to_owned(),
4218 vec![range],
4219 )));
4220 };
4221
4222 match &unsolved.kind {
4223 UnsolvedSegmentKind::Point { position, .. } => {
4224 let (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) = (&position[0], &position[1]) else {
4225 return Err(KclError::new_semantic(KclErrorDetails::new(
4226 "point coordinates must be sketch vars for symmetric()".to_owned(),
4227 vec![range],
4228 )));
4229 };
4230 Ok(SymmetricInput::Point(SymmetricPointVars {
4231 coords: [*x, *y],
4232 object_id: unsolved.object_id,
4233 }))
4234 }
4235 UnsolvedSegmentKind::Line { start, end, .. } => {
4236 let (
4237 UnsolvedExpr::Unknown(start_x),
4238 UnsolvedExpr::Unknown(start_y),
4239 UnsolvedExpr::Unknown(end_x),
4240 UnsolvedExpr::Unknown(end_y),
4241 ) = (&start[0], &start[1], &end[0], &end[1])
4242 else {
4243 return Err(KclError::new_semantic(KclErrorDetails::new(
4244 "line coordinates must be sketch vars for symmetric()".to_owned(),
4245 vec![range],
4246 )));
4247 };
4248 Ok(SymmetricInput::Line(SymmetricLineVars {
4249 start: [*start_x, *start_y],
4250 end: [*end_x, *end_y],
4251 object_id: unsolved.object_id,
4252 }))
4253 }
4254 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
4255 let (
4256 UnsolvedExpr::Unknown(center_x),
4257 UnsolvedExpr::Unknown(center_y),
4258 UnsolvedExpr::Unknown(start_x),
4259 UnsolvedExpr::Unknown(start_y),
4260 UnsolvedExpr::Unknown(end_x),
4261 UnsolvedExpr::Unknown(end_y),
4262 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
4263 else {
4264 return Err(KclError::new_semantic(KclErrorDetails::new(
4265 "arc center/start/end coordinates must be sketch vars for symmetric()".to_owned(),
4266 vec![range],
4267 )));
4268 };
4269 Ok(SymmetricInput::Arc(SymmetricArcVars {
4270 center: [*center_x, *center_y],
4271 start: [*start_x, *start_y],
4272 end: [*end_x, *end_y],
4273 object_id: unsolved.object_id,
4274 }))
4275 }
4276 UnsolvedSegmentKind::Circle { center, start, .. } => {
4277 let (
4278 UnsolvedExpr::Unknown(center_x),
4279 UnsolvedExpr::Unknown(center_y),
4280 UnsolvedExpr::Unknown(start_x),
4281 UnsolvedExpr::Unknown(start_y),
4282 ) = (¢er[0], ¢er[1], &start[0], &start[1])
4283 else {
4284 return Err(KclError::new_semantic(KclErrorDetails::new(
4285 "circle center/start coordinates must be sketch vars for symmetric()".to_owned(),
4286 vec![range],
4287 )));
4288 };
4289 Ok(SymmetricInput::Circle(SymmetricCircleVars {
4290 center: [*center_x, *center_y],
4291 start: [*start_x, *start_y],
4292 object_id: unsolved.object_id,
4293 }))
4294 }
4295 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_semantic(KclErrorDetails::new(
4296 "symmetric() does not yet support control point spline segments".to_owned(),
4297 vec![range],
4298 ))),
4299 }
4300}
4301
4302fn extract_symmetric_axis_line(
4303 segment_value: &KclValue,
4304 range: crate::SourceRange,
4305) -> Result<SymmetricLineVars, KclError> {
4306 let KclValue::Segment { value: segment } = segment_value else {
4307 return Err(KclError::new_semantic(KclErrorDetails::new(
4308 format!(
4309 "symmetric() axis must be a line Segment, but found {}",
4310 segment_value.human_friendly_type()
4311 ),
4312 vec![range],
4313 )));
4314 };
4315 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4316 return Err(KclError::new_semantic(KclErrorDetails::new(
4317 "symmetric() axis must be an unsolved line Segment".to_owned(),
4318 vec![range],
4319 )));
4320 };
4321 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4322 return Err(KclError::new_semantic(KclErrorDetails::new(
4323 "symmetric() axis must be a line Segment".to_owned(),
4324 vec![range],
4325 )));
4326 };
4327 let (
4328 UnsolvedExpr::Unknown(start_x),
4329 UnsolvedExpr::Unknown(start_y),
4330 UnsolvedExpr::Unknown(end_x),
4331 UnsolvedExpr::Unknown(end_y),
4332 ) = (&start[0], &start[1], &end[0], &end[1])
4333 else {
4334 return Err(KclError::new_semantic(KclErrorDetails::new(
4335 "symmetric() axis line coordinates must be sketch vars".to_owned(),
4336 vec![range],
4337 )));
4338 };
4339
4340 Ok(SymmetricLineVars {
4341 start: [*start_x, *start_y],
4342 end: [*end_x, *end_y],
4343 object_id: unsolved.object_id,
4344 })
4345}
4346
4347pub async fn symmetric(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4348 #[derive(Debug, Clone, Copy)]
4349 struct SymmetricCircularVars {
4350 center: [SketchVarId; 2],
4351 start: [SketchVarId; 2],
4352 end: Option<[SketchVarId; 2]>,
4353 }
4354
4355 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
4356 "input",
4357 &RuntimeType::Array(
4358 Box::new(RuntimeType::Primitive(PrimitiveType::Segment)),
4359 ArrayLen::Known(2),
4360 ),
4361 exec_state,
4362 )?;
4363 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
4364 KclError::new_semantic(KclErrorDetails::new(
4365 "symmetric() requires exactly 2 input segments".to_owned(),
4366 vec![args.source_range],
4367 ))
4368 })?;
4369 let axis: KclValue = args.get_kw_arg("axis", &RuntimeType::Primitive(PrimitiveType::Segment), exec_state)?;
4370 let range = args.source_range;
4371
4372 let input0 = extract_symmetric_input(&item0, range)?;
4373 let input1 = extract_symmetric_input(&item1, range)?;
4374 let axis_line = extract_symmetric_axis_line(&axis, range)?;
4375
4376 let solver_axis = DatumLineSegment::new(datum_point(axis_line.start, range)?, datum_point(axis_line.end, range)?);
4377
4378 let (mut solver_constraints, circular_inputs) = match (input0, input1) {
4379 (SymmetricInput::Point(point0), SymmetricInput::Point(point1)) => (
4380 vec![SolverConstraint::Symmetric(
4381 solver_axis,
4382 datum_point(point0.coords, range)?,
4383 datum_point(point1.coords, range)?,
4384 )],
4385 None,
4386 ),
4387 (SymmetricInput::Line(line0), SymmetricInput::Line(line1)) => {
4388 let sketch_vars = {
4389 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4390 return Err(KclError::new_semantic(KclErrorDetails::new(
4391 "symmetric() can only be used inside a sketch block".to_owned(),
4392 vec![range],
4393 )));
4394 };
4395 sketch_state.sketch_vars.clone()
4396 };
4397 let mirrored_start = symmetric_hidden_point_guess(&sketch_vars, line0.start, axis_line, exec_state, range)?;
4398 let mirrored_end = symmetric_hidden_point_guess(&sketch_vars, line0.end, axis_line, exec_state, range)?;
4399 let hidden_start = create_hidden_point(exec_state, mirrored_start, range)?;
4400 let hidden_end = create_hidden_point(exec_state, mirrored_end, range)?;
4401 let mirrored_support_line =
4402 DatumLineSegment::new(datum_point(hidden_start, range)?, datum_point(hidden_end, range)?);
4403 let solver_line1 = DatumLineSegment::new(datum_point(line1.start, range)?, datum_point(line1.end, range)?);
4404
4405 (
4406 vec![
4407 SolverConstraint::Symmetric(
4408 solver_axis,
4409 datum_point(line0.start, range)?,
4410 datum_point(hidden_start, range)?,
4411 ),
4412 SolverConstraint::Symmetric(
4413 solver_axis,
4414 datum_point(line0.end, range)?,
4415 datum_point(hidden_end, range)?,
4416 ),
4417 SolverConstraint::LinesAtAngle(mirrored_support_line, solver_line1, AngleKind::Parallel),
4418 SolverConstraint::PointLineDistance(datum_point(line1.start, range)?, mirrored_support_line, 0.0),
4421 ],
4422 None,
4423 )
4424 }
4425 (SymmetricInput::Arc(arc0), SymmetricInput::Arc(arc1)) => (
4426 vec![SolverConstraint::Symmetric(
4427 solver_axis,
4428 datum_point(arc0.center, range)?,
4429 datum_point(arc1.center, range)?,
4430 )],
4431 Some([
4432 SymmetricCircularVars {
4433 center: arc0.center,
4434 start: arc0.start,
4435 end: Some(arc0.end),
4436 },
4437 SymmetricCircularVars {
4438 center: arc1.center,
4439 start: arc1.start,
4440 end: Some(arc1.end),
4441 },
4442 ]),
4443 ),
4444 (SymmetricInput::Circle(circle0), SymmetricInput::Circle(circle1)) => (
4445 vec![SolverConstraint::Symmetric(
4446 solver_axis,
4447 datum_point(circle0.center, range)?,
4448 datum_point(circle1.center, range)?,
4449 )],
4450 Some([
4451 SymmetricCircularVars {
4452 center: circle0.center,
4453 start: circle0.start,
4454 end: None,
4455 },
4456 SymmetricCircularVars {
4457 center: circle1.center,
4458 start: circle1.start,
4459 end: None,
4460 },
4461 ]),
4462 ),
4463 _ => {
4464 return Err(KclError::new_semantic(KclErrorDetails::new(
4465 format!(
4466 "symmetric() inputs must be homogeneous. You provided {} and {}",
4467 input0.type_name(),
4468 input1.type_name()
4469 ),
4470 vec![range],
4471 )));
4472 }
4473 };
4474
4475 if let Some([circular0, circular1]) = circular_inputs {
4476 let sketch_var_ty = solver_numeric_type(exec_state);
4477 let sketch_vars = {
4478 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4479 return Err(KclError::new_semantic(KclErrorDetails::new(
4480 "symmetric() can only be used inside a sketch block".to_owned(),
4481 vec![range],
4482 )));
4483 };
4484 sketch_state.sketch_vars.clone()
4485 };
4486 let radius_initial_value = radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
4487
4488 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4489 return Err(KclError::new_semantic(KclErrorDetails::new(
4490 "symmetric() can only be used inside a sketch block".to_owned(),
4491 vec![range],
4492 )));
4493 };
4494 let radius_id = sketch_state.next_sketch_var_id();
4495 sketch_state.sketch_vars.push(KclValue::SketchVar {
4496 value: Box::new(crate::execution::SketchVar {
4497 id: radius_id,
4498 initial_value: radius_initial_value,
4499 ty: sketch_var_ty,
4500 node_path: None,
4502 meta: vec![],
4503 }),
4504 });
4505 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
4506
4507 for circular in [circular0, circular1] {
4508 let center = datum_point(circular.center, range)?;
4509 let start = datum_point(circular.start, range)?;
4510 solver_constraints.push(SolverConstraint::DistanceVar(start, center, radius));
4511 if let Some(end) = circular.end {
4512 let end = datum_point(end, range)?;
4513 solver_constraints.push(SolverConstraint::DistanceVar(end, center, radius));
4514 }
4515 }
4516 }
4517
4518 let constraint_id = exec_state.next_object_id();
4519 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4520 return Err(KclError::new_semantic(KclErrorDetails::new(
4521 "symmetric() can only be used inside a sketch block".to_owned(),
4522 vec![range],
4523 )));
4524 };
4525 sketch_state.solver_constraints.extend(solver_constraints);
4526
4527 let constraint = crate::front::Constraint::Symmetric(Symmetric {
4528 input: vec![input0.object_id(), input1.object_id()],
4529 axis: axis_line.object_id,
4530 });
4531 sketch_state.sketch_constraints.push(constraint_id);
4532 track_constraint(constraint_id, constraint, exec_state, &args);
4533
4534 Ok(KclValue::none())
4535}
4536
4537#[derive(Debug, Clone, Copy)]
4538pub(crate) enum LinesAtAngleKind {
4539 Parallel,
4540 Perpendicular,
4541}
4542
4543impl LinesAtAngleKind {
4544 pub fn to_function_name(self) -> &'static str {
4545 match self {
4546 LinesAtAngleKind::Parallel => "parallel",
4547 LinesAtAngleKind::Perpendicular => "perpendicular",
4548 }
4549 }
4550
4551 fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
4552 match self {
4553 LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
4554 LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
4555 }
4556 }
4557
4558 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
4559 match self {
4560 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
4561 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
4562 }
4563 }
4564}
4565
4566#[expect(unused)]
4568fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
4569 kcmc::shared::Angle::from_degrees(angle.to_degrees())
4570}
4571
4572#[expect(unused)]
4574fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
4575 ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
4576}
4577
4578pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4579 #[derive(Clone, Copy)]
4580 struct ConstrainableLine {
4581 solver_line: DatumLineSegment,
4582 object_id: ObjectId,
4583 }
4584
4585 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
4586 "lines",
4587 &RuntimeType::Array(
4588 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
4589 ArrayLen::Minimum(2),
4590 ),
4591 exec_state,
4592 )?;
4593 let range = args.source_range;
4594 let constrainable_lines: Vec<ConstrainableLine> = lines
4595 .iter()
4596 .map(|line| {
4597 let KclValue::Segment { value: segment } = line else {
4598 return Err(KclError::new_semantic(KclErrorDetails::new(
4599 "line argument must be a Segment".to_owned(),
4600 vec![args.source_range],
4601 )));
4602 };
4603 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4604 return Err(KclError::new_internal(KclErrorDetails::new(
4605 "line must be an unsolved Segment".to_owned(),
4606 vec![args.source_range],
4607 )));
4608 };
4609 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4610 return Err(KclError::new_semantic(KclErrorDetails::new(
4611 "line argument must be a line, no other type of Segment".to_owned(),
4612 vec![args.source_range],
4613 )));
4614 };
4615 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
4616 return Err(KclError::new_semantic(KclErrorDetails::new(
4617 "line's start x coordinate must be a var".to_owned(),
4618 vec![args.source_range],
4619 )));
4620 };
4621 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
4622 return Err(KclError::new_semantic(KclErrorDetails::new(
4623 "line's start y coordinate must be a var".to_owned(),
4624 vec![args.source_range],
4625 )));
4626 };
4627 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
4628 return Err(KclError::new_semantic(KclErrorDetails::new(
4629 "line's end x coordinate must be a var".to_owned(),
4630 vec![args.source_range],
4631 )));
4632 };
4633 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
4634 return Err(KclError::new_semantic(KclErrorDetails::new(
4635 "line's end y coordinate must be a var".to_owned(),
4636 vec![args.source_range],
4637 )));
4638 };
4639
4640 let solver_line_p0 =
4641 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
4642 let solver_line_p1 =
4643 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
4644
4645 Ok(ConstrainableLine {
4646 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
4647 object_id: unsolved.object_id,
4648 })
4649 })
4650 .collect::<Result<_, _>>()?;
4651
4652 let constraint_id = exec_state.next_object_id();
4653 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4654 return Err(KclError::new_semantic(KclErrorDetails::new(
4655 "parallel() can only be used inside a sketch block".to_owned(),
4656 vec![args.source_range],
4657 )));
4658 };
4659
4660 let n = constrainable_lines.len();
4661 let mut constrainable_lines_iter = constrainable_lines.iter();
4662 let first_line = constrainable_lines_iter
4663 .next()
4664 .ok_or(KclError::new_semantic(KclErrorDetails::new(
4665 format!("parallel() requires at least 2 lines, but you provided {}", n),
4666 vec![args.source_range],
4667 )))?;
4668 for line in constrainable_lines_iter {
4669 sketch_state.solver_constraints.push(SolverConstraint::LinesAtAngle(
4670 first_line.solver_line,
4671 line.solver_line,
4672 AngleKind::Parallel,
4673 ));
4674 }
4675 let constraint = Constraint::Parallel(Parallel {
4676 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
4677 });
4678 sketch_state.sketch_constraints.push(constraint_id);
4679 track_constraint(constraint_id, constraint, exec_state, &args);
4680 Ok(KclValue::none())
4681}
4682
4683pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4684 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
4685}
4686
4687#[derive(Debug, Clone, Copy)]
4689enum AxisConstraintKind {
4690 Horizontal,
4691 Vertical,
4692}
4693
4694impl AxisConstraintKind {
4695 fn function_name(self) -> &'static str {
4697 match self {
4698 AxisConstraintKind::Horizontal => "horizontal",
4699 AxisConstraintKind::Vertical => "vertical",
4700 }
4701 }
4702
4703 fn line_constraint(self, line: DatumLineSegment) -> SolverConstraint {
4705 match self {
4706 AxisConstraintKind::Horizontal => SolverConstraint::Horizontal(line),
4707 AxisConstraintKind::Vertical => SolverConstraint::Vertical(line),
4708 }
4709 }
4710
4711 fn point_pair_constraint(self, p0: DatumPoint, p1: DatumPoint) -> SolverConstraint {
4713 match self {
4714 AxisConstraintKind::Horizontal => SolverConstraint::VerticalDistance(p1, p0, 0.0),
4716 AxisConstraintKind::Vertical => SolverConstraint::HorizontalDistance(p1, p0, 0.0),
4718 }
4719 }
4720
4721 fn constraint_aligning_point_to_constant(self, p0: DatumPoint, fixed_point: (f64, f64)) -> SolverConstraint {
4723 match self {
4724 AxisConstraintKind::Horizontal => SolverConstraint::Fixed(p0.y_id, fixed_point.1),
4725 AxisConstraintKind::Vertical => SolverConstraint::Fixed(p0.x_id, fixed_point.0),
4726 }
4727 }
4728
4729 fn line_artifact_constraint(self, line: ObjectId) -> Constraint {
4730 match self {
4731 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Line { line }),
4732 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Line { line }),
4733 }
4734 }
4735
4736 fn point_artifact_constraint(self, points: Vec<ConstraintSegment>) -> Constraint {
4737 match self {
4738 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Points { points }),
4739 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Points { points }),
4740 }
4741 }
4742}
4743
4744#[derive(Debug, Clone, Copy)]
4747struct AxisLineVars {
4748 start: [SketchVarId; 2],
4749 end: [SketchVarId; 2],
4750 object_id: ObjectId,
4751}
4752
4753fn extract_axis_line_vars(
4754 segment: &AbstractSegment,
4755 kind: AxisConstraintKind,
4756 source_range: crate::SourceRange,
4757) -> Result<AxisLineVars, KclError> {
4758 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4759 return Err(KclError::new_internal(KclErrorDetails::new(
4760 "line must be an unsolved Segment".to_owned(),
4761 vec![source_range],
4762 )));
4763 };
4764 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4765 return Err(KclError::new_semantic(KclErrorDetails::new(
4766 format!(
4767 "{}() line argument must be a line, no other type of Segment",
4768 kind.function_name()
4769 ),
4770 vec![source_range],
4771 )));
4772 };
4773 let (
4774 UnsolvedExpr::Unknown(start_x),
4775 UnsolvedExpr::Unknown(start_y),
4776 UnsolvedExpr::Unknown(end_x),
4777 UnsolvedExpr::Unknown(end_y),
4778 ) = (&start[0], &start[1], &end[0], &end[1])
4779 else {
4780 return Err(KclError::new_semantic(KclErrorDetails::new(
4781 "line's x and y coordinates of both start and end must be vars".to_owned(),
4782 vec![source_range],
4783 )));
4784 };
4785
4786 Ok(AxisLineVars {
4787 start: [*start_x, *start_y],
4788 end: [*end_x, *end_y],
4789 object_id: unsolved.object_id,
4790 })
4791}
4792
4793#[derive(Debug, Clone)]
4794enum PointToAlign {
4795 Variable { x: SketchVarId, y: SketchVarId },
4797 Fixed { x: TyF64, y: TyF64 },
4799}
4800
4801impl From<[SketchVarId; 2]> for PointToAlign {
4802 fn from(sketch_var: [SketchVarId; 2]) -> Self {
4803 Self::Variable {
4804 x: sketch_var[0],
4805 y: sketch_var[1],
4806 }
4807 }
4808}
4809
4810impl From<[TyF64; 2]> for PointToAlign {
4811 fn from([x, y]: [TyF64; 2]) -> Self {
4812 Self::Fixed { x, y }
4813 }
4814}
4815
4816fn extract_axis_point_vars(
4817 input: &KclValue,
4818 kind: AxisConstraintKind,
4819 source_range: crate::SourceRange,
4820) -> Result<PointToAlign, KclError> {
4821 match input {
4822 KclValue::Segment { value: segment } => {
4823 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4824 return Err(KclError::new_semantic(KclErrorDetails::new(
4825 format!(
4826 "The `{}` function point arguments must be unsolved points",
4827 kind.function_name()
4828 ),
4829 vec![source_range],
4830 )));
4831 };
4832 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
4833 return Err(KclError::new_semantic(KclErrorDetails::new(
4834 format!(
4835 "The `{}` function list arguments must be points, but one item is {}",
4836 kind.function_name(),
4837 unsolved.kind.human_friendly_kind_with_article()
4838 ),
4839 vec![source_range],
4840 )));
4841 };
4842 match (&position[0], &position[1]) {
4843 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed {
4844 x: x.to_owned(),
4845 y: y.to_owned(),
4846 }),
4847 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x: *x, y: *y }),
4848 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
4849 Err(KclError::new_semantic(KclErrorDetails::new(
4850 format!(
4851 "The `{}` function cannot take a fixed X component and a variable Y component",
4852 kind.function_name()
4853 ),
4854 vec![source_range],
4855 )))
4856 }
4857 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
4858 Err(KclError::new_semantic(KclErrorDetails::new(
4859 format!(
4860 "The `{}` function cannot take a fixed X component and a variable Y component",
4861 kind.function_name()
4862 ),
4863 vec![source_range],
4864 )))
4865 }
4866 }
4867 }
4868 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
4869 let [x_value, y_value] = value.as_slice() else {
4870 return Err(KclError::new_semantic(KclErrorDetails::new(
4871 format!(
4872 "The `{}` function point arguments must each be a Point2d like [var 0mm, var 0mm]",
4873 kind.function_name()
4874 ),
4875 vec![source_range],
4876 )));
4877 };
4878 let Some(x_expr) = x_value.as_unsolved_expr() else {
4879 return Err(KclError::new_semantic(KclErrorDetails::new(
4880 format!(
4881 "The `{}` function point x coordinate must be a number or sketch var",
4882 kind.function_name()
4883 ),
4884 vec![source_range],
4885 )));
4886 };
4887 let Some(y_expr) = y_value.as_unsolved_expr() else {
4888 return Err(KclError::new_semantic(KclErrorDetails::new(
4889 format!(
4890 "The `{}` function point y coordinate must be a number or sketch var",
4891 kind.function_name()
4892 ),
4893 vec![source_range],
4894 )));
4895 };
4896 match (x_expr, y_expr) {
4897 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed { x, y }),
4898 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x, y }),
4899 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
4900 Err(KclError::new_semantic(KclErrorDetails::new(
4901 format!(
4902 "The `{}` function cannot take a fixed X component and a variable Y component",
4903 kind.function_name()
4904 ),
4905 vec![source_range],
4906 )))
4907 }
4908 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
4909 Err(KclError::new_semantic(KclErrorDetails::new(
4910 format!(
4911 "The `{}` function cannot take a fixed X component and a variable Y component",
4912 kind.function_name()
4913 ),
4914 vec![source_range],
4915 )))
4916 }
4917 }
4918 }
4919 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4920 format!(
4921 "The `{}` function accepts either a line Segment or a list of points",
4922 kind.function_name()
4923 ),
4924 vec![source_range],
4925 ))),
4926 }
4927}
4928
4929async fn axis_constraint(
4930 kind: AxisConstraintKind,
4931 exec_state: &mut ExecState,
4932 args: Args,
4933) -> Result<KclValue, KclError> {
4934 let input: KclValue =
4935 args.get_unlabeled_kw_arg("input", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
4936
4937 match input {
4939 KclValue::Segment { value } => {
4940 axis_constraint_line(value, kind, exec_state, args)
4942 }
4943 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
4944 axis_constraint_points(value, kind, exec_state, args)
4946 }
4947 other => Err(KclError::new_semantic(KclErrorDetails::new(
4948 format!(
4949 "{}() accepts either a line Segment or a list of at least two points, but you provided {}",
4950 kind.function_name(),
4951 other.human_friendly_type(),
4952 ),
4953 vec![args.source_range],
4954 ))),
4955 }
4956}
4957
4958fn axis_constraint_line(
4960 segment: Box<AbstractSegment>,
4961 kind: AxisConstraintKind,
4962 exec_state: &mut ExecState,
4963 args: Args,
4964) -> Result<KclValue, KclError> {
4965 let line = extract_axis_line_vars(&segment, kind, args.source_range)?;
4966 let range = args.source_range;
4967 let solver_p0 = DatumPoint::new_xy(
4968 line.start[0].to_constraint_id(range)?,
4969 line.start[1].to_constraint_id(range)?,
4970 );
4971 let solver_p1 = DatumPoint::new_xy(
4972 line.end[0].to_constraint_id(range)?,
4973 line.end[1].to_constraint_id(range)?,
4974 );
4975 let solver_line = DatumLineSegment::new(solver_p0, solver_p1);
4976 let constraint = kind.line_constraint(solver_line);
4977 let constraint_id = exec_state.next_object_id();
4978 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4979 return Err(KclError::new_semantic(KclErrorDetails::new(
4980 format!("{}() can only be used inside a sketch block", kind.function_name()),
4981 vec![args.source_range],
4982 )));
4983 };
4984 sketch_state.solver_constraints.push(constraint);
4985 let constraint = kind.line_artifact_constraint(line.object_id);
4986 sketch_state.sketch_constraints.push(constraint_id);
4987 track_constraint(constraint_id, constraint, exec_state, &args);
4988 Ok(KclValue::none())
4989}
4990
4991fn axis_constraint_points(
4993 point_values: Vec<KclValue>,
4994 kind: AxisConstraintKind,
4995 exec_state: &mut ExecState,
4996 args: Args,
4997) -> Result<KclValue, KclError> {
4998 if point_values.len() < 2 {
4999 return Err(KclError::new_semantic(KclErrorDetails::new(
5000 format!("{}() point list must contain at least two points", kind.function_name()),
5001 vec![args.source_range],
5002 )));
5003 }
5004
5005 let trackable_point_ids = point_values
5006 .iter()
5007 .map(|point| match point {
5008 KclValue::Segment { value: segment } => {
5009 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
5010 return None;
5011 };
5012 let UnsolvedSegmentKind::Point { .. } = &unsolved.kind else {
5013 return None;
5014 };
5015 Some(ConstraintSegment::from(unsolved.object_id))
5016 }
5017 point if point2d_is_origin(point) => Some(ConstraintSegment::ORIGIN),
5018 _ => None,
5019 })
5020 .collect::<Option<Vec<_>>>();
5021
5022 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5023 return Err(KclError::new_semantic(KclErrorDetails::new(
5024 format!("{}() can only be used inside a sketch block", kind.function_name()),
5025 vec![args.source_range],
5026 )));
5027 };
5028
5029 let points: Vec<PointToAlign> = point_values
5030 .iter()
5031 .map(|point| extract_axis_point_vars(point, kind, args.source_range))
5032 .collect::<Result<_, _>>()?;
5033
5034 let mut solver_constraints = Vec::with_capacity(points.len().saturating_sub(1));
5035
5036 let mut var_points = Vec::new();
5037 let mut fix_points = Vec::new();
5038 for point in points {
5039 match point {
5040 PointToAlign::Variable { x, y } => var_points.push((x, y)),
5041 PointToAlign::Fixed { x, y } => fix_points.push((x, y)),
5042 }
5043 }
5044 if fix_points.len() > 1 {
5045 return Err(KclError::new_semantic(KclErrorDetails::new(
5046 format!(
5047 "{}() point list can contain at most 1 fixed point, but you provided {}",
5048 kind.function_name(),
5049 fix_points.len()
5050 ),
5051 vec![args.source_range],
5052 )));
5053 }
5054
5055 if let Some(fix_point) = fix_points.pop() {
5056 for point in var_points {
5064 let solver_point = datum_point([point.0, point.1], args.source_range)?;
5065 let fix_point_mm = (fix_point.0.to_mm(), fix_point.1.to_mm());
5066 solver_constraints.push(kind.constraint_aligning_point_to_constant(solver_point, fix_point_mm));
5067 }
5068 } else {
5069 let mut points = var_points.into_iter();
5076 let first_point = points.next().ok_or_else(|| {
5077 KclError::new_semantic(KclErrorDetails::new(
5078 format!("{}() point list must contain at least two points", kind.function_name()),
5079 vec![args.source_range],
5080 ))
5081 })?;
5082 let anchor = datum_point([first_point.0, first_point.1], args.source_range)?;
5083 for point in points {
5084 let solver_point = datum_point([point.0, point.1], args.source_range)?;
5085 solver_constraints.push(kind.point_pair_constraint(anchor, solver_point));
5086 }
5087 }
5088 sketch_state.solver_constraints.extend(solver_constraints);
5089
5090 if let Some(point_ids) = trackable_point_ids {
5091 let constraint_id = exec_state.next_object_id();
5092 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5093 debug_assert!(false, "Constraint created outside a sketch block");
5094 return Ok(KclValue::none());
5095 };
5096 sketch_state.sketch_constraints.push(constraint_id);
5097 let constraint = kind.point_artifact_constraint(point_ids);
5098 track_constraint(constraint_id, constraint, exec_state, &args);
5099 }
5100
5101 Ok(KclValue::none())
5102}
5103
5104pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5105 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
5106 "lines",
5107 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
5108 exec_state,
5109 )?;
5110 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
5111 KclError::new_semantic(KclErrorDetails::new(
5112 "must have two input lines".to_owned(),
5113 vec![args.source_range],
5114 ))
5115 })?;
5116 let KclValue::Segment { value: segment0 } = &line0 else {
5117 return Err(KclError::new_semantic(KclErrorDetails::new(
5118 "line argument must be a Segment".to_owned(),
5119 vec![args.source_range],
5120 )));
5121 };
5122 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
5123 return Err(KclError::new_internal(KclErrorDetails::new(
5124 "line must be an unsolved Segment".to_owned(),
5125 vec![args.source_range],
5126 )));
5127 };
5128 let UnsolvedSegmentKind::Line {
5129 start: start0,
5130 end: end0,
5131 ..
5132 } = &unsolved0.kind
5133 else {
5134 return Err(KclError::new_semantic(KclErrorDetails::new(
5135 "line argument must be a line, no other type of Segment".to_owned(),
5136 vec![args.source_range],
5137 )));
5138 };
5139 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
5140 return Err(KclError::new_semantic(KclErrorDetails::new(
5141 "line's start x coordinate must be a var".to_owned(),
5142 vec![args.source_range],
5143 )));
5144 };
5145 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
5146 return Err(KclError::new_semantic(KclErrorDetails::new(
5147 "line's start y coordinate must be a var".to_owned(),
5148 vec![args.source_range],
5149 )));
5150 };
5151 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
5152 return Err(KclError::new_semantic(KclErrorDetails::new(
5153 "line's end x coordinate must be a var".to_owned(),
5154 vec![args.source_range],
5155 )));
5156 };
5157 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
5158 return Err(KclError::new_semantic(KclErrorDetails::new(
5159 "line's end y coordinate must be a var".to_owned(),
5160 vec![args.source_range],
5161 )));
5162 };
5163 let KclValue::Segment { value: segment1 } = &line1 else {
5164 return Err(KclError::new_semantic(KclErrorDetails::new(
5165 "line argument must be a Segment".to_owned(),
5166 vec![args.source_range],
5167 )));
5168 };
5169 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
5170 return Err(KclError::new_internal(KclErrorDetails::new(
5171 "line must be an unsolved Segment".to_owned(),
5172 vec![args.source_range],
5173 )));
5174 };
5175 let UnsolvedSegmentKind::Line {
5176 start: start1,
5177 end: end1,
5178 ..
5179 } = &unsolved1.kind
5180 else {
5181 return Err(KclError::new_semantic(KclErrorDetails::new(
5182 "line argument must be a line, no other type of Segment".to_owned(),
5183 vec![args.source_range],
5184 )));
5185 };
5186 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
5187 return Err(KclError::new_semantic(KclErrorDetails::new(
5188 "line's start x coordinate must be a var".to_owned(),
5189 vec![args.source_range],
5190 )));
5191 };
5192 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
5193 return Err(KclError::new_semantic(KclErrorDetails::new(
5194 "line's start y coordinate must be a var".to_owned(),
5195 vec![args.source_range],
5196 )));
5197 };
5198 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
5199 return Err(KclError::new_semantic(KclErrorDetails::new(
5200 "line's end x coordinate must be a var".to_owned(),
5201 vec![args.source_range],
5202 )));
5203 };
5204 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
5205 return Err(KclError::new_semantic(KclErrorDetails::new(
5206 "line's end y coordinate must be a var".to_owned(),
5207 vec![args.source_range],
5208 )));
5209 };
5210
5211 let sketch_constraint = SketchConstraint {
5213 kind: SketchConstraintKind::Angle {
5214 line0: crate::execution::ConstrainableLine2d {
5215 object_id: unsolved0.object_id,
5216 vars: [
5217 crate::front::Point2d {
5218 x: *line0_p0_x,
5219 y: *line0_p0_y,
5220 },
5221 crate::front::Point2d {
5222 x: *line0_p1_x,
5223 y: *line0_p1_y,
5224 },
5225 ],
5226 },
5227 line1: crate::execution::ConstrainableLine2d {
5228 object_id: unsolved1.object_id,
5229 vars: [
5230 crate::front::Point2d {
5231 x: *line1_p0_x,
5232 y: *line1_p0_y,
5233 },
5234 crate::front::Point2d {
5235 x: *line1_p1_x,
5236 y: *line1_p1_y,
5237 },
5238 ],
5239 },
5240 },
5241 meta: vec![args.source_range.into()],
5242 };
5243 Ok(KclValue::SketchConstraint {
5244 value: Box::new(sketch_constraint),
5245 })
5246}
5247
5248async fn lines_at_angle(
5249 angle_kind: LinesAtAngleKind,
5250 exec_state: &mut ExecState,
5251 args: Args,
5252) -> Result<KclValue, KclError> {
5253 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
5254 "lines",
5255 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
5256 exec_state,
5257 )?;
5258 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
5259 KclError::new_semantic(KclErrorDetails::new(
5260 "must have two input lines".to_owned(),
5261 vec![args.source_range],
5262 ))
5263 })?;
5264
5265 let KclValue::Segment { value: segment0 } = &line0 else {
5266 return Err(KclError::new_semantic(KclErrorDetails::new(
5267 "line argument must be a Segment".to_owned(),
5268 vec![args.source_range],
5269 )));
5270 };
5271 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
5272 return Err(KclError::new_internal(KclErrorDetails::new(
5273 "line must be an unsolved Segment".to_owned(),
5274 vec![args.source_range],
5275 )));
5276 };
5277 let UnsolvedSegmentKind::Line {
5278 start: start0,
5279 end: end0,
5280 ..
5281 } = &unsolved0.kind
5282 else {
5283 return Err(KclError::new_semantic(KclErrorDetails::new(
5284 "line argument must be a line, no other type of Segment".to_owned(),
5285 vec![args.source_range],
5286 )));
5287 };
5288 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
5289 return Err(KclError::new_semantic(KclErrorDetails::new(
5290 "line's start x coordinate must be a var".to_owned(),
5291 vec![args.source_range],
5292 )));
5293 };
5294 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
5295 return Err(KclError::new_semantic(KclErrorDetails::new(
5296 "line's start y coordinate must be a var".to_owned(),
5297 vec![args.source_range],
5298 )));
5299 };
5300 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
5301 return Err(KclError::new_semantic(KclErrorDetails::new(
5302 "line's end x coordinate must be a var".to_owned(),
5303 vec![args.source_range],
5304 )));
5305 };
5306 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
5307 return Err(KclError::new_semantic(KclErrorDetails::new(
5308 "line's end y coordinate must be a var".to_owned(),
5309 vec![args.source_range],
5310 )));
5311 };
5312 let KclValue::Segment { value: segment1 } = &line1 else {
5313 return Err(KclError::new_semantic(KclErrorDetails::new(
5314 "line argument must be a Segment".to_owned(),
5315 vec![args.source_range],
5316 )));
5317 };
5318 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
5319 return Err(KclError::new_internal(KclErrorDetails::new(
5320 "line must be an unsolved Segment".to_owned(),
5321 vec![args.source_range],
5322 )));
5323 };
5324 let UnsolvedSegmentKind::Line {
5325 start: start1,
5326 end: end1,
5327 ..
5328 } = &unsolved1.kind
5329 else {
5330 return Err(KclError::new_semantic(KclErrorDetails::new(
5331 "line argument must be a line, no other type of Segment".to_owned(),
5332 vec![args.source_range],
5333 )));
5334 };
5335 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
5336 return Err(KclError::new_semantic(KclErrorDetails::new(
5337 "line's start x coordinate must be a var".to_owned(),
5338 vec![args.source_range],
5339 )));
5340 };
5341 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
5342 return Err(KclError::new_semantic(KclErrorDetails::new(
5343 "line's start y coordinate must be a var".to_owned(),
5344 vec![args.source_range],
5345 )));
5346 };
5347 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
5348 return Err(KclError::new_semantic(KclErrorDetails::new(
5349 "line's end x coordinate must be a var".to_owned(),
5350 vec![args.source_range],
5351 )));
5352 };
5353 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
5354 return Err(KclError::new_semantic(KclErrorDetails::new(
5355 "line's end y coordinate must be a var".to_owned(),
5356 vec![args.source_range],
5357 )));
5358 };
5359
5360 let range = args.source_range;
5361 let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5362 line0_p0_x.to_constraint_id(range)?,
5363 line0_p0_y.to_constraint_id(range)?,
5364 );
5365 let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5366 line0_p1_x.to_constraint_id(range)?,
5367 line0_p1_y.to_constraint_id(range)?,
5368 );
5369 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
5370 let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5371 line1_p0_x.to_constraint_id(range)?,
5372 line1_p0_y.to_constraint_id(range)?,
5373 );
5374 let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5375 line1_p1_x.to_constraint_id(range)?,
5376 line1_p1_y.to_constraint_id(range)?,
5377 );
5378 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
5379 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
5380 let constraint_id = exec_state.next_object_id();
5381 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5383 return Err(KclError::new_semantic(KclErrorDetails::new(
5384 format!(
5385 "{}() can only be used inside a sketch block",
5386 angle_kind.to_function_name()
5387 ),
5388 vec![args.source_range],
5389 )));
5390 };
5391 sketch_state.solver_constraints.push(constraint);
5392 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
5393 sketch_state.sketch_constraints.push(constraint_id);
5394 track_constraint(constraint_id, constraint, exec_state, &args);
5395 Ok(KclValue::none())
5396}
5397
5398pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5399 axis_constraint(AxisConstraintKind::Horizontal, exec_state, args).await
5400}
5401
5402pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5403 axis_constraint(AxisConstraintKind::Vertical, exec_state, args).await
5404}