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