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