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