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