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::ExecState;
20use crate::execution::KclValue;
21use crate::execution::SegmentRepr;
22#[cfg(feature = "artifact-graph")]
23use crate::execution::SketchBlockConstraint;
24#[cfg(feature = "artifact-graph")]
25use crate::execution::SketchBlockConstraintType;
26use crate::execution::SketchConstraint;
27use crate::execution::SketchConstraintKind;
28use crate::execution::SketchVarId;
29use crate::execution::UnsolvedExpr;
30use crate::execution::UnsolvedSegment;
31use crate::execution::UnsolvedSegmentKind;
32use crate::execution::normalize_to_solver_distance_unit;
33use crate::execution::solver_numeric_type;
34use crate::execution::types::ArrayLen;
35use crate::execution::types::PrimitiveType;
36use crate::execution::types::RuntimeType;
37use crate::front::ArcCtor;
38use crate::front::CircleCtor;
39#[cfg(feature = "artifact-graph")]
40use crate::front::Coincident;
41#[cfg(feature = "artifact-graph")]
42use crate::front::Constraint;
43#[cfg(feature = "artifact-graph")]
44use crate::front::Horizontal;
45use crate::front::LineCtor;
46#[cfg(feature = "artifact-graph")]
47use crate::front::LinesEqualLength;
48#[cfg(feature = "artifact-graph")]
49use crate::front::Object;
50use crate::front::ObjectId;
51#[cfg(feature = "artifact-graph")]
52use crate::front::ObjectKind;
53#[cfg(feature = "artifact-graph")]
54use crate::front::Parallel;
55#[cfg(feature = "artifact-graph")]
56use crate::front::Perpendicular;
57use crate::front::Point2d;
58use crate::front::PointCtor;
59#[cfg(feature = "artifact-graph")]
60use crate::front::Tangent;
61#[cfg(feature = "artifact-graph")]
62use crate::front::Vertical;
63use crate::std::Args;
64use crate::std::args::FromKclValue;
65use crate::std::args::TyF64;
66
67pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
68 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
69 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
70 KclError::new_semantic(KclErrorDetails::new(
71 "at must be a 2D point".to_owned(),
72 vec![args.source_range],
73 ))
74 })?;
75 let Some(at_x) = at_x_value.as_unsolved_expr() else {
76 return Err(KclError::new_semantic(KclErrorDetails::new(
77 "at x must be a number or sketch var".to_owned(),
78 vec![args.source_range],
79 )));
80 };
81 let Some(at_y) = at_y_value.as_unsolved_expr() else {
82 return Err(KclError::new_semantic(KclErrorDetails::new(
83 "at y must be a number or sketch var".to_owned(),
84 vec![args.source_range],
85 )));
86 };
87 let ctor = PointCtor {
88 position: Point2d {
89 x: at_x_value.to_sketch_expr().ok_or_else(|| {
90 KclError::new_semantic(KclErrorDetails::new(
91 "unable to convert numeric type to suffix".to_owned(),
92 vec![args.source_range],
93 ))
94 })?,
95 y: at_y_value.to_sketch_expr().ok_or_else(|| {
96 KclError::new_semantic(KclErrorDetails::new(
97 "unable to convert numeric type to suffix".to_owned(),
98 vec![args.source_range],
99 ))
100 })?,
101 },
102 };
103 let segment = UnsolvedSegment {
104 id: exec_state.next_uuid(),
105 object_id: exec_state.next_object_id(),
106 kind: UnsolvedSegmentKind::Point {
107 position: [at_x, at_y],
108 ctor: Box::new(ctor),
109 },
110 tag: None,
111 meta: vec![args.source_range.into()],
112 };
113 #[cfg(feature = "artifact-graph")]
114 let optional_constraints = {
115 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
116
117 let mut optional_constraints = Vec::new();
118 if exec_state.segment_ids_edited_contains(&object_id) {
119 if let Some(at_x_var) = at_x_value.as_sketch_var() {
120 let x_initial_value = at_x_var.initial_value_to_solver_units(
121 exec_state,
122 args.source_range,
123 "edited segment fixed constraint value",
124 )?;
125 optional_constraints.push(SolverConstraint::Fixed(
126 at_x_var.id.to_constraint_id(args.source_range)?,
127 x_initial_value.n,
128 ));
129 }
130 if let Some(at_y_var) = at_y_value.as_sketch_var() {
131 let y_initial_value = at_y_var.initial_value_to_solver_units(
132 exec_state,
133 args.source_range,
134 "edited segment fixed constraint value",
135 )?;
136 optional_constraints.push(SolverConstraint::Fixed(
137 at_y_var.id.to_constraint_id(args.source_range)?,
138 y_initial_value.n,
139 ));
140 }
141 }
142 optional_constraints
143 };
144
145 let Some(sketch_state) = exec_state.sketch_block_mut() else {
147 return Err(KclError::new_semantic(KclErrorDetails::new(
148 "line() can only be used inside a sketch block".to_owned(),
149 vec![args.source_range],
150 )));
151 };
152 sketch_state.needed_by_engine.push(segment.clone());
153
154 #[cfg(feature = "artifact-graph")]
155 sketch_state.solver_optional_constraints.extend(optional_constraints);
156
157 let meta = segment.meta.clone();
158 let abstract_segment = AbstractSegment {
159 repr: SegmentRepr::Unsolved {
160 segment: Box::new(segment),
161 },
162 meta,
163 };
164 Ok(KclValue::Segment {
165 value: Box::new(abstract_segment),
166 })
167}
168
169pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
170 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
171 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
172 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
173 let construction: bool = construction_opt.unwrap_or(false);
174 let construction_ctor = construction_opt;
175 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
176 KclError::new_semantic(KclErrorDetails::new(
177 "start must be a 2D point".to_owned(),
178 vec![args.source_range],
179 ))
180 })?;
181 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
182 KclError::new_semantic(KclErrorDetails::new(
183 "end must be a 2D point".to_owned(),
184 vec![args.source_range],
185 ))
186 })?;
187 let Some(start_x) = start_x_value.as_unsolved_expr() else {
188 return Err(KclError::new_semantic(KclErrorDetails::new(
189 "start x must be a number or sketch var".to_owned(),
190 vec![args.source_range],
191 )));
192 };
193 let Some(start_y) = start_y_value.as_unsolved_expr() else {
194 return Err(KclError::new_semantic(KclErrorDetails::new(
195 "start y must be a number or sketch var".to_owned(),
196 vec![args.source_range],
197 )));
198 };
199 let Some(end_x) = end_x_value.as_unsolved_expr() else {
200 return Err(KclError::new_semantic(KclErrorDetails::new(
201 "end x must be a number or sketch var".to_owned(),
202 vec![args.source_range],
203 )));
204 };
205 let Some(end_y) = end_y_value.as_unsolved_expr() else {
206 return Err(KclError::new_semantic(KclErrorDetails::new(
207 "end y must be a number or sketch var".to_owned(),
208 vec![args.source_range],
209 )));
210 };
211 let ctor = LineCtor {
212 start: Point2d {
213 x: start_x_value.to_sketch_expr().ok_or_else(|| {
214 KclError::new_semantic(KclErrorDetails::new(
215 "unable to convert numeric type to suffix".to_owned(),
216 vec![args.source_range],
217 ))
218 })?,
219 y: start_y_value.to_sketch_expr().ok_or_else(|| {
220 KclError::new_semantic(KclErrorDetails::new(
221 "unable to convert numeric type to suffix".to_owned(),
222 vec![args.source_range],
223 ))
224 })?,
225 },
226 end: Point2d {
227 x: end_x_value.to_sketch_expr().ok_or_else(|| {
228 KclError::new_semantic(KclErrorDetails::new(
229 "unable to convert numeric type to suffix".to_owned(),
230 vec![args.source_range],
231 ))
232 })?,
233 y: end_y_value.to_sketch_expr().ok_or_else(|| {
234 KclError::new_semantic(KclErrorDetails::new(
235 "unable to convert numeric type to suffix".to_owned(),
236 vec![args.source_range],
237 ))
238 })?,
239 },
240 construction: construction_ctor,
241 };
242 let start_object_id = exec_state.next_object_id();
244 let end_object_id = exec_state.next_object_id();
245 let line_object_id = exec_state.next_object_id();
246 let segment = UnsolvedSegment {
247 id: exec_state.next_uuid(),
248 object_id: line_object_id,
249 kind: UnsolvedSegmentKind::Line {
250 start: [start_x, start_y],
251 end: [end_x, end_y],
252 ctor: Box::new(ctor),
253 start_object_id,
254 end_object_id,
255 construction,
256 },
257 tag: None,
258 meta: vec![args.source_range.into()],
259 };
260 #[cfg(feature = "artifact-graph")]
261 let optional_constraints = {
262 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
263 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
264 let line_object_id = exec_state.add_placeholder_scene_object(line_object_id, args.source_range);
265
266 let mut optional_constraints = Vec::new();
267 if exec_state.segment_ids_edited_contains(&start_object_id)
268 || exec_state.segment_ids_edited_contains(&line_object_id)
269 {
270 if let Some(start_x_var) = start_x_value.as_sketch_var() {
271 let x_initial_value = start_x_var.initial_value_to_solver_units(
272 exec_state,
273 args.source_range,
274 "edited segment fixed constraint value",
275 )?;
276 optional_constraints.push(SolverConstraint::Fixed(
277 start_x_var.id.to_constraint_id(args.source_range)?,
278 x_initial_value.n,
279 ));
280 }
281 if let Some(start_y_var) = start_y_value.as_sketch_var() {
282 let y_initial_value = start_y_var.initial_value_to_solver_units(
283 exec_state,
284 args.source_range,
285 "edited segment fixed constraint value",
286 )?;
287 optional_constraints.push(SolverConstraint::Fixed(
288 start_y_var.id.to_constraint_id(args.source_range)?,
289 y_initial_value.n,
290 ));
291 }
292 }
293 if exec_state.segment_ids_edited_contains(&end_object_id)
294 || exec_state.segment_ids_edited_contains(&line_object_id)
295 {
296 if let Some(end_x_var) = end_x_value.as_sketch_var() {
297 let x_initial_value = end_x_var.initial_value_to_solver_units(
298 exec_state,
299 args.source_range,
300 "edited segment fixed constraint value",
301 )?;
302 optional_constraints.push(SolverConstraint::Fixed(
303 end_x_var.id.to_constraint_id(args.source_range)?,
304 x_initial_value.n,
305 ));
306 }
307 if let Some(end_y_var) = end_y_value.as_sketch_var() {
308 let y_initial_value = end_y_var.initial_value_to_solver_units(
309 exec_state,
310 args.source_range,
311 "edited segment fixed constraint value",
312 )?;
313 optional_constraints.push(SolverConstraint::Fixed(
314 end_y_var.id.to_constraint_id(args.source_range)?,
315 y_initial_value.n,
316 ));
317 }
318 }
319 optional_constraints
320 };
321
322 let Some(sketch_state) = exec_state.sketch_block_mut() else {
324 return Err(KclError::new_semantic(KclErrorDetails::new(
325 "line() can only be used inside a sketch block".to_owned(),
326 vec![args.source_range],
327 )));
328 };
329 sketch_state.needed_by_engine.push(segment.clone());
330
331 #[cfg(feature = "artifact-graph")]
332 sketch_state.solver_optional_constraints.extend(optional_constraints);
333
334 let meta = segment.meta.clone();
335 let abstract_segment = AbstractSegment {
336 repr: SegmentRepr::Unsolved {
337 segment: Box::new(segment),
338 },
339 meta,
340 };
341 Ok(KclValue::Segment {
342 value: Box::new(abstract_segment),
343 })
344}
345
346pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
347 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
348 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
349 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
351 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
352 let construction: bool = construction_opt.unwrap_or(false);
353 let construction_ctor = construction_opt;
354
355 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
356 KclError::new_semantic(KclErrorDetails::new(
357 "start must be a 2D point".to_owned(),
358 vec![args.source_range],
359 ))
360 })?;
361 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
362 KclError::new_semantic(KclErrorDetails::new(
363 "end must be a 2D point".to_owned(),
364 vec![args.source_range],
365 ))
366 })?;
367 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
368 KclError::new_semantic(KclErrorDetails::new(
369 "center must be a 2D point".to_owned(),
370 vec![args.source_range],
371 ))
372 })?;
373
374 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
375 return Err(KclError::new_semantic(KclErrorDetails::new(
376 "start x must be a sketch var".to_owned(),
377 vec![args.source_range],
378 )));
379 };
380 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
381 return Err(KclError::new_semantic(KclErrorDetails::new(
382 "start y must be a sketch var".to_owned(),
383 vec![args.source_range],
384 )));
385 };
386 let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
387 return Err(KclError::new_semantic(KclErrorDetails::new(
388 "end x must be a sketch var".to_owned(),
389 vec![args.source_range],
390 )));
391 };
392 let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
393 return Err(KclError::new_semantic(KclErrorDetails::new(
394 "end y must be a sketch var".to_owned(),
395 vec![args.source_range],
396 )));
397 };
398 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
399 return Err(KclError::new_semantic(KclErrorDetails::new(
400 "center x must be a sketch var".to_owned(),
401 vec![args.source_range],
402 )));
403 };
404 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
405 return Err(KclError::new_semantic(KclErrorDetails::new(
406 "center y must be a sketch var".to_owned(),
407 vec![args.source_range],
408 )));
409 };
410
411 let ctor = ArcCtor {
412 start: Point2d {
413 x: start_x_value.to_sketch_expr().ok_or_else(|| {
414 KclError::new_semantic(KclErrorDetails::new(
415 "unable to convert numeric type to suffix".to_owned(),
416 vec![args.source_range],
417 ))
418 })?,
419 y: start_y_value.to_sketch_expr().ok_or_else(|| {
420 KclError::new_semantic(KclErrorDetails::new(
421 "unable to convert numeric type to suffix".to_owned(),
422 vec![args.source_range],
423 ))
424 })?,
425 },
426 end: Point2d {
427 x: end_x_value.to_sketch_expr().ok_or_else(|| {
428 KclError::new_semantic(KclErrorDetails::new(
429 "unable to convert numeric type to suffix".to_owned(),
430 vec![args.source_range],
431 ))
432 })?,
433 y: end_y_value.to_sketch_expr().ok_or_else(|| {
434 KclError::new_semantic(KclErrorDetails::new(
435 "unable to convert numeric type to suffix".to_owned(),
436 vec![args.source_range],
437 ))
438 })?,
439 },
440 center: Point2d {
441 x: center_x_value.to_sketch_expr().ok_or_else(|| {
442 KclError::new_semantic(KclErrorDetails::new(
443 "unable to convert numeric type to suffix".to_owned(),
444 vec![args.source_range],
445 ))
446 })?,
447 y: center_y_value.to_sketch_expr().ok_or_else(|| {
448 KclError::new_semantic(KclErrorDetails::new(
449 "unable to convert numeric type to suffix".to_owned(),
450 vec![args.source_range],
451 ))
452 })?,
453 },
454 construction: construction_ctor,
455 };
456
457 let start_object_id = exec_state.next_object_id();
459 let end_object_id = exec_state.next_object_id();
460 let center_object_id = exec_state.next_object_id();
461 let arc_object_id = exec_state.next_object_id();
462 let segment = UnsolvedSegment {
463 id: exec_state.next_uuid(),
464 object_id: arc_object_id,
465 kind: UnsolvedSegmentKind::Arc {
466 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
467 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
468 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
469 ctor: Box::new(ctor),
470 start_object_id,
471 end_object_id,
472 center_object_id,
473 construction,
474 },
475 tag: None,
476 meta: vec![args.source_range.into()],
477 };
478 #[cfg(feature = "artifact-graph")]
479 let optional_constraints = {
480 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
481 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
482 let center_object_id = exec_state.add_placeholder_scene_object(center_object_id, args.source_range);
483 let arc_object_id = exec_state.add_placeholder_scene_object(arc_object_id, args.source_range);
484
485 let mut optional_constraints = Vec::new();
486 if exec_state.segment_ids_edited_contains(&start_object_id)
487 || exec_state.segment_ids_edited_contains(&arc_object_id)
488 {
489 if let Some(start_x_var) = start_x_value.as_sketch_var() {
490 let x_initial_value = start_x_var.initial_value_to_solver_units(
491 exec_state,
492 args.source_range,
493 "edited segment fixed constraint value",
494 )?;
495 optional_constraints.push(ezpz::Constraint::Fixed(
496 start_x_var.id.to_constraint_id(args.source_range)?,
497 x_initial_value.n,
498 ));
499 }
500 if let Some(start_y_var) = start_y_value.as_sketch_var() {
501 let y_initial_value = start_y_var.initial_value_to_solver_units(
502 exec_state,
503 args.source_range,
504 "edited segment fixed constraint value",
505 )?;
506 optional_constraints.push(ezpz::Constraint::Fixed(
507 start_y_var.id.to_constraint_id(args.source_range)?,
508 y_initial_value.n,
509 ));
510 }
511 }
512 if exec_state.segment_ids_edited_contains(&end_object_id)
513 || exec_state.segment_ids_edited_contains(&arc_object_id)
514 {
515 if let Some(end_x_var) = end_x_value.as_sketch_var() {
516 let x_initial_value = end_x_var.initial_value_to_solver_units(
517 exec_state,
518 args.source_range,
519 "edited segment fixed constraint value",
520 )?;
521 optional_constraints.push(ezpz::Constraint::Fixed(
522 end_x_var.id.to_constraint_id(args.source_range)?,
523 x_initial_value.n,
524 ));
525 }
526 if let Some(end_y_var) = end_y_value.as_sketch_var() {
527 let y_initial_value = end_y_var.initial_value_to_solver_units(
528 exec_state,
529 args.source_range,
530 "edited segment fixed constraint value",
531 )?;
532 optional_constraints.push(ezpz::Constraint::Fixed(
533 end_y_var.id.to_constraint_id(args.source_range)?,
534 y_initial_value.n,
535 ));
536 }
537 }
538 if exec_state.segment_ids_edited_contains(¢er_object_id)
539 || exec_state.segment_ids_edited_contains(&arc_object_id)
540 {
541 if let Some(center_x_var) = center_x_value.as_sketch_var() {
542 let x_initial_value = center_x_var.initial_value_to_solver_units(
543 exec_state,
544 args.source_range,
545 "edited segment fixed constraint value",
546 )?;
547 optional_constraints.push(ezpz::Constraint::Fixed(
548 center_x_var.id.to_constraint_id(args.source_range)?,
549 x_initial_value.n,
550 ));
551 }
552 if let Some(center_y_var) = center_y_value.as_sketch_var() {
553 let y_initial_value = center_y_var.initial_value_to_solver_units(
554 exec_state,
555 args.source_range,
556 "edited segment fixed constraint value",
557 )?;
558 optional_constraints.push(ezpz::Constraint::Fixed(
559 center_y_var.id.to_constraint_id(args.source_range)?,
560 y_initial_value.n,
561 ));
562 }
563 }
564 optional_constraints
565 };
566
567 let range = args.source_range;
569 let constraint = ezpz::Constraint::Arc(ezpz::datatypes::inputs::DatumCircularArc {
570 center: ezpz::datatypes::inputs::DatumPoint::new_xy(
571 center_x.to_constraint_id(range)?,
572 center_y.to_constraint_id(range)?,
573 ),
574 start: ezpz::datatypes::inputs::DatumPoint::new_xy(
575 start_x.to_constraint_id(range)?,
576 start_y.to_constraint_id(range)?,
577 ),
578 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
579 end_x.to_constraint_id(range)?,
580 end_y.to_constraint_id(range)?,
581 ),
582 });
583
584 let Some(sketch_state) = exec_state.sketch_block_mut() else {
585 return Err(KclError::new_semantic(KclErrorDetails::new(
586 "arc() can only be used inside a sketch block".to_owned(),
587 vec![args.source_range],
588 )));
589 };
590 sketch_state.needed_by_engine.push(segment.clone());
592 sketch_state.solver_constraints.push(constraint);
594 #[cfg(feature = "artifact-graph")]
598 sketch_state.solver_optional_constraints.extend(optional_constraints);
599
600 let meta = segment.meta.clone();
601 let abstract_segment = AbstractSegment {
602 repr: SegmentRepr::Unsolved {
603 segment: Box::new(segment),
604 },
605 meta,
606 };
607 Ok(KclValue::Segment {
608 value: Box::new(abstract_segment),
609 })
610}
611
612pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
613 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
614 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
615 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
616 let construction: bool = construction_opt.unwrap_or(false);
617 let construction_ctor = construction_opt;
618
619 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
620 KclError::new_semantic(KclErrorDetails::new(
621 "start must be a 2D point".to_owned(),
622 vec![args.source_range],
623 ))
624 })?;
625 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
626 KclError::new_semantic(KclErrorDetails::new(
627 "center must be a 2D point".to_owned(),
628 vec![args.source_range],
629 ))
630 })?;
631
632 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
633 return Err(KclError::new_semantic(KclErrorDetails::new(
634 "start x must be a sketch var".to_owned(),
635 vec![args.source_range],
636 )));
637 };
638 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
639 return Err(KclError::new_semantic(KclErrorDetails::new(
640 "start y must be a sketch var".to_owned(),
641 vec![args.source_range],
642 )));
643 };
644 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
645 return Err(KclError::new_semantic(KclErrorDetails::new(
646 "center x must be a sketch var".to_owned(),
647 vec![args.source_range],
648 )));
649 };
650 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
651 return Err(KclError::new_semantic(KclErrorDetails::new(
652 "center y must be a sketch var".to_owned(),
653 vec![args.source_range],
654 )));
655 };
656
657 let ctor = CircleCtor {
658 start: Point2d {
659 x: start_x_value.to_sketch_expr().ok_or_else(|| {
660 KclError::new_semantic(KclErrorDetails::new(
661 "unable to convert numeric type to suffix".to_owned(),
662 vec![args.source_range],
663 ))
664 })?,
665 y: start_y_value.to_sketch_expr().ok_or_else(|| {
666 KclError::new_semantic(KclErrorDetails::new(
667 "unable to convert numeric type to suffix".to_owned(),
668 vec![args.source_range],
669 ))
670 })?,
671 },
672 center: Point2d {
673 x: center_x_value.to_sketch_expr().ok_or_else(|| {
674 KclError::new_semantic(KclErrorDetails::new(
675 "unable to convert numeric type to suffix".to_owned(),
676 vec![args.source_range],
677 ))
678 })?,
679 y: center_y_value.to_sketch_expr().ok_or_else(|| {
680 KclError::new_semantic(KclErrorDetails::new(
681 "unable to convert numeric type to suffix".to_owned(),
682 vec![args.source_range],
683 ))
684 })?,
685 },
686 construction: construction_ctor,
687 };
688
689 let start_object_id = exec_state.next_object_id();
691 let center_object_id = exec_state.next_object_id();
692 let circle_object_id = exec_state.next_object_id();
693 let segment = UnsolvedSegment {
694 id: exec_state.next_uuid(),
695 object_id: circle_object_id,
696 kind: UnsolvedSegmentKind::Circle {
697 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
698 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
699 ctor: Box::new(ctor),
700 start_object_id,
701 center_object_id,
702 construction,
703 },
704 tag: None,
705 meta: vec![args.source_range.into()],
706 };
707 #[cfg(feature = "artifact-graph")]
708 let optional_constraints = {
709 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
710 let center_object_id = exec_state.add_placeholder_scene_object(center_object_id, args.source_range);
711 let circle_object_id = exec_state.add_placeholder_scene_object(circle_object_id, args.source_range);
712
713 let mut optional_constraints = Vec::new();
714 if exec_state.segment_ids_edited_contains(&start_object_id)
715 || exec_state.segment_ids_edited_contains(&circle_object_id)
716 {
717 if let Some(start_x_var) = start_x_value.as_sketch_var() {
718 let x_initial_value = start_x_var.initial_value_to_solver_units(
719 exec_state,
720 args.source_range,
721 "edited segment fixed constraint value",
722 )?;
723 optional_constraints.push(ezpz::Constraint::Fixed(
724 start_x_var.id.to_constraint_id(args.source_range)?,
725 x_initial_value.n,
726 ));
727 }
728 if let Some(start_y_var) = start_y_value.as_sketch_var() {
729 let y_initial_value = start_y_var.initial_value_to_solver_units(
730 exec_state,
731 args.source_range,
732 "edited segment fixed constraint value",
733 )?;
734 optional_constraints.push(ezpz::Constraint::Fixed(
735 start_y_var.id.to_constraint_id(args.source_range)?,
736 y_initial_value.n,
737 ));
738 }
739 }
740 if exec_state.segment_ids_edited_contains(¢er_object_id)
741 || exec_state.segment_ids_edited_contains(&circle_object_id)
742 {
743 if let Some(center_x_var) = center_x_value.as_sketch_var() {
744 let x_initial_value = center_x_var.initial_value_to_solver_units(
745 exec_state,
746 args.source_range,
747 "edited segment fixed constraint value",
748 )?;
749 optional_constraints.push(ezpz::Constraint::Fixed(
750 center_x_var.id.to_constraint_id(args.source_range)?,
751 x_initial_value.n,
752 ));
753 }
754 if let Some(center_y_var) = center_y_value.as_sketch_var() {
755 let y_initial_value = center_y_var.initial_value_to_solver_units(
756 exec_state,
757 args.source_range,
758 "edited segment fixed constraint value",
759 )?;
760 optional_constraints.push(ezpz::Constraint::Fixed(
761 center_y_var.id.to_constraint_id(args.source_range)?,
762 y_initial_value.n,
763 ));
764 }
765 }
766 optional_constraints
767 };
768
769 let Some(sketch_state) = exec_state.sketch_block_mut() else {
770 return Err(KclError::new_semantic(KclErrorDetails::new(
771 "circle() can only be used inside a sketch block".to_owned(),
772 vec![args.source_range],
773 )));
774 };
775 sketch_state.needed_by_engine.push(segment.clone());
777
778 #[cfg(feature = "artifact-graph")]
779 sketch_state.solver_optional_constraints.extend(optional_constraints);
780
781 let meta = segment.meta.clone();
782 let abstract_segment = AbstractSegment {
783 repr: SegmentRepr::Unsolved {
784 segment: Box::new(segment),
785 },
786 meta,
787 };
788 Ok(KclValue::Segment {
789 value: Box::new(abstract_segment),
790 })
791}
792
793pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
794 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
795 "points",
796 &RuntimeType::Array(
797 Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
798 ArrayLen::Known(2),
799 ),
800 exec_state,
801 )?;
802 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
803 KclError::new_semantic(KclErrorDetails::new(
804 "must have two input points".to_owned(),
805 vec![args.source_range],
806 ))
807 })?;
808
809 let range = args.source_range;
810 match (&point0, &point1) {
811 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
812 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
813 return Err(KclError::new_semantic(KclErrorDetails::new(
814 "first point must be an unsolved segment".to_owned(),
815 vec![args.source_range],
816 )));
817 };
818 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
819 return Err(KclError::new_semantic(KclErrorDetails::new(
820 "second point must be an unsolved segment".to_owned(),
821 vec![args.source_range],
822 )));
823 };
824 match (&unsolved0.kind, &unsolved1.kind) {
825 (
826 UnsolvedSegmentKind::Point { position: pos0, .. },
827 UnsolvedSegmentKind::Point { position: pos1, .. },
828 ) => {
829 let p0_x = &pos0[0];
830 let p0_y = &pos0[1];
831 match (p0_x, p0_y) {
832 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
833 let p1_x = &pos1[0];
834 let p1_y = &pos1[1];
835 match (p1_x, p1_y) {
836 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
837 let constraint = SolverConstraint::PointsCoincident(
838 ezpz::datatypes::inputs::DatumPoint::new_xy(
839 p0_x.to_constraint_id(range)?,
840 p0_y.to_constraint_id(range)?,
841 ),
842 ezpz::datatypes::inputs::DatumPoint::new_xy(
843 p1_x.to_constraint_id(range)?,
844 p1_y.to_constraint_id(range)?,
845 ),
846 );
847 #[cfg(feature = "artifact-graph")]
848 let constraint_id = exec_state.next_object_id();
849 let Some(sketch_state) = exec_state.sketch_block_mut() else {
851 return Err(KclError::new_semantic(KclErrorDetails::new(
852 "coincident() can only be used inside a sketch block".to_owned(),
853 vec![args.source_range],
854 )));
855 };
856 sketch_state.solver_constraints.push(constraint);
857 #[cfg(feature = "artifact-graph")]
858 {
859 let constraint = crate::front::Constraint::Coincident(Coincident {
860 segments: vec![unsolved0.object_id, unsolved1.object_id],
861 });
862 sketch_state.sketch_constraints.push(constraint_id);
863 track_constraint(constraint_id, constraint, exec_state, &args);
864 }
865 Ok(KclValue::none())
866 }
867 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
868 let p1_x = KclValue::Number {
869 value: p1_x.n,
870 ty: p1_x.ty,
871 meta: vec![args.source_range.into()],
872 };
873 let p1_y = KclValue::Number {
874 value: p1_y.n,
875 ty: p1_y.ty,
876 meta: vec![args.source_range.into()],
877 };
878 let (constraint_x, constraint_y) =
879 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
880
881 #[cfg(feature = "artifact-graph")]
882 let constraint_id = exec_state.next_object_id();
883 let Some(sketch_state) = exec_state.sketch_block_mut() else {
885 return Err(KclError::new_semantic(KclErrorDetails::new(
886 "coincident() can only be used inside a sketch block".to_owned(),
887 vec![args.source_range],
888 )));
889 };
890 sketch_state.solver_constraints.push(constraint_x);
891 sketch_state.solver_constraints.push(constraint_y);
892 #[cfg(feature = "artifact-graph")]
893 {
894 let constraint = crate::front::Constraint::Coincident(Coincident {
895 segments: vec![unsolved0.object_id, unsolved1.object_id],
896 });
897 sketch_state.sketch_constraints.push(constraint_id);
898 track_constraint(constraint_id, constraint, exec_state, &args);
899 }
900 Ok(KclValue::none())
901 }
902 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
903 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
904 Err(KclError::new_semantic(KclErrorDetails::new(
906 "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(),
907 vec![args.source_range],
908 )))
909 }
910 }
911 }
912 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
913 let p1_x = &pos1[0];
914 let p1_y = &pos1[1];
915 match (p1_x, p1_y) {
916 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
917 let p0_x = KclValue::Number {
918 value: p0_x.n,
919 ty: p0_x.ty,
920 meta: vec![args.source_range.into()],
921 };
922 let p0_y = KclValue::Number {
923 value: p0_y.n,
924 ty: p0_y.ty,
925 meta: vec![args.source_range.into()],
926 };
927 let (constraint_x, constraint_y) =
928 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
929
930 #[cfg(feature = "artifact-graph")]
931 let constraint_id = exec_state.next_object_id();
932 let Some(sketch_state) = exec_state.sketch_block_mut() else {
934 return Err(KclError::new_semantic(KclErrorDetails::new(
935 "coincident() can only be used inside a sketch block".to_owned(),
936 vec![args.source_range],
937 )));
938 };
939 sketch_state.solver_constraints.push(constraint_x);
940 sketch_state.solver_constraints.push(constraint_y);
941 #[cfg(feature = "artifact-graph")]
942 {
943 let constraint = crate::front::Constraint::Coincident(Coincident {
944 segments: vec![unsolved0.object_id, unsolved1.object_id],
945 });
946 sketch_state.sketch_constraints.push(constraint_id);
947 track_constraint(constraint_id, constraint, exec_state, &args);
948 }
949 Ok(KclValue::none())
950 }
951 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
952 if *p0_x != *p1_x || *p0_y != *p1_y {
953 return Err(KclError::new_semantic(KclErrorDetails::new(
954 "Coincident constraint between two fixed points failed since coordinates differ"
955 .to_owned(),
956 vec![args.source_range],
957 )));
958 }
959 Ok(KclValue::none())
960 }
961 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
962 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
963 Err(KclError::new_semantic(KclErrorDetails::new(
965 "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(),
966 vec![args.source_range],
967 )))
968 }
969 }
970 }
971 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
972 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
973 Err(KclError::new_semantic(KclErrorDetails::new(
975 "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(),
976 vec![args.source_range],
977 )))
978 }
979 }
980 }
981 (
983 UnsolvedSegmentKind::Point {
984 position: point_pos, ..
985 },
986 UnsolvedSegmentKind::Line {
987 start: line_start,
988 end: line_end,
989 ..
990 },
991 )
992 | (
993 UnsolvedSegmentKind::Line {
994 start: line_start,
995 end: line_end,
996 ..
997 },
998 UnsolvedSegmentKind::Point {
999 position: point_pos, ..
1000 },
1001 ) => {
1002 let point_x = &point_pos[0];
1003 let point_y = &point_pos[1];
1004 match (point_x, point_y) {
1005 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1006 let (start_x, start_y) = (&line_start[0], &line_start[1]);
1008 let (end_x, end_y) = (&line_end[0], &line_end[1]);
1009
1010 match (start_x, start_y, end_x, end_y) {
1011 (
1012 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1013 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1014 ) => {
1015 let point = DatumPoint::new_xy(
1016 point_x.to_constraint_id(range)?,
1017 point_y.to_constraint_id(range)?,
1018 );
1019 let line_segment = DatumLineSegment::new(
1020 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
1021 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
1022 );
1023 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
1024
1025 #[cfg(feature = "artifact-graph")]
1026 let constraint_id = exec_state.next_object_id();
1027
1028 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1029 return Err(KclError::new_semantic(KclErrorDetails::new(
1030 "coincident() can only be used inside a sketch block".to_owned(),
1031 vec![args.source_range],
1032 )));
1033 };
1034 sketch_state.solver_constraints.push(constraint);
1035 #[cfg(feature = "artifact-graph")]
1036 {
1037 let constraint = crate::front::Constraint::Coincident(Coincident {
1038 segments: vec![unsolved0.object_id, unsolved1.object_id],
1039 });
1040 sketch_state.sketch_constraints.push(constraint_id);
1041 track_constraint(constraint_id, constraint, exec_state, &args);
1042 }
1043 Ok(KclValue::none())
1044 }
1045 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1046 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
1047 vec![args.source_range],
1048 ))),
1049 }
1050 }
1051 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1052 "Point coordinates must be sketch variables for point-segment coincident constraint"
1053 .to_owned(),
1054 vec![args.source_range],
1055 ))),
1056 }
1057 }
1058 (
1060 UnsolvedSegmentKind::Point {
1061 position: point_pos, ..
1062 },
1063 UnsolvedSegmentKind::Arc {
1064 start: arc_start,
1065 end: arc_end,
1066 center: arc_center,
1067 ..
1068 },
1069 )
1070 | (
1071 UnsolvedSegmentKind::Arc {
1072 start: arc_start,
1073 end: arc_end,
1074 center: arc_center,
1075 ..
1076 },
1077 UnsolvedSegmentKind::Point {
1078 position: point_pos, ..
1079 },
1080 ) => {
1081 let point_x = &point_pos[0];
1082 let point_y = &point_pos[1];
1083 match (point_x, point_y) {
1084 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1085 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
1087 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
1088 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
1089
1090 match (center_x, center_y, start_x, start_y, end_x, end_y) {
1091 (
1092 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
1093 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1094 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1095 ) => {
1096 let point = DatumPoint::new_xy(
1097 point_x.to_constraint_id(range)?,
1098 point_y.to_constraint_id(range)?,
1099 );
1100 let circular_arc = DatumCircularArc {
1101 center: DatumPoint::new_xy(
1102 cx.to_constraint_id(range)?,
1103 cy.to_constraint_id(range)?,
1104 ),
1105 start: DatumPoint::new_xy(
1106 sx.to_constraint_id(range)?,
1107 sy.to_constraint_id(range)?,
1108 ),
1109 end: DatumPoint::new_xy(
1110 ex.to_constraint_id(range)?,
1111 ey.to_constraint_id(range)?,
1112 ),
1113 };
1114 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
1115
1116 #[cfg(feature = "artifact-graph")]
1117 let constraint_id = exec_state.next_object_id();
1118
1119 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1120 return Err(KclError::new_semantic(KclErrorDetails::new(
1121 "coincident() can only be used inside a sketch block".to_owned(),
1122 vec![args.source_range],
1123 )));
1124 };
1125 sketch_state.solver_constraints.push(constraint);
1126 #[cfg(feature = "artifact-graph")]
1127 {
1128 let constraint = crate::front::Constraint::Coincident(Coincident {
1129 segments: vec![unsolved0.object_id, unsolved1.object_id],
1130 });
1131 sketch_state.sketch_constraints.push(constraint_id);
1132 track_constraint(constraint_id, constraint, exec_state, &args);
1133 }
1134 Ok(KclValue::none())
1135 }
1136 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1137 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
1138 vec![args.source_range],
1139 ))),
1140 }
1141 }
1142 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1143 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
1144 vec![args.source_range],
1145 ))),
1146 }
1147 }
1148 (
1150 UnsolvedSegmentKind::Line {
1151 start: line0_start,
1152 end: line0_end,
1153 ..
1154 },
1155 UnsolvedSegmentKind::Line {
1156 start: line1_start,
1157 end: line1_end,
1158 ..
1159 },
1160 ) => {
1161 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
1163 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
1164 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
1165 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
1166
1167 match (
1168 line0_start_x,
1169 line0_start_y,
1170 line0_end_x,
1171 line0_end_y,
1172 line1_start_x,
1173 line1_start_y,
1174 line1_end_x,
1175 line1_end_y,
1176 ) {
1177 (
1178 UnsolvedExpr::Unknown(l0_sx),
1179 UnsolvedExpr::Unknown(l0_sy),
1180 UnsolvedExpr::Unknown(l0_ex),
1181 UnsolvedExpr::Unknown(l0_ey),
1182 UnsolvedExpr::Unknown(l1_sx),
1183 UnsolvedExpr::Unknown(l1_sy),
1184 UnsolvedExpr::Unknown(l1_ex),
1185 UnsolvedExpr::Unknown(l1_ey),
1186 ) => {
1187 let line0_segment = DatumLineSegment::new(
1189 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
1190 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
1191 );
1192 let line1_segment = DatumLineSegment::new(
1193 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
1194 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
1195 );
1196
1197 let parallel_constraint =
1199 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
1200
1201 let point_on_line1 =
1203 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
1204 let distance_constraint =
1205 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
1206
1207 #[cfg(feature = "artifact-graph")]
1208 let constraint_id = exec_state.next_object_id();
1209
1210 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1211 return Err(KclError::new_semantic(KclErrorDetails::new(
1212 "coincident() can only be used inside a sketch block".to_owned(),
1213 vec![args.source_range],
1214 )));
1215 };
1216 sketch_state.solver_constraints.push(parallel_constraint);
1218 sketch_state.solver_constraints.push(distance_constraint);
1219 #[cfg(feature = "artifact-graph")]
1220 {
1221 let constraint = crate::front::Constraint::Coincident(Coincident {
1222 segments: vec![unsolved0.object_id, unsolved1.object_id],
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 "Line segment endpoints must be sketch variables for line-line coincident constraint"
1231 .to_owned(),
1232 vec![args.source_range],
1233 ))),
1234 }
1235 }
1236 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1237 format!(
1238 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
1239 &unsolved0.kind, &unsolved1.kind
1240 ),
1241 vec![args.source_range],
1242 ))),
1243 }
1244 }
1245 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
1247 let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
1248 return Err(KclError::new_semantic(KclErrorDetails::new(
1249 "Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
1250 vec![args.source_range],
1251 )));
1252 };
1253 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1254 return Err(KclError::new_semantic(KclErrorDetails::new(
1255 "segment must be an unsolved segment".to_owned(),
1256 vec![args.source_range],
1257 )));
1258 };
1259 match &unsolved.kind {
1260 UnsolvedSegmentKind::Point { position, .. } => {
1261 let p_x = &position[0];
1262 let p_y = &position[1];
1263 match (p_x, p_y) {
1264 (UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
1265 let pt_x = KclValue::Number {
1266 value: pt[0].n,
1267 ty: pt[0].ty,
1268 meta: vec![args.source_range.into()],
1269 };
1270 let pt_y = KclValue::Number {
1271 value: pt[1].n,
1272 ty: pt[1].ty,
1273 meta: vec![args.source_range.into()],
1274 };
1275 let (constraint_x, constraint_y) =
1276 coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
1277
1278 #[cfg(feature = "artifact-graph")]
1279 let constraint_id = exec_state.next_object_id();
1280 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1281 return Err(KclError::new_semantic(KclErrorDetails::new(
1282 "coincident() can only be used inside a sketch block".to_owned(),
1283 vec![args.source_range],
1284 )));
1285 };
1286 sketch_state.solver_constraints.push(constraint_x);
1287 sketch_state.solver_constraints.push(constraint_y);
1288 #[cfg(feature = "artifact-graph")]
1289 {
1290 let constraint = crate::front::Constraint::Coincident(Coincident {
1291 segments: vec![unsolved.object_id],
1292 });
1293 sketch_state.sketch_constraints.push(constraint_id);
1294 track_constraint(constraint_id, constraint, exec_state, &args);
1295 }
1296 Ok(KclValue::none())
1297 }
1298 (UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
1299 let pt_x_val = normalize_to_solver_distance_unit(
1300 &KclValue::Number {
1301 value: pt[0].n,
1302 ty: pt[0].ty,
1303 meta: vec![args.source_range.into()],
1304 },
1305 args.source_range,
1306 exec_state,
1307 "coincident constraint value",
1308 )?;
1309 let pt_y_val = normalize_to_solver_distance_unit(
1310 &KclValue::Number {
1311 value: pt[1].n,
1312 ty: pt[1].ty,
1313 meta: vec![args.source_range.into()],
1314 },
1315 args.source_range,
1316 exec_state,
1317 "coincident constraint value",
1318 )?;
1319 let Some(pt_x) = pt_x_val.as_ty_f64() else {
1320 return Err(KclError::new_semantic(KclErrorDetails::new(
1321 "Expected number for Point2d x coordinate".to_owned(),
1322 vec![args.source_range],
1323 )));
1324 };
1325 let Some(pt_y) = pt_y_val.as_ty_f64() else {
1326 return Err(KclError::new_semantic(KclErrorDetails::new(
1327 "Expected number for Point2d y coordinate".to_owned(),
1328 vec![args.source_range],
1329 )));
1330 };
1331 let known_x_val = normalize_to_solver_distance_unit(
1332 &KclValue::Number {
1333 value: known_x.n,
1334 ty: known_x.ty,
1335 meta: vec![args.source_range.into()],
1336 },
1337 args.source_range,
1338 exec_state,
1339 "coincident constraint value",
1340 )?;
1341 let Some(known_x_f) = known_x_val.as_ty_f64() else {
1342 return Err(KclError::new_semantic(KclErrorDetails::new(
1343 "Expected number for known x coordinate".to_owned(),
1344 vec![args.source_range],
1345 )));
1346 };
1347 let known_y_val = normalize_to_solver_distance_unit(
1348 &KclValue::Number {
1349 value: known_y.n,
1350 ty: known_y.ty,
1351 meta: vec![args.source_range.into()],
1352 },
1353 args.source_range,
1354 exec_state,
1355 "coincident constraint value",
1356 )?;
1357 let Some(known_y_f) = known_y_val.as_ty_f64() else {
1358 return Err(KclError::new_semantic(KclErrorDetails::new(
1359 "Expected number for known y coordinate".to_owned(),
1360 vec![args.source_range],
1361 )));
1362 };
1363 if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
1364 return Err(KclError::new_semantic(KclErrorDetails::new(
1365 "Coincident constraint between two fixed points failed since coordinates differ"
1366 .to_owned(),
1367 vec![args.source_range],
1368 )));
1369 }
1370 Ok(KclValue::none())
1371 }
1372 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1373 "Point coordinates must have consistent known/unknown status for coincident constraint"
1374 .to_owned(),
1375 vec![args.source_range],
1376 ))),
1377 }
1378 }
1379 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1380 "A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
1381 vec![args.source_range],
1382 ))),
1383 }
1384 }
1385 _ => {
1387 let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
1388 let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
1389 match (pt0, pt1) {
1390 (Some(a), Some(b)) => {
1391 let a_x = normalize_to_solver_distance_unit(
1393 &KclValue::Number {
1394 value: a[0].n,
1395 ty: a[0].ty,
1396 meta: vec![args.source_range.into()],
1397 },
1398 args.source_range,
1399 exec_state,
1400 "coincident constraint value",
1401 )?;
1402 let a_y = normalize_to_solver_distance_unit(
1403 &KclValue::Number {
1404 value: a[1].n,
1405 ty: a[1].ty,
1406 meta: vec![args.source_range.into()],
1407 },
1408 args.source_range,
1409 exec_state,
1410 "coincident constraint value",
1411 )?;
1412 let b_x = normalize_to_solver_distance_unit(
1413 &KclValue::Number {
1414 value: b[0].n,
1415 ty: b[0].ty,
1416 meta: vec![args.source_range.into()],
1417 },
1418 args.source_range,
1419 exec_state,
1420 "coincident constraint value",
1421 )?;
1422 let b_y = normalize_to_solver_distance_unit(
1423 &KclValue::Number {
1424 value: b[1].n,
1425 ty: b[1].ty,
1426 meta: vec![args.source_range.into()],
1427 },
1428 args.source_range,
1429 exec_state,
1430 "coincident constraint value",
1431 )?;
1432 if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
1433 || a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
1434 {
1435 return Err(KclError::new_semantic(KclErrorDetails::new(
1436 "Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
1437 vec![args.source_range],
1438 )));
1439 }
1440 Ok(KclValue::none())
1441 }
1442 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1443 "All inputs must be Segments or Point2d values".to_owned(),
1444 vec![args.source_range],
1445 ))),
1446 }
1447 }
1448 }
1449}
1450
1451#[cfg(feature = "artifact-graph")]
1452fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
1453 let sketch_id = {
1454 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1455 debug_assert!(false, "Constraint created outside a sketch block");
1456 return;
1457 };
1458 sketch_state.sketch_id
1459 };
1460 let Some(sketch_id) = sketch_id else {
1461 debug_assert!(false, "Constraint created without a sketch id");
1462 return;
1463 };
1464 let artifact_id = exec_state.next_artifact_id();
1465 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
1466 id: artifact_id,
1467 sketch_id,
1468 constraint_id,
1469 constraint_type: SketchBlockConstraintType::from(&constraint),
1470 code_ref: CodeRef::placeholder(args.source_range),
1471 }));
1472 exec_state.add_scene_object(
1473 Object {
1474 id: constraint_id,
1475 kind: ObjectKind::Constraint { constraint },
1476 label: Default::default(),
1477 comments: Default::default(),
1478 artifact_id,
1479 source: args.source_range.into(),
1480 },
1481 args.source_range,
1482 );
1483}
1484
1485fn coincident_constraints_fixed(
1487 p0_x: SketchVarId,
1488 p0_y: SketchVarId,
1489 p1_x: &KclValue,
1490 p1_y: &KclValue,
1491 exec_state: &mut ExecState,
1492 args: &Args,
1493) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
1494 let p1_x_number_value =
1495 normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
1496 let p1_y_number_value =
1497 normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
1498 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
1499 let message = format!(
1500 "Expected number after coercion, but found {}",
1501 p1_x_number_value.human_friendly_type()
1502 );
1503 debug_assert!(false, "{}", &message);
1504 return Err(KclError::new_internal(KclErrorDetails::new(
1505 message,
1506 vec![args.source_range],
1507 )));
1508 };
1509 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
1510 let message = format!(
1511 "Expected number after coercion, but found {}",
1512 p1_y_number_value.human_friendly_type()
1513 );
1514 debug_assert!(false, "{}", &message);
1515 return Err(KclError::new_internal(KclErrorDetails::new(
1516 message,
1517 vec![args.source_range],
1518 )));
1519 };
1520 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
1521 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
1522 Ok((constraint_x, constraint_y))
1523}
1524
1525pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1526 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1527 "points",
1528 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1529 exec_state,
1530 )?;
1531 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1532 KclError::new_semantic(KclErrorDetails::new(
1533 "must have two input points".to_owned(),
1534 vec![args.source_range],
1535 ))
1536 })?;
1537
1538 match (&point0, &point1) {
1539 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1540 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1541 return Err(KclError::new_semantic(KclErrorDetails::new(
1542 "first point must be an unsolved segment".to_owned(),
1543 vec![args.source_range],
1544 )));
1545 };
1546 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1547 return Err(KclError::new_semantic(KclErrorDetails::new(
1548 "second point must be an unsolved segment".to_owned(),
1549 vec![args.source_range],
1550 )));
1551 };
1552 match (&unsolved0.kind, &unsolved1.kind) {
1553 (
1554 UnsolvedSegmentKind::Point { position: pos0, .. },
1555 UnsolvedSegmentKind::Point { position: pos1, .. },
1556 ) => {
1557 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1560 (
1561 UnsolvedExpr::Unknown(p0_x),
1562 UnsolvedExpr::Unknown(p0_y),
1563 UnsolvedExpr::Unknown(p1_x),
1564 UnsolvedExpr::Unknown(p1_y),
1565 ) => {
1566 let sketch_constraint = SketchConstraint {
1568 kind: SketchConstraintKind::Distance {
1569 points: [
1570 ConstrainablePoint2d {
1571 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1572 object_id: unsolved0.object_id,
1573 },
1574 ConstrainablePoint2d {
1575 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1576 object_id: unsolved1.object_id,
1577 },
1578 ],
1579 },
1580 meta: vec![args.source_range.into()],
1581 };
1582 Ok(KclValue::SketchConstraint {
1583 value: Box::new(sketch_constraint),
1584 })
1585 }
1586 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1587 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1588 vec![args.source_range],
1589 ))),
1590 }
1591 }
1592 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1593 "distance() arguments must be unsolved points".to_owned(),
1594 vec![args.source_range],
1595 ))),
1596 }
1597 }
1598 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1599 "distance() arguments must be point segments".to_owned(),
1600 vec![args.source_range],
1601 ))),
1602 }
1603}
1604
1605fn create_circular_radius_constraint(
1608 segment: KclValue,
1609 constraint_kind: fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
1610 source_range: crate::SourceRange,
1611) -> Result<SketchConstraint, KclError> {
1612 let dummy_constraint = constraint_kind([
1614 ConstrainablePoint2d {
1615 vars: crate::front::Point2d {
1616 x: SketchVarId(0),
1617 y: SketchVarId(0),
1618 },
1619 object_id: ObjectId(0),
1620 },
1621 ConstrainablePoint2d {
1622 vars: crate::front::Point2d {
1623 x: SketchVarId(0),
1624 y: SketchVarId(0),
1625 },
1626 object_id: ObjectId(0),
1627 },
1628 ]);
1629 let function_name = dummy_constraint.name();
1630
1631 let KclValue::Segment { value: seg } = segment else {
1632 return Err(KclError::new_semantic(KclErrorDetails::new(
1633 format!("{}() argument must be a segment", function_name),
1634 vec![source_range],
1635 )));
1636 };
1637 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1638 return Err(KclError::new_semantic(KclErrorDetails::new(
1639 "segment must be unsolved".to_owned(),
1640 vec![source_range],
1641 )));
1642 };
1643 match &unsolved.kind {
1644 UnsolvedSegmentKind::Arc {
1645 center,
1646 start,
1647 center_object_id,
1648 start_object_id,
1649 ..
1650 }
1651 | UnsolvedSegmentKind::Circle {
1652 center,
1653 start,
1654 center_object_id,
1655 start_object_id,
1656 ..
1657 } => {
1658 match (¢er[0], ¢er[1], &start[0], &start[1]) {
1660 (
1661 UnsolvedExpr::Unknown(center_x),
1662 UnsolvedExpr::Unknown(center_y),
1663 UnsolvedExpr::Unknown(start_x),
1664 UnsolvedExpr::Unknown(start_y),
1665 ) => {
1666 let sketch_constraint = SketchConstraint {
1668 kind: constraint_kind([
1669 ConstrainablePoint2d {
1670 vars: crate::front::Point2d {
1671 x: *center_x,
1672 y: *center_y,
1673 },
1674 object_id: *center_object_id,
1675 },
1676 ConstrainablePoint2d {
1677 vars: crate::front::Point2d {
1678 x: *start_x,
1679 y: *start_y,
1680 },
1681 object_id: *start_object_id,
1682 },
1683 ]),
1684 meta: vec![source_range.into()],
1685 };
1686 Ok(sketch_constraint)
1687 }
1688 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1689 format!(
1690 "unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
1691 function_name
1692 ),
1693 vec![source_range],
1694 ))),
1695 }
1696 }
1697 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1698 format!("{}() argument must be an arc or circle segment", function_name),
1699 vec![source_range],
1700 ))),
1701 }
1702}
1703
1704pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1705 let segment: KclValue =
1706 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1707
1708 create_circular_radius_constraint(
1709 segment,
1710 |points| SketchConstraintKind::Radius { points },
1711 args.source_range,
1712 )
1713 .map(|constraint| KclValue::SketchConstraint {
1714 value: Box::new(constraint),
1715 })
1716}
1717
1718pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1719 let segment: KclValue =
1720 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1721
1722 create_circular_radius_constraint(
1723 segment,
1724 |points| SketchConstraintKind::Diameter { points },
1725 args.source_range,
1726 )
1727 .map(|constraint| KclValue::SketchConstraint {
1728 value: Box::new(constraint),
1729 })
1730}
1731
1732pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1733 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1734 "points",
1735 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1736 exec_state,
1737 )?;
1738 let [p1, p2] = points.as_slice() else {
1739 return Err(KclError::new_semantic(KclErrorDetails::new(
1740 "must have two input points".to_owned(),
1741 vec![args.source_range],
1742 )));
1743 };
1744 match (p1, p2) {
1745 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1746 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1747 return Err(KclError::new_semantic(KclErrorDetails::new(
1748 "first point must be an unsolved segment".to_owned(),
1749 vec![args.source_range],
1750 )));
1751 };
1752 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1753 return Err(KclError::new_semantic(KclErrorDetails::new(
1754 "second point must be an unsolved segment".to_owned(),
1755 vec![args.source_range],
1756 )));
1757 };
1758 match (&unsolved0.kind, &unsolved1.kind) {
1759 (
1760 UnsolvedSegmentKind::Point { position: pos0, .. },
1761 UnsolvedSegmentKind::Point { position: pos1, .. },
1762 ) => {
1763 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1766 (
1767 UnsolvedExpr::Unknown(p0_x),
1768 UnsolvedExpr::Unknown(p0_y),
1769 UnsolvedExpr::Unknown(p1_x),
1770 UnsolvedExpr::Unknown(p1_y),
1771 ) => {
1772 let sketch_constraint = SketchConstraint {
1774 kind: SketchConstraintKind::HorizontalDistance {
1775 points: [
1776 ConstrainablePoint2d {
1777 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1778 object_id: unsolved0.object_id,
1779 },
1780 ConstrainablePoint2d {
1781 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1782 object_id: unsolved1.object_id,
1783 },
1784 ],
1785 },
1786 meta: vec![args.source_range.into()],
1787 };
1788 Ok(KclValue::SketchConstraint {
1789 value: Box::new(sketch_constraint),
1790 })
1791 }
1792 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1793 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
1794 .to_owned(),
1795 vec![args.source_range],
1796 ))),
1797 }
1798 }
1799 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1800 "horizontalDistance() arguments must be unsolved points".to_owned(),
1801 vec![args.source_range],
1802 ))),
1803 }
1804 }
1805 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1806 "horizontalDistance() arguments must be point segments".to_owned(),
1807 vec![args.source_range],
1808 ))),
1809 }
1810}
1811
1812pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1813 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1814 "points",
1815 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1816 exec_state,
1817 )?;
1818 let [p1, p2] = points.as_slice() else {
1819 return Err(KclError::new_semantic(KclErrorDetails::new(
1820 "must have two input points".to_owned(),
1821 vec![args.source_range],
1822 )));
1823 };
1824 match (p1, p2) {
1825 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1826 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1827 return Err(KclError::new_semantic(KclErrorDetails::new(
1828 "first point must be an unsolved segment".to_owned(),
1829 vec![args.source_range],
1830 )));
1831 };
1832 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1833 return Err(KclError::new_semantic(KclErrorDetails::new(
1834 "second point must be an unsolved segment".to_owned(),
1835 vec![args.source_range],
1836 )));
1837 };
1838 match (&unsolved0.kind, &unsolved1.kind) {
1839 (
1840 UnsolvedSegmentKind::Point { position: pos0, .. },
1841 UnsolvedSegmentKind::Point { position: pos1, .. },
1842 ) => {
1843 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1846 (
1847 UnsolvedExpr::Unknown(p0_x),
1848 UnsolvedExpr::Unknown(p0_y),
1849 UnsolvedExpr::Unknown(p1_x),
1850 UnsolvedExpr::Unknown(p1_y),
1851 ) => {
1852 let sketch_constraint = SketchConstraint {
1854 kind: SketchConstraintKind::VerticalDistance {
1855 points: [
1856 ConstrainablePoint2d {
1857 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1858 object_id: unsolved0.object_id,
1859 },
1860 ConstrainablePoint2d {
1861 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1862 object_id: unsolved1.object_id,
1863 },
1864 ],
1865 },
1866 meta: vec![args.source_range.into()],
1867 };
1868 Ok(KclValue::SketchConstraint {
1869 value: Box::new(sketch_constraint),
1870 })
1871 }
1872 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1873 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
1874 .to_owned(),
1875 vec![args.source_range],
1876 ))),
1877 }
1878 }
1879 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1880 "verticalDistance() arguments must be unsolved points".to_owned(),
1881 vec![args.source_range],
1882 ))),
1883 }
1884 }
1885 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1886 "verticalDistance() arguments must be point segments".to_owned(),
1887 vec![args.source_range],
1888 ))),
1889 }
1890}
1891
1892pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1893 #[derive(Clone, Copy)]
1894 struct ConstrainableLine {
1895 solver_line: DatumLineSegment,
1896 #[cfg(feature = "artifact-graph")]
1897 object_id: ObjectId,
1898 }
1899
1900 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1901 "lines",
1902 &RuntimeType::Array(
1903 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
1904 ArrayLen::Minimum(2),
1905 ),
1906 exec_state,
1907 )?;
1908 let range = args.source_range;
1909 let constrainable_lines: Vec<ConstrainableLine> = lines
1910 .iter()
1911 .map(|line| {
1912 let KclValue::Segment { value: segment } = line else {
1913 return Err(KclError::new_semantic(KclErrorDetails::new(
1914 "line argument must be a Segment".to_owned(),
1915 vec![args.source_range],
1916 )));
1917 };
1918 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1919 return Err(KclError::new_internal(KclErrorDetails::new(
1920 "line must be an unsolved Segment".to_owned(),
1921 vec![args.source_range],
1922 )));
1923 };
1924 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1925 return Err(KclError::new_semantic(KclErrorDetails::new(
1926 "line argument must be a line, no other type of Segment".to_owned(),
1927 vec![args.source_range],
1928 )));
1929 };
1930 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
1931 return Err(KclError::new_semantic(KclErrorDetails::new(
1932 "line's start x coordinate must be a var".to_owned(),
1933 vec![args.source_range],
1934 )));
1935 };
1936 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
1937 return Err(KclError::new_semantic(KclErrorDetails::new(
1938 "line's start y coordinate must be a var".to_owned(),
1939 vec![args.source_range],
1940 )));
1941 };
1942 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
1943 return Err(KclError::new_semantic(KclErrorDetails::new(
1944 "line's end x coordinate must be a var".to_owned(),
1945 vec![args.source_range],
1946 )));
1947 };
1948 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
1949 return Err(KclError::new_semantic(KclErrorDetails::new(
1950 "line's end y coordinate must be a var".to_owned(),
1951 vec![args.source_range],
1952 )));
1953 };
1954
1955 let solver_line_p0 =
1956 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
1957 let solver_line_p1 =
1958 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
1959
1960 Ok(ConstrainableLine {
1961 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
1962 #[cfg(feature = "artifact-graph")]
1963 object_id: unsolved.object_id,
1964 })
1965 })
1966 .collect::<Result<_, _>>()?;
1967
1968 #[cfg(feature = "artifact-graph")]
1969 let constraint_id = exec_state.next_object_id();
1970 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1972 return Err(KclError::new_semantic(KclErrorDetails::new(
1973 "equalLength() can only be used inside a sketch block".to_owned(),
1974 vec![args.source_range],
1975 )));
1976 };
1977 let first_line = constrainable_lines[0];
1978 for line in constrainable_lines.iter().skip(1) {
1979 sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
1980 first_line.solver_line,
1981 line.solver_line,
1982 ));
1983 }
1984 #[cfg(feature = "artifact-graph")]
1985 {
1986 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
1987 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
1988 });
1989 sketch_state.sketch_constraints.push(constraint_id);
1990 track_constraint(constraint_id, constraint, exec_state, &args);
1991 }
1992 Ok(KclValue::none())
1993}
1994
1995pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1996 #[derive(Debug, Clone, Copy)]
1997 struct ConstrainableLineVars {
1998 start: [SketchVarId; 2],
1999 end: [SketchVarId; 2],
2000 }
2001
2002 #[derive(Debug, Clone, Copy)]
2003 struct ConstrainableCircularVars {
2004 center: [SketchVarId; 2],
2005 start: [SketchVarId; 2],
2006 end: Option<[SketchVarId; 2]>,
2007 }
2008
2009 #[derive(Debug, Clone, Copy)]
2010 enum TangentInput {
2011 Line(ConstrainableLineVars),
2012 Circular(ConstrainableCircularVars),
2013 }
2014
2015 fn extract_tangent_input(
2016 segment_value: &KclValue,
2017 range: crate::SourceRange,
2018 ) -> Result<(TangentInput, ObjectId), KclError> {
2019 let KclValue::Segment { value: segment } = segment_value else {
2020 return Err(KclError::new_semantic(KclErrorDetails::new(
2021 "tangent() arguments must be segments".to_owned(),
2022 vec![range],
2023 )));
2024 };
2025 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2026 return Err(KclError::new_semantic(KclErrorDetails::new(
2027 "tangent() arguments must be unsolved segments".to_owned(),
2028 vec![range],
2029 )));
2030 };
2031 match &unsolved.kind {
2032 UnsolvedSegmentKind::Line { start, end, .. } => {
2033 let (
2034 UnsolvedExpr::Unknown(start_x),
2035 UnsolvedExpr::Unknown(start_y),
2036 UnsolvedExpr::Unknown(end_x),
2037 UnsolvedExpr::Unknown(end_y),
2038 ) = (&start[0], &start[1], &end[0], &end[1])
2039 else {
2040 return Err(KclError::new_semantic(KclErrorDetails::new(
2041 "line coordinates must be sketch vars for tangent()".to_owned(),
2042 vec![range],
2043 )));
2044 };
2045 Ok((
2046 TangentInput::Line(ConstrainableLineVars {
2047 start: [*start_x, *start_y],
2048 end: [*end_x, *end_y],
2049 }),
2050 unsolved.object_id,
2051 ))
2052 }
2053 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
2054 let (
2055 UnsolvedExpr::Unknown(center_x),
2056 UnsolvedExpr::Unknown(center_y),
2057 UnsolvedExpr::Unknown(start_x),
2058 UnsolvedExpr::Unknown(start_y),
2059 UnsolvedExpr::Unknown(end_x),
2060 UnsolvedExpr::Unknown(end_y),
2061 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
2062 else {
2063 return Err(KclError::new_semantic(KclErrorDetails::new(
2064 "arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
2065 vec![range],
2066 )));
2067 };
2068 Ok((
2069 TangentInput::Circular(ConstrainableCircularVars {
2070 center: [*center_x, *center_y],
2071 start: [*start_x, *start_y],
2072 end: Some([*end_x, *end_y]),
2073 }),
2074 unsolved.object_id,
2075 ))
2076 }
2077 UnsolvedSegmentKind::Circle { center, start, .. } => {
2078 let (
2079 UnsolvedExpr::Unknown(center_x),
2080 UnsolvedExpr::Unknown(center_y),
2081 UnsolvedExpr::Unknown(start_x),
2082 UnsolvedExpr::Unknown(start_y),
2083 ) = (¢er[0], ¢er[1], &start[0], &start[1])
2084 else {
2085 return Err(KclError::new_semantic(KclErrorDetails::new(
2086 "circle center/start coordinates must be sketch vars for tangent()".to_owned(),
2087 vec![range],
2088 )));
2089 };
2090 Ok((
2091 TangentInput::Circular(ConstrainableCircularVars {
2092 center: [*center_x, *center_y],
2093 start: [*start_x, *start_y],
2094 end: None,
2095 }),
2096 unsolved.object_id,
2097 ))
2098 }
2099 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2100 "tangent() supports only line, arc, and circle segments".to_owned(),
2101 vec![range],
2102 ))),
2103 }
2104 }
2105
2106 fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
2107 Ok(DatumPoint::new_xy(
2108 coords[0].to_constraint_id(range)?,
2109 coords[1].to_constraint_id(range)?,
2110 ))
2111 }
2112
2113 fn sketch_var_initial_value(
2114 sketch_vars: &[KclValue],
2115 id: SketchVarId,
2116 exec_state: &mut ExecState,
2117 range: crate::SourceRange,
2118 ) -> Result<f64, KclError> {
2119 sketch_vars
2120 .get(id.0)
2121 .and_then(KclValue::as_sketch_var)
2122 .map(|sketch_var| {
2123 sketch_var
2124 .initial_value_to_solver_units(exec_state, range, "tangent() hidden radius initial value")
2125 .map(|value| value.n)
2126 })
2127 .transpose()?
2128 .ok_or_else(|| {
2129 KclError::new_internal(KclErrorDetails::new(
2130 format!("Missing sketch variable initial value for id {}", id.0),
2131 vec![range],
2132 ))
2133 })
2134 }
2135
2136 fn radius_guess(
2137 sketch_vars: &[KclValue],
2138 center: [SketchVarId; 2],
2139 point: [SketchVarId; 2],
2140 exec_state: &mut ExecState,
2141 range: crate::SourceRange,
2142 ) -> Result<f64, KclError> {
2143 let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
2144 - sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
2145 let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
2146 - sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
2147 Ok(dx.hypot(dy))
2148 }
2149
2150 fn point_initial_position(
2151 sketch_vars: &[KclValue],
2152 point: [SketchVarId; 2],
2153 exec_state: &mut ExecState,
2154 range: crate::SourceRange,
2155 ) -> Result<[f64; 2], KclError> {
2156 Ok([
2157 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
2158 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
2159 ])
2160 }
2161
2162 fn canonicalize_line_for_tangent(
2163 sketch_vars: &[KclValue],
2164 line: ConstrainableLineVars,
2165 arc_center: [SketchVarId; 2],
2166 exec_state: &mut ExecState,
2167 range: crate::SourceRange,
2168 ) -> Result<ConstrainableLineVars, KclError> {
2169 let [sx, sy] = point_initial_position(sketch_vars, line.start, exec_state, range)?;
2170 let [ex, ey] = point_initial_position(sketch_vars, line.end, exec_state, range)?;
2171 let [cx, cy] = point_initial_position(sketch_vars, arc_center, exec_state, range)?;
2172
2173 let signed_side = (ex - sx) * (cy - sy) - (ey - sy) * (cx - sx);
2177 if signed_side < -1e-9 {
2178 Ok(ConstrainableLineVars {
2179 start: line.end,
2180 end: line.start,
2181 })
2182 } else {
2183 Ok(line)
2184 }
2185 }
2186
2187 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
2188 "input",
2189 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2190 exec_state,
2191 )?;
2192 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
2193 KclError::new_semantic(KclErrorDetails::new(
2194 "tangent() requires exactly 2 input segments".to_owned(),
2195 vec![args.source_range],
2196 ))
2197 })?;
2198 let range = args.source_range;
2199 let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
2200 let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
2201 #[cfg(not(feature = "artifact-graph"))]
2202 let _ = (input0_object_id, input1_object_id);
2203
2204 enum TangentCase {
2205 LineCircular(ConstrainableLineVars, ConstrainableCircularVars),
2206 CircularCircular(ConstrainableCircularVars, ConstrainableCircularVars),
2207 }
2208 let tangent_case = match (input0, input1) {
2209 (TangentInput::Line(line), TangentInput::Circular(circular))
2210 | (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
2211 (TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
2212 TangentCase::CircularCircular(circular0, circular1)
2213 }
2214 (TangentInput::Line(_), TangentInput::Line(_)) => {
2215 return Err(KclError::new_semantic(KclErrorDetails::new(
2216 "tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
2217 vec![range],
2218 )));
2219 }
2220 };
2221
2222 let sketch_var_ty = solver_numeric_type(exec_state);
2223 #[cfg(feature = "artifact-graph")]
2224 let constraint_id = exec_state.next_object_id();
2225
2226 let sketch_vars = {
2227 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2228 return Err(KclError::new_semantic(KclErrorDetails::new(
2229 "tangent() can only be used inside a sketch block".to_owned(),
2230 vec![range],
2231 )));
2232 };
2233 sketch_state.sketch_vars.clone()
2234 };
2235
2236 match tangent_case {
2238 TangentCase::LineCircular(line, circular) => {
2239 let canonical_line = canonicalize_line_for_tangent(&sketch_vars, line, circular.center, exec_state, range)?;
2240 let line_p0 = datum_point(canonical_line.start, range)?;
2241 let line_p1 = datum_point(canonical_line.end, range)?;
2242 let line_datum = DatumLineSegment::new(line_p0, line_p1);
2243
2244 let center = datum_point(circular.center, range)?;
2245 let circular_start = datum_point(circular.start, range)?;
2246 let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
2247 let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
2248 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2249 return Err(KclError::new_semantic(KclErrorDetails::new(
2250 "tangent() can only be used inside a sketch block".to_owned(),
2251 vec![range],
2252 )));
2253 };
2254 let radius_id = sketch_state.next_sketch_var_id();
2255 sketch_state.sketch_vars.push(KclValue::SketchVar {
2256 value: Box::new(crate::execution::SketchVar {
2257 id: radius_id,
2258 initial_value: radius_initial_value,
2259 ty: sketch_var_ty,
2260 meta: vec![],
2261 }),
2262 });
2263 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
2264 let circle = DatumCircle { center, radius };
2265
2266 sketch_state
2273 .solver_constraints
2274 .push(SolverConstraint::DistanceVar(circular_start, center, radius));
2275 if let Some(circular_end) = circular_end {
2276 sketch_state
2277 .solver_constraints
2278 .push(SolverConstraint::DistanceVar(circular_end, center, radius));
2279 }
2280 sketch_state
2281 .solver_constraints
2282 .push(SolverConstraint::LineTangentToCircle(line_datum, circle));
2283 }
2284 TangentCase::CircularCircular(circular0, circular1) => {
2285 let center0 = datum_point(circular0.center, range)?;
2286 let start0 = datum_point(circular0.start, range)?;
2287 let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
2288 let radius0_initial_value =
2289 radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
2290 let center1 = datum_point(circular1.center, range)?;
2291 let start1 = datum_point(circular1.start, range)?;
2292 let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
2293 let radius1_initial_value =
2294 radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
2295 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2296 return Err(KclError::new_semantic(KclErrorDetails::new(
2297 "tangent() can only be used inside a sketch block".to_owned(),
2298 vec![range],
2299 )));
2300 };
2301 let radius0_id = sketch_state.next_sketch_var_id();
2302 sketch_state.sketch_vars.push(KclValue::SketchVar {
2303 value: Box::new(crate::execution::SketchVar {
2304 id: radius0_id,
2305 initial_value: radius0_initial_value,
2306 ty: sketch_var_ty,
2307 meta: vec![],
2308 }),
2309 });
2310 let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
2311 let circle0 = DatumCircle {
2312 center: center0,
2313 radius: radius0,
2314 };
2315
2316 let radius1_id = sketch_state.next_sketch_var_id();
2317 sketch_state.sketch_vars.push(KclValue::SketchVar {
2318 value: Box::new(crate::execution::SketchVar {
2319 id: radius1_id,
2320 initial_value: radius1_initial_value,
2321 ty: sketch_var_ty,
2322 meta: vec![],
2323 }),
2324 });
2325 let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
2326 let circle1 = DatumCircle {
2327 center: center1,
2328 radius: radius1,
2329 };
2330
2331 sketch_state
2336 .solver_constraints
2337 .push(SolverConstraint::DistanceVar(start0, center0, radius0));
2338 if let Some(end0) = end0 {
2339 sketch_state
2340 .solver_constraints
2341 .push(SolverConstraint::DistanceVar(end0, center0, radius0));
2342 }
2343 sketch_state
2344 .solver_constraints
2345 .push(SolverConstraint::DistanceVar(start1, center1, radius1));
2346 if let Some(end1) = end1 {
2347 sketch_state
2348 .solver_constraints
2349 .push(SolverConstraint::DistanceVar(end1, center1, radius1));
2350 }
2351 sketch_state
2352 .solver_constraints
2353 .push(SolverConstraint::CircleTangentToCircle(circle0, circle1));
2354 }
2355 }
2356
2357 #[cfg(feature = "artifact-graph")]
2358 {
2359 let constraint = crate::front::Constraint::Tangent(Tangent {
2360 input: vec![input0_object_id, input1_object_id],
2361 });
2362 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2363 return Err(KclError::new_semantic(KclErrorDetails::new(
2364 "tangent() can only be used inside a sketch block".to_owned(),
2365 vec![range],
2366 )));
2367 };
2368 sketch_state.sketch_constraints.push(constraint_id);
2369 track_constraint(constraint_id, constraint, exec_state, &args);
2370 }
2371
2372 Ok(KclValue::none())
2373}
2374
2375#[derive(Debug, Clone, Copy)]
2376pub(crate) enum LinesAtAngleKind {
2377 Parallel,
2378 Perpendicular,
2379}
2380
2381impl LinesAtAngleKind {
2382 pub fn to_function_name(self) -> &'static str {
2383 match self {
2384 LinesAtAngleKind::Parallel => "parallel",
2385 LinesAtAngleKind::Perpendicular => "perpendicular",
2386 }
2387 }
2388
2389 fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
2390 match self {
2391 LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
2392 LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
2393 }
2394 }
2395
2396 #[cfg(feature = "artifact-graph")]
2397 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
2398 match self {
2399 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
2400 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
2401 }
2402 }
2403}
2404
2405#[expect(unused)]
2407fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
2408 kcmc::shared::Angle::from_degrees(angle.to_degrees())
2409}
2410
2411#[expect(unused)]
2413fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
2414 ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
2415}
2416
2417pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2418 lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
2419}
2420
2421pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2422 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
2423}
2424
2425pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2426 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2427 "lines",
2428 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2429 exec_state,
2430 )?;
2431 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
2432 KclError::new_semantic(KclErrorDetails::new(
2433 "must have two input lines".to_owned(),
2434 vec![args.source_range],
2435 ))
2436 })?;
2437 let KclValue::Segment { value: segment0 } = &line0 else {
2438 return Err(KclError::new_semantic(KclErrorDetails::new(
2439 "line argument must be a Segment".to_owned(),
2440 vec![args.source_range],
2441 )));
2442 };
2443 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
2444 return Err(KclError::new_internal(KclErrorDetails::new(
2445 "line must be an unsolved Segment".to_owned(),
2446 vec![args.source_range],
2447 )));
2448 };
2449 let UnsolvedSegmentKind::Line {
2450 start: start0,
2451 end: end0,
2452 ..
2453 } = &unsolved0.kind
2454 else {
2455 return Err(KclError::new_semantic(KclErrorDetails::new(
2456 "line argument must be a line, no other type of Segment".to_owned(),
2457 vec![args.source_range],
2458 )));
2459 };
2460 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
2461 return Err(KclError::new_semantic(KclErrorDetails::new(
2462 "line's start x coordinate must be a var".to_owned(),
2463 vec![args.source_range],
2464 )));
2465 };
2466 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
2467 return Err(KclError::new_semantic(KclErrorDetails::new(
2468 "line's start y coordinate must be a var".to_owned(),
2469 vec![args.source_range],
2470 )));
2471 };
2472 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
2473 return Err(KclError::new_semantic(KclErrorDetails::new(
2474 "line's end x coordinate must be a var".to_owned(),
2475 vec![args.source_range],
2476 )));
2477 };
2478 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
2479 return Err(KclError::new_semantic(KclErrorDetails::new(
2480 "line's end y coordinate must be a var".to_owned(),
2481 vec![args.source_range],
2482 )));
2483 };
2484 let KclValue::Segment { value: segment1 } = &line1 else {
2485 return Err(KclError::new_semantic(KclErrorDetails::new(
2486 "line argument must be a Segment".to_owned(),
2487 vec![args.source_range],
2488 )));
2489 };
2490 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
2491 return Err(KclError::new_internal(KclErrorDetails::new(
2492 "line must be an unsolved Segment".to_owned(),
2493 vec![args.source_range],
2494 )));
2495 };
2496 let UnsolvedSegmentKind::Line {
2497 start: start1,
2498 end: end1,
2499 ..
2500 } = &unsolved1.kind
2501 else {
2502 return Err(KclError::new_semantic(KclErrorDetails::new(
2503 "line argument must be a line, no other type of Segment".to_owned(),
2504 vec![args.source_range],
2505 )));
2506 };
2507 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
2508 return Err(KclError::new_semantic(KclErrorDetails::new(
2509 "line's start x coordinate must be a var".to_owned(),
2510 vec![args.source_range],
2511 )));
2512 };
2513 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
2514 return Err(KclError::new_semantic(KclErrorDetails::new(
2515 "line's start y coordinate must be a var".to_owned(),
2516 vec![args.source_range],
2517 )));
2518 };
2519 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
2520 return Err(KclError::new_semantic(KclErrorDetails::new(
2521 "line's end x coordinate must be a var".to_owned(),
2522 vec![args.source_range],
2523 )));
2524 };
2525 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
2526 return Err(KclError::new_semantic(KclErrorDetails::new(
2527 "line's end y coordinate must be a var".to_owned(),
2528 vec![args.source_range],
2529 )));
2530 };
2531
2532 let sketch_constraint = SketchConstraint {
2534 kind: SketchConstraintKind::Angle {
2535 line0: crate::execution::ConstrainableLine2d {
2536 object_id: unsolved0.object_id,
2537 vars: [
2538 crate::front::Point2d {
2539 x: *line0_p0_x,
2540 y: *line0_p0_y,
2541 },
2542 crate::front::Point2d {
2543 x: *line0_p1_x,
2544 y: *line0_p1_y,
2545 },
2546 ],
2547 },
2548 line1: crate::execution::ConstrainableLine2d {
2549 object_id: unsolved1.object_id,
2550 vars: [
2551 crate::front::Point2d {
2552 x: *line1_p0_x,
2553 y: *line1_p0_y,
2554 },
2555 crate::front::Point2d {
2556 x: *line1_p1_x,
2557 y: *line1_p1_y,
2558 },
2559 ],
2560 },
2561 },
2562 meta: vec![args.source_range.into()],
2563 };
2564 Ok(KclValue::SketchConstraint {
2565 value: Box::new(sketch_constraint),
2566 })
2567}
2568
2569async fn lines_at_angle(
2570 angle_kind: LinesAtAngleKind,
2571 exec_state: &mut ExecState,
2572 args: Args,
2573) -> Result<KclValue, KclError> {
2574 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2575 "lines",
2576 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2577 exec_state,
2578 )?;
2579 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
2580 KclError::new_semantic(KclErrorDetails::new(
2581 "must have two input lines".to_owned(),
2582 vec![args.source_range],
2583 ))
2584 })?;
2585
2586 let KclValue::Segment { value: segment0 } = &line0 else {
2587 return Err(KclError::new_semantic(KclErrorDetails::new(
2588 "line argument must be a Segment".to_owned(),
2589 vec![args.source_range],
2590 )));
2591 };
2592 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
2593 return Err(KclError::new_internal(KclErrorDetails::new(
2594 "line must be an unsolved Segment".to_owned(),
2595 vec![args.source_range],
2596 )));
2597 };
2598 let UnsolvedSegmentKind::Line {
2599 start: start0,
2600 end: end0,
2601 ..
2602 } = &unsolved0.kind
2603 else {
2604 return Err(KclError::new_semantic(KclErrorDetails::new(
2605 "line argument must be a line, no other type of Segment".to_owned(),
2606 vec![args.source_range],
2607 )));
2608 };
2609 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
2610 return Err(KclError::new_semantic(KclErrorDetails::new(
2611 "line's start x coordinate must be a var".to_owned(),
2612 vec![args.source_range],
2613 )));
2614 };
2615 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
2616 return Err(KclError::new_semantic(KclErrorDetails::new(
2617 "line's start y coordinate must be a var".to_owned(),
2618 vec![args.source_range],
2619 )));
2620 };
2621 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
2622 return Err(KclError::new_semantic(KclErrorDetails::new(
2623 "line's end x coordinate must be a var".to_owned(),
2624 vec![args.source_range],
2625 )));
2626 };
2627 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
2628 return Err(KclError::new_semantic(KclErrorDetails::new(
2629 "line's end y coordinate must be a var".to_owned(),
2630 vec![args.source_range],
2631 )));
2632 };
2633 let KclValue::Segment { value: segment1 } = &line1 else {
2634 return Err(KclError::new_semantic(KclErrorDetails::new(
2635 "line argument must be a Segment".to_owned(),
2636 vec![args.source_range],
2637 )));
2638 };
2639 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
2640 return Err(KclError::new_internal(KclErrorDetails::new(
2641 "line must be an unsolved Segment".to_owned(),
2642 vec![args.source_range],
2643 )));
2644 };
2645 let UnsolvedSegmentKind::Line {
2646 start: start1,
2647 end: end1,
2648 ..
2649 } = &unsolved1.kind
2650 else {
2651 return Err(KclError::new_semantic(KclErrorDetails::new(
2652 "line argument must be a line, no other type of Segment".to_owned(),
2653 vec![args.source_range],
2654 )));
2655 };
2656 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
2657 return Err(KclError::new_semantic(KclErrorDetails::new(
2658 "line's start x coordinate must be a var".to_owned(),
2659 vec![args.source_range],
2660 )));
2661 };
2662 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
2663 return Err(KclError::new_semantic(KclErrorDetails::new(
2664 "line's start y coordinate must be a var".to_owned(),
2665 vec![args.source_range],
2666 )));
2667 };
2668 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
2669 return Err(KclError::new_semantic(KclErrorDetails::new(
2670 "line's end x coordinate must be a var".to_owned(),
2671 vec![args.source_range],
2672 )));
2673 };
2674 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
2675 return Err(KclError::new_semantic(KclErrorDetails::new(
2676 "line's end y coordinate must be a var".to_owned(),
2677 vec![args.source_range],
2678 )));
2679 };
2680
2681 let range = args.source_range;
2682 let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2683 line0_p0_x.to_constraint_id(range)?,
2684 line0_p0_y.to_constraint_id(range)?,
2685 );
2686 let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2687 line0_p1_x.to_constraint_id(range)?,
2688 line0_p1_y.to_constraint_id(range)?,
2689 );
2690 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
2691 let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2692 line1_p0_x.to_constraint_id(range)?,
2693 line1_p0_y.to_constraint_id(range)?,
2694 );
2695 let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2696 line1_p1_x.to_constraint_id(range)?,
2697 line1_p1_y.to_constraint_id(range)?,
2698 );
2699 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
2700 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
2701 #[cfg(feature = "artifact-graph")]
2702 let constraint_id = exec_state.next_object_id();
2703 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2705 return Err(KclError::new_semantic(KclErrorDetails::new(
2706 format!(
2707 "{}() can only be used inside a sketch block",
2708 angle_kind.to_function_name()
2709 ),
2710 vec![args.source_range],
2711 )));
2712 };
2713 sketch_state.solver_constraints.push(constraint);
2714 #[cfg(feature = "artifact-graph")]
2715 {
2716 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
2717 sketch_state.sketch_constraints.push(constraint_id);
2718 track_constraint(constraint_id, constraint, exec_state, &args);
2719 }
2720 Ok(KclValue::none())
2721}
2722
2723pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2724 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
2725 let KclValue::Segment { value: segment } = line else {
2726 return Err(KclError::new_semantic(KclErrorDetails::new(
2727 "line argument must be a Segment".to_owned(),
2728 vec![args.source_range],
2729 )));
2730 };
2731 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2732 return Err(KclError::new_internal(KclErrorDetails::new(
2733 "line must be an unsolved Segment".to_owned(),
2734 vec![args.source_range],
2735 )));
2736 };
2737 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
2738 return Err(KclError::new_semantic(KclErrorDetails::new(
2739 "line argument must be a line, no other type of Segment".to_owned(),
2740 vec![args.source_range],
2741 )));
2742 };
2743 let p0_x = &start[0];
2744 let p0_y = &start[1];
2745 let p1_x = &end[0];
2746 let p1_y = &end[1];
2747 match (p0_x, p0_y, p1_x, p1_y) {
2748 (
2749 UnsolvedExpr::Unknown(p0_x),
2750 UnsolvedExpr::Unknown(p0_y),
2751 UnsolvedExpr::Unknown(p1_x),
2752 UnsolvedExpr::Unknown(p1_y),
2753 ) => {
2754 let range = args.source_range;
2755 let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2756 p0_x.to_constraint_id(range)?,
2757 p0_y.to_constraint_id(range)?,
2758 );
2759 let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2760 p1_x.to_constraint_id(range)?,
2761 p1_y.to_constraint_id(range)?,
2762 );
2763 let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
2764 let constraint = ezpz::Constraint::Horizontal(solver_line);
2765 #[cfg(feature = "artifact-graph")]
2766 let constraint_id = exec_state.next_object_id();
2767 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2769 return Err(KclError::new_semantic(KclErrorDetails::new(
2770 "horizontal() can only be used inside a sketch block".to_owned(),
2771 vec![args.source_range],
2772 )));
2773 };
2774 sketch_state.solver_constraints.push(constraint);
2775 #[cfg(feature = "artifact-graph")]
2776 {
2777 let constraint = crate::front::Constraint::Horizontal(Horizontal {
2778 line: unsolved.object_id,
2779 });
2780 sketch_state.sketch_constraints.push(constraint_id);
2781 track_constraint(constraint_id, constraint, exec_state, &args);
2782 }
2783 Ok(KclValue::none())
2784 }
2785 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2786 "line's x and y coordinates of both start and end must be vars".to_owned(),
2787 vec![args.source_range],
2788 ))),
2789 }
2790}
2791
2792pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2793 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
2794 let KclValue::Segment { value: segment } = line else {
2795 return Err(KclError::new_semantic(KclErrorDetails::new(
2796 "line argument must be a Segment".to_owned(),
2797 vec![args.source_range],
2798 )));
2799 };
2800 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2801 return Err(KclError::new_internal(KclErrorDetails::new(
2802 "line must be an unsolved Segment".to_owned(),
2803 vec![args.source_range],
2804 )));
2805 };
2806 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
2807 return Err(KclError::new_semantic(KclErrorDetails::new(
2808 "line argument must be a line, no other type of Segment".to_owned(),
2809 vec![args.source_range],
2810 )));
2811 };
2812 let p0_x = &start[0];
2813 let p0_y = &start[1];
2814 let p1_x = &end[0];
2815 let p1_y = &end[1];
2816 match (p0_x, p0_y, p1_x, p1_y) {
2817 (
2818 UnsolvedExpr::Unknown(p0_x),
2819 UnsolvedExpr::Unknown(p0_y),
2820 UnsolvedExpr::Unknown(p1_x),
2821 UnsolvedExpr::Unknown(p1_y),
2822 ) => {
2823 let range = args.source_range;
2824 let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2825 p0_x.to_constraint_id(range)?,
2826 p0_y.to_constraint_id(range)?,
2827 );
2828 let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2829 p1_x.to_constraint_id(range)?,
2830 p1_y.to_constraint_id(range)?,
2831 );
2832 let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
2833 let constraint = ezpz::Constraint::Vertical(solver_line);
2834 #[cfg(feature = "artifact-graph")]
2835 let constraint_id = exec_state.next_object_id();
2836 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2838 return Err(KclError::new_semantic(KclErrorDetails::new(
2839 "vertical() can only be used inside a sketch block".to_owned(),
2840 vec![args.source_range],
2841 )));
2842 };
2843 sketch_state.solver_constraints.push(constraint);
2844 #[cfg(feature = "artifact-graph")]
2845 {
2846 let constraint = crate::front::Constraint::Vertical(Vertical {
2847 line: unsolved.object_id,
2848 });
2849 sketch_state.sketch_constraints.push(constraint_id);
2850 track_constraint(constraint_id, constraint, exec_state, &args);
2851 }
2852 Ok(KclValue::none())
2853 }
2854 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2855 "line's x and y coordinates of both start and end must be vars".to_owned(),
2856 vec![args.source_range],
2857 ))),
2858 }
2859}