1use anyhow::Result;
2
3use crate::{
4 errors::{KclError, KclErrorDetails},
5 execution::{
6 AbstractSegment, ConstrainablePoint2d, ExecState, KclValue, SegmentRepr, SketchConstraint,
7 SketchConstraintKind, SketchVarId, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind,
8 normalize_to_solver_unit,
9 types::{ArrayLen, PrimitiveType, RuntimeType},
10 },
11 front::{LineCtor, Point2d, PointCtor},
12 std::Args,
13};
14#[cfg(feature = "artifact-graph")]
15use crate::{
16 execution::ArtifactId,
17 front::{Coincident, Constraint, Horizontal, LinesEqualLength, Object, ObjectId, ObjectKind, Parallel, Vertical},
18};
19
20pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
22 if at.len() != 2 {
23 return Err(KclError::new_semantic(KclErrorDetails::new(
24 "at must be a 2D point".to_owned(),
25 vec![args.source_range],
26 )));
27 }
28 let at_x_value = at.first().unwrap().clone();
29 let Some(at_x) = at_x_value.as_unsolved_expr() else {
30 return Err(KclError::new_semantic(KclErrorDetails::new(
31 "at x must be a number or sketch var".to_owned(),
32 vec![args.source_range],
33 )));
34 };
35 let at_y_value = at.get(1).unwrap().clone();
36 let Some(at_y) = at_y_value.as_unsolved_expr() else {
37 return Err(KclError::new_semantic(KclErrorDetails::new(
38 "at y must be a number or sketch var".to_owned(),
39 vec![args.source_range],
40 )));
41 };
42 let ctor = PointCtor {
43 position: Point2d {
44 x: at_x_value.to_sketch_expr().ok_or_else(|| {
45 KclError::new_semantic(KclErrorDetails::new(
46 "unable to convert numeric type to suffix".to_owned(),
47 vec![args.source_range],
48 ))
49 })?,
50 y: at_y_value.to_sketch_expr().ok_or_else(|| {
51 KclError::new_semantic(KclErrorDetails::new(
52 "unable to convert numeric type to suffix".to_owned(),
53 vec![args.source_range],
54 ))
55 })?,
56 },
57 };
58 let segment = UnsolvedSegment {
59 object_id: exec_state.next_object_id(),
60 kind: UnsolvedSegmentKind::Point {
61 position: [at_x, at_y],
62 ctor: Box::new(ctor),
63 },
64 meta: vec![args.source_range.into()],
65 };
66 #[cfg(feature = "artifact-graph")]
67 let optional_constraints = {
68 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range);
69
70 let mut optional_constraints = Vec::new();
71 if exec_state.segment_ids_edited_contains(&object_id) {
72 if let Some(at_x_var) = at_x_value.as_sketch_var() {
73 let x_initial_value = at_x_var.initial_value_to_solver_units(
74 exec_state,
75 args.source_range,
76 "edited segment fixed constraint value",
77 )?;
78 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
79 at_x_var.id.to_constraint_id(args.source_range)?,
80 x_initial_value.n,
81 ));
82 }
83 if let Some(at_y_var) = at_y_value.as_sketch_var() {
84 let y_initial_value = at_y_var.initial_value_to_solver_units(
85 exec_state,
86 args.source_range,
87 "edited segment fixed constraint value",
88 )?;
89 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
90 at_y_var.id.to_constraint_id(args.source_range)?,
91 y_initial_value.n,
92 ));
93 }
94 }
95 optional_constraints
96 };
97
98 let Some(sketch_state) = exec_state.sketch_block_mut() else {
100 return Err(KclError::new_semantic(KclErrorDetails::new(
101 "line() can only be used inside a sketch block".to_owned(),
102 vec![args.source_range],
103 )));
104 };
105 sketch_state.needed_by_engine.push(segment.clone());
106
107 #[cfg(feature = "artifact-graph")]
108 sketch_state.solver_optional_constraints.extend(optional_constraints);
109
110 let meta = segment.meta.clone();
111 let abstract_segment = AbstractSegment {
112 repr: SegmentRepr::Unsolved { segment },
113 meta,
114 };
115 Ok(KclValue::Segment {
116 value: Box::new(abstract_segment),
117 })
118}
119
120pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
121 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
122 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
124 if start.len() != 2 {
125 return Err(KclError::new_semantic(KclErrorDetails::new(
126 "start must be a 2D point".to_owned(),
127 vec![args.source_range],
128 )));
129 }
130 if end.len() != 2 {
131 return Err(KclError::new_semantic(KclErrorDetails::new(
132 "end must be a 2D point".to_owned(),
133 vec![args.source_range],
134 )));
135 }
136 let start_x_value = start.first().unwrap().clone();
138 let Some(start_x) = start_x_value.as_unsolved_expr() else {
139 return Err(KclError::new_semantic(KclErrorDetails::new(
140 "start x must be a number or sketch var".to_owned(),
141 vec![args.source_range],
142 )));
143 };
144 let start_y_value = start.get(1).unwrap().clone();
145 let Some(start_y) = start_y_value.as_unsolved_expr() else {
146 return Err(KclError::new_semantic(KclErrorDetails::new(
147 "start y must be a number or sketch var".to_owned(),
148 vec![args.source_range],
149 )));
150 };
151 let end_x_value = end.first().unwrap().clone();
152 let Some(end_x) = end_x_value.as_unsolved_expr() else {
153 return Err(KclError::new_semantic(KclErrorDetails::new(
154 "end x must be a number or sketch var".to_owned(),
155 vec![args.source_range],
156 )));
157 };
158 let end_y_value = end.get(1).unwrap().clone();
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 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 if let Some(start_x_var) = start_x_value.as_sketch_var() {
219 let x_initial_value = start_x_var.initial_value_to_solver_units(
220 exec_state,
221 args.source_range,
222 "edited segment fixed constraint value",
223 )?;
224 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
225 start_x_var.id.to_constraint_id(args.source_range)?,
226 x_initial_value.n,
227 ));
228 }
229 if let Some(start_y_var) = start_y_value.as_sketch_var() {
230 let y_initial_value = start_y_var.initial_value_to_solver_units(
231 exec_state,
232 args.source_range,
233 "edited segment fixed constraint value",
234 )?;
235 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
236 start_y_var.id.to_constraint_id(args.source_range)?,
237 y_initial_value.n,
238 ));
239 }
240 }
241 if exec_state.segment_ids_edited_contains(&end_object_id) {
242 if let Some(end_x_var) = end_x_value.as_sketch_var() {
243 let x_initial_value = end_x_var.initial_value_to_solver_units(
244 exec_state,
245 args.source_range,
246 "edited segment fixed constraint value",
247 )?;
248 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
249 end_x_var.id.to_constraint_id(args.source_range)?,
250 x_initial_value.n,
251 ));
252 }
253 if let Some(end_y_var) = end_y_value.as_sketch_var() {
254 let y_initial_value = end_y_var.initial_value_to_solver_units(
255 exec_state,
256 args.source_range,
257 "edited segment fixed constraint value",
258 )?;
259 optional_constraints.push(kcl_ezpz::Constraint::Fixed(
260 end_y_var.id.to_constraint_id(args.source_range)?,
261 y_initial_value.n,
262 ));
263 }
264 }
265 optional_constraints
266 };
267
268 let Some(sketch_state) = exec_state.sketch_block_mut() else {
270 return Err(KclError::new_semantic(KclErrorDetails::new(
271 "line() can only be used inside a sketch block".to_owned(),
272 vec![args.source_range],
273 )));
274 };
275 sketch_state.needed_by_engine.push(segment.clone());
276
277 #[cfg(feature = "artifact-graph")]
278 sketch_state.solver_optional_constraints.extend(optional_constraints);
279
280 let meta = segment.meta.clone();
281 let abstract_segment = AbstractSegment {
282 repr: SegmentRepr::Unsolved { segment },
283 meta,
284 };
285 Ok(KclValue::Segment {
286 value: Box::new(abstract_segment),
287 })
288}
289
290pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
291 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
292 "points",
293 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
294 exec_state,
295 )?;
296 if points.len() != 2 {
297 return Err(KclError::new_semantic(KclErrorDetails::new(
298 "must have two input points".to_owned(),
299 vec![args.source_range],
300 )));
301 }
302
303 let range = args.source_range;
304 match (&points[0], &points[1]) {
305 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
306 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
307 return Err(KclError::new_semantic(KclErrorDetails::new(
308 "first point must be an unsolved segment".to_owned(),
309 vec![args.source_range],
310 )));
311 };
312 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
313 return Err(KclError::new_semantic(KclErrorDetails::new(
314 "second point must be an unsolved segment".to_owned(),
315 vec![args.source_range],
316 )));
317 };
318 match (&unsolved0.kind, &unsolved1.kind) {
319 (
320 UnsolvedSegmentKind::Point { position: pos0, .. },
321 UnsolvedSegmentKind::Point { position: pos1, .. },
322 ) => {
323 let p0_x = &pos0[0];
324 let p0_y = &pos0[1];
325 match (p0_x, p0_y) {
326 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
327 let p1_x = &pos1[0];
328 let p1_y = &pos1[1];
329 match (p1_x, p1_y) {
330 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
331 let constraint = kcl_ezpz::Constraint::PointsCoincident(
332 kcl_ezpz::datatypes::DatumPoint::new_xy(
333 p0_x.to_constraint_id(range)?,
334 p0_y.to_constraint_id(range)?,
335 ),
336 kcl_ezpz::datatypes::DatumPoint::new_xy(
337 p1_x.to_constraint_id(range)?,
338 p1_y.to_constraint_id(range)?,
339 ),
340 );
341 #[cfg(feature = "artifact-graph")]
342 let constraint_id = exec_state.next_object_id();
343 let Some(sketch_state) = exec_state.sketch_block_mut() else {
345 return Err(KclError::new_semantic(KclErrorDetails::new(
346 "coincident() can only be used inside a sketch block".to_owned(),
347 vec![args.source_range],
348 )));
349 };
350 sketch_state.solver_constraints.push(constraint);
351 #[cfg(feature = "artifact-graph")]
352 {
353 let constraint = crate::front::Constraint::Coincident(Coincident {
354 points: vec![unsolved0.object_id, unsolved1.object_id],
355 });
356 sketch_state.sketch_constraints.push(constraint_id);
357 track_constraint(constraint_id, constraint, exec_state, &args);
358 }
359 Ok(KclValue::none())
360 }
361 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
362 let p1_x = KclValue::Number {
363 value: p1_x.n,
364 ty: p1_x.ty,
365 meta: vec![args.source_range.into()],
366 };
367 let p1_y = KclValue::Number {
368 value: p1_y.n,
369 ty: p1_y.ty,
370 meta: vec![args.source_range.into()],
371 };
372 let (constraint_x, constraint_y) =
373 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
374
375 #[cfg(feature = "artifact-graph")]
376 let constraint_id = exec_state.next_object_id();
377 let Some(sketch_state) = exec_state.sketch_block_mut() else {
379 return Err(KclError::new_semantic(KclErrorDetails::new(
380 "coincident() can only be used inside a sketch block".to_owned(),
381 vec![args.source_range],
382 )));
383 };
384 sketch_state.solver_constraints.push(constraint_x);
385 sketch_state.solver_constraints.push(constraint_y);
386 #[cfg(feature = "artifact-graph")]
387 {
388 let constraint = crate::front::Constraint::Coincident(Coincident {
389 points: vec![unsolved0.object_id, unsolved1.object_id],
390 });
391 sketch_state.sketch_constraints.push(constraint_id);
392 track_constraint(constraint_id, constraint, exec_state, &args);
393 }
394 Ok(KclValue::none())
395 }
396 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
397 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
398 Err(KclError::new_semantic(KclErrorDetails::new(
400 "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(),
401 vec![args.source_range],
402 )))
403 }
404 }
405 }
406 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
407 let p1_x = &pos1[0];
408 let p1_y = &pos1[1];
409 match (p1_x, p1_y) {
410 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
411 let p0_x = KclValue::Number {
412 value: p0_x.n,
413 ty: p0_x.ty,
414 meta: vec![args.source_range.into()],
415 };
416 let p0_y = KclValue::Number {
417 value: p0_y.n,
418 ty: p0_y.ty,
419 meta: vec![args.source_range.into()],
420 };
421 let (constraint_x, constraint_y) =
422 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
423
424 #[cfg(feature = "artifact-graph")]
425 let constraint_id = exec_state.next_object_id();
426 let Some(sketch_state) = exec_state.sketch_block_mut() else {
428 return Err(KclError::new_semantic(KclErrorDetails::new(
429 "coincident() can only be used inside a sketch block".to_owned(),
430 vec![args.source_range],
431 )));
432 };
433 sketch_state.solver_constraints.push(constraint_x);
434 sketch_state.solver_constraints.push(constraint_y);
435 #[cfg(feature = "artifact-graph")]
436 {
437 let constraint = crate::front::Constraint::Coincident(Coincident {
438 points: vec![unsolved0.object_id, unsolved1.object_id],
439 });
440 sketch_state.sketch_constraints.push(constraint_id);
441 track_constraint(constraint_id, constraint, exec_state, &args);
442 }
443 Ok(KclValue::none())
444 }
445 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
446 if *p0_x != *p1_x || *p0_y != *p1_y {
447 return Err(KclError::new_semantic(KclErrorDetails::new(
448 "Coincident constraint between two fixed points failed since coordinates differ"
449 .to_owned(),
450 vec![args.source_range],
451 )));
452 }
453 Ok(KclValue::none())
454 }
455 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
456 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
457 Err(KclError::new_semantic(KclErrorDetails::new(
459 "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(),
460 vec![args.source_range],
461 )))
462 }
463 }
464 }
465 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
466 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
467 Err(KclError::new_semantic(KclErrorDetails::new(
469 "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(),
470 vec![args.source_range],
471 )))
472 }
473 }
474 }
475 _ => Err(KclError::new_semantic(KclErrorDetails::new(
476 format!(
477 "both inputs of coincident must be points; found {:?} and {:?}",
478 &unsolved0.kind, &unsolved1.kind
479 ),
480 vec![args.source_range],
481 ))),
482 }
483 }
484 _ => Err(KclError::new_semantic(KclErrorDetails::new(
485 "All inputs must be points, created from point(), line(), or another sketch function".to_owned(),
486 vec![args.source_range],
487 ))),
488 }
489}
490
491#[cfg(feature = "artifact-graph")]
492fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
493 exec_state.add_scene_object(
494 Object {
495 id: constraint_id,
496 kind: ObjectKind::Constraint { constraint },
497 label: Default::default(),
498 comments: Default::default(),
499 artifact_id: ArtifactId::constraint(),
500 source: args.source_range.into(),
501 },
502 args.source_range,
503 );
504}
505
506fn coincident_constraints_fixed(
508 p0_x: SketchVarId,
509 p0_y: SketchVarId,
510 p1_x: &KclValue,
511 p1_y: &KclValue,
512 exec_state: &mut ExecState,
513 args: &Args,
514) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
515 let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
516 let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
517 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
518 let message = format!(
519 "Expected number after coercion, but found {}",
520 p1_x_number_value.human_friendly_type()
521 );
522 debug_assert!(false, "{}", &message);
523 return Err(KclError::new_internal(KclErrorDetails::new(
524 message,
525 vec![args.source_range],
526 )));
527 };
528 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
529 let message = format!(
530 "Expected number after coercion, but found {}",
531 p1_y_number_value.human_friendly_type()
532 );
533 debug_assert!(false, "{}", &message);
534 return Err(KclError::new_internal(KclErrorDetails::new(
535 message,
536 vec![args.source_range],
537 )));
538 };
539 let constraint_x = kcl_ezpz::Constraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
540 let constraint_y = kcl_ezpz::Constraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
541 Ok((constraint_x, constraint_y))
542}
543
544pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
545 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
546 "points",
547 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
548 exec_state,
549 )?;
550 if points.len() != 2 {
551 return Err(KclError::new_semantic(KclErrorDetails::new(
552 "must have two input points".to_owned(),
553 vec![args.source_range],
554 )));
555 }
556
557 match (&points[0], &points[1]) {
558 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
559 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
560 return Err(KclError::new_semantic(KclErrorDetails::new(
561 "first point must be an unsolved segment".to_owned(),
562 vec![args.source_range],
563 )));
564 };
565 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
566 return Err(KclError::new_semantic(KclErrorDetails::new(
567 "second point must be an unsolved segment".to_owned(),
568 vec![args.source_range],
569 )));
570 };
571 match (&unsolved0.kind, &unsolved1.kind) {
572 (
573 UnsolvedSegmentKind::Point { position: pos0, .. },
574 UnsolvedSegmentKind::Point { position: pos1, .. },
575 ) => {
576 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
579 (
580 UnsolvedExpr::Unknown(p0_x),
581 UnsolvedExpr::Unknown(p0_y),
582 UnsolvedExpr::Unknown(p1_x),
583 UnsolvedExpr::Unknown(p1_y),
584 ) => {
585 let sketch_constraint = SketchConstraint {
587 kind: SketchConstraintKind::Distance {
588 points: [
589 ConstrainablePoint2d {
590 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
591 object_id: unsolved0.object_id,
592 },
593 ConstrainablePoint2d {
594 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
595 object_id: unsolved1.object_id,
596 },
597 ],
598 },
599 meta: vec![args.source_range.into()],
600 };
601 Ok(KclValue::SketchConstraint {
602 value: Box::new(sketch_constraint),
603 })
604 }
605 _ => Err(KclError::new_semantic(KclErrorDetails::new(
606 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
607 vec![args.source_range],
608 ))),
609 }
610 }
611 _ => Err(KclError::new_semantic(KclErrorDetails::new(
612 "distance() arguments must be unsolved points".to_owned(),
613 vec![args.source_range],
614 ))),
615 }
616 }
617 _ => Err(KclError::new_semantic(KclErrorDetails::new(
618 "distance() arguments must be point segments".to_owned(),
619 vec![args.source_range],
620 ))),
621 }
622}
623
624pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
625 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
626 "lines",
627 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
628 exec_state,
629 )?;
630 if lines.len() != 2 {
631 return Err(KclError::new_semantic(KclErrorDetails::new(
632 "must have two input lines".to_owned(),
633 vec![args.source_range],
634 )));
635 }
636
637 let KclValue::Segment { value: segment0 } = &lines[0] else {
638 return Err(KclError::new_semantic(KclErrorDetails::new(
639 "line argument must be a Segment".to_owned(),
640 vec![args.source_range],
641 )));
642 };
643 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
644 return Err(KclError::new_internal(KclErrorDetails::new(
645 "line must be an unsolved Segment".to_owned(),
646 vec![args.source_range],
647 )));
648 };
649 let UnsolvedSegmentKind::Line {
650 start: start0,
651 end: end0,
652 ..
653 } = &unsolved0.kind
654 else {
655 return Err(KclError::new_semantic(KclErrorDetails::new(
656 "line argument must be a line, no other type of Segment".to_owned(),
657 vec![args.source_range],
658 )));
659 };
660 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
661 return Err(KclError::new_semantic(KclErrorDetails::new(
662 "line's start x coordinate must be a var".to_owned(),
663 vec![args.source_range],
664 )));
665 };
666 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
667 return Err(KclError::new_semantic(KclErrorDetails::new(
668 "line's start y coordinate must be a var".to_owned(),
669 vec![args.source_range],
670 )));
671 };
672 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
673 return Err(KclError::new_semantic(KclErrorDetails::new(
674 "line's end x coordinate must be a var".to_owned(),
675 vec![args.source_range],
676 )));
677 };
678 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
679 return Err(KclError::new_semantic(KclErrorDetails::new(
680 "line's end y coordinate must be a var".to_owned(),
681 vec![args.source_range],
682 )));
683 };
684 let KclValue::Segment { value: segment1 } = &lines[1] else {
685 return Err(KclError::new_semantic(KclErrorDetails::new(
686 "line argument must be a Segment".to_owned(),
687 vec![args.source_range],
688 )));
689 };
690 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
691 return Err(KclError::new_internal(KclErrorDetails::new(
692 "line must be an unsolved Segment".to_owned(),
693 vec![args.source_range],
694 )));
695 };
696 let UnsolvedSegmentKind::Line {
697 start: start1,
698 end: end1,
699 ..
700 } = &unsolved1.kind
701 else {
702 return Err(KclError::new_semantic(KclErrorDetails::new(
703 "line argument must be a line, no other type of Segment".to_owned(),
704 vec![args.source_range],
705 )));
706 };
707 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
708 return Err(KclError::new_semantic(KclErrorDetails::new(
709 "line's start x coordinate must be a var".to_owned(),
710 vec![args.source_range],
711 )));
712 };
713 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
714 return Err(KclError::new_semantic(KclErrorDetails::new(
715 "line's start y coordinate must be a var".to_owned(),
716 vec![args.source_range],
717 )));
718 };
719 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
720 return Err(KclError::new_semantic(KclErrorDetails::new(
721 "line's end x coordinate must be a var".to_owned(),
722 vec![args.source_range],
723 )));
724 };
725 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
726 return Err(KclError::new_semantic(KclErrorDetails::new(
727 "line's end y coordinate must be a var".to_owned(),
728 vec![args.source_range],
729 )));
730 };
731
732 let range = args.source_range;
733 let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
734 line0_p0_x.to_constraint_id(range)?,
735 line0_p0_y.to_constraint_id(range)?,
736 );
737 let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
738 line0_p1_x.to_constraint_id(range)?,
739 line0_p1_y.to_constraint_id(range)?,
740 );
741 let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
742 let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
743 line1_p0_x.to_constraint_id(range)?,
744 line1_p0_y.to_constraint_id(range)?,
745 );
746 let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
747 line1_p1_x.to_constraint_id(range)?,
748 line1_p1_y.to_constraint_id(range)?,
749 );
750 let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
751 let constraint = kcl_ezpz::Constraint::LinesEqualLength(solver_line0, solver_line1);
752 #[cfg(feature = "artifact-graph")]
753 let constraint_id = exec_state.next_object_id();
754 let Some(sketch_state) = exec_state.sketch_block_mut() else {
756 return Err(KclError::new_semantic(KclErrorDetails::new(
757 "equalLength() can only be used inside a sketch block".to_owned(),
758 vec![args.source_range],
759 )));
760 };
761 sketch_state.solver_constraints.push(constraint);
762 #[cfg(feature = "artifact-graph")]
763 {
764 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
765 lines: vec![unsolved0.object_id, unsolved1.object_id],
766 });
767 sketch_state.sketch_constraints.push(constraint_id);
768 track_constraint(constraint_id, constraint, exec_state, &args);
769 }
770 Ok(KclValue::none())
771}
772
773pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
774 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
775 "lines",
776 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
777 exec_state,
778 )?;
779 if lines.len() != 2 {
780 return Err(KclError::new_semantic(KclErrorDetails::new(
781 "must have two input lines".to_owned(),
782 vec![args.source_range],
783 )));
784 }
785
786 let KclValue::Segment { value: segment0 } = &lines[0] else {
787 return Err(KclError::new_semantic(KclErrorDetails::new(
788 "line argument must be a Segment".to_owned(),
789 vec![args.source_range],
790 )));
791 };
792 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
793 return Err(KclError::new_internal(KclErrorDetails::new(
794 "line must be an unsolved Segment".to_owned(),
795 vec![args.source_range],
796 )));
797 };
798 let UnsolvedSegmentKind::Line {
799 start: start0,
800 end: end0,
801 ..
802 } = &unsolved0.kind
803 else {
804 return Err(KclError::new_semantic(KclErrorDetails::new(
805 "line argument must be a line, no other type of Segment".to_owned(),
806 vec![args.source_range],
807 )));
808 };
809 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
810 return Err(KclError::new_semantic(KclErrorDetails::new(
811 "line's start x coordinate must be a var".to_owned(),
812 vec![args.source_range],
813 )));
814 };
815 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
816 return Err(KclError::new_semantic(KclErrorDetails::new(
817 "line's start y coordinate must be a var".to_owned(),
818 vec![args.source_range],
819 )));
820 };
821 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
822 return Err(KclError::new_semantic(KclErrorDetails::new(
823 "line's end x coordinate must be a var".to_owned(),
824 vec![args.source_range],
825 )));
826 };
827 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
828 return Err(KclError::new_semantic(KclErrorDetails::new(
829 "line's end y coordinate must be a var".to_owned(),
830 vec![args.source_range],
831 )));
832 };
833 let KclValue::Segment { value: segment1 } = &lines[1] else {
834 return Err(KclError::new_semantic(KclErrorDetails::new(
835 "line argument must be a Segment".to_owned(),
836 vec![args.source_range],
837 )));
838 };
839 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
840 return Err(KclError::new_internal(KclErrorDetails::new(
841 "line must be an unsolved Segment".to_owned(),
842 vec![args.source_range],
843 )));
844 };
845 let UnsolvedSegmentKind::Line {
846 start: start1,
847 end: end1,
848 ..
849 } = &unsolved1.kind
850 else {
851 return Err(KclError::new_semantic(KclErrorDetails::new(
852 "line argument must be a line, no other type of Segment".to_owned(),
853 vec![args.source_range],
854 )));
855 };
856 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
857 return Err(KclError::new_semantic(KclErrorDetails::new(
858 "line's start x coordinate must be a var".to_owned(),
859 vec![args.source_range],
860 )));
861 };
862 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
863 return Err(KclError::new_semantic(KclErrorDetails::new(
864 "line's start y coordinate must be a var".to_owned(),
865 vec![args.source_range],
866 )));
867 };
868 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
869 return Err(KclError::new_semantic(KclErrorDetails::new(
870 "line's end x coordinate must be a var".to_owned(),
871 vec![args.source_range],
872 )));
873 };
874 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
875 return Err(KclError::new_semantic(KclErrorDetails::new(
876 "line's end y coordinate must be a var".to_owned(),
877 vec![args.source_range],
878 )));
879 };
880
881 let range = args.source_range;
882 let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
883 line0_p0_x.to_constraint_id(range)?,
884 line0_p0_y.to_constraint_id(range)?,
885 );
886 let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
887 line0_p1_x.to_constraint_id(range)?,
888 line0_p1_y.to_constraint_id(range)?,
889 );
890 let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
891 let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
892 line1_p0_x.to_constraint_id(range)?,
893 line1_p0_y.to_constraint_id(range)?,
894 );
895 let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
896 line1_p1_x.to_constraint_id(range)?,
897 line1_p1_y.to_constraint_id(range)?,
898 );
899 let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
900 let constraint =
901 kcl_ezpz::Constraint::LinesAtAngle(solver_line0, solver_line1, kcl_ezpz::datatypes::AngleKind::Parallel);
902 #[cfg(feature = "artifact-graph")]
903 let constraint_id = exec_state.next_object_id();
904 let Some(sketch_state) = exec_state.sketch_block_mut() else {
906 return Err(KclError::new_semantic(KclErrorDetails::new(
907 "equalLength() can only be used inside a sketch block".to_owned(),
908 vec![args.source_range],
909 )));
910 };
911 sketch_state.solver_constraints.push(constraint);
912 #[cfg(feature = "artifact-graph")]
913 {
914 let constraint = crate::front::Constraint::Parallel(Parallel {
915 lines: vec![unsolved0.object_id, unsolved1.object_id],
916 });
917 sketch_state.sketch_constraints.push(constraint_id);
918 track_constraint(constraint_id, constraint, exec_state, &args);
919 }
920 Ok(KclValue::none())
921}
922
923pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
924 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
925 let KclValue::Segment { value: segment } = line else {
926 return Err(KclError::new_semantic(KclErrorDetails::new(
927 "line argument must be a Segment".to_owned(),
928 vec![args.source_range],
929 )));
930 };
931 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
932 return Err(KclError::new_internal(KclErrorDetails::new(
933 "line must be an unsolved Segment".to_owned(),
934 vec![args.source_range],
935 )));
936 };
937 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
938 return Err(KclError::new_semantic(KclErrorDetails::new(
939 "line argument must be a line, no other type of Segment".to_owned(),
940 vec![args.source_range],
941 )));
942 };
943 let p0_x = &start[0];
944 let p0_y = &start[1];
945 let p1_x = &end[0];
946 let p1_y = &end[1];
947 match (p0_x, p0_y, p1_x, p1_y) {
948 (
949 UnsolvedExpr::Unknown(p0_x),
950 UnsolvedExpr::Unknown(p0_y),
951 UnsolvedExpr::Unknown(p1_x),
952 UnsolvedExpr::Unknown(p1_y),
953 ) => {
954 let range = args.source_range;
955 let solver_p0 =
956 kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
957 let solver_p1 =
958 kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
959 let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
960 let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
961 #[cfg(feature = "artifact-graph")]
962 let constraint_id = exec_state.next_object_id();
963 let Some(sketch_state) = exec_state.sketch_block_mut() else {
965 return Err(KclError::new_semantic(KclErrorDetails::new(
966 "horizontal() can only be used inside a sketch block".to_owned(),
967 vec![args.source_range],
968 )));
969 };
970 sketch_state.solver_constraints.push(constraint);
971 #[cfg(feature = "artifact-graph")]
972 {
973 let constraint = crate::front::Constraint::Horizontal(Horizontal {
974 line: unsolved.object_id,
975 });
976 sketch_state.sketch_constraints.push(constraint_id);
977 track_constraint(constraint_id, constraint, exec_state, &args);
978 }
979 Ok(KclValue::none())
980 }
981 _ => Err(KclError::new_semantic(KclErrorDetails::new(
982 "line's x and y coordinates of both start and end must be vars".to_owned(),
983 vec![args.source_range],
984 ))),
985 }
986}
987
988pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
989 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
990 let KclValue::Segment { value: segment } = line else {
991 return Err(KclError::new_semantic(KclErrorDetails::new(
992 "line argument must be a Segment".to_owned(),
993 vec![args.source_range],
994 )));
995 };
996 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
997 return Err(KclError::new_internal(KclErrorDetails::new(
998 "line must be an unsolved Segment".to_owned(),
999 vec![args.source_range],
1000 )));
1001 };
1002 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1003 return Err(KclError::new_semantic(KclErrorDetails::new(
1004 "line argument must be a line, no other type of Segment".to_owned(),
1005 vec![args.source_range],
1006 )));
1007 };
1008 let p0_x = &start[0];
1009 let p0_y = &start[1];
1010 let p1_x = &end[0];
1011 let p1_y = &end[1];
1012 match (p0_x, p0_y, p1_x, p1_y) {
1013 (
1014 UnsolvedExpr::Unknown(p0_x),
1015 UnsolvedExpr::Unknown(p0_y),
1016 UnsolvedExpr::Unknown(p1_x),
1017 UnsolvedExpr::Unknown(p1_y),
1018 ) => {
1019 let range = args.source_range;
1020 let solver_p0 =
1021 kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
1022 let solver_p1 =
1023 kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
1024 let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
1025 let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1026 #[cfg(feature = "artifact-graph")]
1027 let constraint_id = exec_state.next_object_id();
1028 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1030 return Err(KclError::new_semantic(KclErrorDetails::new(
1031 "vertical() can only be used inside a sketch block".to_owned(),
1032 vec![args.source_range],
1033 )));
1034 };
1035 sketch_state.solver_constraints.push(constraint);
1036 #[cfg(feature = "artifact-graph")]
1037 {
1038 let constraint = crate::front::Constraint::Vertical(Vertical {
1039 line: unsolved.object_id,
1040 });
1041 sketch_state.sketch_constraints.push(constraint_id);
1042 track_constraint(constraint_id, constraint, exec_state, &args);
1043 }
1044 Ok(KclValue::none())
1045 }
1046 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1047 "line's x and y coordinates of both start and end must be vars".to_owned(),
1048 vec![args.source_range],
1049 ))),
1050 }
1051}