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