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