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