1use anyhow::Result;
2use kcl_ezpz::{
3 Constraint as SolverConstraint,
4 datatypes::{
5 AngleKind,
6 inputs::{DatumCircularArc, DatumLineSegment, DatumPoint},
7 },
8};
9
10use crate::{
11 errors::{KclError, KclErrorDetails},
12 execution::{
13 AbstractSegment, ConstrainablePoint2d, ExecState, KclValue, SegmentRepr, SketchConstraint,
14 SketchConstraintKind, SketchVarId, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind,
15 normalize_to_solver_unit,
16 types::{ArrayLen, PrimitiveType, RuntimeType},
17 },
18 front::{ArcCtor, LineCtor, ObjectId, Point2d, PointCtor},
19 std::Args,
20};
21#[cfg(feature = "artifact-graph")]
22use crate::{
23 execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
24 front::{
25 Coincident, Constraint, Horizontal, LinesEqualLength, Object, ObjectKind, Parallel, Perpendicular, Vertical,
26 },
27};
28
29pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
30 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
31 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
32 KclError::new_semantic(KclErrorDetails::new(
33 "at must be a 2D point".to_owned(),
34 vec![args.source_range],
35 ))
36 })?;
37 let Some(at_x) = at_x_value.as_unsolved_expr() else {
38 return Err(KclError::new_semantic(KclErrorDetails::new(
39 "at x must be a number or sketch var".to_owned(),
40 vec![args.source_range],
41 )));
42 };
43 let Some(at_y) = at_y_value.as_unsolved_expr() else {
44 return Err(KclError::new_semantic(KclErrorDetails::new(
45 "at y must be a number or sketch var".to_owned(),
46 vec![args.source_range],
47 )));
48 };
49 let ctor = PointCtor {
50 position: Point2d {
51 x: at_x_value.to_sketch_expr().ok_or_else(|| {
52 KclError::new_semantic(KclErrorDetails::new(
53 "unable to convert numeric type to suffix".to_owned(),
54 vec![args.source_range],
55 ))
56 })?,
57 y: at_y_value.to_sketch_expr().ok_or_else(|| {
58 KclError::new_semantic(KclErrorDetails::new(
59 "unable to convert numeric type to suffix".to_owned(),
60 vec![args.source_range],
61 ))
62 })?,
63 },
64 };
65 let segment = UnsolvedSegment {
66 id: exec_state.next_uuid(),
67 object_id: exec_state.next_object_id(),
68 kind: UnsolvedSegmentKind::Point {
69 position: [at_x, at_y],
70 ctor: Box::new(ctor),
71 },
72 meta: vec![args.source_range.into()],
73 };
74 #[cfg(feature = "artifact-graph")]
75 let optional_constraints = {
76 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
77
78 let mut optional_constraints = Vec::new();
79 if exec_state.segment_ids_edited_contains(&object_id) {
80 if let Some(at_x_var) = at_x_value.as_sketch_var() {
81 let x_initial_value = at_x_var.initial_value_to_solver_units(
82 exec_state,
83 args.source_range,
84 "edited segment fixed constraint value",
85 )?;
86 optional_constraints.push(SolverConstraint::Fixed(
87 at_x_var.id.to_constraint_id(args.source_range)?,
88 x_initial_value.n,
89 ));
90 }
91 if let Some(at_y_var) = at_y_value.as_sketch_var() {
92 let y_initial_value = at_y_var.initial_value_to_solver_units(
93 exec_state,
94 args.source_range,
95 "edited segment fixed constraint value",
96 )?;
97 optional_constraints.push(SolverConstraint::Fixed(
98 at_y_var.id.to_constraint_id(args.source_range)?,
99 y_initial_value.n,
100 ));
101 }
102 }
103 optional_constraints
104 };
105
106 let Some(sketch_state) = exec_state.sketch_block_mut() else {
108 return Err(KclError::new_semantic(KclErrorDetails::new(
109 "line() can only be used inside a sketch block".to_owned(),
110 vec![args.source_range],
111 )));
112 };
113 sketch_state.needed_by_engine.push(segment.clone());
114
115 #[cfg(feature = "artifact-graph")]
116 sketch_state.solver_optional_constraints.extend(optional_constraints);
117
118 let meta = segment.meta.clone();
119 let abstract_segment = AbstractSegment {
120 repr: SegmentRepr::Unsolved { segment },
121 meta,
122 };
123 Ok(KclValue::Segment {
124 value: Box::new(abstract_segment),
125 })
126}
127
128pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
129 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
130 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
132 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
133 let construction: bool = construction_opt.unwrap_or(false);
134 let construction_ctor = construction_opt;
135 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
136 KclError::new_semantic(KclErrorDetails::new(
137 "start must be a 2D point".to_owned(),
138 vec![args.source_range],
139 ))
140 })?;
141 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
142 KclError::new_semantic(KclErrorDetails::new(
143 "end must be a 2D point".to_owned(),
144 vec![args.source_range],
145 ))
146 })?;
147 let Some(start_x) = start_x_value.as_unsolved_expr() else {
148 return Err(KclError::new_semantic(KclErrorDetails::new(
149 "start x must be a number or sketch var".to_owned(),
150 vec![args.source_range],
151 )));
152 };
153 let Some(start_y) = start_y_value.as_unsolved_expr() else {
154 return Err(KclError::new_semantic(KclErrorDetails::new(
155 "start y must be a number or sketch var".to_owned(),
156 vec![args.source_range],
157 )));
158 };
159 let Some(end_x) = end_x_value.as_unsolved_expr() else {
160 return Err(KclError::new_semantic(KclErrorDetails::new(
161 "end x must be a number or sketch var".to_owned(),
162 vec![args.source_range],
163 )));
164 };
165 let Some(end_y) = end_y_value.as_unsolved_expr() else {
166 return Err(KclError::new_semantic(KclErrorDetails::new(
167 "end y must be a number or sketch var".to_owned(),
168 vec![args.source_range],
169 )));
170 };
171 let ctor = LineCtor {
172 start: Point2d {
173 x: start_x_value.to_sketch_expr().ok_or_else(|| {
174 KclError::new_semantic(KclErrorDetails::new(
175 "unable to convert numeric type to suffix".to_owned(),
176 vec![args.source_range],
177 ))
178 })?,
179 y: start_y_value.to_sketch_expr().ok_or_else(|| {
180 KclError::new_semantic(KclErrorDetails::new(
181 "unable to convert numeric type to suffix".to_owned(),
182 vec![args.source_range],
183 ))
184 })?,
185 },
186 end: Point2d {
187 x: end_x_value.to_sketch_expr().ok_or_else(|| {
188 KclError::new_semantic(KclErrorDetails::new(
189 "unable to convert numeric type to suffix".to_owned(),
190 vec![args.source_range],
191 ))
192 })?,
193 y: end_y_value.to_sketch_expr().ok_or_else(|| {
194 KclError::new_semantic(KclErrorDetails::new(
195 "unable to convert numeric type to suffix".to_owned(),
196 vec![args.source_range],
197 ))
198 })?,
199 },
200 construction: construction_ctor,
201 };
202 let start_object_id = exec_state.next_object_id();
204 let end_object_id = exec_state.next_object_id();
205 let line_object_id = exec_state.next_object_id();
206 let segment = UnsolvedSegment {
207 id: exec_state.next_uuid(),
208 object_id: line_object_id,
209 kind: UnsolvedSegmentKind::Line {
210 start: [start_x, start_y],
211 end: [end_x, end_y],
212 ctor: Box::new(ctor),
213 start_object_id,
214 end_object_id,
215 construction,
216 },
217 meta: vec![args.source_range.into()],
218 };
219 #[cfg(feature = "artifact-graph")]
220 let optional_constraints = {
221 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
222 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
223 let line_object_id = exec_state.add_placeholder_scene_object(line_object_id, args.source_range);
224
225 let mut optional_constraints = Vec::new();
226 if exec_state.segment_ids_edited_contains(&start_object_id)
227 || exec_state.segment_ids_edited_contains(&line_object_id)
228 {
229 if let Some(start_x_var) = start_x_value.as_sketch_var() {
230 let x_initial_value = start_x_var.initial_value_to_solver_units(
231 exec_state,
232 args.source_range,
233 "edited segment fixed constraint value",
234 )?;
235 optional_constraints.push(SolverConstraint::Fixed(
236 start_x_var.id.to_constraint_id(args.source_range)?,
237 x_initial_value.n,
238 ));
239 }
240 if let Some(start_y_var) = start_y_value.as_sketch_var() {
241 let y_initial_value = start_y_var.initial_value_to_solver_units(
242 exec_state,
243 args.source_range,
244 "edited segment fixed constraint value",
245 )?;
246 optional_constraints.push(SolverConstraint::Fixed(
247 start_y_var.id.to_constraint_id(args.source_range)?,
248 y_initial_value.n,
249 ));
250 }
251 }
252 if exec_state.segment_ids_edited_contains(&end_object_id)
253 || exec_state.segment_ids_edited_contains(&line_object_id)
254 {
255 if let Some(end_x_var) = end_x_value.as_sketch_var() {
256 let x_initial_value = end_x_var.initial_value_to_solver_units(
257 exec_state,
258 args.source_range,
259 "edited segment fixed constraint value",
260 )?;
261 optional_constraints.push(SolverConstraint::Fixed(
262 end_x_var.id.to_constraint_id(args.source_range)?,
263 x_initial_value.n,
264 ));
265 }
266 if let Some(end_y_var) = end_y_value.as_sketch_var() {
267 let y_initial_value = end_y_var.initial_value_to_solver_units(
268 exec_state,
269 args.source_range,
270 "edited segment fixed constraint value",
271 )?;
272 optional_constraints.push(SolverConstraint::Fixed(
273 end_y_var.id.to_constraint_id(args.source_range)?,
274 y_initial_value.n,
275 ));
276 }
277 }
278 optional_constraints
279 };
280
281 let Some(sketch_state) = exec_state.sketch_block_mut() else {
283 return Err(KclError::new_semantic(KclErrorDetails::new(
284 "line() can only be used inside a sketch block".to_owned(),
285 vec![args.source_range],
286 )));
287 };
288 sketch_state.needed_by_engine.push(segment.clone());
289
290 #[cfg(feature = "artifact-graph")]
291 sketch_state.solver_optional_constraints.extend(optional_constraints);
292
293 let meta = segment.meta.clone();
294 let abstract_segment = AbstractSegment {
295 repr: SegmentRepr::Unsolved { segment },
296 meta,
297 };
298 Ok(KclValue::Segment {
299 value: Box::new(abstract_segment),
300 })
301}
302
303pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
304 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
305 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
306 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
308 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
309 let construction: bool = construction_opt.unwrap_or(false);
310 let construction_ctor = construction_opt;
311
312 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
313 KclError::new_semantic(KclErrorDetails::new(
314 "start must be a 2D point".to_owned(),
315 vec![args.source_range],
316 ))
317 })?;
318 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
319 KclError::new_semantic(KclErrorDetails::new(
320 "end must be a 2D point".to_owned(),
321 vec![args.source_range],
322 ))
323 })?;
324 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
325 KclError::new_semantic(KclErrorDetails::new(
326 "center must be a 2D point".to_owned(),
327 vec![args.source_range],
328 ))
329 })?;
330
331 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
332 return Err(KclError::new_semantic(KclErrorDetails::new(
333 "start x must be a sketch var".to_owned(),
334 vec![args.source_range],
335 )));
336 };
337 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
338 return Err(KclError::new_semantic(KclErrorDetails::new(
339 "start y must be a sketch var".to_owned(),
340 vec![args.source_range],
341 )));
342 };
343 let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
344 return Err(KclError::new_semantic(KclErrorDetails::new(
345 "end x must be a sketch var".to_owned(),
346 vec![args.source_range],
347 )));
348 };
349 let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
350 return Err(KclError::new_semantic(KclErrorDetails::new(
351 "end y must be a sketch var".to_owned(),
352 vec![args.source_range],
353 )));
354 };
355 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
356 return Err(KclError::new_semantic(KclErrorDetails::new(
357 "center x must be a sketch var".to_owned(),
358 vec![args.source_range],
359 )));
360 };
361 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
362 return Err(KclError::new_semantic(KclErrorDetails::new(
363 "center y must be a sketch var".to_owned(),
364 vec![args.source_range],
365 )));
366 };
367
368 let ctor = ArcCtor {
369 start: Point2d {
370 x: start_x_value.to_sketch_expr().ok_or_else(|| {
371 KclError::new_semantic(KclErrorDetails::new(
372 "unable to convert numeric type to suffix".to_owned(),
373 vec![args.source_range],
374 ))
375 })?,
376 y: start_y_value.to_sketch_expr().ok_or_else(|| {
377 KclError::new_semantic(KclErrorDetails::new(
378 "unable to convert numeric type to suffix".to_owned(),
379 vec![args.source_range],
380 ))
381 })?,
382 },
383 end: Point2d {
384 x: end_x_value.to_sketch_expr().ok_or_else(|| {
385 KclError::new_semantic(KclErrorDetails::new(
386 "unable to convert numeric type to suffix".to_owned(),
387 vec![args.source_range],
388 ))
389 })?,
390 y: end_y_value.to_sketch_expr().ok_or_else(|| {
391 KclError::new_semantic(KclErrorDetails::new(
392 "unable to convert numeric type to suffix".to_owned(),
393 vec![args.source_range],
394 ))
395 })?,
396 },
397 center: Point2d {
398 x: center_x_value.to_sketch_expr().ok_or_else(|| {
399 KclError::new_semantic(KclErrorDetails::new(
400 "unable to convert numeric type to suffix".to_owned(),
401 vec![args.source_range],
402 ))
403 })?,
404 y: center_y_value.to_sketch_expr().ok_or_else(|| {
405 KclError::new_semantic(KclErrorDetails::new(
406 "unable to convert numeric type to suffix".to_owned(),
407 vec![args.source_range],
408 ))
409 })?,
410 },
411 construction: construction_ctor,
412 };
413
414 let start_object_id = exec_state.next_object_id();
416 let end_object_id = exec_state.next_object_id();
417 let center_object_id = exec_state.next_object_id();
418 let arc_object_id = exec_state.next_object_id();
419 let segment = UnsolvedSegment {
420 id: exec_state.next_uuid(),
421 object_id: arc_object_id,
422 kind: UnsolvedSegmentKind::Arc {
423 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
424 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
425 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
426 ctor: Box::new(ctor),
427 start_object_id,
428 end_object_id,
429 center_object_id,
430 construction,
431 },
432 meta: vec![args.source_range.into()],
433 };
434 #[cfg(feature = "artifact-graph")]
435 let optional_constraints = {
436 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
437 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
438 let center_object_id = exec_state.add_placeholder_scene_object(center_object_id, args.source_range);
439 let arc_object_id = exec_state.add_placeholder_scene_object(arc_object_id, args.source_range);
440
441 let mut optional_constraints = Vec::new();
442 if exec_state.segment_ids_edited_contains(&start_object_id)
443 || exec_state.segment_ids_edited_contains(&arc_object_id)
444 {
445 if let Some(start_x_var) = start_x_value.as_sketch_var() {
446 let x_initial_value = start_x_var.initial_value_to_solver_units(
447 exec_state,
448 args.source_range,
449 "edited segment fixed constraint value",
450 )?;
451 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
452 start_x_var.id.to_constraint_id(args.source_range)?,
453 x_initial_value.n,
454 ));
455 }
456 if let Some(start_y_var) = start_y_value.as_sketch_var() {
457 let y_initial_value = start_y_var.initial_value_to_solver_units(
458 exec_state,
459 args.source_range,
460 "edited segment fixed constraint value",
461 )?;
462 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
463 start_y_var.id.to_constraint_id(args.source_range)?,
464 y_initial_value.n,
465 ));
466 }
467 }
468 if exec_state.segment_ids_edited_contains(&end_object_id)
469 || exec_state.segment_ids_edited_contains(&arc_object_id)
470 {
471 if let Some(end_x_var) = end_x_value.as_sketch_var() {
472 let x_initial_value = end_x_var.initial_value_to_solver_units(
473 exec_state,
474 args.source_range,
475 "edited segment fixed constraint value",
476 )?;
477 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
478 end_x_var.id.to_constraint_id(args.source_range)?,
479 x_initial_value.n,
480 ));
481 }
482 if let Some(end_y_var) = end_y_value.as_sketch_var() {
483 let y_initial_value = end_y_var.initial_value_to_solver_units(
484 exec_state,
485 args.source_range,
486 "edited segment fixed constraint value",
487 )?;
488 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
489 end_y_var.id.to_constraint_id(args.source_range)?,
490 y_initial_value.n,
491 ));
492 }
493 }
494 if exec_state.segment_ids_edited_contains(¢er_object_id)
495 || exec_state.segment_ids_edited_contains(&arc_object_id)
496 {
497 if let Some(center_x_var) = center_x_value.as_sketch_var() {
498 let x_initial_value = center_x_var.initial_value_to_solver_units(
499 exec_state,
500 args.source_range,
501 "edited segment fixed constraint value",
502 )?;
503 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
504 center_x_var.id.to_constraint_id(args.source_range)?,
505 x_initial_value.n,
506 ));
507 }
508 if let Some(center_y_var) = center_y_value.as_sketch_var() {
509 let y_initial_value = center_y_var.initial_value_to_solver_units(
510 exec_state,
511 args.source_range,
512 "edited segment fixed constraint value",
513 )?;
514 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
515 center_y_var.id.to_constraint_id(args.source_range)?,
516 y_initial_value.n,
517 ));
518 }
519 }
520 optional_constraints
521 };
522
523 let range = args.source_range;
525 let constraint = kcl_ezpz::Constraint::Arc(kcl_ezpz::datatypes::inputs::DatumCircularArc {
526 center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
527 center_x.to_constraint_id(range)?,
528 center_y.to_constraint_id(range)?,
529 ),
530 start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
531 start_x.to_constraint_id(range)?,
532 start_y.to_constraint_id(range)?,
533 ),
534 end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
535 end_x.to_constraint_id(range)?,
536 end_y.to_constraint_id(range)?,
537 ),
538 });
539
540 let Some(sketch_state) = exec_state.sketch_block_mut() else {
541 return Err(KclError::new_semantic(KclErrorDetails::new(
542 "arc() can only be used inside a sketch block".to_owned(),
543 vec![args.source_range],
544 )));
545 };
546 sketch_state.needed_by_engine.push(segment.clone());
548 sketch_state.solver_constraints.push(constraint);
550 #[cfg(feature = "artifact-graph")]
554 sketch_state.solver_optional_constraints.extend(optional_constraints);
555
556 let meta = segment.meta.clone();
557 let abstract_segment = AbstractSegment {
558 repr: SegmentRepr::Unsolved { segment },
559 meta,
560 };
561 Ok(KclValue::Segment {
562 value: Box::new(abstract_segment),
563 })
564}
565
566pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
567 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
568 "points",
569 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
570 exec_state,
571 )?;
572 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
573 KclError::new_semantic(KclErrorDetails::new(
574 "must have two input points".to_owned(),
575 vec![args.source_range],
576 ))
577 })?;
578
579 let range = args.source_range;
580 match (&point0, &point1) {
581 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
582 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
583 return Err(KclError::new_semantic(KclErrorDetails::new(
584 "first point must be an unsolved segment".to_owned(),
585 vec![args.source_range],
586 )));
587 };
588 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
589 return Err(KclError::new_semantic(KclErrorDetails::new(
590 "second point must be an unsolved segment".to_owned(),
591 vec![args.source_range],
592 )));
593 };
594 match (&unsolved0.kind, &unsolved1.kind) {
595 (
596 UnsolvedSegmentKind::Point { position: pos0, .. },
597 UnsolvedSegmentKind::Point { position: pos1, .. },
598 ) => {
599 let p0_x = &pos0[0];
600 let p0_y = &pos0[1];
601 match (p0_x, p0_y) {
602 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
603 let p1_x = &pos1[0];
604 let p1_y = &pos1[1];
605 match (p1_x, p1_y) {
606 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
607 let constraint = SolverConstraint::PointsCoincident(
608 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
609 p0_x.to_constraint_id(range)?,
610 p0_y.to_constraint_id(range)?,
611 ),
612 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
613 p1_x.to_constraint_id(range)?,
614 p1_y.to_constraint_id(range)?,
615 ),
616 );
617 #[cfg(feature = "artifact-graph")]
618 let constraint_id = exec_state.next_object_id();
619 let Some(sketch_state) = exec_state.sketch_block_mut() else {
621 return Err(KclError::new_semantic(KclErrorDetails::new(
622 "coincident() can only be used inside a sketch block".to_owned(),
623 vec![args.source_range],
624 )));
625 };
626 sketch_state.solver_constraints.push(constraint);
627 #[cfg(feature = "artifact-graph")]
628 {
629 let constraint = crate::front::Constraint::Coincident(Coincident {
630 segments: vec![unsolved0.object_id, unsolved1.object_id],
631 });
632 sketch_state.sketch_constraints.push(constraint_id);
633 track_constraint(constraint_id, constraint, exec_state, &args);
634 }
635 Ok(KclValue::none())
636 }
637 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
638 let p1_x = KclValue::Number {
639 value: p1_x.n,
640 ty: p1_x.ty,
641 meta: vec![args.source_range.into()],
642 };
643 let p1_y = KclValue::Number {
644 value: p1_y.n,
645 ty: p1_y.ty,
646 meta: vec![args.source_range.into()],
647 };
648 let (constraint_x, constraint_y) =
649 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
650
651 #[cfg(feature = "artifact-graph")]
652 let constraint_id = exec_state.next_object_id();
653 let Some(sketch_state) = exec_state.sketch_block_mut() else {
655 return Err(KclError::new_semantic(KclErrorDetails::new(
656 "coincident() can only be used inside a sketch block".to_owned(),
657 vec![args.source_range],
658 )));
659 };
660 sketch_state.solver_constraints.push(constraint_x);
661 sketch_state.solver_constraints.push(constraint_y);
662 #[cfg(feature = "artifact-graph")]
663 {
664 let constraint = crate::front::Constraint::Coincident(Coincident {
665 segments: vec![unsolved0.object_id, unsolved1.object_id],
666 });
667 sketch_state.sketch_constraints.push(constraint_id);
668 track_constraint(constraint_id, constraint, exec_state, &args);
669 }
670 Ok(KclValue::none())
671 }
672 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
673 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
674 Err(KclError::new_semantic(KclErrorDetails::new(
676 "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(),
677 vec![args.source_range],
678 )))
679 }
680 }
681 }
682 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
683 let p1_x = &pos1[0];
684 let p1_y = &pos1[1];
685 match (p1_x, p1_y) {
686 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
687 let p0_x = KclValue::Number {
688 value: p0_x.n,
689 ty: p0_x.ty,
690 meta: vec![args.source_range.into()],
691 };
692 let p0_y = KclValue::Number {
693 value: p0_y.n,
694 ty: p0_y.ty,
695 meta: vec![args.source_range.into()],
696 };
697 let (constraint_x, constraint_y) =
698 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
699
700 #[cfg(feature = "artifact-graph")]
701 let constraint_id = exec_state.next_object_id();
702 let Some(sketch_state) = exec_state.sketch_block_mut() else {
704 return Err(KclError::new_semantic(KclErrorDetails::new(
705 "coincident() can only be used inside a sketch block".to_owned(),
706 vec![args.source_range],
707 )));
708 };
709 sketch_state.solver_constraints.push(constraint_x);
710 sketch_state.solver_constraints.push(constraint_y);
711 #[cfg(feature = "artifact-graph")]
712 {
713 let constraint = crate::front::Constraint::Coincident(Coincident {
714 segments: vec![unsolved0.object_id, unsolved1.object_id],
715 });
716 sketch_state.sketch_constraints.push(constraint_id);
717 track_constraint(constraint_id, constraint, exec_state, &args);
718 }
719 Ok(KclValue::none())
720 }
721 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
722 if *p0_x != *p1_x || *p0_y != *p1_y {
723 return Err(KclError::new_semantic(KclErrorDetails::new(
724 "Coincident constraint between two fixed points failed since coordinates differ"
725 .to_owned(),
726 vec![args.source_range],
727 )));
728 }
729 Ok(KclValue::none())
730 }
731 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
732 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
733 Err(KclError::new_semantic(KclErrorDetails::new(
735 "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(),
736 vec![args.source_range],
737 )))
738 }
739 }
740 }
741 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
742 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
743 Err(KclError::new_semantic(KclErrorDetails::new(
745 "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(),
746 vec![args.source_range],
747 )))
748 }
749 }
750 }
751 (
753 UnsolvedSegmentKind::Point {
754 position: point_pos, ..
755 },
756 UnsolvedSegmentKind::Line {
757 start: line_start,
758 end: line_end,
759 ..
760 },
761 )
762 | (
763 UnsolvedSegmentKind::Line {
764 start: line_start,
765 end: line_end,
766 ..
767 },
768 UnsolvedSegmentKind::Point {
769 position: point_pos, ..
770 },
771 ) => {
772 let point_x = &point_pos[0];
773 let point_y = &point_pos[1];
774 match (point_x, point_y) {
775 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
776 let (start_x, start_y) = (&line_start[0], &line_start[1]);
778 let (end_x, end_y) = (&line_end[0], &line_end[1]);
779
780 match (start_x, start_y, end_x, end_y) {
781 (
782 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
783 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
784 ) => {
785 let point = DatumPoint::new_xy(
786 point_x.to_constraint_id(range)?,
787 point_y.to_constraint_id(range)?,
788 );
789 let line_segment = DatumLineSegment::new(
790 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
791 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
792 );
793 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
794
795 #[cfg(feature = "artifact-graph")]
796 let constraint_id = exec_state.next_object_id();
797
798 let Some(sketch_state) = exec_state.sketch_block_mut() else {
799 return Err(KclError::new_semantic(KclErrorDetails::new(
800 "coincident() can only be used inside a sketch block".to_owned(),
801 vec![args.source_range],
802 )));
803 };
804 sketch_state.solver_constraints.push(constraint);
805 #[cfg(feature = "artifact-graph")]
806 {
807 let constraint = crate::front::Constraint::Coincident(Coincident {
808 segments: vec![unsolved0.object_id, unsolved1.object_id],
809 });
810 sketch_state.sketch_constraints.push(constraint_id);
811 track_constraint(constraint_id, constraint, exec_state, &args);
812 }
813 Ok(KclValue::none())
814 }
815 _ => Err(KclError::new_semantic(KclErrorDetails::new(
816 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
817 vec![args.source_range],
818 ))),
819 }
820 }
821 _ => Err(KclError::new_semantic(KclErrorDetails::new(
822 "Point coordinates must be sketch variables for point-segment coincident constraint"
823 .to_owned(),
824 vec![args.source_range],
825 ))),
826 }
827 }
828 (
830 UnsolvedSegmentKind::Point {
831 position: point_pos, ..
832 },
833 UnsolvedSegmentKind::Arc {
834 start: arc_start,
835 end: arc_end,
836 center: arc_center,
837 ..
838 },
839 )
840 | (
841 UnsolvedSegmentKind::Arc {
842 start: arc_start,
843 end: arc_end,
844 center: arc_center,
845 ..
846 },
847 UnsolvedSegmentKind::Point {
848 position: point_pos, ..
849 },
850 ) => {
851 let point_x = &point_pos[0];
852 let point_y = &point_pos[1];
853 match (point_x, point_y) {
854 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
855 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
857 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
858 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
859
860 match (center_x, center_y, start_x, start_y, end_x, end_y) {
861 (
862 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
863 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
864 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
865 ) => {
866 let point = DatumPoint::new_xy(
867 point_x.to_constraint_id(range)?,
868 point_y.to_constraint_id(range)?,
869 );
870 let circular_arc = DatumCircularArc {
871 center: DatumPoint::new_xy(
872 cx.to_constraint_id(range)?,
873 cy.to_constraint_id(range)?,
874 ),
875 start: DatumPoint::new_xy(
876 sx.to_constraint_id(range)?,
877 sy.to_constraint_id(range)?,
878 ),
879 end: DatumPoint::new_xy(
880 ex.to_constraint_id(range)?,
881 ey.to_constraint_id(range)?,
882 ),
883 };
884 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
885
886 #[cfg(feature = "artifact-graph")]
887 let constraint_id = exec_state.next_object_id();
888
889 let Some(sketch_state) = exec_state.sketch_block_mut() else {
890 return Err(KclError::new_semantic(KclErrorDetails::new(
891 "coincident() can only be used inside a sketch block".to_owned(),
892 vec![args.source_range],
893 )));
894 };
895 sketch_state.solver_constraints.push(constraint);
896 #[cfg(feature = "artifact-graph")]
897 {
898 let constraint = crate::front::Constraint::Coincident(Coincident {
899 segments: vec![unsolved0.object_id, unsolved1.object_id],
900 });
901 sketch_state.sketch_constraints.push(constraint_id);
902 track_constraint(constraint_id, constraint, exec_state, &args);
903 }
904 Ok(KclValue::none())
905 }
906 _ => Err(KclError::new_semantic(KclErrorDetails::new(
907 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
908 vec![args.source_range],
909 ))),
910 }
911 }
912 _ => Err(KclError::new_semantic(KclErrorDetails::new(
913 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
914 vec![args.source_range],
915 ))),
916 }
917 }
918 (
920 UnsolvedSegmentKind::Line {
921 start: line0_start,
922 end: line0_end,
923 ..
924 },
925 UnsolvedSegmentKind::Line {
926 start: line1_start,
927 end: line1_end,
928 ..
929 },
930 ) => {
931 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
933 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
934 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
935 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
936
937 match (
938 line0_start_x,
939 line0_start_y,
940 line0_end_x,
941 line0_end_y,
942 line1_start_x,
943 line1_start_y,
944 line1_end_x,
945 line1_end_y,
946 ) {
947 (
948 UnsolvedExpr::Unknown(l0_sx),
949 UnsolvedExpr::Unknown(l0_sy),
950 UnsolvedExpr::Unknown(l0_ex),
951 UnsolvedExpr::Unknown(l0_ey),
952 UnsolvedExpr::Unknown(l1_sx),
953 UnsolvedExpr::Unknown(l1_sy),
954 UnsolvedExpr::Unknown(l1_ex),
955 UnsolvedExpr::Unknown(l1_ey),
956 ) => {
957 let line0_segment = DatumLineSegment::new(
959 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
960 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
961 );
962 let line1_segment = DatumLineSegment::new(
963 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
964 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
965 );
966
967 let parallel_constraint =
969 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
970
971 let point_on_line1 =
973 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
974 let distance_constraint =
975 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
976
977 #[cfg(feature = "artifact-graph")]
978 let constraint_id = exec_state.next_object_id();
979
980 let Some(sketch_state) = exec_state.sketch_block_mut() else {
981 return Err(KclError::new_semantic(KclErrorDetails::new(
982 "coincident() can only be used inside a sketch block".to_owned(),
983 vec![args.source_range],
984 )));
985 };
986 sketch_state.solver_constraints.push(parallel_constraint);
988 sketch_state.solver_constraints.push(distance_constraint);
989 #[cfg(feature = "artifact-graph")]
990 {
991 let constraint = crate::front::Constraint::Coincident(Coincident {
992 segments: vec![unsolved0.object_id, unsolved1.object_id],
993 });
994 sketch_state.sketch_constraints.push(constraint_id);
995 track_constraint(constraint_id, constraint, exec_state, &args);
996 }
997 Ok(KclValue::none())
998 }
999 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1000 "Line segment endpoints must be sketch variables for line-line coincident constraint"
1001 .to_owned(),
1002 vec![args.source_range],
1003 ))),
1004 }
1005 }
1006 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1007 format!(
1008 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
1009 &unsolved0.kind, &unsolved1.kind
1010 ),
1011 vec![args.source_range],
1012 ))),
1013 }
1014 }
1015 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1016 "All inputs must be segments (points or lines), created from point(), line(), or another sketch function"
1017 .to_owned(),
1018 vec![args.source_range],
1019 ))),
1020 }
1021}
1022
1023#[cfg(feature = "artifact-graph")]
1024fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
1025 let sketch_id = {
1026 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1027 debug_assert!(false, "Constraint created outside a sketch block");
1028 return;
1029 };
1030 sketch_state.sketch_id
1031 };
1032 let Some(sketch_id) = sketch_id else {
1033 debug_assert!(false, "Constraint created without a sketch id");
1034 return;
1035 };
1036 let artifact_id = exec_state.next_artifact_id();
1037 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
1038 id: artifact_id,
1039 sketch_id,
1040 constraint_id,
1041 constraint_type: SketchBlockConstraintType::from(&constraint),
1042 code_ref: CodeRef::placeholder(args.source_range),
1043 }));
1044 exec_state.add_scene_object(
1045 Object {
1046 id: constraint_id,
1047 kind: ObjectKind::Constraint { constraint },
1048 label: Default::default(),
1049 comments: Default::default(),
1050 artifact_id,
1051 source: args.source_range.into(),
1052 },
1053 args.source_range,
1054 );
1055}
1056
1057fn coincident_constraints_fixed(
1059 p0_x: SketchVarId,
1060 p0_y: SketchVarId,
1061 p1_x: &KclValue,
1062 p1_y: &KclValue,
1063 exec_state: &mut ExecState,
1064 args: &Args,
1065) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
1066 let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
1067 let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
1068 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
1069 let message = format!(
1070 "Expected number after coercion, but found {}",
1071 p1_x_number_value.human_friendly_type()
1072 );
1073 debug_assert!(false, "{}", &message);
1074 return Err(KclError::new_internal(KclErrorDetails::new(
1075 message,
1076 vec![args.source_range],
1077 )));
1078 };
1079 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
1080 let message = format!(
1081 "Expected number after coercion, but found {}",
1082 p1_y_number_value.human_friendly_type()
1083 );
1084 debug_assert!(false, "{}", &message);
1085 return Err(KclError::new_internal(KclErrorDetails::new(
1086 message,
1087 vec![args.source_range],
1088 )));
1089 };
1090 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
1091 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
1092 Ok((constraint_x, constraint_y))
1093}
1094
1095pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1096 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1097 "points",
1098 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1099 exec_state,
1100 )?;
1101 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1102 KclError::new_semantic(KclErrorDetails::new(
1103 "must have two input points".to_owned(),
1104 vec![args.source_range],
1105 ))
1106 })?;
1107
1108 match (&point0, &point1) {
1109 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1110 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1111 return Err(KclError::new_semantic(KclErrorDetails::new(
1112 "first point must be an unsolved segment".to_owned(),
1113 vec![args.source_range],
1114 )));
1115 };
1116 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1117 return Err(KclError::new_semantic(KclErrorDetails::new(
1118 "second point must be an unsolved segment".to_owned(),
1119 vec![args.source_range],
1120 )));
1121 };
1122 match (&unsolved0.kind, &unsolved1.kind) {
1123 (
1124 UnsolvedSegmentKind::Point { position: pos0, .. },
1125 UnsolvedSegmentKind::Point { position: pos1, .. },
1126 ) => {
1127 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1130 (
1131 UnsolvedExpr::Unknown(p0_x),
1132 UnsolvedExpr::Unknown(p0_y),
1133 UnsolvedExpr::Unknown(p1_x),
1134 UnsolvedExpr::Unknown(p1_y),
1135 ) => {
1136 let sketch_constraint = SketchConstraint {
1138 kind: SketchConstraintKind::Distance {
1139 points: [
1140 ConstrainablePoint2d {
1141 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1142 object_id: unsolved0.object_id,
1143 },
1144 ConstrainablePoint2d {
1145 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1146 object_id: unsolved1.object_id,
1147 },
1148 ],
1149 },
1150 meta: vec![args.source_range.into()],
1151 };
1152 Ok(KclValue::SketchConstraint {
1153 value: Box::new(sketch_constraint),
1154 })
1155 }
1156 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1157 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1158 vec![args.source_range],
1159 ))),
1160 }
1161 }
1162 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1163 "distance() arguments must be unsolved points".to_owned(),
1164 vec![args.source_range],
1165 ))),
1166 }
1167 }
1168 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1169 "distance() arguments must be point segments".to_owned(),
1170 vec![args.source_range],
1171 ))),
1172 }
1173}
1174
1175fn create_arc_radius_constraint(
1178 segment: KclValue,
1179 constraint_kind: fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
1180 source_range: crate::SourceRange,
1181) -> Result<SketchConstraint, KclError> {
1182 let dummy_constraint = constraint_kind([
1184 ConstrainablePoint2d {
1185 vars: crate::front::Point2d {
1186 x: SketchVarId(0),
1187 y: SketchVarId(0),
1188 },
1189 object_id: ObjectId(0),
1190 },
1191 ConstrainablePoint2d {
1192 vars: crate::front::Point2d {
1193 x: SketchVarId(0),
1194 y: SketchVarId(0),
1195 },
1196 object_id: ObjectId(0),
1197 },
1198 ]);
1199 let function_name = dummy_constraint.name();
1200
1201 let KclValue::Segment { value: seg } = segment else {
1202 return Err(KclError::new_semantic(KclErrorDetails::new(
1203 format!("{}() argument must be a segment", function_name),
1204 vec![source_range],
1205 )));
1206 };
1207 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1208 return Err(KclError::new_semantic(KclErrorDetails::new(
1209 "segment must be unsolved".to_owned(),
1210 vec![source_range],
1211 )));
1212 };
1213 match &unsolved.kind {
1214 UnsolvedSegmentKind::Arc {
1215 center,
1216 start,
1217 center_object_id,
1218 start_object_id,
1219 ..
1220 } => {
1221 match (¢er[0], ¢er[1], &start[0], &start[1]) {
1223 (
1224 UnsolvedExpr::Unknown(center_x),
1225 UnsolvedExpr::Unknown(center_y),
1226 UnsolvedExpr::Unknown(start_x),
1227 UnsolvedExpr::Unknown(start_y),
1228 ) => {
1229 let sketch_constraint = SketchConstraint {
1231 kind: constraint_kind([
1232 ConstrainablePoint2d {
1233 vars: crate::front::Point2d {
1234 x: *center_x,
1235 y: *center_y,
1236 },
1237 object_id: *center_object_id,
1238 },
1239 ConstrainablePoint2d {
1240 vars: crate::front::Point2d {
1241 x: *start_x,
1242 y: *start_y,
1243 },
1244 object_id: *start_object_id,
1245 },
1246 ]),
1247 meta: vec![source_range.into()],
1248 };
1249 Ok(sketch_constraint)
1250 }
1251 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1252 format!(
1253 "unimplemented: {}() arc segment must have all sketch vars in all coordinates",
1254 function_name
1255 ),
1256 vec![source_range],
1257 ))),
1258 }
1259 }
1260 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1261 format!("{}() argument must be an arc segment", function_name),
1262 vec![source_range],
1263 ))),
1264 }
1265}
1266
1267pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1268 let segment: KclValue =
1269 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1270
1271 create_arc_radius_constraint(
1272 segment,
1273 |points| SketchConstraintKind::Radius { points },
1274 args.source_range,
1275 )
1276 .map(|constraint| KclValue::SketchConstraint {
1277 value: Box::new(constraint),
1278 })
1279}
1280
1281pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1282 let segment: KclValue =
1283 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1284
1285 create_arc_radius_constraint(
1286 segment,
1287 |points| SketchConstraintKind::Diameter { points },
1288 args.source_range,
1289 )
1290 .map(|constraint| KclValue::SketchConstraint {
1291 value: Box::new(constraint),
1292 })
1293}
1294
1295pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1296 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1297 "points",
1298 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1299 exec_state,
1300 )?;
1301 let [p1, p2] = points.as_slice() else {
1302 return Err(KclError::new_semantic(KclErrorDetails::new(
1303 "must have two input points".to_owned(),
1304 vec![args.source_range],
1305 )));
1306 };
1307 match (p1, p2) {
1308 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1309 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1310 return Err(KclError::new_semantic(KclErrorDetails::new(
1311 "first point must be an unsolved segment".to_owned(),
1312 vec![args.source_range],
1313 )));
1314 };
1315 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1316 return Err(KclError::new_semantic(KclErrorDetails::new(
1317 "second point must be an unsolved segment".to_owned(),
1318 vec![args.source_range],
1319 )));
1320 };
1321 match (&unsolved0.kind, &unsolved1.kind) {
1322 (
1323 UnsolvedSegmentKind::Point { position: pos0, .. },
1324 UnsolvedSegmentKind::Point { position: pos1, .. },
1325 ) => {
1326 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1329 (
1330 UnsolvedExpr::Unknown(p0_x),
1331 UnsolvedExpr::Unknown(p0_y),
1332 UnsolvedExpr::Unknown(p1_x),
1333 UnsolvedExpr::Unknown(p1_y),
1334 ) => {
1335 let sketch_constraint = SketchConstraint {
1337 kind: SketchConstraintKind::HorizontalDistance {
1338 points: [
1339 ConstrainablePoint2d {
1340 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1341 object_id: unsolved0.object_id,
1342 },
1343 ConstrainablePoint2d {
1344 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1345 object_id: unsolved1.object_id,
1346 },
1347 ],
1348 },
1349 meta: vec![args.source_range.into()],
1350 };
1351 Ok(KclValue::SketchConstraint {
1352 value: Box::new(sketch_constraint),
1353 })
1354 }
1355 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1356 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
1357 .to_owned(),
1358 vec![args.source_range],
1359 ))),
1360 }
1361 }
1362 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1363 "horizontalDistance() arguments must be unsolved points".to_owned(),
1364 vec![args.source_range],
1365 ))),
1366 }
1367 }
1368 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1369 "horizontalDistance() arguments must be point segments".to_owned(),
1370 vec![args.source_range],
1371 ))),
1372 }
1373}
1374
1375pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1376 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1377 "points",
1378 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1379 exec_state,
1380 )?;
1381 let [p1, p2] = points.as_slice() else {
1382 return Err(KclError::new_semantic(KclErrorDetails::new(
1383 "must have two input points".to_owned(),
1384 vec![args.source_range],
1385 )));
1386 };
1387 match (p1, p2) {
1388 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1389 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1390 return Err(KclError::new_semantic(KclErrorDetails::new(
1391 "first point must be an unsolved segment".to_owned(),
1392 vec![args.source_range],
1393 )));
1394 };
1395 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1396 return Err(KclError::new_semantic(KclErrorDetails::new(
1397 "second point must be an unsolved segment".to_owned(),
1398 vec![args.source_range],
1399 )));
1400 };
1401 match (&unsolved0.kind, &unsolved1.kind) {
1402 (
1403 UnsolvedSegmentKind::Point { position: pos0, .. },
1404 UnsolvedSegmentKind::Point { position: pos1, .. },
1405 ) => {
1406 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1409 (
1410 UnsolvedExpr::Unknown(p0_x),
1411 UnsolvedExpr::Unknown(p0_y),
1412 UnsolvedExpr::Unknown(p1_x),
1413 UnsolvedExpr::Unknown(p1_y),
1414 ) => {
1415 let sketch_constraint = SketchConstraint {
1417 kind: SketchConstraintKind::VerticalDistance {
1418 points: [
1419 ConstrainablePoint2d {
1420 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1421 object_id: unsolved0.object_id,
1422 },
1423 ConstrainablePoint2d {
1424 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1425 object_id: unsolved1.object_id,
1426 },
1427 ],
1428 },
1429 meta: vec![args.source_range.into()],
1430 };
1431 Ok(KclValue::SketchConstraint {
1432 value: Box::new(sketch_constraint),
1433 })
1434 }
1435 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1436 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
1437 .to_owned(),
1438 vec![args.source_range],
1439 ))),
1440 }
1441 }
1442 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1443 "verticalDistance() arguments must be unsolved points".to_owned(),
1444 vec![args.source_range],
1445 ))),
1446 }
1447 }
1448 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1449 "verticalDistance() arguments must be point segments".to_owned(),
1450 vec![args.source_range],
1451 ))),
1452 }
1453}
1454
1455pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1456 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1457 "lines",
1458 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1459 exec_state,
1460 )?;
1461 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1462 KclError::new_semantic(KclErrorDetails::new(
1463 "must have two input lines".to_owned(),
1464 vec![args.source_range],
1465 ))
1466 })?;
1467
1468 let KclValue::Segment { value: segment0 } = &line0 else {
1469 return Err(KclError::new_semantic(KclErrorDetails::new(
1470 "line argument must be a Segment".to_owned(),
1471 vec![args.source_range],
1472 )));
1473 };
1474 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1475 return Err(KclError::new_internal(KclErrorDetails::new(
1476 "line must be an unsolved Segment".to_owned(),
1477 vec![args.source_range],
1478 )));
1479 };
1480 let UnsolvedSegmentKind::Line {
1481 start: start0,
1482 end: end0,
1483 ..
1484 } = &unsolved0.kind
1485 else {
1486 return Err(KclError::new_semantic(KclErrorDetails::new(
1487 "line argument must be a line, no other type of Segment".to_owned(),
1488 vec![args.source_range],
1489 )));
1490 };
1491 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1492 return Err(KclError::new_semantic(KclErrorDetails::new(
1493 "line's start x coordinate must be a var".to_owned(),
1494 vec![args.source_range],
1495 )));
1496 };
1497 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1498 return Err(KclError::new_semantic(KclErrorDetails::new(
1499 "line's start y coordinate must be a var".to_owned(),
1500 vec![args.source_range],
1501 )));
1502 };
1503 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1504 return Err(KclError::new_semantic(KclErrorDetails::new(
1505 "line's end x coordinate must be a var".to_owned(),
1506 vec![args.source_range],
1507 )));
1508 };
1509 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1510 return Err(KclError::new_semantic(KclErrorDetails::new(
1511 "line's end y coordinate must be a var".to_owned(),
1512 vec![args.source_range],
1513 )));
1514 };
1515 let KclValue::Segment { value: segment1 } = &line1 else {
1516 return Err(KclError::new_semantic(KclErrorDetails::new(
1517 "line argument must be a Segment".to_owned(),
1518 vec![args.source_range],
1519 )));
1520 };
1521 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1522 return Err(KclError::new_internal(KclErrorDetails::new(
1523 "line must be an unsolved Segment".to_owned(),
1524 vec![args.source_range],
1525 )));
1526 };
1527 let UnsolvedSegmentKind::Line {
1528 start: start1,
1529 end: end1,
1530 ..
1531 } = &unsolved1.kind
1532 else {
1533 return Err(KclError::new_semantic(KclErrorDetails::new(
1534 "line argument must be a line, no other type of Segment".to_owned(),
1535 vec![args.source_range],
1536 )));
1537 };
1538 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1539 return Err(KclError::new_semantic(KclErrorDetails::new(
1540 "line's start x coordinate must be a var".to_owned(),
1541 vec![args.source_range],
1542 )));
1543 };
1544 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1545 return Err(KclError::new_semantic(KclErrorDetails::new(
1546 "line's start y coordinate must be a var".to_owned(),
1547 vec![args.source_range],
1548 )));
1549 };
1550 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1551 return Err(KclError::new_semantic(KclErrorDetails::new(
1552 "line's end x coordinate must be a var".to_owned(),
1553 vec![args.source_range],
1554 )));
1555 };
1556 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1557 return Err(KclError::new_semantic(KclErrorDetails::new(
1558 "line's end y coordinate must be a var".to_owned(),
1559 vec![args.source_range],
1560 )));
1561 };
1562
1563 let range = args.source_range;
1564 let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1565 line0_p0_x.to_constraint_id(range)?,
1566 line0_p0_y.to_constraint_id(range)?,
1567 );
1568 let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1569 line0_p1_x.to_constraint_id(range)?,
1570 line0_p1_y.to_constraint_id(range)?,
1571 );
1572 let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1573 let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1574 line1_p0_x.to_constraint_id(range)?,
1575 line1_p0_y.to_constraint_id(range)?,
1576 );
1577 let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1578 line1_p1_x.to_constraint_id(range)?,
1579 line1_p1_y.to_constraint_id(range)?,
1580 );
1581 let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1582 let constraint = SolverConstraint::LinesEqualLength(solver_line0, solver_line1);
1583 #[cfg(feature = "artifact-graph")]
1584 let constraint_id = exec_state.next_object_id();
1585 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1587 return Err(KclError::new_semantic(KclErrorDetails::new(
1588 "equalLength() can only be used inside a sketch block".to_owned(),
1589 vec![args.source_range],
1590 )));
1591 };
1592 sketch_state.solver_constraints.push(constraint);
1593 #[cfg(feature = "artifact-graph")]
1594 {
1595 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
1596 lines: vec![unsolved0.object_id, unsolved1.object_id],
1597 });
1598 sketch_state.sketch_constraints.push(constraint_id);
1599 track_constraint(constraint_id, constraint, exec_state, &args);
1600 }
1601 Ok(KclValue::none())
1602}
1603
1604#[derive(Debug, Clone, Copy)]
1605pub(crate) enum LinesAtAngleKind {
1606 Parallel,
1607 Perpendicular,
1608}
1609
1610impl LinesAtAngleKind {
1611 pub fn to_function_name(self) -> &'static str {
1612 match self {
1613 LinesAtAngleKind::Parallel => "parallel",
1614 LinesAtAngleKind::Perpendicular => "perpendicular",
1615 }
1616 }
1617
1618 fn to_solver_angle(self) -> kcl_ezpz::datatypes::AngleKind {
1619 match self {
1620 LinesAtAngleKind::Parallel => kcl_ezpz::datatypes::AngleKind::Parallel,
1621 LinesAtAngleKind::Perpendicular => kcl_ezpz::datatypes::AngleKind::Perpendicular,
1622 }
1623 }
1624
1625 #[cfg(feature = "artifact-graph")]
1626 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
1627 match self {
1628 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
1629 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
1630 }
1631 }
1632}
1633
1634pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1635 lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
1636}
1637pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1638 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
1639}
1640
1641async fn lines_at_angle(
1642 angle_kind: LinesAtAngleKind,
1643 exec_state: &mut ExecState,
1644 args: Args,
1645) -> Result<KclValue, KclError> {
1646 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1647 "lines",
1648 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1649 exec_state,
1650 )?;
1651 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1652 KclError::new_semantic(KclErrorDetails::new(
1653 "must have two input lines".to_owned(),
1654 vec![args.source_range],
1655 ))
1656 })?;
1657
1658 let KclValue::Segment { value: segment0 } = &line0 else {
1659 return Err(KclError::new_semantic(KclErrorDetails::new(
1660 "line argument must be a Segment".to_owned(),
1661 vec![args.source_range],
1662 )));
1663 };
1664 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1665 return Err(KclError::new_internal(KclErrorDetails::new(
1666 "line must be an unsolved Segment".to_owned(),
1667 vec![args.source_range],
1668 )));
1669 };
1670 let UnsolvedSegmentKind::Line {
1671 start: start0,
1672 end: end0,
1673 ..
1674 } = &unsolved0.kind
1675 else {
1676 return Err(KclError::new_semantic(KclErrorDetails::new(
1677 "line argument must be a line, no other type of Segment".to_owned(),
1678 vec![args.source_range],
1679 )));
1680 };
1681 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1682 return Err(KclError::new_semantic(KclErrorDetails::new(
1683 "line's start x coordinate must be a var".to_owned(),
1684 vec![args.source_range],
1685 )));
1686 };
1687 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1688 return Err(KclError::new_semantic(KclErrorDetails::new(
1689 "line's start y coordinate must be a var".to_owned(),
1690 vec![args.source_range],
1691 )));
1692 };
1693 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1694 return Err(KclError::new_semantic(KclErrorDetails::new(
1695 "line's end x coordinate must be a var".to_owned(),
1696 vec![args.source_range],
1697 )));
1698 };
1699 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1700 return Err(KclError::new_semantic(KclErrorDetails::new(
1701 "line's end y coordinate must be a var".to_owned(),
1702 vec![args.source_range],
1703 )));
1704 };
1705 let KclValue::Segment { value: segment1 } = &line1 else {
1706 return Err(KclError::new_semantic(KclErrorDetails::new(
1707 "line argument must be a Segment".to_owned(),
1708 vec![args.source_range],
1709 )));
1710 };
1711 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1712 return Err(KclError::new_internal(KclErrorDetails::new(
1713 "line must be an unsolved Segment".to_owned(),
1714 vec![args.source_range],
1715 )));
1716 };
1717 let UnsolvedSegmentKind::Line {
1718 start: start1,
1719 end: end1,
1720 ..
1721 } = &unsolved1.kind
1722 else {
1723 return Err(KclError::new_semantic(KclErrorDetails::new(
1724 "line argument must be a line, no other type of Segment".to_owned(),
1725 vec![args.source_range],
1726 )));
1727 };
1728 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1729 return Err(KclError::new_semantic(KclErrorDetails::new(
1730 "line's start x coordinate must be a var".to_owned(),
1731 vec![args.source_range],
1732 )));
1733 };
1734 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1735 return Err(KclError::new_semantic(KclErrorDetails::new(
1736 "line's start y coordinate must be a var".to_owned(),
1737 vec![args.source_range],
1738 )));
1739 };
1740 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1741 return Err(KclError::new_semantic(KclErrorDetails::new(
1742 "line's end x coordinate must be a var".to_owned(),
1743 vec![args.source_range],
1744 )));
1745 };
1746 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1747 return Err(KclError::new_semantic(KclErrorDetails::new(
1748 "line's end y coordinate must be a var".to_owned(),
1749 vec![args.source_range],
1750 )));
1751 };
1752
1753 let range = args.source_range;
1754 let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1755 line0_p0_x.to_constraint_id(range)?,
1756 line0_p0_y.to_constraint_id(range)?,
1757 );
1758 let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1759 line0_p1_x.to_constraint_id(range)?,
1760 line0_p1_y.to_constraint_id(range)?,
1761 );
1762 let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1763 let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1764 line1_p0_x.to_constraint_id(range)?,
1765 line1_p0_y.to_constraint_id(range)?,
1766 );
1767 let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1768 line1_p1_x.to_constraint_id(range)?,
1769 line1_p1_y.to_constraint_id(range)?,
1770 );
1771 let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1772 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
1773 #[cfg(feature = "artifact-graph")]
1774 let constraint_id = exec_state.next_object_id();
1775 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1777 return Err(KclError::new_semantic(KclErrorDetails::new(
1778 format!(
1779 "{}() can only be used inside a sketch block",
1780 angle_kind.to_function_name()
1781 ),
1782 vec![args.source_range],
1783 )));
1784 };
1785 sketch_state.solver_constraints.push(constraint);
1786 #[cfg(feature = "artifact-graph")]
1787 {
1788 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
1789 sketch_state.sketch_constraints.push(constraint_id);
1790 track_constraint(constraint_id, constraint, exec_state, &args);
1791 }
1792 Ok(KclValue::none())
1793}
1794
1795pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1796 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1797 let KclValue::Segment { value: segment } = line else {
1798 return Err(KclError::new_semantic(KclErrorDetails::new(
1799 "line argument must be a Segment".to_owned(),
1800 vec![args.source_range],
1801 )));
1802 };
1803 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1804 return Err(KclError::new_internal(KclErrorDetails::new(
1805 "line must be an unsolved Segment".to_owned(),
1806 vec![args.source_range],
1807 )));
1808 };
1809 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1810 return Err(KclError::new_semantic(KclErrorDetails::new(
1811 "line argument must be a line, no other type of Segment".to_owned(),
1812 vec![args.source_range],
1813 )));
1814 };
1815 let p0_x = &start[0];
1816 let p0_y = &start[1];
1817 let p1_x = &end[0];
1818 let p1_y = &end[1];
1819 match (p0_x, p0_y, p1_x, p1_y) {
1820 (
1821 UnsolvedExpr::Unknown(p0_x),
1822 UnsolvedExpr::Unknown(p0_y),
1823 UnsolvedExpr::Unknown(p1_x),
1824 UnsolvedExpr::Unknown(p1_y),
1825 ) => {
1826 let range = args.source_range;
1827 let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1828 p0_x.to_constraint_id(range)?,
1829 p0_y.to_constraint_id(range)?,
1830 );
1831 let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1832 p1_x.to_constraint_id(range)?,
1833 p1_y.to_constraint_id(range)?,
1834 );
1835 let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1836 let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
1837 #[cfg(feature = "artifact-graph")]
1838 let constraint_id = exec_state.next_object_id();
1839 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1841 return Err(KclError::new_semantic(KclErrorDetails::new(
1842 "horizontal() can only be used inside a sketch block".to_owned(),
1843 vec![args.source_range],
1844 )));
1845 };
1846 sketch_state.solver_constraints.push(constraint);
1847 #[cfg(feature = "artifact-graph")]
1848 {
1849 let constraint = crate::front::Constraint::Horizontal(Horizontal {
1850 line: unsolved.object_id,
1851 });
1852 sketch_state.sketch_constraints.push(constraint_id);
1853 track_constraint(constraint_id, constraint, exec_state, &args);
1854 }
1855 Ok(KclValue::none())
1856 }
1857 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1858 "line's x and y coordinates of both start and end must be vars".to_owned(),
1859 vec![args.source_range],
1860 ))),
1861 }
1862}
1863
1864pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1865 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1866 let KclValue::Segment { value: segment } = line else {
1867 return Err(KclError::new_semantic(KclErrorDetails::new(
1868 "line argument must be a Segment".to_owned(),
1869 vec![args.source_range],
1870 )));
1871 };
1872 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1873 return Err(KclError::new_internal(KclErrorDetails::new(
1874 "line must be an unsolved Segment".to_owned(),
1875 vec![args.source_range],
1876 )));
1877 };
1878 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1879 return Err(KclError::new_semantic(KclErrorDetails::new(
1880 "line argument must be a line, no other type of Segment".to_owned(),
1881 vec![args.source_range],
1882 )));
1883 };
1884 let p0_x = &start[0];
1885 let p0_y = &start[1];
1886 let p1_x = &end[0];
1887 let p1_y = &end[1];
1888 match (p0_x, p0_y, p1_x, p1_y) {
1889 (
1890 UnsolvedExpr::Unknown(p0_x),
1891 UnsolvedExpr::Unknown(p0_y),
1892 UnsolvedExpr::Unknown(p1_x),
1893 UnsolvedExpr::Unknown(p1_y),
1894 ) => {
1895 let range = args.source_range;
1896 let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1897 p0_x.to_constraint_id(range)?,
1898 p0_y.to_constraint_id(range)?,
1899 );
1900 let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1901 p1_x.to_constraint_id(range)?,
1902 p1_y.to_constraint_id(range)?,
1903 );
1904 let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1905 let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1906 #[cfg(feature = "artifact-graph")]
1907 let constraint_id = exec_state.next_object_id();
1908 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1910 return Err(KclError::new_semantic(KclErrorDetails::new(
1911 "vertical() can only be used inside a sketch block".to_owned(),
1912 vec![args.source_range],
1913 )));
1914 };
1915 sketch_state.solver_constraints.push(constraint);
1916 #[cfg(feature = "artifact-graph")]
1917 {
1918 let constraint = crate::front::Constraint::Vertical(Vertical {
1919 line: unsolved.object_id,
1920 });
1921 sketch_state.sketch_constraints.push(constraint_id);
1922 track_constraint(constraint_id, constraint, exec_state, &args);
1923 }
1924 Ok(KclValue::none())
1925 }
1926 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1927 "line's x and y coordinates of both start and end must be vars".to_owned(),
1928 vec![args.source_range],
1929 ))),
1930 }
1931}