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