1use anyhow::Result;
2use ezpz::Constraint as SolverConstraint;
3use ezpz::datatypes::AngleKind;
4use ezpz::datatypes::inputs::DatumCircle;
5use ezpz::datatypes::inputs::DatumCircularArc;
6use ezpz::datatypes::inputs::DatumDistance;
7use ezpz::datatypes::inputs::DatumLineSegment;
8use ezpz::datatypes::inputs::DatumPoint;
9use kittycad_modeling_cmds as kcmc;
10
11use crate::errors::KclError;
12use crate::errors::KclErrorDetails;
13use crate::execution::AbstractSegment;
14#[cfg(feature = "artifact-graph")]
15use crate::execution::Artifact;
16#[cfg(feature = "artifact-graph")]
17use crate::execution::CodeRef;
18use crate::execution::ConstrainablePoint2d;
19use crate::execution::ConstrainablePoint2dOrOrigin;
20use crate::execution::ExecState;
21use crate::execution::KclValue;
22use crate::execution::SegmentRepr;
23#[cfg(feature = "artifact-graph")]
24use crate::execution::SketchBlockConstraint;
25#[cfg(feature = "artifact-graph")]
26use crate::execution::SketchBlockConstraintType;
27use crate::execution::SketchConstraint;
28use crate::execution::SketchConstraintKind;
29use crate::execution::SketchVarId;
30use crate::execution::UnsolvedExpr;
31use crate::execution::UnsolvedSegment;
32use crate::execution::UnsolvedSegmentKind;
33use crate::execution::normalize_to_solver_distance_unit;
34use crate::execution::solver_numeric_type;
35use crate::execution::types::ArrayLen;
36use crate::execution::types::PrimitiveType;
37use crate::execution::types::RuntimeType;
38use crate::front::ArcCtor;
39use crate::front::CircleCtor;
40#[cfg(feature = "artifact-graph")]
41use crate::front::Coincident;
42#[cfg(feature = "artifact-graph")]
43use crate::front::Constraint;
44#[cfg(feature = "artifact-graph")]
45use crate::front::Horizontal;
46use crate::front::LineCtor;
47#[cfg(feature = "artifact-graph")]
48use crate::front::LinesEqualLength;
49#[cfg(feature = "artifact-graph")]
50use crate::front::Object;
51use crate::front::ObjectId;
52#[cfg(feature = "artifact-graph")]
53use crate::front::ObjectKind;
54#[cfg(feature = "artifact-graph")]
55use crate::front::Parallel;
56#[cfg(feature = "artifact-graph")]
57use crate::front::Perpendicular;
58use crate::front::Point2d;
59use crate::front::PointCtor;
60#[cfg(feature = "artifact-graph")]
61use crate::front::SourceRef;
62#[cfg(feature = "artifact-graph")]
63use crate::front::Tangent;
64#[cfg(feature = "artifact-graph")]
65use crate::front::Vertical;
66#[cfg(feature = "artifact-graph")]
67use crate::frontend::sketch::ConstraintSegment;
68use crate::std::Args;
69use crate::std::args::FromKclValue;
70use crate::std::args::TyF64;
71
72fn point2d_is_origin(point2d: &KclValue) -> bool {
73 let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point2d) else {
74 return false;
75 };
76 if x.ty.as_length().is_none() || y.ty.as_length().is_none() {
79 return false;
80 }
81 x.n == 0.0 && y.n == 0.0
84}
85
86#[cfg(feature = "artifact-graph")]
87fn coincident_segments_for_segment_and_point2d(
88 segment_id: ObjectId,
89 point2d: &KclValue,
90 segment_first: bool,
91) -> Vec<ConstraintSegment> {
92 if !point2d_is_origin(point2d) {
93 return vec![segment_id.into()];
94 }
95
96 if segment_first {
97 vec![segment_id.into(), ConstraintSegment::ORIGIN]
98 } else {
99 vec![ConstraintSegment::ORIGIN, segment_id.into()]
100 }
101}
102
103pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
104 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
105 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
106 KclError::new_semantic(KclErrorDetails::new(
107 "at must be a 2D point".to_owned(),
108 vec![args.source_range],
109 ))
110 })?;
111 let Some(at_x) = at_x_value.as_unsolved_expr() else {
112 return Err(KclError::new_semantic(KclErrorDetails::new(
113 "at x must be a number or sketch var".to_owned(),
114 vec![args.source_range],
115 )));
116 };
117 let Some(at_y) = at_y_value.as_unsolved_expr() else {
118 return Err(KclError::new_semantic(KclErrorDetails::new(
119 "at y must be a number or sketch var".to_owned(),
120 vec![args.source_range],
121 )));
122 };
123 let ctor = PointCtor {
124 position: Point2d {
125 x: at_x_value.to_sketch_expr().ok_or_else(|| {
126 KclError::new_semantic(KclErrorDetails::new(
127 "unable to convert numeric type to suffix".to_owned(),
128 vec![args.source_range],
129 ))
130 })?,
131 y: at_y_value.to_sketch_expr().ok_or_else(|| {
132 KclError::new_semantic(KclErrorDetails::new(
133 "unable to convert numeric type to suffix".to_owned(),
134 vec![args.source_range],
135 ))
136 })?,
137 },
138 };
139 let segment = UnsolvedSegment {
140 id: exec_state.next_uuid(),
141 object_id: exec_state.next_object_id(),
142 kind: UnsolvedSegmentKind::Point {
143 position: [at_x, at_y],
144 ctor: Box::new(ctor),
145 },
146 tag: None,
147 node_path: args.node_path.clone(),
148 meta: vec![args.source_range.into()],
149 };
150 #[cfg(feature = "artifact-graph")]
151 let optional_constraints = {
152 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range, args.node_path);
153
154 let mut optional_constraints = Vec::new();
155 if exec_state.segment_ids_edited_contains(&object_id) {
156 if let Some(at_x_var) = at_x_value.as_sketch_var() {
157 let x_initial_value = at_x_var.initial_value_to_solver_units(
158 exec_state,
159 args.source_range,
160 "edited segment fixed constraint value",
161 )?;
162 optional_constraints.push(SolverConstraint::Fixed(
163 at_x_var.id.to_constraint_id(args.source_range)?,
164 x_initial_value.n,
165 ));
166 }
167 if let Some(at_y_var) = at_y_value.as_sketch_var() {
168 let y_initial_value = at_y_var.initial_value_to_solver_units(
169 exec_state,
170 args.source_range,
171 "edited segment fixed constraint value",
172 )?;
173 optional_constraints.push(SolverConstraint::Fixed(
174 at_y_var.id.to_constraint_id(args.source_range)?,
175 y_initial_value.n,
176 ));
177 }
178 }
179 optional_constraints
180 };
181
182 let Some(sketch_state) = exec_state.sketch_block_mut() else {
184 return Err(KclError::new_semantic(KclErrorDetails::new(
185 "line() can only be used inside a sketch block".to_owned(),
186 vec![args.source_range],
187 )));
188 };
189 sketch_state.needed_by_engine.push(segment.clone());
190
191 #[cfg(feature = "artifact-graph")]
192 sketch_state.solver_optional_constraints.extend(optional_constraints);
193
194 let meta = segment.meta.clone();
195 let abstract_segment = AbstractSegment {
196 repr: SegmentRepr::Unsolved {
197 segment: Box::new(segment),
198 },
199 meta,
200 };
201 Ok(KclValue::Segment {
202 value: Box::new(abstract_segment),
203 })
204}
205
206pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
207 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
208 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
209 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
210 let construction: bool = construction_opt.unwrap_or(false);
211 let construction_ctor = construction_opt;
212 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
213 KclError::new_semantic(KclErrorDetails::new(
214 "start must be a 2D point".to_owned(),
215 vec![args.source_range],
216 ))
217 })?;
218 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
219 KclError::new_semantic(KclErrorDetails::new(
220 "end must be a 2D point".to_owned(),
221 vec![args.source_range],
222 ))
223 })?;
224 let Some(start_x) = start_x_value.as_unsolved_expr() else {
225 return Err(KclError::new_semantic(KclErrorDetails::new(
226 "start x must be a number or sketch var".to_owned(),
227 vec![args.source_range],
228 )));
229 };
230 let Some(start_y) = start_y_value.as_unsolved_expr() else {
231 return Err(KclError::new_semantic(KclErrorDetails::new(
232 "start y must be a number or sketch var".to_owned(),
233 vec![args.source_range],
234 )));
235 };
236 let Some(end_x) = end_x_value.as_unsolved_expr() else {
237 return Err(KclError::new_semantic(KclErrorDetails::new(
238 "end x must be a number or sketch var".to_owned(),
239 vec![args.source_range],
240 )));
241 };
242 let Some(end_y) = end_y_value.as_unsolved_expr() else {
243 return Err(KclError::new_semantic(KclErrorDetails::new(
244 "end y must be a number or sketch var".to_owned(),
245 vec![args.source_range],
246 )));
247 };
248 let ctor = LineCtor {
249 start: Point2d {
250 x: start_x_value.to_sketch_expr().ok_or_else(|| {
251 KclError::new_semantic(KclErrorDetails::new(
252 "unable to convert numeric type to suffix".to_owned(),
253 vec![args.source_range],
254 ))
255 })?,
256 y: start_y_value.to_sketch_expr().ok_or_else(|| {
257 KclError::new_semantic(KclErrorDetails::new(
258 "unable to convert numeric type to suffix".to_owned(),
259 vec![args.source_range],
260 ))
261 })?,
262 },
263 end: Point2d {
264 x: end_x_value.to_sketch_expr().ok_or_else(|| {
265 KclError::new_semantic(KclErrorDetails::new(
266 "unable to convert numeric type to suffix".to_owned(),
267 vec![args.source_range],
268 ))
269 })?,
270 y: end_y_value.to_sketch_expr().ok_or_else(|| {
271 KclError::new_semantic(KclErrorDetails::new(
272 "unable to convert numeric type to suffix".to_owned(),
273 vec![args.source_range],
274 ))
275 })?,
276 },
277 construction: construction_ctor,
278 };
279 let start_object_id = exec_state.next_object_id();
281 let end_object_id = exec_state.next_object_id();
282 let line_object_id = exec_state.next_object_id();
283 let segment = UnsolvedSegment {
284 id: exec_state.next_uuid(),
285 object_id: line_object_id,
286 kind: UnsolvedSegmentKind::Line {
287 start: [start_x, start_y],
288 end: [end_x, end_y],
289 ctor: Box::new(ctor),
290 start_object_id,
291 end_object_id,
292 construction,
293 },
294 tag: None,
295 node_path: args.node_path.clone(),
296 meta: vec![args.source_range.into()],
297 };
298 #[cfg(feature = "artifact-graph")]
299 let optional_constraints = {
300 let start_object_id =
301 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
302 let end_object_id =
303 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
304 let line_object_id =
305 exec_state.add_placeholder_scene_object(line_object_id, args.source_range, args.node_path.clone());
306
307 let mut optional_constraints = Vec::new();
308 if exec_state.segment_ids_edited_contains(&start_object_id)
309 || exec_state.segment_ids_edited_contains(&line_object_id)
310 {
311 if let Some(start_x_var) = start_x_value.as_sketch_var() {
312 let x_initial_value = start_x_var.initial_value_to_solver_units(
313 exec_state,
314 args.source_range,
315 "edited segment fixed constraint value",
316 )?;
317 optional_constraints.push(SolverConstraint::Fixed(
318 start_x_var.id.to_constraint_id(args.source_range)?,
319 x_initial_value.n,
320 ));
321 }
322 if let Some(start_y_var) = start_y_value.as_sketch_var() {
323 let y_initial_value = start_y_var.initial_value_to_solver_units(
324 exec_state,
325 args.source_range,
326 "edited segment fixed constraint value",
327 )?;
328 optional_constraints.push(SolverConstraint::Fixed(
329 start_y_var.id.to_constraint_id(args.source_range)?,
330 y_initial_value.n,
331 ));
332 }
333 }
334 if exec_state.segment_ids_edited_contains(&end_object_id)
335 || exec_state.segment_ids_edited_contains(&line_object_id)
336 {
337 if let Some(end_x_var) = end_x_value.as_sketch_var() {
338 let x_initial_value = end_x_var.initial_value_to_solver_units(
339 exec_state,
340 args.source_range,
341 "edited segment fixed constraint value",
342 )?;
343 optional_constraints.push(SolverConstraint::Fixed(
344 end_x_var.id.to_constraint_id(args.source_range)?,
345 x_initial_value.n,
346 ));
347 }
348 if let Some(end_y_var) = end_y_value.as_sketch_var() {
349 let y_initial_value = end_y_var.initial_value_to_solver_units(
350 exec_state,
351 args.source_range,
352 "edited segment fixed constraint value",
353 )?;
354 optional_constraints.push(SolverConstraint::Fixed(
355 end_y_var.id.to_constraint_id(args.source_range)?,
356 y_initial_value.n,
357 ));
358 }
359 }
360 optional_constraints
361 };
362
363 let Some(sketch_state) = exec_state.sketch_block_mut() else {
365 return Err(KclError::new_semantic(KclErrorDetails::new(
366 "line() can only be used inside a sketch block".to_owned(),
367 vec![args.source_range],
368 )));
369 };
370 sketch_state.needed_by_engine.push(segment.clone());
371
372 #[cfg(feature = "artifact-graph")]
373 sketch_state.solver_optional_constraints.extend(optional_constraints);
374
375 let meta = segment.meta.clone();
376 let abstract_segment = AbstractSegment {
377 repr: SegmentRepr::Unsolved {
378 segment: Box::new(segment),
379 },
380 meta,
381 };
382 Ok(KclValue::Segment {
383 value: Box::new(abstract_segment),
384 })
385}
386
387pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
388 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
389 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
390 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
392 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
393 let construction: bool = construction_opt.unwrap_or(false);
394 let construction_ctor = construction_opt;
395
396 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
397 KclError::new_semantic(KclErrorDetails::new(
398 "start must be a 2D point".to_owned(),
399 vec![args.source_range],
400 ))
401 })?;
402 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
403 KclError::new_semantic(KclErrorDetails::new(
404 "end must be a 2D point".to_owned(),
405 vec![args.source_range],
406 ))
407 })?;
408 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
409 KclError::new_semantic(KclErrorDetails::new(
410 "center must be a 2D point".to_owned(),
411 vec![args.source_range],
412 ))
413 })?;
414
415 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
416 return Err(KclError::new_semantic(KclErrorDetails::new(
417 "start x must be a sketch var".to_owned(),
418 vec![args.source_range],
419 )));
420 };
421 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
422 return Err(KclError::new_semantic(KclErrorDetails::new(
423 "start y must be a sketch var".to_owned(),
424 vec![args.source_range],
425 )));
426 };
427 let Some(UnsolvedExpr::Unknown(end_x)) = end_x_value.as_unsolved_expr() else {
428 return Err(KclError::new_semantic(KclErrorDetails::new(
429 "end x must be a sketch var".to_owned(),
430 vec![args.source_range],
431 )));
432 };
433 let Some(UnsolvedExpr::Unknown(end_y)) = end_y_value.as_unsolved_expr() else {
434 return Err(KclError::new_semantic(KclErrorDetails::new(
435 "end y must be a sketch var".to_owned(),
436 vec![args.source_range],
437 )));
438 };
439 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
440 return Err(KclError::new_semantic(KclErrorDetails::new(
441 "center x must be a sketch var".to_owned(),
442 vec![args.source_range],
443 )));
444 };
445 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
446 return Err(KclError::new_semantic(KclErrorDetails::new(
447 "center y must be a sketch var".to_owned(),
448 vec![args.source_range],
449 )));
450 };
451
452 let ctor = ArcCtor {
453 start: Point2d {
454 x: start_x_value.to_sketch_expr().ok_or_else(|| {
455 KclError::new_semantic(KclErrorDetails::new(
456 "unable to convert numeric type to suffix".to_owned(),
457 vec![args.source_range],
458 ))
459 })?,
460 y: start_y_value.to_sketch_expr().ok_or_else(|| {
461 KclError::new_semantic(KclErrorDetails::new(
462 "unable to convert numeric type to suffix".to_owned(),
463 vec![args.source_range],
464 ))
465 })?,
466 },
467 end: Point2d {
468 x: end_x_value.to_sketch_expr().ok_or_else(|| {
469 KclError::new_semantic(KclErrorDetails::new(
470 "unable to convert numeric type to suffix".to_owned(),
471 vec![args.source_range],
472 ))
473 })?,
474 y: end_y_value.to_sketch_expr().ok_or_else(|| {
475 KclError::new_semantic(KclErrorDetails::new(
476 "unable to convert numeric type to suffix".to_owned(),
477 vec![args.source_range],
478 ))
479 })?,
480 },
481 center: Point2d {
482 x: center_x_value.to_sketch_expr().ok_or_else(|| {
483 KclError::new_semantic(KclErrorDetails::new(
484 "unable to convert numeric type to suffix".to_owned(),
485 vec![args.source_range],
486 ))
487 })?,
488 y: center_y_value.to_sketch_expr().ok_or_else(|| {
489 KclError::new_semantic(KclErrorDetails::new(
490 "unable to convert numeric type to suffix".to_owned(),
491 vec![args.source_range],
492 ))
493 })?,
494 },
495 construction: construction_ctor,
496 };
497
498 let start_object_id = exec_state.next_object_id();
500 let end_object_id = exec_state.next_object_id();
501 let center_object_id = exec_state.next_object_id();
502 let arc_object_id = exec_state.next_object_id();
503 let segment = UnsolvedSegment {
504 id: exec_state.next_uuid(),
505 object_id: arc_object_id,
506 kind: UnsolvedSegmentKind::Arc {
507 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
508 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
509 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
510 ctor: Box::new(ctor),
511 start_object_id,
512 end_object_id,
513 center_object_id,
514 construction,
515 },
516 tag: None,
517 node_path: args.node_path.clone(),
518 meta: vec![args.source_range.into()],
519 };
520 #[cfg(feature = "artifact-graph")]
521 let optional_constraints = {
522 let start_object_id =
523 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
524 let end_object_id =
525 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
526 let center_object_id =
527 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
528 let arc_object_id =
529 exec_state.add_placeholder_scene_object(arc_object_id, args.source_range, args.node_path.clone());
530
531 let mut optional_constraints = Vec::new();
532 if exec_state.segment_ids_edited_contains(&start_object_id)
533 || exec_state.segment_ids_edited_contains(&arc_object_id)
534 {
535 if let Some(start_x_var) = start_x_value.as_sketch_var() {
536 let x_initial_value = start_x_var.initial_value_to_solver_units(
537 exec_state,
538 args.source_range,
539 "edited segment fixed constraint value",
540 )?;
541 optional_constraints.push(ezpz::Constraint::Fixed(
542 start_x_var.id.to_constraint_id(args.source_range)?,
543 x_initial_value.n,
544 ));
545 }
546 if let Some(start_y_var) = start_y_value.as_sketch_var() {
547 let y_initial_value = start_y_var.initial_value_to_solver_units(
548 exec_state,
549 args.source_range,
550 "edited segment fixed constraint value",
551 )?;
552 optional_constraints.push(ezpz::Constraint::Fixed(
553 start_y_var.id.to_constraint_id(args.source_range)?,
554 y_initial_value.n,
555 ));
556 }
557 }
558 if exec_state.segment_ids_edited_contains(&end_object_id)
559 || exec_state.segment_ids_edited_contains(&arc_object_id)
560 {
561 if let Some(end_x_var) = end_x_value.as_sketch_var() {
562 let x_initial_value = end_x_var.initial_value_to_solver_units(
563 exec_state,
564 args.source_range,
565 "edited segment fixed constraint value",
566 )?;
567 optional_constraints.push(ezpz::Constraint::Fixed(
568 end_x_var.id.to_constraint_id(args.source_range)?,
569 x_initial_value.n,
570 ));
571 }
572 if let Some(end_y_var) = end_y_value.as_sketch_var() {
573 let y_initial_value = end_y_var.initial_value_to_solver_units(
574 exec_state,
575 args.source_range,
576 "edited segment fixed constraint value",
577 )?;
578 optional_constraints.push(ezpz::Constraint::Fixed(
579 end_y_var.id.to_constraint_id(args.source_range)?,
580 y_initial_value.n,
581 ));
582 }
583 }
584 if exec_state.segment_ids_edited_contains(¢er_object_id)
585 || exec_state.segment_ids_edited_contains(&arc_object_id)
586 {
587 if let Some(center_x_var) = center_x_value.as_sketch_var() {
588 let x_initial_value = center_x_var.initial_value_to_solver_units(
589 exec_state,
590 args.source_range,
591 "edited segment fixed constraint value",
592 )?;
593 optional_constraints.push(ezpz::Constraint::Fixed(
594 center_x_var.id.to_constraint_id(args.source_range)?,
595 x_initial_value.n,
596 ));
597 }
598 if let Some(center_y_var) = center_y_value.as_sketch_var() {
599 let y_initial_value = center_y_var.initial_value_to_solver_units(
600 exec_state,
601 args.source_range,
602 "edited segment fixed constraint value",
603 )?;
604 optional_constraints.push(ezpz::Constraint::Fixed(
605 center_y_var.id.to_constraint_id(args.source_range)?,
606 y_initial_value.n,
607 ));
608 }
609 }
610 optional_constraints
611 };
612
613 let range = args.source_range;
615 let constraint = ezpz::Constraint::Arc(ezpz::datatypes::inputs::DatumCircularArc {
616 center: ezpz::datatypes::inputs::DatumPoint::new_xy(
617 center_x.to_constraint_id(range)?,
618 center_y.to_constraint_id(range)?,
619 ),
620 start: ezpz::datatypes::inputs::DatumPoint::new_xy(
621 start_x.to_constraint_id(range)?,
622 start_y.to_constraint_id(range)?,
623 ),
624 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
625 end_x.to_constraint_id(range)?,
626 end_y.to_constraint_id(range)?,
627 ),
628 });
629
630 let Some(sketch_state) = exec_state.sketch_block_mut() else {
631 return Err(KclError::new_semantic(KclErrorDetails::new(
632 "arc() can only be used inside a sketch block".to_owned(),
633 vec![args.source_range],
634 )));
635 };
636 sketch_state.needed_by_engine.push(segment.clone());
638 sketch_state.solver_constraints.push(constraint);
640 #[cfg(feature = "artifact-graph")]
644 sketch_state.solver_optional_constraints.extend(optional_constraints);
645
646 let meta = segment.meta.clone();
647 let abstract_segment = AbstractSegment {
648 repr: SegmentRepr::Unsolved {
649 segment: Box::new(segment),
650 },
651 meta,
652 };
653 Ok(KclValue::Segment {
654 value: Box::new(abstract_segment),
655 })
656}
657
658pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
659 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
660 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
661 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
662 let construction: bool = construction_opt.unwrap_or(false);
663 let construction_ctor = construction_opt;
664
665 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
666 KclError::new_semantic(KclErrorDetails::new(
667 "start must be a 2D point".to_owned(),
668 vec![args.source_range],
669 ))
670 })?;
671 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
672 KclError::new_semantic(KclErrorDetails::new(
673 "center must be a 2D point".to_owned(),
674 vec![args.source_range],
675 ))
676 })?;
677
678 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
679 return Err(KclError::new_semantic(KclErrorDetails::new(
680 "start x must be a sketch var".to_owned(),
681 vec![args.source_range],
682 )));
683 };
684 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
685 return Err(KclError::new_semantic(KclErrorDetails::new(
686 "start y must be a sketch var".to_owned(),
687 vec![args.source_range],
688 )));
689 };
690 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
691 return Err(KclError::new_semantic(KclErrorDetails::new(
692 "center x must be a sketch var".to_owned(),
693 vec![args.source_range],
694 )));
695 };
696 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
697 return Err(KclError::new_semantic(KclErrorDetails::new(
698 "center y must be a sketch var".to_owned(),
699 vec![args.source_range],
700 )));
701 };
702
703 let ctor = CircleCtor {
704 start: Point2d {
705 x: start_x_value.to_sketch_expr().ok_or_else(|| {
706 KclError::new_semantic(KclErrorDetails::new(
707 "unable to convert numeric type to suffix".to_owned(),
708 vec![args.source_range],
709 ))
710 })?,
711 y: start_y_value.to_sketch_expr().ok_or_else(|| {
712 KclError::new_semantic(KclErrorDetails::new(
713 "unable to convert numeric type to suffix".to_owned(),
714 vec![args.source_range],
715 ))
716 })?,
717 },
718 center: Point2d {
719 x: center_x_value.to_sketch_expr().ok_or_else(|| {
720 KclError::new_semantic(KclErrorDetails::new(
721 "unable to convert numeric type to suffix".to_owned(),
722 vec![args.source_range],
723 ))
724 })?,
725 y: center_y_value.to_sketch_expr().ok_or_else(|| {
726 KclError::new_semantic(KclErrorDetails::new(
727 "unable to convert numeric type to suffix".to_owned(),
728 vec![args.source_range],
729 ))
730 })?,
731 },
732 construction: construction_ctor,
733 };
734
735 let start_object_id = exec_state.next_object_id();
737 let center_object_id = exec_state.next_object_id();
738 let circle_object_id = exec_state.next_object_id();
739 let segment = UnsolvedSegment {
740 id: exec_state.next_uuid(),
741 object_id: circle_object_id,
742 kind: UnsolvedSegmentKind::Circle {
743 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
744 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
745 ctor: Box::new(ctor),
746 start_object_id,
747 center_object_id,
748 construction,
749 },
750 tag: None,
751 node_path: args.node_path.clone(),
752 meta: vec![args.source_range.into()],
753 };
754 #[cfg(feature = "artifact-graph")]
755 let optional_constraints = {
756 let start_object_id =
757 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
758 let center_object_id =
759 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
760 let circle_object_id =
761 exec_state.add_placeholder_scene_object(circle_object_id, args.source_range, args.node_path.clone());
762
763 let mut optional_constraints = Vec::new();
764 if exec_state.segment_ids_edited_contains(&start_object_id)
765 || exec_state.segment_ids_edited_contains(&circle_object_id)
766 {
767 if let Some(start_x_var) = start_x_value.as_sketch_var() {
768 let x_initial_value = start_x_var.initial_value_to_solver_units(
769 exec_state,
770 args.source_range,
771 "edited segment fixed constraint value",
772 )?;
773 optional_constraints.push(ezpz::Constraint::Fixed(
774 start_x_var.id.to_constraint_id(args.source_range)?,
775 x_initial_value.n,
776 ));
777 }
778 if let Some(start_y_var) = start_y_value.as_sketch_var() {
779 let y_initial_value = start_y_var.initial_value_to_solver_units(
780 exec_state,
781 args.source_range,
782 "edited segment fixed constraint value",
783 )?;
784 optional_constraints.push(ezpz::Constraint::Fixed(
785 start_y_var.id.to_constraint_id(args.source_range)?,
786 y_initial_value.n,
787 ));
788 }
789 }
790 if exec_state.segment_ids_edited_contains(¢er_object_id)
791 || exec_state.segment_ids_edited_contains(&circle_object_id)
792 {
793 if let Some(center_x_var) = center_x_value.as_sketch_var() {
794 let x_initial_value = center_x_var.initial_value_to_solver_units(
795 exec_state,
796 args.source_range,
797 "edited segment fixed constraint value",
798 )?;
799 optional_constraints.push(ezpz::Constraint::Fixed(
800 center_x_var.id.to_constraint_id(args.source_range)?,
801 x_initial_value.n,
802 ));
803 }
804 if let Some(center_y_var) = center_y_value.as_sketch_var() {
805 let y_initial_value = center_y_var.initial_value_to_solver_units(
806 exec_state,
807 args.source_range,
808 "edited segment fixed constraint value",
809 )?;
810 optional_constraints.push(ezpz::Constraint::Fixed(
811 center_y_var.id.to_constraint_id(args.source_range)?,
812 y_initial_value.n,
813 ));
814 }
815 }
816 optional_constraints
817 };
818
819 let Some(sketch_state) = exec_state.sketch_block_mut() else {
820 return Err(KclError::new_semantic(KclErrorDetails::new(
821 "circle() can only be used inside a sketch block".to_owned(),
822 vec![args.source_range],
823 )));
824 };
825 sketch_state.needed_by_engine.push(segment.clone());
827
828 #[cfg(feature = "artifact-graph")]
829 sketch_state.solver_optional_constraints.extend(optional_constraints);
830
831 let meta = segment.meta.clone();
832 let abstract_segment = AbstractSegment {
833 repr: SegmentRepr::Unsolved {
834 segment: Box::new(segment),
835 },
836 meta,
837 };
838 Ok(KclValue::Segment {
839 value: Box::new(abstract_segment),
840 })
841}
842
843pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
844 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
845 "points",
846 &RuntimeType::Array(
847 Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
848 ArrayLen::Known(2),
849 ),
850 exec_state,
851 )?;
852 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
853 KclError::new_semantic(KclErrorDetails::new(
854 "must have two input points".to_owned(),
855 vec![args.source_range],
856 ))
857 })?;
858
859 let range = args.source_range;
860 match (&point0, &point1) {
861 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
862 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
863 return Err(KclError::new_semantic(KclErrorDetails::new(
864 "first point must be an unsolved segment".to_owned(),
865 vec![args.source_range],
866 )));
867 };
868 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
869 return Err(KclError::new_semantic(KclErrorDetails::new(
870 "second point must be an unsolved segment".to_owned(),
871 vec![args.source_range],
872 )));
873 };
874 match (&unsolved0.kind, &unsolved1.kind) {
875 (
876 UnsolvedSegmentKind::Point { position: pos0, .. },
877 UnsolvedSegmentKind::Point { position: pos1, .. },
878 ) => {
879 let p0_x = &pos0[0];
880 let p0_y = &pos0[1];
881 match (p0_x, p0_y) {
882 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
883 let p1_x = &pos1[0];
884 let p1_y = &pos1[1];
885 match (p1_x, p1_y) {
886 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
887 let constraint = SolverConstraint::PointsCoincident(
888 ezpz::datatypes::inputs::DatumPoint::new_xy(
889 p0_x.to_constraint_id(range)?,
890 p0_y.to_constraint_id(range)?,
891 ),
892 ezpz::datatypes::inputs::DatumPoint::new_xy(
893 p1_x.to_constraint_id(range)?,
894 p1_y.to_constraint_id(range)?,
895 ),
896 );
897 #[cfg(feature = "artifact-graph")]
898 let constraint_id = exec_state.next_object_id();
899 let Some(sketch_state) = exec_state.sketch_block_mut() else {
901 return Err(KclError::new_semantic(KclErrorDetails::new(
902 "coincident() can only be used inside a sketch block".to_owned(),
903 vec![args.source_range],
904 )));
905 };
906 sketch_state.solver_constraints.push(constraint);
907 #[cfg(feature = "artifact-graph")]
908 {
909 let constraint = crate::front::Constraint::Coincident(Coincident {
910 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
911 });
912 sketch_state.sketch_constraints.push(constraint_id);
913 track_constraint(constraint_id, constraint, exec_state, &args);
914 }
915 Ok(KclValue::none())
916 }
917 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
918 let p1_x = KclValue::Number {
919 value: p1_x.n,
920 ty: p1_x.ty,
921 meta: vec![args.source_range.into()],
922 };
923 let p1_y = KclValue::Number {
924 value: p1_y.n,
925 ty: p1_y.ty,
926 meta: vec![args.source_range.into()],
927 };
928 let (constraint_x, constraint_y) =
929 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
930
931 #[cfg(feature = "artifact-graph")]
932 let constraint_id = exec_state.next_object_id();
933 let Some(sketch_state) = exec_state.sketch_block_mut() else {
935 return Err(KclError::new_semantic(KclErrorDetails::new(
936 "coincident() can only be used inside a sketch block".to_owned(),
937 vec![args.source_range],
938 )));
939 };
940 sketch_state.solver_constraints.push(constraint_x);
941 sketch_state.solver_constraints.push(constraint_y);
942 #[cfg(feature = "artifact-graph")]
943 {
944 let constraint = crate::front::Constraint::Coincident(Coincident {
945 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
946 });
947 sketch_state.sketch_constraints.push(constraint_id);
948 track_constraint(constraint_id, constraint, exec_state, &args);
949 }
950 Ok(KclValue::none())
951 }
952 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
953 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
954 Err(KclError::new_semantic(KclErrorDetails::new(
956 "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(),
957 vec![args.source_range],
958 )))
959 }
960 }
961 }
962 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
963 let p1_x = &pos1[0];
964 let p1_y = &pos1[1];
965 match (p1_x, p1_y) {
966 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
967 let p0_x = KclValue::Number {
968 value: p0_x.n,
969 ty: p0_x.ty,
970 meta: vec![args.source_range.into()],
971 };
972 let p0_y = KclValue::Number {
973 value: p0_y.n,
974 ty: p0_y.ty,
975 meta: vec![args.source_range.into()],
976 };
977 let (constraint_x, constraint_y) =
978 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
979
980 #[cfg(feature = "artifact-graph")]
981 let constraint_id = exec_state.next_object_id();
982 let Some(sketch_state) = exec_state.sketch_block_mut() else {
984 return Err(KclError::new_semantic(KclErrorDetails::new(
985 "coincident() can only be used inside a sketch block".to_owned(),
986 vec![args.source_range],
987 )));
988 };
989 sketch_state.solver_constraints.push(constraint_x);
990 sketch_state.solver_constraints.push(constraint_y);
991 #[cfg(feature = "artifact-graph")]
992 {
993 let constraint = crate::front::Constraint::Coincident(Coincident {
994 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
995 });
996 sketch_state.sketch_constraints.push(constraint_id);
997 track_constraint(constraint_id, constraint, exec_state, &args);
998 }
999 Ok(KclValue::none())
1000 }
1001 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1002 if *p0_x != *p1_x || *p0_y != *p1_y {
1003 return Err(KclError::new_semantic(KclErrorDetails::new(
1004 "Coincident constraint between two fixed points failed since coordinates differ"
1005 .to_owned(),
1006 vec![args.source_range],
1007 )));
1008 }
1009 Ok(KclValue::none())
1010 }
1011 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1012 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1013 Err(KclError::new_semantic(KclErrorDetails::new(
1015 "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(),
1016 vec![args.source_range],
1017 )))
1018 }
1019 }
1020 }
1021 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1022 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1023 Err(KclError::new_semantic(KclErrorDetails::new(
1025 "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(),
1026 vec![args.source_range],
1027 )))
1028 }
1029 }
1030 }
1031 (
1033 UnsolvedSegmentKind::Point {
1034 position: point_pos, ..
1035 },
1036 UnsolvedSegmentKind::Line {
1037 start: line_start,
1038 end: line_end,
1039 ..
1040 },
1041 )
1042 | (
1043 UnsolvedSegmentKind::Line {
1044 start: line_start,
1045 end: line_end,
1046 ..
1047 },
1048 UnsolvedSegmentKind::Point {
1049 position: point_pos, ..
1050 },
1051 ) => {
1052 let point_x = &point_pos[0];
1053 let point_y = &point_pos[1];
1054 match (point_x, point_y) {
1055 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1056 let (start_x, start_y) = (&line_start[0], &line_start[1]);
1058 let (end_x, end_y) = (&line_end[0], &line_end[1]);
1059
1060 match (start_x, start_y, end_x, end_y) {
1061 (
1062 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1063 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1064 ) => {
1065 let point = DatumPoint::new_xy(
1066 point_x.to_constraint_id(range)?,
1067 point_y.to_constraint_id(range)?,
1068 );
1069 let line_segment = DatumLineSegment::new(
1070 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
1071 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
1072 );
1073 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
1074
1075 #[cfg(feature = "artifact-graph")]
1076 let constraint_id = exec_state.next_object_id();
1077
1078 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1079 return Err(KclError::new_semantic(KclErrorDetails::new(
1080 "coincident() can only be used inside a sketch block".to_owned(),
1081 vec![args.source_range],
1082 )));
1083 };
1084 sketch_state.solver_constraints.push(constraint);
1085 #[cfg(feature = "artifact-graph")]
1086 {
1087 let constraint = crate::front::Constraint::Coincident(Coincident {
1088 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1089 });
1090 sketch_state.sketch_constraints.push(constraint_id);
1091 track_constraint(constraint_id, constraint, exec_state, &args);
1092 }
1093 Ok(KclValue::none())
1094 }
1095 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1096 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
1097 vec![args.source_range],
1098 ))),
1099 }
1100 }
1101 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1102 "Point coordinates must be sketch variables for point-segment coincident constraint"
1103 .to_owned(),
1104 vec![args.source_range],
1105 ))),
1106 }
1107 }
1108 (
1110 UnsolvedSegmentKind::Point {
1111 position: point_pos, ..
1112 },
1113 UnsolvedSegmentKind::Arc {
1114 start: arc_start,
1115 end: arc_end,
1116 center: arc_center,
1117 ..
1118 },
1119 )
1120 | (
1121 UnsolvedSegmentKind::Arc {
1122 start: arc_start,
1123 end: arc_end,
1124 center: arc_center,
1125 ..
1126 },
1127 UnsolvedSegmentKind::Point {
1128 position: point_pos, ..
1129 },
1130 ) => {
1131 let point_x = &point_pos[0];
1132 let point_y = &point_pos[1];
1133 match (point_x, point_y) {
1134 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1135 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
1137 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
1138 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
1139
1140 match (center_x, center_y, start_x, start_y, end_x, end_y) {
1141 (
1142 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
1143 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1144 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1145 ) => {
1146 let point = DatumPoint::new_xy(
1147 point_x.to_constraint_id(range)?,
1148 point_y.to_constraint_id(range)?,
1149 );
1150 let circular_arc = DatumCircularArc {
1151 center: DatumPoint::new_xy(
1152 cx.to_constraint_id(range)?,
1153 cy.to_constraint_id(range)?,
1154 ),
1155 start: DatumPoint::new_xy(
1156 sx.to_constraint_id(range)?,
1157 sy.to_constraint_id(range)?,
1158 ),
1159 end: DatumPoint::new_xy(
1160 ex.to_constraint_id(range)?,
1161 ey.to_constraint_id(range)?,
1162 ),
1163 };
1164 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
1165
1166 #[cfg(feature = "artifact-graph")]
1167 let constraint_id = exec_state.next_object_id();
1168
1169 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1170 return Err(KclError::new_semantic(KclErrorDetails::new(
1171 "coincident() can only be used inside a sketch block".to_owned(),
1172 vec![args.source_range],
1173 )));
1174 };
1175 sketch_state.solver_constraints.push(constraint);
1176 #[cfg(feature = "artifact-graph")]
1177 {
1178 let constraint = crate::front::Constraint::Coincident(Coincident {
1179 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1180 });
1181 sketch_state.sketch_constraints.push(constraint_id);
1182 track_constraint(constraint_id, constraint, exec_state, &args);
1183 }
1184 Ok(KclValue::none())
1185 }
1186 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1187 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
1188 vec![args.source_range],
1189 ))),
1190 }
1191 }
1192 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1193 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
1194 vec![args.source_range],
1195 ))),
1196 }
1197 }
1198 (
1201 UnsolvedSegmentKind::Point {
1202 position: point_pos, ..
1203 },
1204 UnsolvedSegmentKind::Circle {
1205 start: circle_start,
1206 center: circle_center,
1207 ..
1208 },
1209 )
1210 | (
1211 UnsolvedSegmentKind::Circle {
1212 start: circle_start,
1213 center: circle_center,
1214 ..
1215 },
1216 UnsolvedSegmentKind::Point {
1217 position: point_pos, ..
1218 },
1219 ) => {
1220 let point_x = &point_pos[0];
1221 let point_y = &point_pos[1];
1222 match (point_x, point_y) {
1223 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1224 let (center_x, center_y) = (&circle_center[0], &circle_center[1]);
1226 let (start_x, start_y) = (&circle_start[0], &circle_start[1]);
1227
1228 match (center_x, center_y, start_x, start_y) {
1229 (
1230 UnsolvedExpr::Unknown(cx),
1231 UnsolvedExpr::Unknown(cy),
1232 UnsolvedExpr::Unknown(sx),
1233 UnsolvedExpr::Unknown(sy),
1234 ) => {
1235 let point_radius_line = DatumLineSegment::new(
1236 DatumPoint::new_xy(
1237 cx.to_constraint_id(range)?,
1238 cy.to_constraint_id(range)?,
1239 ),
1240 DatumPoint::new_xy(
1241 point_x.to_constraint_id(range)?,
1242 point_y.to_constraint_id(range)?,
1243 ),
1244 );
1245 let circle_radius_line = DatumLineSegment::new(
1246 DatumPoint::new_xy(
1247 cx.to_constraint_id(range)?,
1248 cy.to_constraint_id(range)?,
1249 ),
1250 DatumPoint::new_xy(
1251 sx.to_constraint_id(range)?,
1252 sy.to_constraint_id(range)?,
1253 ),
1254 );
1255 let constraint =
1256 SolverConstraint::LinesEqualLength(point_radius_line, circle_radius_line);
1257
1258 #[cfg(feature = "artifact-graph")]
1259 let constraint_id = exec_state.next_object_id();
1260
1261 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1262 return Err(KclError::new_semantic(KclErrorDetails::new(
1263 "coincident() can only be used inside a sketch block".to_owned(),
1264 vec![args.source_range],
1265 )));
1266 };
1267 sketch_state.solver_constraints.push(constraint);
1268 #[cfg(feature = "artifact-graph")]
1269 {
1270 let constraint = crate::front::Constraint::Coincident(Coincident {
1271 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1272 });
1273 sketch_state.sketch_constraints.push(constraint_id);
1274 track_constraint(constraint_id, constraint, exec_state, &args);
1275 }
1276 Ok(KclValue::none())
1277 }
1278 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1279 "Circle start and center points must be sketch variables for point-circle coincident constraint".to_owned(),
1280 vec![args.source_range],
1281 ))),
1282 }
1283 }
1284 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1285 "Point coordinates must be sketch variables for point-circle coincident constraint"
1286 .to_owned(),
1287 vec![args.source_range],
1288 ))),
1289 }
1290 }
1291 (
1293 UnsolvedSegmentKind::Line {
1294 start: line0_start,
1295 end: line0_end,
1296 ..
1297 },
1298 UnsolvedSegmentKind::Line {
1299 start: line1_start,
1300 end: line1_end,
1301 ..
1302 },
1303 ) => {
1304 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
1306 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
1307 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
1308 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
1309
1310 match (
1311 line0_start_x,
1312 line0_start_y,
1313 line0_end_x,
1314 line0_end_y,
1315 line1_start_x,
1316 line1_start_y,
1317 line1_end_x,
1318 line1_end_y,
1319 ) {
1320 (
1321 UnsolvedExpr::Unknown(l0_sx),
1322 UnsolvedExpr::Unknown(l0_sy),
1323 UnsolvedExpr::Unknown(l0_ex),
1324 UnsolvedExpr::Unknown(l0_ey),
1325 UnsolvedExpr::Unknown(l1_sx),
1326 UnsolvedExpr::Unknown(l1_sy),
1327 UnsolvedExpr::Unknown(l1_ex),
1328 UnsolvedExpr::Unknown(l1_ey),
1329 ) => {
1330 let line0_segment = DatumLineSegment::new(
1332 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
1333 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
1334 );
1335 let line1_segment = DatumLineSegment::new(
1336 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
1337 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
1338 );
1339
1340 let parallel_constraint =
1342 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
1343
1344 let point_on_line1 =
1346 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
1347 let distance_constraint =
1348 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
1349
1350 #[cfg(feature = "artifact-graph")]
1351 let constraint_id = exec_state.next_object_id();
1352
1353 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1354 return Err(KclError::new_semantic(KclErrorDetails::new(
1355 "coincident() can only be used inside a sketch block".to_owned(),
1356 vec![args.source_range],
1357 )));
1358 };
1359 sketch_state.solver_constraints.push(parallel_constraint);
1361 sketch_state.solver_constraints.push(distance_constraint);
1362 #[cfg(feature = "artifact-graph")]
1363 {
1364 let constraint = crate::front::Constraint::Coincident(Coincident {
1365 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1366 });
1367 sketch_state.sketch_constraints.push(constraint_id);
1368 track_constraint(constraint_id, constraint, exec_state, &args);
1369 }
1370 Ok(KclValue::none())
1371 }
1372 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1373 "Line segment endpoints must be sketch variables for line-line coincident constraint"
1374 .to_owned(),
1375 vec![args.source_range],
1376 ))),
1377 }
1378 }
1379 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1380 format!(
1381 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
1382 &unsolved0.kind, &unsolved1.kind
1383 ),
1384 vec![args.source_range],
1385 ))),
1386 }
1387 }
1388 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
1391 let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
1392 return Err(KclError::new_semantic(KclErrorDetails::new(
1393 "Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
1394 vec![args.source_range],
1395 )));
1396 };
1397 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1398 return Err(KclError::new_semantic(KclErrorDetails::new(
1399 "segment must be an unsolved segment".to_owned(),
1400 vec![args.source_range],
1401 )));
1402 };
1403 match &unsolved.kind {
1404 UnsolvedSegmentKind::Point { position, .. } => {
1405 let p_x = &position[0];
1406 let p_y = &position[1];
1407 match (p_x, p_y) {
1408 (UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
1409 let pt_x = KclValue::Number {
1410 value: pt[0].n,
1411 ty: pt[0].ty,
1412 meta: vec![args.source_range.into()],
1413 };
1414 let pt_y = KclValue::Number {
1415 value: pt[1].n,
1416 ty: pt[1].ty,
1417 meta: vec![args.source_range.into()],
1418 };
1419 let (constraint_x, constraint_y) =
1420 coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
1421
1422 #[cfg(feature = "artifact-graph")]
1423 let constraint_id = exec_state.next_object_id();
1424 #[cfg(feature = "artifact-graph")]
1425 let coincident_segments = coincident_segments_for_segment_and_point2d(
1426 unsolved.object_id,
1427 point2d,
1428 matches!((&point0, &point1), (KclValue::Segment { .. }, _)),
1429 );
1430 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1431 return Err(KclError::new_semantic(KclErrorDetails::new(
1432 "coincident() can only be used inside a sketch block".to_owned(),
1433 vec![args.source_range],
1434 )));
1435 };
1436 sketch_state.solver_constraints.push(constraint_x);
1437 sketch_state.solver_constraints.push(constraint_y);
1438 #[cfg(feature = "artifact-graph")]
1439 {
1440 let constraint = crate::front::Constraint::Coincident(Coincident {
1441 segments: coincident_segments,
1442 });
1443 sketch_state.sketch_constraints.push(constraint_id);
1444 track_constraint(constraint_id, constraint, exec_state, &args);
1445 }
1446 Ok(KclValue::none())
1447 }
1448 (UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
1449 let pt_x_val = normalize_to_solver_distance_unit(
1450 &KclValue::Number {
1451 value: pt[0].n,
1452 ty: pt[0].ty,
1453 meta: vec![args.source_range.into()],
1454 },
1455 args.source_range,
1456 exec_state,
1457 "coincident constraint value",
1458 )?;
1459 let pt_y_val = normalize_to_solver_distance_unit(
1460 &KclValue::Number {
1461 value: pt[1].n,
1462 ty: pt[1].ty,
1463 meta: vec![args.source_range.into()],
1464 },
1465 args.source_range,
1466 exec_state,
1467 "coincident constraint value",
1468 )?;
1469 let Some(pt_x) = pt_x_val.as_ty_f64() else {
1470 return Err(KclError::new_semantic(KclErrorDetails::new(
1471 "Expected number for Point2d x coordinate".to_owned(),
1472 vec![args.source_range],
1473 )));
1474 };
1475 let Some(pt_y) = pt_y_val.as_ty_f64() else {
1476 return Err(KclError::new_semantic(KclErrorDetails::new(
1477 "Expected number for Point2d y coordinate".to_owned(),
1478 vec![args.source_range],
1479 )));
1480 };
1481 let known_x_val = normalize_to_solver_distance_unit(
1482 &KclValue::Number {
1483 value: known_x.n,
1484 ty: known_x.ty,
1485 meta: vec![args.source_range.into()],
1486 },
1487 args.source_range,
1488 exec_state,
1489 "coincident constraint value",
1490 )?;
1491 let Some(known_x_f) = known_x_val.as_ty_f64() else {
1492 return Err(KclError::new_semantic(KclErrorDetails::new(
1493 "Expected number for known x coordinate".to_owned(),
1494 vec![args.source_range],
1495 )));
1496 };
1497 let known_y_val = normalize_to_solver_distance_unit(
1498 &KclValue::Number {
1499 value: known_y.n,
1500 ty: known_y.ty,
1501 meta: vec![args.source_range.into()],
1502 },
1503 args.source_range,
1504 exec_state,
1505 "coincident constraint value",
1506 )?;
1507 let Some(known_y_f) = known_y_val.as_ty_f64() else {
1508 return Err(KclError::new_semantic(KclErrorDetails::new(
1509 "Expected number for known y coordinate".to_owned(),
1510 vec![args.source_range],
1511 )));
1512 };
1513 if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
1514 return Err(KclError::new_semantic(KclErrorDetails::new(
1515 "Coincident constraint between two fixed points failed since coordinates differ"
1516 .to_owned(),
1517 vec![args.source_range],
1518 )));
1519 }
1520 Ok(KclValue::none())
1521 }
1522 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1523 "Point coordinates must have consistent known/unknown status for coincident constraint"
1524 .to_owned(),
1525 vec![args.source_range],
1526 ))),
1527 }
1528 }
1529 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1530 "A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
1531 vec![args.source_range],
1532 ))),
1533 }
1534 }
1535 _ => {
1537 let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
1538 let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
1539 match (pt0, pt1) {
1540 (Some(a), Some(b)) => {
1541 let a_x = normalize_to_solver_distance_unit(
1543 &KclValue::Number {
1544 value: a[0].n,
1545 ty: a[0].ty,
1546 meta: vec![args.source_range.into()],
1547 },
1548 args.source_range,
1549 exec_state,
1550 "coincident constraint value",
1551 )?;
1552 let a_y = normalize_to_solver_distance_unit(
1553 &KclValue::Number {
1554 value: a[1].n,
1555 ty: a[1].ty,
1556 meta: vec![args.source_range.into()],
1557 },
1558 args.source_range,
1559 exec_state,
1560 "coincident constraint value",
1561 )?;
1562 let b_x = normalize_to_solver_distance_unit(
1563 &KclValue::Number {
1564 value: b[0].n,
1565 ty: b[0].ty,
1566 meta: vec![args.source_range.into()],
1567 },
1568 args.source_range,
1569 exec_state,
1570 "coincident constraint value",
1571 )?;
1572 let b_y = normalize_to_solver_distance_unit(
1573 &KclValue::Number {
1574 value: b[1].n,
1575 ty: b[1].ty,
1576 meta: vec![args.source_range.into()],
1577 },
1578 args.source_range,
1579 exec_state,
1580 "coincident constraint value",
1581 )?;
1582 if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
1583 || a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
1584 {
1585 return Err(KclError::new_semantic(KclErrorDetails::new(
1586 "Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
1587 vec![args.source_range],
1588 )));
1589 }
1590 Ok(KclValue::none())
1591 }
1592 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1593 "All inputs must be Segments or Point2d values".to_owned(),
1594 vec![args.source_range],
1595 ))),
1596 }
1597 }
1598 }
1599}
1600
1601#[cfg(feature = "artifact-graph")]
1602fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
1603 let sketch_id = {
1604 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1605 debug_assert!(false, "Constraint created outside a sketch block");
1606 return;
1607 };
1608 sketch_state.sketch_id
1609 };
1610 let Some(sketch_id) = sketch_id else {
1611 debug_assert!(false, "Constraint created without a sketch id");
1612 return;
1613 };
1614 let artifact_id = exec_state.next_artifact_id();
1615 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
1616 id: artifact_id,
1617 sketch_id,
1618 constraint_id,
1619 constraint_type: SketchBlockConstraintType::from(&constraint),
1620 code_ref: CodeRef::placeholder(args.source_range),
1621 }));
1622 exec_state.add_scene_object(
1623 Object {
1624 id: constraint_id,
1625 kind: ObjectKind::Constraint { constraint },
1626 label: Default::default(),
1627 comments: Default::default(),
1628 artifact_id,
1629 source: SourceRef::new(args.source_range, args.node_path.clone()),
1630 },
1631 args.source_range,
1632 );
1633}
1634
1635fn coincident_constraints_fixed(
1637 p0_x: SketchVarId,
1638 p0_y: SketchVarId,
1639 p1_x: &KclValue,
1640 p1_y: &KclValue,
1641 exec_state: &mut ExecState,
1642 args: &Args,
1643) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
1644 let p1_x_number_value =
1645 normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
1646 let p1_y_number_value =
1647 normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
1648 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
1649 let message = format!(
1650 "Expected number after coercion, but found {}",
1651 p1_x_number_value.human_friendly_type()
1652 );
1653 debug_assert!(false, "{}", &message);
1654 return Err(KclError::new_internal(KclErrorDetails::new(
1655 message,
1656 vec![args.source_range],
1657 )));
1658 };
1659 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
1660 let message = format!(
1661 "Expected number after coercion, but found {}",
1662 p1_y_number_value.human_friendly_type()
1663 );
1664 debug_assert!(false, "{}", &message);
1665 return Err(KclError::new_internal(KclErrorDetails::new(
1666 message,
1667 vec![args.source_range],
1668 )));
1669 };
1670 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
1671 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
1672 Ok((constraint_x, constraint_y))
1673}
1674
1675pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1676 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1677 "points",
1678 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1679 exec_state,
1680 )?;
1681 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1682 KclError::new_semantic(KclErrorDetails::new(
1683 "must have two input points".to_owned(),
1684 vec![args.source_range],
1685 ))
1686 })?;
1687
1688 match (&point0, &point1) {
1689 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1690 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1691 return Err(KclError::new_semantic(KclErrorDetails::new(
1692 "first point must be an unsolved segment".to_owned(),
1693 vec![args.source_range],
1694 )));
1695 };
1696 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1697 return Err(KclError::new_semantic(KclErrorDetails::new(
1698 "second point must be an unsolved segment".to_owned(),
1699 vec![args.source_range],
1700 )));
1701 };
1702 match (&unsolved0.kind, &unsolved1.kind) {
1703 (
1704 UnsolvedSegmentKind::Point { position: pos0, .. },
1705 UnsolvedSegmentKind::Point { position: pos1, .. },
1706 ) => {
1707 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1710 (
1711 UnsolvedExpr::Unknown(p0_x),
1712 UnsolvedExpr::Unknown(p0_y),
1713 UnsolvedExpr::Unknown(p1_x),
1714 UnsolvedExpr::Unknown(p1_y),
1715 ) => {
1716 let sketch_constraint = SketchConstraint {
1718 kind: SketchConstraintKind::Distance {
1719 points: [
1720 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1721 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1722 object_id: unsolved0.object_id,
1723 }),
1724 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1725 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1726 object_id: unsolved1.object_id,
1727 }),
1728 ],
1729 },
1730 meta: vec![args.source_range.into()],
1731 };
1732 Ok(KclValue::SketchConstraint {
1733 value: Box::new(sketch_constraint),
1734 })
1735 }
1736 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1737 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1738 vec![args.source_range],
1739 ))),
1740 }
1741 }
1742 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1743 "distance() arguments must be unsolved points".to_owned(),
1744 vec![args.source_range],
1745 ))),
1746 }
1747 }
1748 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
1750 if !point2d_is_origin(point2d) {
1751 return Err(KclError::new_semantic(KclErrorDetails::new(
1752 "distance() Point2d arguments must be ORIGIN".to_owned(),
1753 vec![args.source_range],
1754 )));
1755 }
1756
1757 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1758 return Err(KclError::new_semantic(KclErrorDetails::new(
1759 "segment must be an unsolved segment".to_owned(),
1760 vec![args.source_range],
1761 )));
1762 };
1763 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
1764 return Err(KclError::new_semantic(KclErrorDetails::new(
1765 "distance() arguments must be unsolved points or ORIGIN".to_owned(),
1766 vec![args.source_range],
1767 )));
1768 };
1769 match (&position[0], &position[1]) {
1770 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1771 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1772 vars: crate::front::Point2d {
1773 x: *point_x,
1774 y: *point_y,
1775 },
1776 object_id: unsolved.object_id,
1777 });
1778 let points = if matches!((&point0, &point1), (KclValue::Segment { .. }, _)) {
1779 [point, ConstrainablePoint2dOrOrigin::Origin]
1780 } else {
1781 [ConstrainablePoint2dOrOrigin::Origin, point]
1782 };
1783 Ok(KclValue::SketchConstraint {
1784 value: Box::new(SketchConstraint {
1785 kind: SketchConstraintKind::Distance { points },
1786 meta: vec![args.source_range.into()],
1787 }),
1788 })
1789 }
1790 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1791 "unimplemented: distance() point arguments must be sketch vars in all coordinates".to_owned(),
1792 vec![args.source_range],
1793 ))),
1794 }
1795 }
1796 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1797 "distance() arguments must be point segments or ORIGIN".to_owned(),
1798 vec![args.source_range],
1799 ))),
1800 }
1801}
1802
1803fn create_circular_radius_constraint(
1806 segment: KclValue,
1807 constraint_kind: fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
1808 source_range: crate::SourceRange,
1809) -> Result<SketchConstraint, KclError> {
1810 let dummy_constraint = constraint_kind([
1812 ConstrainablePoint2d {
1813 vars: crate::front::Point2d {
1814 x: SketchVarId(0),
1815 y: SketchVarId(0),
1816 },
1817 object_id: ObjectId(0),
1818 },
1819 ConstrainablePoint2d {
1820 vars: crate::front::Point2d {
1821 x: SketchVarId(0),
1822 y: SketchVarId(0),
1823 },
1824 object_id: ObjectId(0),
1825 },
1826 ]);
1827 let function_name = dummy_constraint.name();
1828
1829 let KclValue::Segment { value: seg } = segment else {
1830 return Err(KclError::new_semantic(KclErrorDetails::new(
1831 format!("{}() argument must be a segment", function_name),
1832 vec![source_range],
1833 )));
1834 };
1835 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1836 return Err(KclError::new_semantic(KclErrorDetails::new(
1837 "segment must be unsolved".to_owned(),
1838 vec![source_range],
1839 )));
1840 };
1841 match &unsolved.kind {
1842 UnsolvedSegmentKind::Arc {
1843 center,
1844 start,
1845 center_object_id,
1846 start_object_id,
1847 ..
1848 }
1849 | UnsolvedSegmentKind::Circle {
1850 center,
1851 start,
1852 center_object_id,
1853 start_object_id,
1854 ..
1855 } => {
1856 match (¢er[0], ¢er[1], &start[0], &start[1]) {
1858 (
1859 UnsolvedExpr::Unknown(center_x),
1860 UnsolvedExpr::Unknown(center_y),
1861 UnsolvedExpr::Unknown(start_x),
1862 UnsolvedExpr::Unknown(start_y),
1863 ) => {
1864 let sketch_constraint = SketchConstraint {
1866 kind: constraint_kind([
1867 ConstrainablePoint2d {
1868 vars: crate::front::Point2d {
1869 x: *center_x,
1870 y: *center_y,
1871 },
1872 object_id: *center_object_id,
1873 },
1874 ConstrainablePoint2d {
1875 vars: crate::front::Point2d {
1876 x: *start_x,
1877 y: *start_y,
1878 },
1879 object_id: *start_object_id,
1880 },
1881 ]),
1882 meta: vec![source_range.into()],
1883 };
1884 Ok(sketch_constraint)
1885 }
1886 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1887 format!(
1888 "unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
1889 function_name
1890 ),
1891 vec![source_range],
1892 ))),
1893 }
1894 }
1895 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1896 format!("{}() argument must be an arc or circle segment", function_name),
1897 vec![source_range],
1898 ))),
1899 }
1900}
1901
1902pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1903 let segment: KclValue =
1904 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1905
1906 create_circular_radius_constraint(
1907 segment,
1908 |points| SketchConstraintKind::Radius { points },
1909 args.source_range,
1910 )
1911 .map(|constraint| KclValue::SketchConstraint {
1912 value: Box::new(constraint),
1913 })
1914}
1915
1916pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1917 let segment: KclValue =
1918 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1919
1920 create_circular_radius_constraint(
1921 segment,
1922 |points| SketchConstraintKind::Diameter { points },
1923 args.source_range,
1924 )
1925 .map(|constraint| KclValue::SketchConstraint {
1926 value: Box::new(constraint),
1927 })
1928}
1929
1930pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1931 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1932 "points",
1933 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1934 exec_state,
1935 )?;
1936 let [p1, p2] = points.as_slice() else {
1937 return Err(KclError::new_semantic(KclErrorDetails::new(
1938 "must have two input points".to_owned(),
1939 vec![args.source_range],
1940 )));
1941 };
1942 match (p1, p2) {
1943 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1944 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1945 return Err(KclError::new_semantic(KclErrorDetails::new(
1946 "first point must be an unsolved segment".to_owned(),
1947 vec![args.source_range],
1948 )));
1949 };
1950 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1951 return Err(KclError::new_semantic(KclErrorDetails::new(
1952 "second point must be an unsolved segment".to_owned(),
1953 vec![args.source_range],
1954 )));
1955 };
1956 match (&unsolved0.kind, &unsolved1.kind) {
1957 (
1958 UnsolvedSegmentKind::Point { position: pos0, .. },
1959 UnsolvedSegmentKind::Point { position: pos1, .. },
1960 ) => {
1961 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1964 (
1965 UnsolvedExpr::Unknown(p0_x),
1966 UnsolvedExpr::Unknown(p0_y),
1967 UnsolvedExpr::Unknown(p1_x),
1968 UnsolvedExpr::Unknown(p1_y),
1969 ) => {
1970 let sketch_constraint = SketchConstraint {
1972 kind: SketchConstraintKind::HorizontalDistance {
1973 points: [
1974 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1975 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1976 object_id: unsolved0.object_id,
1977 }),
1978 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1979 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1980 object_id: unsolved1.object_id,
1981 }),
1982 ],
1983 },
1984 meta: vec![args.source_range.into()],
1985 };
1986 Ok(KclValue::SketchConstraint {
1987 value: Box::new(sketch_constraint),
1988 })
1989 }
1990 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1991 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
1992 .to_owned(),
1993 vec![args.source_range],
1994 ))),
1995 }
1996 }
1997 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1998 "horizontalDistance() arguments must be unsolved points".to_owned(),
1999 vec![args.source_range],
2000 ))),
2001 }
2002 }
2003 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2005 if !point2d_is_origin(point2d) {
2006 return Err(KclError::new_semantic(KclErrorDetails::new(
2007 "horizontalDistance() Point2d arguments must be ORIGIN".to_owned(),
2008 vec![args.source_range],
2009 )));
2010 }
2011
2012 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2013 return Err(KclError::new_semantic(KclErrorDetails::new(
2014 "segment must be an unsolved segment".to_owned(),
2015 vec![args.source_range],
2016 )));
2017 };
2018 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2019 return Err(KclError::new_semantic(KclErrorDetails::new(
2020 "horizontalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
2021 vec![args.source_range],
2022 )));
2023 };
2024 match (&position[0], &position[1]) {
2025 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2026 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2027 vars: crate::front::Point2d {
2028 x: *point_x,
2029 y: *point_y,
2030 },
2031 object_id: unsolved.object_id,
2032 });
2033 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
2034 [point, ConstrainablePoint2dOrOrigin::Origin]
2035 } else {
2036 [ConstrainablePoint2dOrOrigin::Origin, point]
2037 };
2038 Ok(KclValue::SketchConstraint {
2039 value: Box::new(SketchConstraint {
2040 kind: SketchConstraintKind::HorizontalDistance { points },
2041 meta: vec![args.source_range.into()],
2042 }),
2043 })
2044 }
2045 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2046 "unimplemented: horizontalDistance() point arguments must be sketch vars in all coordinates"
2047 .to_owned(),
2048 vec![args.source_range],
2049 ))),
2050 }
2051 }
2052 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2053 "horizontalDistance() arguments must be point segments or ORIGIN".to_owned(),
2054 vec![args.source_range],
2055 ))),
2056 }
2057}
2058
2059pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2060 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2061 "points",
2062 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2063 exec_state,
2064 )?;
2065 let [p1, p2] = points.as_slice() else {
2066 return Err(KclError::new_semantic(KclErrorDetails::new(
2067 "must have two input points".to_owned(),
2068 vec![args.source_range],
2069 )));
2070 };
2071 match (p1, p2) {
2072 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2073 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2074 return Err(KclError::new_semantic(KclErrorDetails::new(
2075 "first point must be an unsolved segment".to_owned(),
2076 vec![args.source_range],
2077 )));
2078 };
2079 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2080 return Err(KclError::new_semantic(KclErrorDetails::new(
2081 "second point must be an unsolved segment".to_owned(),
2082 vec![args.source_range],
2083 )));
2084 };
2085 match (&unsolved0.kind, &unsolved1.kind) {
2086 (
2087 UnsolvedSegmentKind::Point { position: pos0, .. },
2088 UnsolvedSegmentKind::Point { position: pos1, .. },
2089 ) => {
2090 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2093 (
2094 UnsolvedExpr::Unknown(p0_x),
2095 UnsolvedExpr::Unknown(p0_y),
2096 UnsolvedExpr::Unknown(p1_x),
2097 UnsolvedExpr::Unknown(p1_y),
2098 ) => {
2099 let sketch_constraint = SketchConstraint {
2101 kind: SketchConstraintKind::VerticalDistance {
2102 points: [
2103 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2104 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2105 object_id: unsolved0.object_id,
2106 }),
2107 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2108 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2109 object_id: unsolved1.object_id,
2110 }),
2111 ],
2112 },
2113 meta: vec![args.source_range.into()],
2114 };
2115 Ok(KclValue::SketchConstraint {
2116 value: Box::new(sketch_constraint),
2117 })
2118 }
2119 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2120 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
2121 .to_owned(),
2122 vec![args.source_range],
2123 ))),
2124 }
2125 }
2126 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2127 "verticalDistance() arguments must be unsolved points".to_owned(),
2128 vec![args.source_range],
2129 ))),
2130 }
2131 }
2132 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2133 if !point2d_is_origin(point2d) {
2134 return Err(KclError::new_semantic(KclErrorDetails::new(
2135 "verticalDistance() Point2d arguments must be ORIGIN".to_owned(),
2136 vec![args.source_range],
2137 )));
2138 }
2139
2140 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2141 return Err(KclError::new_semantic(KclErrorDetails::new(
2142 "segment must be an unsolved segment".to_owned(),
2143 vec![args.source_range],
2144 )));
2145 };
2146 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2147 return Err(KclError::new_semantic(KclErrorDetails::new(
2148 "verticalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
2149 vec![args.source_range],
2150 )));
2151 };
2152 match (&position[0], &position[1]) {
2153 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2154 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2155 vars: crate::front::Point2d {
2156 x: *point_x,
2157 y: *point_y,
2158 },
2159 object_id: unsolved.object_id,
2160 });
2161 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
2162 [point, ConstrainablePoint2dOrOrigin::Origin]
2163 } else {
2164 [ConstrainablePoint2dOrOrigin::Origin, point]
2165 };
2166 Ok(KclValue::SketchConstraint {
2167 value: Box::new(SketchConstraint {
2168 kind: SketchConstraintKind::VerticalDistance { points },
2169 meta: vec![args.source_range.into()],
2170 }),
2171 })
2172 }
2173 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2174 "unimplemented: verticalDistance() point arguments must be sketch vars in all coordinates"
2175 .to_owned(),
2176 vec![args.source_range],
2177 ))),
2178 }
2179 }
2180 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2181 "verticalDistance() arguments must be point segments or ORIGIN".to_owned(),
2182 vec![args.source_range],
2183 ))),
2184 }
2185}
2186
2187pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2188 #[derive(Clone, Copy)]
2189 struct ConstrainableLine {
2190 solver_line: DatumLineSegment,
2191 #[cfg(feature = "artifact-graph")]
2192 object_id: ObjectId,
2193 }
2194
2195 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2196 "lines",
2197 &RuntimeType::Array(
2198 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
2199 ArrayLen::Minimum(2),
2200 ),
2201 exec_state,
2202 )?;
2203 let range = args.source_range;
2204 let constrainable_lines: Vec<ConstrainableLine> = lines
2205 .iter()
2206 .map(|line| {
2207 let KclValue::Segment { value: segment } = line else {
2208 return Err(KclError::new_semantic(KclErrorDetails::new(
2209 "line argument must be a Segment".to_owned(),
2210 vec![args.source_range],
2211 )));
2212 };
2213 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2214 return Err(KclError::new_internal(KclErrorDetails::new(
2215 "line must be an unsolved Segment".to_owned(),
2216 vec![args.source_range],
2217 )));
2218 };
2219 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
2220 return Err(KclError::new_semantic(KclErrorDetails::new(
2221 "line argument must be a line, no other type of Segment".to_owned(),
2222 vec![args.source_range],
2223 )));
2224 };
2225 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
2226 return Err(KclError::new_semantic(KclErrorDetails::new(
2227 "line's start x coordinate must be a var".to_owned(),
2228 vec![args.source_range],
2229 )));
2230 };
2231 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
2232 return Err(KclError::new_semantic(KclErrorDetails::new(
2233 "line's start y coordinate must be a var".to_owned(),
2234 vec![args.source_range],
2235 )));
2236 };
2237 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
2238 return Err(KclError::new_semantic(KclErrorDetails::new(
2239 "line's end x coordinate must be a var".to_owned(),
2240 vec![args.source_range],
2241 )));
2242 };
2243 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
2244 return Err(KclError::new_semantic(KclErrorDetails::new(
2245 "line's end y coordinate must be a var".to_owned(),
2246 vec![args.source_range],
2247 )));
2248 };
2249
2250 let solver_line_p0 =
2251 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
2252 let solver_line_p1 =
2253 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
2254
2255 Ok(ConstrainableLine {
2256 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
2257 #[cfg(feature = "artifact-graph")]
2258 object_id: unsolved.object_id,
2259 })
2260 })
2261 .collect::<Result<_, _>>()?;
2262
2263 #[cfg(feature = "artifact-graph")]
2264 let constraint_id = exec_state.next_object_id();
2265 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2267 return Err(KclError::new_semantic(KclErrorDetails::new(
2268 "equalLength() can only be used inside a sketch block".to_owned(),
2269 vec![args.source_range],
2270 )));
2271 };
2272 let first_line = constrainable_lines[0];
2273 for line in constrainable_lines.iter().skip(1) {
2274 sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
2275 first_line.solver_line,
2276 line.solver_line,
2277 ));
2278 }
2279 #[cfg(feature = "artifact-graph")]
2280 {
2281 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
2282 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
2283 });
2284 sketch_state.sketch_constraints.push(constraint_id);
2285 track_constraint(constraint_id, constraint, exec_state, &args);
2286 }
2287 Ok(KclValue::none())
2288}
2289
2290pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2291 #[derive(Debug, Clone, Copy)]
2292 struct ConstrainableLineVars {
2293 start: [SketchVarId; 2],
2294 end: [SketchVarId; 2],
2295 }
2296
2297 #[derive(Debug, Clone, Copy)]
2298 struct ConstrainableCircularVars {
2299 center: [SketchVarId; 2],
2300 start: [SketchVarId; 2],
2301 end: Option<[SketchVarId; 2]>,
2302 }
2303
2304 #[derive(Debug, Clone, Copy)]
2305 enum TangentInput {
2306 Line(ConstrainableLineVars),
2307 Circular(ConstrainableCircularVars),
2308 }
2309
2310 fn extract_tangent_input(
2311 segment_value: &KclValue,
2312 range: crate::SourceRange,
2313 ) -> Result<(TangentInput, ObjectId), KclError> {
2314 let KclValue::Segment { value: segment } = segment_value else {
2315 return Err(KclError::new_semantic(KclErrorDetails::new(
2316 "tangent() arguments must be segments".to_owned(),
2317 vec![range],
2318 )));
2319 };
2320 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2321 return Err(KclError::new_semantic(KclErrorDetails::new(
2322 "tangent() arguments must be unsolved segments".to_owned(),
2323 vec![range],
2324 )));
2325 };
2326 match &unsolved.kind {
2327 UnsolvedSegmentKind::Line { start, end, .. } => {
2328 let (
2329 UnsolvedExpr::Unknown(start_x),
2330 UnsolvedExpr::Unknown(start_y),
2331 UnsolvedExpr::Unknown(end_x),
2332 UnsolvedExpr::Unknown(end_y),
2333 ) = (&start[0], &start[1], &end[0], &end[1])
2334 else {
2335 return Err(KclError::new_semantic(KclErrorDetails::new(
2336 "line coordinates must be sketch vars for tangent()".to_owned(),
2337 vec![range],
2338 )));
2339 };
2340 Ok((
2341 TangentInput::Line(ConstrainableLineVars {
2342 start: [*start_x, *start_y],
2343 end: [*end_x, *end_y],
2344 }),
2345 unsolved.object_id,
2346 ))
2347 }
2348 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
2349 let (
2350 UnsolvedExpr::Unknown(center_x),
2351 UnsolvedExpr::Unknown(center_y),
2352 UnsolvedExpr::Unknown(start_x),
2353 UnsolvedExpr::Unknown(start_y),
2354 UnsolvedExpr::Unknown(end_x),
2355 UnsolvedExpr::Unknown(end_y),
2356 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
2357 else {
2358 return Err(KclError::new_semantic(KclErrorDetails::new(
2359 "arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
2360 vec![range],
2361 )));
2362 };
2363 Ok((
2364 TangentInput::Circular(ConstrainableCircularVars {
2365 center: [*center_x, *center_y],
2366 start: [*start_x, *start_y],
2367 end: Some([*end_x, *end_y]),
2368 }),
2369 unsolved.object_id,
2370 ))
2371 }
2372 UnsolvedSegmentKind::Circle { center, start, .. } => {
2373 let (
2374 UnsolvedExpr::Unknown(center_x),
2375 UnsolvedExpr::Unknown(center_y),
2376 UnsolvedExpr::Unknown(start_x),
2377 UnsolvedExpr::Unknown(start_y),
2378 ) = (¢er[0], ¢er[1], &start[0], &start[1])
2379 else {
2380 return Err(KclError::new_semantic(KclErrorDetails::new(
2381 "circle center/start coordinates must be sketch vars for tangent()".to_owned(),
2382 vec![range],
2383 )));
2384 };
2385 Ok((
2386 TangentInput::Circular(ConstrainableCircularVars {
2387 center: [*center_x, *center_y],
2388 start: [*start_x, *start_y],
2389 end: None,
2390 }),
2391 unsolved.object_id,
2392 ))
2393 }
2394 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2395 "tangent() supports only line, arc, and circle segments".to_owned(),
2396 vec![range],
2397 ))),
2398 }
2399 }
2400
2401 fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
2402 Ok(DatumPoint::new_xy(
2403 coords[0].to_constraint_id(range)?,
2404 coords[1].to_constraint_id(range)?,
2405 ))
2406 }
2407
2408 fn sketch_var_initial_value(
2409 sketch_vars: &[KclValue],
2410 id: SketchVarId,
2411 exec_state: &mut ExecState,
2412 range: crate::SourceRange,
2413 ) -> Result<f64, KclError> {
2414 sketch_vars
2415 .get(id.0)
2416 .and_then(KclValue::as_sketch_var)
2417 .map(|sketch_var| {
2418 sketch_var
2419 .initial_value_to_solver_units(exec_state, range, "tangent() hidden radius initial value")
2420 .map(|value| value.n)
2421 })
2422 .transpose()?
2423 .ok_or_else(|| {
2424 KclError::new_internal(KclErrorDetails::new(
2425 format!("Missing sketch variable initial value for id {}", id.0),
2426 vec![range],
2427 ))
2428 })
2429 }
2430
2431 fn radius_guess(
2432 sketch_vars: &[KclValue],
2433 center: [SketchVarId; 2],
2434 point: [SketchVarId; 2],
2435 exec_state: &mut ExecState,
2436 range: crate::SourceRange,
2437 ) -> Result<f64, KclError> {
2438 let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
2439 - sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
2440 let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
2441 - sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
2442 Ok(dx.hypot(dy))
2443 }
2444
2445 fn point_initial_position(
2446 sketch_vars: &[KclValue],
2447 point: [SketchVarId; 2],
2448 exec_state: &mut ExecState,
2449 range: crate::SourceRange,
2450 ) -> Result<[f64; 2], KclError> {
2451 Ok([
2452 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
2453 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
2454 ])
2455 }
2456
2457 fn canonicalize_line_for_tangent(
2458 sketch_vars: &[KclValue],
2459 line: ConstrainableLineVars,
2460 arc_center: [SketchVarId; 2],
2461 exec_state: &mut ExecState,
2462 range: crate::SourceRange,
2463 ) -> Result<ConstrainableLineVars, KclError> {
2464 let [sx, sy] = point_initial_position(sketch_vars, line.start, exec_state, range)?;
2465 let [ex, ey] = point_initial_position(sketch_vars, line.end, exec_state, range)?;
2466 let [cx, cy] = point_initial_position(sketch_vars, arc_center, exec_state, range)?;
2467
2468 let signed_side = (ex - sx) * (cy - sy) - (ey - sy) * (cx - sx);
2472 if signed_side < -1e-9 {
2473 Ok(ConstrainableLineVars {
2474 start: line.end,
2475 end: line.start,
2476 })
2477 } else {
2478 Ok(line)
2479 }
2480 }
2481
2482 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
2483 "input",
2484 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2485 exec_state,
2486 )?;
2487 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
2488 KclError::new_semantic(KclErrorDetails::new(
2489 "tangent() requires exactly 2 input segments".to_owned(),
2490 vec![args.source_range],
2491 ))
2492 })?;
2493 let range = args.source_range;
2494 let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
2495 let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
2496 #[cfg(not(feature = "artifact-graph"))]
2497 let _ = (input0_object_id, input1_object_id);
2498
2499 enum TangentCase {
2500 LineCircular(ConstrainableLineVars, ConstrainableCircularVars),
2501 CircularCircular(ConstrainableCircularVars, ConstrainableCircularVars),
2502 }
2503 let tangent_case = match (input0, input1) {
2504 (TangentInput::Line(line), TangentInput::Circular(circular))
2505 | (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
2506 (TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
2507 TangentCase::CircularCircular(circular0, circular1)
2508 }
2509 (TangentInput::Line(_), TangentInput::Line(_)) => {
2510 return Err(KclError::new_semantic(KclErrorDetails::new(
2511 "tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
2512 vec![range],
2513 )));
2514 }
2515 };
2516
2517 let sketch_var_ty = solver_numeric_type(exec_state);
2518 #[cfg(feature = "artifact-graph")]
2519 let constraint_id = exec_state.next_object_id();
2520
2521 let sketch_vars = {
2522 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2523 return Err(KclError::new_semantic(KclErrorDetails::new(
2524 "tangent() can only be used inside a sketch block".to_owned(),
2525 vec![range],
2526 )));
2527 };
2528 sketch_state.sketch_vars.clone()
2529 };
2530
2531 match tangent_case {
2533 TangentCase::LineCircular(line, circular) => {
2534 let canonical_line = canonicalize_line_for_tangent(&sketch_vars, line, circular.center, exec_state, range)?;
2535 let line_p0 = datum_point(canonical_line.start, range)?;
2536 let line_p1 = datum_point(canonical_line.end, range)?;
2537 let line_datum = DatumLineSegment::new(line_p0, line_p1);
2538
2539 let center = datum_point(circular.center, range)?;
2540 let circular_start = datum_point(circular.start, range)?;
2541 let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
2542 let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
2543 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2544 return Err(KclError::new_semantic(KclErrorDetails::new(
2545 "tangent() can only be used inside a sketch block".to_owned(),
2546 vec![range],
2547 )));
2548 };
2549 let radius_id = sketch_state.next_sketch_var_id();
2550 sketch_state.sketch_vars.push(KclValue::SketchVar {
2551 value: Box::new(crate::execution::SketchVar {
2552 id: radius_id,
2553 initial_value: radius_initial_value,
2554 ty: sketch_var_ty,
2555 meta: vec![],
2556 }),
2557 });
2558 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
2559 let circle = DatumCircle { center, radius };
2560
2561 sketch_state
2568 .solver_constraints
2569 .push(SolverConstraint::DistanceVar(circular_start, center, radius));
2570 if let Some(circular_end) = circular_end {
2571 sketch_state
2572 .solver_constraints
2573 .push(SolverConstraint::DistanceVar(circular_end, center, radius));
2574 }
2575 sketch_state
2576 .solver_constraints
2577 .push(SolverConstraint::LineTangentToCircle(line_datum, circle));
2578 }
2579 TangentCase::CircularCircular(circular0, circular1) => {
2580 let center0 = datum_point(circular0.center, range)?;
2581 let start0 = datum_point(circular0.start, range)?;
2582 let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
2583 let radius0_initial_value =
2584 radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
2585 let center1 = datum_point(circular1.center, range)?;
2586 let start1 = datum_point(circular1.start, range)?;
2587 let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
2588 let radius1_initial_value =
2589 radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
2590 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2591 return Err(KclError::new_semantic(KclErrorDetails::new(
2592 "tangent() can only be used inside a sketch block".to_owned(),
2593 vec![range],
2594 )));
2595 };
2596 let radius0_id = sketch_state.next_sketch_var_id();
2597 sketch_state.sketch_vars.push(KclValue::SketchVar {
2598 value: Box::new(crate::execution::SketchVar {
2599 id: radius0_id,
2600 initial_value: radius0_initial_value,
2601 ty: sketch_var_ty,
2602 meta: vec![],
2603 }),
2604 });
2605 let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
2606 let circle0 = DatumCircle {
2607 center: center0,
2608 radius: radius0,
2609 };
2610
2611 let radius1_id = sketch_state.next_sketch_var_id();
2612 sketch_state.sketch_vars.push(KclValue::SketchVar {
2613 value: Box::new(crate::execution::SketchVar {
2614 id: radius1_id,
2615 initial_value: radius1_initial_value,
2616 ty: sketch_var_ty,
2617 meta: vec![],
2618 }),
2619 });
2620 let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
2621 let circle1 = DatumCircle {
2622 center: center1,
2623 radius: radius1,
2624 };
2625
2626 sketch_state
2631 .solver_constraints
2632 .push(SolverConstraint::DistanceVar(start0, center0, radius0));
2633 if let Some(end0) = end0 {
2634 sketch_state
2635 .solver_constraints
2636 .push(SolverConstraint::DistanceVar(end0, center0, radius0));
2637 }
2638 sketch_state
2639 .solver_constraints
2640 .push(SolverConstraint::DistanceVar(start1, center1, radius1));
2641 if let Some(end1) = end1 {
2642 sketch_state
2643 .solver_constraints
2644 .push(SolverConstraint::DistanceVar(end1, center1, radius1));
2645 }
2646 sketch_state
2647 .solver_constraints
2648 .push(SolverConstraint::CircleTangentToCircle(circle0, circle1));
2649 }
2650 }
2651
2652 #[cfg(feature = "artifact-graph")]
2653 {
2654 let constraint = crate::front::Constraint::Tangent(Tangent {
2655 input: vec![input0_object_id, input1_object_id],
2656 });
2657 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2658 return Err(KclError::new_semantic(KclErrorDetails::new(
2659 "tangent() can only be used inside a sketch block".to_owned(),
2660 vec![range],
2661 )));
2662 };
2663 sketch_state.sketch_constraints.push(constraint_id);
2664 track_constraint(constraint_id, constraint, exec_state, &args);
2665 }
2666
2667 Ok(KclValue::none())
2668}
2669
2670#[derive(Debug, Clone, Copy)]
2671pub(crate) enum LinesAtAngleKind {
2672 Parallel,
2673 Perpendicular,
2674}
2675
2676impl LinesAtAngleKind {
2677 pub fn to_function_name(self) -> &'static str {
2678 match self {
2679 LinesAtAngleKind::Parallel => "parallel",
2680 LinesAtAngleKind::Perpendicular => "perpendicular",
2681 }
2682 }
2683
2684 fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
2685 match self {
2686 LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
2687 LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
2688 }
2689 }
2690
2691 #[cfg(feature = "artifact-graph")]
2692 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
2693 match self {
2694 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
2695 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
2696 }
2697 }
2698}
2699
2700#[expect(unused)]
2702fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
2703 kcmc::shared::Angle::from_degrees(angle.to_degrees())
2704}
2705
2706#[expect(unused)]
2708fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
2709 ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
2710}
2711
2712pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2713 lines_at_angle(LinesAtAngleKind::Parallel, exec_state, args).await
2714}
2715
2716pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2717 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
2718}
2719
2720pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2721 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2722 "lines",
2723 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2724 exec_state,
2725 )?;
2726 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
2727 KclError::new_semantic(KclErrorDetails::new(
2728 "must have two input lines".to_owned(),
2729 vec![args.source_range],
2730 ))
2731 })?;
2732 let KclValue::Segment { value: segment0 } = &line0 else {
2733 return Err(KclError::new_semantic(KclErrorDetails::new(
2734 "line argument must be a Segment".to_owned(),
2735 vec![args.source_range],
2736 )));
2737 };
2738 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
2739 return Err(KclError::new_internal(KclErrorDetails::new(
2740 "line must be an unsolved Segment".to_owned(),
2741 vec![args.source_range],
2742 )));
2743 };
2744 let UnsolvedSegmentKind::Line {
2745 start: start0,
2746 end: end0,
2747 ..
2748 } = &unsolved0.kind
2749 else {
2750 return Err(KclError::new_semantic(KclErrorDetails::new(
2751 "line argument must be a line, no other type of Segment".to_owned(),
2752 vec![args.source_range],
2753 )));
2754 };
2755 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
2756 return Err(KclError::new_semantic(KclErrorDetails::new(
2757 "line's start x coordinate must be a var".to_owned(),
2758 vec![args.source_range],
2759 )));
2760 };
2761 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
2762 return Err(KclError::new_semantic(KclErrorDetails::new(
2763 "line's start y coordinate must be a var".to_owned(),
2764 vec![args.source_range],
2765 )));
2766 };
2767 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
2768 return Err(KclError::new_semantic(KclErrorDetails::new(
2769 "line's end x coordinate must be a var".to_owned(),
2770 vec![args.source_range],
2771 )));
2772 };
2773 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
2774 return Err(KclError::new_semantic(KclErrorDetails::new(
2775 "line's end y coordinate must be a var".to_owned(),
2776 vec![args.source_range],
2777 )));
2778 };
2779 let KclValue::Segment { value: segment1 } = &line1 else {
2780 return Err(KclError::new_semantic(KclErrorDetails::new(
2781 "line argument must be a Segment".to_owned(),
2782 vec![args.source_range],
2783 )));
2784 };
2785 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
2786 return Err(KclError::new_internal(KclErrorDetails::new(
2787 "line must be an unsolved Segment".to_owned(),
2788 vec![args.source_range],
2789 )));
2790 };
2791 let UnsolvedSegmentKind::Line {
2792 start: start1,
2793 end: end1,
2794 ..
2795 } = &unsolved1.kind
2796 else {
2797 return Err(KclError::new_semantic(KclErrorDetails::new(
2798 "line argument must be a line, no other type of Segment".to_owned(),
2799 vec![args.source_range],
2800 )));
2801 };
2802 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
2803 return Err(KclError::new_semantic(KclErrorDetails::new(
2804 "line's start x coordinate must be a var".to_owned(),
2805 vec![args.source_range],
2806 )));
2807 };
2808 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
2809 return Err(KclError::new_semantic(KclErrorDetails::new(
2810 "line's start y coordinate must be a var".to_owned(),
2811 vec![args.source_range],
2812 )));
2813 };
2814 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
2815 return Err(KclError::new_semantic(KclErrorDetails::new(
2816 "line's end x coordinate must be a var".to_owned(),
2817 vec![args.source_range],
2818 )));
2819 };
2820 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
2821 return Err(KclError::new_semantic(KclErrorDetails::new(
2822 "line's end y coordinate must be a var".to_owned(),
2823 vec![args.source_range],
2824 )));
2825 };
2826
2827 let sketch_constraint = SketchConstraint {
2829 kind: SketchConstraintKind::Angle {
2830 line0: crate::execution::ConstrainableLine2d {
2831 object_id: unsolved0.object_id,
2832 vars: [
2833 crate::front::Point2d {
2834 x: *line0_p0_x,
2835 y: *line0_p0_y,
2836 },
2837 crate::front::Point2d {
2838 x: *line0_p1_x,
2839 y: *line0_p1_y,
2840 },
2841 ],
2842 },
2843 line1: crate::execution::ConstrainableLine2d {
2844 object_id: unsolved1.object_id,
2845 vars: [
2846 crate::front::Point2d {
2847 x: *line1_p0_x,
2848 y: *line1_p0_y,
2849 },
2850 crate::front::Point2d {
2851 x: *line1_p1_x,
2852 y: *line1_p1_y,
2853 },
2854 ],
2855 },
2856 },
2857 meta: vec![args.source_range.into()],
2858 };
2859 Ok(KclValue::SketchConstraint {
2860 value: Box::new(sketch_constraint),
2861 })
2862}
2863
2864async fn lines_at_angle(
2865 angle_kind: LinesAtAngleKind,
2866 exec_state: &mut ExecState,
2867 args: Args,
2868) -> Result<KclValue, KclError> {
2869 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2870 "lines",
2871 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2872 exec_state,
2873 )?;
2874 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
2875 KclError::new_semantic(KclErrorDetails::new(
2876 "must have two input lines".to_owned(),
2877 vec![args.source_range],
2878 ))
2879 })?;
2880
2881 let KclValue::Segment { value: segment0 } = &line0 else {
2882 return Err(KclError::new_semantic(KclErrorDetails::new(
2883 "line argument must be a Segment".to_owned(),
2884 vec![args.source_range],
2885 )));
2886 };
2887 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
2888 return Err(KclError::new_internal(KclErrorDetails::new(
2889 "line must be an unsolved Segment".to_owned(),
2890 vec![args.source_range],
2891 )));
2892 };
2893 let UnsolvedSegmentKind::Line {
2894 start: start0,
2895 end: end0,
2896 ..
2897 } = &unsolved0.kind
2898 else {
2899 return Err(KclError::new_semantic(KclErrorDetails::new(
2900 "line argument must be a line, no other type of Segment".to_owned(),
2901 vec![args.source_range],
2902 )));
2903 };
2904 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
2905 return Err(KclError::new_semantic(KclErrorDetails::new(
2906 "line's start x coordinate must be a var".to_owned(),
2907 vec![args.source_range],
2908 )));
2909 };
2910 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
2911 return Err(KclError::new_semantic(KclErrorDetails::new(
2912 "line's start y coordinate must be a var".to_owned(),
2913 vec![args.source_range],
2914 )));
2915 };
2916 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
2917 return Err(KclError::new_semantic(KclErrorDetails::new(
2918 "line's end x coordinate must be a var".to_owned(),
2919 vec![args.source_range],
2920 )));
2921 };
2922 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
2923 return Err(KclError::new_semantic(KclErrorDetails::new(
2924 "line's end y coordinate must be a var".to_owned(),
2925 vec![args.source_range],
2926 )));
2927 };
2928 let KclValue::Segment { value: segment1 } = &line1 else {
2929 return Err(KclError::new_semantic(KclErrorDetails::new(
2930 "line argument must be a Segment".to_owned(),
2931 vec![args.source_range],
2932 )));
2933 };
2934 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
2935 return Err(KclError::new_internal(KclErrorDetails::new(
2936 "line must be an unsolved Segment".to_owned(),
2937 vec![args.source_range],
2938 )));
2939 };
2940 let UnsolvedSegmentKind::Line {
2941 start: start1,
2942 end: end1,
2943 ..
2944 } = &unsolved1.kind
2945 else {
2946 return Err(KclError::new_semantic(KclErrorDetails::new(
2947 "line argument must be a line, no other type of Segment".to_owned(),
2948 vec![args.source_range],
2949 )));
2950 };
2951 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
2952 return Err(KclError::new_semantic(KclErrorDetails::new(
2953 "line's start x coordinate must be a var".to_owned(),
2954 vec![args.source_range],
2955 )));
2956 };
2957 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
2958 return Err(KclError::new_semantic(KclErrorDetails::new(
2959 "line's start y coordinate must be a var".to_owned(),
2960 vec![args.source_range],
2961 )));
2962 };
2963 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
2964 return Err(KclError::new_semantic(KclErrorDetails::new(
2965 "line's end x coordinate must be a var".to_owned(),
2966 vec![args.source_range],
2967 )));
2968 };
2969 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
2970 return Err(KclError::new_semantic(KclErrorDetails::new(
2971 "line's end y coordinate must be a var".to_owned(),
2972 vec![args.source_range],
2973 )));
2974 };
2975
2976 let range = args.source_range;
2977 let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2978 line0_p0_x.to_constraint_id(range)?,
2979 line0_p0_y.to_constraint_id(range)?,
2980 );
2981 let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2982 line0_p1_x.to_constraint_id(range)?,
2983 line0_p1_y.to_constraint_id(range)?,
2984 );
2985 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
2986 let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2987 line1_p0_x.to_constraint_id(range)?,
2988 line1_p0_y.to_constraint_id(range)?,
2989 );
2990 let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
2991 line1_p1_x.to_constraint_id(range)?,
2992 line1_p1_y.to_constraint_id(range)?,
2993 );
2994 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
2995 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
2996 #[cfg(feature = "artifact-graph")]
2997 let constraint_id = exec_state.next_object_id();
2998 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3000 return Err(KclError::new_semantic(KclErrorDetails::new(
3001 format!(
3002 "{}() can only be used inside a sketch block",
3003 angle_kind.to_function_name()
3004 ),
3005 vec![args.source_range],
3006 )));
3007 };
3008 sketch_state.solver_constraints.push(constraint);
3009 #[cfg(feature = "artifact-graph")]
3010 {
3011 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
3012 sketch_state.sketch_constraints.push(constraint_id);
3013 track_constraint(constraint_id, constraint, exec_state, &args);
3014 }
3015 Ok(KclValue::none())
3016}
3017
3018pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3019 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
3020 let KclValue::Segment { value: segment } = line else {
3021 return Err(KclError::new_semantic(KclErrorDetails::new(
3022 "line argument must be a Segment".to_owned(),
3023 vec![args.source_range],
3024 )));
3025 };
3026 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3027 return Err(KclError::new_internal(KclErrorDetails::new(
3028 "line must be an unsolved Segment".to_owned(),
3029 vec![args.source_range],
3030 )));
3031 };
3032 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
3033 return Err(KclError::new_semantic(KclErrorDetails::new(
3034 "line argument must be a line, no other type of Segment".to_owned(),
3035 vec![args.source_range],
3036 )));
3037 };
3038 let p0_x = &start[0];
3039 let p0_y = &start[1];
3040 let p1_x = &end[0];
3041 let p1_y = &end[1];
3042 match (p0_x, p0_y, p1_x, p1_y) {
3043 (
3044 UnsolvedExpr::Unknown(p0_x),
3045 UnsolvedExpr::Unknown(p0_y),
3046 UnsolvedExpr::Unknown(p1_x),
3047 UnsolvedExpr::Unknown(p1_y),
3048 ) => {
3049 let range = args.source_range;
3050 let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3051 p0_x.to_constraint_id(range)?,
3052 p0_y.to_constraint_id(range)?,
3053 );
3054 let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3055 p1_x.to_constraint_id(range)?,
3056 p1_y.to_constraint_id(range)?,
3057 );
3058 let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
3059 let constraint = ezpz::Constraint::Horizontal(solver_line);
3060 #[cfg(feature = "artifact-graph")]
3061 let constraint_id = exec_state.next_object_id();
3062 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3064 return Err(KclError::new_semantic(KclErrorDetails::new(
3065 "horizontal() can only be used inside a sketch block".to_owned(),
3066 vec![args.source_range],
3067 )));
3068 };
3069 sketch_state.solver_constraints.push(constraint);
3070 #[cfg(feature = "artifact-graph")]
3071 {
3072 let constraint = crate::front::Constraint::Horizontal(Horizontal {
3073 line: unsolved.object_id,
3074 });
3075 sketch_state.sketch_constraints.push(constraint_id);
3076 track_constraint(constraint_id, constraint, exec_state, &args);
3077 }
3078 Ok(KclValue::none())
3079 }
3080 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3081 "line's x and y coordinates of both start and end must be vars".to_owned(),
3082 vec![args.source_range],
3083 ))),
3084 }
3085}
3086
3087pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3088 let line: KclValue = args.get_unlabeled_kw_arg("line", &RuntimeType::segment(), exec_state)?;
3089 let KclValue::Segment { value: segment } = line else {
3090 return Err(KclError::new_semantic(KclErrorDetails::new(
3091 "line argument must be a Segment".to_owned(),
3092 vec![args.source_range],
3093 )));
3094 };
3095 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3096 return Err(KclError::new_internal(KclErrorDetails::new(
3097 "line must be an unsolved Segment".to_owned(),
3098 vec![args.source_range],
3099 )));
3100 };
3101 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
3102 return Err(KclError::new_semantic(KclErrorDetails::new(
3103 "line argument must be a line, no other type of Segment".to_owned(),
3104 vec![args.source_range],
3105 )));
3106 };
3107 let p0_x = &start[0];
3108 let p0_y = &start[1];
3109 let p1_x = &end[0];
3110 let p1_y = &end[1];
3111 match (p0_x, p0_y, p1_x, p1_y) {
3112 (
3113 UnsolvedExpr::Unknown(p0_x),
3114 UnsolvedExpr::Unknown(p0_y),
3115 UnsolvedExpr::Unknown(p1_x),
3116 UnsolvedExpr::Unknown(p1_y),
3117 ) => {
3118 let range = args.source_range;
3119 let solver_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3120 p0_x.to_constraint_id(range)?,
3121 p0_y.to_constraint_id(range)?,
3122 );
3123 let solver_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3124 p1_x.to_constraint_id(range)?,
3125 p1_y.to_constraint_id(range)?,
3126 );
3127 let solver_line = ezpz::datatypes::inputs::DatumLineSegment::new(solver_p0, solver_p1);
3128 let constraint = ezpz::Constraint::Vertical(solver_line);
3129 #[cfg(feature = "artifact-graph")]
3130 let constraint_id = exec_state.next_object_id();
3131 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3133 return Err(KclError::new_semantic(KclErrorDetails::new(
3134 "vertical() can only be used inside a sketch block".to_owned(),
3135 vec![args.source_range],
3136 )));
3137 };
3138 sketch_state.solver_constraints.push(constraint);
3139 #[cfg(feature = "artifact-graph")]
3140 {
3141 let constraint = crate::front::Constraint::Vertical(Vertical {
3142 line: unsolved.object_id,
3143 });
3144 sketch_state.sketch_constraints.push(constraint_id);
3145 track_constraint(constraint_id, constraint, exec_state, &args);
3146 }
3147 Ok(KclValue::none())
3148 }
3149 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3150 "line's x and y coordinates of both start and end must be vars".to_owned(),
3151 vec![args.source_range],
3152 ))),
3153 }
3154}