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