1use anyhow::Result;
2use kcl_ezpz::{
3 Constraint as SolverConstraint,
4 datatypes::inputs::{DatumCircularArc, DatumLineSegment, DatumPoint},
5};
6
7use crate::{
8 errors::{KclError, KclErrorDetails},
9 execution::{
10 AbstractSegment, ConstrainablePoint2d, ExecState, KclValue, SegmentRepr, SketchConstraint,
11 SketchConstraintKind, SketchVarId, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind,
12 normalize_to_solver_unit,
13 types::{ArrayLen, PrimitiveType, RuntimeType},
14 },
15 front::{ArcCtor, LineCtor, Point2d, PointCtor},
16 std::Args,
17};
18#[cfg(feature = "artifact-graph")]
19use crate::{
20 execution::ArtifactId,
21 front::{
22 Coincident, Constraint, Horizontal, LinesEqualLength, Object, ObjectId, ObjectKind, Parallel, Perpendicular,
23 Vertical,
24 },
25};
26
27pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
28 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
29 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
30 KclError::new_semantic(KclErrorDetails::new(
31 "at must be a 2D point".to_owned(),
32 vec![args.source_range],
33 ))
34 })?;
35 let Some(at_x) = at_x_value.as_unsolved_expr() else {
36 return Err(KclError::new_semantic(KclErrorDetails::new(
37 "at x must be a number or sketch var".to_owned(),
38 vec![args.source_range],
39 )));
40 };
41 let Some(at_y) = at_y_value.as_unsolved_expr() else {
42 return Err(KclError::new_semantic(KclErrorDetails::new(
43 "at y must be a number or sketch var".to_owned(),
44 vec![args.source_range],
45 )));
46 };
47 let ctor = PointCtor {
48 position: Point2d {
49 x: at_x_value.to_sketch_expr().ok_or_else(|| {
50 KclError::new_semantic(KclErrorDetails::new(
51 "unable to convert numeric type to suffix".to_owned(),
52 vec![args.source_range],
53 ))
54 })?,
55 y: at_y_value.to_sketch_expr().ok_or_else(|| {
56 KclError::new_semantic(KclErrorDetails::new(
57 "unable to convert numeric type to suffix".to_owned(),
58 vec![args.source_range],
59 ))
60 })?,
61 },
62 };
63 let segment = UnsolvedSegment {
64 object_id: exec_state.next_object_id(),
65 kind: UnsolvedSegmentKind::Point {
66 position: [at_x, at_y],
67 ctor: Box::new(ctor),
68 },
69 meta: vec![args.source_range.into()],
70 };
71 #[cfg(feature = "artifact-graph")]
72 let optional_constraints = {
73 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
74
75 let mut optional_constraints = Vec::new();
76 if exec_state.segment_ids_edited_contains(&object_id) {
77 if let Some(at_x_var) = at_x_value.as_sketch_var() {
78 let x_initial_value = at_x_var.initial_value_to_solver_units(
79 exec_state,
80 args.source_range,
81 "edited segment fixed constraint value",
82 )?;
83 optional_constraints.push(SolverConstraint::Fixed(
84 at_x_var.id.to_constraint_id(args.source_range)?,
85 x_initial_value.n,
86 ));
87 }
88 if let Some(at_y_var) = at_y_value.as_sketch_var() {
89 let y_initial_value = at_y_var.initial_value_to_solver_units(
90 exec_state,
91 args.source_range,
92 "edited segment fixed constraint value",
93 )?;
94 optional_constraints.push(SolverConstraint::Fixed(
95 at_y_var.id.to_constraint_id(args.source_range)?,
96 y_initial_value.n,
97 ));
98 }
99 }
100 optional_constraints
101 };
102
103 let Some(sketch_state) = exec_state.sketch_block_mut() else {
105 return Err(KclError::new_semantic(KclErrorDetails::new(
106 "line() can only be used inside a sketch block".to_owned(),
107 vec![args.source_range],
108 )));
109 };
110 sketch_state.needed_by_engine.push(segment.clone());
111
112 #[cfg(feature = "artifact-graph")]
113 sketch_state.solver_optional_constraints.extend(optional_constraints);
114
115 let meta = segment.meta.clone();
116 let abstract_segment = AbstractSegment {
117 repr: SegmentRepr::Unsolved { segment },
118 meta,
119 };
120 Ok(KclValue::Segment {
121 value: Box::new(abstract_segment),
122 })
123}
124
125pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
126 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
127 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
129 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
130 KclError::new_semantic(KclErrorDetails::new(
131 "start must be a 2D point".to_owned(),
132 vec![args.source_range],
133 ))
134 })?;
135 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
136 KclError::new_semantic(KclErrorDetails::new(
137 "end must be a 2D point".to_owned(),
138 vec![args.source_range],
139 ))
140 })?;
141 let Some(start_x) = start_x_value.as_unsolved_expr() else {
142 return Err(KclError::new_semantic(KclErrorDetails::new(
143 "start x must be a number or sketch var".to_owned(),
144 vec![args.source_range],
145 )));
146 };
147 let Some(start_y) = start_y_value.as_unsolved_expr() else {
148 return Err(KclError::new_semantic(KclErrorDetails::new(
149 "start y must be a number or sketch var".to_owned(),
150 vec![args.source_range],
151 )));
152 };
153 let Some(end_x) = end_x_value.as_unsolved_expr() else {
154 return Err(KclError::new_semantic(KclErrorDetails::new(
155 "end x must be a number or sketch var".to_owned(),
156 vec![args.source_range],
157 )));
158 };
159 let Some(end_y) = end_y_value.as_unsolved_expr() else {
160 return Err(KclError::new_semantic(KclErrorDetails::new(
161 "end y must be a number or sketch var".to_owned(),
162 vec![args.source_range],
163 )));
164 };
165 let ctor = LineCtor {
166 start: Point2d {
167 x: start_x_value.to_sketch_expr().ok_or_else(|| {
168 KclError::new_semantic(KclErrorDetails::new(
169 "unable to convert numeric type to suffix".to_owned(),
170 vec![args.source_range],
171 ))
172 })?,
173 y: start_y_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 },
180 end: Point2d {
181 x: end_x_value.to_sketch_expr().ok_or_else(|| {
182 KclError::new_semantic(KclErrorDetails::new(
183 "unable to convert numeric type to suffix".to_owned(),
184 vec![args.source_range],
185 ))
186 })?,
187 y: end_y_value.to_sketch_expr().ok_or_else(|| {
188 KclError::new_semantic(KclErrorDetails::new(
189 "unable to convert numeric type to suffix".to_owned(),
190 vec![args.source_range],
191 ))
192 })?,
193 },
194 };
195 let start_object_id = exec_state.next_object_id();
197 let end_object_id = exec_state.next_object_id();
198 let line_object_id = exec_state.next_object_id();
199 let segment = UnsolvedSegment {
200 object_id: line_object_id,
201 kind: UnsolvedSegmentKind::Line {
202 start: [start_x, start_y],
203 end: [end_x, end_y],
204 ctor: Box::new(ctor),
205 start_object_id,
206 end_object_id,
207 },
208 meta: vec![args.source_range.into()],
209 };
210 #[cfg(feature = "artifact-graph")]
211 let optional_constraints = {
212 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
213 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
214 let line_object_id = exec_state.add_placeholder_scene_object(line_object_id, args.source_range);
215
216 let mut optional_constraints = Vec::new();
217 if exec_state.segment_ids_edited_contains(&start_object_id)
218 || exec_state.segment_ids_edited_contains(&line_object_id)
219 {
220 if let Some(start_x_var) = start_x_value.as_sketch_var() {
221 let x_initial_value = start_x_var.initial_value_to_solver_units(
222 exec_state,
223 args.source_range,
224 "edited segment fixed constraint value",
225 )?;
226 optional_constraints.push(SolverConstraint::Fixed(
227 start_x_var.id.to_constraint_id(args.source_range)?,
228 x_initial_value.n,
229 ));
230 }
231 if let Some(start_y_var) = start_y_value.as_sketch_var() {
232 let y_initial_value = start_y_var.initial_value_to_solver_units(
233 exec_state,
234 args.source_range,
235 "edited segment fixed constraint value",
236 )?;
237 optional_constraints.push(SolverConstraint::Fixed(
238 start_y_var.id.to_constraint_id(args.source_range)?,
239 y_initial_value.n,
240 ));
241 }
242 }
243 if exec_state.segment_ids_edited_contains(&end_object_id)
244 || exec_state.segment_ids_edited_contains(&line_object_id)
245 {
246 if let Some(end_x_var) = end_x_value.as_sketch_var() {
247 let x_initial_value = end_x_var.initial_value_to_solver_units(
248 exec_state,
249 args.source_range,
250 "edited segment fixed constraint value",
251 )?;
252 optional_constraints.push(SolverConstraint::Fixed(
253 end_x_var.id.to_constraint_id(args.source_range)?,
254 x_initial_value.n,
255 ));
256 }
257 if let Some(end_y_var) = end_y_value.as_sketch_var() {
258 let y_initial_value = end_y_var.initial_value_to_solver_units(
259 exec_state,
260 args.source_range,
261 "edited segment fixed constraint value",
262 )?;
263 optional_constraints.push(SolverConstraint::Fixed(
264 end_y_var.id.to_constraint_id(args.source_range)?,
265 y_initial_value.n,
266 ));
267 }
268 }
269 optional_constraints
270 };
271
272 let Some(sketch_state) = exec_state.sketch_block_mut() else {
274 return Err(KclError::new_semantic(KclErrorDetails::new(
275 "line() can only be used inside a sketch block".to_owned(),
276 vec![args.source_range],
277 )));
278 };
279 sketch_state.needed_by_engine.push(segment.clone());
280
281 #[cfg(feature = "artifact-graph")]
282 sketch_state.solver_optional_constraints.extend(optional_constraints);
283
284 let meta = segment.meta.clone();
285 let abstract_segment = AbstractSegment {
286 repr: SegmentRepr::Unsolved { segment },
287 meta,
288 };
289 Ok(KclValue::Segment {
290 value: Box::new(abstract_segment),
291 })
292}
293
294pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
295 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
296 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
297 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
299
300 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
301 KclError::new_semantic(KclErrorDetails::new(
302 "start must be a 2D point".to_owned(),
303 vec![args.source_range],
304 ))
305 })?;
306 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
307 KclError::new_semantic(KclErrorDetails::new(
308 "end must be a 2D point".to_owned(),
309 vec![args.source_range],
310 ))
311 })?;
312 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
313 KclError::new_semantic(KclErrorDetails::new(
314 "center must be a 2D point".to_owned(),
315 vec![args.source_range],
316 ))
317 })?;
318
319 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
320 return Err(KclError::new_semantic(KclErrorDetails::new(
321 "start x must be a sketch var".to_owned(),
322 vec![args.source_range],
323 )));
324 };
325 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
326 return Err(KclError::new_semantic(KclErrorDetails::new(
327 "start y must be a sketch var".to_owned(),
328 vec![args.source_range],
329 )));
330 };
331 let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
332 return Err(KclError::new_semantic(KclErrorDetails::new(
333 "end x must be a sketch var".to_owned(),
334 vec![args.source_range],
335 )));
336 };
337 let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
338 return Err(KclError::new_semantic(KclErrorDetails::new(
339 "end y must be a sketch var".to_owned(),
340 vec![args.source_range],
341 )));
342 };
343 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
344 return Err(KclError::new_semantic(KclErrorDetails::new(
345 "center x must be a sketch var".to_owned(),
346 vec![args.source_range],
347 )));
348 };
349 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
350 return Err(KclError::new_semantic(KclErrorDetails::new(
351 "center y must be a sketch var".to_owned(),
352 vec![args.source_range],
353 )));
354 };
355
356 let ctor = ArcCtor {
357 start: Point2d {
358 x: start_x_value.to_sketch_expr().ok_or_else(|| {
359 KclError::new_semantic(KclErrorDetails::new(
360 "unable to convert numeric type to suffix".to_owned(),
361 vec![args.source_range],
362 ))
363 })?,
364 y: start_y_value.to_sketch_expr().ok_or_else(|| {
365 KclError::new_semantic(KclErrorDetails::new(
366 "unable to convert numeric type to suffix".to_owned(),
367 vec![args.source_range],
368 ))
369 })?,
370 },
371 end: Point2d {
372 x: end_x_value.to_sketch_expr().ok_or_else(|| {
373 KclError::new_semantic(KclErrorDetails::new(
374 "unable to convert numeric type to suffix".to_owned(),
375 vec![args.source_range],
376 ))
377 })?,
378 y: end_y_value.to_sketch_expr().ok_or_else(|| {
379 KclError::new_semantic(KclErrorDetails::new(
380 "unable to convert numeric type to suffix".to_owned(),
381 vec![args.source_range],
382 ))
383 })?,
384 },
385 center: Point2d {
386 x: center_x_value.to_sketch_expr().ok_or_else(|| {
387 KclError::new_semantic(KclErrorDetails::new(
388 "unable to convert numeric type to suffix".to_owned(),
389 vec![args.source_range],
390 ))
391 })?,
392 y: center_y_value.to_sketch_expr().ok_or_else(|| {
393 KclError::new_semantic(KclErrorDetails::new(
394 "unable to convert numeric type to suffix".to_owned(),
395 vec![args.source_range],
396 ))
397 })?,
398 },
399 };
400
401 let start_object_id = exec_state.next_object_id();
403 let end_object_id = exec_state.next_object_id();
404 let center_object_id = exec_state.next_object_id();
405 let arc_object_id = exec_state.next_object_id();
406 let segment = UnsolvedSegment {
407 object_id: arc_object_id,
408 kind: UnsolvedSegmentKind::Arc {
409 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
410 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
411 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
412 ctor: Box::new(ctor),
413 start_object_id,
414 end_object_id,
415 center_object_id,
416 },
417 meta: vec![args.source_range.into()],
418 };
419 #[cfg(feature = "artifact-graph")]
420 let optional_constraints = {
421 let start_object_id = exec_state.add_placeholder_scene_object(start_object_id, args.source_range);
422 let end_object_id = exec_state.add_placeholder_scene_object(end_object_id, args.source_range);
423 let center_object_id = exec_state.add_placeholder_scene_object(center_object_id, args.source_range);
424 let arc_object_id = exec_state.add_placeholder_scene_object(arc_object_id, args.source_range);
425
426 let mut optional_constraints = Vec::new();
427 if exec_state.segment_ids_edited_contains(&start_object_id)
428 || exec_state.segment_ids_edited_contains(&arc_object_id)
429 {
430 if let Some(start_x_var) = start_x_value.as_sketch_var() {
431 let x_initial_value = start_x_var.initial_value_to_solver_units(
432 exec_state,
433 args.source_range,
434 "edited segment fixed constraint value",
435 )?;
436 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
437 start_x_var.id.to_constraint_id(args.source_range)?,
438 x_initial_value.n,
439 ));
440 }
441 if let Some(start_y_var) = start_y_value.as_sketch_var() {
442 let y_initial_value = start_y_var.initial_value_to_solver_units(
443 exec_state,
444 args.source_range,
445 "edited segment fixed constraint value",
446 )?;
447 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
448 start_y_var.id.to_constraint_id(args.source_range)?,
449 y_initial_value.n,
450 ));
451 }
452 }
453 if exec_state.segment_ids_edited_contains(&end_object_id)
454 || exec_state.segment_ids_edited_contains(&arc_object_id)
455 {
456 if let Some(end_x_var) = end_x_value.as_sketch_var() {
457 let x_initial_value = end_x_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 end_x_var.id.to_constraint_id(args.source_range)?,
464 x_initial_value.n,
465 ));
466 }
467 if let Some(end_y_var) = end_y_value.as_sketch_var() {
468 let y_initial_value = end_y_var.initial_value_to_solver_units(
469 exec_state,
470 args.source_range,
471 "edited segment fixed constraint value",
472 )?;
473 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
474 end_y_var.id.to_constraint_id(args.source_range)?,
475 y_initial_value.n,
476 ));
477 }
478 }
479 if exec_state.segment_ids_edited_contains(¢er_object_id)
480 || exec_state.segment_ids_edited_contains(&arc_object_id)
481 {
482 if let Some(center_x_var) = center_x_value.as_sketch_var() {
483 let x_initial_value = center_x_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 center_x_var.id.to_constraint_id(args.source_range)?,
490 x_initial_value.n,
491 ));
492 }
493 if let Some(center_y_var) = center_y_value.as_sketch_var() {
494 let y_initial_value = center_y_var.initial_value_to_solver_units(
495 exec_state,
496 args.source_range,
497 "edited segment fixed constraint value",
498 )?;
499 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
500 center_y_var.id.to_constraint_id(args.source_range)?,
501 y_initial_value.n,
502 ));
503 }
504 }
505 optional_constraints
506 };
507
508 let range = args.source_range;
510 let constraint = kcl_ezpz::Constraint::Arc(kcl_ezpz::datatypes::inputs::DatumCircularArc {
511 center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
512 center_x.to_constraint_id(range)?,
513 center_y.to_constraint_id(range)?,
514 ),
515 start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
516 start_x.to_constraint_id(range)?,
517 start_y.to_constraint_id(range)?,
518 ),
519 end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
520 end_x.to_constraint_id(range)?,
521 end_y.to_constraint_id(range)?,
522 ),
523 });
524
525 let Some(sketch_state) = exec_state.sketch_block_mut() else {
526 return Err(KclError::new_semantic(KclErrorDetails::new(
527 "arc() can only be used inside a sketch block".to_owned(),
528 vec![args.source_range],
529 )));
530 };
531 sketch_state.needed_by_engine.push(segment.clone());
533 sketch_state.solver_constraints.push(constraint);
535 #[cfg(feature = "artifact-graph")]
539 sketch_state.solver_optional_constraints.extend(optional_constraints);
540
541 let meta = segment.meta.clone();
542 let abstract_segment = AbstractSegment {
543 repr: SegmentRepr::Unsolved { segment },
544 meta,
545 };
546 Ok(KclValue::Segment {
547 value: Box::new(abstract_segment),
548 })
549}
550
551pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
552 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
553 "points",
554 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
555 exec_state,
556 )?;
557 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
558 KclError::new_semantic(KclErrorDetails::new(
559 "must have two input points".to_owned(),
560 vec![args.source_range],
561 ))
562 })?;
563
564 let range = args.source_range;
565 match (&point0, &point1) {
566 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
567 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
568 return Err(KclError::new_semantic(KclErrorDetails::new(
569 "first point must be an unsolved segment".to_owned(),
570 vec![args.source_range],
571 )));
572 };
573 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
574 return Err(KclError::new_semantic(KclErrorDetails::new(
575 "second point must be an unsolved segment".to_owned(),
576 vec![args.source_range],
577 )));
578 };
579 match (&unsolved0.kind, &unsolved1.kind) {
580 (
581 UnsolvedSegmentKind::Point { position: pos0, .. },
582 UnsolvedSegmentKind::Point { position: pos1, .. },
583 ) => {
584 let p0_x = &pos0[0];
585 let p0_y = &pos0[1];
586 match (p0_x, p0_y) {
587 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
588 let p1_x = &pos1[0];
589 let p1_y = &pos1[1];
590 match (p1_x, p1_y) {
591 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
592 let constraint = SolverConstraint::PointsCoincident(
593 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
594 p0_x.to_constraint_id(range)?,
595 p0_y.to_constraint_id(range)?,
596 ),
597 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
598 p1_x.to_constraint_id(range)?,
599 p1_y.to_constraint_id(range)?,
600 ),
601 );
602 #[cfg(feature = "artifact-graph")]
603 let constraint_id = exec_state.next_object_id();
604 let Some(sketch_state) = exec_state.sketch_block_mut() else {
606 return Err(KclError::new_semantic(KclErrorDetails::new(
607 "coincident() can only be used inside a sketch block".to_owned(),
608 vec![args.source_range],
609 )));
610 };
611 sketch_state.solver_constraints.push(constraint);
612 #[cfg(feature = "artifact-graph")]
613 {
614 let constraint = crate::front::Constraint::Coincident(Coincident {
615 segments: vec![unsolved0.object_id, unsolved1.object_id],
616 });
617 sketch_state.sketch_constraints.push(constraint_id);
618 track_constraint(constraint_id, constraint, exec_state, &args);
619 }
620 Ok(KclValue::none())
621 }
622 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
623 let p1_x = KclValue::Number {
624 value: p1_x.n,
625 ty: p1_x.ty,
626 meta: vec![args.source_range.into()],
627 };
628 let p1_y = KclValue::Number {
629 value: p1_y.n,
630 ty: p1_y.ty,
631 meta: vec![args.source_range.into()],
632 };
633 let (constraint_x, constraint_y) =
634 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
635
636 #[cfg(feature = "artifact-graph")]
637 let constraint_id = exec_state.next_object_id();
638 let Some(sketch_state) = exec_state.sketch_block_mut() else {
640 return Err(KclError::new_semantic(KclErrorDetails::new(
641 "coincident() can only be used inside a sketch block".to_owned(),
642 vec![args.source_range],
643 )));
644 };
645 sketch_state.solver_constraints.push(constraint_x);
646 sketch_state.solver_constraints.push(constraint_y);
647 #[cfg(feature = "artifact-graph")]
648 {
649 let constraint = crate::front::Constraint::Coincident(Coincident {
650 segments: vec![unsolved0.object_id, unsolved1.object_id],
651 });
652 sketch_state.sketch_constraints.push(constraint_id);
653 track_constraint(constraint_id, constraint, exec_state, &args);
654 }
655 Ok(KclValue::none())
656 }
657 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
658 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
659 Err(KclError::new_semantic(KclErrorDetails::new(
661 "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(),
662 vec![args.source_range],
663 )))
664 }
665 }
666 }
667 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
668 let p1_x = &pos1[0];
669 let p1_y = &pos1[1];
670 match (p1_x, p1_y) {
671 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
672 let p0_x = KclValue::Number {
673 value: p0_x.n,
674 ty: p0_x.ty,
675 meta: vec![args.source_range.into()],
676 };
677 let p0_y = KclValue::Number {
678 value: p0_y.n,
679 ty: p0_y.ty,
680 meta: vec![args.source_range.into()],
681 };
682 let (constraint_x, constraint_y) =
683 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
684
685 #[cfg(feature = "artifact-graph")]
686 let constraint_id = exec_state.next_object_id();
687 let Some(sketch_state) = exec_state.sketch_block_mut() else {
689 return Err(KclError::new_semantic(KclErrorDetails::new(
690 "coincident() can only be used inside a sketch block".to_owned(),
691 vec![args.source_range],
692 )));
693 };
694 sketch_state.solver_constraints.push(constraint_x);
695 sketch_state.solver_constraints.push(constraint_y);
696 #[cfg(feature = "artifact-graph")]
697 {
698 let constraint = crate::front::Constraint::Coincident(Coincident {
699 segments: vec![unsolved0.object_id, unsolved1.object_id],
700 });
701 sketch_state.sketch_constraints.push(constraint_id);
702 track_constraint(constraint_id, constraint, exec_state, &args);
703 }
704 Ok(KclValue::none())
705 }
706 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
707 if *p0_x != *p1_x || *p0_y != *p1_y {
708 return Err(KclError::new_semantic(KclErrorDetails::new(
709 "Coincident constraint between two fixed points failed since coordinates differ"
710 .to_owned(),
711 vec![args.source_range],
712 )));
713 }
714 Ok(KclValue::none())
715 }
716 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
717 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
718 Err(KclError::new_semantic(KclErrorDetails::new(
720 "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(),
721 vec![args.source_range],
722 )))
723 }
724 }
725 }
726 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
727 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
728 Err(KclError::new_semantic(KclErrorDetails::new(
730 "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(),
731 vec![args.source_range],
732 )))
733 }
734 }
735 }
736 (
738 UnsolvedSegmentKind::Point {
739 position: point_pos, ..
740 },
741 UnsolvedSegmentKind::Line {
742 start: line_start,
743 end: line_end,
744 ..
745 },
746 )
747 | (
748 UnsolvedSegmentKind::Line {
749 start: line_start,
750 end: line_end,
751 ..
752 },
753 UnsolvedSegmentKind::Point {
754 position: point_pos, ..
755 },
756 ) => {
757 let point_x = &point_pos[0];
758 let point_y = &point_pos[1];
759 match (point_x, point_y) {
760 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
761 let (start_x, start_y) = (&line_start[0], &line_start[1]);
763 let (end_x, end_y) = (&line_end[0], &line_end[1]);
764
765 match (start_x, start_y, end_x, end_y) {
766 (
767 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
768 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
769 ) => {
770 let point = DatumPoint::new_xy(
771 point_x.to_constraint_id(range)?,
772 point_y.to_constraint_id(range)?,
773 );
774 let line_segment = DatumLineSegment::new(
775 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
776 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
777 );
778 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
779
780 #[cfg(feature = "artifact-graph")]
781 let constraint_id = exec_state.next_object_id();
782
783 let Some(sketch_state) = exec_state.sketch_block_mut() else {
784 return Err(KclError::new_semantic(KclErrorDetails::new(
785 "coincident() can only be used inside a sketch block".to_owned(),
786 vec![args.source_range],
787 )));
788 };
789 sketch_state.solver_constraints.push(constraint);
790 #[cfg(feature = "artifact-graph")]
791 {
792 let constraint = crate::front::Constraint::Coincident(Coincident {
793 segments: vec![unsolved0.object_id, unsolved1.object_id],
794 });
795 sketch_state.sketch_constraints.push(constraint_id);
796 track_constraint(constraint_id, constraint, exec_state, &args);
797 }
798 Ok(KclValue::none())
799 }
800 _ => Err(KclError::new_semantic(KclErrorDetails::new(
801 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
802 vec![args.source_range],
803 ))),
804 }
805 }
806 _ => Err(KclError::new_semantic(KclErrorDetails::new(
807 "Point coordinates must be sketch variables for point-segment coincident constraint"
808 .to_owned(),
809 vec![args.source_range],
810 ))),
811 }
812 }
813 (
815 UnsolvedSegmentKind::Point {
816 position: point_pos, ..
817 },
818 UnsolvedSegmentKind::Arc {
819 start: arc_start,
820 end: arc_end,
821 center: arc_center,
822 ..
823 },
824 )
825 | (
826 UnsolvedSegmentKind::Arc {
827 start: arc_start,
828 end: arc_end,
829 center: arc_center,
830 ..
831 },
832 UnsolvedSegmentKind::Point {
833 position: point_pos, ..
834 },
835 ) => {
836 let point_x = &point_pos[0];
837 let point_y = &point_pos[1];
838 match (point_x, point_y) {
839 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
840 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
842 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
843 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
844
845 match (center_x, center_y, start_x, start_y, end_x, end_y) {
846 (
847 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
848 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
849 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
850 ) => {
851 let point = DatumPoint::new_xy(
852 point_x.to_constraint_id(range)?,
853 point_y.to_constraint_id(range)?,
854 );
855 let circular_arc = DatumCircularArc {
856 center: DatumPoint::new_xy(
857 cx.to_constraint_id(range)?,
858 cy.to_constraint_id(range)?,
859 ),
860 start: DatumPoint::new_xy(
861 sx.to_constraint_id(range)?,
862 sy.to_constraint_id(range)?,
863 ),
864 end: DatumPoint::new_xy(
865 ex.to_constraint_id(range)?,
866 ey.to_constraint_id(range)?,
867 ),
868 };
869 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
870
871 #[cfg(feature = "artifact-graph")]
872 let constraint_id = exec_state.next_object_id();
873
874 let Some(sketch_state) = exec_state.sketch_block_mut() else {
875 return Err(KclError::new_semantic(KclErrorDetails::new(
876 "coincident() can only be used inside a sketch block".to_owned(),
877 vec![args.source_range],
878 )));
879 };
880 sketch_state.solver_constraints.push(constraint);
881 #[cfg(feature = "artifact-graph")]
882 {
883 let constraint = crate::front::Constraint::Coincident(Coincident {
884 segments: vec![unsolved0.object_id, unsolved1.object_id],
885 });
886 sketch_state.sketch_constraints.push(constraint_id);
887 track_constraint(constraint_id, constraint, exec_state, &args);
888 }
889 Ok(KclValue::none())
890 }
891 _ => Err(KclError::new_semantic(KclErrorDetails::new(
892 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
893 vec![args.source_range],
894 ))),
895 }
896 }
897 _ => Err(KclError::new_semantic(KclErrorDetails::new(
898 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
899 vec![args.source_range],
900 ))),
901 }
902 }
903 _ => Err(KclError::new_semantic(KclErrorDetails::new(
904 format!(
905 "coincident supports point-point or point-segment; found {:?} and {:?}",
906 &unsolved0.kind, &unsolved1.kind
907 ),
908 vec![args.source_range],
909 ))),
910 }
911 }
912 _ => Err(KclError::new_semantic(KclErrorDetails::new(
913 "All inputs must be segments (points or lines), created from point(), line(), or another sketch function"
914 .to_owned(),
915 vec![args.source_range],
916 ))),
917 }
918}
919
920#[cfg(feature = "artifact-graph")]
921fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
922 exec_state.add_scene_object(
923 Object {
924 id: constraint_id,
925 kind: ObjectKind::Constraint { constraint },
926 label: Default::default(),
927 comments: Default::default(),
928 artifact_id: ArtifactId::constraint(),
929 source: args.source_range.into(),
930 },
931 args.source_range,
932 );
933}
934
935fn coincident_constraints_fixed(
937 p0_x: SketchVarId,
938 p0_y: SketchVarId,
939 p1_x: &KclValue,
940 p1_y: &KclValue,
941 exec_state: &mut ExecState,
942 args: &Args,
943) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
944 let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
945 let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
946 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
947 let message = format!(
948 "Expected number after coercion, but found {}",
949 p1_x_number_value.human_friendly_type()
950 );
951 debug_assert!(false, "{}", &message);
952 return Err(KclError::new_internal(KclErrorDetails::new(
953 message,
954 vec![args.source_range],
955 )));
956 };
957 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
958 let message = format!(
959 "Expected number after coercion, but found {}",
960 p1_y_number_value.human_friendly_type()
961 );
962 debug_assert!(false, "{}", &message);
963 return Err(KclError::new_internal(KclErrorDetails::new(
964 message,
965 vec![args.source_range],
966 )));
967 };
968 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
969 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
970 Ok((constraint_x, constraint_y))
971}
972
973pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
974 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
975 "points",
976 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
977 exec_state,
978 )?;
979 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
980 KclError::new_semantic(KclErrorDetails::new(
981 "must have two input points".to_owned(),
982 vec![args.source_range],
983 ))
984 })?;
985
986 match (&point0, &point1) {
987 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
988 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
989 return Err(KclError::new_semantic(KclErrorDetails::new(
990 "first point must be an unsolved segment".to_owned(),
991 vec![args.source_range],
992 )));
993 };
994 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
995 return Err(KclError::new_semantic(KclErrorDetails::new(
996 "second point must be an unsolved segment".to_owned(),
997 vec![args.source_range],
998 )));
999 };
1000 match (&unsolved0.kind, &unsolved1.kind) {
1001 (
1002 UnsolvedSegmentKind::Point { position: pos0, .. },
1003 UnsolvedSegmentKind::Point { position: pos1, .. },
1004 ) => {
1005 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1008 (
1009 UnsolvedExpr::Unknown(p0_x),
1010 UnsolvedExpr::Unknown(p0_y),
1011 UnsolvedExpr::Unknown(p1_x),
1012 UnsolvedExpr::Unknown(p1_y),
1013 ) => {
1014 let sketch_constraint = SketchConstraint {
1016 kind: SketchConstraintKind::Distance {
1017 points: [
1018 ConstrainablePoint2d {
1019 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1020 object_id: unsolved0.object_id,
1021 },
1022 ConstrainablePoint2d {
1023 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1024 object_id: unsolved1.object_id,
1025 },
1026 ],
1027 },
1028 meta: vec![args.source_range.into()],
1029 };
1030 Ok(KclValue::SketchConstraint {
1031 value: Box::new(sketch_constraint),
1032 })
1033 }
1034 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1035 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1036 vec![args.source_range],
1037 ))),
1038 }
1039 }
1040 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1041 "distance() arguments must be unsolved points".to_owned(),
1042 vec![args.source_range],
1043 ))),
1044 }
1045 }
1046 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1047 "distance() arguments must be point segments".to_owned(),
1048 vec![args.source_range],
1049 ))),
1050 }
1051}
1052
1053pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1054 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1055 "lines",
1056 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1057 exec_state,
1058 )?;
1059 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1060 KclError::new_semantic(KclErrorDetails::new(
1061 "must have two input lines".to_owned(),
1062 vec![args.source_range],
1063 ))
1064 })?;
1065
1066 let KclValue::Segment { value: segment0 } = &line0 else {
1067 return Err(KclError::new_semantic(KclErrorDetails::new(
1068 "line argument must be a Segment".to_owned(),
1069 vec![args.source_range],
1070 )));
1071 };
1072 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1073 return Err(KclError::new_internal(KclErrorDetails::new(
1074 "line must be an unsolved Segment".to_owned(),
1075 vec![args.source_range],
1076 )));
1077 };
1078 let UnsolvedSegmentKind::Line {
1079 start: start0,
1080 end: end0,
1081 ..
1082 } = &unsolved0.kind
1083 else {
1084 return Err(KclError::new_semantic(KclErrorDetails::new(
1085 "line argument must be a line, no other type of Segment".to_owned(),
1086 vec![args.source_range],
1087 )));
1088 };
1089 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1090 return Err(KclError::new_semantic(KclErrorDetails::new(
1091 "line's start x coordinate must be a var".to_owned(),
1092 vec![args.source_range],
1093 )));
1094 };
1095 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1096 return Err(KclError::new_semantic(KclErrorDetails::new(
1097 "line's start y coordinate must be a var".to_owned(),
1098 vec![args.source_range],
1099 )));
1100 };
1101 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1102 return Err(KclError::new_semantic(KclErrorDetails::new(
1103 "line's end x coordinate must be a var".to_owned(),
1104 vec![args.source_range],
1105 )));
1106 };
1107 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1108 return Err(KclError::new_semantic(KclErrorDetails::new(
1109 "line's end y coordinate must be a var".to_owned(),
1110 vec![args.source_range],
1111 )));
1112 };
1113 let KclValue::Segment { value: segment1 } = &line1 else {
1114 return Err(KclError::new_semantic(KclErrorDetails::new(
1115 "line argument must be a Segment".to_owned(),
1116 vec![args.source_range],
1117 )));
1118 };
1119 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1120 return Err(KclError::new_internal(KclErrorDetails::new(
1121 "line must be an unsolved Segment".to_owned(),
1122 vec![args.source_range],
1123 )));
1124 };
1125 let UnsolvedSegmentKind::Line {
1126 start: start1,
1127 end: end1,
1128 ..
1129 } = &unsolved1.kind
1130 else {
1131 return Err(KclError::new_semantic(KclErrorDetails::new(
1132 "line argument must be a line, no other type of Segment".to_owned(),
1133 vec![args.source_range],
1134 )));
1135 };
1136 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1137 return Err(KclError::new_semantic(KclErrorDetails::new(
1138 "line's start x coordinate must be a var".to_owned(),
1139 vec![args.source_range],
1140 )));
1141 };
1142 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1143 return Err(KclError::new_semantic(KclErrorDetails::new(
1144 "line's start y coordinate must be a var".to_owned(),
1145 vec![args.source_range],
1146 )));
1147 };
1148 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1149 return Err(KclError::new_semantic(KclErrorDetails::new(
1150 "line's end x coordinate must be a var".to_owned(),
1151 vec![args.source_range],
1152 )));
1153 };
1154 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1155 return Err(KclError::new_semantic(KclErrorDetails::new(
1156 "line's end y coordinate must be a var".to_owned(),
1157 vec![args.source_range],
1158 )));
1159 };
1160
1161 let range = args.source_range;
1162 let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1163 line0_p0_x.to_constraint_id(range)?,
1164 line0_p0_y.to_constraint_id(range)?,
1165 );
1166 let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1167 line0_p1_x.to_constraint_id(range)?,
1168 line0_p1_y.to_constraint_id(range)?,
1169 );
1170 let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1171 let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1172 line1_p0_x.to_constraint_id(range)?,
1173 line1_p0_y.to_constraint_id(range)?,
1174 );
1175 let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1176 line1_p1_x.to_constraint_id(range)?,
1177 line1_p1_y.to_constraint_id(range)?,
1178 );
1179 let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1180 let constraint = SolverConstraint::LinesEqualLength(solver_line0, solver_line1);
1181 #[cfg(feature = "artifact-graph")]
1182 let constraint_id = exec_state.next_object_id();
1183 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1185 return Err(KclError::new_semantic(KclErrorDetails::new(
1186 "equalLength() can only be used inside a sketch block".to_owned(),
1187 vec![args.source_range],
1188 )));
1189 };
1190 sketch_state.solver_constraints.push(constraint);
1191 #[cfg(feature = "artifact-graph")]
1192 {
1193 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
1194 lines: vec![unsolved0.object_id, unsolved1.object_id],
1195 });
1196 sketch_state.sketch_constraints.push(constraint_id);
1197 track_constraint(constraint_id, constraint, exec_state, &args);
1198 }
1199 Ok(KclValue::none())
1200}
1201
1202#[derive(Debug, Clone, Copy)]
1203pub(crate) enum LinesAtAngleKind {
1204 Parallel,
1205 Perpendicular,
1206}
1207
1208impl LinesAtAngleKind {
1209 pub fn to_function_name(self) -> &'static str {
1210 match self {
1211 LinesAtAngleKind::Parallel => "parallel",
1212 LinesAtAngleKind::Perpendicular => "perpendicular",
1213 }
1214 }
1215
1216 fn to_solver_angle(self) -> kcl_ezpz::datatypes::AngleKind {
1217 match self {
1218 LinesAtAngleKind::Parallel => kcl_ezpz::datatypes::AngleKind::Parallel,
1219 LinesAtAngleKind::Perpendicular => kcl_ezpz::datatypes::AngleKind::Perpendicular,
1220 }
1221 }
1222
1223 #[cfg(feature = "artifact-graph")]
1224 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
1225 match self {
1226 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
1227 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
1228 }
1229 }
1230}
1231
1232pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1233 lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
1234}
1235pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1236 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
1237}
1238
1239async fn lines_at_angle(
1240 angle_kind: LinesAtAngleKind,
1241 exec_state: &mut ExecState,
1242 args: Args,
1243) -> Result<KclValue, KclError> {
1244 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1245 "lines",
1246 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1247 exec_state,
1248 )?;
1249 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1250 KclError::new_semantic(KclErrorDetails::new(
1251 "must have two input lines".to_owned(),
1252 vec![args.source_range],
1253 ))
1254 })?;
1255
1256 let KclValue::Segment { value: segment0 } = &line0 else {
1257 return Err(KclError::new_semantic(KclErrorDetails::new(
1258 "line argument must be a Segment".to_owned(),
1259 vec![args.source_range],
1260 )));
1261 };
1262 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1263 return Err(KclError::new_internal(KclErrorDetails::new(
1264 "line must be an unsolved Segment".to_owned(),
1265 vec![args.source_range],
1266 )));
1267 };
1268 let UnsolvedSegmentKind::Line {
1269 start: start0,
1270 end: end0,
1271 ..
1272 } = &unsolved0.kind
1273 else {
1274 return Err(KclError::new_semantic(KclErrorDetails::new(
1275 "line argument must be a line, no other type of Segment".to_owned(),
1276 vec![args.source_range],
1277 )));
1278 };
1279 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1280 return Err(KclError::new_semantic(KclErrorDetails::new(
1281 "line's start x coordinate must be a var".to_owned(),
1282 vec![args.source_range],
1283 )));
1284 };
1285 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1286 return Err(KclError::new_semantic(KclErrorDetails::new(
1287 "line's start y coordinate must be a var".to_owned(),
1288 vec![args.source_range],
1289 )));
1290 };
1291 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1292 return Err(KclError::new_semantic(KclErrorDetails::new(
1293 "line's end x coordinate must be a var".to_owned(),
1294 vec![args.source_range],
1295 )));
1296 };
1297 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1298 return Err(KclError::new_semantic(KclErrorDetails::new(
1299 "line's end y coordinate must be a var".to_owned(),
1300 vec![args.source_range],
1301 )));
1302 };
1303 let KclValue::Segment { value: segment1 } = &line1 else {
1304 return Err(KclError::new_semantic(KclErrorDetails::new(
1305 "line argument must be a Segment".to_owned(),
1306 vec![args.source_range],
1307 )));
1308 };
1309 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1310 return Err(KclError::new_internal(KclErrorDetails::new(
1311 "line must be an unsolved Segment".to_owned(),
1312 vec![args.source_range],
1313 )));
1314 };
1315 let UnsolvedSegmentKind::Line {
1316 start: start1,
1317 end: end1,
1318 ..
1319 } = &unsolved1.kind
1320 else {
1321 return Err(KclError::new_semantic(KclErrorDetails::new(
1322 "line argument must be a line, no other type of Segment".to_owned(),
1323 vec![args.source_range],
1324 )));
1325 };
1326 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1327 return Err(KclError::new_semantic(KclErrorDetails::new(
1328 "line's start x coordinate must be a var".to_owned(),
1329 vec![args.source_range],
1330 )));
1331 };
1332 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1333 return Err(KclError::new_semantic(KclErrorDetails::new(
1334 "line's start y coordinate must be a var".to_owned(),
1335 vec![args.source_range],
1336 )));
1337 };
1338 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1339 return Err(KclError::new_semantic(KclErrorDetails::new(
1340 "line's end x coordinate must be a var".to_owned(),
1341 vec![args.source_range],
1342 )));
1343 };
1344 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1345 return Err(KclError::new_semantic(KclErrorDetails::new(
1346 "line's end y coordinate must be a var".to_owned(),
1347 vec![args.source_range],
1348 )));
1349 };
1350
1351 let range = args.source_range;
1352 let solver_line0_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1353 line0_p0_x.to_constraint_id(range)?,
1354 line0_p0_y.to_constraint_id(range)?,
1355 );
1356 let solver_line0_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1357 line0_p1_x.to_constraint_id(range)?,
1358 line0_p1_y.to_constraint_id(range)?,
1359 );
1360 let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
1361 let solver_line1_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1362 line1_p0_x.to_constraint_id(range)?,
1363 line1_p0_y.to_constraint_id(range)?,
1364 );
1365 let solver_line1_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1366 line1_p1_x.to_constraint_id(range)?,
1367 line1_p1_y.to_constraint_id(range)?,
1368 );
1369 let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
1370 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
1371 #[cfg(feature = "artifact-graph")]
1372 let constraint_id = exec_state.next_object_id();
1373 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1375 return Err(KclError::new_semantic(KclErrorDetails::new(
1376 format!(
1377 "{}() can only be used inside a sketch block",
1378 angle_kind.to_function_name()
1379 ),
1380 vec![args.source_range],
1381 )));
1382 };
1383 sketch_state.solver_constraints.push(constraint);
1384 #[cfg(feature = "artifact-graph")]
1385 {
1386 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
1387 sketch_state.sketch_constraints.push(constraint_id);
1388 track_constraint(constraint_id, constraint, exec_state, &args);
1389 }
1390 Ok(KclValue::none())
1391}
1392
1393pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1394 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1395 let KclValue::Segment { value: segment } = line else {
1396 return Err(KclError::new_semantic(KclErrorDetails::new(
1397 "line argument must be a Segment".to_owned(),
1398 vec![args.source_range],
1399 )));
1400 };
1401 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1402 return Err(KclError::new_internal(KclErrorDetails::new(
1403 "line must be an unsolved Segment".to_owned(),
1404 vec![args.source_range],
1405 )));
1406 };
1407 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1408 return Err(KclError::new_semantic(KclErrorDetails::new(
1409 "line argument must be a line, no other type of Segment".to_owned(),
1410 vec![args.source_range],
1411 )));
1412 };
1413 let p0_x = &start[0];
1414 let p0_y = &start[1];
1415 let p1_x = &end[0];
1416 let p1_y = &end[1];
1417 match (p0_x, p0_y, p1_x, p1_y) {
1418 (
1419 UnsolvedExpr::Unknown(p0_x),
1420 UnsolvedExpr::Unknown(p0_y),
1421 UnsolvedExpr::Unknown(p1_x),
1422 UnsolvedExpr::Unknown(p1_y),
1423 ) => {
1424 let range = args.source_range;
1425 let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1426 p0_x.to_constraint_id(range)?,
1427 p0_y.to_constraint_id(range)?,
1428 );
1429 let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1430 p1_x.to_constraint_id(range)?,
1431 p1_y.to_constraint_id(range)?,
1432 );
1433 let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1434 let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
1435 #[cfg(feature = "artifact-graph")]
1436 let constraint_id = exec_state.next_object_id();
1437 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1439 return Err(KclError::new_semantic(KclErrorDetails::new(
1440 "horizontal() can only be used inside a sketch block".to_owned(),
1441 vec![args.source_range],
1442 )));
1443 };
1444 sketch_state.solver_constraints.push(constraint);
1445 #[cfg(feature = "artifact-graph")]
1446 {
1447 let constraint = crate::front::Constraint::Horizontal(Horizontal {
1448 line: unsolved.object_id,
1449 });
1450 sketch_state.sketch_constraints.push(constraint_id);
1451 track_constraint(constraint_id, constraint, exec_state, &args);
1452 }
1453 Ok(KclValue::none())
1454 }
1455 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1456 "line's x and y coordinates of both start and end must be vars".to_owned(),
1457 vec![args.source_range],
1458 ))),
1459 }
1460}
1461
1462pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1463 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1464 let KclValue::Segment { value: segment } = line else {
1465 return Err(KclError::new_semantic(KclErrorDetails::new(
1466 "line argument must be a Segment".to_owned(),
1467 vec![args.source_range],
1468 )));
1469 };
1470 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1471 return Err(KclError::new_internal(KclErrorDetails::new(
1472 "line must be an unsolved Segment".to_owned(),
1473 vec![args.source_range],
1474 )));
1475 };
1476 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1477 return Err(KclError::new_semantic(KclErrorDetails::new(
1478 "line argument must be a line, no other type of Segment".to_owned(),
1479 vec![args.source_range],
1480 )));
1481 };
1482 let p0_x = &start[0];
1483 let p0_y = &start[1];
1484 let p1_x = &end[0];
1485 let p1_y = &end[1];
1486 match (p0_x, p0_y, p1_x, p1_y) {
1487 (
1488 UnsolvedExpr::Unknown(p0_x),
1489 UnsolvedExpr::Unknown(p0_y),
1490 UnsolvedExpr::Unknown(p1_x),
1491 UnsolvedExpr::Unknown(p1_y),
1492 ) => {
1493 let range = args.source_range;
1494 let solver_p0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1495 p0_x.to_constraint_id(range)?,
1496 p0_y.to_constraint_id(range)?,
1497 );
1498 let solver_p1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
1499 p1_x.to_constraint_id(range)?,
1500 p1_y.to_constraint_id(range)?,
1501 );
1502 let solver_line = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
1503 let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1504 #[cfg(feature = "artifact-graph")]
1505 let constraint_id = exec_state.next_object_id();
1506 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1508 return Err(KclError::new_semantic(KclErrorDetails::new(
1509 "vertical() can only be used inside a sketch block".to_owned(),
1510 vec![args.source_range],
1511 )));
1512 };
1513 sketch_state.solver_constraints.push(constraint);
1514 #[cfg(feature = "artifact-graph")]
1515 {
1516 let constraint = crate::front::Constraint::Vertical(Vertical {
1517 line: unsolved.object_id,
1518 });
1519 sketch_state.sketch_constraints.push(constraint_id);
1520 track_constraint(constraint_id, constraint, exec_state, &args);
1521 }
1522 Ok(KclValue::none())
1523 }
1524 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1525 "line's x and y coordinates of both start and end must be vars".to_owned(),
1526 vec![args.source_range],
1527 ))),
1528 }
1529}