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