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