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 let optional_constraints = {
1426 let placeholder_control_ids = control_object_ids
1427 .iter()
1428 .map(|control_object_id| {
1429 exec_state.add_placeholder_scene_object(*control_object_id, args.source_range, args.node_path.clone())
1430 })
1431 .collect::<Vec<_>>();
1432 control_polygon_edge_object_ids.iter().for_each(|edge_object_id| {
1433 exec_state.add_placeholder_scene_object(*edge_object_id, args.source_range, args.node_path.clone());
1434 });
1435 let spline_object_id =
1436 exec_state.add_placeholder_scene_object(spline_object_id, args.source_range, args.node_path.clone());
1437
1438 let mut optional_constraints = Vec::new();
1439 for (index, [x_value, y_value]) in control_values.iter().enumerate() {
1440 let control_object_id = placeholder_control_ids[index];
1441 if !(exec_state.segment_ids_edited_contains(&control_object_id)
1442 || exec_state.segment_ids_edited_contains(&spline_object_id))
1443 {
1444 continue;
1445 }
1446
1447 if let Some(x_var) = x_value.as_sketch_var() {
1448 let x_initial_value = x_var.initial_value_to_solver_units(
1449 exec_state,
1450 args.source_range,
1451 "edited segment fixed constraint value",
1452 )?;
1453 optional_constraints.push(SolverConstraint::Fixed(
1454 x_var.id.to_constraint_id(args.source_range)?,
1455 x_initial_value.n,
1456 ));
1457 }
1458
1459 if let Some(y_var) = y_value.as_sketch_var() {
1460 let y_initial_value = y_var.initial_value_to_solver_units(
1461 exec_state,
1462 args.source_range,
1463 "edited segment fixed constraint value",
1464 )?;
1465 optional_constraints.push(SolverConstraint::Fixed(
1466 y_var.id.to_constraint_id(args.source_range)?,
1467 y_initial_value.n,
1468 ));
1469 }
1470 }
1471 optional_constraints
1472 };
1473
1474 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1475 return Err(KclError::new_semantic(KclErrorDetails::new(
1476 "controlPointSpline() can only be used inside a sketch block".to_owned(),
1477 vec![args.source_range],
1478 )));
1479 };
1480 sketch_state.needed_by_engine.push(segment.clone());
1481
1482 sketch_state.solver_optional_constraints.extend(optional_constraints);
1483
1484 let meta = segment.meta.clone();
1485 let abstract_segment = AbstractSegment {
1486 repr: SegmentRepr::Unsolved {
1487 segment: Box::new(segment),
1488 },
1489 meta,
1490 };
1491 Ok(KclValue::Segment {
1492 value: Box::new(abstract_segment),
1493 })
1494}
1495
1496pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1497 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1498 "points",
1499 &RuntimeType::Array(
1500 Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
1501 ArrayLen::Minimum(2),
1502 ),
1503 exec_state,
1504 )?;
1505 if points.len() > 2 {
1506 return coincident_points(points, exec_state, args);
1507 }
1508 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1509 KclError::new_semantic(KclErrorDetails::new(
1510 "must have two input points".to_owned(),
1511 vec![args.source_range],
1512 ))
1513 })?;
1514
1515 let range = args.source_range;
1516 match (&point0, &point1) {
1517 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1518 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1519 return Err(KclError::new_semantic(KclErrorDetails::new(
1520 "first point must be an unsolved segment".to_owned(),
1521 vec![args.source_range],
1522 )));
1523 };
1524 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1525 return Err(KclError::new_semantic(KclErrorDetails::new(
1526 "second point must be an unsolved segment".to_owned(),
1527 vec![args.source_range],
1528 )));
1529 };
1530 match (&unsolved0.kind, &unsolved1.kind) {
1531 (
1532 UnsolvedSegmentKind::Point { position: pos0, .. },
1533 UnsolvedSegmentKind::Point { position: pos1, .. },
1534 ) => {
1535 let p0_x = &pos0[0];
1536 let p0_y = &pos0[1];
1537 match (p0_x, p0_y) {
1538 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
1539 let p1_x = &pos1[0];
1540 let p1_y = &pos1[1];
1541 match (p1_x, p1_y) {
1542 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
1543 let constraint = SolverConstraint::PointsCoincident(
1544 ezpz::datatypes::inputs::DatumPoint::new_xy(
1545 p0_x.to_constraint_id(range)?,
1546 p0_y.to_constraint_id(range)?,
1547 ),
1548 ezpz::datatypes::inputs::DatumPoint::new_xy(
1549 p1_x.to_constraint_id(range)?,
1550 p1_y.to_constraint_id(range)?,
1551 ),
1552 );
1553 let constraint_id = exec_state.next_object_id();
1554 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1556 return Err(KclError::new_semantic(KclErrorDetails::new(
1557 "coincident() can only be used inside a sketch block".to_owned(),
1558 vec![args.source_range],
1559 )));
1560 };
1561 sketch_state.solver_constraints.push(constraint);
1562 let constraint = crate::front::Constraint::Coincident(Coincident {
1563 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1564 });
1565 sketch_state.sketch_constraints.push(constraint_id);
1566 track_constraint(constraint_id, constraint, exec_state, &args);
1567 Ok(KclValue::none())
1568 }
1569 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1570 let p1_x = KclValue::Number {
1571 value: p1_x.n,
1572 ty: p1_x.ty,
1573 meta: vec![args.source_range.into()],
1574 };
1575 let p1_y = KclValue::Number {
1576 value: p1_y.n,
1577 ty: p1_y.ty,
1578 meta: vec![args.source_range.into()],
1579 };
1580 let (constraint_x, constraint_y) =
1581 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
1582
1583 let constraint_id = exec_state.next_object_id();
1584 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1586 return Err(KclError::new_semantic(KclErrorDetails::new(
1587 "coincident() can only be used inside a sketch block".to_owned(),
1588 vec![args.source_range],
1589 )));
1590 };
1591 sketch_state.solver_constraints.push(constraint_x);
1592 sketch_state.solver_constraints.push(constraint_y);
1593 let constraint = crate::front::Constraint::Coincident(Coincident {
1594 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1595 });
1596 sketch_state.sketch_constraints.push(constraint_id);
1597 track_constraint(constraint_id, constraint, exec_state, &args);
1598 Ok(KclValue::none())
1599 }
1600 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1601 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1602 Err(KclError::new_semantic(KclErrorDetails::new(
1604 "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(),
1605 vec![args.source_range],
1606 )))
1607 }
1608 }
1609 }
1610 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
1611 let p1_x = &pos1[0];
1612 let p1_y = &pos1[1];
1613 match (p1_x, p1_y) {
1614 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
1615 let p0_x = KclValue::Number {
1616 value: p0_x.n,
1617 ty: p0_x.ty,
1618 meta: vec![args.source_range.into()],
1619 };
1620 let p0_y = KclValue::Number {
1621 value: p0_y.n,
1622 ty: p0_y.ty,
1623 meta: vec![args.source_range.into()],
1624 };
1625 let (constraint_x, constraint_y) =
1626 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
1627
1628 let constraint_id = exec_state.next_object_id();
1629 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1631 return Err(KclError::new_semantic(KclErrorDetails::new(
1632 "coincident() can only be used inside a sketch block".to_owned(),
1633 vec![args.source_range],
1634 )));
1635 };
1636 sketch_state.solver_constraints.push(constraint_x);
1637 sketch_state.solver_constraints.push(constraint_y);
1638 let constraint = crate::front::Constraint::Coincident(Coincident {
1639 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1640 });
1641 sketch_state.sketch_constraints.push(constraint_id);
1642 track_constraint(constraint_id, constraint, exec_state, &args);
1643 Ok(KclValue::none())
1644 }
1645 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1646 if *p0_x != *p1_x || *p0_y != *p1_y {
1647 return Err(KclError::new_semantic(KclErrorDetails::new(
1648 "Coincident constraint between two fixed points failed since coordinates differ"
1649 .to_owned(),
1650 vec![args.source_range],
1651 )));
1652 }
1653 Ok(KclValue::none())
1654 }
1655 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1656 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1657 Err(KclError::new_semantic(KclErrorDetails::new(
1659 "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(),
1660 vec![args.source_range],
1661 )))
1662 }
1663 }
1664 }
1665 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1666 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1667 Err(KclError::new_semantic(KclErrorDetails::new(
1669 "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(),
1670 vec![args.source_range],
1671 )))
1672 }
1673 }
1674 }
1675 (
1677 UnsolvedSegmentKind::Point {
1678 position: point_pos, ..
1679 },
1680 UnsolvedSegmentKind::Line {
1681 start: line_start,
1682 end: line_end,
1683 ..
1684 },
1685 )
1686 | (
1687 UnsolvedSegmentKind::Line {
1688 start: line_start,
1689 end: line_end,
1690 ..
1691 },
1692 UnsolvedSegmentKind::Point {
1693 position: point_pos, ..
1694 },
1695 ) => {
1696 let point_x = &point_pos[0];
1697 let point_y = &point_pos[1];
1698 match (point_x, point_y) {
1699 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1700 let (start_x, start_y) = (&line_start[0], &line_start[1]);
1702 let (end_x, end_y) = (&line_end[0], &line_end[1]);
1703
1704 match (start_x, start_y, end_x, end_y) {
1705 (
1706 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1707 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1708 ) => {
1709 let point = DatumPoint::new_xy(
1710 point_x.to_constraint_id(range)?,
1711 point_y.to_constraint_id(range)?,
1712 );
1713 let line_segment = DatumLineSegment::new(
1714 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
1715 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
1716 );
1717 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
1718
1719 let constraint_id = exec_state.next_object_id();
1720
1721 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1722 return Err(KclError::new_semantic(KclErrorDetails::new(
1723 "coincident() can only be used inside a sketch block".to_owned(),
1724 vec![args.source_range],
1725 )));
1726 };
1727 sketch_state.solver_constraints.push(constraint);
1728 let constraint = crate::front::Constraint::Coincident(Coincident {
1729 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1730 });
1731 sketch_state.sketch_constraints.push(constraint_id);
1732 track_constraint(constraint_id, constraint, exec_state, &args);
1733 Ok(KclValue::none())
1734 }
1735 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1736 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
1737 vec![args.source_range],
1738 ))),
1739 }
1740 }
1741 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1742 "Point coordinates must be sketch variables for point-segment coincident constraint"
1743 .to_owned(),
1744 vec![args.source_range],
1745 ))),
1746 }
1747 }
1748 (
1750 UnsolvedSegmentKind::Point {
1751 position: point_pos, ..
1752 },
1753 UnsolvedSegmentKind::Arc {
1754 start: arc_start,
1755 end: arc_end,
1756 center: arc_center,
1757 ..
1758 },
1759 )
1760 | (
1761 UnsolvedSegmentKind::Arc {
1762 start: arc_start,
1763 end: arc_end,
1764 center: arc_center,
1765 ..
1766 },
1767 UnsolvedSegmentKind::Point {
1768 position: point_pos, ..
1769 },
1770 ) => {
1771 let point_x = &point_pos[0];
1772 let point_y = &point_pos[1];
1773 match (point_x, point_y) {
1774 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1775 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
1777 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
1778 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
1779
1780 match (center_x, center_y, start_x, start_y, end_x, end_y) {
1781 (
1782 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
1783 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1784 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1785 ) => {
1786 let point = DatumPoint::new_xy(
1787 point_x.to_constraint_id(range)?,
1788 point_y.to_constraint_id(range)?,
1789 );
1790 let circular_arc = DatumCircularArc {
1791 center: DatumPoint::new_xy(
1792 cx.to_constraint_id(range)?,
1793 cy.to_constraint_id(range)?,
1794 ),
1795 start: DatumPoint::new_xy(
1796 sx.to_constraint_id(range)?,
1797 sy.to_constraint_id(range)?,
1798 ),
1799 end: DatumPoint::new_xy(
1800 ex.to_constraint_id(range)?,
1801 ey.to_constraint_id(range)?,
1802 ),
1803 };
1804 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
1805
1806 let constraint_id = exec_state.next_object_id();
1807
1808 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1809 return Err(KclError::new_semantic(KclErrorDetails::new(
1810 "coincident() can only be used inside a sketch block".to_owned(),
1811 vec![args.source_range],
1812 )));
1813 };
1814 sketch_state.solver_constraints.push(constraint);
1815 let constraint = crate::front::Constraint::Coincident(Coincident {
1816 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1817 });
1818 sketch_state.sketch_constraints.push(constraint_id);
1819 track_constraint(constraint_id, constraint, exec_state, &args);
1820 Ok(KclValue::none())
1821 }
1822 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1823 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
1824 vec![args.source_range],
1825 ))),
1826 }
1827 }
1828 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1829 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
1830 vec![args.source_range],
1831 ))),
1832 }
1833 }
1834 (
1837 UnsolvedSegmentKind::Point {
1838 position: point_pos, ..
1839 },
1840 UnsolvedSegmentKind::Circle {
1841 start: circle_start,
1842 center: circle_center,
1843 ..
1844 },
1845 )
1846 | (
1847 UnsolvedSegmentKind::Circle {
1848 start: circle_start,
1849 center: circle_center,
1850 ..
1851 },
1852 UnsolvedSegmentKind::Point {
1853 position: point_pos, ..
1854 },
1855 ) => {
1856 let point_x = &point_pos[0];
1857 let point_y = &point_pos[1];
1858 match (point_x, point_y) {
1859 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1860 let (center_x, center_y) = (&circle_center[0], &circle_center[1]);
1862 let (start_x, start_y) = (&circle_start[0], &circle_start[1]);
1863
1864 match (center_x, center_y, start_x, start_y) {
1865 (
1866 UnsolvedExpr::Unknown(cx),
1867 UnsolvedExpr::Unknown(cy),
1868 UnsolvedExpr::Unknown(sx),
1869 UnsolvedExpr::Unknown(sy),
1870 ) => {
1871 let point_radius_line = DatumLineSegment::new(
1872 DatumPoint::new_xy(
1873 cx.to_constraint_id(range)?,
1874 cy.to_constraint_id(range)?,
1875 ),
1876 DatumPoint::new_xy(
1877 point_x.to_constraint_id(range)?,
1878 point_y.to_constraint_id(range)?,
1879 ),
1880 );
1881 let circle_radius_line = DatumLineSegment::new(
1882 DatumPoint::new_xy(
1883 cx.to_constraint_id(range)?,
1884 cy.to_constraint_id(range)?,
1885 ),
1886 DatumPoint::new_xy(
1887 sx.to_constraint_id(range)?,
1888 sy.to_constraint_id(range)?,
1889 ),
1890 );
1891 let constraint =
1892 SolverConstraint::LinesEqualLength(point_radius_line, circle_radius_line);
1893
1894 let constraint_id = exec_state.next_object_id();
1895
1896 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1897 return Err(KclError::new_semantic(KclErrorDetails::new(
1898 "coincident() can only be used inside a sketch block".to_owned(),
1899 vec![args.source_range],
1900 )));
1901 };
1902 sketch_state.solver_constraints.push(constraint);
1903 let constraint = crate::front::Constraint::Coincident(Coincident {
1904 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1905 });
1906 sketch_state.sketch_constraints.push(constraint_id);
1907 track_constraint(constraint_id, constraint, exec_state, &args);
1908 Ok(KclValue::none())
1909 }
1910 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1911 "Circle start and center points must be sketch variables for point-circle coincident constraint".to_owned(),
1912 vec![args.source_range],
1913 ))),
1914 }
1915 }
1916 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1917 "Point coordinates must be sketch variables for point-circle coincident constraint"
1918 .to_owned(),
1919 vec![args.source_range],
1920 ))),
1921 }
1922 }
1923 (
1925 UnsolvedSegmentKind::Line {
1926 start: line0_start,
1927 end: line0_end,
1928 ..
1929 },
1930 UnsolvedSegmentKind::Line {
1931 start: line1_start,
1932 end: line1_end,
1933 ..
1934 },
1935 ) => {
1936 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
1938 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
1939 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
1940 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
1941
1942 match (
1943 line0_start_x,
1944 line0_start_y,
1945 line0_end_x,
1946 line0_end_y,
1947 line1_start_x,
1948 line1_start_y,
1949 line1_end_x,
1950 line1_end_y,
1951 ) {
1952 (
1953 UnsolvedExpr::Unknown(l0_sx),
1954 UnsolvedExpr::Unknown(l0_sy),
1955 UnsolvedExpr::Unknown(l0_ex),
1956 UnsolvedExpr::Unknown(l0_ey),
1957 UnsolvedExpr::Unknown(l1_sx),
1958 UnsolvedExpr::Unknown(l1_sy),
1959 UnsolvedExpr::Unknown(l1_ex),
1960 UnsolvedExpr::Unknown(l1_ey),
1961 ) => {
1962 let line0_segment = DatumLineSegment::new(
1964 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
1965 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
1966 );
1967 let line1_segment = DatumLineSegment::new(
1968 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
1969 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
1970 );
1971
1972 let parallel_constraint =
1974 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
1975
1976 let point_on_line1 =
1978 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
1979 let distance_constraint =
1980 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
1981
1982 let constraint_id = exec_state.next_object_id();
1983
1984 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1985 return Err(KclError::new_semantic(KclErrorDetails::new(
1986 "coincident() can only be used inside a sketch block".to_owned(),
1987 vec![args.source_range],
1988 )));
1989 };
1990 sketch_state.solver_constraints.push(parallel_constraint);
1992 sketch_state.solver_constraints.push(distance_constraint);
1993 let constraint = crate::front::Constraint::Coincident(Coincident {
1994 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1995 });
1996 sketch_state.sketch_constraints.push(constraint_id);
1997 track_constraint(constraint_id, constraint, exec_state, &args);
1998 Ok(KclValue::none())
1999 }
2000 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2001 "Line segment endpoints must be sketch variables for line-line coincident constraint"
2002 .to_owned(),
2003 vec![args.source_range],
2004 ))),
2005 }
2006 }
2007 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2008 format!(
2009 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
2010 &unsolved0.kind, &unsolved1.kind
2011 ),
2012 vec![args.source_range],
2013 ))),
2014 }
2015 }
2016 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2019 let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
2020 return Err(KclError::new_semantic(KclErrorDetails::new(
2021 "Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
2022 vec![args.source_range],
2023 )));
2024 };
2025 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2026 return Err(KclError::new_semantic(KclErrorDetails::new(
2027 "segment must be an unsolved segment".to_owned(),
2028 vec![args.source_range],
2029 )));
2030 };
2031 match &unsolved.kind {
2032 UnsolvedSegmentKind::Point { position, .. } => {
2033 let p_x = &position[0];
2034 let p_y = &position[1];
2035 match (p_x, p_y) {
2036 (UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
2037 let pt_x = KclValue::Number {
2038 value: pt[0].n,
2039 ty: pt[0].ty,
2040 meta: vec![args.source_range.into()],
2041 };
2042 let pt_y = KclValue::Number {
2043 value: pt[1].n,
2044 ty: pt[1].ty,
2045 meta: vec![args.source_range.into()],
2046 };
2047 let (constraint_x, constraint_y) =
2048 coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
2049
2050 let constraint_id = exec_state.next_object_id();
2051 let coincident_segments = coincident_segments_for_segment_and_point2d(
2052 unsolved.object_id,
2053 point2d,
2054 matches!((&point0, &point1), (KclValue::Segment { .. }, _)),
2055 );
2056 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2057 return Err(KclError::new_semantic(KclErrorDetails::new(
2058 "coincident() can only be used inside a sketch block".to_owned(),
2059 vec![args.source_range],
2060 )));
2061 };
2062 sketch_state.solver_constraints.push(constraint_x);
2063 sketch_state.solver_constraints.push(constraint_y);
2064 let constraint = crate::front::Constraint::Coincident(Coincident {
2065 segments: coincident_segments,
2066 });
2067 sketch_state.sketch_constraints.push(constraint_id);
2068 track_constraint(constraint_id, constraint, exec_state, &args);
2069 Ok(KclValue::none())
2070 }
2071 (UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
2072 let pt_x_val = normalize_to_solver_distance_unit(
2073 &KclValue::Number {
2074 value: pt[0].n,
2075 ty: pt[0].ty,
2076 meta: vec![args.source_range.into()],
2077 },
2078 args.source_range,
2079 exec_state,
2080 "coincident constraint value",
2081 )?;
2082 let pt_y_val = normalize_to_solver_distance_unit(
2083 &KclValue::Number {
2084 value: pt[1].n,
2085 ty: pt[1].ty,
2086 meta: vec![args.source_range.into()],
2087 },
2088 args.source_range,
2089 exec_state,
2090 "coincident constraint value",
2091 )?;
2092 let Some(pt_x) = pt_x_val.as_ty_f64() else {
2093 return Err(KclError::new_semantic(KclErrorDetails::new(
2094 "Expected number for Point2d x coordinate".to_owned(),
2095 vec![args.source_range],
2096 )));
2097 };
2098 let Some(pt_y) = pt_y_val.as_ty_f64() else {
2099 return Err(KclError::new_semantic(KclErrorDetails::new(
2100 "Expected number for Point2d y coordinate".to_owned(),
2101 vec![args.source_range],
2102 )));
2103 };
2104 let known_x_val = normalize_to_solver_distance_unit(
2105 &KclValue::Number {
2106 value: known_x.n,
2107 ty: known_x.ty,
2108 meta: vec![args.source_range.into()],
2109 },
2110 args.source_range,
2111 exec_state,
2112 "coincident constraint value",
2113 )?;
2114 let Some(known_x_f) = known_x_val.as_ty_f64() else {
2115 return Err(KclError::new_semantic(KclErrorDetails::new(
2116 "Expected number for known x coordinate".to_owned(),
2117 vec![args.source_range],
2118 )));
2119 };
2120 let known_y_val = normalize_to_solver_distance_unit(
2121 &KclValue::Number {
2122 value: known_y.n,
2123 ty: known_y.ty,
2124 meta: vec![args.source_range.into()],
2125 },
2126 args.source_range,
2127 exec_state,
2128 "coincident constraint value",
2129 )?;
2130 let Some(known_y_f) = known_y_val.as_ty_f64() else {
2131 return Err(KclError::new_semantic(KclErrorDetails::new(
2132 "Expected number for known y coordinate".to_owned(),
2133 vec![args.source_range],
2134 )));
2135 };
2136 if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
2137 return Err(KclError::new_semantic(KclErrorDetails::new(
2138 "Coincident constraint between two fixed points failed since coordinates differ"
2139 .to_owned(),
2140 vec![args.source_range],
2141 )));
2142 }
2143 Ok(KclValue::none())
2144 }
2145 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2146 "Point coordinates must have consistent known/unknown status for coincident constraint"
2147 .to_owned(),
2148 vec![args.source_range],
2149 ))),
2150 }
2151 }
2152 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2153 "A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
2154 vec![args.source_range],
2155 ))),
2156 }
2157 }
2158 _ => {
2160 let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
2161 let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
2162 match (pt0, pt1) {
2163 (Some(a), Some(b)) => {
2164 let a_x = normalize_to_solver_distance_unit(
2166 &KclValue::Number {
2167 value: a[0].n,
2168 ty: a[0].ty,
2169 meta: vec![args.source_range.into()],
2170 },
2171 args.source_range,
2172 exec_state,
2173 "coincident constraint value",
2174 )?;
2175 let a_y = normalize_to_solver_distance_unit(
2176 &KclValue::Number {
2177 value: a[1].n,
2178 ty: a[1].ty,
2179 meta: vec![args.source_range.into()],
2180 },
2181 args.source_range,
2182 exec_state,
2183 "coincident constraint value",
2184 )?;
2185 let b_x = normalize_to_solver_distance_unit(
2186 &KclValue::Number {
2187 value: b[0].n,
2188 ty: b[0].ty,
2189 meta: vec![args.source_range.into()],
2190 },
2191 args.source_range,
2192 exec_state,
2193 "coincident constraint value",
2194 )?;
2195 let b_y = normalize_to_solver_distance_unit(
2196 &KclValue::Number {
2197 value: b[1].n,
2198 ty: b[1].ty,
2199 meta: vec![args.source_range.into()],
2200 },
2201 args.source_range,
2202 exec_state,
2203 "coincident constraint value",
2204 )?;
2205 if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
2206 || a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
2207 {
2208 return Err(KclError::new_semantic(KclErrorDetails::new(
2209 "Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
2210 vec![args.source_range],
2211 )));
2212 }
2213 Ok(KclValue::none())
2214 }
2215 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2216 "All inputs must be Segments or Point2d values".to_owned(),
2217 vec![args.source_range],
2218 ))),
2219 }
2220 }
2221 }
2222}
2223
2224fn coincident_points(
2225 point_values: Vec<KclValue>,
2226 exec_state: &mut ExecState,
2227 args: Args,
2228) -> Result<KclValue, KclError> {
2229 if point_values.len() < 2 {
2230 return Err(KclError::new_semantic(KclErrorDetails::new(
2231 "coincident() point list must contain at least two points".to_owned(),
2232 vec![args.source_range],
2233 )));
2234 }
2235
2236 let points = point_values
2238 .iter()
2239 .map(|point| extract_multi_coincident_point(point, args.source_range))
2240 .collect::<Result<Vec<_>, _>>()?;
2241
2242 let constraint_segments = points.iter().map(|point| point.constraint_segment).collect::<Vec<_>>();
2243
2244 let mut variable_points = Vec::new();
2245 let mut fixed_points = Vec::new();
2246 for point in points {
2247 match point.point {
2248 PointToAlign::Variable { x, y } => variable_points.push([x, y]),
2249 PointToAlign::Fixed { x, y } => fixed_points.push([x, y]),
2250 }
2251 }
2252
2253 let mut solver_constraints = Vec::with_capacity(point_values.len().saturating_sub(1) * 2);
2254 if let Some((anchor_fixed, remaining_fixed_points)) = fixed_points.split_first() {
2255 if remaining_fixed_points
2257 .iter()
2258 .any(|point| !fixed_points_match(point, anchor_fixed))
2259 {
2260 return Err(KclError::new_semantic(KclErrorDetails::new(
2261 "coincident() with more than two inputs can include at most one fixed point location".to_owned(),
2262 vec![args.source_range],
2263 )));
2264 }
2265
2266 let anchor_x = ty_f64_to_kcl_value(anchor_fixed[0].clone(), args.source_range);
2267 let anchor_y = ty_f64_to_kcl_value(anchor_fixed[1].clone(), args.source_range);
2268 for point in variable_points {
2269 let (constraint_x, constraint_y) =
2270 coincident_constraints_fixed(point[0], point[1], &anchor_x, &anchor_y, exec_state, &args)?;
2271 solver_constraints.push(constraint_x);
2272 solver_constraints.push(constraint_y);
2273 }
2274 } else {
2275 let mut points = variable_points.into_iter();
2277 let first_point = points.next().ok_or_else(|| {
2278 KclError::new_semantic(KclErrorDetails::new(
2279 "coincident() point list must contain at least two points".to_owned(),
2280 vec![args.source_range],
2281 ))
2282 })?;
2283 let anchor = datum_point(first_point, args.source_range)?;
2284 for point in points {
2285 let solver_point = datum_point(point, args.source_range)?;
2286 solver_constraints.push(SolverConstraint::PointsCoincident(anchor, solver_point));
2287 }
2288 }
2289
2290 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2291 return Err(KclError::new_semantic(KclErrorDetails::new(
2292 "coincident() can only be used inside a sketch block".to_owned(),
2293 vec![args.source_range],
2294 )));
2295 };
2296 sketch_state.solver_constraints.extend(solver_constraints);
2297
2298 let constraint_id = exec_state.next_object_id();
2300 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2301 debug_assert!(false, "Constraint created outside a sketch block");
2302 return Ok(KclValue::none());
2303 };
2304 sketch_state.sketch_constraints.push(constraint_id);
2305 let constraint = Constraint::Coincident(Coincident {
2306 segments: constraint_segments,
2307 });
2308 track_constraint(constraint_id, constraint, exec_state, &args);
2309
2310 Ok(KclValue::none())
2311}
2312
2313fn extract_multi_coincident_point(
2314 input: &KclValue,
2315 source_range: crate::SourceRange,
2316) -> Result<CoincidentPointInput, KclError> {
2317 match input {
2319 KclValue::Segment { value: segment } => {
2320 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2321 return Err(KclError::new_semantic(KclErrorDetails::new(
2322 "coincident() with more than two inputs only supports unsolved points or ORIGIN".to_owned(),
2323 vec![source_range],
2324 )));
2325 };
2326 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2327 return Err(KclError::new_semantic(KclErrorDetails::new(
2328 format!(
2329 "coincident() with more than two inputs only supports points or ORIGIN, but one item is {}",
2330 unsolved.kind.human_friendly_kind_with_article()
2331 ),
2332 vec![source_range],
2333 )));
2334 };
2335 match (&position[0], &position[1]) {
2336 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(CoincidentPointInput {
2337 point: PointToAlign::Fixed {
2338 x: x.to_owned(),
2339 y: y.to_owned(),
2340 },
2341 constraint_segment: unsolved.object_id.into(),
2342 }),
2343 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(CoincidentPointInput {
2344 point: PointToAlign::Variable { x: *x, y: *y },
2345 constraint_segment: unsolved.object_id.into(),
2346 }),
2347 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..))
2349 | (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => Err(KclError::new_semantic(
2350 KclErrorDetails::new(
2351 "coincident() with more than two inputs requires each point to be fully fixed or fully variable"
2352 .to_owned(),
2353 vec![source_range],
2354 ),
2355 )),
2356 }
2357 }
2358 point if point2d_is_origin(point) => {
2359 let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point) else {
2360 debug_assert!(false, "Origin literal should coerce to Point2d");
2361 return Err(KclError::new_internal(KclErrorDetails::new(
2362 "Origin literal could not be converted to a point".to_owned(),
2363 vec![source_range],
2364 )));
2365 };
2366 Ok(CoincidentPointInput {
2367 point: PointToAlign::Fixed { x, y },
2368 constraint_segment: ConstraintSegment::ORIGIN,
2369 })
2370 }
2371 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2372 "coincident() with more than two inputs only supports points and ORIGIN".to_owned(),
2373 vec![source_range],
2374 ))),
2375 }
2376}
2377
2378#[derive(Debug, Clone)]
2379struct CoincidentPointInput {
2380 point: PointToAlign,
2381 constraint_segment: ConstraintSegment,
2382}
2383
2384fn fixed_points_match(a: &[TyF64; 2], b: &[TyF64; 2]) -> bool {
2385 a[0].to_mm() == b[0].to_mm() && a[1].to_mm() == b[1].to_mm()
2386}
2387
2388fn ty_f64_to_kcl_value(value: TyF64, source_range: crate::SourceRange) -> KclValue {
2389 KclValue::Number {
2390 value: value.n,
2391 ty: value.ty,
2392 meta: vec![source_range.into()],
2393 }
2394}
2395
2396fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
2397 let sketch_id = {
2398 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2399 debug_assert!(false, "Constraint created outside a sketch block");
2400 return;
2401 };
2402 sketch_state.sketch_id
2403 };
2404 let Some(sketch_id) = sketch_id else {
2405 debug_assert!(false, "Constraint created without a sketch id");
2406 return;
2407 };
2408 let artifact_id = exec_state.next_artifact_id();
2409 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2410 id: artifact_id,
2411 sketch_id,
2412 constraint_id,
2413 constraint_type: SketchBlockConstraintType::from(&constraint),
2414 code_ref: CodeRef::placeholder(args.source_range),
2415 }));
2416 exec_state.add_scene_object(
2417 Object {
2418 id: constraint_id,
2419 kind: ObjectKind::Constraint { constraint },
2420 label: Default::default(),
2421 comments: Default::default(),
2422 artifact_id,
2423 source: SourceRef::new(args.source_range, args.node_path.clone()),
2424 },
2425 args.source_range,
2426 );
2427}
2428
2429fn coincident_constraints_fixed(
2431 p0_x: SketchVarId,
2432 p0_y: SketchVarId,
2433 p1_x: &KclValue,
2434 p1_y: &KclValue,
2435 exec_state: &mut ExecState,
2436 args: &Args,
2437) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
2438 let p1_x_number_value =
2439 normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
2440 let p1_y_number_value =
2441 normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
2442 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
2443 let message = format!(
2444 "Expected number after coercion, but found {}",
2445 p1_x_number_value.human_friendly_type()
2446 );
2447 debug_assert!(false, "{}", &message);
2448 return Err(KclError::new_internal(KclErrorDetails::new(
2449 message,
2450 vec![args.source_range],
2451 )));
2452 };
2453 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
2454 let message = format!(
2455 "Expected number after coercion, but found {}",
2456 p1_y_number_value.human_friendly_type()
2457 );
2458 debug_assert!(false, "{}", &message);
2459 return Err(KclError::new_internal(KclErrorDetails::new(
2460 message,
2461 vec![args.source_range],
2462 )));
2463 };
2464 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
2465 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
2466 Ok((constraint_x, constraint_y))
2467}
2468
2469pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2470 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2471 "points",
2472 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2473 exec_state,
2474 )?;
2475 let label_position = get_constraint_label_position(exec_state, &args, "distance")?;
2476 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
2477 KclError::new_semantic(KclErrorDetails::new(
2478 "must have two input points".to_owned(),
2479 vec![args.source_range],
2480 ))
2481 })?;
2482
2483 match (&point0, &point1) {
2484 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2485 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2486 return Err(KclError::new_semantic(KclErrorDetails::new(
2487 "first point must be an unsolved segment".to_owned(),
2488 vec![args.source_range],
2489 )));
2490 };
2491 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2492 return Err(KclError::new_semantic(KclErrorDetails::new(
2493 "second point must be an unsolved segment".to_owned(),
2494 vec![args.source_range],
2495 )));
2496 };
2497 match (&unsolved0.kind, &unsolved1.kind) {
2498 (
2499 UnsolvedSegmentKind::Point { position: pos0, .. },
2500 UnsolvedSegmentKind::Point { position: pos1, .. },
2501 ) => {
2502 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2505 (
2506 UnsolvedExpr::Unknown(p0_x),
2507 UnsolvedExpr::Unknown(p0_y),
2508 UnsolvedExpr::Unknown(p1_x),
2509 UnsolvedExpr::Unknown(p1_y),
2510 ) => {
2511 let sketch_constraint = SketchConstraint {
2513 kind: SketchConstraintKind::Distance {
2514 points: [
2515 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2516 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2517 object_id: unsolved0.object_id,
2518 }),
2519 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2520 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2521 object_id: unsolved1.object_id,
2522 }),
2523 ],
2524 label_position,
2525 },
2526 meta: vec![args.source_range.into()],
2527 };
2528 Ok(KclValue::SketchConstraint {
2529 value: Box::new(sketch_constraint),
2530 })
2531 }
2532 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2533 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
2534 vec![args.source_range],
2535 ))),
2536 }
2537 }
2538 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Line { .. })
2539 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Point { .. }) => {
2540 let (point_segment, line_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2541 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Line { .. }) => (unsolved0, unsolved1),
2542 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Point { .. }) => (unsolved1, unsolved0),
2543 _ => {
2544 return Err(KclError::new_semantic(KclErrorDetails::new(
2545 "distance() expected a point-line segment pair".to_owned(),
2546 vec![args.source_range],
2547 )));
2548 }
2549 };
2550 let point =
2551 constrainable_point_from_unsolved_segment(point_segment, "distance", args.source_range)?;
2552 let line = constrainable_line_from_unsolved_segment(line_segment, "distance", args.source_range)?;
2553
2554 Ok(KclValue::SketchConstraint {
2555 value: Box::new(SketchConstraint {
2556 kind: SketchConstraintKind::PointLineDistance {
2557 point: ConstrainablePoint2dOrOrigin::Point(point),
2558 line,
2559 input_object_ids: [Some(unsolved0.object_id), Some(unsolved1.object_id)],
2560 label_position,
2561 },
2562 meta: vec![args.source_range.into()],
2563 }),
2564 })
2565 }
2566 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Arc { .. })
2567 | (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Circle { .. })
2568 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Point { .. })
2569 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Point { .. }) => {
2570 let (point_segment, circular_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2571 (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Arc { .. })
2572 | (UnsolvedSegmentKind::Point { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2573 (unsolved0, unsolved1)
2574 }
2575 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Point { .. })
2576 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Point { .. }) => {
2577 (unsolved1, unsolved0)
2578 }
2579 _ => {
2580 return Err(KclError::new_semantic(KclErrorDetails::new(
2581 "distance() expected a point-arc or point-circle segment pair".to_owned(),
2582 vec![args.source_range],
2583 )));
2584 }
2585 };
2586 let point =
2587 constrainable_point_from_unsolved_segment(point_segment, "distance", args.source_range)?;
2588 let (center, start, end) =
2589 constrainable_circular_from_unsolved_segment(circular_segment, "distance", args.source_range)?;
2590
2591 Ok(KclValue::SketchConstraint {
2592 value: Box::new(SketchConstraint {
2593 kind: SketchConstraintKind::PointCircularDistance {
2594 point: ConstrainablePoint2dOrOrigin::Point(point),
2595 center,
2596 start,
2597 end,
2598 input_object_ids: [Some(unsolved0.object_id), Some(unsolved1.object_id)],
2599 label_position,
2600 },
2601 meta: vec![args.source_range.into()],
2602 }),
2603 })
2604 }
2605 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Arc { .. })
2606 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Circle { .. })
2607 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Line { .. })
2608 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Line { .. }) => {
2609 let (line_segment, circular_segment) = match (&unsolved0.kind, &unsolved1.kind) {
2610 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Arc { .. })
2611 | (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2612 (unsolved0, unsolved1)
2613 }
2614 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Line { .. })
2615 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Line { .. }) => {
2616 (unsolved1, unsolved0)
2617 }
2618 _ => {
2619 return Err(KclError::new_semantic(KclErrorDetails::new(
2620 "distance() expected a line-arc or line-circle segment pair".to_owned(),
2621 vec![args.source_range],
2622 )));
2623 }
2624 };
2625 let line = constrainable_line_from_unsolved_segment(line_segment, "distance", args.source_range)?;
2626 let (center, start, end) =
2627 constrainable_circular_from_unsolved_segment(circular_segment, "distance", args.source_range)?;
2628
2629 Ok(KclValue::SketchConstraint {
2630 value: Box::new(SketchConstraint {
2631 kind: SketchConstraintKind::LineCircularDistance {
2632 line,
2633 center,
2634 start,
2635 end,
2636 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2637 label_position,
2638 },
2639 meta: vec![args.source_range.into()],
2640 }),
2641 })
2642 }
2643 (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Arc { .. })
2644 | (UnsolvedSegmentKind::Arc { .. }, UnsolvedSegmentKind::Circle { .. })
2645 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Arc { .. })
2646 | (UnsolvedSegmentKind::Circle { .. }, UnsolvedSegmentKind::Circle { .. }) => {
2647 let (center0, start0, end0) =
2648 constrainable_circular_from_unsolved_segment(unsolved0, "distance", args.source_range)?;
2649 let (center1, start1, end1) =
2650 constrainable_circular_from_unsolved_segment(unsolved1, "distance", args.source_range)?;
2651
2652 Ok(KclValue::SketchConstraint {
2653 value: Box::new(SketchConstraint {
2654 kind: SketchConstraintKind::CircularCircularDistance {
2655 center0,
2656 start0,
2657 end0,
2658 center1,
2659 start1,
2660 end1,
2661 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2662 label_position,
2663 },
2664 meta: vec![args.source_range.into()],
2665 }),
2666 })
2667 }
2668 (UnsolvedSegmentKind::Line { .. }, UnsolvedSegmentKind::Line { .. }) => {
2669 let line0 = constrainable_line_from_unsolved_segment(unsolved0, "distance", args.source_range)?;
2670 let line1 = constrainable_line_from_unsolved_segment(unsolved1, "distance", args.source_range)?;
2671
2672 Ok(KclValue::SketchConstraint {
2673 value: Box::new(SketchConstraint {
2674 kind: SketchConstraintKind::LineLineDistance {
2675 line0,
2676 line1,
2677 input_object_ids: [unsolved0.object_id, unsolved1.object_id],
2678 label_position,
2679 },
2680 meta: vec![args.source_range.into()],
2681 }),
2682 })
2683 }
2684 (UnsolvedSegmentKind::ControlPointSpline { .. }, _)
2685 | (_, UnsolvedSegmentKind::ControlPointSpline { .. }) => {
2686 Err(KclError::new_semantic(KclErrorDetails::new(
2687 "distance() does not yet support control point spline segments".to_owned(),
2688 vec![args.source_range],
2689 )))
2690 }
2691 }
2692 }
2693 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2695 if !point2d_is_origin(point2d) {
2696 return Err(KclError::new_semantic(KclErrorDetails::new(
2697 "distance() Point2d arguments must be ORIGIN".to_owned(),
2698 vec![args.source_range],
2699 )));
2700 }
2701
2702 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2703 return Err(KclError::new_semantic(KclErrorDetails::new(
2704 "segment must be an unsolved segment".to_owned(),
2705 vec![args.source_range],
2706 )));
2707 };
2708 let segment_first = matches!((&point0, &point1), (KclValue::Segment { .. }, _));
2709 let input_object_ids = if segment_first {
2710 [Some(unsolved.object_id), None]
2711 } else {
2712 [None, Some(unsolved.object_id)]
2713 };
2714 match &unsolved.kind {
2715 UnsolvedSegmentKind::Point { position, .. } => match (&position[0], &position[1]) {
2716 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2717 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2718 vars: crate::front::Point2d {
2719 x: *point_x,
2720 y: *point_y,
2721 },
2722 object_id: unsolved.object_id,
2723 });
2724 let points = if segment_first {
2725 [point, ConstrainablePoint2dOrOrigin::Origin]
2726 } else {
2727 [ConstrainablePoint2dOrOrigin::Origin, point]
2728 };
2729 Ok(KclValue::SketchConstraint {
2730 value: Box::new(SketchConstraint {
2731 kind: SketchConstraintKind::Distance { points, label_position },
2732 meta: vec![args.source_range.into()],
2733 }),
2734 })
2735 }
2736 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2737 "unimplemented: distance() point arguments must be sketch vars in all coordinates".to_owned(),
2738 vec![args.source_range],
2739 ))),
2740 },
2741 UnsolvedSegmentKind::Line { .. } => {
2742 let line = constrainable_line_from_unsolved_segment(unsolved, "distance", args.source_range)?;
2743 Ok(KclValue::SketchConstraint {
2744 value: Box::new(SketchConstraint {
2745 kind: SketchConstraintKind::PointLineDistance {
2746 point: ConstrainablePoint2dOrOrigin::Origin,
2747 line,
2748 input_object_ids,
2749 label_position,
2750 },
2751 meta: vec![args.source_range.into()],
2752 }),
2753 })
2754 }
2755 UnsolvedSegmentKind::Arc { .. } | UnsolvedSegmentKind::Circle { .. } => {
2756 let (center, start, end) =
2757 constrainable_circular_from_unsolved_segment(unsolved, "distance", args.source_range)?;
2758 Ok(KclValue::SketchConstraint {
2759 value: Box::new(SketchConstraint {
2760 kind: SketchConstraintKind::PointCircularDistance {
2761 point: ConstrainablePoint2dOrOrigin::Origin,
2762 center,
2763 start,
2764 end,
2765 input_object_ids,
2766 label_position,
2767 },
2768 meta: vec![args.source_range.into()],
2769 }),
2770 })
2771 }
2772 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_semantic(KclErrorDetails::new(
2773 "distance() does not yet support control point spline segments".to_owned(),
2774 vec![args.source_range],
2775 ))),
2776 }
2777 }
2778 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2779 "distance() arguments must be point segments or ORIGIN".to_owned(),
2780 vec![args.source_range],
2781 ))),
2782 }
2783}
2784
2785fn get_constraint_label_position(
2786 exec_state: &mut ExecState,
2787 args: &Args,
2788 constraint_name: &str,
2789) -> Result<Option<Point2d<Number>>, KclError> {
2790 let label_position = args.get_kw_arg_opt::<[TyF64; 2]>("labelPosition", &RuntimeType::point2d(), exec_state)?;
2791
2792 label_position
2793 .map(|label| {
2794 TyF64::to_point2d(&label).map_err(|_| {
2795 KclError::new_internal(KclErrorDetails::new(
2796 format!("Could not convert {constraint_name} label position to a Point2d"),
2797 vec![args.source_range],
2798 ))
2799 })
2800 })
2801 .transpose()
2802}
2803
2804fn create_circular_radius_constraint(
2807 segment: KclValue,
2808 constraint_kind: impl Fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
2809 source_range: crate::SourceRange,
2810) -> Result<SketchConstraint, KclError> {
2811 let dummy_constraint = constraint_kind([
2813 ConstrainablePoint2d {
2814 vars: crate::front::Point2d {
2815 x: SketchVarId(0),
2816 y: SketchVarId(0),
2817 },
2818 object_id: ObjectId(0),
2819 },
2820 ConstrainablePoint2d {
2821 vars: crate::front::Point2d {
2822 x: SketchVarId(0),
2823 y: SketchVarId(0),
2824 },
2825 object_id: ObjectId(0),
2826 },
2827 ]);
2828 let function_name = dummy_constraint.name();
2829
2830 let KclValue::Segment { value: seg } = segment else {
2831 return Err(KclError::new_semantic(KclErrorDetails::new(
2832 format!("{}() argument must be a segment", function_name),
2833 vec![source_range],
2834 )));
2835 };
2836 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2837 return Err(KclError::new_semantic(KclErrorDetails::new(
2838 "segment must be unsolved".to_owned(),
2839 vec![source_range],
2840 )));
2841 };
2842 match &unsolved.kind {
2843 UnsolvedSegmentKind::Arc {
2844 center,
2845 start,
2846 center_object_id,
2847 start_object_id,
2848 ..
2849 }
2850 | UnsolvedSegmentKind::Circle {
2851 center,
2852 start,
2853 center_object_id,
2854 start_object_id,
2855 ..
2856 } => {
2857 match (¢er[0], ¢er[1], &start[0], &start[1]) {
2859 (
2860 UnsolvedExpr::Unknown(center_x),
2861 UnsolvedExpr::Unknown(center_y),
2862 UnsolvedExpr::Unknown(start_x),
2863 UnsolvedExpr::Unknown(start_y),
2864 ) => {
2865 let sketch_constraint = SketchConstraint {
2867 kind: constraint_kind([
2868 ConstrainablePoint2d {
2869 vars: crate::front::Point2d {
2870 x: *center_x,
2871 y: *center_y,
2872 },
2873 object_id: *center_object_id,
2874 },
2875 ConstrainablePoint2d {
2876 vars: crate::front::Point2d {
2877 x: *start_x,
2878 y: *start_y,
2879 },
2880 object_id: *start_object_id,
2881 },
2882 ]),
2883 meta: vec![source_range.into()],
2884 };
2885 Ok(sketch_constraint)
2886 }
2887 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2888 format!(
2889 "unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
2890 function_name
2891 ),
2892 vec![source_range],
2893 ))),
2894 }
2895 }
2896 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2897 format!("{}() argument must be an arc or circle segment", function_name),
2898 vec![source_range],
2899 ))),
2900 }
2901}
2902
2903pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2904 let segment: KclValue =
2905 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
2906 let label_position = get_constraint_label_position(exec_state, &args, "radius")?;
2907
2908 create_circular_radius_constraint(
2909 segment,
2910 |points| SketchConstraintKind::Radius {
2911 points,
2912 label_position: label_position.clone(),
2913 },
2914 args.source_range,
2915 )
2916 .map(|constraint| KclValue::SketchConstraint {
2917 value: Box::new(constraint),
2918 })
2919}
2920
2921pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2922 let segment: KclValue =
2923 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
2924 let label_position = get_constraint_label_position(exec_state, &args, "diameter")?;
2925
2926 create_circular_radius_constraint(
2927 segment,
2928 |points| SketchConstraintKind::Diameter {
2929 points,
2930 label_position: label_position.clone(),
2931 },
2932 args.source_range,
2933 )
2934 .map(|constraint| KclValue::SketchConstraint {
2935 value: Box::new(constraint),
2936 })
2937}
2938
2939pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2940 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2941 "points",
2942 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2943 exec_state,
2944 )?;
2945 let label_position = get_constraint_label_position(exec_state, &args, "horizontalDistance")?;
2946 let [p1, p2] = points.as_slice() else {
2947 return Err(KclError::new_semantic(KclErrorDetails::new(
2948 "must have two input points".to_owned(),
2949 vec![args.source_range],
2950 )));
2951 };
2952 match (p1, p2) {
2953 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2954 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2955 return Err(KclError::new_semantic(KclErrorDetails::new(
2956 "first point must be an unsolved segment".to_owned(),
2957 vec![args.source_range],
2958 )));
2959 };
2960 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2961 return Err(KclError::new_semantic(KclErrorDetails::new(
2962 "second point must be an unsolved segment".to_owned(),
2963 vec![args.source_range],
2964 )));
2965 };
2966 match (&unsolved0.kind, &unsolved1.kind) {
2967 (
2968 UnsolvedSegmentKind::Point { position: pos0, .. },
2969 UnsolvedSegmentKind::Point { position: pos1, .. },
2970 ) => {
2971 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2974 (
2975 UnsolvedExpr::Unknown(p0_x),
2976 UnsolvedExpr::Unknown(p0_y),
2977 UnsolvedExpr::Unknown(p1_x),
2978 UnsolvedExpr::Unknown(p1_y),
2979 ) => {
2980 let sketch_constraint = SketchConstraint {
2982 kind: SketchConstraintKind::HorizontalDistance {
2983 points: [
2984 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2985 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2986 object_id: unsolved0.object_id,
2987 }),
2988 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2989 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2990 object_id: unsolved1.object_id,
2991 }),
2992 ],
2993 label_position,
2994 },
2995 meta: vec![args.source_range.into()],
2996 };
2997 Ok(KclValue::SketchConstraint {
2998 value: Box::new(sketch_constraint),
2999 })
3000 }
3001 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3002 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
3003 .to_owned(),
3004 vec![args.source_range],
3005 ))),
3006 }
3007 }
3008 (
3009 UnsolvedSegmentKind::Point { .. },
3010 UnsolvedSegmentKind::Line { .. },
3011 )
3012 | (
3013 UnsolvedSegmentKind::Line { .. },
3014 UnsolvedSegmentKind::Point { .. },
3015 ) => Err(KclError::new_semantic(KclErrorDetails::new(
3016 "horizontalDistance() between a point and a line is invalid because the constraint is under-specified".to_owned(),
3017 vec![args.source_range],
3018 ))),
3019 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3020 "horizontalDistance() arguments must be unsolved points".to_owned(),
3021 vec![args.source_range],
3022 ))),
3023 }
3024 }
3025 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
3027 if !point2d_is_origin(point2d) {
3028 return Err(KclError::new_semantic(KclErrorDetails::new(
3029 "horizontalDistance() Point2d arguments must be ORIGIN".to_owned(),
3030 vec![args.source_range],
3031 )));
3032 }
3033
3034 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
3035 return Err(KclError::new_semantic(KclErrorDetails::new(
3036 "segment must be an unsolved segment".to_owned(),
3037 vec![args.source_range],
3038 )));
3039 };
3040 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3041 return Err(KclError::new_semantic(KclErrorDetails::new(
3042 "horizontalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
3043 vec![args.source_range],
3044 )));
3045 };
3046 match (&position[0], &position[1]) {
3047 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
3048 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3049 vars: crate::front::Point2d {
3050 x: *point_x,
3051 y: *point_y,
3052 },
3053 object_id: unsolved.object_id,
3054 });
3055 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
3056 [point, ConstrainablePoint2dOrOrigin::Origin]
3057 } else {
3058 [ConstrainablePoint2dOrOrigin::Origin, point]
3059 };
3060 Ok(KclValue::SketchConstraint {
3061 value: Box::new(SketchConstraint {
3062 kind: SketchConstraintKind::HorizontalDistance { points, label_position },
3063 meta: vec![args.source_range.into()],
3064 }),
3065 })
3066 }
3067 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3068 "unimplemented: horizontalDistance() point arguments must be sketch vars in all coordinates"
3069 .to_owned(),
3070 vec![args.source_range],
3071 ))),
3072 }
3073 }
3074 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3075 "horizontalDistance() arguments must be point segments or ORIGIN".to_owned(),
3076 vec![args.source_range],
3077 ))),
3078 }
3079}
3080
3081pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3082 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
3083 "points",
3084 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3085 exec_state,
3086 )?;
3087 let label_position = get_constraint_label_position(exec_state, &args, "verticalDistance")?;
3088 let [p1, p2] = points.as_slice() else {
3089 return Err(KclError::new_semantic(KclErrorDetails::new(
3090 "must have two input points".to_owned(),
3091 vec![args.source_range],
3092 )));
3093 };
3094 match (p1, p2) {
3095 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
3096 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
3097 return Err(KclError::new_semantic(KclErrorDetails::new(
3098 "first point must be an unsolved segment".to_owned(),
3099 vec![args.source_range],
3100 )));
3101 };
3102 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
3103 return Err(KclError::new_semantic(KclErrorDetails::new(
3104 "second point must be an unsolved segment".to_owned(),
3105 vec![args.source_range],
3106 )));
3107 };
3108 match (&unsolved0.kind, &unsolved1.kind) {
3109 (
3110 UnsolvedSegmentKind::Point { position: pos0, .. },
3111 UnsolvedSegmentKind::Point { position: pos1, .. },
3112 ) => {
3113 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
3116 (
3117 UnsolvedExpr::Unknown(p0_x),
3118 UnsolvedExpr::Unknown(p0_y),
3119 UnsolvedExpr::Unknown(p1_x),
3120 UnsolvedExpr::Unknown(p1_y),
3121 ) => {
3122 let sketch_constraint = SketchConstraint {
3124 kind: SketchConstraintKind::VerticalDistance {
3125 points: [
3126 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3127 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
3128 object_id: unsolved0.object_id,
3129 }),
3130 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3131 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
3132 object_id: unsolved1.object_id,
3133 }),
3134 ],
3135 label_position,
3136 },
3137 meta: vec![args.source_range.into()],
3138 };
3139 Ok(KclValue::SketchConstraint {
3140 value: Box::new(sketch_constraint),
3141 })
3142 }
3143 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3144 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
3145 .to_owned(),
3146 vec![args.source_range],
3147 ))),
3148 }
3149 }
3150 (
3151 UnsolvedSegmentKind::Point { .. },
3152 UnsolvedSegmentKind::Line { .. },
3153 )
3154 | (
3155 UnsolvedSegmentKind::Line { .. },
3156 UnsolvedSegmentKind::Point { .. },
3157 ) => Err(KclError::new_semantic(KclErrorDetails::new(
3158 "verticalDistance() between a point and a line is invalid because the constraint is under-specified".to_owned(),
3159 vec![args.source_range],
3160 ))),
3161 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3162 "verticalDistance() arguments must be unsolved points".to_owned(),
3163 vec![args.source_range],
3164 ))),
3165 }
3166 }
3167 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
3168 if !point2d_is_origin(point2d) {
3169 return Err(KclError::new_semantic(KclErrorDetails::new(
3170 "verticalDistance() Point2d arguments must be ORIGIN".to_owned(),
3171 vec![args.source_range],
3172 )));
3173 }
3174
3175 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
3176 return Err(KclError::new_semantic(KclErrorDetails::new(
3177 "segment must be an unsolved segment".to_owned(),
3178 vec![args.source_range],
3179 )));
3180 };
3181 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3182 return Err(KclError::new_semantic(KclErrorDetails::new(
3183 "verticalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
3184 vec![args.source_range],
3185 )));
3186 };
3187 match (&position[0], &position[1]) {
3188 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
3189 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
3190 vars: crate::front::Point2d {
3191 x: *point_x,
3192 y: *point_y,
3193 },
3194 object_id: unsolved.object_id,
3195 });
3196 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
3197 [point, ConstrainablePoint2dOrOrigin::Origin]
3198 } else {
3199 [ConstrainablePoint2dOrOrigin::Origin, point]
3200 };
3201 Ok(KclValue::SketchConstraint {
3202 value: Box::new(SketchConstraint {
3203 kind: SketchConstraintKind::VerticalDistance { points, label_position },
3204 meta: vec![args.source_range.into()],
3205 }),
3206 })
3207 }
3208 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3209 "unimplemented: verticalDistance() point arguments must be sketch vars in all coordinates"
3210 .to_owned(),
3211 vec![args.source_range],
3212 ))),
3213 }
3214 }
3215 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3216 "verticalDistance() arguments must be point segments or ORIGIN".to_owned(),
3217 vec![args.source_range],
3218 ))),
3219 }
3220}
3221
3222#[derive(Debug, Clone, Copy)]
3223enum MidpointPointVars {
3224 Segment {
3225 coords: [SketchVarId; 2],
3226 constraint_segment: ConstraintSegment,
3227 },
3228 Origin,
3229}
3230
3231impl MidpointPointVars {
3232 fn constraint_segment(self) -> ConstraintSegment {
3233 match self {
3234 Self::Segment { constraint_segment, .. } => constraint_segment,
3235 Self::Origin => ConstraintSegment::ORIGIN,
3236 }
3237 }
3238}
3239
3240#[derive(Debug, Clone, Copy)]
3241enum MidpointTargetVars {
3242 Line {
3243 start: [SketchVarId; 2],
3244 end: [SketchVarId; 2],
3245 object_id: ObjectId,
3246 },
3247 Arc {
3248 center: [SketchVarId; 2],
3249 start: [SketchVarId; 2],
3250 end: [SketchVarId; 2],
3251 object_id: ObjectId,
3252 },
3253}
3254
3255impl MidpointTargetVars {
3256 fn object_id(self) -> ObjectId {
3257 match self {
3258 Self::Line { object_id, .. } | Self::Arc { object_id, .. } => object_id,
3259 }
3260 }
3261}
3262
3263fn extract_midpoint_point(segment_value: &KclValue, range: crate::SourceRange) -> Result<MidpointPointVars, KclError> {
3264 if point2d_is_origin(segment_value) {
3265 return Ok(MidpointPointVars::Origin);
3266 }
3267
3268 let KclValue::Segment { value: segment } = segment_value else {
3269 return Err(KclError::new_semantic(KclErrorDetails::new(
3270 format!(
3271 "midpoint() point must be a point Segment or ORIGIN, but found {}",
3272 segment_value.human_friendly_type()
3273 ),
3274 vec![range],
3275 )));
3276 };
3277 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3278 return Err(KclError::new_semantic(KclErrorDetails::new(
3279 "midpoint() point must be an unsolved point Segment".to_owned(),
3280 vec![range],
3281 )));
3282 };
3283 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3284 return Err(KclError::new_semantic(KclErrorDetails::new(
3285 "midpoint() point must be a point Segment".to_owned(),
3286 vec![range],
3287 )));
3288 };
3289 let (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) = (&position[0], &position[1]) else {
3290 return Err(KclError::new_semantic(KclErrorDetails::new(
3291 "midpoint() point coordinates must be sketch vars".to_owned(),
3292 vec![range],
3293 )));
3294 };
3295
3296 Ok(MidpointPointVars::Segment {
3297 coords: [*point_x, *point_y],
3298 constraint_segment: unsolved.object_id.into(),
3299 })
3300}
3301
3302fn extract_midpoint_target(
3303 segment_value: &KclValue,
3304 range: crate::SourceRange,
3305) -> Result<MidpointTargetVars, KclError> {
3306 let KclValue::Segment { value: segment } = segment_value else {
3307 return Err(KclError::new_semantic(KclErrorDetails::new(
3308 format!(
3309 "midpoint() target must be a line or arc Segment, but found {}",
3310 segment_value.human_friendly_type()
3311 ),
3312 vec![range],
3313 )));
3314 };
3315 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3316 return Err(KclError::new_semantic(KclErrorDetails::new(
3317 "midpoint() target must be an unsolved line or arc Segment".to_owned(),
3318 vec![range],
3319 )));
3320 };
3321 match &unsolved.kind {
3322 UnsolvedSegmentKind::Line { start, end, .. } => {
3323 let (
3324 UnsolvedExpr::Unknown(start_x),
3325 UnsolvedExpr::Unknown(start_y),
3326 UnsolvedExpr::Unknown(end_x),
3327 UnsolvedExpr::Unknown(end_y),
3328 ) = (&start[0], &start[1], &end[0], &end[1])
3329 else {
3330 return Err(KclError::new_semantic(KclErrorDetails::new(
3331 "midpoint() line coordinates must be sketch vars".to_owned(),
3332 vec![range],
3333 )));
3334 };
3335
3336 Ok(MidpointTargetVars::Line {
3337 start: [*start_x, *start_y],
3338 end: [*end_x, *end_y],
3339 object_id: unsolved.object_id,
3340 })
3341 }
3342 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3343 let (
3344 UnsolvedExpr::Unknown(center_x),
3345 UnsolvedExpr::Unknown(center_y),
3346 UnsolvedExpr::Unknown(start_x),
3347 UnsolvedExpr::Unknown(start_y),
3348 UnsolvedExpr::Unknown(end_x),
3349 UnsolvedExpr::Unknown(end_y),
3350 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3351 else {
3352 return Err(KclError::new_semantic(KclErrorDetails::new(
3353 "midpoint() arc center/start/end coordinates must be sketch vars".to_owned(),
3354 vec![range],
3355 )));
3356 };
3357
3358 Ok(MidpointTargetVars::Arc {
3359 center: [*center_x, *center_y],
3360 start: [*start_x, *start_y],
3361 end: [*end_x, *end_y],
3362 object_id: unsolved.object_id,
3363 })
3364 }
3365 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3366 "midpoint() target must be a line or circular arc Segment".to_owned(),
3367 vec![range],
3368 ))),
3369 }
3370}
3371
3372pub async fn midpoint(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3373 let target: KclValue =
3374 args.get_unlabeled_kw_arg("input", &RuntimeType::Primitive(PrimitiveType::Segment), exec_state)?;
3375 let point: KclValue = args.get_kw_arg(
3376 "point",
3377 &RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()]),
3378 exec_state,
3379 )?;
3380 let range = args.source_range;
3381
3382 let point = extract_midpoint_point(&point, range)?;
3383 let target = extract_midpoint_target(&target, range)?;
3384
3385 let (solver_point, origin_constraints) = match point {
3386 MidpointPointVars::Segment { coords, .. } => (datum_point(coords, range)?, None),
3387 MidpointPointVars::Origin => {
3388 let (origin_point, origin_constraints) = fixed_origin_datum_point(exec_state, range, "midpoint")?;
3389 (origin_point, Some(origin_constraints))
3390 }
3391 };
3392
3393 let constraint_id = exec_state.next_object_id();
3394 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3395 return Err(KclError::new_semantic(KclErrorDetails::new(
3396 "midpoint() can only be used inside a sketch block".to_owned(),
3397 vec![range],
3398 )));
3399 };
3400
3401 if let Some(origin_constraints) = origin_constraints {
3402 sketch_state.solver_constraints.extend(origin_constraints);
3403 }
3404
3405 match target {
3406 MidpointTargetVars::Line { start, end, .. } => {
3407 sketch_state.solver_constraints.push(SolverConstraint::Midpoint(
3408 DatumLineSegment::new(datum_point(start, range)?, datum_point(end, range)?),
3409 solver_point,
3410 ));
3411 }
3412 MidpointTargetVars::Arc { center, start, end, .. } => {
3413 sketch_state
3414 .solver_constraints
3415 .extend(SolverConstraint::point_bisects_arc(
3416 DatumCircularArc {
3417 center: datum_point(center, range)?,
3418 start: datum_point(start, range)?,
3419 end: datum_point(end, range)?,
3420 },
3421 solver_point,
3422 ));
3423 }
3424 }
3425
3426 let constraint = Constraint::Midpoint(Midpoint {
3427 point: point.constraint_segment(),
3428 segment: target.object_id(),
3429 });
3430 sketch_state.sketch_constraints.push(constraint_id);
3431 track_constraint(constraint_id, constraint, exec_state, &args);
3432
3433 Ok(KclValue::none())
3434}
3435
3436pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3437 #[derive(Clone, Copy)]
3438 struct ConstrainableLine {
3439 solver_line: DatumLineSegment,
3440 object_id: ObjectId,
3441 }
3442
3443 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
3444 "lines",
3445 &RuntimeType::Array(
3446 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
3447 ArrayLen::Minimum(2),
3448 ),
3449 exec_state,
3450 )?;
3451 let range = args.source_range;
3452 let constrainable_lines: Vec<ConstrainableLine> = lines
3453 .iter()
3454 .map(|line| {
3455 let KclValue::Segment { value: segment } = line else {
3456 return Err(KclError::new_semantic(KclErrorDetails::new(
3457 "line argument must be a Segment".to_owned(),
3458 vec![args.source_range],
3459 )));
3460 };
3461 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3462 return Err(KclError::new_internal(KclErrorDetails::new(
3463 "line must be an unsolved Segment".to_owned(),
3464 vec![args.source_range],
3465 )));
3466 };
3467 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
3468 return Err(KclError::new_semantic(KclErrorDetails::new(
3469 "line argument must be a line, no other type of Segment".to_owned(),
3470 vec![args.source_range],
3471 )));
3472 };
3473 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
3474 return Err(KclError::new_semantic(KclErrorDetails::new(
3475 "line's start x coordinate must be a var".to_owned(),
3476 vec![args.source_range],
3477 )));
3478 };
3479 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
3480 return Err(KclError::new_semantic(KclErrorDetails::new(
3481 "line's start y coordinate must be a var".to_owned(),
3482 vec![args.source_range],
3483 )));
3484 };
3485 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
3486 return Err(KclError::new_semantic(KclErrorDetails::new(
3487 "line's end x coordinate must be a var".to_owned(),
3488 vec![args.source_range],
3489 )));
3490 };
3491 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
3492 return Err(KclError::new_semantic(KclErrorDetails::new(
3493 "line's end y coordinate must be a var".to_owned(),
3494 vec![args.source_range],
3495 )));
3496 };
3497
3498 let solver_line_p0 =
3499 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
3500 let solver_line_p1 =
3501 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
3502
3503 Ok(ConstrainableLine {
3504 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
3505 object_id: unsolved.object_id,
3506 })
3507 })
3508 .collect::<Result<_, _>>()?;
3509
3510 let constraint_id = exec_state.next_object_id();
3511 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3513 return Err(KclError::new_semantic(KclErrorDetails::new(
3514 "equalLength() can only be used inside a sketch block".to_owned(),
3515 vec![args.source_range],
3516 )));
3517 };
3518 let first_line = constrainable_lines[0];
3519 for line in constrainable_lines.iter().skip(1) {
3520 sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
3521 first_line.solver_line,
3522 line.solver_line,
3523 ));
3524 }
3525 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
3526 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
3527 });
3528 sketch_state.sketch_constraints.push(constraint_id);
3529 track_constraint(constraint_id, constraint, exec_state, &args);
3530 Ok(KclValue::none())
3531}
3532
3533fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
3534 Ok(DatumPoint::new_xy(
3535 coords[0].to_constraint_id(range)?,
3536 coords[1].to_constraint_id(range)?,
3537 ))
3538}
3539
3540fn sketch_var_initial_value(
3541 sketch_vars: &[KclValue],
3542 id: SketchVarId,
3543 exec_state: &mut ExecState,
3544 range: crate::SourceRange,
3545) -> Result<f64, KclError> {
3546 sketch_vars
3547 .get(id.0)
3548 .and_then(KclValue::as_sketch_var)
3549 .map(|sketch_var| {
3550 sketch_var
3551 .initial_value_to_solver_units(exec_state, range, "equalRadius() hidden shared radius initial value")
3552 .map(|value| value.n)
3553 })
3554 .transpose()?
3555 .ok_or_else(|| {
3556 KclError::new_internal(KclErrorDetails::new(
3557 format!("Missing sketch variable initial value for id {}", id.0),
3558 vec![range],
3559 ))
3560 })
3561}
3562
3563fn radius_guess(
3564 sketch_vars: &[KclValue],
3565 center: [SketchVarId; 2],
3566 point: [SketchVarId; 2],
3567 exec_state: &mut ExecState,
3568 range: crate::SourceRange,
3569) -> Result<f64, KclError> {
3570 let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
3571 - sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
3572 let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
3573 - sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
3574 Ok(libm::hypot(dx, dy))
3575}
3576
3577fn reflect_point_across_line(point: [f64; 2], axis_start: [f64; 2], axis_end: [f64; 2]) -> [f64; 2] {
3578 let [px, py] = point;
3579 let [ax, ay] = axis_start;
3580 let [bx, by] = axis_end;
3581 let dx = bx - ax;
3582 let dy = by - ay;
3583 let axis_len_sq = dx * dx + dy * dy;
3584 if axis_len_sq <= f64::EPSILON {
3585 return point;
3586 }
3587
3588 let point_from_axis = [px - ax, py - ay];
3589 let projection_scale = (point_from_axis[0] * dx + point_from_axis[1] * dy) / axis_len_sq;
3590 let projected = [ax + projection_scale * dx, ay + projection_scale * dy];
3591
3592 [2.0 * projected[0] - px, 2.0 * projected[1] - py]
3593}
3594
3595fn symmetric_hidden_point_guess(
3598 sketch_vars: &[KclValue],
3599 point: [SketchVarId; 2],
3600 axis: SymmetricLineVars,
3601 exec_state: &mut ExecState,
3602 range: crate::SourceRange,
3603) -> Result<[f64; 2], KclError> {
3604 let point = [
3605 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
3606 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
3607 ];
3608 let axis_start = [
3609 sketch_var_initial_value(sketch_vars, axis.start[0], exec_state, range)?,
3610 sketch_var_initial_value(sketch_vars, axis.start[1], exec_state, range)?,
3611 ];
3612 let axis_end = [
3613 sketch_var_initial_value(sketch_vars, axis.end[0], exec_state, range)?,
3614 sketch_var_initial_value(sketch_vars, axis.end[1], exec_state, range)?,
3615 ];
3616
3617 Ok(reflect_point_across_line(point, axis_start, axis_end))
3618}
3619
3620fn create_hidden_point(
3621 exec_state: &mut ExecState,
3622 initial_position: [f64; 2],
3623 range: crate::SourceRange,
3624) -> Result<[SketchVarId; 2], KclError> {
3625 let sketch_var_ty = solver_numeric_type(exec_state);
3626 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3627 return Err(KclError::new_semantic(KclErrorDetails::new(
3628 "symmetric() can only be used inside a sketch block".to_owned(),
3629 vec![range],
3630 )));
3631 };
3632
3633 let x_id = sketch_state.next_sketch_var_id();
3634 sketch_state.sketch_vars.push(KclValue::SketchVar {
3635 value: Box::new(crate::execution::SketchVar {
3636 id: x_id,
3637 initial_value: initial_position[0],
3638 ty: sketch_var_ty,
3639 node_path: None,
3641 meta: vec![],
3642 }),
3643 });
3644
3645 let y_id = sketch_state.next_sketch_var_id();
3646 sketch_state.sketch_vars.push(KclValue::SketchVar {
3647 value: Box::new(crate::execution::SketchVar {
3648 id: y_id,
3649 initial_value: initial_position[1],
3650 ty: sketch_var_ty,
3651 node_path: None,
3653 meta: vec![],
3654 }),
3655 });
3656
3657 Ok([x_id, y_id])
3658}
3659
3660pub async fn equal_radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3661 #[derive(Debug, Clone, Copy)]
3662 struct RadiusInputVars {
3663 center: [SketchVarId; 2],
3664 start: [SketchVarId; 2],
3665 end: Option<[SketchVarId; 2]>,
3666 }
3667
3668 #[derive(Debug, Clone, Copy)]
3669 enum EqualRadiusInput {
3670 Radius(RadiusInputVars),
3671 }
3672
3673 fn extract_equal_radius_input(
3674 segment_value: &KclValue,
3675 range: crate::SourceRange,
3676 ) -> Result<(EqualRadiusInput, ObjectId), KclError> {
3677 let KclValue::Segment { value: segment } = segment_value else {
3678 return Err(KclError::new_semantic(KclErrorDetails::new(
3679 format!(
3680 "equalRadius() arguments must be segments but found {}",
3681 segment_value.human_friendly_type()
3682 ),
3683 vec![range],
3684 )));
3685 };
3686 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3687 return Err(KclError::new_semantic(KclErrorDetails::new(
3688 "equalRadius() arguments must be unsolved segments".to_owned(),
3689 vec![range],
3690 )));
3691 };
3692 match &unsolved.kind {
3693 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3694 let (
3695 UnsolvedExpr::Unknown(center_x),
3696 UnsolvedExpr::Unknown(center_y),
3697 UnsolvedExpr::Unknown(start_x),
3698 UnsolvedExpr::Unknown(start_y),
3699 UnsolvedExpr::Unknown(end_x),
3700 UnsolvedExpr::Unknown(end_y),
3701 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3702 else {
3703 return Err(KclError::new_semantic(KclErrorDetails::new(
3704 "arc center/start/end coordinates must be sketch vars for equalRadius()".to_owned(),
3705 vec![range],
3706 )));
3707 };
3708 Ok((
3709 EqualRadiusInput::Radius(RadiusInputVars {
3710 center: [*center_x, *center_y],
3711 start: [*start_x, *start_y],
3712 end: Some([*end_x, *end_y]),
3713 }),
3714 unsolved.object_id,
3715 ))
3716 }
3717 UnsolvedSegmentKind::Circle { center, start, .. } => {
3718 let (
3719 UnsolvedExpr::Unknown(center_x),
3720 UnsolvedExpr::Unknown(center_y),
3721 UnsolvedExpr::Unknown(start_x),
3722 UnsolvedExpr::Unknown(start_y),
3723 ) = (¢er[0], ¢er[1], &start[0], &start[1])
3724 else {
3725 return Err(KclError::new_semantic(KclErrorDetails::new(
3726 "circle center/start coordinates must be sketch vars for equalRadius()".to_owned(),
3727 vec![range],
3728 )));
3729 };
3730 Ok((
3731 EqualRadiusInput::Radius(RadiusInputVars {
3732 center: [*center_x, *center_y],
3733 start: [*start_x, *start_y],
3734 end: None,
3735 }),
3736 unsolved.object_id,
3737 ))
3738 }
3739 other => Err(KclError::new_semantic(KclErrorDetails::new(
3740 format!(
3741 "equalRadius() currently supports only arc and circle segments, you provided {}",
3742 other.human_friendly_kind_with_article()
3743 ),
3744 vec![range],
3745 ))),
3746 }
3747 }
3748
3749 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
3750 "input",
3751 &RuntimeType::Array(
3752 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
3753 ArrayLen::Minimum(2),
3754 ),
3755 exec_state,
3756 )?;
3757 let range = args.source_range;
3758
3759 let extracted_input = input
3760 .iter()
3761 .map(|segment_value| extract_equal_radius_input(segment_value, range))
3762 .collect::<Result<Vec<_>, _>>()?;
3763 let radius_inputs: Vec<RadiusInputVars> = extracted_input
3764 .iter()
3765 .map(|(equal_radius_input, _)| match equal_radius_input {
3766 EqualRadiusInput::Radius(radius_input) => *radius_input,
3767 })
3768 .collect();
3769 let input_object_ids: Vec<ObjectId> = extracted_input.iter().map(|(_, object_id)| *object_id).collect();
3770
3771 let sketch_var_ty = solver_numeric_type(exec_state);
3772 let constraint_id = exec_state.next_object_id();
3773
3774 let sketch_vars = {
3775 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3776 return Err(KclError::new_semantic(KclErrorDetails::new(
3777 "equalRadius() can only be used inside a sketch block".to_owned(),
3778 vec![range],
3779 )));
3780 };
3781 sketch_state.sketch_vars.clone()
3782 };
3783
3784 let radius_initial_value = radius_guess(
3785 &sketch_vars,
3786 radius_inputs[0].center,
3787 radius_inputs[0].start,
3788 exec_state,
3789 range,
3790 )?;
3791
3792 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3793 return Err(KclError::new_semantic(KclErrorDetails::new(
3794 "equalRadius() can only be used inside a sketch block".to_owned(),
3795 vec![range],
3796 )));
3797 };
3798 let radius_id = sketch_state.next_sketch_var_id();
3799 sketch_state.sketch_vars.push(KclValue::SketchVar {
3800 value: Box::new(crate::execution::SketchVar {
3801 id: radius_id,
3802 initial_value: radius_initial_value,
3803 ty: sketch_var_ty,
3804 node_path: None,
3806 meta: vec![],
3807 }),
3808 });
3809 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
3810
3811 for radius_input in radius_inputs {
3812 let center = datum_point(radius_input.center, range)?;
3813 let start = datum_point(radius_input.start, range)?;
3814 sketch_state
3815 .solver_constraints
3816 .push(SolverConstraint::DistanceVar(start, center, radius));
3817 if let Some(end) = radius_input.end {
3818 let end = datum_point(end, range)?;
3819 sketch_state
3820 .solver_constraints
3821 .push(SolverConstraint::DistanceVar(end, center, radius));
3822 }
3823 }
3824
3825 let constraint = crate::front::Constraint::EqualRadius(EqualRadius {
3826 input: input_object_ids,
3827 });
3828 sketch_state.sketch_constraints.push(constraint_id);
3829 track_constraint(constraint_id, constraint, exec_state, &args);
3830
3831 Ok(KclValue::none())
3832}
3833
3834pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3835 let Some(Some(sketch_id)) = exec_state.sketch_block().map(|sb| sb.sketch_id) else {
3836 return Err(KclError::new_semantic(KclErrorDetails::new(
3837 "tangent() cannot be used outside a sketch block".to_owned(),
3838 vec![args.source_range],
3839 )));
3840 };
3841
3842 #[derive(Debug, Clone)]
3843 enum TangentInput {
3844 Line(LineVars),
3845 Circular(ArcVars),
3846 }
3847
3848 fn extract_tangent_input(
3849 segment_value: &KclValue,
3850 range: crate::SourceRange,
3851 ) -> Result<(TangentInput, ObjectId), KclError> {
3852 let KclValue::Segment { value: segment } = segment_value else {
3853 return Err(KclError::new_semantic(KclErrorDetails::new(
3854 "tangent() arguments must be segments".to_owned(),
3855 vec![range],
3856 )));
3857 };
3858 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3859 return Err(KclError::new_semantic(KclErrorDetails::new(
3860 "tangent() arguments must be unsolved segments".to_owned(),
3861 vec![range],
3862 )));
3863 };
3864 match &unsolved.kind {
3865 UnsolvedSegmentKind::Line { start, end, .. } => {
3866 let (
3867 UnsolvedExpr::Unknown(start_x),
3868 UnsolvedExpr::Unknown(start_y),
3869 UnsolvedExpr::Unknown(end_x),
3870 UnsolvedExpr::Unknown(end_y),
3871 ) = (&start[0], &start[1], &end[0], &end[1])
3872 else {
3873 return Err(KclError::new_semantic(KclErrorDetails::new(
3874 "line coordinates must be sketch vars for tangent()".to_owned(),
3875 vec![range],
3876 )));
3877 };
3878 Ok((
3879 TangentInput::Line(LineVars {
3880 start: [*start_x, *start_y],
3881 end: [*end_x, *end_y],
3882 }),
3883 unsolved.object_id,
3884 ))
3885 }
3886 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
3887 let (
3888 UnsolvedExpr::Unknown(center_x),
3889 UnsolvedExpr::Unknown(center_y),
3890 UnsolvedExpr::Unknown(start_x),
3891 UnsolvedExpr::Unknown(start_y),
3892 UnsolvedExpr::Unknown(end_x),
3893 UnsolvedExpr::Unknown(end_y),
3894 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
3895 else {
3896 return Err(KclError::new_semantic(KclErrorDetails::new(
3897 "arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
3898 vec![range],
3899 )));
3900 };
3901 Ok((
3902 TangentInput::Circular(ArcVars {
3903 center: [*center_x, *center_y],
3904 start: [*start_x, *start_y],
3905 end: Some([*end_x, *end_y]),
3906 }),
3907 unsolved.object_id,
3908 ))
3909 }
3910 UnsolvedSegmentKind::Circle { center, start, .. } => {
3911 let (
3912 UnsolvedExpr::Unknown(center_x),
3913 UnsolvedExpr::Unknown(center_y),
3914 UnsolvedExpr::Unknown(start_x),
3915 UnsolvedExpr::Unknown(start_y),
3916 ) = (¢er[0], ¢er[1], &start[0], &start[1])
3917 else {
3918 return Err(KclError::new_semantic(KclErrorDetails::new(
3919 "circle center/start coordinates must be sketch vars for tangent()".to_owned(),
3920 vec![range],
3921 )));
3922 };
3923 Ok((
3924 TangentInput::Circular(ArcVars {
3925 center: [*center_x, *center_y],
3926 start: [*start_x, *start_y],
3927 end: None,
3928 }),
3929 unsolved.object_id,
3930 ))
3931 }
3932 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3933 "tangent() supports only line, arc, and circle segments".to_owned(),
3934 vec![range],
3935 ))),
3936 }
3937 }
3938
3939 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
3940 "input",
3941 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3942 exec_state,
3943 )?;
3944 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
3945 KclError::new_semantic(KclErrorDetails::new(
3946 "tangent() requires exactly 2 input segments".to_owned(),
3947 vec![args.source_range],
3948 ))
3949 })?;
3950 let range = args.source_range;
3951 let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
3952 let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
3953
3954 enum TangentCase {
3955 LineCircular(LineVars, ArcVars),
3956 CircularCircular(ArcVars, ArcVars),
3957 }
3958 let tangent_case = match (input0, input1) {
3959 (TangentInput::Line(line), TangentInput::Circular(circular))
3960 | (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
3961 (TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
3962 TangentCase::CircularCircular(circular0, circular1)
3963 }
3964 (TangentInput::Line(_), TangentInput::Line(_)) => {
3965 return Err(KclError::new_semantic(KclErrorDetails::new(
3966 "tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
3967 vec![range],
3968 )));
3969 }
3970 };
3971
3972 let sketch_var_ty = solver_numeric_type(exec_state);
3973 let constraint_id = exec_state.next_object_id();
3974
3975 let sketch_vars = {
3976 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3977 return Err(KclError::new_semantic(KclErrorDetails::new(
3978 "tangent() can only be used inside a sketch block".to_owned(),
3979 vec![range],
3980 )));
3981 };
3982 sketch_state.sketch_vars.clone()
3983 };
3984
3985 match tangent_case {
3987 TangentCase::LineCircular(line, circular) => {
3988 let tangency_key = make_line_arc_tangency_key(line, circular);
3989 let tangency_side = match exec_state.constraint_state(sketch_id, &tangency_key) {
3990 Some(ConstraintState::Tangency(TangencyMode::LineCircle(side))) => side,
3991 _ => {
3992 let side = infer_line_tangent_side(&sketch_vars, line, circular.center, exec_state, range)?;
3993 exec_state.set_constraint_state(
3994 sketch_id,
3995 tangency_key,
3996 ConstraintState::Tangency(TangencyMode::LineCircle(side)),
3997 );
3998 side
3999 }
4000 };
4001 let line_p0 = datum_point(line.start, range)?;
4002 let line_p1 = datum_point(line.end, range)?;
4003 let line_datum = DatumLineSegment::new(line_p0, line_p1);
4004
4005 let center = datum_point(circular.center, range)?;
4006 let circular_start = datum_point(circular.start, range)?;
4007 let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
4008 let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
4009 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4010 return Err(KclError::new_semantic(KclErrorDetails::new(
4011 "tangent() can only be used inside a sketch block".to_owned(),
4012 vec![range],
4013 )));
4014 };
4015 let radius_id = sketch_state.next_sketch_var_id();
4016 sketch_state.sketch_vars.push(KclValue::SketchVar {
4017 value: Box::new(crate::execution::SketchVar {
4018 id: radius_id,
4019 initial_value: radius_initial_value,
4020 ty: sketch_var_ty,
4021 node_path: None,
4023 meta: vec![],
4024 }),
4025 });
4026 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
4027 let circle = DatumCircle { center, radius };
4028
4029 sketch_state
4034 .solver_constraints
4035 .push(SolverConstraint::DistanceVar(circular_start, center, radius));
4036 if let Some(circular_end) = circular_end {
4037 sketch_state
4038 .solver_constraints
4039 .push(SolverConstraint::DistanceVar(circular_end, center, radius));
4040 }
4041 sketch_state
4042 .solver_constraints
4043 .push(SolverConstraint::LineTangentToCircle(line_datum, circle, tangency_side));
4044 }
4045 TangentCase::CircularCircular(circular0, circular1) => {
4046 let tangency_key = make_arc_arc_tangency_key(circular0, circular1);
4047 let tangency_side = match exec_state.constraint_state(sketch_id, &tangency_key) {
4048 Some(ConstraintState::Tangency(TangencyMode::CircleCircle(side))) => side,
4049 _ => {
4050 let side = infer_arc_tangent_side(&sketch_vars, circular0, circular1, exec_state, range)?;
4051 exec_state.set_constraint_state(
4052 sketch_id,
4053 tangency_key,
4054 ConstraintState::Tangency(TangencyMode::CircleCircle(side)),
4055 );
4056 side
4057 }
4058 };
4059 let center0 = datum_point(circular0.center, range)?;
4060 let start0 = datum_point(circular0.start, range)?;
4061 let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
4062 let radius0_initial_value =
4063 radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
4064 let center1 = datum_point(circular1.center, range)?;
4065 let start1 = datum_point(circular1.start, range)?;
4066 let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
4067 let radius1_initial_value =
4068 radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
4069 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4070 return Err(KclError::new_semantic(KclErrorDetails::new(
4071 "tangent() can only be used inside a sketch block".to_owned(),
4072 vec![range],
4073 )));
4074 };
4075 let radius0_id = sketch_state.next_sketch_var_id();
4076 sketch_state.sketch_vars.push(KclValue::SketchVar {
4077 value: Box::new(crate::execution::SketchVar {
4078 id: radius0_id,
4079 initial_value: radius0_initial_value,
4080 ty: sketch_var_ty,
4081 node_path: None,
4083 meta: vec![],
4084 }),
4085 });
4086 let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
4087 let circle0 = DatumCircle {
4088 center: center0,
4089 radius: radius0,
4090 };
4091
4092 let radius1_id = sketch_state.next_sketch_var_id();
4093 sketch_state.sketch_vars.push(KclValue::SketchVar {
4094 value: Box::new(crate::execution::SketchVar {
4095 id: radius1_id,
4096 initial_value: radius1_initial_value,
4097 ty: sketch_var_ty,
4098 node_path: None,
4100 meta: vec![],
4101 }),
4102 });
4103 let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
4104 let circle1 = DatumCircle {
4105 center: center1,
4106 radius: radius1,
4107 };
4108
4109 sketch_state
4114 .solver_constraints
4115 .push(SolverConstraint::DistanceVar(start0, center0, radius0));
4116 if let Some(end0) = end0 {
4117 sketch_state
4118 .solver_constraints
4119 .push(SolverConstraint::DistanceVar(end0, center0, radius0));
4120 }
4121 sketch_state
4122 .solver_constraints
4123 .push(SolverConstraint::DistanceVar(start1, center1, radius1));
4124 if let Some(end1) = end1 {
4125 sketch_state
4126 .solver_constraints
4127 .push(SolverConstraint::DistanceVar(end1, center1, radius1));
4128 }
4129 sketch_state
4130 .solver_constraints
4131 .push(SolverConstraint::CircleTangentToCircle(circle0, circle1, tangency_side));
4132 }
4133 }
4134
4135 let constraint = crate::front::Constraint::Tangent(Tangent {
4136 input: vec![input0_object_id, input1_object_id],
4137 });
4138 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4139 return Err(KclError::new_semantic(KclErrorDetails::new(
4140 "tangent() can only be used inside a sketch block".to_owned(),
4141 vec![range],
4142 )));
4143 };
4144 sketch_state.sketch_constraints.push(constraint_id);
4145 track_constraint(constraint_id, constraint, exec_state, &args);
4146
4147 Ok(KclValue::none())
4148}
4149
4150#[derive(Debug, Clone, Copy)]
4151struct SymmetricPointVars {
4152 coords: [SketchVarId; 2],
4153 object_id: ObjectId,
4154}
4155
4156#[derive(Debug, Clone, Copy)]
4158struct SymmetricLineVars {
4159 start: [SketchVarId; 2],
4160 end: [SketchVarId; 2],
4161 object_id: ObjectId,
4162}
4163
4164#[derive(Debug, Clone, Copy)]
4165struct SymmetricArcVars {
4166 center: [SketchVarId; 2],
4167 start: [SketchVarId; 2],
4168 end: [SketchVarId; 2],
4169 object_id: ObjectId,
4170}
4171
4172#[derive(Debug, Clone, Copy)]
4173struct SymmetricCircleVars {
4174 center: [SketchVarId; 2],
4175 start: [SketchVarId; 2],
4176 object_id: ObjectId,
4177}
4178
4179#[derive(Debug, Clone, Copy)]
4180enum SymmetricInput {
4181 Point(SymmetricPointVars),
4182 Line(SymmetricLineVars),
4183 Arc(SymmetricArcVars),
4184 Circle(SymmetricCircleVars),
4185}
4186
4187impl SymmetricInput {
4188 fn type_name(self) -> &'static str {
4189 match self {
4190 SymmetricInput::Point(_) => "points",
4191 SymmetricInput::Line(_) => "lines",
4192 SymmetricInput::Arc(_) => "arcs",
4193 SymmetricInput::Circle(_) => "circles",
4194 }
4195 }
4196
4197 fn object_id(self) -> ObjectId {
4198 match self {
4199 SymmetricInput::Point(point) => point.object_id,
4200 SymmetricInput::Line(line) => line.object_id,
4201 SymmetricInput::Arc(arc) => arc.object_id,
4202 SymmetricInput::Circle(circle) => circle.object_id,
4203 }
4204 }
4205}
4206
4207fn extract_symmetric_input(segment_value: &KclValue, range: crate::SourceRange) -> Result<SymmetricInput, KclError> {
4208 let KclValue::Segment { value: segment } = segment_value else {
4209 return Err(KclError::new_semantic(KclErrorDetails::new(
4210 format!(
4211 "symmetric() arguments must be point, line, arc, or circle segments, but found {}",
4212 segment_value.human_friendly_type()
4213 ),
4214 vec![range],
4215 )));
4216 };
4217 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4218 return Err(KclError::new_semantic(KclErrorDetails::new(
4219 "symmetric() arguments must be unsolved segments".to_owned(),
4220 vec![range],
4221 )));
4222 };
4223
4224 match &unsolved.kind {
4225 UnsolvedSegmentKind::Point { position, .. } => {
4226 let (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) = (&position[0], &position[1]) else {
4227 return Err(KclError::new_semantic(KclErrorDetails::new(
4228 "point coordinates must be sketch vars for symmetric()".to_owned(),
4229 vec![range],
4230 )));
4231 };
4232 Ok(SymmetricInput::Point(SymmetricPointVars {
4233 coords: [*x, *y],
4234 object_id: unsolved.object_id,
4235 }))
4236 }
4237 UnsolvedSegmentKind::Line { start, end, .. } => {
4238 let (
4239 UnsolvedExpr::Unknown(start_x),
4240 UnsolvedExpr::Unknown(start_y),
4241 UnsolvedExpr::Unknown(end_x),
4242 UnsolvedExpr::Unknown(end_y),
4243 ) = (&start[0], &start[1], &end[0], &end[1])
4244 else {
4245 return Err(KclError::new_semantic(KclErrorDetails::new(
4246 "line coordinates must be sketch vars for symmetric()".to_owned(),
4247 vec![range],
4248 )));
4249 };
4250 Ok(SymmetricInput::Line(SymmetricLineVars {
4251 start: [*start_x, *start_y],
4252 end: [*end_x, *end_y],
4253 object_id: unsolved.object_id,
4254 }))
4255 }
4256 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
4257 let (
4258 UnsolvedExpr::Unknown(center_x),
4259 UnsolvedExpr::Unknown(center_y),
4260 UnsolvedExpr::Unknown(start_x),
4261 UnsolvedExpr::Unknown(start_y),
4262 UnsolvedExpr::Unknown(end_x),
4263 UnsolvedExpr::Unknown(end_y),
4264 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
4265 else {
4266 return Err(KclError::new_semantic(KclErrorDetails::new(
4267 "arc center/start/end coordinates must be sketch vars for symmetric()".to_owned(),
4268 vec![range],
4269 )));
4270 };
4271 Ok(SymmetricInput::Arc(SymmetricArcVars {
4272 center: [*center_x, *center_y],
4273 start: [*start_x, *start_y],
4274 end: [*end_x, *end_y],
4275 object_id: unsolved.object_id,
4276 }))
4277 }
4278 UnsolvedSegmentKind::Circle { center, start, .. } => {
4279 let (
4280 UnsolvedExpr::Unknown(center_x),
4281 UnsolvedExpr::Unknown(center_y),
4282 UnsolvedExpr::Unknown(start_x),
4283 UnsolvedExpr::Unknown(start_y),
4284 ) = (¢er[0], ¢er[1], &start[0], &start[1])
4285 else {
4286 return Err(KclError::new_semantic(KclErrorDetails::new(
4287 "circle center/start coordinates must be sketch vars for symmetric()".to_owned(),
4288 vec![range],
4289 )));
4290 };
4291 Ok(SymmetricInput::Circle(SymmetricCircleVars {
4292 center: [*center_x, *center_y],
4293 start: [*start_x, *start_y],
4294 object_id: unsolved.object_id,
4295 }))
4296 }
4297 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_semantic(KclErrorDetails::new(
4298 "symmetric() does not yet support control point spline segments".to_owned(),
4299 vec![range],
4300 ))),
4301 }
4302}
4303
4304fn extract_symmetric_axis_line(
4305 segment_value: &KclValue,
4306 range: crate::SourceRange,
4307) -> Result<SymmetricLineVars, KclError> {
4308 let KclValue::Segment { value: segment } = segment_value else {
4309 return Err(KclError::new_semantic(KclErrorDetails::new(
4310 format!(
4311 "symmetric() axis must be a line Segment, but found {}",
4312 segment_value.human_friendly_type()
4313 ),
4314 vec![range],
4315 )));
4316 };
4317 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4318 return Err(KclError::new_semantic(KclErrorDetails::new(
4319 "symmetric() axis must be an unsolved line Segment".to_owned(),
4320 vec![range],
4321 )));
4322 };
4323 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4324 return Err(KclError::new_semantic(KclErrorDetails::new(
4325 "symmetric() axis must be a line Segment".to_owned(),
4326 vec![range],
4327 )));
4328 };
4329 let (
4330 UnsolvedExpr::Unknown(start_x),
4331 UnsolvedExpr::Unknown(start_y),
4332 UnsolvedExpr::Unknown(end_x),
4333 UnsolvedExpr::Unknown(end_y),
4334 ) = (&start[0], &start[1], &end[0], &end[1])
4335 else {
4336 return Err(KclError::new_semantic(KclErrorDetails::new(
4337 "symmetric() axis line coordinates must be sketch vars".to_owned(),
4338 vec![range],
4339 )));
4340 };
4341
4342 Ok(SymmetricLineVars {
4343 start: [*start_x, *start_y],
4344 end: [*end_x, *end_y],
4345 object_id: unsolved.object_id,
4346 })
4347}
4348
4349pub async fn symmetric(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4350 #[derive(Debug, Clone, Copy)]
4351 struct SymmetricCircularVars {
4352 center: [SketchVarId; 2],
4353 start: [SketchVarId; 2],
4354 end: Option<[SketchVarId; 2]>,
4355 }
4356
4357 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
4358 "input",
4359 &RuntimeType::Array(
4360 Box::new(RuntimeType::Primitive(PrimitiveType::Segment)),
4361 ArrayLen::Known(2),
4362 ),
4363 exec_state,
4364 )?;
4365 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
4366 KclError::new_semantic(KclErrorDetails::new(
4367 "symmetric() requires exactly 2 input segments".to_owned(),
4368 vec![args.source_range],
4369 ))
4370 })?;
4371 let axis: KclValue = args.get_kw_arg("axis", &RuntimeType::Primitive(PrimitiveType::Segment), exec_state)?;
4372 let range = args.source_range;
4373
4374 let input0 = extract_symmetric_input(&item0, range)?;
4375 let input1 = extract_symmetric_input(&item1, range)?;
4376 let axis_line = extract_symmetric_axis_line(&axis, range)?;
4377
4378 let solver_axis = DatumLineSegment::new(datum_point(axis_line.start, range)?, datum_point(axis_line.end, range)?);
4379
4380 let (mut solver_constraints, circular_inputs) = match (input0, input1) {
4381 (SymmetricInput::Point(point0), SymmetricInput::Point(point1)) => (
4382 vec![SolverConstraint::Symmetric(
4383 solver_axis,
4384 datum_point(point0.coords, range)?,
4385 datum_point(point1.coords, range)?,
4386 )],
4387 None,
4388 ),
4389 (SymmetricInput::Line(line0), SymmetricInput::Line(line1)) => {
4390 let sketch_vars = {
4391 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4392 return Err(KclError::new_semantic(KclErrorDetails::new(
4393 "symmetric() can only be used inside a sketch block".to_owned(),
4394 vec![range],
4395 )));
4396 };
4397 sketch_state.sketch_vars.clone()
4398 };
4399 let mirrored_start = symmetric_hidden_point_guess(&sketch_vars, line0.start, axis_line, exec_state, range)?;
4400 let mirrored_end = symmetric_hidden_point_guess(&sketch_vars, line0.end, axis_line, exec_state, range)?;
4401 let hidden_start = create_hidden_point(exec_state, mirrored_start, range)?;
4402 let hidden_end = create_hidden_point(exec_state, mirrored_end, range)?;
4403 let mirrored_support_line =
4404 DatumLineSegment::new(datum_point(hidden_start, range)?, datum_point(hidden_end, range)?);
4405 let solver_line1 = DatumLineSegment::new(datum_point(line1.start, range)?, datum_point(line1.end, range)?);
4406
4407 (
4408 vec![
4409 SolverConstraint::Symmetric(
4410 solver_axis,
4411 datum_point(line0.start, range)?,
4412 datum_point(hidden_start, range)?,
4413 ),
4414 SolverConstraint::Symmetric(
4415 solver_axis,
4416 datum_point(line0.end, range)?,
4417 datum_point(hidden_end, range)?,
4418 ),
4419 SolverConstraint::LinesAtAngle(mirrored_support_line, solver_line1, AngleKind::Parallel),
4420 SolverConstraint::PointLineDistance(datum_point(line1.start, range)?, mirrored_support_line, 0.0),
4423 ],
4424 None,
4425 )
4426 }
4427 (SymmetricInput::Arc(arc0), SymmetricInput::Arc(arc1)) => (
4428 vec![SolverConstraint::Symmetric(
4429 solver_axis,
4430 datum_point(arc0.center, range)?,
4431 datum_point(arc1.center, range)?,
4432 )],
4433 Some([
4434 SymmetricCircularVars {
4435 center: arc0.center,
4436 start: arc0.start,
4437 end: Some(arc0.end),
4438 },
4439 SymmetricCircularVars {
4440 center: arc1.center,
4441 start: arc1.start,
4442 end: Some(arc1.end),
4443 },
4444 ]),
4445 ),
4446 (SymmetricInput::Circle(circle0), SymmetricInput::Circle(circle1)) => (
4447 vec![SolverConstraint::Symmetric(
4448 solver_axis,
4449 datum_point(circle0.center, range)?,
4450 datum_point(circle1.center, range)?,
4451 )],
4452 Some([
4453 SymmetricCircularVars {
4454 center: circle0.center,
4455 start: circle0.start,
4456 end: None,
4457 },
4458 SymmetricCircularVars {
4459 center: circle1.center,
4460 start: circle1.start,
4461 end: None,
4462 },
4463 ]),
4464 ),
4465 _ => {
4466 return Err(KclError::new_semantic(KclErrorDetails::new(
4467 format!(
4468 "symmetric() inputs must be homogeneous. You provided {} and {}",
4469 input0.type_name(),
4470 input1.type_name()
4471 ),
4472 vec![range],
4473 )));
4474 }
4475 };
4476
4477 if let Some([circular0, circular1]) = circular_inputs {
4478 let sketch_var_ty = solver_numeric_type(exec_state);
4479 let sketch_vars = {
4480 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4481 return Err(KclError::new_semantic(KclErrorDetails::new(
4482 "symmetric() can only be used inside a sketch block".to_owned(),
4483 vec![range],
4484 )));
4485 };
4486 sketch_state.sketch_vars.clone()
4487 };
4488 let radius_initial_value = radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
4489
4490 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4491 return Err(KclError::new_semantic(KclErrorDetails::new(
4492 "symmetric() can only be used inside a sketch block".to_owned(),
4493 vec![range],
4494 )));
4495 };
4496 let radius_id = sketch_state.next_sketch_var_id();
4497 sketch_state.sketch_vars.push(KclValue::SketchVar {
4498 value: Box::new(crate::execution::SketchVar {
4499 id: radius_id,
4500 initial_value: radius_initial_value,
4501 ty: sketch_var_ty,
4502 node_path: None,
4504 meta: vec![],
4505 }),
4506 });
4507 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
4508
4509 for circular in [circular0, circular1] {
4510 let center = datum_point(circular.center, range)?;
4511 let start = datum_point(circular.start, range)?;
4512 solver_constraints.push(SolverConstraint::DistanceVar(start, center, radius));
4513 if let Some(end) = circular.end {
4514 let end = datum_point(end, range)?;
4515 solver_constraints.push(SolverConstraint::DistanceVar(end, center, radius));
4516 }
4517 }
4518 }
4519
4520 let constraint_id = exec_state.next_object_id();
4521 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4522 return Err(KclError::new_semantic(KclErrorDetails::new(
4523 "symmetric() can only be used inside a sketch block".to_owned(),
4524 vec![range],
4525 )));
4526 };
4527 sketch_state.solver_constraints.extend(solver_constraints);
4528
4529 let constraint = crate::front::Constraint::Symmetric(Symmetric {
4530 input: vec![input0.object_id(), input1.object_id()],
4531 axis: axis_line.object_id,
4532 });
4533 sketch_state.sketch_constraints.push(constraint_id);
4534 track_constraint(constraint_id, constraint, exec_state, &args);
4535
4536 Ok(KclValue::none())
4537}
4538
4539#[derive(Debug, Clone, Copy)]
4540pub(crate) enum LinesAtAngleKind {
4541 Parallel,
4542 Perpendicular,
4543}
4544
4545impl LinesAtAngleKind {
4546 pub fn to_function_name(self) -> &'static str {
4547 match self {
4548 LinesAtAngleKind::Parallel => "parallel",
4549 LinesAtAngleKind::Perpendicular => "perpendicular",
4550 }
4551 }
4552
4553 fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
4554 match self {
4555 LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
4556 LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
4557 }
4558 }
4559
4560 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
4561 match self {
4562 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
4563 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
4564 }
4565 }
4566}
4567
4568#[expect(unused)]
4570fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
4571 kcmc::shared::Angle::from_degrees(angle.to_degrees())
4572}
4573
4574#[expect(unused)]
4576fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
4577 ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
4578}
4579
4580pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4581 #[derive(Clone, Copy)]
4582 struct ConstrainableLine {
4583 solver_line: DatumLineSegment,
4584 object_id: ObjectId,
4585 }
4586
4587 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
4588 "lines",
4589 &RuntimeType::Array(
4590 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
4591 ArrayLen::Minimum(2),
4592 ),
4593 exec_state,
4594 )?;
4595 let range = args.source_range;
4596 let constrainable_lines: Vec<ConstrainableLine> = lines
4597 .iter()
4598 .map(|line| {
4599 let KclValue::Segment { value: segment } = line else {
4600 return Err(KclError::new_semantic(KclErrorDetails::new(
4601 "line argument must be a Segment".to_owned(),
4602 vec![args.source_range],
4603 )));
4604 };
4605 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4606 return Err(KclError::new_internal(KclErrorDetails::new(
4607 "line must be an unsolved Segment".to_owned(),
4608 vec![args.source_range],
4609 )));
4610 };
4611 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4612 return Err(KclError::new_semantic(KclErrorDetails::new(
4613 "line argument must be a line, no other type of Segment".to_owned(),
4614 vec![args.source_range],
4615 )));
4616 };
4617 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
4618 return Err(KclError::new_semantic(KclErrorDetails::new(
4619 "line's start x coordinate must be a var".to_owned(),
4620 vec![args.source_range],
4621 )));
4622 };
4623 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
4624 return Err(KclError::new_semantic(KclErrorDetails::new(
4625 "line's start y coordinate must be a var".to_owned(),
4626 vec![args.source_range],
4627 )));
4628 };
4629 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
4630 return Err(KclError::new_semantic(KclErrorDetails::new(
4631 "line's end x coordinate must be a var".to_owned(),
4632 vec![args.source_range],
4633 )));
4634 };
4635 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
4636 return Err(KclError::new_semantic(KclErrorDetails::new(
4637 "line's end y coordinate must be a var".to_owned(),
4638 vec![args.source_range],
4639 )));
4640 };
4641
4642 let solver_line_p0 =
4643 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
4644 let solver_line_p1 =
4645 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
4646
4647 Ok(ConstrainableLine {
4648 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
4649 object_id: unsolved.object_id,
4650 })
4651 })
4652 .collect::<Result<_, _>>()?;
4653
4654 let constraint_id = exec_state.next_object_id();
4655 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4656 return Err(KclError::new_semantic(KclErrorDetails::new(
4657 "parallel() can only be used inside a sketch block".to_owned(),
4658 vec![args.source_range],
4659 )));
4660 };
4661
4662 let n = constrainable_lines.len();
4663 let mut constrainable_lines_iter = constrainable_lines.iter();
4664 let first_line = constrainable_lines_iter
4665 .next()
4666 .ok_or(KclError::new_semantic(KclErrorDetails::new(
4667 format!("parallel() requires at least 2 lines, but you provided {}", n),
4668 vec![args.source_range],
4669 )))?;
4670 for line in constrainable_lines_iter {
4671 sketch_state.solver_constraints.push(SolverConstraint::LinesAtAngle(
4672 first_line.solver_line,
4673 line.solver_line,
4674 AngleKind::Parallel,
4675 ));
4676 }
4677 let constraint = Constraint::Parallel(Parallel {
4678 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
4679 });
4680 sketch_state.sketch_constraints.push(constraint_id);
4681 track_constraint(constraint_id, constraint, exec_state, &args);
4682 Ok(KclValue::none())
4683}
4684
4685pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
4686 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
4687}
4688
4689#[derive(Debug, Clone, Copy)]
4691enum AxisConstraintKind {
4692 Horizontal,
4693 Vertical,
4694}
4695
4696impl AxisConstraintKind {
4697 fn function_name(self) -> &'static str {
4699 match self {
4700 AxisConstraintKind::Horizontal => "horizontal",
4701 AxisConstraintKind::Vertical => "vertical",
4702 }
4703 }
4704
4705 fn line_constraint(self, line: DatumLineSegment) -> SolverConstraint {
4707 match self {
4708 AxisConstraintKind::Horizontal => SolverConstraint::Horizontal(line),
4709 AxisConstraintKind::Vertical => SolverConstraint::Vertical(line),
4710 }
4711 }
4712
4713 fn point_pair_constraint(self, p0: DatumPoint, p1: DatumPoint) -> SolverConstraint {
4715 match self {
4716 AxisConstraintKind::Horizontal => SolverConstraint::VerticalDistance(p1, p0, 0.0),
4718 AxisConstraintKind::Vertical => SolverConstraint::HorizontalDistance(p1, p0, 0.0),
4720 }
4721 }
4722
4723 fn constraint_aligning_point_to_constant(self, p0: DatumPoint, fixed_point: (f64, f64)) -> SolverConstraint {
4725 match self {
4726 AxisConstraintKind::Horizontal => SolverConstraint::Fixed(p0.y_id, fixed_point.1),
4727 AxisConstraintKind::Vertical => SolverConstraint::Fixed(p0.x_id, fixed_point.0),
4728 }
4729 }
4730
4731 fn line_artifact_constraint(self, line: ObjectId) -> Constraint {
4732 match self {
4733 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Line { line }),
4734 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Line { line }),
4735 }
4736 }
4737
4738 fn point_artifact_constraint(self, points: Vec<ConstraintSegment>) -> Constraint {
4739 match self {
4740 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Points { points }),
4741 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Points { points }),
4742 }
4743 }
4744}
4745
4746#[derive(Debug, Clone, Copy)]
4749struct AxisLineVars {
4750 start: [SketchVarId; 2],
4751 end: [SketchVarId; 2],
4752 object_id: ObjectId,
4753}
4754
4755fn extract_axis_line_vars(
4756 segment: &AbstractSegment,
4757 kind: AxisConstraintKind,
4758 source_range: crate::SourceRange,
4759) -> Result<AxisLineVars, KclError> {
4760 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4761 return Err(KclError::new_internal(KclErrorDetails::new(
4762 "line must be an unsolved Segment".to_owned(),
4763 vec![source_range],
4764 )));
4765 };
4766 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
4767 return Err(KclError::new_semantic(KclErrorDetails::new(
4768 format!(
4769 "{}() line argument must be a line, no other type of Segment",
4770 kind.function_name()
4771 ),
4772 vec![source_range],
4773 )));
4774 };
4775 let (
4776 UnsolvedExpr::Unknown(start_x),
4777 UnsolvedExpr::Unknown(start_y),
4778 UnsolvedExpr::Unknown(end_x),
4779 UnsolvedExpr::Unknown(end_y),
4780 ) = (&start[0], &start[1], &end[0], &end[1])
4781 else {
4782 return Err(KclError::new_semantic(KclErrorDetails::new(
4783 "line's x and y coordinates of both start and end must be vars".to_owned(),
4784 vec![source_range],
4785 )));
4786 };
4787
4788 Ok(AxisLineVars {
4789 start: [*start_x, *start_y],
4790 end: [*end_x, *end_y],
4791 object_id: unsolved.object_id,
4792 })
4793}
4794
4795#[derive(Debug, Clone)]
4796enum PointToAlign {
4797 Variable { x: SketchVarId, y: SketchVarId },
4799 Fixed { x: TyF64, y: TyF64 },
4801}
4802
4803impl From<[SketchVarId; 2]> for PointToAlign {
4804 fn from(sketch_var: [SketchVarId; 2]) -> Self {
4805 Self::Variable {
4806 x: sketch_var[0],
4807 y: sketch_var[1],
4808 }
4809 }
4810}
4811
4812impl From<[TyF64; 2]> for PointToAlign {
4813 fn from([x, y]: [TyF64; 2]) -> Self {
4814 Self::Fixed { x, y }
4815 }
4816}
4817
4818fn extract_axis_point_vars(
4819 input: &KclValue,
4820 kind: AxisConstraintKind,
4821 source_range: crate::SourceRange,
4822) -> Result<PointToAlign, KclError> {
4823 match input {
4824 KclValue::Segment { value: segment } => {
4825 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
4826 return Err(KclError::new_semantic(KclErrorDetails::new(
4827 format!(
4828 "The `{}` function point arguments must be unsolved points",
4829 kind.function_name()
4830 ),
4831 vec![source_range],
4832 )));
4833 };
4834 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
4835 return Err(KclError::new_semantic(KclErrorDetails::new(
4836 format!(
4837 "The `{}` function list arguments must be points, but one item is {}",
4838 kind.function_name(),
4839 unsolved.kind.human_friendly_kind_with_article()
4840 ),
4841 vec![source_range],
4842 )));
4843 };
4844 match (&position[0], &position[1]) {
4845 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed {
4846 x: x.to_owned(),
4847 y: y.to_owned(),
4848 }),
4849 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x: *x, y: *y }),
4850 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
4851 Err(KclError::new_semantic(KclErrorDetails::new(
4852 format!(
4853 "The `{}` function cannot take a fixed X component and a variable Y component",
4854 kind.function_name()
4855 ),
4856 vec![source_range],
4857 )))
4858 }
4859 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
4860 Err(KclError::new_semantic(KclErrorDetails::new(
4861 format!(
4862 "The `{}` function cannot take a fixed X component and a variable Y component",
4863 kind.function_name()
4864 ),
4865 vec![source_range],
4866 )))
4867 }
4868 }
4869 }
4870 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
4871 let [x_value, y_value] = value.as_slice() else {
4872 return Err(KclError::new_semantic(KclErrorDetails::new(
4873 format!(
4874 "The `{}` function point arguments must each be a Point2d like [var 0mm, var 0mm]",
4875 kind.function_name()
4876 ),
4877 vec![source_range],
4878 )));
4879 };
4880 let Some(x_expr) = x_value.as_unsolved_expr() else {
4881 return Err(KclError::new_semantic(KclErrorDetails::new(
4882 format!(
4883 "The `{}` function point x coordinate must be a number or sketch var",
4884 kind.function_name()
4885 ),
4886 vec![source_range],
4887 )));
4888 };
4889 let Some(y_expr) = y_value.as_unsolved_expr() else {
4890 return Err(KclError::new_semantic(KclErrorDetails::new(
4891 format!(
4892 "The `{}` function point y coordinate must be a number or sketch var",
4893 kind.function_name()
4894 ),
4895 vec![source_range],
4896 )));
4897 };
4898 match (x_expr, y_expr) {
4899 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed { x, y }),
4900 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x, y }),
4901 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
4902 Err(KclError::new_semantic(KclErrorDetails::new(
4903 format!(
4904 "The `{}` function cannot take a fixed X component and a variable Y component",
4905 kind.function_name()
4906 ),
4907 vec![source_range],
4908 )))
4909 }
4910 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
4911 Err(KclError::new_semantic(KclErrorDetails::new(
4912 format!(
4913 "The `{}` function cannot take a fixed X component and a variable Y component",
4914 kind.function_name()
4915 ),
4916 vec![source_range],
4917 )))
4918 }
4919 }
4920 }
4921 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4922 format!(
4923 "The `{}` function accepts either a line Segment or a list of points",
4924 kind.function_name()
4925 ),
4926 vec![source_range],
4927 ))),
4928 }
4929}
4930
4931async fn axis_constraint(
4932 kind: AxisConstraintKind,
4933 exec_state: &mut ExecState,
4934 args: Args,
4935) -> Result<KclValue, KclError> {
4936 let input: KclValue =
4937 args.get_unlabeled_kw_arg("input", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
4938
4939 match input {
4941 KclValue::Segment { value } => {
4942 axis_constraint_line(value, kind, exec_state, args)
4944 }
4945 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
4946 axis_constraint_points(value, kind, exec_state, args)
4948 }
4949 other => Err(KclError::new_semantic(KclErrorDetails::new(
4950 format!(
4951 "{}() accepts either a line Segment or a list of at least two points, but you provided {}",
4952 kind.function_name(),
4953 other.human_friendly_type(),
4954 ),
4955 vec![args.source_range],
4956 ))),
4957 }
4958}
4959
4960fn axis_constraint_line(
4962 segment: Box<AbstractSegment>,
4963 kind: AxisConstraintKind,
4964 exec_state: &mut ExecState,
4965 args: Args,
4966) -> Result<KclValue, KclError> {
4967 let line = extract_axis_line_vars(&segment, kind, args.source_range)?;
4968 let range = args.source_range;
4969 let solver_p0 = DatumPoint::new_xy(
4970 line.start[0].to_constraint_id(range)?,
4971 line.start[1].to_constraint_id(range)?,
4972 );
4973 let solver_p1 = DatumPoint::new_xy(
4974 line.end[0].to_constraint_id(range)?,
4975 line.end[1].to_constraint_id(range)?,
4976 );
4977 let solver_line = DatumLineSegment::new(solver_p0, solver_p1);
4978 let constraint = kind.line_constraint(solver_line);
4979 let constraint_id = exec_state.next_object_id();
4980 let Some(sketch_state) = exec_state.sketch_block_mut() else {
4981 return Err(KclError::new_semantic(KclErrorDetails::new(
4982 format!("{}() can only be used inside a sketch block", kind.function_name()),
4983 vec![args.source_range],
4984 )));
4985 };
4986 sketch_state.solver_constraints.push(constraint);
4987 let constraint = kind.line_artifact_constraint(line.object_id);
4988 sketch_state.sketch_constraints.push(constraint_id);
4989 track_constraint(constraint_id, constraint, exec_state, &args);
4990 Ok(KclValue::none())
4991}
4992
4993fn axis_constraint_points(
4995 point_values: Vec<KclValue>,
4996 kind: AxisConstraintKind,
4997 exec_state: &mut ExecState,
4998 args: Args,
4999) -> Result<KclValue, KclError> {
5000 if point_values.len() < 2 {
5001 return Err(KclError::new_semantic(KclErrorDetails::new(
5002 format!("{}() point list must contain at least two points", kind.function_name()),
5003 vec![args.source_range],
5004 )));
5005 }
5006
5007 let trackable_point_ids = point_values
5008 .iter()
5009 .map(|point| match point {
5010 KclValue::Segment { value: segment } => {
5011 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
5012 return None;
5013 };
5014 let UnsolvedSegmentKind::Point { .. } = &unsolved.kind else {
5015 return None;
5016 };
5017 Some(ConstraintSegment::from(unsolved.object_id))
5018 }
5019 point if point2d_is_origin(point) => Some(ConstraintSegment::ORIGIN),
5020 _ => None,
5021 })
5022 .collect::<Option<Vec<_>>>();
5023
5024 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5025 return Err(KclError::new_semantic(KclErrorDetails::new(
5026 format!("{}() can only be used inside a sketch block", kind.function_name()),
5027 vec![args.source_range],
5028 )));
5029 };
5030
5031 let points: Vec<PointToAlign> = point_values
5032 .iter()
5033 .map(|point| extract_axis_point_vars(point, kind, args.source_range))
5034 .collect::<Result<_, _>>()?;
5035
5036 let mut solver_constraints = Vec::with_capacity(points.len().saturating_sub(1));
5037
5038 let mut var_points = Vec::new();
5039 let mut fix_points = Vec::new();
5040 for point in points {
5041 match point {
5042 PointToAlign::Variable { x, y } => var_points.push((x, y)),
5043 PointToAlign::Fixed { x, y } => fix_points.push((x, y)),
5044 }
5045 }
5046 if fix_points.len() > 1 {
5047 return Err(KclError::new_semantic(KclErrorDetails::new(
5048 format!(
5049 "{}() point list can contain at most 1 fixed point, but you provided {}",
5050 kind.function_name(),
5051 fix_points.len()
5052 ),
5053 vec![args.source_range],
5054 )));
5055 }
5056
5057 if let Some(fix_point) = fix_points.pop() {
5058 for point in var_points {
5066 let solver_point = datum_point([point.0, point.1], args.source_range)?;
5067 let fix_point_mm = (fix_point.0.to_mm(), fix_point.1.to_mm());
5068 solver_constraints.push(kind.constraint_aligning_point_to_constant(solver_point, fix_point_mm));
5069 }
5070 } else {
5071 let mut points = var_points.into_iter();
5078 let first_point = points.next().ok_or_else(|| {
5079 KclError::new_semantic(KclErrorDetails::new(
5080 format!("{}() point list must contain at least two points", kind.function_name()),
5081 vec![args.source_range],
5082 ))
5083 })?;
5084 let anchor = datum_point([first_point.0, first_point.1], args.source_range)?;
5085 for point in points {
5086 let solver_point = datum_point([point.0, point.1], args.source_range)?;
5087 solver_constraints.push(kind.point_pair_constraint(anchor, solver_point));
5088 }
5089 }
5090 sketch_state.solver_constraints.extend(solver_constraints);
5091
5092 if let Some(point_ids) = trackable_point_ids {
5093 let constraint_id = exec_state.next_object_id();
5094 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5095 debug_assert!(false, "Constraint created outside a sketch block");
5096 return Ok(KclValue::none());
5097 };
5098 sketch_state.sketch_constraints.push(constraint_id);
5099 let constraint = kind.point_artifact_constraint(point_ids);
5100 track_constraint(constraint_id, constraint, exec_state, &args);
5101 }
5102
5103 Ok(KclValue::none())
5104}
5105
5106pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5107 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
5108 "lines",
5109 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
5110 exec_state,
5111 )?;
5112 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
5113 KclError::new_semantic(KclErrorDetails::new(
5114 "must have two input lines".to_owned(),
5115 vec![args.source_range],
5116 ))
5117 })?;
5118 let KclValue::Segment { value: segment0 } = &line0 else {
5119 return Err(KclError::new_semantic(KclErrorDetails::new(
5120 "line argument must be a Segment".to_owned(),
5121 vec![args.source_range],
5122 )));
5123 };
5124 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
5125 return Err(KclError::new_internal(KclErrorDetails::new(
5126 "line must be an unsolved Segment".to_owned(),
5127 vec![args.source_range],
5128 )));
5129 };
5130 let UnsolvedSegmentKind::Line {
5131 start: start0,
5132 end: end0,
5133 ..
5134 } = &unsolved0.kind
5135 else {
5136 return Err(KclError::new_semantic(KclErrorDetails::new(
5137 "line argument must be a line, no other type of Segment".to_owned(),
5138 vec![args.source_range],
5139 )));
5140 };
5141 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
5142 return Err(KclError::new_semantic(KclErrorDetails::new(
5143 "line's start x coordinate must be a var".to_owned(),
5144 vec![args.source_range],
5145 )));
5146 };
5147 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
5148 return Err(KclError::new_semantic(KclErrorDetails::new(
5149 "line's start y coordinate must be a var".to_owned(),
5150 vec![args.source_range],
5151 )));
5152 };
5153 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
5154 return Err(KclError::new_semantic(KclErrorDetails::new(
5155 "line's end x coordinate must be a var".to_owned(),
5156 vec![args.source_range],
5157 )));
5158 };
5159 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
5160 return Err(KclError::new_semantic(KclErrorDetails::new(
5161 "line's end y coordinate must be a var".to_owned(),
5162 vec![args.source_range],
5163 )));
5164 };
5165 let KclValue::Segment { value: segment1 } = &line1 else {
5166 return Err(KclError::new_semantic(KclErrorDetails::new(
5167 "line argument must be a Segment".to_owned(),
5168 vec![args.source_range],
5169 )));
5170 };
5171 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
5172 return Err(KclError::new_internal(KclErrorDetails::new(
5173 "line must be an unsolved Segment".to_owned(),
5174 vec![args.source_range],
5175 )));
5176 };
5177 let UnsolvedSegmentKind::Line {
5178 start: start1,
5179 end: end1,
5180 ..
5181 } = &unsolved1.kind
5182 else {
5183 return Err(KclError::new_semantic(KclErrorDetails::new(
5184 "line argument must be a line, no other type of Segment".to_owned(),
5185 vec![args.source_range],
5186 )));
5187 };
5188 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
5189 return Err(KclError::new_semantic(KclErrorDetails::new(
5190 "line's start x coordinate must be a var".to_owned(),
5191 vec![args.source_range],
5192 )));
5193 };
5194 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
5195 return Err(KclError::new_semantic(KclErrorDetails::new(
5196 "line's start y coordinate must be a var".to_owned(),
5197 vec![args.source_range],
5198 )));
5199 };
5200 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
5201 return Err(KclError::new_semantic(KclErrorDetails::new(
5202 "line's end x coordinate must be a var".to_owned(),
5203 vec![args.source_range],
5204 )));
5205 };
5206 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
5207 return Err(KclError::new_semantic(KclErrorDetails::new(
5208 "line's end y coordinate must be a var".to_owned(),
5209 vec![args.source_range],
5210 )));
5211 };
5212
5213 let sketch_constraint = SketchConstraint {
5215 kind: SketchConstraintKind::Angle {
5216 line0: crate::execution::ConstrainableLine2d {
5217 object_id: unsolved0.object_id,
5218 vars: [
5219 crate::front::Point2d {
5220 x: *line0_p0_x,
5221 y: *line0_p0_y,
5222 },
5223 crate::front::Point2d {
5224 x: *line0_p1_x,
5225 y: *line0_p1_y,
5226 },
5227 ],
5228 },
5229 line1: crate::execution::ConstrainableLine2d {
5230 object_id: unsolved1.object_id,
5231 vars: [
5232 crate::front::Point2d {
5233 x: *line1_p0_x,
5234 y: *line1_p0_y,
5235 },
5236 crate::front::Point2d {
5237 x: *line1_p1_x,
5238 y: *line1_p1_y,
5239 },
5240 ],
5241 },
5242 },
5243 meta: vec![args.source_range.into()],
5244 };
5245 Ok(KclValue::SketchConstraint {
5246 value: Box::new(sketch_constraint),
5247 })
5248}
5249
5250async fn lines_at_angle(
5251 angle_kind: LinesAtAngleKind,
5252 exec_state: &mut ExecState,
5253 args: Args,
5254) -> Result<KclValue, KclError> {
5255 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
5256 "lines",
5257 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
5258 exec_state,
5259 )?;
5260 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
5261 KclError::new_semantic(KclErrorDetails::new(
5262 "must have two input lines".to_owned(),
5263 vec![args.source_range],
5264 ))
5265 })?;
5266
5267 let KclValue::Segment { value: segment0 } = &line0 else {
5268 return Err(KclError::new_semantic(KclErrorDetails::new(
5269 "line argument must be a Segment".to_owned(),
5270 vec![args.source_range],
5271 )));
5272 };
5273 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
5274 return Err(KclError::new_internal(KclErrorDetails::new(
5275 "line must be an unsolved Segment".to_owned(),
5276 vec![args.source_range],
5277 )));
5278 };
5279 let UnsolvedSegmentKind::Line {
5280 start: start0,
5281 end: end0,
5282 ..
5283 } = &unsolved0.kind
5284 else {
5285 return Err(KclError::new_semantic(KclErrorDetails::new(
5286 "line argument must be a line, no other type of Segment".to_owned(),
5287 vec![args.source_range],
5288 )));
5289 };
5290 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
5291 return Err(KclError::new_semantic(KclErrorDetails::new(
5292 "line's start x coordinate must be a var".to_owned(),
5293 vec![args.source_range],
5294 )));
5295 };
5296 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
5297 return Err(KclError::new_semantic(KclErrorDetails::new(
5298 "line's start y coordinate must be a var".to_owned(),
5299 vec![args.source_range],
5300 )));
5301 };
5302 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
5303 return Err(KclError::new_semantic(KclErrorDetails::new(
5304 "line's end x coordinate must be a var".to_owned(),
5305 vec![args.source_range],
5306 )));
5307 };
5308 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
5309 return Err(KclError::new_semantic(KclErrorDetails::new(
5310 "line's end y coordinate must be a var".to_owned(),
5311 vec![args.source_range],
5312 )));
5313 };
5314 let KclValue::Segment { value: segment1 } = &line1 else {
5315 return Err(KclError::new_semantic(KclErrorDetails::new(
5316 "line argument must be a Segment".to_owned(),
5317 vec![args.source_range],
5318 )));
5319 };
5320 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
5321 return Err(KclError::new_internal(KclErrorDetails::new(
5322 "line must be an unsolved Segment".to_owned(),
5323 vec![args.source_range],
5324 )));
5325 };
5326 let UnsolvedSegmentKind::Line {
5327 start: start1,
5328 end: end1,
5329 ..
5330 } = &unsolved1.kind
5331 else {
5332 return Err(KclError::new_semantic(KclErrorDetails::new(
5333 "line argument must be a line, no other type of Segment".to_owned(),
5334 vec![args.source_range],
5335 )));
5336 };
5337 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
5338 return Err(KclError::new_semantic(KclErrorDetails::new(
5339 "line's start x coordinate must be a var".to_owned(),
5340 vec![args.source_range],
5341 )));
5342 };
5343 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
5344 return Err(KclError::new_semantic(KclErrorDetails::new(
5345 "line's start y coordinate must be a var".to_owned(),
5346 vec![args.source_range],
5347 )));
5348 };
5349 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
5350 return Err(KclError::new_semantic(KclErrorDetails::new(
5351 "line's end x coordinate must be a var".to_owned(),
5352 vec![args.source_range],
5353 )));
5354 };
5355 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
5356 return Err(KclError::new_semantic(KclErrorDetails::new(
5357 "line's end y coordinate must be a var".to_owned(),
5358 vec![args.source_range],
5359 )));
5360 };
5361
5362 let range = args.source_range;
5363 let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5364 line0_p0_x.to_constraint_id(range)?,
5365 line0_p0_y.to_constraint_id(range)?,
5366 );
5367 let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5368 line0_p1_x.to_constraint_id(range)?,
5369 line0_p1_y.to_constraint_id(range)?,
5370 );
5371 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
5372 let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5373 line1_p0_x.to_constraint_id(range)?,
5374 line1_p0_y.to_constraint_id(range)?,
5375 );
5376 let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5377 line1_p1_x.to_constraint_id(range)?,
5378 line1_p1_y.to_constraint_id(range)?,
5379 );
5380 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
5381 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
5382 let constraint_id = exec_state.next_object_id();
5383 let Some(sketch_state) = exec_state.sketch_block_mut() else {
5385 return Err(KclError::new_semantic(KclErrorDetails::new(
5386 format!(
5387 "{}() can only be used inside a sketch block",
5388 angle_kind.to_function_name()
5389 ),
5390 vec![args.source_range],
5391 )));
5392 };
5393 sketch_state.solver_constraints.push(constraint);
5394 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
5395 sketch_state.sketch_constraints.push(constraint_id);
5396 track_constraint(constraint_id, constraint, exec_state, &args);
5397 Ok(KclValue::none())
5398}
5399
5400pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5401 axis_constraint(AxisConstraintKind::Horizontal, exec_state, args).await
5402}
5403
5404pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
5405 axis_constraint(AxisConstraintKind::Vertical, exec_state, args).await
5406}