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