1use anyhow::Result;
2use kcl_ezpz::{
3 Constraint as SolverConstraint,
4 datatypes::{DatumPoint, LineSegment},
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::CircularArc {
511 center: kcl_ezpz::datatypes::DatumPoint::new_xy(
512 center_x.to_constraint_id(range)?,
513 center_y.to_constraint_id(range)?,
514 ),
515 a: kcl_ezpz::datatypes::DatumPoint::new_xy(start_x.to_constraint_id(range)?, start_y.to_constraint_id(range)?),
516 b: kcl_ezpz::datatypes::DatumPoint::new_xy(end_x.to_constraint_id(range)?, end_y.to_constraint_id(range)?),
517 });
518
519 let Some(sketch_state) = exec_state.sketch_block_mut() else {
520 return Err(KclError::new_semantic(KclErrorDetails::new(
521 "arc() can only be used inside a sketch block".to_owned(),
522 vec![args.source_range],
523 )));
524 };
525 sketch_state.needed_by_engine.push(segment.clone());
527 sketch_state.solver_constraints.push(constraint);
529 #[cfg(feature = "artifact-graph")]
533 sketch_state.solver_optional_constraints.extend(optional_constraints);
534
535 let meta = segment.meta.clone();
536 let abstract_segment = AbstractSegment {
537 repr: SegmentRepr::Unsolved { segment },
538 meta,
539 };
540 Ok(KclValue::Segment {
541 value: Box::new(abstract_segment),
542 })
543}
544
545pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
546 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
547 "points",
548 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
549 exec_state,
550 )?;
551 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
552 KclError::new_semantic(KclErrorDetails::new(
553 "must have two input points".to_owned(),
554 vec![args.source_range],
555 ))
556 })?;
557
558 let range = args.source_range;
559 match (&point0, &point1) {
560 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
561 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
562 return Err(KclError::new_semantic(KclErrorDetails::new(
563 "first point must be an unsolved segment".to_owned(),
564 vec![args.source_range],
565 )));
566 };
567 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
568 return Err(KclError::new_semantic(KclErrorDetails::new(
569 "second point must be an unsolved segment".to_owned(),
570 vec![args.source_range],
571 )));
572 };
573 match (&unsolved0.kind, &unsolved1.kind) {
574 (
575 UnsolvedSegmentKind::Point { position: pos0, .. },
576 UnsolvedSegmentKind::Point { position: pos1, .. },
577 ) => {
578 let p0_x = &pos0[0];
579 let p0_y = &pos0[1];
580 match (p0_x, p0_y) {
581 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
582 let p1_x = &pos1[0];
583 let p1_y = &pos1[1];
584 match (p1_x, p1_y) {
585 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
586 let constraint = SolverConstraint::PointsCoincident(
587 kcl_ezpz::datatypes::DatumPoint::new_xy(
588 p0_x.to_constraint_id(range)?,
589 p0_y.to_constraint_id(range)?,
590 ),
591 kcl_ezpz::datatypes::DatumPoint::new_xy(
592 p1_x.to_constraint_id(range)?,
593 p1_y.to_constraint_id(range)?,
594 ),
595 );
596 #[cfg(feature = "artifact-graph")]
597 let constraint_id = exec_state.next_object_id();
598 let Some(sketch_state) = exec_state.sketch_block_mut() else {
600 return Err(KclError::new_semantic(KclErrorDetails::new(
601 "coincident() can only be used inside a sketch block".to_owned(),
602 vec![args.source_range],
603 )));
604 };
605 sketch_state.solver_constraints.push(constraint);
606 #[cfg(feature = "artifact-graph")]
607 {
608 let constraint = crate::front::Constraint::Coincident(Coincident {
609 segments: vec![unsolved0.object_id, unsolved1.object_id],
610 });
611 sketch_state.sketch_constraints.push(constraint_id);
612 track_constraint(constraint_id, constraint, exec_state, &args);
613 }
614 Ok(KclValue::none())
615 }
616 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
617 let p1_x = KclValue::Number {
618 value: p1_x.n,
619 ty: p1_x.ty,
620 meta: vec![args.source_range.into()],
621 };
622 let p1_y = KclValue::Number {
623 value: p1_y.n,
624 ty: p1_y.ty,
625 meta: vec![args.source_range.into()],
626 };
627 let (constraint_x, constraint_y) =
628 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
629
630 #[cfg(feature = "artifact-graph")]
631 let constraint_id = exec_state.next_object_id();
632 let Some(sketch_state) = exec_state.sketch_block_mut() else {
634 return Err(KclError::new_semantic(KclErrorDetails::new(
635 "coincident() can only be used inside a sketch block".to_owned(),
636 vec![args.source_range],
637 )));
638 };
639 sketch_state.solver_constraints.push(constraint_x);
640 sketch_state.solver_constraints.push(constraint_y);
641 #[cfg(feature = "artifact-graph")]
642 {
643 let constraint = crate::front::Constraint::Coincident(Coincident {
644 segments: vec![unsolved0.object_id, unsolved1.object_id],
645 });
646 sketch_state.sketch_constraints.push(constraint_id);
647 track_constraint(constraint_id, constraint, exec_state, &args);
648 }
649 Ok(KclValue::none())
650 }
651 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
652 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
653 Err(KclError::new_semantic(KclErrorDetails::new(
655 "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(),
656 vec![args.source_range],
657 )))
658 }
659 }
660 }
661 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
662 let p1_x = &pos1[0];
663 let p1_y = &pos1[1];
664 match (p1_x, p1_y) {
665 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
666 let p0_x = KclValue::Number {
667 value: p0_x.n,
668 ty: p0_x.ty,
669 meta: vec![args.source_range.into()],
670 };
671 let p0_y = KclValue::Number {
672 value: p0_y.n,
673 ty: p0_y.ty,
674 meta: vec![args.source_range.into()],
675 };
676 let (constraint_x, constraint_y) =
677 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
678
679 #[cfg(feature = "artifact-graph")]
680 let constraint_id = exec_state.next_object_id();
681 let Some(sketch_state) = exec_state.sketch_block_mut() else {
683 return Err(KclError::new_semantic(KclErrorDetails::new(
684 "coincident() can only be used inside a sketch block".to_owned(),
685 vec![args.source_range],
686 )));
687 };
688 sketch_state.solver_constraints.push(constraint_x);
689 sketch_state.solver_constraints.push(constraint_y);
690 #[cfg(feature = "artifact-graph")]
691 {
692 let constraint = crate::front::Constraint::Coincident(Coincident {
693 segments: vec![unsolved0.object_id, unsolved1.object_id],
694 });
695 sketch_state.sketch_constraints.push(constraint_id);
696 track_constraint(constraint_id, constraint, exec_state, &args);
697 }
698 Ok(KclValue::none())
699 }
700 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
701 if *p0_x != *p1_x || *p0_y != *p1_y {
702 return Err(KclError::new_semantic(KclErrorDetails::new(
703 "Coincident constraint between two fixed points failed since coordinates differ"
704 .to_owned(),
705 vec![args.source_range],
706 )));
707 }
708 Ok(KclValue::none())
709 }
710 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
711 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
712 Err(KclError::new_semantic(KclErrorDetails::new(
714 "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(),
715 vec![args.source_range],
716 )))
717 }
718 }
719 }
720 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
721 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
722 Err(KclError::new_semantic(KclErrorDetails::new(
724 "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(),
725 vec![args.source_range],
726 )))
727 }
728 }
729 }
730 (
732 UnsolvedSegmentKind::Point {
733 position: point_pos, ..
734 },
735 UnsolvedSegmentKind::Line {
736 start: line_start,
737 end: line_end,
738 ..
739 },
740 )
741 | (
742 UnsolvedSegmentKind::Line {
743 start: line_start,
744 end: line_end,
745 ..
746 },
747 UnsolvedSegmentKind::Point {
748 position: point_pos, ..
749 },
750 ) => {
751 let point_x = &point_pos[0];
752 let point_y = &point_pos[1];
753 match (point_x, point_y) {
754 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
755 let (start_x, start_y) = (&line_start[0], &line_start[1]);
757 let (end_x, end_y) = (&line_end[0], &line_end[1]);
758
759 match (start_x, start_y, end_x, end_y) {
760 (
761 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
762 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
763 ) => {
764 let point = DatumPoint::new_xy(
765 point_x.to_constraint_id(range)?,
766 point_y.to_constraint_id(range)?,
767 );
768 let line_segment = LineSegment::new(
769 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
770 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
771 );
772 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
773
774 #[cfg(feature = "artifact-graph")]
775 let constraint_id = exec_state.next_object_id();
776
777 let Some(sketch_state) = exec_state.sketch_block_mut() else {
778 return Err(KclError::new_semantic(KclErrorDetails::new(
779 "coincident() can only be used inside a sketch block".to_owned(),
780 vec![args.source_range],
781 )));
782 };
783 sketch_state.solver_constraints.push(constraint);
784 #[cfg(feature = "artifact-graph")]
785 {
786 let constraint = crate::front::Constraint::Coincident(Coincident {
787 segments: vec![unsolved0.object_id, unsolved1.object_id],
788 });
789 sketch_state.sketch_constraints.push(constraint_id);
790 track_constraint(constraint_id, constraint, exec_state, &args);
791 }
792 Ok(KclValue::none())
793 }
794 _ => Err(KclError::new_semantic(KclErrorDetails::new(
795 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
796 vec![args.source_range],
797 ))),
798 }
799 }
800 _ => Err(KclError::new_semantic(KclErrorDetails::new(
801 "Point coordinates must be sketch variables for point-segment coincident constraint"
802 .to_owned(),
803 vec![args.source_range],
804 ))),
805 }
806 }
807 _ => Err(KclError::new_semantic(KclErrorDetails::new(
808 format!(
809 "coincident supports point-point or point-segment; found {:?} and {:?}",
810 &unsolved0.kind, &unsolved1.kind
811 ),
812 vec![args.source_range],
813 ))),
814 }
815 }
816 _ => Err(KclError::new_semantic(KclErrorDetails::new(
817 "All inputs must be segments (points or lines), created from point(), line(), or another sketch function"
818 .to_owned(),
819 vec![args.source_range],
820 ))),
821 }
822}
823
824#[cfg(feature = "artifact-graph")]
825fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
826 exec_state.add_scene_object(
827 Object {
828 id: constraint_id,
829 kind: ObjectKind::Constraint { constraint },
830 label: Default::default(),
831 comments: Default::default(),
832 artifact_id: ArtifactId::constraint(),
833 source: args.source_range.into(),
834 },
835 args.source_range,
836 );
837}
838
839fn coincident_constraints_fixed(
841 p0_x: SketchVarId,
842 p0_y: SketchVarId,
843 p1_x: &KclValue,
844 p1_y: &KclValue,
845 exec_state: &mut ExecState,
846 args: &Args,
847) -> Result<(kcl_ezpz::Constraint, kcl_ezpz::Constraint), KclError> {
848 let p1_x_number_value = normalize_to_solver_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
849 let p1_y_number_value = normalize_to_solver_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
850 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
851 let message = format!(
852 "Expected number after coercion, but found {}",
853 p1_x_number_value.human_friendly_type()
854 );
855 debug_assert!(false, "{}", &message);
856 return Err(KclError::new_internal(KclErrorDetails::new(
857 message,
858 vec![args.source_range],
859 )));
860 };
861 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
862 let message = format!(
863 "Expected number after coercion, but found {}",
864 p1_y_number_value.human_friendly_type()
865 );
866 debug_assert!(false, "{}", &message);
867 return Err(KclError::new_internal(KclErrorDetails::new(
868 message,
869 vec![args.source_range],
870 )));
871 };
872 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
873 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
874 Ok((constraint_x, constraint_y))
875}
876
877pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
878 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
879 "points",
880 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
881 exec_state,
882 )?;
883 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
884 KclError::new_semantic(KclErrorDetails::new(
885 "must have two input points".to_owned(),
886 vec![args.source_range],
887 ))
888 })?;
889
890 match (&point0, &point1) {
891 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
892 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
893 return Err(KclError::new_semantic(KclErrorDetails::new(
894 "first point must be an unsolved segment".to_owned(),
895 vec![args.source_range],
896 )));
897 };
898 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
899 return Err(KclError::new_semantic(KclErrorDetails::new(
900 "second point must be an unsolved segment".to_owned(),
901 vec![args.source_range],
902 )));
903 };
904 match (&unsolved0.kind, &unsolved1.kind) {
905 (
906 UnsolvedSegmentKind::Point { position: pos0, .. },
907 UnsolvedSegmentKind::Point { position: pos1, .. },
908 ) => {
909 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
912 (
913 UnsolvedExpr::Unknown(p0_x),
914 UnsolvedExpr::Unknown(p0_y),
915 UnsolvedExpr::Unknown(p1_x),
916 UnsolvedExpr::Unknown(p1_y),
917 ) => {
918 let sketch_constraint = SketchConstraint {
920 kind: SketchConstraintKind::Distance {
921 points: [
922 ConstrainablePoint2d {
923 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
924 object_id: unsolved0.object_id,
925 },
926 ConstrainablePoint2d {
927 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
928 object_id: unsolved1.object_id,
929 },
930 ],
931 },
932 meta: vec![args.source_range.into()],
933 };
934 Ok(KclValue::SketchConstraint {
935 value: Box::new(sketch_constraint),
936 })
937 }
938 _ => Err(KclError::new_semantic(KclErrorDetails::new(
939 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
940 vec![args.source_range],
941 ))),
942 }
943 }
944 _ => Err(KclError::new_semantic(KclErrorDetails::new(
945 "distance() arguments must be unsolved points".to_owned(),
946 vec![args.source_range],
947 ))),
948 }
949 }
950 _ => Err(KclError::new_semantic(KclErrorDetails::new(
951 "distance() arguments must be point segments".to_owned(),
952 vec![args.source_range],
953 ))),
954 }
955}
956
957pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
958 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
959 "lines",
960 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
961 exec_state,
962 )?;
963 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
964 KclError::new_semantic(KclErrorDetails::new(
965 "must have two input lines".to_owned(),
966 vec![args.source_range],
967 ))
968 })?;
969
970 let KclValue::Segment { value: segment0 } = &line0 else {
971 return Err(KclError::new_semantic(KclErrorDetails::new(
972 "line argument must be a Segment".to_owned(),
973 vec![args.source_range],
974 )));
975 };
976 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
977 return Err(KclError::new_internal(KclErrorDetails::new(
978 "line must be an unsolved Segment".to_owned(),
979 vec![args.source_range],
980 )));
981 };
982 let UnsolvedSegmentKind::Line {
983 start: start0,
984 end: end0,
985 ..
986 } = &unsolved0.kind
987 else {
988 return Err(KclError::new_semantic(KclErrorDetails::new(
989 "line argument must be a line, no other type of Segment".to_owned(),
990 vec![args.source_range],
991 )));
992 };
993 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
994 return Err(KclError::new_semantic(KclErrorDetails::new(
995 "line's start x coordinate must be a var".to_owned(),
996 vec![args.source_range],
997 )));
998 };
999 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1000 return Err(KclError::new_semantic(KclErrorDetails::new(
1001 "line's start y coordinate must be a var".to_owned(),
1002 vec![args.source_range],
1003 )));
1004 };
1005 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1006 return Err(KclError::new_semantic(KclErrorDetails::new(
1007 "line's end x coordinate must be a var".to_owned(),
1008 vec![args.source_range],
1009 )));
1010 };
1011 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1012 return Err(KclError::new_semantic(KclErrorDetails::new(
1013 "line's end y coordinate must be a var".to_owned(),
1014 vec![args.source_range],
1015 )));
1016 };
1017 let KclValue::Segment { value: segment1 } = &line1 else {
1018 return Err(KclError::new_semantic(KclErrorDetails::new(
1019 "line argument must be a Segment".to_owned(),
1020 vec![args.source_range],
1021 )));
1022 };
1023 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1024 return Err(KclError::new_internal(KclErrorDetails::new(
1025 "line must be an unsolved Segment".to_owned(),
1026 vec![args.source_range],
1027 )));
1028 };
1029 let UnsolvedSegmentKind::Line {
1030 start: start1,
1031 end: end1,
1032 ..
1033 } = &unsolved1.kind
1034 else {
1035 return Err(KclError::new_semantic(KclErrorDetails::new(
1036 "line argument must be a line, no other type of Segment".to_owned(),
1037 vec![args.source_range],
1038 )));
1039 };
1040 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1041 return Err(KclError::new_semantic(KclErrorDetails::new(
1042 "line's start x coordinate must be a var".to_owned(),
1043 vec![args.source_range],
1044 )));
1045 };
1046 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1047 return Err(KclError::new_semantic(KclErrorDetails::new(
1048 "line's start y coordinate must be a var".to_owned(),
1049 vec![args.source_range],
1050 )));
1051 };
1052 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1053 return Err(KclError::new_semantic(KclErrorDetails::new(
1054 "line's end x coordinate must be a var".to_owned(),
1055 vec![args.source_range],
1056 )));
1057 };
1058 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1059 return Err(KclError::new_semantic(KclErrorDetails::new(
1060 "line's end y coordinate must be a var".to_owned(),
1061 vec![args.source_range],
1062 )));
1063 };
1064
1065 let range = args.source_range;
1066 let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1067 line0_p0_x.to_constraint_id(range)?,
1068 line0_p0_y.to_constraint_id(range)?,
1069 );
1070 let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1071 line0_p1_x.to_constraint_id(range)?,
1072 line0_p1_y.to_constraint_id(range)?,
1073 );
1074 let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
1075 let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1076 line1_p0_x.to_constraint_id(range)?,
1077 line1_p0_y.to_constraint_id(range)?,
1078 );
1079 let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1080 line1_p1_x.to_constraint_id(range)?,
1081 line1_p1_y.to_constraint_id(range)?,
1082 );
1083 let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
1084 let constraint = SolverConstraint::LinesEqualLength(solver_line0, solver_line1);
1085 #[cfg(feature = "artifact-graph")]
1086 let constraint_id = exec_state.next_object_id();
1087 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1089 return Err(KclError::new_semantic(KclErrorDetails::new(
1090 "equalLength() can only be used inside a sketch block".to_owned(),
1091 vec![args.source_range],
1092 )));
1093 };
1094 sketch_state.solver_constraints.push(constraint);
1095 #[cfg(feature = "artifact-graph")]
1096 {
1097 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
1098 lines: vec![unsolved0.object_id, unsolved1.object_id],
1099 });
1100 sketch_state.sketch_constraints.push(constraint_id);
1101 track_constraint(constraint_id, constraint, exec_state, &args);
1102 }
1103 Ok(KclValue::none())
1104}
1105
1106#[derive(Debug, Clone, Copy)]
1107pub(crate) enum LinesAtAngleKind {
1108 Parallel,
1109 Perpendicular,
1110}
1111
1112impl LinesAtAngleKind {
1113 pub fn to_function_name(self) -> &'static str {
1114 match self {
1115 LinesAtAngleKind::Parallel => "parallel",
1116 LinesAtAngleKind::Perpendicular => "perpendicular",
1117 }
1118 }
1119
1120 fn to_solver_angle(self) -> kcl_ezpz::datatypes::AngleKind {
1121 match self {
1122 LinesAtAngleKind::Parallel => kcl_ezpz::datatypes::AngleKind::Parallel,
1123 LinesAtAngleKind::Perpendicular => kcl_ezpz::datatypes::AngleKind::Perpendicular,
1124 }
1125 }
1126
1127 #[cfg(feature = "artifact-graph")]
1128 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
1129 match self {
1130 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
1131 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
1132 }
1133 }
1134}
1135
1136pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1137 lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
1138}
1139pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1140 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
1141}
1142
1143async fn lines_at_angle(
1144 angle_kind: LinesAtAngleKind,
1145 exec_state: &mut ExecState,
1146 args: Args,
1147) -> Result<KclValue, KclError> {
1148 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
1149 "lines",
1150 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1151 exec_state,
1152 )?;
1153 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
1154 KclError::new_semantic(KclErrorDetails::new(
1155 "must have two input lines".to_owned(),
1156 vec![args.source_range],
1157 ))
1158 })?;
1159
1160 let KclValue::Segment { value: segment0 } = &line0 else {
1161 return Err(KclError::new_semantic(KclErrorDetails::new(
1162 "line argument must be a Segment".to_owned(),
1163 vec![args.source_range],
1164 )));
1165 };
1166 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
1167 return Err(KclError::new_internal(KclErrorDetails::new(
1168 "line must be an unsolved Segment".to_owned(),
1169 vec![args.source_range],
1170 )));
1171 };
1172 let UnsolvedSegmentKind::Line {
1173 start: start0,
1174 end: end0,
1175 ..
1176 } = &unsolved0.kind
1177 else {
1178 return Err(KclError::new_semantic(KclErrorDetails::new(
1179 "line argument must be a line, no other type of Segment".to_owned(),
1180 vec![args.source_range],
1181 )));
1182 };
1183 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
1184 return Err(KclError::new_semantic(KclErrorDetails::new(
1185 "line's start x coordinate must be a var".to_owned(),
1186 vec![args.source_range],
1187 )));
1188 };
1189 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
1190 return Err(KclError::new_semantic(KclErrorDetails::new(
1191 "line's start y coordinate must be a var".to_owned(),
1192 vec![args.source_range],
1193 )));
1194 };
1195 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
1196 return Err(KclError::new_semantic(KclErrorDetails::new(
1197 "line's end x coordinate must be a var".to_owned(),
1198 vec![args.source_range],
1199 )));
1200 };
1201 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
1202 return Err(KclError::new_semantic(KclErrorDetails::new(
1203 "line's end y coordinate must be a var".to_owned(),
1204 vec![args.source_range],
1205 )));
1206 };
1207 let KclValue::Segment { value: segment1 } = &line1 else {
1208 return Err(KclError::new_semantic(KclErrorDetails::new(
1209 "line argument must be a Segment".to_owned(),
1210 vec![args.source_range],
1211 )));
1212 };
1213 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
1214 return Err(KclError::new_internal(KclErrorDetails::new(
1215 "line must be an unsolved Segment".to_owned(),
1216 vec![args.source_range],
1217 )));
1218 };
1219 let UnsolvedSegmentKind::Line {
1220 start: start1,
1221 end: end1,
1222 ..
1223 } = &unsolved1.kind
1224 else {
1225 return Err(KclError::new_semantic(KclErrorDetails::new(
1226 "line argument must be a line, no other type of Segment".to_owned(),
1227 vec![args.source_range],
1228 )));
1229 };
1230 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
1231 return Err(KclError::new_semantic(KclErrorDetails::new(
1232 "line's start x coordinate must be a var".to_owned(),
1233 vec![args.source_range],
1234 )));
1235 };
1236 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
1237 return Err(KclError::new_semantic(KclErrorDetails::new(
1238 "line's start y coordinate must be a var".to_owned(),
1239 vec![args.source_range],
1240 )));
1241 };
1242 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
1243 return Err(KclError::new_semantic(KclErrorDetails::new(
1244 "line's end x coordinate must be a var".to_owned(),
1245 vec![args.source_range],
1246 )));
1247 };
1248 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
1249 return Err(KclError::new_semantic(KclErrorDetails::new(
1250 "line's end y coordinate must be a var".to_owned(),
1251 vec![args.source_range],
1252 )));
1253 };
1254
1255 let range = args.source_range;
1256 let solver_line0_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1257 line0_p0_x.to_constraint_id(range)?,
1258 line0_p0_y.to_constraint_id(range)?,
1259 );
1260 let solver_line0_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1261 line0_p1_x.to_constraint_id(range)?,
1262 line0_p1_y.to_constraint_id(range)?,
1263 );
1264 let solver_line0 = kcl_ezpz::datatypes::LineSegment::new(solver_line0_p0, solver_line0_p1);
1265 let solver_line1_p0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1266 line1_p0_x.to_constraint_id(range)?,
1267 line1_p0_y.to_constraint_id(range)?,
1268 );
1269 let solver_line1_p1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
1270 line1_p1_x.to_constraint_id(range)?,
1271 line1_p1_y.to_constraint_id(range)?,
1272 );
1273 let solver_line1 = kcl_ezpz::datatypes::LineSegment::new(solver_line1_p0, solver_line1_p1);
1274 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
1275 #[cfg(feature = "artifact-graph")]
1276 let constraint_id = exec_state.next_object_id();
1277 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1279 return Err(KclError::new_semantic(KclErrorDetails::new(
1280 format!(
1281 "{}() can only be used inside a sketch block",
1282 angle_kind.to_function_name()
1283 ),
1284 vec![args.source_range],
1285 )));
1286 };
1287 sketch_state.solver_constraints.push(constraint);
1288 #[cfg(feature = "artifact-graph")]
1289 {
1290 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
1291 sketch_state.sketch_constraints.push(constraint_id);
1292 track_constraint(constraint_id, constraint, exec_state, &args);
1293 }
1294 Ok(KclValue::none())
1295}
1296
1297pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1298 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1299 let KclValue::Segment { value: segment } = line else {
1300 return Err(KclError::new_semantic(KclErrorDetails::new(
1301 "line argument must be a Segment".to_owned(),
1302 vec![args.source_range],
1303 )));
1304 };
1305 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1306 return Err(KclError::new_internal(KclErrorDetails::new(
1307 "line must be an unsolved Segment".to_owned(),
1308 vec![args.source_range],
1309 )));
1310 };
1311 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1312 return Err(KclError::new_semantic(KclErrorDetails::new(
1313 "line argument must be a line, no other type of Segment".to_owned(),
1314 vec![args.source_range],
1315 )));
1316 };
1317 let p0_x = &start[0];
1318 let p0_y = &start[1];
1319 let p1_x = &end[0];
1320 let p1_y = &end[1];
1321 match (p0_x, p0_y, p1_x, p1_y) {
1322 (
1323 UnsolvedExpr::Unknown(p0_x),
1324 UnsolvedExpr::Unknown(p0_y),
1325 UnsolvedExpr::Unknown(p1_x),
1326 UnsolvedExpr::Unknown(p1_y),
1327 ) => {
1328 let range = args.source_range;
1329 let solver_p0 =
1330 kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
1331 let solver_p1 =
1332 kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
1333 let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
1334 let constraint = kcl_ezpz::Constraint::Horizontal(solver_line);
1335 #[cfg(feature = "artifact-graph")]
1336 let constraint_id = exec_state.next_object_id();
1337 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1339 return Err(KclError::new_semantic(KclErrorDetails::new(
1340 "horizontal() can only be used inside a sketch block".to_owned(),
1341 vec![args.source_range],
1342 )));
1343 };
1344 sketch_state.solver_constraints.push(constraint);
1345 #[cfg(feature = "artifact-graph")]
1346 {
1347 let constraint = crate::front::Constraint::Horizontal(Horizontal {
1348 line: unsolved.object_id,
1349 });
1350 sketch_state.sketch_constraints.push(constraint_id);
1351 track_constraint(constraint_id, constraint, exec_state, &args);
1352 }
1353 Ok(KclValue::none())
1354 }
1355 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1356 "line's x and y coordinates of both start and end must be vars".to_owned(),
1357 vec![args.source_range],
1358 ))),
1359 }
1360}
1361
1362pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1363 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
1364 let KclValue::Segment { value: segment } = line else {
1365 return Err(KclError::new_semantic(KclErrorDetails::new(
1366 "line argument must be a Segment".to_owned(),
1367 vec![args.source_range],
1368 )));
1369 };
1370 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
1371 return Err(KclError::new_internal(KclErrorDetails::new(
1372 "line must be an unsolved Segment".to_owned(),
1373 vec![args.source_range],
1374 )));
1375 };
1376 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
1377 return Err(KclError::new_semantic(KclErrorDetails::new(
1378 "line argument must be a line, no other type of Segment".to_owned(),
1379 vec![args.source_range],
1380 )));
1381 };
1382 let p0_x = &start[0];
1383 let p0_y = &start[1];
1384 let p1_x = &end[0];
1385 let p1_y = &end[1];
1386 match (p0_x, p0_y, p1_x, p1_y) {
1387 (
1388 UnsolvedExpr::Unknown(p0_x),
1389 UnsolvedExpr::Unknown(p0_y),
1390 UnsolvedExpr::Unknown(p1_x),
1391 UnsolvedExpr::Unknown(p1_y),
1392 ) => {
1393 let range = args.source_range;
1394 let solver_p0 =
1395 kcl_ezpz::datatypes::DatumPoint::new_xy(p0_x.to_constraint_id(range)?, p0_y.to_constraint_id(range)?);
1396 let solver_p1 =
1397 kcl_ezpz::datatypes::DatumPoint::new_xy(p1_x.to_constraint_id(range)?, p1_y.to_constraint_id(range)?);
1398 let solver_line = kcl_ezpz::datatypes::LineSegment::new(solver_p0, solver_p1);
1399 let constraint = kcl_ezpz::Constraint::Vertical(solver_line);
1400 #[cfg(feature = "artifact-graph")]
1401 let constraint_id = exec_state.next_object_id();
1402 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1404 return Err(KclError::new_semantic(KclErrorDetails::new(
1405 "vertical() can only be used inside a sketch block".to_owned(),
1406 vec![args.source_range],
1407 )));
1408 };
1409 sketch_state.solver_constraints.push(constraint);
1410 #[cfg(feature = "artifact-graph")]
1411 {
1412 let constraint = crate::front::Constraint::Vertical(Vertical {
1413 line: unsolved.object_id,
1414 });
1415 sketch_state.sketch_constraints.push(constraint_id);
1416 track_constraint(constraint_id, constraint, exec_state, &args);
1417 }
1418 Ok(KclValue::none())
1419 }
1420 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1421 "line's x and y coordinates of both start and end must be vars".to_owned(),
1422 vec![args.source_range],
1423 ))),
1424 }
1425}