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::EqualRadius;
46#[cfg(feature = "artifact-graph")]
47use crate::front::Horizontal;
48use crate::front::LineCtor;
49#[cfg(feature = "artifact-graph")]
50use crate::front::LinesEqualLength;
51#[cfg(feature = "artifact-graph")]
52use crate::front::Object;
53use crate::front::ObjectId;
54#[cfg(feature = "artifact-graph")]
55use crate::front::ObjectKind;
56#[cfg(feature = "artifact-graph")]
57use crate::front::Parallel;
58#[cfg(feature = "artifact-graph")]
59use crate::front::Perpendicular;
60use crate::front::Point2d;
61use crate::front::PointCtor;
62#[cfg(feature = "artifact-graph")]
63use crate::front::SourceRef;
64#[cfg(feature = "artifact-graph")]
65use crate::front::Tangent;
66#[cfg(feature = "artifact-graph")]
67use crate::front::Vertical;
68#[cfg(feature = "artifact-graph")]
69use crate::frontend::sketch::ConstraintSegment;
70use crate::std::Args;
71use crate::std::args::FromKclValue;
72use crate::std::args::TyF64;
73
74fn point2d_is_origin(point2d: &KclValue) -> bool {
75 let Some([x, y]) = <[TyF64; 2]>::from_kcl_val(point2d) else {
76 return false;
77 };
78 if x.ty.as_length().is_none() || y.ty.as_length().is_none() {
81 return false;
82 }
83 x.n == 0.0 && y.n == 0.0
86}
87
88fn extract_arc_component(
95 value: &KclValue,
96 exec_state: &mut ExecState,
97 range: crate::SourceRange,
98 description: &str,
99) -> Result<(SketchVarId, Option<SolverConstraint>), KclError> {
100 match value.as_unsolved_expr() {
101 None => Err(KclError::new_semantic(KclErrorDetails::new(
102 format!("{description} must be a number or sketch var"),
103 vec![range],
104 ))),
105 Some(UnsolvedExpr::Unknown(var_id)) => Ok((var_id, None)),
106 Some(UnsolvedExpr::Known(_)) => {
107 let value_in_solver_units = normalize_to_solver_distance_unit(value, range, exec_state, description)?;
108 let Some(normalized_value) = value_in_solver_units.as_ty_f64() else {
109 return Err(KclError::new_internal(KclErrorDetails::new(
110 "Expected number after coercion".to_owned(),
111 vec![range],
112 )));
113 };
114
115 let Some(sketch_state) = exec_state.sketch_block_mut() else {
116 return Err(KclError::new_semantic(KclErrorDetails::new(
117 "arc() can only be used inside a sketch block".to_owned(),
118 vec![range],
119 )));
120 };
121 let var_id = sketch_state.next_sketch_var_id();
122 sketch_state.sketch_vars.push(KclValue::SketchVar {
123 value: Box::new(crate::execution::SketchVar {
124 id: var_id,
125 initial_value: normalized_value.n,
126 ty: normalized_value.ty,
127 meta: vec![],
128 }),
129 });
130
131 Ok((
132 var_id,
133 Some(SolverConstraint::Fixed(
134 var_id.to_constraint_id(range)?,
135 normalized_value.n,
136 )),
137 ))
138 }
139 }
140}
141
142#[cfg(feature = "artifact-graph")]
143fn coincident_segments_for_segment_and_point2d(
144 segment_id: ObjectId,
145 point2d: &KclValue,
146 segment_first: bool,
147) -> Vec<ConstraintSegment> {
148 if !point2d_is_origin(point2d) {
149 return vec![segment_id.into()];
150 }
151
152 if segment_first {
153 vec![segment_id.into(), ConstraintSegment::ORIGIN]
154 } else {
155 vec![ConstraintSegment::ORIGIN, segment_id.into()]
156 }
157}
158
159pub async fn point(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
160 let at: Vec<KclValue> = args.get_kw_arg("at", &RuntimeType::point2d(), exec_state)?;
161 let [at_x_value, at_y_value]: [KclValue; 2] = at.try_into().map_err(|_| {
162 KclError::new_semantic(KclErrorDetails::new(
163 "at must be a 2D point".to_owned(),
164 vec![args.source_range],
165 ))
166 })?;
167 let Some(at_x) = at_x_value.as_unsolved_expr() else {
168 return Err(KclError::new_semantic(KclErrorDetails::new(
169 "at x must be a number or sketch var".to_owned(),
170 vec![args.source_range],
171 )));
172 };
173 let Some(at_y) = at_y_value.as_unsolved_expr() else {
174 return Err(KclError::new_semantic(KclErrorDetails::new(
175 "at y must be a number or sketch var".to_owned(),
176 vec![args.source_range],
177 )));
178 };
179 let ctor = PointCtor {
180 position: Point2d {
181 x: at_x_value.to_sketch_expr().ok_or_else(|| {
182 KclError::new_semantic(KclErrorDetails::new(
183 "unable to convert numeric type to suffix".to_owned(),
184 vec![args.source_range],
185 ))
186 })?,
187 y: at_y_value.to_sketch_expr().ok_or_else(|| {
188 KclError::new_semantic(KclErrorDetails::new(
189 "unable to convert numeric type to suffix".to_owned(),
190 vec![args.source_range],
191 ))
192 })?,
193 },
194 };
195 let segment = UnsolvedSegment {
196 id: exec_state.next_uuid(),
197 object_id: exec_state.next_object_id(),
198 kind: UnsolvedSegmentKind::Point {
199 position: [at_x, at_y],
200 ctor: Box::new(ctor),
201 },
202 tag: None,
203 node_path: args.node_path.clone(),
204 meta: vec![args.source_range.into()],
205 };
206 #[cfg(feature = "artifact-graph")]
207 let optional_constraints = {
208 let object_id = exec_state.add_placeholder_scene_object(segment.object_id, args.source_range, args.node_path);
209
210 let mut optional_constraints = Vec::new();
211 if exec_state.segment_ids_edited_contains(&object_id) {
212 if let Some(at_x_var) = at_x_value.as_sketch_var() {
213 let x_initial_value = at_x_var.initial_value_to_solver_units(
214 exec_state,
215 args.source_range,
216 "edited segment fixed constraint value",
217 )?;
218 optional_constraints.push(SolverConstraint::Fixed(
219 at_x_var.id.to_constraint_id(args.source_range)?,
220 x_initial_value.n,
221 ));
222 }
223 if let Some(at_y_var) = at_y_value.as_sketch_var() {
224 let y_initial_value = at_y_var.initial_value_to_solver_units(
225 exec_state,
226 args.source_range,
227 "edited segment fixed constraint value",
228 )?;
229 optional_constraints.push(SolverConstraint::Fixed(
230 at_y_var.id.to_constraint_id(args.source_range)?,
231 y_initial_value.n,
232 ));
233 }
234 }
235 optional_constraints
236 };
237
238 let Some(sketch_state) = exec_state.sketch_block_mut() else {
240 return Err(KclError::new_semantic(KclErrorDetails::new(
241 "line() can only be used inside a sketch block".to_owned(),
242 vec![args.source_range],
243 )));
244 };
245 sketch_state.needed_by_engine.push(segment.clone());
246
247 #[cfg(feature = "artifact-graph")]
248 sketch_state.solver_optional_constraints.extend(optional_constraints);
249
250 let meta = segment.meta.clone();
251 let abstract_segment = AbstractSegment {
252 repr: SegmentRepr::Unsolved {
253 segment: Box::new(segment),
254 },
255 meta,
256 };
257 Ok(KclValue::Segment {
258 value: Box::new(abstract_segment),
259 })
260}
261
262pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
263 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
264 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
265 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
266 let construction: bool = construction_opt.unwrap_or(false);
267 let construction_ctor = construction_opt;
268 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
269 KclError::new_semantic(KclErrorDetails::new(
270 "start must be a 2D point".to_owned(),
271 vec![args.source_range],
272 ))
273 })?;
274 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
275 KclError::new_semantic(KclErrorDetails::new(
276 "end must be a 2D point".to_owned(),
277 vec![args.source_range],
278 ))
279 })?;
280 let Some(start_x) = start_x_value.as_unsolved_expr() else {
281 return Err(KclError::new_semantic(KclErrorDetails::new(
282 "start x must be a number or sketch var".to_owned(),
283 vec![args.source_range],
284 )));
285 };
286 let Some(start_y) = start_y_value.as_unsolved_expr() else {
287 return Err(KclError::new_semantic(KclErrorDetails::new(
288 "start y must be a number or sketch var".to_owned(),
289 vec![args.source_range],
290 )));
291 };
292 let Some(end_x) = end_x_value.as_unsolved_expr() else {
293 return Err(KclError::new_semantic(KclErrorDetails::new(
294 "end x must be a number or sketch var".to_owned(),
295 vec![args.source_range],
296 )));
297 };
298 let Some(end_y) = end_y_value.as_unsolved_expr() else {
299 return Err(KclError::new_semantic(KclErrorDetails::new(
300 "end y must be a number or sketch var".to_owned(),
301 vec![args.source_range],
302 )));
303 };
304 let ctor = LineCtor {
305 start: Point2d {
306 x: start_x_value.to_sketch_expr().ok_or_else(|| {
307 KclError::new_semantic(KclErrorDetails::new(
308 "unable to convert numeric type to suffix".to_owned(),
309 vec![args.source_range],
310 ))
311 })?,
312 y: start_y_value.to_sketch_expr().ok_or_else(|| {
313 KclError::new_semantic(KclErrorDetails::new(
314 "unable to convert numeric type to suffix".to_owned(),
315 vec![args.source_range],
316 ))
317 })?,
318 },
319 end: Point2d {
320 x: end_x_value.to_sketch_expr().ok_or_else(|| {
321 KclError::new_semantic(KclErrorDetails::new(
322 "unable to convert numeric type to suffix".to_owned(),
323 vec![args.source_range],
324 ))
325 })?,
326 y: end_y_value.to_sketch_expr().ok_or_else(|| {
327 KclError::new_semantic(KclErrorDetails::new(
328 "unable to convert numeric type to suffix".to_owned(),
329 vec![args.source_range],
330 ))
331 })?,
332 },
333 construction: construction_ctor,
334 };
335 let start_object_id = exec_state.next_object_id();
337 let end_object_id = exec_state.next_object_id();
338 let line_object_id = exec_state.next_object_id();
339 let segment = UnsolvedSegment {
340 id: exec_state.next_uuid(),
341 object_id: line_object_id,
342 kind: UnsolvedSegmentKind::Line {
343 start: [start_x, start_y],
344 end: [end_x, end_y],
345 ctor: Box::new(ctor),
346 start_object_id,
347 end_object_id,
348 construction,
349 },
350 tag: None,
351 node_path: args.node_path.clone(),
352 meta: vec![args.source_range.into()],
353 };
354 #[cfg(feature = "artifact-graph")]
355 let optional_constraints = {
356 let start_object_id =
357 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
358 let end_object_id =
359 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
360 let line_object_id =
361 exec_state.add_placeholder_scene_object(line_object_id, args.source_range, args.node_path.clone());
362
363 let mut optional_constraints = Vec::new();
364 if exec_state.segment_ids_edited_contains(&start_object_id)
365 || exec_state.segment_ids_edited_contains(&line_object_id)
366 {
367 if let Some(start_x_var) = start_x_value.as_sketch_var() {
368 let x_initial_value = start_x_var.initial_value_to_solver_units(
369 exec_state,
370 args.source_range,
371 "edited segment fixed constraint value",
372 )?;
373 optional_constraints.push(SolverConstraint::Fixed(
374 start_x_var.id.to_constraint_id(args.source_range)?,
375 x_initial_value.n,
376 ));
377 }
378 if let Some(start_y_var) = start_y_value.as_sketch_var() {
379 let y_initial_value = start_y_var.initial_value_to_solver_units(
380 exec_state,
381 args.source_range,
382 "edited segment fixed constraint value",
383 )?;
384 optional_constraints.push(SolverConstraint::Fixed(
385 start_y_var.id.to_constraint_id(args.source_range)?,
386 y_initial_value.n,
387 ));
388 }
389 }
390 if exec_state.segment_ids_edited_contains(&end_object_id)
391 || exec_state.segment_ids_edited_contains(&line_object_id)
392 {
393 if let Some(end_x_var) = end_x_value.as_sketch_var() {
394 let x_initial_value = end_x_var.initial_value_to_solver_units(
395 exec_state,
396 args.source_range,
397 "edited segment fixed constraint value",
398 )?;
399 optional_constraints.push(SolverConstraint::Fixed(
400 end_x_var.id.to_constraint_id(args.source_range)?,
401 x_initial_value.n,
402 ));
403 }
404 if let Some(end_y_var) = end_y_value.as_sketch_var() {
405 let y_initial_value = end_y_var.initial_value_to_solver_units(
406 exec_state,
407 args.source_range,
408 "edited segment fixed constraint value",
409 )?;
410 optional_constraints.push(SolverConstraint::Fixed(
411 end_y_var.id.to_constraint_id(args.source_range)?,
412 y_initial_value.n,
413 ));
414 }
415 }
416 optional_constraints
417 };
418
419 let Some(sketch_state) = exec_state.sketch_block_mut() else {
421 return Err(KclError::new_semantic(KclErrorDetails::new(
422 "line() can only be used inside a sketch block".to_owned(),
423 vec![args.source_range],
424 )));
425 };
426 sketch_state.needed_by_engine.push(segment.clone());
427
428 #[cfg(feature = "artifact-graph")]
429 sketch_state.solver_optional_constraints.extend(optional_constraints);
430
431 let meta = segment.meta.clone();
432 let abstract_segment = AbstractSegment {
433 repr: SegmentRepr::Unsolved {
434 segment: Box::new(segment),
435 },
436 meta,
437 };
438 Ok(KclValue::Segment {
439 value: Box::new(abstract_segment),
440 })
441}
442
443pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
444 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
445 let end: Vec<KclValue> = args.get_kw_arg("end", &RuntimeType::point2d(), exec_state)?;
446 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
448 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
449 let construction: bool = construction_opt.unwrap_or(false);
450 let construction_ctor = construction_opt;
451
452 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
453 KclError::new_semantic(KclErrorDetails::new(
454 "start must be a 2D point".to_owned(),
455 vec![args.source_range],
456 ))
457 })?;
458 let [end_x_value, end_y_value]: [KclValue; 2] = end.try_into().map_err(|_| {
459 KclError::new_semantic(KclErrorDetails::new(
460 "end must be a 2D point".to_owned(),
461 vec![args.source_range],
462 ))
463 })?;
464 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
465 KclError::new_semantic(KclErrorDetails::new(
466 "center must be a 2D point".to_owned(),
467 vec![args.source_range],
468 ))
469 })?;
470
471 let (start_x, start_x_fixed) = extract_arc_component(&start_x_value, exec_state, args.source_range, "start x")?;
472 let (start_y, start_y_fixed) = extract_arc_component(&start_y_value, exec_state, args.source_range, "start y")?;
473 let (end_x, end_x_fixed) = extract_arc_component(&end_x_value, exec_state, args.source_range, "end x")?;
474 let (end_y, end_y_fixed) = extract_arc_component(&end_y_value, exec_state, args.source_range, "end y")?;
475 let (center_x, center_x_fixed) = extract_arc_component(¢er_x_value, exec_state, args.source_range, "center x")?;
476 let (center_y, center_y_fixed) = extract_arc_component(¢er_y_value, exec_state, args.source_range, "center y")?;
477 let arc_fixed_constraints = [
480 start_x_fixed,
481 start_y_fixed,
482 end_x_fixed,
483 end_y_fixed,
484 center_x_fixed,
485 center_y_fixed,
486 ]
487 .into_iter()
488 .flatten();
489
490 let ctor = ArcCtor {
491 start: Point2d {
492 x: start_x_value.to_sketch_expr().ok_or_else(|| {
493 KclError::new_semantic(KclErrorDetails::new(
494 "unable to convert numeric type to suffix".to_owned(),
495 vec![args.source_range],
496 ))
497 })?,
498 y: start_y_value.to_sketch_expr().ok_or_else(|| {
499 KclError::new_semantic(KclErrorDetails::new(
500 "unable to convert numeric type to suffix".to_owned(),
501 vec![args.source_range],
502 ))
503 })?,
504 },
505 end: Point2d {
506 x: end_x_value.to_sketch_expr().ok_or_else(|| {
507 KclError::new_semantic(KclErrorDetails::new(
508 "unable to convert numeric type to suffix".to_owned(),
509 vec![args.source_range],
510 ))
511 })?,
512 y: end_y_value.to_sketch_expr().ok_or_else(|| {
513 KclError::new_semantic(KclErrorDetails::new(
514 "unable to convert numeric type to suffix".to_owned(),
515 vec![args.source_range],
516 ))
517 })?,
518 },
519 center: Point2d {
520 x: center_x_value.to_sketch_expr().ok_or_else(|| {
521 KclError::new_semantic(KclErrorDetails::new(
522 "unable to convert numeric type to suffix".to_owned(),
523 vec![args.source_range],
524 ))
525 })?,
526 y: center_y_value.to_sketch_expr().ok_or_else(|| {
527 KclError::new_semantic(KclErrorDetails::new(
528 "unable to convert numeric type to suffix".to_owned(),
529 vec![args.source_range],
530 ))
531 })?,
532 },
533 construction: construction_ctor,
534 };
535
536 let start_object_id = exec_state.next_object_id();
538 let end_object_id = exec_state.next_object_id();
539 let center_object_id = exec_state.next_object_id();
540 let arc_object_id = exec_state.next_object_id();
541 let segment = UnsolvedSegment {
542 id: exec_state.next_uuid(),
543 object_id: arc_object_id,
544 kind: UnsolvedSegmentKind::Arc {
545 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
546 end: [UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)],
547 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
548 ctor: Box::new(ctor),
549 start_object_id,
550 end_object_id,
551 center_object_id,
552 construction,
553 },
554 tag: None,
555 node_path: args.node_path.clone(),
556 meta: vec![args.source_range.into()],
557 };
558 #[cfg(feature = "artifact-graph")]
559 let optional_constraints = {
560 let start_object_id =
561 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
562 let end_object_id =
563 exec_state.add_placeholder_scene_object(end_object_id, args.source_range, args.node_path.clone());
564 let center_object_id =
565 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
566 let arc_object_id =
567 exec_state.add_placeholder_scene_object(arc_object_id, args.source_range, args.node_path.clone());
568
569 let mut optional_constraints = Vec::new();
570 if exec_state.segment_ids_edited_contains(&start_object_id)
571 || exec_state.segment_ids_edited_contains(&arc_object_id)
572 {
573 if let Some(start_x_var) = start_x_value.as_sketch_var() {
574 let x_initial_value = start_x_var.initial_value_to_solver_units(
575 exec_state,
576 args.source_range,
577 "edited segment fixed constraint value",
578 )?;
579 optional_constraints.push(ezpz::Constraint::Fixed(
580 start_x_var.id.to_constraint_id(args.source_range)?,
581 x_initial_value.n,
582 ));
583 }
584 if let Some(start_y_var) = start_y_value.as_sketch_var() {
585 let y_initial_value = start_y_var.initial_value_to_solver_units(
586 exec_state,
587 args.source_range,
588 "edited segment fixed constraint value",
589 )?;
590 optional_constraints.push(ezpz::Constraint::Fixed(
591 start_y_var.id.to_constraint_id(args.source_range)?,
592 y_initial_value.n,
593 ));
594 }
595 }
596 if exec_state.segment_ids_edited_contains(&end_object_id)
597 || exec_state.segment_ids_edited_contains(&arc_object_id)
598 {
599 if let Some(end_x_var) = end_x_value.as_sketch_var() {
600 let x_initial_value = end_x_var.initial_value_to_solver_units(
601 exec_state,
602 args.source_range,
603 "edited segment fixed constraint value",
604 )?;
605 optional_constraints.push(ezpz::Constraint::Fixed(
606 end_x_var.id.to_constraint_id(args.source_range)?,
607 x_initial_value.n,
608 ));
609 }
610 if let Some(end_y_var) = end_y_value.as_sketch_var() {
611 let y_initial_value = end_y_var.initial_value_to_solver_units(
612 exec_state,
613 args.source_range,
614 "edited segment fixed constraint value",
615 )?;
616 optional_constraints.push(ezpz::Constraint::Fixed(
617 end_y_var.id.to_constraint_id(args.source_range)?,
618 y_initial_value.n,
619 ));
620 }
621 }
622 if exec_state.segment_ids_edited_contains(¢er_object_id)
623 || exec_state.segment_ids_edited_contains(&arc_object_id)
624 {
625 if let Some(center_x_var) = center_x_value.as_sketch_var() {
626 let x_initial_value = center_x_var.initial_value_to_solver_units(
627 exec_state,
628 args.source_range,
629 "edited segment fixed constraint value",
630 )?;
631 optional_constraints.push(ezpz::Constraint::Fixed(
632 center_x_var.id.to_constraint_id(args.source_range)?,
633 x_initial_value.n,
634 ));
635 }
636 if let Some(center_y_var) = center_y_value.as_sketch_var() {
637 let y_initial_value = center_y_var.initial_value_to_solver_units(
638 exec_state,
639 args.source_range,
640 "edited segment fixed constraint value",
641 )?;
642 optional_constraints.push(ezpz::Constraint::Fixed(
643 center_y_var.id.to_constraint_id(args.source_range)?,
644 y_initial_value.n,
645 ));
646 }
647 }
648 optional_constraints
649 };
650
651 let range = args.source_range;
653 let mut required_constraints = Vec::with_capacity(7);
654 required_constraints.extend(arc_fixed_constraints);
655 required_constraints.push(ezpz::Constraint::Arc(ezpz::datatypes::inputs::DatumCircularArc {
656 center: ezpz::datatypes::inputs::DatumPoint::new_xy(
657 center_x.to_constraint_id(range)?,
658 center_y.to_constraint_id(range)?,
659 ),
660 start: ezpz::datatypes::inputs::DatumPoint::new_xy(
661 start_x.to_constraint_id(range)?,
662 start_y.to_constraint_id(range)?,
663 ),
664 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
665 end_x.to_constraint_id(range)?,
666 end_y.to_constraint_id(range)?,
667 ),
668 }));
669
670 let Some(sketch_state) = exec_state.sketch_block_mut() else {
671 return Err(KclError::new_semantic(KclErrorDetails::new(
672 "arc() can only be used inside a sketch block".to_owned(),
673 vec![args.source_range],
674 )));
675 };
676 sketch_state.needed_by_engine.push(segment.clone());
678 sketch_state.solver_constraints.extend(required_constraints);
680 #[cfg(feature = "artifact-graph")]
684 sketch_state.solver_optional_constraints.extend(optional_constraints);
685
686 let meta = segment.meta.clone();
687 let abstract_segment = AbstractSegment {
688 repr: SegmentRepr::Unsolved {
689 segment: Box::new(segment),
690 },
691 meta,
692 };
693 Ok(KclValue::Segment {
694 value: Box::new(abstract_segment),
695 })
696}
697
698pub async fn circle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
699 let start: Vec<KclValue> = args.get_kw_arg("start", &RuntimeType::point2d(), exec_state)?;
700 let center: Vec<KclValue> = args.get_kw_arg("center", &RuntimeType::point2d(), exec_state)?;
701 let construction_opt = args.get_kw_arg_opt("construction", &RuntimeType::bool(), exec_state)?;
702 let construction: bool = construction_opt.unwrap_or(false);
703 let construction_ctor = construction_opt;
704
705 let [start_x_value, start_y_value]: [KclValue; 2] = start.try_into().map_err(|_| {
706 KclError::new_semantic(KclErrorDetails::new(
707 "start must be a 2D point".to_owned(),
708 vec![args.source_range],
709 ))
710 })?;
711 let [center_x_value, center_y_value]: [KclValue; 2] = center.try_into().map_err(|_| {
712 KclError::new_semantic(KclErrorDetails::new(
713 "center must be a 2D point".to_owned(),
714 vec![args.source_range],
715 ))
716 })?;
717
718 let Some(UnsolvedExpr::Unknown(start_x)) = start_x_value.as_unsolved_expr() else {
719 return Err(KclError::new_semantic(KclErrorDetails::new(
720 "start x must be a sketch var".to_owned(),
721 vec![args.source_range],
722 )));
723 };
724 let Some(UnsolvedExpr::Unknown(start_y)) = start_y_value.as_unsolved_expr() else {
725 return Err(KclError::new_semantic(KclErrorDetails::new(
726 "start y must be a sketch var".to_owned(),
727 vec![args.source_range],
728 )));
729 };
730 let Some(UnsolvedExpr::Unknown(center_x)) = center_x_value.as_unsolved_expr() else {
731 return Err(KclError::new_semantic(KclErrorDetails::new(
732 "center x must be a sketch var".to_owned(),
733 vec![args.source_range],
734 )));
735 };
736 let Some(UnsolvedExpr::Unknown(center_y)) = center_y_value.as_unsolved_expr() else {
737 return Err(KclError::new_semantic(KclErrorDetails::new(
738 "center y must be a sketch var".to_owned(),
739 vec![args.source_range],
740 )));
741 };
742
743 let ctor = CircleCtor {
744 start: Point2d {
745 x: start_x_value.to_sketch_expr().ok_or_else(|| {
746 KclError::new_semantic(KclErrorDetails::new(
747 "unable to convert numeric type to suffix".to_owned(),
748 vec![args.source_range],
749 ))
750 })?,
751 y: start_y_value.to_sketch_expr().ok_or_else(|| {
752 KclError::new_semantic(KclErrorDetails::new(
753 "unable to convert numeric type to suffix".to_owned(),
754 vec![args.source_range],
755 ))
756 })?,
757 },
758 center: Point2d {
759 x: center_x_value.to_sketch_expr().ok_or_else(|| {
760 KclError::new_semantic(KclErrorDetails::new(
761 "unable to convert numeric type to suffix".to_owned(),
762 vec![args.source_range],
763 ))
764 })?,
765 y: center_y_value.to_sketch_expr().ok_or_else(|| {
766 KclError::new_semantic(KclErrorDetails::new(
767 "unable to convert numeric type to suffix".to_owned(),
768 vec![args.source_range],
769 ))
770 })?,
771 },
772 construction: construction_ctor,
773 };
774
775 let start_object_id = exec_state.next_object_id();
777 let center_object_id = exec_state.next_object_id();
778 let circle_object_id = exec_state.next_object_id();
779 let segment = UnsolvedSegment {
780 id: exec_state.next_uuid(),
781 object_id: circle_object_id,
782 kind: UnsolvedSegmentKind::Circle {
783 start: [UnsolvedExpr::Unknown(start_x), UnsolvedExpr::Unknown(start_y)],
784 center: [UnsolvedExpr::Unknown(center_x), UnsolvedExpr::Unknown(center_y)],
785 ctor: Box::new(ctor),
786 start_object_id,
787 center_object_id,
788 construction,
789 },
790 tag: None,
791 node_path: args.node_path.clone(),
792 meta: vec![args.source_range.into()],
793 };
794 #[cfg(feature = "artifact-graph")]
795 let optional_constraints = {
796 let start_object_id =
797 exec_state.add_placeholder_scene_object(start_object_id, args.source_range, args.node_path.clone());
798 let center_object_id =
799 exec_state.add_placeholder_scene_object(center_object_id, args.source_range, args.node_path.clone());
800 let circle_object_id =
801 exec_state.add_placeholder_scene_object(circle_object_id, args.source_range, args.node_path.clone());
802
803 let mut optional_constraints = Vec::new();
804 if exec_state.segment_ids_edited_contains(&start_object_id)
805 || exec_state.segment_ids_edited_contains(&circle_object_id)
806 {
807 if let Some(start_x_var) = start_x_value.as_sketch_var() {
808 let x_initial_value = start_x_var.initial_value_to_solver_units(
809 exec_state,
810 args.source_range,
811 "edited segment fixed constraint value",
812 )?;
813 optional_constraints.push(ezpz::Constraint::Fixed(
814 start_x_var.id.to_constraint_id(args.source_range)?,
815 x_initial_value.n,
816 ));
817 }
818 if let Some(start_y_var) = start_y_value.as_sketch_var() {
819 let y_initial_value = start_y_var.initial_value_to_solver_units(
820 exec_state,
821 args.source_range,
822 "edited segment fixed constraint value",
823 )?;
824 optional_constraints.push(ezpz::Constraint::Fixed(
825 start_y_var.id.to_constraint_id(args.source_range)?,
826 y_initial_value.n,
827 ));
828 }
829 }
830 if exec_state.segment_ids_edited_contains(¢er_object_id)
831 || exec_state.segment_ids_edited_contains(&circle_object_id)
832 {
833 if let Some(center_x_var) = center_x_value.as_sketch_var() {
834 let x_initial_value = center_x_var.initial_value_to_solver_units(
835 exec_state,
836 args.source_range,
837 "edited segment fixed constraint value",
838 )?;
839 optional_constraints.push(ezpz::Constraint::Fixed(
840 center_x_var.id.to_constraint_id(args.source_range)?,
841 x_initial_value.n,
842 ));
843 }
844 if let Some(center_y_var) = center_y_value.as_sketch_var() {
845 let y_initial_value = center_y_var.initial_value_to_solver_units(
846 exec_state,
847 args.source_range,
848 "edited segment fixed constraint value",
849 )?;
850 optional_constraints.push(ezpz::Constraint::Fixed(
851 center_y_var.id.to_constraint_id(args.source_range)?,
852 y_initial_value.n,
853 ));
854 }
855 }
856 optional_constraints
857 };
858
859 let Some(sketch_state) = exec_state.sketch_block_mut() else {
860 return Err(KclError::new_semantic(KclErrorDetails::new(
861 "circle() can only be used inside a sketch block".to_owned(),
862 vec![args.source_range],
863 )));
864 };
865 sketch_state.needed_by_engine.push(segment.clone());
867
868 #[cfg(feature = "artifact-graph")]
869 sketch_state.solver_optional_constraints.extend(optional_constraints);
870
871 let meta = segment.meta.clone();
872 let abstract_segment = AbstractSegment {
873 repr: SegmentRepr::Unsolved {
874 segment: Box::new(segment),
875 },
876 meta,
877 };
878 Ok(KclValue::Segment {
879 value: Box::new(abstract_segment),
880 })
881}
882
883pub async fn coincident(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
884 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
885 "points",
886 &RuntimeType::Array(
887 Box::new(RuntimeType::Union(vec![RuntimeType::segment(), RuntimeType::point2d()])),
888 ArrayLen::Known(2),
889 ),
890 exec_state,
891 )?;
892 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
893 KclError::new_semantic(KclErrorDetails::new(
894 "must have two input points".to_owned(),
895 vec![args.source_range],
896 ))
897 })?;
898
899 let range = args.source_range;
900 match (&point0, &point1) {
901 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
902 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
903 return Err(KclError::new_semantic(KclErrorDetails::new(
904 "first point must be an unsolved segment".to_owned(),
905 vec![args.source_range],
906 )));
907 };
908 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
909 return Err(KclError::new_semantic(KclErrorDetails::new(
910 "second point must be an unsolved segment".to_owned(),
911 vec![args.source_range],
912 )));
913 };
914 match (&unsolved0.kind, &unsolved1.kind) {
915 (
916 UnsolvedSegmentKind::Point { position: pos0, .. },
917 UnsolvedSegmentKind::Point { position: pos1, .. },
918 ) => {
919 let p0_x = &pos0[0];
920 let p0_y = &pos0[1];
921 match (p0_x, p0_y) {
922 (UnsolvedExpr::Unknown(p0_x), UnsolvedExpr::Unknown(p0_y)) => {
923 let p1_x = &pos1[0];
924 let p1_y = &pos1[1];
925 match (p1_x, p1_y) {
926 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
927 let constraint = SolverConstraint::PointsCoincident(
928 ezpz::datatypes::inputs::DatumPoint::new_xy(
929 p0_x.to_constraint_id(range)?,
930 p0_y.to_constraint_id(range)?,
931 ),
932 ezpz::datatypes::inputs::DatumPoint::new_xy(
933 p1_x.to_constraint_id(range)?,
934 p1_y.to_constraint_id(range)?,
935 ),
936 );
937 #[cfg(feature = "artifact-graph")]
938 let constraint_id = exec_state.next_object_id();
939 let Some(sketch_state) = exec_state.sketch_block_mut() else {
941 return Err(KclError::new_semantic(KclErrorDetails::new(
942 "coincident() can only be used inside a sketch block".to_owned(),
943 vec![args.source_range],
944 )));
945 };
946 sketch_state.solver_constraints.push(constraint);
947 #[cfg(feature = "artifact-graph")]
948 {
949 let constraint = crate::front::Constraint::Coincident(Coincident {
950 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
951 });
952 sketch_state.sketch_constraints.push(constraint_id);
953 track_constraint(constraint_id, constraint, exec_state, &args);
954 }
955 Ok(KclValue::none())
956 }
957 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
958 let p1_x = KclValue::Number {
959 value: p1_x.n,
960 ty: p1_x.ty,
961 meta: vec![args.source_range.into()],
962 };
963 let p1_y = KclValue::Number {
964 value: p1_y.n,
965 ty: p1_y.ty,
966 meta: vec![args.source_range.into()],
967 };
968 let (constraint_x, constraint_y) =
969 coincident_constraints_fixed(*p0_x, *p0_y, &p1_x, &p1_y, exec_state, &args)?;
970
971 #[cfg(feature = "artifact-graph")]
972 let constraint_id = exec_state.next_object_id();
973 let Some(sketch_state) = exec_state.sketch_block_mut() else {
975 return Err(KclError::new_semantic(KclErrorDetails::new(
976 "coincident() can only be used inside a sketch block".to_owned(),
977 vec![args.source_range],
978 )));
979 };
980 sketch_state.solver_constraints.push(constraint_x);
981 sketch_state.solver_constraints.push(constraint_y);
982 #[cfg(feature = "artifact-graph")]
983 {
984 let constraint = crate::front::Constraint::Coincident(Coincident {
985 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
986 });
987 sketch_state.sketch_constraints.push(constraint_id);
988 track_constraint(constraint_id, constraint, exec_state, &args);
989 }
990 Ok(KclValue::none())
991 }
992 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
993 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
994 Err(KclError::new_semantic(KclErrorDetails::new(
996 "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(),
997 vec![args.source_range],
998 )))
999 }
1000 }
1001 }
1002 (UnsolvedExpr::Known(p0_x), UnsolvedExpr::Known(p0_y)) => {
1003 let p1_x = &pos1[0];
1004 let p1_y = &pos1[1];
1005 match (p1_x, p1_y) {
1006 (UnsolvedExpr::Unknown(p1_x), UnsolvedExpr::Unknown(p1_y)) => {
1007 let p0_x = KclValue::Number {
1008 value: p0_x.n,
1009 ty: p0_x.ty,
1010 meta: vec![args.source_range.into()],
1011 };
1012 let p0_y = KclValue::Number {
1013 value: p0_y.n,
1014 ty: p0_y.ty,
1015 meta: vec![args.source_range.into()],
1016 };
1017 let (constraint_x, constraint_y) =
1018 coincident_constraints_fixed(*p1_x, *p1_y, &p0_x, &p0_y, exec_state, &args)?;
1019
1020 #[cfg(feature = "artifact-graph")]
1021 let constraint_id = exec_state.next_object_id();
1022 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1024 return Err(KclError::new_semantic(KclErrorDetails::new(
1025 "coincident() can only be used inside a sketch block".to_owned(),
1026 vec![args.source_range],
1027 )));
1028 };
1029 sketch_state.solver_constraints.push(constraint_x);
1030 sketch_state.solver_constraints.push(constraint_y);
1031 #[cfg(feature = "artifact-graph")]
1032 {
1033 let constraint = crate::front::Constraint::Coincident(Coincident {
1034 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1035 });
1036 sketch_state.sketch_constraints.push(constraint_id);
1037 track_constraint(constraint_id, constraint, exec_state, &args);
1038 }
1039 Ok(KclValue::none())
1040 }
1041 (UnsolvedExpr::Known(p1_x), UnsolvedExpr::Known(p1_y)) => {
1042 if *p0_x != *p1_x || *p0_y != *p1_y {
1043 return Err(KclError::new_semantic(KclErrorDetails::new(
1044 "Coincident constraint between two fixed points failed since coordinates differ"
1045 .to_owned(),
1046 vec![args.source_range],
1047 )));
1048 }
1049 Ok(KclValue::none())
1050 }
1051 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1052 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1053 Err(KclError::new_semantic(KclErrorDetails::new(
1055 "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(),
1056 vec![args.source_range],
1057 )))
1058 }
1059 }
1060 }
1061 (UnsolvedExpr::Known(_), UnsolvedExpr::Unknown(_))
1062 | (UnsolvedExpr::Unknown(_), UnsolvedExpr::Known(_)) => {
1063 Err(KclError::new_semantic(KclErrorDetails::new(
1065 "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(),
1066 vec![args.source_range],
1067 )))
1068 }
1069 }
1070 }
1071 (
1073 UnsolvedSegmentKind::Point {
1074 position: point_pos, ..
1075 },
1076 UnsolvedSegmentKind::Line {
1077 start: line_start,
1078 end: line_end,
1079 ..
1080 },
1081 )
1082 | (
1083 UnsolvedSegmentKind::Line {
1084 start: line_start,
1085 end: line_end,
1086 ..
1087 },
1088 UnsolvedSegmentKind::Point {
1089 position: point_pos, ..
1090 },
1091 ) => {
1092 let point_x = &point_pos[0];
1093 let point_y = &point_pos[1];
1094 match (point_x, point_y) {
1095 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1096 let (start_x, start_y) = (&line_start[0], &line_start[1]);
1098 let (end_x, end_y) = (&line_end[0], &line_end[1]);
1099
1100 match (start_x, start_y, end_x, end_y) {
1101 (
1102 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1103 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1104 ) => {
1105 let point = DatumPoint::new_xy(
1106 point_x.to_constraint_id(range)?,
1107 point_y.to_constraint_id(range)?,
1108 );
1109 let line_segment = DatumLineSegment::new(
1110 DatumPoint::new_xy(sx.to_constraint_id(range)?, sy.to_constraint_id(range)?),
1111 DatumPoint::new_xy(ex.to_constraint_id(range)?, ey.to_constraint_id(range)?),
1112 );
1113 let constraint = SolverConstraint::PointLineDistance(point, line_segment, 0.0);
1114
1115 #[cfg(feature = "artifact-graph")]
1116 let constraint_id = exec_state.next_object_id();
1117
1118 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1119 return Err(KclError::new_semantic(KclErrorDetails::new(
1120 "coincident() can only be used inside a sketch block".to_owned(),
1121 vec![args.source_range],
1122 )));
1123 };
1124 sketch_state.solver_constraints.push(constraint);
1125 #[cfg(feature = "artifact-graph")]
1126 {
1127 let constraint = crate::front::Constraint::Coincident(Coincident {
1128 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1129 });
1130 sketch_state.sketch_constraints.push(constraint_id);
1131 track_constraint(constraint_id, constraint, exec_state, &args);
1132 }
1133 Ok(KclValue::none())
1134 }
1135 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1136 "Line segment endpoints must be sketch variables for point-segment coincident constraint".to_owned(),
1137 vec![args.source_range],
1138 ))),
1139 }
1140 }
1141 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1142 "Point coordinates must be sketch variables for point-segment coincident constraint"
1143 .to_owned(),
1144 vec![args.source_range],
1145 ))),
1146 }
1147 }
1148 (
1150 UnsolvedSegmentKind::Point {
1151 position: point_pos, ..
1152 },
1153 UnsolvedSegmentKind::Arc {
1154 start: arc_start,
1155 end: arc_end,
1156 center: arc_center,
1157 ..
1158 },
1159 )
1160 | (
1161 UnsolvedSegmentKind::Arc {
1162 start: arc_start,
1163 end: arc_end,
1164 center: arc_center,
1165 ..
1166 },
1167 UnsolvedSegmentKind::Point {
1168 position: point_pos, ..
1169 },
1170 ) => {
1171 let point_x = &point_pos[0];
1172 let point_y = &point_pos[1];
1173 match (point_x, point_y) {
1174 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1175 let (center_x, center_y) = (&arc_center[0], &arc_center[1]);
1177 let (start_x, start_y) = (&arc_start[0], &arc_start[1]);
1178 let (end_x, end_y) = (&arc_end[0], &arc_end[1]);
1179
1180 match (center_x, center_y, start_x, start_y, end_x, end_y) {
1181 (
1182 UnsolvedExpr::Unknown(cx), UnsolvedExpr::Unknown(cy),
1183 UnsolvedExpr::Unknown(sx), UnsolvedExpr::Unknown(sy),
1184 UnsolvedExpr::Unknown(ex), UnsolvedExpr::Unknown(ey),
1185 ) => {
1186 let point = DatumPoint::new_xy(
1187 point_x.to_constraint_id(range)?,
1188 point_y.to_constraint_id(range)?,
1189 );
1190 let circular_arc = DatumCircularArc {
1191 center: DatumPoint::new_xy(
1192 cx.to_constraint_id(range)?,
1193 cy.to_constraint_id(range)?,
1194 ),
1195 start: DatumPoint::new_xy(
1196 sx.to_constraint_id(range)?,
1197 sy.to_constraint_id(range)?,
1198 ),
1199 end: DatumPoint::new_xy(
1200 ex.to_constraint_id(range)?,
1201 ey.to_constraint_id(range)?,
1202 ),
1203 };
1204 let constraint = SolverConstraint::PointArcCoincident(circular_arc, point);
1205
1206 #[cfg(feature = "artifact-graph")]
1207 let constraint_id = exec_state.next_object_id();
1208
1209 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1210 return Err(KclError::new_semantic(KclErrorDetails::new(
1211 "coincident() can only be used inside a sketch block".to_owned(),
1212 vec![args.source_range],
1213 )));
1214 };
1215 sketch_state.solver_constraints.push(constraint);
1216 #[cfg(feature = "artifact-graph")]
1217 {
1218 let constraint = crate::front::Constraint::Coincident(Coincident {
1219 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1220 });
1221 sketch_state.sketch_constraints.push(constraint_id);
1222 track_constraint(constraint_id, constraint, exec_state, &args);
1223 }
1224 Ok(KclValue::none())
1225 }
1226 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1227 "Arc center, start, and end points must be sketch variables for point-arc coincident constraint".to_owned(),
1228 vec![args.source_range],
1229 ))),
1230 }
1231 }
1232 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1233 "Point coordinates must be sketch variables for point-arc coincident constraint".to_owned(),
1234 vec![args.source_range],
1235 ))),
1236 }
1237 }
1238 (
1241 UnsolvedSegmentKind::Point {
1242 position: point_pos, ..
1243 },
1244 UnsolvedSegmentKind::Circle {
1245 start: circle_start,
1246 center: circle_center,
1247 ..
1248 },
1249 )
1250 | (
1251 UnsolvedSegmentKind::Circle {
1252 start: circle_start,
1253 center: circle_center,
1254 ..
1255 },
1256 UnsolvedSegmentKind::Point {
1257 position: point_pos, ..
1258 },
1259 ) => {
1260 let point_x = &point_pos[0];
1261 let point_y = &point_pos[1];
1262 match (point_x, point_y) {
1263 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1264 let (center_x, center_y) = (&circle_center[0], &circle_center[1]);
1266 let (start_x, start_y) = (&circle_start[0], &circle_start[1]);
1267
1268 match (center_x, center_y, start_x, start_y) {
1269 (
1270 UnsolvedExpr::Unknown(cx),
1271 UnsolvedExpr::Unknown(cy),
1272 UnsolvedExpr::Unknown(sx),
1273 UnsolvedExpr::Unknown(sy),
1274 ) => {
1275 let point_radius_line = DatumLineSegment::new(
1276 DatumPoint::new_xy(
1277 cx.to_constraint_id(range)?,
1278 cy.to_constraint_id(range)?,
1279 ),
1280 DatumPoint::new_xy(
1281 point_x.to_constraint_id(range)?,
1282 point_y.to_constraint_id(range)?,
1283 ),
1284 );
1285 let circle_radius_line = DatumLineSegment::new(
1286 DatumPoint::new_xy(
1287 cx.to_constraint_id(range)?,
1288 cy.to_constraint_id(range)?,
1289 ),
1290 DatumPoint::new_xy(
1291 sx.to_constraint_id(range)?,
1292 sy.to_constraint_id(range)?,
1293 ),
1294 );
1295 let constraint =
1296 SolverConstraint::LinesEqualLength(point_radius_line, circle_radius_line);
1297
1298 #[cfg(feature = "artifact-graph")]
1299 let constraint_id = exec_state.next_object_id();
1300
1301 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1302 return Err(KclError::new_semantic(KclErrorDetails::new(
1303 "coincident() can only be used inside a sketch block".to_owned(),
1304 vec![args.source_range],
1305 )));
1306 };
1307 sketch_state.solver_constraints.push(constraint);
1308 #[cfg(feature = "artifact-graph")]
1309 {
1310 let constraint = crate::front::Constraint::Coincident(Coincident {
1311 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1312 });
1313 sketch_state.sketch_constraints.push(constraint_id);
1314 track_constraint(constraint_id, constraint, exec_state, &args);
1315 }
1316 Ok(KclValue::none())
1317 }
1318 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1319 "Circle start and center points must be sketch variables for point-circle coincident constraint".to_owned(),
1320 vec![args.source_range],
1321 ))),
1322 }
1323 }
1324 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1325 "Point coordinates must be sketch variables for point-circle coincident constraint"
1326 .to_owned(),
1327 vec![args.source_range],
1328 ))),
1329 }
1330 }
1331 (
1333 UnsolvedSegmentKind::Line {
1334 start: line0_start,
1335 end: line0_end,
1336 ..
1337 },
1338 UnsolvedSegmentKind::Line {
1339 start: line1_start,
1340 end: line1_end,
1341 ..
1342 },
1343 ) => {
1344 let (line0_start_x, line0_start_y) = (&line0_start[0], &line0_start[1]);
1346 let (line0_end_x, line0_end_y) = (&line0_end[0], &line0_end[1]);
1347 let (line1_start_x, line1_start_y) = (&line1_start[0], &line1_start[1]);
1348 let (line1_end_x, line1_end_y) = (&line1_end[0], &line1_end[1]);
1349
1350 match (
1351 line0_start_x,
1352 line0_start_y,
1353 line0_end_x,
1354 line0_end_y,
1355 line1_start_x,
1356 line1_start_y,
1357 line1_end_x,
1358 line1_end_y,
1359 ) {
1360 (
1361 UnsolvedExpr::Unknown(l0_sx),
1362 UnsolvedExpr::Unknown(l0_sy),
1363 UnsolvedExpr::Unknown(l0_ex),
1364 UnsolvedExpr::Unknown(l0_ey),
1365 UnsolvedExpr::Unknown(l1_sx),
1366 UnsolvedExpr::Unknown(l1_sy),
1367 UnsolvedExpr::Unknown(l1_ex),
1368 UnsolvedExpr::Unknown(l1_ey),
1369 ) => {
1370 let line0_segment = DatumLineSegment::new(
1372 DatumPoint::new_xy(l0_sx.to_constraint_id(range)?, l0_sy.to_constraint_id(range)?),
1373 DatumPoint::new_xy(l0_ex.to_constraint_id(range)?, l0_ey.to_constraint_id(range)?),
1374 );
1375 let line1_segment = DatumLineSegment::new(
1376 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?),
1377 DatumPoint::new_xy(l1_ex.to_constraint_id(range)?, l1_ey.to_constraint_id(range)?),
1378 );
1379
1380 let parallel_constraint =
1382 SolverConstraint::LinesAtAngle(line0_segment, line1_segment, AngleKind::Parallel);
1383
1384 let point_on_line1 =
1386 DatumPoint::new_xy(l1_sx.to_constraint_id(range)?, l1_sy.to_constraint_id(range)?);
1387 let distance_constraint =
1388 SolverConstraint::PointLineDistance(point_on_line1, line0_segment, 0.0);
1389
1390 #[cfg(feature = "artifact-graph")]
1391 let constraint_id = exec_state.next_object_id();
1392
1393 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1394 return Err(KclError::new_semantic(KclErrorDetails::new(
1395 "coincident() can only be used inside a sketch block".to_owned(),
1396 vec![args.source_range],
1397 )));
1398 };
1399 sketch_state.solver_constraints.push(parallel_constraint);
1401 sketch_state.solver_constraints.push(distance_constraint);
1402 #[cfg(feature = "artifact-graph")]
1403 {
1404 let constraint = crate::front::Constraint::Coincident(Coincident {
1405 segments: vec![unsolved0.object_id.into(), unsolved1.object_id.into()],
1406 });
1407 sketch_state.sketch_constraints.push(constraint_id);
1408 track_constraint(constraint_id, constraint, exec_state, &args);
1409 }
1410 Ok(KclValue::none())
1411 }
1412 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1413 "Line segment endpoints must be sketch variables for line-line coincident constraint"
1414 .to_owned(),
1415 vec![args.source_range],
1416 ))),
1417 }
1418 }
1419 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1420 format!(
1421 "coincident supports point-point, point-segment, or segment-segment; found {:?} and {:?}",
1422 &unsolved0.kind, &unsolved1.kind
1423 ),
1424 vec![args.source_range],
1425 ))),
1426 }
1427 }
1428 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
1431 let Some(pt) = <[TyF64; 2]>::from_kcl_val(point2d) else {
1432 return Err(KclError::new_semantic(KclErrorDetails::new(
1433 "Expected a Segment or Point2d (e.g. [1mm, 2mm])".to_owned(),
1434 vec![args.source_range],
1435 )));
1436 };
1437 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1438 return Err(KclError::new_semantic(KclErrorDetails::new(
1439 "segment must be an unsolved segment".to_owned(),
1440 vec![args.source_range],
1441 )));
1442 };
1443 match &unsolved.kind {
1444 UnsolvedSegmentKind::Point { position, .. } => {
1445 let p_x = &position[0];
1446 let p_y = &position[1];
1447 match (p_x, p_y) {
1448 (UnsolvedExpr::Unknown(p_x), UnsolvedExpr::Unknown(p_y)) => {
1449 let pt_x = KclValue::Number {
1450 value: pt[0].n,
1451 ty: pt[0].ty,
1452 meta: vec![args.source_range.into()],
1453 };
1454 let pt_y = KclValue::Number {
1455 value: pt[1].n,
1456 ty: pt[1].ty,
1457 meta: vec![args.source_range.into()],
1458 };
1459 let (constraint_x, constraint_y) =
1460 coincident_constraints_fixed(*p_x, *p_y, &pt_x, &pt_y, exec_state, &args)?;
1461
1462 #[cfg(feature = "artifact-graph")]
1463 let constraint_id = exec_state.next_object_id();
1464 #[cfg(feature = "artifact-graph")]
1465 let coincident_segments = coincident_segments_for_segment_and_point2d(
1466 unsolved.object_id,
1467 point2d,
1468 matches!((&point0, &point1), (KclValue::Segment { .. }, _)),
1469 );
1470 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1471 return Err(KclError::new_semantic(KclErrorDetails::new(
1472 "coincident() can only be used inside a sketch block".to_owned(),
1473 vec![args.source_range],
1474 )));
1475 };
1476 sketch_state.solver_constraints.push(constraint_x);
1477 sketch_state.solver_constraints.push(constraint_y);
1478 #[cfg(feature = "artifact-graph")]
1479 {
1480 let constraint = crate::front::Constraint::Coincident(Coincident {
1481 segments: coincident_segments,
1482 });
1483 sketch_state.sketch_constraints.push(constraint_id);
1484 track_constraint(constraint_id, constraint, exec_state, &args);
1485 }
1486 Ok(KclValue::none())
1487 }
1488 (UnsolvedExpr::Known(known_x), UnsolvedExpr::Known(known_y)) => {
1489 let pt_x_val = normalize_to_solver_distance_unit(
1490 &KclValue::Number {
1491 value: pt[0].n,
1492 ty: pt[0].ty,
1493 meta: vec![args.source_range.into()],
1494 },
1495 args.source_range,
1496 exec_state,
1497 "coincident constraint value",
1498 )?;
1499 let pt_y_val = normalize_to_solver_distance_unit(
1500 &KclValue::Number {
1501 value: pt[1].n,
1502 ty: pt[1].ty,
1503 meta: vec![args.source_range.into()],
1504 },
1505 args.source_range,
1506 exec_state,
1507 "coincident constraint value",
1508 )?;
1509 let Some(pt_x) = pt_x_val.as_ty_f64() else {
1510 return Err(KclError::new_semantic(KclErrorDetails::new(
1511 "Expected number for Point2d x coordinate".to_owned(),
1512 vec![args.source_range],
1513 )));
1514 };
1515 let Some(pt_y) = pt_y_val.as_ty_f64() else {
1516 return Err(KclError::new_semantic(KclErrorDetails::new(
1517 "Expected number for Point2d y coordinate".to_owned(),
1518 vec![args.source_range],
1519 )));
1520 };
1521 let known_x_val = normalize_to_solver_distance_unit(
1522 &KclValue::Number {
1523 value: known_x.n,
1524 ty: known_x.ty,
1525 meta: vec![args.source_range.into()],
1526 },
1527 args.source_range,
1528 exec_state,
1529 "coincident constraint value",
1530 )?;
1531 let Some(known_x_f) = known_x_val.as_ty_f64() else {
1532 return Err(KclError::new_semantic(KclErrorDetails::new(
1533 "Expected number for known x coordinate".to_owned(),
1534 vec![args.source_range],
1535 )));
1536 };
1537 let known_y_val = normalize_to_solver_distance_unit(
1538 &KclValue::Number {
1539 value: known_y.n,
1540 ty: known_y.ty,
1541 meta: vec![args.source_range.into()],
1542 },
1543 args.source_range,
1544 exec_state,
1545 "coincident constraint value",
1546 )?;
1547 let Some(known_y_f) = known_y_val.as_ty_f64() else {
1548 return Err(KclError::new_semantic(KclErrorDetails::new(
1549 "Expected number for known y coordinate".to_owned(),
1550 vec![args.source_range],
1551 )));
1552 };
1553 if known_x_f.n != pt_x.n || known_y_f.n != pt_y.n {
1554 return Err(KclError::new_semantic(KclErrorDetails::new(
1555 "Coincident constraint between two fixed points failed since coordinates differ"
1556 .to_owned(),
1557 vec![args.source_range],
1558 )));
1559 }
1560 Ok(KclValue::none())
1561 }
1562 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1563 "Point coordinates must have consistent known/unknown status for coincident constraint"
1564 .to_owned(),
1565 vec![args.source_range],
1566 ))),
1567 }
1568 }
1569 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1570 "A Point2d can only be constrained coincident with a point segment, not a line or arc".to_owned(),
1571 vec![args.source_range],
1572 ))),
1573 }
1574 }
1575 _ => {
1577 let pt0 = <[TyF64; 2]>::from_kcl_val(&point0);
1578 let pt1 = <[TyF64; 2]>::from_kcl_val(&point1);
1579 match (pt0, pt1) {
1580 (Some(a), Some(b)) => {
1581 let a_x = normalize_to_solver_distance_unit(
1583 &KclValue::Number {
1584 value: a[0].n,
1585 ty: a[0].ty,
1586 meta: vec![args.source_range.into()],
1587 },
1588 args.source_range,
1589 exec_state,
1590 "coincident constraint value",
1591 )?;
1592 let a_y = normalize_to_solver_distance_unit(
1593 &KclValue::Number {
1594 value: a[1].n,
1595 ty: a[1].ty,
1596 meta: vec![args.source_range.into()],
1597 },
1598 args.source_range,
1599 exec_state,
1600 "coincident constraint value",
1601 )?;
1602 let b_x = normalize_to_solver_distance_unit(
1603 &KclValue::Number {
1604 value: b[0].n,
1605 ty: b[0].ty,
1606 meta: vec![args.source_range.into()],
1607 },
1608 args.source_range,
1609 exec_state,
1610 "coincident constraint value",
1611 )?;
1612 let b_y = normalize_to_solver_distance_unit(
1613 &KclValue::Number {
1614 value: b[1].n,
1615 ty: b[1].ty,
1616 meta: vec![args.source_range.into()],
1617 },
1618 args.source_range,
1619 exec_state,
1620 "coincident constraint value",
1621 )?;
1622 if a_x.as_ty_f64().map(|v| v.n) != b_x.as_ty_f64().map(|v| v.n)
1623 || a_y.as_ty_f64().map(|v| v.n) != b_y.as_ty_f64().map(|v| v.n)
1624 {
1625 return Err(KclError::new_semantic(KclErrorDetails::new(
1626 "Coincident constraint between two fixed points failed since coordinates differ".to_owned(),
1627 vec![args.source_range],
1628 )));
1629 }
1630 Ok(KclValue::none())
1631 }
1632 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1633 "All inputs must be Segments or Point2d values".to_owned(),
1634 vec![args.source_range],
1635 ))),
1636 }
1637 }
1638 }
1639}
1640
1641#[cfg(feature = "artifact-graph")]
1642fn track_constraint(constraint_id: ObjectId, constraint: Constraint, exec_state: &mut ExecState, args: &Args) {
1643 let sketch_id = {
1644 let Some(sketch_state) = exec_state.sketch_block_mut() else {
1645 debug_assert!(false, "Constraint created outside a sketch block");
1646 return;
1647 };
1648 sketch_state.sketch_id
1649 };
1650 let Some(sketch_id) = sketch_id else {
1651 debug_assert!(false, "Constraint created without a sketch id");
1652 return;
1653 };
1654 let artifact_id = exec_state.next_artifact_id();
1655 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
1656 id: artifact_id,
1657 sketch_id,
1658 constraint_id,
1659 constraint_type: SketchBlockConstraintType::from(&constraint),
1660 code_ref: CodeRef::placeholder(args.source_range),
1661 }));
1662 exec_state.add_scene_object(
1663 Object {
1664 id: constraint_id,
1665 kind: ObjectKind::Constraint { constraint },
1666 label: Default::default(),
1667 comments: Default::default(),
1668 artifact_id,
1669 source: SourceRef::new(args.source_range, args.node_path.clone()),
1670 },
1671 args.source_range,
1672 );
1673}
1674
1675fn coincident_constraints_fixed(
1677 p0_x: SketchVarId,
1678 p0_y: SketchVarId,
1679 p1_x: &KclValue,
1680 p1_y: &KclValue,
1681 exec_state: &mut ExecState,
1682 args: &Args,
1683) -> Result<(ezpz::Constraint, ezpz::Constraint), KclError> {
1684 let p1_x_number_value =
1685 normalize_to_solver_distance_unit(p1_x, p1_x.into(), exec_state, "coincident constraint value")?;
1686 let p1_y_number_value =
1687 normalize_to_solver_distance_unit(p1_y, p1_y.into(), exec_state, "coincident constraint value")?;
1688 let Some(p1_x) = p1_x_number_value.as_ty_f64() else {
1689 let message = format!(
1690 "Expected number after coercion, but found {}",
1691 p1_x_number_value.human_friendly_type()
1692 );
1693 debug_assert!(false, "{}", &message);
1694 return Err(KclError::new_internal(KclErrorDetails::new(
1695 message,
1696 vec![args.source_range],
1697 )));
1698 };
1699 let Some(p1_y) = p1_y_number_value.as_ty_f64() else {
1700 let message = format!(
1701 "Expected number after coercion, but found {}",
1702 p1_y_number_value.human_friendly_type()
1703 );
1704 debug_assert!(false, "{}", &message);
1705 return Err(KclError::new_internal(KclErrorDetails::new(
1706 message,
1707 vec![args.source_range],
1708 )));
1709 };
1710 let constraint_x = SolverConstraint::Fixed(p0_x.to_constraint_id(args.source_range)?, p1_x.n);
1711 let constraint_y = SolverConstraint::Fixed(p0_y.to_constraint_id(args.source_range)?, p1_y.n);
1712 Ok((constraint_x, constraint_y))
1713}
1714
1715pub async fn distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1716 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1717 "points",
1718 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1719 exec_state,
1720 )?;
1721 let [point0, point1]: [KclValue; 2] = points.try_into().map_err(|_| {
1722 KclError::new_semantic(KclErrorDetails::new(
1723 "must have two input points".to_owned(),
1724 vec![args.source_range],
1725 ))
1726 })?;
1727
1728 match (&point0, &point1) {
1729 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1730 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1731 return Err(KclError::new_semantic(KclErrorDetails::new(
1732 "first point must be an unsolved segment".to_owned(),
1733 vec![args.source_range],
1734 )));
1735 };
1736 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1737 return Err(KclError::new_semantic(KclErrorDetails::new(
1738 "second point must be an unsolved segment".to_owned(),
1739 vec![args.source_range],
1740 )));
1741 };
1742 match (&unsolved0.kind, &unsolved1.kind) {
1743 (
1744 UnsolvedSegmentKind::Point { position: pos0, .. },
1745 UnsolvedSegmentKind::Point { position: pos1, .. },
1746 ) => {
1747 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
1750 (
1751 UnsolvedExpr::Unknown(p0_x),
1752 UnsolvedExpr::Unknown(p0_y),
1753 UnsolvedExpr::Unknown(p1_x),
1754 UnsolvedExpr::Unknown(p1_y),
1755 ) => {
1756 let sketch_constraint = SketchConstraint {
1758 kind: SketchConstraintKind::Distance {
1759 points: [
1760 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1761 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
1762 object_id: unsolved0.object_id,
1763 }),
1764 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1765 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
1766 object_id: unsolved1.object_id,
1767 }),
1768 ],
1769 },
1770 meta: vec![args.source_range.into()],
1771 };
1772 Ok(KclValue::SketchConstraint {
1773 value: Box::new(sketch_constraint),
1774 })
1775 }
1776 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1777 "unimplemented: distance() arguments must be all sketch vars in all coordinates".to_owned(),
1778 vec![args.source_range],
1779 ))),
1780 }
1781 }
1782 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1783 "distance() arguments must be unsolved points".to_owned(),
1784 vec![args.source_range],
1785 ))),
1786 }
1787 }
1788 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
1790 if !point2d_is_origin(point2d) {
1791 return Err(KclError::new_semantic(KclErrorDetails::new(
1792 "distance() Point2d arguments must be ORIGIN".to_owned(),
1793 vec![args.source_range],
1794 )));
1795 }
1796
1797 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1798 return Err(KclError::new_semantic(KclErrorDetails::new(
1799 "segment must be an unsolved segment".to_owned(),
1800 vec![args.source_range],
1801 )));
1802 };
1803 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
1804 return Err(KclError::new_semantic(KclErrorDetails::new(
1805 "distance() arguments must be unsolved points or ORIGIN".to_owned(),
1806 vec![args.source_range],
1807 )));
1808 };
1809 match (&position[0], &position[1]) {
1810 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
1811 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
1812 vars: crate::front::Point2d {
1813 x: *point_x,
1814 y: *point_y,
1815 },
1816 object_id: unsolved.object_id,
1817 });
1818 let points = if matches!((&point0, &point1), (KclValue::Segment { .. }, _)) {
1819 [point, ConstrainablePoint2dOrOrigin::Origin]
1820 } else {
1821 [ConstrainablePoint2dOrOrigin::Origin, point]
1822 };
1823 Ok(KclValue::SketchConstraint {
1824 value: Box::new(SketchConstraint {
1825 kind: SketchConstraintKind::Distance { points },
1826 meta: vec![args.source_range.into()],
1827 }),
1828 })
1829 }
1830 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1831 "unimplemented: distance() point arguments must be sketch vars in all coordinates".to_owned(),
1832 vec![args.source_range],
1833 ))),
1834 }
1835 }
1836 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1837 "distance() arguments must be point segments or ORIGIN".to_owned(),
1838 vec![args.source_range],
1839 ))),
1840 }
1841}
1842
1843fn create_circular_radius_constraint(
1846 segment: KclValue,
1847 constraint_kind: fn([ConstrainablePoint2d; 2]) -> SketchConstraintKind,
1848 source_range: crate::SourceRange,
1849) -> Result<SketchConstraint, KclError> {
1850 let dummy_constraint = constraint_kind([
1852 ConstrainablePoint2d {
1853 vars: crate::front::Point2d {
1854 x: SketchVarId(0),
1855 y: SketchVarId(0),
1856 },
1857 object_id: ObjectId(0),
1858 },
1859 ConstrainablePoint2d {
1860 vars: crate::front::Point2d {
1861 x: SketchVarId(0),
1862 y: SketchVarId(0),
1863 },
1864 object_id: ObjectId(0),
1865 },
1866 ]);
1867 let function_name = dummy_constraint.name();
1868
1869 let KclValue::Segment { value: seg } = segment else {
1870 return Err(KclError::new_semantic(KclErrorDetails::new(
1871 format!("{}() argument must be a segment", function_name),
1872 vec![source_range],
1873 )));
1874 };
1875 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
1876 return Err(KclError::new_semantic(KclErrorDetails::new(
1877 "segment must be unsolved".to_owned(),
1878 vec![source_range],
1879 )));
1880 };
1881 match &unsolved.kind {
1882 UnsolvedSegmentKind::Arc {
1883 center,
1884 start,
1885 center_object_id,
1886 start_object_id,
1887 ..
1888 }
1889 | UnsolvedSegmentKind::Circle {
1890 center,
1891 start,
1892 center_object_id,
1893 start_object_id,
1894 ..
1895 } => {
1896 match (¢er[0], ¢er[1], &start[0], &start[1]) {
1898 (
1899 UnsolvedExpr::Unknown(center_x),
1900 UnsolvedExpr::Unknown(center_y),
1901 UnsolvedExpr::Unknown(start_x),
1902 UnsolvedExpr::Unknown(start_y),
1903 ) => {
1904 let sketch_constraint = SketchConstraint {
1906 kind: constraint_kind([
1907 ConstrainablePoint2d {
1908 vars: crate::front::Point2d {
1909 x: *center_x,
1910 y: *center_y,
1911 },
1912 object_id: *center_object_id,
1913 },
1914 ConstrainablePoint2d {
1915 vars: crate::front::Point2d {
1916 x: *start_x,
1917 y: *start_y,
1918 },
1919 object_id: *start_object_id,
1920 },
1921 ]),
1922 meta: vec![source_range.into()],
1923 };
1924 Ok(sketch_constraint)
1925 }
1926 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1927 format!(
1928 "unimplemented: {}() arc or circle segment must have all sketch vars in all coordinates",
1929 function_name
1930 ),
1931 vec![source_range],
1932 ))),
1933 }
1934 }
1935 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1936 format!("{}() argument must be an arc or circle segment", function_name),
1937 vec![source_range],
1938 ))),
1939 }
1940}
1941
1942pub async fn radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1943 let segment: KclValue =
1944 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1945
1946 create_circular_radius_constraint(
1947 segment,
1948 |points| SketchConstraintKind::Radius { points },
1949 args.source_range,
1950 )
1951 .map(|constraint| KclValue::SketchConstraint {
1952 value: Box::new(constraint),
1953 })
1954}
1955
1956pub async fn diameter(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1957 let segment: KclValue =
1958 args.get_unlabeled_kw_arg("points", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
1959
1960 create_circular_radius_constraint(
1961 segment,
1962 |points| SketchConstraintKind::Diameter { points },
1963 args.source_range,
1964 )
1965 .map(|constraint| KclValue::SketchConstraint {
1966 value: Box::new(constraint),
1967 })
1968}
1969
1970pub async fn horizontal_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
1971 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
1972 "points",
1973 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
1974 exec_state,
1975 )?;
1976 let [p1, p2] = points.as_slice() else {
1977 return Err(KclError::new_semantic(KclErrorDetails::new(
1978 "must have two input points".to_owned(),
1979 vec![args.source_range],
1980 )));
1981 };
1982 match (p1, p2) {
1983 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
1984 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
1985 return Err(KclError::new_semantic(KclErrorDetails::new(
1986 "first point must be an unsolved segment".to_owned(),
1987 vec![args.source_range],
1988 )));
1989 };
1990 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
1991 return Err(KclError::new_semantic(KclErrorDetails::new(
1992 "second point must be an unsolved segment".to_owned(),
1993 vec![args.source_range],
1994 )));
1995 };
1996 match (&unsolved0.kind, &unsolved1.kind) {
1997 (
1998 UnsolvedSegmentKind::Point { position: pos0, .. },
1999 UnsolvedSegmentKind::Point { position: pos1, .. },
2000 ) => {
2001 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2004 (
2005 UnsolvedExpr::Unknown(p0_x),
2006 UnsolvedExpr::Unknown(p0_y),
2007 UnsolvedExpr::Unknown(p1_x),
2008 UnsolvedExpr::Unknown(p1_y),
2009 ) => {
2010 let sketch_constraint = SketchConstraint {
2012 kind: SketchConstraintKind::HorizontalDistance {
2013 points: [
2014 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2015 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2016 object_id: unsolved0.object_id,
2017 }),
2018 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2019 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2020 object_id: unsolved1.object_id,
2021 }),
2022 ],
2023 },
2024 meta: vec![args.source_range.into()],
2025 };
2026 Ok(KclValue::SketchConstraint {
2027 value: Box::new(sketch_constraint),
2028 })
2029 }
2030 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2031 "unimplemented: horizontalDistance() arguments must be all sketch vars in all coordinates"
2032 .to_owned(),
2033 vec![args.source_range],
2034 ))),
2035 }
2036 }
2037 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2038 "horizontalDistance() arguments must be unsolved points".to_owned(),
2039 vec![args.source_range],
2040 ))),
2041 }
2042 }
2043 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2045 if !point2d_is_origin(point2d) {
2046 return Err(KclError::new_semantic(KclErrorDetails::new(
2047 "horizontalDistance() Point2d arguments must be ORIGIN".to_owned(),
2048 vec![args.source_range],
2049 )));
2050 }
2051
2052 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2053 return Err(KclError::new_semantic(KclErrorDetails::new(
2054 "segment must be an unsolved segment".to_owned(),
2055 vec![args.source_range],
2056 )));
2057 };
2058 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2059 return Err(KclError::new_semantic(KclErrorDetails::new(
2060 "horizontalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
2061 vec![args.source_range],
2062 )));
2063 };
2064 match (&position[0], &position[1]) {
2065 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2066 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2067 vars: crate::front::Point2d {
2068 x: *point_x,
2069 y: *point_y,
2070 },
2071 object_id: unsolved.object_id,
2072 });
2073 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
2074 [point, ConstrainablePoint2dOrOrigin::Origin]
2075 } else {
2076 [ConstrainablePoint2dOrOrigin::Origin, point]
2077 };
2078 Ok(KclValue::SketchConstraint {
2079 value: Box::new(SketchConstraint {
2080 kind: SketchConstraintKind::HorizontalDistance { points },
2081 meta: vec![args.source_range.into()],
2082 }),
2083 })
2084 }
2085 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2086 "unimplemented: horizontalDistance() point arguments must be sketch vars in all coordinates"
2087 .to_owned(),
2088 vec![args.source_range],
2089 ))),
2090 }
2091 }
2092 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2093 "horizontalDistance() arguments must be point segments or ORIGIN".to_owned(),
2094 vec![args.source_range],
2095 ))),
2096 }
2097}
2098
2099pub async fn vertical_distance(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2100 let points: Vec<KclValue> = args.get_unlabeled_kw_arg(
2101 "points",
2102 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2103 exec_state,
2104 )?;
2105 let [p1, p2] = points.as_slice() else {
2106 return Err(KclError::new_semantic(KclErrorDetails::new(
2107 "must have two input points".to_owned(),
2108 vec![args.source_range],
2109 )));
2110 };
2111 match (p1, p2) {
2112 (KclValue::Segment { value: seg0 }, KclValue::Segment { value: seg1 }) => {
2113 let SegmentRepr::Unsolved { segment: unsolved0 } = &seg0.repr else {
2114 return Err(KclError::new_semantic(KclErrorDetails::new(
2115 "first point must be an unsolved segment".to_owned(),
2116 vec![args.source_range],
2117 )));
2118 };
2119 let SegmentRepr::Unsolved { segment: unsolved1 } = &seg1.repr else {
2120 return Err(KclError::new_semantic(KclErrorDetails::new(
2121 "second point must be an unsolved segment".to_owned(),
2122 vec![args.source_range],
2123 )));
2124 };
2125 match (&unsolved0.kind, &unsolved1.kind) {
2126 (
2127 UnsolvedSegmentKind::Point { position: pos0, .. },
2128 UnsolvedSegmentKind::Point { position: pos1, .. },
2129 ) => {
2130 match (&pos0[0], &pos0[1], &pos1[0], &pos1[1]) {
2133 (
2134 UnsolvedExpr::Unknown(p0_x),
2135 UnsolvedExpr::Unknown(p0_y),
2136 UnsolvedExpr::Unknown(p1_x),
2137 UnsolvedExpr::Unknown(p1_y),
2138 ) => {
2139 let sketch_constraint = SketchConstraint {
2141 kind: SketchConstraintKind::VerticalDistance {
2142 points: [
2143 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2144 vars: crate::front::Point2d { x: *p0_x, y: *p0_y },
2145 object_id: unsolved0.object_id,
2146 }),
2147 ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2148 vars: crate::front::Point2d { x: *p1_x, y: *p1_y },
2149 object_id: unsolved1.object_id,
2150 }),
2151 ],
2152 },
2153 meta: vec![args.source_range.into()],
2154 };
2155 Ok(KclValue::SketchConstraint {
2156 value: Box::new(sketch_constraint),
2157 })
2158 }
2159 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2160 "unimplemented: verticalDistance() arguments must be all sketch vars in all coordinates"
2161 .to_owned(),
2162 vec![args.source_range],
2163 ))),
2164 }
2165 }
2166 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2167 "verticalDistance() arguments must be unsolved points".to_owned(),
2168 vec![args.source_range],
2169 ))),
2170 }
2171 }
2172 (KclValue::Segment { value: seg }, point2d) | (point2d, KclValue::Segment { value: seg }) => {
2173 if !point2d_is_origin(point2d) {
2174 return Err(KclError::new_semantic(KclErrorDetails::new(
2175 "verticalDistance() Point2d arguments must be ORIGIN".to_owned(),
2176 vec![args.source_range],
2177 )));
2178 }
2179
2180 let SegmentRepr::Unsolved { segment: unsolved } = &seg.repr else {
2181 return Err(KclError::new_semantic(KclErrorDetails::new(
2182 "segment must be an unsolved segment".to_owned(),
2183 vec![args.source_range],
2184 )));
2185 };
2186 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
2187 return Err(KclError::new_semantic(KclErrorDetails::new(
2188 "verticalDistance() arguments must be unsolved points or ORIGIN".to_owned(),
2189 vec![args.source_range],
2190 )));
2191 };
2192 match (&position[0], &position[1]) {
2193 (UnsolvedExpr::Unknown(point_x), UnsolvedExpr::Unknown(point_y)) => {
2194 let point = ConstrainablePoint2dOrOrigin::Point(ConstrainablePoint2d {
2195 vars: crate::front::Point2d {
2196 x: *point_x,
2197 y: *point_y,
2198 },
2199 object_id: unsolved.object_id,
2200 });
2201 let points = if matches!((p1, p2), (KclValue::Segment { .. }, _)) {
2202 [point, ConstrainablePoint2dOrOrigin::Origin]
2203 } else {
2204 [ConstrainablePoint2dOrOrigin::Origin, point]
2205 };
2206 Ok(KclValue::SketchConstraint {
2207 value: Box::new(SketchConstraint {
2208 kind: SketchConstraintKind::VerticalDistance { points },
2209 meta: vec![args.source_range.into()],
2210 }),
2211 })
2212 }
2213 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2214 "unimplemented: verticalDistance() point arguments must be sketch vars in all coordinates"
2215 .to_owned(),
2216 vec![args.source_range],
2217 ))),
2218 }
2219 }
2220 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2221 "verticalDistance() arguments must be point segments or ORIGIN".to_owned(),
2222 vec![args.source_range],
2223 ))),
2224 }
2225}
2226
2227pub async fn equal_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2228 #[derive(Clone, Copy)]
2229 struct ConstrainableLine {
2230 solver_line: DatumLineSegment,
2231 #[cfg(feature = "artifact-graph")]
2232 object_id: ObjectId,
2233 }
2234
2235 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2236 "lines",
2237 &RuntimeType::Array(
2238 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
2239 ArrayLen::Minimum(2),
2240 ),
2241 exec_state,
2242 )?;
2243 let range = args.source_range;
2244 let constrainable_lines: Vec<ConstrainableLine> = lines
2245 .iter()
2246 .map(|line| {
2247 let KclValue::Segment { value: segment } = line else {
2248 return Err(KclError::new_semantic(KclErrorDetails::new(
2249 "line argument must be a Segment".to_owned(),
2250 vec![args.source_range],
2251 )));
2252 };
2253 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2254 return Err(KclError::new_internal(KclErrorDetails::new(
2255 "line must be an unsolved Segment".to_owned(),
2256 vec![args.source_range],
2257 )));
2258 };
2259 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
2260 return Err(KclError::new_semantic(KclErrorDetails::new(
2261 "line argument must be a line, no other type of Segment".to_owned(),
2262 vec![args.source_range],
2263 )));
2264 };
2265 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
2266 return Err(KclError::new_semantic(KclErrorDetails::new(
2267 "line's start x coordinate must be a var".to_owned(),
2268 vec![args.source_range],
2269 )));
2270 };
2271 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
2272 return Err(KclError::new_semantic(KclErrorDetails::new(
2273 "line's start y coordinate must be a var".to_owned(),
2274 vec![args.source_range],
2275 )));
2276 };
2277 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
2278 return Err(KclError::new_semantic(KclErrorDetails::new(
2279 "line's end x coordinate must be a var".to_owned(),
2280 vec![args.source_range],
2281 )));
2282 };
2283 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
2284 return Err(KclError::new_semantic(KclErrorDetails::new(
2285 "line's end y coordinate must be a var".to_owned(),
2286 vec![args.source_range],
2287 )));
2288 };
2289
2290 let solver_line_p0 =
2291 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
2292 let solver_line_p1 =
2293 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
2294
2295 Ok(ConstrainableLine {
2296 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
2297 #[cfg(feature = "artifact-graph")]
2298 object_id: unsolved.object_id,
2299 })
2300 })
2301 .collect::<Result<_, _>>()?;
2302
2303 #[cfg(feature = "artifact-graph")]
2304 let constraint_id = exec_state.next_object_id();
2305 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2307 return Err(KclError::new_semantic(KclErrorDetails::new(
2308 "equalLength() can only be used inside a sketch block".to_owned(),
2309 vec![args.source_range],
2310 )));
2311 };
2312 let first_line = constrainable_lines[0];
2313 for line in constrainable_lines.iter().skip(1) {
2314 sketch_state.solver_constraints.push(SolverConstraint::LinesEqualLength(
2315 first_line.solver_line,
2316 line.solver_line,
2317 ));
2318 }
2319 #[cfg(feature = "artifact-graph")]
2320 {
2321 let constraint = crate::front::Constraint::LinesEqualLength(LinesEqualLength {
2322 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
2323 });
2324 sketch_state.sketch_constraints.push(constraint_id);
2325 track_constraint(constraint_id, constraint, exec_state, &args);
2326 }
2327 Ok(KclValue::none())
2328}
2329
2330fn datum_point(coords: [SketchVarId; 2], range: crate::SourceRange) -> Result<DatumPoint, KclError> {
2331 Ok(DatumPoint::new_xy(
2332 coords[0].to_constraint_id(range)?,
2333 coords[1].to_constraint_id(range)?,
2334 ))
2335}
2336
2337fn sketch_var_initial_value(
2338 sketch_vars: &[KclValue],
2339 id: SketchVarId,
2340 exec_state: &mut ExecState,
2341 range: crate::SourceRange,
2342) -> Result<f64, KclError> {
2343 sketch_vars
2344 .get(id.0)
2345 .and_then(KclValue::as_sketch_var)
2346 .map(|sketch_var| {
2347 sketch_var
2348 .initial_value_to_solver_units(exec_state, range, "equalRadius() hidden shared radius initial value")
2349 .map(|value| value.n)
2350 })
2351 .transpose()?
2352 .ok_or_else(|| {
2353 KclError::new_internal(KclErrorDetails::new(
2354 format!("Missing sketch variable initial value for id {}", id.0),
2355 vec![range],
2356 ))
2357 })
2358}
2359
2360fn radius_guess(
2361 sketch_vars: &[KclValue],
2362 center: [SketchVarId; 2],
2363 point: [SketchVarId; 2],
2364 exec_state: &mut ExecState,
2365 range: crate::SourceRange,
2366) -> Result<f64, KclError> {
2367 let dx = sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?
2368 - sketch_var_initial_value(sketch_vars, center[0], exec_state, range)?;
2369 let dy = sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?
2370 - sketch_var_initial_value(sketch_vars, center[1], exec_state, range)?;
2371 Ok(libm::hypot(dx, dy))
2372}
2373
2374pub async fn equal_radius(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2375 #[derive(Debug, Clone, Copy)]
2376 struct RadiusInputVars {
2377 center: [SketchVarId; 2],
2378 start: [SketchVarId; 2],
2379 end: Option<[SketchVarId; 2]>,
2380 }
2381
2382 #[derive(Debug, Clone, Copy)]
2383 enum EqualRadiusInput {
2384 Radius(RadiusInputVars),
2385 }
2386
2387 fn extract_equal_radius_input(
2388 segment_value: &KclValue,
2389 range: crate::SourceRange,
2390 ) -> Result<(EqualRadiusInput, ObjectId), KclError> {
2391 let KclValue::Segment { value: segment } = segment_value else {
2392 return Err(KclError::new_semantic(KclErrorDetails::new(
2393 format!(
2394 "equalRadius() arguments must be segments but found {}",
2395 segment_value.human_friendly_type()
2396 ),
2397 vec![range],
2398 )));
2399 };
2400 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2401 return Err(KclError::new_semantic(KclErrorDetails::new(
2402 "equalRadius() arguments must be unsolved segments".to_owned(),
2403 vec![range],
2404 )));
2405 };
2406 match &unsolved.kind {
2407 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
2408 let (
2409 UnsolvedExpr::Unknown(center_x),
2410 UnsolvedExpr::Unknown(center_y),
2411 UnsolvedExpr::Unknown(start_x),
2412 UnsolvedExpr::Unknown(start_y),
2413 UnsolvedExpr::Unknown(end_x),
2414 UnsolvedExpr::Unknown(end_y),
2415 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
2416 else {
2417 return Err(KclError::new_semantic(KclErrorDetails::new(
2418 "arc center/start/end coordinates must be sketch vars for equalRadius()".to_owned(),
2419 vec![range],
2420 )));
2421 };
2422 Ok((
2423 EqualRadiusInput::Radius(RadiusInputVars {
2424 center: [*center_x, *center_y],
2425 start: [*start_x, *start_y],
2426 end: Some([*end_x, *end_y]),
2427 }),
2428 unsolved.object_id,
2429 ))
2430 }
2431 UnsolvedSegmentKind::Circle { center, start, .. } => {
2432 let (
2433 UnsolvedExpr::Unknown(center_x),
2434 UnsolvedExpr::Unknown(center_y),
2435 UnsolvedExpr::Unknown(start_x),
2436 UnsolvedExpr::Unknown(start_y),
2437 ) = (¢er[0], ¢er[1], &start[0], &start[1])
2438 else {
2439 return Err(KclError::new_semantic(KclErrorDetails::new(
2440 "circle center/start coordinates must be sketch vars for equalRadius()".to_owned(),
2441 vec![range],
2442 )));
2443 };
2444 Ok((
2445 EqualRadiusInput::Radius(RadiusInputVars {
2446 center: [*center_x, *center_y],
2447 start: [*start_x, *start_y],
2448 end: None,
2449 }),
2450 unsolved.object_id,
2451 ))
2452 }
2453 other => Err(KclError::new_semantic(KclErrorDetails::new(
2454 format!(
2455 "equalRadius() currently supports only arc and circle segments, you provided {}",
2456 other.human_friendly_kind_with_article()
2457 ),
2458 vec![range],
2459 ))),
2460 }
2461 }
2462
2463 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
2464 "input",
2465 &RuntimeType::Array(
2466 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
2467 ArrayLen::Minimum(2),
2468 ),
2469 exec_state,
2470 )?;
2471 let range = args.source_range;
2472
2473 let extracted_input = input
2474 .iter()
2475 .map(|segment_value| extract_equal_radius_input(segment_value, range))
2476 .collect::<Result<Vec<_>, _>>()?;
2477 let radius_inputs: Vec<RadiusInputVars> = extracted_input
2478 .iter()
2479 .map(|(equal_radius_input, _)| match equal_radius_input {
2480 EqualRadiusInput::Radius(radius_input) => *radius_input,
2481 })
2482 .collect();
2483 #[cfg(feature = "artifact-graph")]
2484 let input_object_ids: Vec<ObjectId> = extracted_input.iter().map(|(_, object_id)| *object_id).collect();
2485
2486 let sketch_var_ty = solver_numeric_type(exec_state);
2487 #[cfg(feature = "artifact-graph")]
2488 let constraint_id = exec_state.next_object_id();
2489
2490 let sketch_vars = {
2491 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2492 return Err(KclError::new_semantic(KclErrorDetails::new(
2493 "equalRadius() can only be used inside a sketch block".to_owned(),
2494 vec![range],
2495 )));
2496 };
2497 sketch_state.sketch_vars.clone()
2498 };
2499
2500 let radius_initial_value = radius_guess(
2501 &sketch_vars,
2502 radius_inputs[0].center,
2503 radius_inputs[0].start,
2504 exec_state,
2505 range,
2506 )?;
2507
2508 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2509 return Err(KclError::new_semantic(KclErrorDetails::new(
2510 "equalRadius() can only be used inside a sketch block".to_owned(),
2511 vec![range],
2512 )));
2513 };
2514 let radius_id = sketch_state.next_sketch_var_id();
2515 sketch_state.sketch_vars.push(KclValue::SketchVar {
2516 value: Box::new(crate::execution::SketchVar {
2517 id: radius_id,
2518 initial_value: radius_initial_value,
2519 ty: sketch_var_ty,
2520 meta: vec![],
2521 }),
2522 });
2523 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
2524
2525 for radius_input in radius_inputs {
2526 let center = datum_point(radius_input.center, range)?;
2527 let start = datum_point(radius_input.start, range)?;
2528 sketch_state
2529 .solver_constraints
2530 .push(SolverConstraint::DistanceVar(start, center, radius));
2531 if let Some(end) = radius_input.end {
2532 let end = datum_point(end, range)?;
2533 sketch_state
2534 .solver_constraints
2535 .push(SolverConstraint::DistanceVar(end, center, radius));
2536 }
2537 }
2538
2539 #[cfg(feature = "artifact-graph")]
2540 {
2541 let constraint = crate::front::Constraint::EqualRadius(EqualRadius {
2542 input: input_object_ids,
2543 });
2544 sketch_state.sketch_constraints.push(constraint_id);
2545 track_constraint(constraint_id, constraint, exec_state, &args);
2546 }
2547
2548 Ok(KclValue::none())
2549}
2550
2551pub async fn tangent(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2552 #[derive(Debug, Clone, Copy)]
2553 struct ConstrainableLineVars {
2554 start: [SketchVarId; 2],
2555 end: [SketchVarId; 2],
2556 }
2557
2558 #[derive(Debug, Clone, Copy)]
2559 struct ConstrainableCircularVars {
2560 center: [SketchVarId; 2],
2561 start: [SketchVarId; 2],
2562 end: Option<[SketchVarId; 2]>,
2563 }
2564
2565 #[derive(Debug, Clone, Copy)]
2566 enum TangentInput {
2567 Line(ConstrainableLineVars),
2568 Circular(ConstrainableCircularVars),
2569 }
2570
2571 fn extract_tangent_input(
2572 segment_value: &KclValue,
2573 range: crate::SourceRange,
2574 ) -> Result<(TangentInput, ObjectId), KclError> {
2575 let KclValue::Segment { value: segment } = segment_value else {
2576 return Err(KclError::new_semantic(KclErrorDetails::new(
2577 "tangent() arguments must be segments".to_owned(),
2578 vec![range],
2579 )));
2580 };
2581 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2582 return Err(KclError::new_semantic(KclErrorDetails::new(
2583 "tangent() arguments must be unsolved segments".to_owned(),
2584 vec![range],
2585 )));
2586 };
2587 match &unsolved.kind {
2588 UnsolvedSegmentKind::Line { start, end, .. } => {
2589 let (
2590 UnsolvedExpr::Unknown(start_x),
2591 UnsolvedExpr::Unknown(start_y),
2592 UnsolvedExpr::Unknown(end_x),
2593 UnsolvedExpr::Unknown(end_y),
2594 ) = (&start[0], &start[1], &end[0], &end[1])
2595 else {
2596 return Err(KclError::new_semantic(KclErrorDetails::new(
2597 "line coordinates must be sketch vars for tangent()".to_owned(),
2598 vec![range],
2599 )));
2600 };
2601 Ok((
2602 TangentInput::Line(ConstrainableLineVars {
2603 start: [*start_x, *start_y],
2604 end: [*end_x, *end_y],
2605 }),
2606 unsolved.object_id,
2607 ))
2608 }
2609 UnsolvedSegmentKind::Arc { center, start, end, .. } => {
2610 let (
2611 UnsolvedExpr::Unknown(center_x),
2612 UnsolvedExpr::Unknown(center_y),
2613 UnsolvedExpr::Unknown(start_x),
2614 UnsolvedExpr::Unknown(start_y),
2615 UnsolvedExpr::Unknown(end_x),
2616 UnsolvedExpr::Unknown(end_y),
2617 ) = (¢er[0], ¢er[1], &start[0], &start[1], &end[0], &end[1])
2618 else {
2619 return Err(KclError::new_semantic(KclErrorDetails::new(
2620 "arc center/start/end coordinates must be sketch vars for tangent()".to_owned(),
2621 vec![range],
2622 )));
2623 };
2624 Ok((
2625 TangentInput::Circular(ConstrainableCircularVars {
2626 center: [*center_x, *center_y],
2627 start: [*start_x, *start_y],
2628 end: Some([*end_x, *end_y]),
2629 }),
2630 unsolved.object_id,
2631 ))
2632 }
2633 UnsolvedSegmentKind::Circle { center, start, .. } => {
2634 let (
2635 UnsolvedExpr::Unknown(center_x),
2636 UnsolvedExpr::Unknown(center_y),
2637 UnsolvedExpr::Unknown(start_x),
2638 UnsolvedExpr::Unknown(start_y),
2639 ) = (¢er[0], ¢er[1], &start[0], &start[1])
2640 else {
2641 return Err(KclError::new_semantic(KclErrorDetails::new(
2642 "circle center/start coordinates must be sketch vars for tangent()".to_owned(),
2643 vec![range],
2644 )));
2645 };
2646 Ok((
2647 TangentInput::Circular(ConstrainableCircularVars {
2648 center: [*center_x, *center_y],
2649 start: [*start_x, *start_y],
2650 end: None,
2651 }),
2652 unsolved.object_id,
2653 ))
2654 }
2655 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2656 "tangent() supports only line, arc, and circle segments".to_owned(),
2657 vec![range],
2658 ))),
2659 }
2660 }
2661
2662 fn point_initial_position(
2663 sketch_vars: &[KclValue],
2664 point: [SketchVarId; 2],
2665 exec_state: &mut ExecState,
2666 range: crate::SourceRange,
2667 ) -> Result<[f64; 2], KclError> {
2668 Ok([
2669 sketch_var_initial_value(sketch_vars, point[0], exec_state, range)?,
2670 sketch_var_initial_value(sketch_vars, point[1], exec_state, range)?,
2671 ])
2672 }
2673
2674 fn canonicalize_line_for_tangent(
2675 sketch_vars: &[KclValue],
2676 line: ConstrainableLineVars,
2677 arc_center: [SketchVarId; 2],
2678 exec_state: &mut ExecState,
2679 range: crate::SourceRange,
2680 ) -> Result<ConstrainableLineVars, KclError> {
2681 let [sx, sy] = point_initial_position(sketch_vars, line.start, exec_state, range)?;
2682 let [ex, ey] = point_initial_position(sketch_vars, line.end, exec_state, range)?;
2683 let [cx, cy] = point_initial_position(sketch_vars, arc_center, exec_state, range)?;
2684
2685 let signed_side = (ex - sx) * (cy - sy) - (ey - sy) * (cx - sx);
2689 if signed_side < -1e-9 {
2690 Ok(ConstrainableLineVars {
2691 start: line.end,
2692 end: line.start,
2693 })
2694 } else {
2695 Ok(line)
2696 }
2697 }
2698
2699 let input: Vec<KclValue> = args.get_unlabeled_kw_arg(
2700 "input",
2701 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
2702 exec_state,
2703 )?;
2704 let [item0, item1]: [KclValue; 2] = input.try_into().map_err(|_| {
2705 KclError::new_semantic(KclErrorDetails::new(
2706 "tangent() requires exactly 2 input segments".to_owned(),
2707 vec![args.source_range],
2708 ))
2709 })?;
2710 let range = args.source_range;
2711 let (input0, input0_object_id) = extract_tangent_input(&item0, range)?;
2712 let (input1, input1_object_id) = extract_tangent_input(&item1, range)?;
2713 #[cfg(not(feature = "artifact-graph"))]
2714 let _ = (input0_object_id, input1_object_id);
2715
2716 enum TangentCase {
2717 LineCircular(ConstrainableLineVars, ConstrainableCircularVars),
2718 CircularCircular(ConstrainableCircularVars, ConstrainableCircularVars),
2719 }
2720 let tangent_case = match (input0, input1) {
2721 (TangentInput::Line(line), TangentInput::Circular(circular))
2722 | (TangentInput::Circular(circular), TangentInput::Line(line)) => TangentCase::LineCircular(line, circular),
2723 (TangentInput::Circular(circular0), TangentInput::Circular(circular1)) => {
2724 TangentCase::CircularCircular(circular0, circular1)
2725 }
2726 (TangentInput::Line(_), TangentInput::Line(_)) => {
2727 return Err(KclError::new_semantic(KclErrorDetails::new(
2728 "tangent() does not support Line/Line. Tangency requires at least one circular segment.".to_owned(),
2729 vec![range],
2730 )));
2731 }
2732 };
2733
2734 let sketch_var_ty = solver_numeric_type(exec_state);
2735 #[cfg(feature = "artifact-graph")]
2736 let constraint_id = exec_state.next_object_id();
2737
2738 let sketch_vars = {
2739 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2740 return Err(KclError::new_semantic(KclErrorDetails::new(
2741 "tangent() can only be used inside a sketch block".to_owned(),
2742 vec![range],
2743 )));
2744 };
2745 sketch_state.sketch_vars.clone()
2746 };
2747
2748 match tangent_case {
2750 TangentCase::LineCircular(line, circular) => {
2751 let canonical_line = canonicalize_line_for_tangent(&sketch_vars, line, circular.center, exec_state, range)?;
2752 let line_p0 = datum_point(canonical_line.start, range)?;
2753 let line_p1 = datum_point(canonical_line.end, range)?;
2754 let line_datum = DatumLineSegment::new(line_p0, line_p1);
2755
2756 let center = datum_point(circular.center, range)?;
2757 let circular_start = datum_point(circular.start, range)?;
2758 let circular_end = circular.end.map(|end| datum_point(end, range)).transpose()?;
2759 let radius_initial_value = radius_guess(&sketch_vars, circular.center, circular.start, exec_state, range)?;
2760 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2761 return Err(KclError::new_semantic(KclErrorDetails::new(
2762 "tangent() can only be used inside a sketch block".to_owned(),
2763 vec![range],
2764 )));
2765 };
2766 let radius_id = sketch_state.next_sketch_var_id();
2767 sketch_state.sketch_vars.push(KclValue::SketchVar {
2768 value: Box::new(crate::execution::SketchVar {
2769 id: radius_id,
2770 initial_value: radius_initial_value,
2771 ty: sketch_var_ty,
2772 meta: vec![],
2773 }),
2774 });
2775 let radius = DatumDistance::new(radius_id.to_constraint_id(range)?);
2776 let circle = DatumCircle { center, radius };
2777
2778 sketch_state
2785 .solver_constraints
2786 .push(SolverConstraint::DistanceVar(circular_start, center, radius));
2787 if let Some(circular_end) = circular_end {
2788 sketch_state
2789 .solver_constraints
2790 .push(SolverConstraint::DistanceVar(circular_end, center, radius));
2791 }
2792 sketch_state
2793 .solver_constraints
2794 .push(SolverConstraint::LineTangentToCircle(line_datum, circle));
2795 }
2796 TangentCase::CircularCircular(circular0, circular1) => {
2797 let center0 = datum_point(circular0.center, range)?;
2798 let start0 = datum_point(circular0.start, range)?;
2799 let end0 = circular0.end.map(|end| datum_point(end, range)).transpose()?;
2800 let radius0_initial_value =
2801 radius_guess(&sketch_vars, circular0.center, circular0.start, exec_state, range)?;
2802 let center1 = datum_point(circular1.center, range)?;
2803 let start1 = datum_point(circular1.start, range)?;
2804 let end1 = circular1.end.map(|end| datum_point(end, range)).transpose()?;
2805 let radius1_initial_value =
2806 radius_guess(&sketch_vars, circular1.center, circular1.start, exec_state, range)?;
2807 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2808 return Err(KclError::new_semantic(KclErrorDetails::new(
2809 "tangent() can only be used inside a sketch block".to_owned(),
2810 vec![range],
2811 )));
2812 };
2813 let radius0_id = sketch_state.next_sketch_var_id();
2814 sketch_state.sketch_vars.push(KclValue::SketchVar {
2815 value: Box::new(crate::execution::SketchVar {
2816 id: radius0_id,
2817 initial_value: radius0_initial_value,
2818 ty: sketch_var_ty,
2819 meta: vec![],
2820 }),
2821 });
2822 let radius0 = DatumDistance::new(radius0_id.to_constraint_id(range)?);
2823 let circle0 = DatumCircle {
2824 center: center0,
2825 radius: radius0,
2826 };
2827
2828 let radius1_id = sketch_state.next_sketch_var_id();
2829 sketch_state.sketch_vars.push(KclValue::SketchVar {
2830 value: Box::new(crate::execution::SketchVar {
2831 id: radius1_id,
2832 initial_value: radius1_initial_value,
2833 ty: sketch_var_ty,
2834 meta: vec![],
2835 }),
2836 });
2837 let radius1 = DatumDistance::new(radius1_id.to_constraint_id(range)?);
2838 let circle1 = DatumCircle {
2839 center: center1,
2840 radius: radius1,
2841 };
2842
2843 sketch_state
2848 .solver_constraints
2849 .push(SolverConstraint::DistanceVar(start0, center0, radius0));
2850 if let Some(end0) = end0 {
2851 sketch_state
2852 .solver_constraints
2853 .push(SolverConstraint::DistanceVar(end0, center0, radius0));
2854 }
2855 sketch_state
2856 .solver_constraints
2857 .push(SolverConstraint::DistanceVar(start1, center1, radius1));
2858 if let Some(end1) = end1 {
2859 sketch_state
2860 .solver_constraints
2861 .push(SolverConstraint::DistanceVar(end1, center1, radius1));
2862 }
2863 sketch_state
2864 .solver_constraints
2865 .push(SolverConstraint::CircleTangentToCircle(circle0, circle1));
2866 }
2867 }
2868
2869 #[cfg(feature = "artifact-graph")]
2870 {
2871 let constraint = crate::front::Constraint::Tangent(Tangent {
2872 input: vec![input0_object_id, input1_object_id],
2873 });
2874 let Some(sketch_state) = exec_state.sketch_block_mut() else {
2875 return Err(KclError::new_semantic(KclErrorDetails::new(
2876 "tangent() can only be used inside a sketch block".to_owned(),
2877 vec![range],
2878 )));
2879 };
2880 sketch_state.sketch_constraints.push(constraint_id);
2881 track_constraint(constraint_id, constraint, exec_state, &args);
2882 }
2883
2884 Ok(KclValue::none())
2885}
2886
2887#[derive(Debug, Clone, Copy)]
2888pub(crate) enum LinesAtAngleKind {
2889 Parallel,
2890 Perpendicular,
2891}
2892
2893impl LinesAtAngleKind {
2894 pub fn to_function_name(self) -> &'static str {
2895 match self {
2896 LinesAtAngleKind::Parallel => "parallel",
2897 LinesAtAngleKind::Perpendicular => "perpendicular",
2898 }
2899 }
2900
2901 fn to_solver_angle(self) -> ezpz::datatypes::AngleKind {
2902 match self {
2903 LinesAtAngleKind::Parallel => ezpz::datatypes::AngleKind::Parallel,
2904 LinesAtAngleKind::Perpendicular => ezpz::datatypes::AngleKind::Perpendicular,
2905 }
2906 }
2907
2908 #[cfg(feature = "artifact-graph")]
2909 fn constraint(&self, lines: Vec<ObjectId>) -> Constraint {
2910 match self {
2911 LinesAtAngleKind::Parallel => Constraint::Parallel(Parallel { lines }),
2912 LinesAtAngleKind::Perpendicular => Constraint::Perpendicular(Perpendicular { lines }),
2913 }
2914 }
2915}
2916
2917#[expect(unused)]
2919fn into_kcmc_angle(angle: ezpz::datatypes::Angle) -> kcmc::shared::Angle {
2920 kcmc::shared::Angle::from_degrees(angle.to_degrees())
2921}
2922
2923#[expect(unused)]
2925fn into_ezpz_angle(angle: kcmc::shared::Angle) -> ezpz::datatypes::Angle {
2926 ezpz::datatypes::Angle::from_degrees(angle.to_degrees())
2927}
2928
2929pub async fn parallel(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
2930 #[derive(Clone, Copy)]
2931 struct ConstrainableLine {
2932 solver_line: DatumLineSegment,
2933 #[cfg(feature = "artifact-graph")]
2934 object_id: ObjectId,
2935 }
2936
2937 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
2938 "lines",
2939 &RuntimeType::Array(
2940 Box::new(RuntimeType::Primitive(PrimitiveType::Any)),
2941 ArrayLen::Minimum(2),
2942 ),
2943 exec_state,
2944 )?;
2945 let range = args.source_range;
2946 let constrainable_lines: Vec<ConstrainableLine> = lines
2947 .iter()
2948 .map(|line| {
2949 let KclValue::Segment { value: segment } = line else {
2950 return Err(KclError::new_semantic(KclErrorDetails::new(
2951 "line argument must be a Segment".to_owned(),
2952 vec![args.source_range],
2953 )));
2954 };
2955 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
2956 return Err(KclError::new_internal(KclErrorDetails::new(
2957 "line must be an unsolved Segment".to_owned(),
2958 vec![args.source_range],
2959 )));
2960 };
2961 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
2962 return Err(KclError::new_semantic(KclErrorDetails::new(
2963 "line argument must be a line, no other type of Segment".to_owned(),
2964 vec![args.source_range],
2965 )));
2966 };
2967 let UnsolvedExpr::Unknown(line_p0_x) = &start[0] else {
2968 return Err(KclError::new_semantic(KclErrorDetails::new(
2969 "line's start x coordinate must be a var".to_owned(),
2970 vec![args.source_range],
2971 )));
2972 };
2973 let UnsolvedExpr::Unknown(line_p0_y) = &start[1] else {
2974 return Err(KclError::new_semantic(KclErrorDetails::new(
2975 "line's start y coordinate must be a var".to_owned(),
2976 vec![args.source_range],
2977 )));
2978 };
2979 let UnsolvedExpr::Unknown(line_p1_x) = &end[0] else {
2980 return Err(KclError::new_semantic(KclErrorDetails::new(
2981 "line's end x coordinate must be a var".to_owned(),
2982 vec![args.source_range],
2983 )));
2984 };
2985 let UnsolvedExpr::Unknown(line_p1_y) = &end[1] else {
2986 return Err(KclError::new_semantic(KclErrorDetails::new(
2987 "line's end y coordinate must be a var".to_owned(),
2988 vec![args.source_range],
2989 )));
2990 };
2991
2992 let solver_line_p0 =
2993 DatumPoint::new_xy(line_p0_x.to_constraint_id(range)?, line_p0_y.to_constraint_id(range)?);
2994 let solver_line_p1 =
2995 DatumPoint::new_xy(line_p1_x.to_constraint_id(range)?, line_p1_y.to_constraint_id(range)?);
2996
2997 Ok(ConstrainableLine {
2998 solver_line: DatumLineSegment::new(solver_line_p0, solver_line_p1),
2999 #[cfg(feature = "artifact-graph")]
3000 object_id: unsolved.object_id,
3001 })
3002 })
3003 .collect::<Result<_, _>>()?;
3004
3005 #[cfg(feature = "artifact-graph")]
3006 let constraint_id = exec_state.next_object_id();
3007 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3008 return Err(KclError::new_semantic(KclErrorDetails::new(
3009 "parallel() can only be used inside a sketch block".to_owned(),
3010 vec![args.source_range],
3011 )));
3012 };
3013
3014 let n = constrainable_lines.len();
3015 let mut constrainable_lines_iter = constrainable_lines.iter();
3016 let first_line = constrainable_lines_iter
3017 .next()
3018 .ok_or(KclError::new_semantic(KclErrorDetails::new(
3019 format!("parallel() requires at least 2 lines, but you provided {}", n),
3020 vec![args.source_range],
3021 )))?;
3022 for line in constrainable_lines_iter {
3023 sketch_state.solver_constraints.push(SolverConstraint::LinesAtAngle(
3024 first_line.solver_line,
3025 line.solver_line,
3026 AngleKind::Parallel,
3027 ));
3028 }
3029 #[cfg(feature = "artifact-graph")]
3030 {
3031 let constraint = Constraint::Parallel(Parallel {
3032 lines: constrainable_lines.iter().map(|line| line.object_id).collect(),
3033 });
3034 sketch_state.sketch_constraints.push(constraint_id);
3035 track_constraint(constraint_id, constraint, exec_state, &args);
3036 }
3037 Ok(KclValue::none())
3038}
3039
3040pub async fn perpendicular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3041 lines_at_angle(LinesAtAngleKind::Perpendicular, exec_state, args).await
3042}
3043
3044#[derive(Debug, Clone, Copy)]
3046enum AxisConstraintKind {
3047 Horizontal,
3048 Vertical,
3049}
3050
3051impl AxisConstraintKind {
3052 fn function_name(self) -> &'static str {
3054 match self {
3055 AxisConstraintKind::Horizontal => "horizontal",
3056 AxisConstraintKind::Vertical => "vertical",
3057 }
3058 }
3059
3060 fn line_constraint(self, line: DatumLineSegment) -> SolverConstraint {
3062 match self {
3063 AxisConstraintKind::Horizontal => SolverConstraint::Horizontal(line),
3064 AxisConstraintKind::Vertical => SolverConstraint::Vertical(line),
3065 }
3066 }
3067
3068 fn point_pair_constraint(self, p0: DatumPoint, p1: DatumPoint) -> SolverConstraint {
3070 match self {
3071 AxisConstraintKind::Horizontal => SolverConstraint::VerticalDistance(p1, p0, 0.0),
3073 AxisConstraintKind::Vertical => SolverConstraint::HorizontalDistance(p1, p0, 0.0),
3075 }
3076 }
3077
3078 fn constraint_aligning_point_to_constant(self, p0: DatumPoint, fixed_point: (f64, f64)) -> SolverConstraint {
3080 match self {
3081 AxisConstraintKind::Horizontal => SolverConstraint::Fixed(p0.y_id, fixed_point.1),
3082 AxisConstraintKind::Vertical => SolverConstraint::Fixed(p0.x_id, fixed_point.0),
3083 }
3084 }
3085
3086 #[cfg(feature = "artifact-graph")]
3087 fn line_artifact_constraint(self, line: ObjectId) -> Constraint {
3088 match self {
3089 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Line { line }),
3090 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Line { line }),
3091 }
3092 }
3093
3094 #[cfg(feature = "artifact-graph")]
3095 fn point_artifact_constraint(self, points: Vec<ConstraintSegment>) -> Constraint {
3096 match self {
3097 AxisConstraintKind::Horizontal => Constraint::Horizontal(Horizontal::Points { points }),
3098 AxisConstraintKind::Vertical => Constraint::Vertical(Vertical::Points { points }),
3099 }
3100 }
3101}
3102
3103#[derive(Debug, Clone, Copy)]
3106struct AxisLineVars {
3107 start: [SketchVarId; 2],
3108 end: [SketchVarId; 2],
3109 object_id: ObjectId,
3110}
3111
3112fn extract_axis_line_vars(
3113 segment: &AbstractSegment,
3114 kind: AxisConstraintKind,
3115 source_range: crate::SourceRange,
3116) -> Result<AxisLineVars, KclError> {
3117 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3118 return Err(KclError::new_internal(KclErrorDetails::new(
3119 "line must be an unsolved Segment".to_owned(),
3120 vec![source_range],
3121 )));
3122 };
3123 let UnsolvedSegmentKind::Line { start, end, .. } = &unsolved.kind else {
3124 return Err(KclError::new_semantic(KclErrorDetails::new(
3125 format!(
3126 "{}() line argument must be a line, no other type of Segment",
3127 kind.function_name()
3128 ),
3129 vec![source_range],
3130 )));
3131 };
3132 let (
3133 UnsolvedExpr::Unknown(start_x),
3134 UnsolvedExpr::Unknown(start_y),
3135 UnsolvedExpr::Unknown(end_x),
3136 UnsolvedExpr::Unknown(end_y),
3137 ) = (&start[0], &start[1], &end[0], &end[1])
3138 else {
3139 return Err(KclError::new_semantic(KclErrorDetails::new(
3140 "line's x and y coordinates of both start and end must be vars".to_owned(),
3141 vec![source_range],
3142 )));
3143 };
3144
3145 Ok(AxisLineVars {
3146 start: [*start_x, *start_y],
3147 end: [*end_x, *end_y],
3148 object_id: unsolved.object_id,
3149 })
3150}
3151
3152#[derive(Debug, Clone)]
3153enum PointToAlign {
3154 Variable { x: SketchVarId, y: SketchVarId },
3156 Fixed { x: TyF64, y: TyF64 },
3158}
3159
3160impl From<[SketchVarId; 2]> for PointToAlign {
3161 fn from(sketch_var: [SketchVarId; 2]) -> Self {
3162 Self::Variable {
3163 x: sketch_var[0],
3164 y: sketch_var[1],
3165 }
3166 }
3167}
3168
3169impl From<[TyF64; 2]> for PointToAlign {
3170 fn from([x, y]: [TyF64; 2]) -> Self {
3171 Self::Fixed { x, y }
3172 }
3173}
3174
3175fn extract_axis_point_vars(
3176 input: &KclValue,
3177 kind: AxisConstraintKind,
3178 source_range: crate::SourceRange,
3179) -> Result<PointToAlign, KclError> {
3180 match input {
3181 KclValue::Segment { value: segment } => {
3182 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3183 return Err(KclError::new_semantic(KclErrorDetails::new(
3184 format!(
3185 "The `{}` function point arguments must be unsolved points",
3186 kind.function_name()
3187 ),
3188 vec![source_range],
3189 )));
3190 };
3191 let UnsolvedSegmentKind::Point { position, .. } = &unsolved.kind else {
3192 return Err(KclError::new_semantic(KclErrorDetails::new(
3193 format!(
3194 "The `{}` function list arguments must be points, but one item is {}",
3195 kind.function_name(),
3196 unsolved.kind.human_friendly_kind_with_article()
3197 ),
3198 vec![source_range],
3199 )));
3200 };
3201 match (&position[0], &position[1]) {
3202 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed {
3203 x: x.to_owned(),
3204 y: y.to_owned(),
3205 }),
3206 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x: *x, y: *y }),
3207 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
3208 Err(KclError::new_semantic(KclErrorDetails::new(
3209 format!(
3210 "The `{}` function cannot take a fixed X component and a variable Y component",
3211 kind.function_name()
3212 ),
3213 vec![source_range],
3214 )))
3215 }
3216 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
3217 Err(KclError::new_semantic(KclErrorDetails::new(
3218 format!(
3219 "The `{}` function cannot take a fixed X component and a variable Y component",
3220 kind.function_name()
3221 ),
3222 vec![source_range],
3223 )))
3224 }
3225 }
3226 }
3227 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
3228 let [x_value, y_value] = value.as_slice() else {
3229 return Err(KclError::new_semantic(KclErrorDetails::new(
3230 format!(
3231 "The `{}` function point arguments must each be a Point2d like [var 0mm, var 0mm]",
3232 kind.function_name()
3233 ),
3234 vec![source_range],
3235 )));
3236 };
3237 let Some(x_expr) = x_value.as_unsolved_expr() else {
3238 return Err(KclError::new_semantic(KclErrorDetails::new(
3239 format!(
3240 "The `{}` function point x coordinate must be a number or sketch var",
3241 kind.function_name()
3242 ),
3243 vec![source_range],
3244 )));
3245 };
3246 let Some(y_expr) = y_value.as_unsolved_expr() else {
3247 return Err(KclError::new_semantic(KclErrorDetails::new(
3248 format!(
3249 "The `{}` function point y coordinate must be a number or sketch var",
3250 kind.function_name()
3251 ),
3252 vec![source_range],
3253 )));
3254 };
3255 match (x_expr, y_expr) {
3256 (UnsolvedExpr::Known(x), UnsolvedExpr::Known(y)) => Ok(PointToAlign::Fixed { x, y }),
3257 (UnsolvedExpr::Unknown(x), UnsolvedExpr::Unknown(y)) => Ok(PointToAlign::Variable { x, y }),
3258 (UnsolvedExpr::Known(..), UnsolvedExpr::Unknown(..)) => {
3259 Err(KclError::new_semantic(KclErrorDetails::new(
3260 format!(
3261 "The `{}` function cannot take a fixed X component and a variable Y component",
3262 kind.function_name()
3263 ),
3264 vec![source_range],
3265 )))
3266 }
3267 (UnsolvedExpr::Unknown(..), UnsolvedExpr::Known(..)) => {
3268 Err(KclError::new_semantic(KclErrorDetails::new(
3269 format!(
3270 "The `{}` function cannot take a fixed X component and a variable Y component",
3271 kind.function_name()
3272 ),
3273 vec![source_range],
3274 )))
3275 }
3276 }
3277 }
3278 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3279 format!(
3280 "The `{}` function accepts either a line Segment or a list of points",
3281 kind.function_name()
3282 ),
3283 vec![source_range],
3284 ))),
3285 }
3286}
3287
3288async fn axis_constraint(
3289 kind: AxisConstraintKind,
3290 exec_state: &mut ExecState,
3291 args: Args,
3292) -> Result<KclValue, KclError> {
3293 let input: KclValue =
3294 args.get_unlabeled_kw_arg("input", &RuntimeType::Primitive(PrimitiveType::Any), exec_state)?;
3295
3296 match input {
3298 KclValue::Segment { value } => {
3299 axis_constraint_line(value, kind, exec_state, args)
3301 }
3302 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
3303 axis_constraint_points(value, kind, exec_state, args)
3305 }
3306 other => Err(KclError::new_semantic(KclErrorDetails::new(
3307 format!(
3308 "{}() accepts either a line Segment or a list of at least two points, but you provided {}",
3309 kind.function_name(),
3310 other.human_friendly_type(),
3311 ),
3312 vec![args.source_range],
3313 ))),
3314 }
3315}
3316
3317fn axis_constraint_line(
3319 segment: Box<AbstractSegment>,
3320 kind: AxisConstraintKind,
3321 exec_state: &mut ExecState,
3322 args: Args,
3323) -> Result<KclValue, KclError> {
3324 let line = extract_axis_line_vars(&segment, kind, args.source_range)?;
3325 let range = args.source_range;
3326 let solver_p0 = DatumPoint::new_xy(
3327 line.start[0].to_constraint_id(range)?,
3328 line.start[1].to_constraint_id(range)?,
3329 );
3330 let solver_p1 = DatumPoint::new_xy(
3331 line.end[0].to_constraint_id(range)?,
3332 line.end[1].to_constraint_id(range)?,
3333 );
3334 let solver_line = DatumLineSegment::new(solver_p0, solver_p1);
3335 let constraint = kind.line_constraint(solver_line);
3336 #[cfg(feature = "artifact-graph")]
3337 let constraint_id = exec_state.next_object_id();
3338 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3339 return Err(KclError::new_semantic(KclErrorDetails::new(
3340 format!("{}() can only be used inside a sketch block", kind.function_name()),
3341 vec![args.source_range],
3342 )));
3343 };
3344 sketch_state.solver_constraints.push(constraint);
3345 #[cfg(feature = "artifact-graph")]
3346 {
3347 let constraint = kind.line_artifact_constraint(line.object_id);
3348 sketch_state.sketch_constraints.push(constraint_id);
3349 track_constraint(constraint_id, constraint, exec_state, &args);
3350 }
3351 Ok(KclValue::none())
3352}
3353
3354fn axis_constraint_points(
3356 point_values: Vec<KclValue>,
3357 kind: AxisConstraintKind,
3358 exec_state: &mut ExecState,
3359 args: Args,
3360) -> Result<KclValue, KclError> {
3361 if point_values.len() < 2 {
3362 return Err(KclError::new_semantic(KclErrorDetails::new(
3363 format!("{}() point list must contain at least two points", kind.function_name()),
3364 vec![args.source_range],
3365 )));
3366 }
3367
3368 #[cfg(feature = "artifact-graph")]
3369 let trackable_point_ids = point_values
3370 .iter()
3371 .map(|point| match point {
3372 KclValue::Segment { value: segment } => {
3373 let SegmentRepr::Unsolved { segment: unsolved } = &segment.repr else {
3374 return None;
3375 };
3376 let UnsolvedSegmentKind::Point { .. } = &unsolved.kind else {
3377 return None;
3378 };
3379 Some(ConstraintSegment::from(unsolved.object_id))
3380 }
3381 point if point2d_is_origin(point) => Some(ConstraintSegment::ORIGIN),
3382 _ => None,
3383 })
3384 .collect::<Option<Vec<_>>>();
3385
3386 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3387 return Err(KclError::new_semantic(KclErrorDetails::new(
3388 format!("{}() can only be used inside a sketch block", kind.function_name()),
3389 vec![args.source_range],
3390 )));
3391 };
3392
3393 let points: Vec<PointToAlign> = point_values
3394 .iter()
3395 .map(|point| extract_axis_point_vars(point, kind, args.source_range))
3396 .collect::<Result<_, _>>()?;
3397
3398 let mut solver_constraints = Vec::with_capacity(points.len().saturating_sub(1));
3399
3400 let mut var_points = Vec::new();
3401 let mut fix_points = Vec::new();
3402 for point in points {
3403 match point {
3404 PointToAlign::Variable { x, y } => var_points.push((x, y)),
3405 PointToAlign::Fixed { x, y } => fix_points.push((x, y)),
3406 }
3407 }
3408 if fix_points.len() > 1 {
3409 return Err(KclError::new_semantic(KclErrorDetails::new(
3410 format!(
3411 "{}() point list can contain at most 1 fixed point, but you provided {}",
3412 kind.function_name(),
3413 fix_points.len()
3414 ),
3415 vec![args.source_range],
3416 )));
3417 }
3418
3419 if let Some(fix_point) = fix_points.pop() {
3420 for point in var_points {
3428 let solver_point = datum_point([point.0, point.1], args.source_range)?;
3429 let fix_point_mm = (fix_point.0.to_mm(), fix_point.1.to_mm());
3430 solver_constraints.push(kind.constraint_aligning_point_to_constant(solver_point, fix_point_mm));
3431 }
3432 } else {
3433 let mut points = var_points.into_iter();
3440 let first_point = points.next().ok_or_else(|| {
3441 KclError::new_semantic(KclErrorDetails::new(
3442 format!("{}() point list must contain at least two points", kind.function_name()),
3443 vec![args.source_range],
3444 ))
3445 })?;
3446 let anchor = datum_point([first_point.0, first_point.1], args.source_range)?;
3447 for point in points {
3448 let solver_point = datum_point([point.0, point.1], args.source_range)?;
3449 solver_constraints.push(kind.point_pair_constraint(anchor, solver_point));
3450 }
3451 }
3452 sketch_state.solver_constraints.extend(solver_constraints);
3453
3454 #[cfg(feature = "artifact-graph")]
3455 if let Some(point_ids) = trackable_point_ids {
3456 let constraint_id = exec_state.next_object_id();
3457 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3458 debug_assert!(false, "Constraint created outside a sketch block");
3459 return Ok(KclValue::none());
3460 };
3461 sketch_state.sketch_constraints.push(constraint_id);
3462 let constraint = kind.point_artifact_constraint(point_ids);
3463 track_constraint(constraint_id, constraint, exec_state, &args);
3464 }
3465
3466 Ok(KclValue::none())
3467}
3468
3469pub async fn angle(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3470 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
3471 "lines",
3472 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3473 exec_state,
3474 )?;
3475 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
3476 KclError::new_semantic(KclErrorDetails::new(
3477 "must have two input lines".to_owned(),
3478 vec![args.source_range],
3479 ))
3480 })?;
3481 let KclValue::Segment { value: segment0 } = &line0 else {
3482 return Err(KclError::new_semantic(KclErrorDetails::new(
3483 "line argument must be a Segment".to_owned(),
3484 vec![args.source_range],
3485 )));
3486 };
3487 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
3488 return Err(KclError::new_internal(KclErrorDetails::new(
3489 "line must be an unsolved Segment".to_owned(),
3490 vec![args.source_range],
3491 )));
3492 };
3493 let UnsolvedSegmentKind::Line {
3494 start: start0,
3495 end: end0,
3496 ..
3497 } = &unsolved0.kind
3498 else {
3499 return Err(KclError::new_semantic(KclErrorDetails::new(
3500 "line argument must be a line, no other type of Segment".to_owned(),
3501 vec![args.source_range],
3502 )));
3503 };
3504 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
3505 return Err(KclError::new_semantic(KclErrorDetails::new(
3506 "line's start x coordinate must be a var".to_owned(),
3507 vec![args.source_range],
3508 )));
3509 };
3510 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
3511 return Err(KclError::new_semantic(KclErrorDetails::new(
3512 "line's start y coordinate must be a var".to_owned(),
3513 vec![args.source_range],
3514 )));
3515 };
3516 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
3517 return Err(KclError::new_semantic(KclErrorDetails::new(
3518 "line's end x coordinate must be a var".to_owned(),
3519 vec![args.source_range],
3520 )));
3521 };
3522 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
3523 return Err(KclError::new_semantic(KclErrorDetails::new(
3524 "line's end y coordinate must be a var".to_owned(),
3525 vec![args.source_range],
3526 )));
3527 };
3528 let KclValue::Segment { value: segment1 } = &line1 else {
3529 return Err(KclError::new_semantic(KclErrorDetails::new(
3530 "line argument must be a Segment".to_owned(),
3531 vec![args.source_range],
3532 )));
3533 };
3534 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
3535 return Err(KclError::new_internal(KclErrorDetails::new(
3536 "line must be an unsolved Segment".to_owned(),
3537 vec![args.source_range],
3538 )));
3539 };
3540 let UnsolvedSegmentKind::Line {
3541 start: start1,
3542 end: end1,
3543 ..
3544 } = &unsolved1.kind
3545 else {
3546 return Err(KclError::new_semantic(KclErrorDetails::new(
3547 "line argument must be a line, no other type of Segment".to_owned(),
3548 vec![args.source_range],
3549 )));
3550 };
3551 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
3552 return Err(KclError::new_semantic(KclErrorDetails::new(
3553 "line's start x coordinate must be a var".to_owned(),
3554 vec![args.source_range],
3555 )));
3556 };
3557 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
3558 return Err(KclError::new_semantic(KclErrorDetails::new(
3559 "line's start y coordinate must be a var".to_owned(),
3560 vec![args.source_range],
3561 )));
3562 };
3563 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
3564 return Err(KclError::new_semantic(KclErrorDetails::new(
3565 "line's end x coordinate must be a var".to_owned(),
3566 vec![args.source_range],
3567 )));
3568 };
3569 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
3570 return Err(KclError::new_semantic(KclErrorDetails::new(
3571 "line's end y coordinate must be a var".to_owned(),
3572 vec![args.source_range],
3573 )));
3574 };
3575
3576 let sketch_constraint = SketchConstraint {
3578 kind: SketchConstraintKind::Angle {
3579 line0: crate::execution::ConstrainableLine2d {
3580 object_id: unsolved0.object_id,
3581 vars: [
3582 crate::front::Point2d {
3583 x: *line0_p0_x,
3584 y: *line0_p0_y,
3585 },
3586 crate::front::Point2d {
3587 x: *line0_p1_x,
3588 y: *line0_p1_y,
3589 },
3590 ],
3591 },
3592 line1: crate::execution::ConstrainableLine2d {
3593 object_id: unsolved1.object_id,
3594 vars: [
3595 crate::front::Point2d {
3596 x: *line1_p0_x,
3597 y: *line1_p0_y,
3598 },
3599 crate::front::Point2d {
3600 x: *line1_p1_x,
3601 y: *line1_p1_y,
3602 },
3603 ],
3604 },
3605 },
3606 meta: vec![args.source_range.into()],
3607 };
3608 Ok(KclValue::SketchConstraint {
3609 value: Box::new(sketch_constraint),
3610 })
3611}
3612
3613async fn lines_at_angle(
3614 angle_kind: LinesAtAngleKind,
3615 exec_state: &mut ExecState,
3616 args: Args,
3617) -> Result<KclValue, KclError> {
3618 let lines: Vec<KclValue> = args.get_unlabeled_kw_arg(
3619 "lines",
3620 &RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::Known(2)),
3621 exec_state,
3622 )?;
3623 let [line0, line1]: [KclValue; 2] = lines.try_into().map_err(|_| {
3624 KclError::new_semantic(KclErrorDetails::new(
3625 "must have two input lines".to_owned(),
3626 vec![args.source_range],
3627 ))
3628 })?;
3629
3630 let KclValue::Segment { value: segment0 } = &line0 else {
3631 return Err(KclError::new_semantic(KclErrorDetails::new(
3632 "line argument must be a Segment".to_owned(),
3633 vec![args.source_range],
3634 )));
3635 };
3636 let SegmentRepr::Unsolved { segment: unsolved0 } = &segment0.repr else {
3637 return Err(KclError::new_internal(KclErrorDetails::new(
3638 "line must be an unsolved Segment".to_owned(),
3639 vec![args.source_range],
3640 )));
3641 };
3642 let UnsolvedSegmentKind::Line {
3643 start: start0,
3644 end: end0,
3645 ..
3646 } = &unsolved0.kind
3647 else {
3648 return Err(KclError::new_semantic(KclErrorDetails::new(
3649 "line argument must be a line, no other type of Segment".to_owned(),
3650 vec![args.source_range],
3651 )));
3652 };
3653 let UnsolvedExpr::Unknown(line0_p0_x) = &start0[0] else {
3654 return Err(KclError::new_semantic(KclErrorDetails::new(
3655 "line's start x coordinate must be a var".to_owned(),
3656 vec![args.source_range],
3657 )));
3658 };
3659 let UnsolvedExpr::Unknown(line0_p0_y) = &start0[1] else {
3660 return Err(KclError::new_semantic(KclErrorDetails::new(
3661 "line's start y coordinate must be a var".to_owned(),
3662 vec![args.source_range],
3663 )));
3664 };
3665 let UnsolvedExpr::Unknown(line0_p1_x) = &end0[0] else {
3666 return Err(KclError::new_semantic(KclErrorDetails::new(
3667 "line's end x coordinate must be a var".to_owned(),
3668 vec![args.source_range],
3669 )));
3670 };
3671 let UnsolvedExpr::Unknown(line0_p1_y) = &end0[1] else {
3672 return Err(KclError::new_semantic(KclErrorDetails::new(
3673 "line's end y coordinate must be a var".to_owned(),
3674 vec![args.source_range],
3675 )));
3676 };
3677 let KclValue::Segment { value: segment1 } = &line1 else {
3678 return Err(KclError::new_semantic(KclErrorDetails::new(
3679 "line argument must be a Segment".to_owned(),
3680 vec![args.source_range],
3681 )));
3682 };
3683 let SegmentRepr::Unsolved { segment: unsolved1 } = &segment1.repr else {
3684 return Err(KclError::new_internal(KclErrorDetails::new(
3685 "line must be an unsolved Segment".to_owned(),
3686 vec![args.source_range],
3687 )));
3688 };
3689 let UnsolvedSegmentKind::Line {
3690 start: start1,
3691 end: end1,
3692 ..
3693 } = &unsolved1.kind
3694 else {
3695 return Err(KclError::new_semantic(KclErrorDetails::new(
3696 "line argument must be a line, no other type of Segment".to_owned(),
3697 vec![args.source_range],
3698 )));
3699 };
3700 let UnsolvedExpr::Unknown(line1_p0_x) = &start1[0] else {
3701 return Err(KclError::new_semantic(KclErrorDetails::new(
3702 "line's start x coordinate must be a var".to_owned(),
3703 vec![args.source_range],
3704 )));
3705 };
3706 let UnsolvedExpr::Unknown(line1_p0_y) = &start1[1] else {
3707 return Err(KclError::new_semantic(KclErrorDetails::new(
3708 "line's start y coordinate must be a var".to_owned(),
3709 vec![args.source_range],
3710 )));
3711 };
3712 let UnsolvedExpr::Unknown(line1_p1_x) = &end1[0] else {
3713 return Err(KclError::new_semantic(KclErrorDetails::new(
3714 "line's end x coordinate must be a var".to_owned(),
3715 vec![args.source_range],
3716 )));
3717 };
3718 let UnsolvedExpr::Unknown(line1_p1_y) = &end1[1] else {
3719 return Err(KclError::new_semantic(KclErrorDetails::new(
3720 "line's end y coordinate must be a var".to_owned(),
3721 vec![args.source_range],
3722 )));
3723 };
3724
3725 let range = args.source_range;
3726 let solver_line0_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3727 line0_p0_x.to_constraint_id(range)?,
3728 line0_p0_y.to_constraint_id(range)?,
3729 );
3730 let solver_line0_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3731 line0_p1_x.to_constraint_id(range)?,
3732 line0_p1_y.to_constraint_id(range)?,
3733 );
3734 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line0_p0, solver_line0_p1);
3735 let solver_line1_p0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3736 line1_p0_x.to_constraint_id(range)?,
3737 line1_p0_y.to_constraint_id(range)?,
3738 );
3739 let solver_line1_p1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3740 line1_p1_x.to_constraint_id(range)?,
3741 line1_p1_y.to_constraint_id(range)?,
3742 );
3743 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(solver_line1_p0, solver_line1_p1);
3744 let constraint = SolverConstraint::LinesAtAngle(solver_line0, solver_line1, angle_kind.to_solver_angle());
3745 #[cfg(feature = "artifact-graph")]
3746 let constraint_id = exec_state.next_object_id();
3747 let Some(sketch_state) = exec_state.sketch_block_mut() else {
3749 return Err(KclError::new_semantic(KclErrorDetails::new(
3750 format!(
3751 "{}() can only be used inside a sketch block",
3752 angle_kind.to_function_name()
3753 ),
3754 vec![args.source_range],
3755 )));
3756 };
3757 sketch_state.solver_constraints.push(constraint);
3758 #[cfg(feature = "artifact-graph")]
3759 {
3760 let constraint = angle_kind.constraint(vec![unsolved0.object_id, unsolved1.object_id]);
3761 sketch_state.sketch_constraints.push(constraint_id);
3762 track_constraint(constraint_id, constraint, exec_state, &args);
3763 }
3764 Ok(KclValue::none())
3765}
3766
3767pub async fn horizontal(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3768 axis_constraint(AxisConstraintKind::Horizontal, exec_state, args).await
3769}
3770
3771pub async fn vertical(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
3772 axis_constraint(AxisConstraintKind::Vertical, exec_state, args).await
3773}