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