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