1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use ezpz::Constraint;
5use ezpz::NonLinearSystemError;
6use indexmap::IndexMap;
7use kittycad_modeling_cmds as kcmc;
8
9use crate::CompilationIssue;
10use crate::NodePath;
11use crate::SourceRange;
12use crate::errors::KclError;
13use crate::errors::KclErrorDetails;
14use crate::exec::Sketch;
15use crate::execution::AbstractSegment;
16use crate::execution::Artifact;
17use crate::execution::ArtifactId;
18use crate::execution::BodyType;
19use crate::execution::ConstraintKind;
20use crate::execution::ControlFlowKind;
21use crate::execution::EarlyReturn;
22use crate::execution::EnvironmentRef;
23use crate::execution::ExecState;
24use crate::execution::ExecutorContext;
25use crate::execution::Group;
26use crate::execution::KclValue;
27use crate::execution::KclValueControlFlow;
28use crate::execution::Metadata;
29use crate::execution::ModelingCmdMeta;
30use crate::execution::ModuleArtifactState;
31use crate::execution::Operation;
32use crate::execution::PreserveMem;
33use crate::execution::SKETCH_BLOCK_PARAM_ON;
34use crate::execution::SKETCH_OBJECT_META;
35use crate::execution::SKETCH_OBJECT_META_SKETCH;
36use crate::execution::Segment;
37use crate::execution::SegmentKind;
38use crate::execution::SegmentRepr;
39use crate::execution::SketchConstraintKind;
40use crate::execution::SketchSurface;
41use crate::execution::StatementKind;
42use crate::execution::TagIdentifier;
43use crate::execution::UnsolvedExpr;
44use crate::execution::UnsolvedSegment;
45use crate::execution::UnsolvedSegmentKind;
46use crate::execution::annotations;
47use crate::execution::annotations::FnAttrs;
48use crate::execution::cad_op::OpKclValue;
49use crate::execution::control_continue;
50use crate::execution::early_return;
51use crate::execution::fn_call::Arg;
52use crate::execution::fn_call::Args;
53use crate::execution::kcl_value::FunctionSource;
54use crate::execution::kcl_value::KclFunctionSourceParams;
55use crate::execution::kcl_value::TypeDef;
56use crate::execution::memory::SKETCH_PREFIX;
57use crate::execution::memory::{self};
58use crate::execution::sketch_constraint_status_for_sketch;
59use crate::execution::sketch_solve::FreedomAnalysis;
60use crate::execution::sketch_solve::Solved;
61use crate::execution::sketch_solve::create_segment_scene_objects;
62use crate::execution::sketch_solve::normalize_to_solver_angle_unit;
63use crate::execution::sketch_solve::normalize_to_solver_distance_unit;
64use crate::execution::sketch_solve::solver_numeric_type;
65use crate::execution::sketch_solve::substitute_sketch_var_in_segment;
66use crate::execution::sketch_solve::substitute_sketch_vars;
67use crate::execution::state::ModuleState;
68use crate::execution::state::SketchBlockState;
69use crate::execution::types::NumericType;
70use crate::execution::types::PrimitiveType;
71use crate::execution::types::RuntimeType;
72use crate::front::LineCtor;
73use crate::front::Object;
74use crate::front::ObjectId;
75use crate::front::ObjectKind;
76use crate::front::PointCtor;
77use crate::modules::ModuleExecutionOutcome;
78use crate::modules::ModuleId;
79use crate::modules::ModulePath;
80use crate::modules::ModuleRepr;
81use crate::parsing::ast::types::Annotation;
82use crate::parsing::ast::types::ArrayExpression;
83use crate::parsing::ast::types::ArrayRangeExpression;
84use crate::parsing::ast::types::AscribedExpression;
85use crate::parsing::ast::types::BinaryExpression;
86use crate::parsing::ast::types::BinaryOperator;
87use crate::parsing::ast::types::BinaryPart;
88use crate::parsing::ast::types::BodyItem;
89use crate::parsing::ast::types::CodeBlock;
90use crate::parsing::ast::types::Expr;
91use crate::parsing::ast::types::IfExpression;
92use crate::parsing::ast::types::ImportPath;
93use crate::parsing::ast::types::ImportSelector;
94use crate::parsing::ast::types::ItemVisibility;
95use crate::parsing::ast::types::MemberExpression;
96use crate::parsing::ast::types::Name;
97use crate::parsing::ast::types::Node;
98use crate::parsing::ast::types::ObjectExpression;
99use crate::parsing::ast::types::PipeExpression;
100use crate::parsing::ast::types::Program;
101use crate::parsing::ast::types::SketchBlock;
102use crate::parsing::ast::types::SketchVar;
103use crate::parsing::ast::types::TagDeclarator;
104use crate::parsing::ast::types::Type;
105use crate::parsing::ast::types::UnaryExpression;
106use crate::parsing::ast::types::UnaryOperator;
107use crate::std::StdFnProps;
108use crate::std::args::FromKclValue;
109use crate::std::args::TyF64;
110use crate::std::shapes::SketchOrSurface;
111use crate::std::sketch::ensure_sketch_plane_in_engine;
112use crate::std::solver::SOLVER_CONVERGENCE_TOLERANCE;
113use crate::std::solver::create_segments_in_engine;
114
115fn internal_err(message: impl Into<String>, range: impl Into<SourceRange>) -> KclError {
116 KclError::new_internal(KclErrorDetails::new(message.into(), vec![range.into()]))
117}
118
119fn datum_point_from_constrainable(
120 point: &crate::execution::ConstrainablePoint2d,
121 range: SourceRange,
122) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
123 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
124 point.vars.x.to_constraint_id(range)?,
125 point.vars.y.to_constraint_id(range)?,
126 ))
127}
128
129fn push_fixed_origin_point(
130 sketch_block_state: &mut SketchBlockState,
131 sketch_var_ty: NumericType,
132 range: SourceRange,
133) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
134 let origin_x_id = sketch_block_state.next_sketch_var_id();
135 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
136 value: Box::new(crate::execution::SketchVar {
137 id: origin_x_id,
138 initial_value: 0.0,
139 ty: sketch_var_ty,
140 meta: vec![],
141 }),
142 });
143 let origin_y_id = sketch_block_state.next_sketch_var_id();
144 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
145 value: Box::new(crate::execution::SketchVar {
146 id: origin_y_id,
147 initial_value: 0.0,
148 ty: sketch_var_ty,
149 meta: vec![],
150 }),
151 });
152
153 sketch_block_state
154 .solver_constraints
155 .push(Constraint::Fixed(origin_x_id.to_constraint_id(range)?, 0.0));
156 sketch_block_state
157 .solver_constraints
158 .push(Constraint::Fixed(origin_y_id.to_constraint_id(range)?, 0.0));
159
160 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
161 origin_x_id.to_constraint_id(range)?,
162 origin_y_id.to_constraint_id(range)?,
163 ))
164}
165
166fn datum_point_from_constrainable_or_origin(
167 sketch_block_state: &mut SketchBlockState,
168 sketch_var_ty: NumericType,
169 point: &crate::execution::ConstrainablePoint2dOrOrigin,
170 range: SourceRange,
171) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
172 match point {
173 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => datum_point_from_constrainable(point, range),
174 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
175 push_fixed_origin_point(sketch_block_state, sketch_var_ty, range)
176 }
177 }
178}
179
180fn datum_line_from_constrainable(
181 line: &crate::execution::ConstrainableLine2d,
182 range: SourceRange,
183) -> Result<ezpz::datatypes::inputs::DatumLineSegment, KclError> {
184 Ok(ezpz::datatypes::inputs::DatumLineSegment::new(
185 ezpz::datatypes::inputs::DatumPoint::new_xy(
186 line.vars[0].x.to_constraint_id(range)?,
187 line.vars[0].y.to_constraint_id(range)?,
188 ),
189 ezpz::datatypes::inputs::DatumPoint::new_xy(
190 line.vars[1].x.to_constraint_id(range)?,
191 line.vars[1].y.to_constraint_id(range)?,
192 ),
193 ))
194}
195
196fn sketch_var_initial_value(
197 sketch_vars: &[KclValue],
198 id: crate::execution::SketchVarId,
199 exec_state: &mut ExecState,
200 range: SourceRange,
201 description: &str,
202) -> Result<f64, KclError> {
203 sketch_vars
204 .get(id.0)
205 .and_then(KclValue::as_sketch_var)
206 .map(|sketch_var| {
207 sketch_var
208 .initial_value_to_solver_units(exec_state, range, description)
209 .map(|value| value.n)
210 })
211 .transpose()?
212 .ok_or_else(|| internal_err(format!("Missing sketch variable initial value for id {}", id.0), range))
213}
214
215fn constrainable_point_initial_position(
216 sketch_vars: &[KclValue],
217 point: &crate::execution::ConstrainablePoint2d,
218 exec_state: &mut ExecState,
219 range: SourceRange,
220 description: &str,
221) -> Result<[f64; 2], KclError> {
222 Ok([
223 sketch_var_initial_value(sketch_vars, point.vars.x, exec_state, range, description)?,
224 sketch_var_initial_value(sketch_vars, point.vars.y, exec_state, range, description)?,
225 ])
226}
227
228fn constrainable_point_or_origin_initial_position(
229 sketch_vars: &[KclValue],
230 point: &crate::execution::ConstrainablePoint2dOrOrigin,
231 exec_state: &mut ExecState,
232 range: SourceRange,
233 description: &str,
234) -> Result<[f64; 2], KclError> {
235 match point {
236 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
237 constrainable_point_initial_position(sketch_vars, point, exec_state, range, description)
238 }
239 crate::execution::ConstrainablePoint2dOrOrigin::Origin => Ok([0.0, 0.0]),
240 }
241}
242
243fn constrainable_line_initial_positions(
249 sketch_vars: &[KclValue],
250 line: &crate::execution::ConstrainableLine2d,
251 exec_state: &mut ExecState,
252 range: SourceRange,
253 description: &str,
254) -> Result<([f64; 2], [f64; 2]), KclError> {
255 let start = crate::execution::ConstrainablePoint2d {
256 vars: line.vars[0].clone(),
257 object_id: line.object_id,
258 };
259 let end = crate::execution::ConstrainablePoint2d {
260 vars: line.vars[1].clone(),
261 object_id: line.object_id,
262 };
263 Ok((
264 constrainable_point_initial_position(sketch_vars, &start, exec_state, range, description)?,
265 constrainable_point_initial_position(sketch_vars, &end, exec_state, range, description)?,
266 ))
267}
268
269fn projected_point_on_line_initial_position(
270 sketch_vars: &[KclValue],
271 point: &crate::execution::ConstrainablePoint2dOrOrigin,
272 line: &crate::execution::ConstrainableLine2d,
273 exec_state: &mut ExecState,
274 range: SourceRange,
275) -> Result<[f64; 2], KclError> {
276 let point = constrainable_point_or_origin_initial_position(
277 sketch_vars,
278 point,
279 exec_state,
280 range,
281 "point-line distance initial point",
282 )?;
283 let (line_start, line_end) =
284 constrainable_line_initial_positions(sketch_vars, line, exec_state, range, "point-line distance initial line")?;
285 let dx = line_end[0] - line_start[0];
286 let dy = line_end[1] - line_start[1];
287 let len_sq = dx * dx + dy * dy;
288 if len_sq == 0.0 {
289 return Err(KclError::new_semantic(KclErrorDetails::new(
290 "distance() line input must have non-zero length".to_owned(),
291 vec![range],
292 )));
293 }
294
295 let t = ((point[0] - line_start[0]) * dx + (point[1] - line_start[1]) * dy) / len_sq;
298 Ok([line_start[0] + t * dx, line_start[1] + t * dy])
299}
300
301fn constrainable_points_initial_distance(
302 sketch_vars: &[KclValue],
303 point0: &crate::execution::ConstrainablePoint2d,
304 point1: &crate::execution::ConstrainablePoint2d,
305 exec_state: &mut ExecState,
306 range: SourceRange,
307 description: &str,
308) -> Result<f64, KclError> {
309 let p0 = constrainable_point_initial_position(sketch_vars, point0, exec_state, range, description)?;
310 let p1 = constrainable_point_initial_position(sketch_vars, point1, exec_state, range, description)?;
311 Ok(libm::hypot(p0[0] - p1[0], p0[1] - p1[1]))
312}
313
314#[derive(Clone, Copy)]
318struct CircularDistanceDatums {
319 center: ezpz::datatypes::inputs::DatumPoint,
320 start: ezpz::datatypes::inputs::DatumPoint,
321 end: Option<ezpz::datatypes::inputs::DatumPoint>,
322 radius_initial_value: f64,
323}
324
325fn circular_distance_datums(
326 sketch_vars: &[KclValue],
327 center: &crate::execution::ConstrainablePoint2d,
328 start: &crate::execution::ConstrainablePoint2d,
329 end: Option<&crate::execution::ConstrainablePoint2d>,
330 exec_state: &mut ExecState,
331 range: SourceRange,
332) -> Result<CircularDistanceDatums, KclError> {
333 Ok(CircularDistanceDatums {
334 center: datum_point_from_constrainable(center, range)?,
335 start: datum_point_from_constrainable(start, range)?,
336 end: end.map(|end| datum_point_from_constrainable(end, range)).transpose()?,
337 radius_initial_value: constrainable_points_initial_distance(
338 sketch_vars,
339 center,
340 start,
341 exec_state,
342 range,
343 "circular distance radius initial value",
344 )?,
345 })
346}
347
348fn circular_circular_support_initial_position(
349 sketch_vars: &[KclValue],
350 center0: &crate::execution::ConstrainablePoint2d,
351 center1: &crate::execution::ConstrainablePoint2d,
352 radius0: f64,
353 distance_value: f64,
354 exec_state: &mut ExecState,
355 range: SourceRange,
356) -> Result<[f64; 2], KclError> {
357 let center0_initial =
358 constrainable_point_initial_position(sketch_vars, center0, exec_state, range, "circular distance center")?;
359 let center1_initial =
360 constrainable_point_initial_position(sketch_vars, center1, exec_state, range, "circular distance center")?;
361 let dx = center1_initial[0] - center0_initial[0];
362 let dy = center1_initial[1] - center0_initial[1];
363 let center_distance = libm::hypot(dx, dy);
364 let support_distance = radius0 + distance_value / 2.0;
369
370 if center_distance <= f64::EPSILON {
371 return Ok([center0_initial[0] + support_distance, center0_initial[1]]);
374 }
375
376 Ok([
377 center0_initial[0] + dx / center_distance * support_distance,
378 center0_initial[1] + dy / center_distance * support_distance,
379 ])
380}
381
382fn push_circular_radius_constraints(
383 sketch_block_state: &mut SketchBlockState,
384 sketch_var_ty: NumericType,
385 circular: CircularDistanceDatums,
386 range: SourceRange,
387) -> Result<ezpz::datatypes::inputs::DatumCircle, KclError> {
388 let circular_radius_id = sketch_block_state.next_sketch_var_id();
392 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
393 value: Box::new(crate::execution::SketchVar {
394 id: circular_radius_id,
395 initial_value: circular.radius_initial_value,
396 ty: sketch_var_ty,
397 meta: vec![],
398 }),
399 });
400 let circular_radius = ezpz::datatypes::inputs::DatumDistance::new(circular_radius_id.to_constraint_id(range)?);
401
402 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
403 circular.start,
404 circular.center,
405 circular_radius,
406 ));
407 if let Some(end) = circular.end {
408 sketch_block_state
409 .solver_constraints
410 .push(Constraint::DistanceVar(end, circular.center, circular_radius));
411 }
412
413 Ok(ezpz::datatypes::inputs::DatumCircle {
414 center: circular.center,
415 radius: circular_radius,
416 })
417}
418
419fn push_circular_distance_constraints(
420 sketch_block_state: &mut SketchBlockState,
421 sketch_var_ty: NumericType,
422 target_point: ezpz::datatypes::inputs::DatumPoint,
423 circular: CircularDistanceDatums,
424 distance_value: f64,
425 range: SourceRange,
426) -> Result<(), KclError> {
427 let circular_target = push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular, range)?;
428
429 let target_distance_id = sketch_block_state.next_sketch_var_id();
432 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
433 value: Box::new(crate::execution::SketchVar {
434 id: target_distance_id,
435 initial_value: distance_value,
436 ty: sketch_var_ty,
437 meta: vec![],
438 }),
439 });
440 let target_distance = ezpz::datatypes::inputs::DatumDistance::new(target_distance_id.to_constraint_id(range)?);
441
442 sketch_block_state
443 .solver_constraints
444 .push(Constraint::Fixed(target_distance.id, distance_value));
445
446 let target_circle = ezpz::datatypes::inputs::DatumCircle {
447 center: target_point,
448 radius: target_distance,
449 };
450 sketch_block_state
451 .solver_constraints
452 .push(Constraint::CircleTangentToCircle(
453 target_circle,
454 circular_target,
455 ezpz::CircleSide::Exterior,
456 ));
457
458 Ok(())
459}
460
461fn sketch_on_cache_name(sketch_id: ObjectId) -> String {
462 format!("{SKETCH_PREFIX}{}_on", sketch_id.0)
463}
464
465fn default_plane_name_from_expr(expr: &Expr) -> Option<crate::engine::PlaneName> {
466 fn parse_name(name: &str, negative: bool) -> Option<crate::engine::PlaneName> {
467 use crate::engine::PlaneName;
468
469 match (name, negative) {
470 ("XY", false) => Some(PlaneName::Xy),
471 ("XY", true) => Some(PlaneName::NegXy),
472 ("XZ", false) => Some(PlaneName::Xz),
473 ("XZ", true) => Some(PlaneName::NegXz),
474 ("YZ", false) => Some(PlaneName::Yz),
475 ("YZ", true) => Some(PlaneName::NegYz),
476 _ => None,
477 }
478 }
479
480 match expr {
481 Expr::Name(name) => {
482 if !name.path.is_empty() {
483 return None;
484 }
485 parse_name(&name.name.name, false)
486 }
487 Expr::UnaryExpression(unary) => {
488 if unary.operator != UnaryOperator::Neg {
489 return None;
490 }
491 let crate::parsing::ast::types::BinaryPart::Name(name) = &unary.argument else {
492 return None;
493 };
494 if !name.path.is_empty() {
495 return None;
496 }
497 parse_name(&name.name.name, true)
498 }
499 _ => None,
500 }
501}
502
503fn sketch_on_frontend_plane(
504 arguments: &[crate::parsing::ast::types::LabeledArg],
505 on_object_id: crate::front::ObjectId,
506) -> crate::front::Plane {
507 for arg in arguments {
508 let Some(label) = &arg.label else {
509 continue;
510 };
511 if label.name != SKETCH_BLOCK_PARAM_ON {
512 continue;
513 }
514 if let Some(name) = default_plane_name_from_expr(&arg.arg) {
515 return crate::front::Plane::Default(name);
516 }
517 break;
518 }
519
520 crate::front::Plane::Object(on_object_id)
521}
522
523impl<'a> StatementKind<'a> {
524 fn expect_name(&self) -> &'a str {
525 match self {
526 StatementKind::Declaration { name } => name,
527 StatementKind::Expression => unreachable!(),
528 }
529 }
530}
531
532impl ExecutorContext {
533 async fn handle_annotations(
535 &self,
536 annotations: impl Iterator<Item = &Node<Annotation>>,
537 body_type: BodyType,
538 exec_state: &mut ExecState,
539 ) -> Result<bool, KclError> {
540 let mut no_prelude = false;
541 for annotation in annotations {
542 if annotation.name() == Some(annotations::SETTINGS) {
543 if matches!(body_type, BodyType::Root) {
544 let (updated_len, updated_angle) =
545 exec_state.mod_local.settings.update_from_annotation(annotation)?;
546 if updated_len {
547 exec_state.mod_local.explicit_length_units = true;
548 }
549 if updated_angle {
550 exec_state.warn(
551 CompilationIssue::err(
552 annotation.as_source_range(),
553 "Prefer to use explicit units for angles",
554 ),
555 annotations::WARN_ANGLE_UNITS,
556 );
557 }
558 } else {
559 exec_state.err(CompilationIssue::err(
560 annotation.as_source_range(),
561 "Settings can only be modified at the top level scope of a file",
562 ));
563 }
564 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
565 if matches!(body_type, BodyType::Root) {
566 no_prelude = true;
567 } else {
568 exec_state.err(CompilationIssue::err(
569 annotation.as_source_range(),
570 "The standard library can only be skipped at the top level scope of a file",
571 ));
572 }
573 } else if annotation.name() == Some(annotations::WARNINGS) {
574 if matches!(body_type, BodyType::Root) {
576 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
577 for p in props {
578 match &*p.inner.key.name {
579 annotations::WARN_ALLOW => {
580 let allowed = annotations::many_of(
581 &p.inner.value,
582 &annotations::WARN_VALUES,
583 annotation.as_source_range(),
584 )?;
585 exec_state.mod_local.allowed_warnings = allowed;
586 }
587 annotations::WARN_DENY => {
588 let denied = annotations::many_of(
589 &p.inner.value,
590 &annotations::WARN_VALUES,
591 annotation.as_source_range(),
592 )?;
593 exec_state.mod_local.denied_warnings = denied;
594 }
595 name => {
596 return Err(KclError::new_semantic(KclErrorDetails::new(
597 format!(
598 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
599 annotations::WARN_ALLOW,
600 annotations::WARN_DENY,
601 ),
602 vec![annotation.as_source_range()],
603 )));
604 }
605 }
606 }
607 } else {
608 exec_state.err(CompilationIssue::err(
609 annotation.as_source_range(),
610 "Warnings can only be customized at the top level scope of a file",
611 ));
612 }
613 } else {
614 exec_state.warn(
615 CompilationIssue::err(annotation.as_source_range(), "Unknown annotation"),
616 annotations::WARN_UNKNOWN_ATTR,
617 );
618 }
619 }
620 Ok(no_prelude)
621 }
622
623 pub(super) async fn exec_module_body(
624 &self,
625 program: &Node<Program>,
626 exec_state: &mut ExecState,
627 preserve_mem: PreserveMem,
628 module_id: ModuleId,
629 path: &ModulePath,
630 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
631 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
632
633 let mut local_state = ModuleState::new(
642 path.clone(),
643 exec_state.stack().memory.clone(),
644 Some(module_id),
645 exec_state.mod_local.sketch_mode,
646 exec_state.mod_local.freedom_analysis,
647 );
648 match preserve_mem {
649 PreserveMem::Always => {
650 exec_state
651 .mod_local
652 .artifacts
653 .restore_scene_objects(&exec_state.global.root_module_artifacts.scene_objects);
654 }
655 PreserveMem::Normal => {
656 local_state
657 .artifacts
658 .restore_scene_objects(&exec_state.mod_local.artifacts.scene_objects);
659 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
660 }
661 }
662
663 let no_prelude = self
664 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
665 .await
666 .map_err(|err| (err, None, None))?;
667
668 if preserve_mem.normal() {
669 exec_state.mut_stack().push_new_root_env(!no_prelude);
670 }
671
672 let result = self
673 .exec_block(program, exec_state, crate::execution::BodyType::Root)
674 .await;
675
676 let env_ref = match preserve_mem {
677 PreserveMem::Always => exec_state.mut_stack().pop_and_preserve_env(),
678 PreserveMem::Normal => exec_state.mut_stack().pop_env(),
679 };
680 let module_artifacts = match preserve_mem {
681 PreserveMem::Always => std::mem::take(&mut exec_state.mod_local.artifacts),
682 PreserveMem::Normal => {
683 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
684 local_state.artifacts
685 }
686 };
687
688 crate::log::log(format!("leave {path}"));
689
690 result
691 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
692 .map(|last_expr| ModuleExecutionOutcome {
693 last_expr: last_expr.map(|value_cf| value_cf.into_value()),
694 environment: env_ref,
695 exports: local_state.module_exports,
696 artifacts: module_artifacts,
697 })
698 }
699
700 #[async_recursion]
702 pub(super) async fn exec_block<'a, B>(
703 &'a self,
704 block: &'a B,
705 exec_state: &mut ExecState,
706 body_type: BodyType,
707 ) -> Result<Option<KclValueControlFlow>, KclError>
708 where
709 B: CodeBlock + Sync,
710 {
711 let mut last_expr = None;
712 for statement in block.body() {
714 match statement {
715 BodyItem::ImportStatement(import_stmt) => {
716 if exec_state.sketch_mode() {
717 continue;
718 }
719 if !matches!(body_type, BodyType::Root) {
720 return Err(KclError::new_semantic(KclErrorDetails::new(
721 "Imports are only supported at the top-level of a file.".to_owned(),
722 vec![import_stmt.into()],
723 )));
724 }
725
726 let source_range = SourceRange::from(import_stmt);
727 let attrs = &import_stmt.outer_attrs;
728 let module_path = ModulePath::from_import_path(
729 &import_stmt.path,
730 &self.settings.project_directory,
731 &exec_state.mod_local.path,
732 )?;
733 let module_id = self
734 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
735 .await?;
736
737 match &import_stmt.selector {
738 ImportSelector::List { items } => {
739 let (env_ref, module_exports) =
740 self.exec_module_for_items(module_id, exec_state, source_range).await?;
741 for import_item in items {
742 let mem = &exec_state.stack().memory;
744 let mut value = mem
745 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
746 .cloned();
747 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
748 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
749 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
750 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
751
752 if value.is_err() && ty.is_err() && mod_value.is_err() {
753 return Err(KclError::new_undefined_value(
754 KclErrorDetails::new(
755 format!("{} is not defined in module", import_item.name.name),
756 vec![SourceRange::from(&import_item.name)],
757 ),
758 None,
759 ));
760 }
761
762 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
764 value = Err(KclError::new_semantic(KclErrorDetails::new(
765 format!(
766 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
767 import_item.name.name
768 ),
769 vec![SourceRange::from(&import_item.name)],
770 )));
771 }
772
773 if ty.is_ok() && !module_exports.contains(&ty_name) {
774 ty = Err(KclError::new_semantic(KclErrorDetails::new(
775 format!(
776 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
777 import_item.name.name
778 ),
779 vec![SourceRange::from(&import_item.name)],
780 )));
781 }
782
783 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
784 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
785 format!(
786 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
787 import_item.name.name
788 ),
789 vec![SourceRange::from(&import_item.name)],
790 )));
791 }
792
793 if value.is_err() && ty.is_err() && mod_value.is_err() {
794 return value.map(|v| Some(v.continue_()));
795 }
796
797 if let Ok(value) = value {
799 exec_state.mut_stack().add(
800 import_item.identifier().to_owned(),
801 value,
802 SourceRange::from(&import_item.name),
803 )?;
804
805 if let ItemVisibility::Export = import_stmt.visibility {
806 exec_state
807 .mod_local
808 .module_exports
809 .push(import_item.identifier().to_owned());
810 }
811 }
812
813 if let Ok(ty) = ty {
814 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
815 exec_state.mut_stack().add(
816 ty_name.clone(),
817 ty,
818 SourceRange::from(&import_item.name),
819 )?;
820
821 if let ItemVisibility::Export = import_stmt.visibility {
822 exec_state.mod_local.module_exports.push(ty_name);
823 }
824 }
825
826 if let Ok(mod_value) = mod_value {
827 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
828 exec_state.mut_stack().add(
829 mod_name.clone(),
830 mod_value,
831 SourceRange::from(&import_item.name),
832 )?;
833
834 if let ItemVisibility::Export = import_stmt.visibility {
835 exec_state.mod_local.module_exports.push(mod_name);
836 }
837 }
838 }
839 }
840 ImportSelector::Glob(_) => {
841 let (env_ref, module_exports) =
842 self.exec_module_for_items(module_id, exec_state, source_range).await?;
843 for name in module_exports.iter() {
844 let item = exec_state
845 .stack()
846 .memory
847 .get_from(name, env_ref, source_range, 0)
848 .map_err(|_err| {
849 internal_err(
850 format!("{name} is not defined in module (but was exported?)"),
851 source_range,
852 )
853 })?
854 .clone();
855 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
856
857 if let ItemVisibility::Export = import_stmt.visibility {
858 exec_state.mod_local.module_exports.push(name.clone());
859 }
860 }
861 }
862 ImportSelector::None { .. } => {
863 let name = import_stmt.module_name().unwrap();
864 let item = KclValue::Module {
865 value: module_id,
866 meta: vec![source_range.into()],
867 };
868 exec_state.mut_stack().add(
869 format!("{}{}", memory::MODULE_PREFIX, name),
870 item,
871 source_range,
872 )?;
873 }
874 }
875 last_expr = None;
876 }
877 BodyItem::ExpressionStatement(expression_statement) => {
878 if exec_state.sketch_mode() && sketch_mode_should_skip(&expression_statement.expression) {
879 continue;
880 }
881
882 let metadata = Metadata::from(expression_statement);
883 let value = self
884 .execute_expr(
885 &expression_statement.expression,
886 exec_state,
887 &metadata,
888 &[],
889 StatementKind::Expression,
890 )
891 .await?;
892
893 let is_return = value.is_some_return();
894 last_expr = Some(value);
895
896 if is_return {
897 break;
898 }
899 }
900 BodyItem::VariableDeclaration(variable_declaration) => {
901 if exec_state.sketch_mode() && sketch_mode_should_skip(&variable_declaration.declaration.init) {
902 continue;
903 }
904
905 let var_name = variable_declaration.declaration.id.name.to_string();
906 let source_range = SourceRange::from(&variable_declaration.declaration.init);
907 let metadata = Metadata { source_range };
908
909 let annotations = &variable_declaration.outer_attrs;
910
911 let lhs = variable_declaration.inner.name().to_owned();
914 let prev_being_declared = exec_state.mod_local.being_declared.take();
915 exec_state.mod_local.being_declared = Some(lhs);
916 let rhs_result = self
917 .execute_expr(
918 &variable_declaration.declaration.init,
919 exec_state,
920 &metadata,
921 annotations,
922 StatementKind::Declaration { name: &var_name },
923 )
924 .await;
925 exec_state.mod_local.being_declared = prev_being_declared;
927 let rhs = rhs_result?;
928
929 if rhs.is_some_return() {
930 last_expr = Some(rhs);
931 break;
932 }
933 let mut rhs = rhs.into_value();
934
935 if let KclValue::Segment { value } = &mut rhs
939 && let SegmentRepr::Unsolved { segment } = &mut value.repr
940 {
941 segment.tag = Some(TagIdentifier {
942 value: variable_declaration.declaration.id.name.clone(),
943 info: Default::default(),
944 meta: vec![SourceRange::from(&variable_declaration.declaration.id).into()],
945 });
946 }
947 let rhs = rhs; let should_bind_name =
950 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
951 var_name != fn_name
955 } else {
956 true
959 };
960 if should_bind_name {
961 exec_state
962 .mut_stack()
963 .add(var_name.clone(), rhs.clone(), source_range)?;
964 }
965
966 if let Some(sketch_block_state) = exec_state.mod_local.sketch_block.as_mut()
967 && let KclValue::Segment { value } = &rhs
968 {
969 let segment_object_id = match &value.repr {
972 SegmentRepr::Unsolved { segment } => segment.object_id,
973 SegmentRepr::Solved { segment } => segment.object_id,
974 };
975 sketch_block_state
976 .segment_tags
977 .entry(segment_object_id)
978 .or_insert_with(|| {
979 let id_node = &variable_declaration.declaration.id;
980 Node::new(
981 TagDeclarator {
982 name: id_node.name.clone(),
983 digest: None,
984 },
985 id_node.start,
986 id_node.end,
987 id_node.module_id,
988 )
989 });
990 }
991
992 let should_show_in_feature_tree =
996 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
997 if should_show_in_feature_tree {
998 exec_state.push_op(Operation::VariableDeclaration {
999 name: var_name.clone(),
1000 value: OpKclValue::from(&rhs),
1001 visibility: variable_declaration.visibility,
1002 node_path: NodePath::placeholder(),
1003 source_range,
1004 });
1005 }
1006
1007 if let ItemVisibility::Export = variable_declaration.visibility {
1009 if matches!(body_type, BodyType::Root) {
1010 exec_state.mod_local.module_exports.push(var_name);
1011 } else {
1012 exec_state.err(CompilationIssue::err(
1013 variable_declaration.as_source_range(),
1014 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
1015 ));
1016 }
1017 }
1018 last_expr = matches!(body_type, BodyType::Root).then_some(rhs.continue_());
1020 }
1021 BodyItem::TypeDeclaration(ty) => {
1022 if exec_state.sketch_mode() {
1023 continue;
1024 }
1025
1026 let metadata = Metadata::from(&**ty);
1027 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
1028 match attrs.impl_ {
1029 annotations::Impl::Rust
1030 | annotations::Impl::RustConstrainable
1031 | annotations::Impl::RustConstraint => {
1032 let std_path = match &exec_state.mod_local.path {
1033 ModulePath::Std { value } => value,
1034 ModulePath::Local { .. } | ModulePath::Main => {
1035 return Err(KclError::new_semantic(KclErrorDetails::new(
1036 "User-defined types are not yet supported.".to_owned(),
1037 vec![metadata.source_range],
1038 )));
1039 }
1040 };
1041 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
1042 let value = KclValue::Type {
1043 value: TypeDef::RustRepr(t, props),
1044 meta: vec![metadata],
1045 experimental: attrs.experimental,
1046 };
1047 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1048 exec_state
1049 .mut_stack()
1050 .add(name_in_mem.clone(), value, metadata.source_range)
1051 .map_err(|_| {
1052 KclError::new_semantic(KclErrorDetails::new(
1053 format!("Redefinition of type {}.", ty.name.name),
1054 vec![metadata.source_range],
1055 ))
1056 })?;
1057
1058 if let ItemVisibility::Export = ty.visibility {
1059 exec_state.mod_local.module_exports.push(name_in_mem);
1060 }
1061 }
1062 annotations::Impl::Primitive => {}
1064 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
1065 Some(alias) => {
1066 let value = KclValue::Type {
1067 value: TypeDef::Alias(
1068 RuntimeType::from_parsed(
1069 alias.inner.clone(),
1070 exec_state,
1071 metadata.source_range,
1072 attrs.impl_ == annotations::Impl::KclConstrainable,
1073 false,
1074 )
1075 .map_err(|e| KclError::new_semantic(e.into()))?,
1076 ),
1077 meta: vec![metadata],
1078 experimental: attrs.experimental,
1079 };
1080 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1081 exec_state
1082 .mut_stack()
1083 .add(name_in_mem.clone(), value, metadata.source_range)
1084 .map_err(|_| {
1085 KclError::new_semantic(KclErrorDetails::new(
1086 format!("Redefinition of type {}.", ty.name.name),
1087 vec![metadata.source_range],
1088 ))
1089 })?;
1090
1091 if let ItemVisibility::Export = ty.visibility {
1092 exec_state.mod_local.module_exports.push(name_in_mem);
1093 }
1094 }
1095 None => {
1096 return Err(KclError::new_semantic(KclErrorDetails::new(
1097 "User-defined types are not yet supported.".to_owned(),
1098 vec![metadata.source_range],
1099 )));
1100 }
1101 },
1102 }
1103
1104 last_expr = None;
1105 }
1106 BodyItem::ReturnStatement(return_statement) => {
1107 if exec_state.sketch_mode() && sketch_mode_should_skip(&return_statement.argument) {
1108 continue;
1109 }
1110
1111 let metadata = Metadata::from(return_statement);
1112
1113 if matches!(body_type, BodyType::Root) {
1114 return Err(KclError::new_semantic(KclErrorDetails::new(
1115 "Cannot return from outside a function.".to_owned(),
1116 vec![metadata.source_range],
1117 )));
1118 }
1119
1120 let value_cf = self
1121 .execute_expr(
1122 &return_statement.argument,
1123 exec_state,
1124 &metadata,
1125 &[],
1126 StatementKind::Expression,
1127 )
1128 .await?;
1129 if value_cf.is_some_return() {
1130 last_expr = Some(value_cf);
1131 break;
1132 }
1133 let value = value_cf.into_value();
1134 exec_state
1135 .mut_stack()
1136 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
1137 .map_err(|_| {
1138 KclError::new_semantic(KclErrorDetails::new(
1139 "Multiple returns from a single function.".to_owned(),
1140 vec![metadata.source_range],
1141 ))
1142 })?;
1143 last_expr = None;
1144 }
1145 }
1146 }
1147
1148 if matches!(body_type, BodyType::Root) {
1149 exec_state
1151 .flush_batch(
1152 ModelingCmdMeta::new(exec_state, self, block.to_source_range()),
1153 true,
1156 )
1157 .await?;
1158 }
1159
1160 Ok(last_expr)
1161 }
1162
1163 pub async fn open_module(
1164 &self,
1165 path: &ImportPath,
1166 attrs: &[Node<Annotation>],
1167 resolved_path: &ModulePath,
1168 exec_state: &mut ExecState,
1169 source_range: SourceRange,
1170 ) -> Result<ModuleId, KclError> {
1171 match path {
1172 ImportPath::Kcl { .. } => {
1173 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
1174
1175 if let Some(id) = exec_state.id_for_module(resolved_path) {
1176 return Ok(id);
1177 }
1178
1179 let id = exec_state.next_module_id();
1180 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1182 let source = resolved_path.source(&self.fs, source_range).await?;
1183 exec_state.add_id_to_source(id, source.clone());
1184 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
1186 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1187
1188 Ok(id)
1189 }
1190 ImportPath::Foreign { .. } => {
1191 if let Some(id) = exec_state.id_for_module(resolved_path) {
1192 return Ok(id);
1193 }
1194
1195 let id = exec_state.next_module_id();
1196 let path = resolved_path.expect_path();
1197 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1199 let format = super::import::format_from_annotations(attrs, path, source_range)?;
1200 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
1201 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
1202 Ok(id)
1203 }
1204 ImportPath::Std { .. } => {
1205 if resolved_path.is_solver_module() && exec_state.mod_local.sketch_block.is_none() {
1206 return Err(KclError::new_semantic(KclErrorDetails::new(
1207 format!("The `{resolved_path}` module is only available inside sketch blocks."),
1208 vec![source_range],
1209 )));
1210 }
1211
1212 if let Some(id) = exec_state.id_for_module(resolved_path) {
1213 return Ok(id);
1214 }
1215
1216 let id = exec_state.next_module_id();
1217 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1219 let source = resolved_path.source(&self.fs, source_range).await?;
1220 exec_state.add_id_to_source(id, source.clone());
1221 let parsed = crate::parsing::parse_str(&source.source, id)
1222 .parse_errs_as_err()
1223 .unwrap();
1224 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1225 Ok(id)
1226 }
1227 }
1228 }
1229
1230 pub(super) async fn exec_module_for_items(
1231 &self,
1232 module_id: ModuleId,
1233 exec_state: &mut ExecState,
1234 source_range: SourceRange,
1235 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
1236 let path = exec_state.global.module_infos[&module_id].path.clone();
1237 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1238 let result = match &mut repr {
1241 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1242 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
1243 ModuleRepr::Kcl(program, cache) => self
1244 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1245 .await
1246 .map(|outcome| {
1247 *cache = Some(outcome.clone());
1248 (outcome.environment, outcome.exports)
1249 }),
1250 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1251 "Cannot import items from foreign modules".to_owned(),
1252 vec![geom.source_range],
1253 ))),
1254 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
1255 };
1256
1257 exec_state.global.module_infos[&module_id].restore_repr(repr);
1258 result
1259 }
1260
1261 async fn exec_module_for_result(
1262 &self,
1263 module_id: ModuleId,
1264 exec_state: &mut ExecState,
1265 source_range: SourceRange,
1266 ) -> Result<Option<KclValue>, KclError> {
1267 let path = exec_state.global.module_infos[&module_id].path.clone();
1268 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1269 let result = match &mut repr {
1272 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1273 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
1274 ModuleRepr::Kcl(program, cached_items) => {
1275 let result = self
1276 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1277 .await;
1278 match result {
1279 Ok(outcome) => {
1280 let value = outcome.last_expr.clone();
1281 *cached_items = Some(outcome);
1282 Ok(value)
1283 }
1284 Err(e) => Err(e),
1285 }
1286 }
1287 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
1288 ModuleRepr::Foreign(geom, cached) => {
1289 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
1290 .await
1291 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
1292
1293 match result {
1294 Ok(val) => {
1295 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
1296 Ok(val)
1297 }
1298 Err(e) => Err(e),
1299 }
1300 }
1301 ModuleRepr::Dummy => unreachable!(),
1302 };
1303
1304 exec_state.global.module_infos[&module_id].restore_repr(repr);
1305
1306 result
1307 }
1308
1309 pub async fn exec_module_from_ast(
1310 &self,
1311 program: &Node<Program>,
1312 module_id: ModuleId,
1313 path: &ModulePath,
1314 exec_state: &mut ExecState,
1315 source_range: SourceRange,
1316 preserve_mem: PreserveMem,
1317 ) -> Result<ModuleExecutionOutcome, KclError> {
1318 exec_state.global.mod_loader.enter_module(path);
1319 let result = self
1320 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
1321 .await;
1322 exec_state.global.mod_loader.leave_module(path, source_range)?;
1323
1324 result.map_err(|(err, _, _)| {
1327 match err {
1328 KclError::ImportCycle { .. } => {
1329 err.override_source_ranges(vec![source_range])
1331 }
1332 KclError::EngineHangup { .. } | KclError::EngineInternal { .. } => {
1333 err.override_source_ranges(vec![source_range])
1336 }
1337 _ => {
1338 KclError::new_semantic(KclErrorDetails::new(
1340 format!(
1341 "Error loading imported file ({path}). Open it to view more details.\n {}",
1342 err.message()
1343 ),
1344 vec![source_range],
1345 ))
1346 }
1347 }
1348 })
1349 }
1350
1351 #[async_recursion]
1352 pub(crate) async fn execute_expr<'a: 'async_recursion>(
1353 &self,
1354 init: &Expr,
1355 exec_state: &mut ExecState,
1356 metadata: &Metadata,
1357 annotations: &[Node<Annotation>],
1358 statement_kind: StatementKind<'a>,
1359 ) -> Result<KclValueControlFlow, KclError> {
1360 let item = match init {
1361 Expr::None(none) => KclValue::from(none).continue_(),
1362 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
1363 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
1364 Expr::Name(name) => {
1365 let being_declared = exec_state.mod_local.being_declared.clone();
1366 let value = name
1367 .get_result(exec_state, self)
1368 .await
1369 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
1370 .clone();
1371 if let KclValue::Module { value: module_id, meta } = value {
1372 self.exec_module_for_result(
1373 module_id,
1374 exec_state,
1375 metadata.source_range
1376 ).await?.map(|v| v.continue_())
1377 .unwrap_or_else(|| {
1378 exec_state.warn(CompilationIssue::err(
1379 metadata.source_range,
1380 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
1381 ),
1382 annotations::WARN_MOD_RETURN_VALUE);
1383
1384 let mut new_meta = vec![metadata.to_owned()];
1385 new_meta.extend(meta);
1386 KclValue::KclNone {
1387 value: Default::default(),
1388 meta: new_meta,
1389 }.continue_()
1390 })
1391 } else {
1392 value.continue_()
1393 }
1394 }
1395 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
1396 Expr::FunctionExpression(function_expression) => {
1397 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
1398 let experimental = attrs
1399 .as_ref()
1400 .map(|a| a.experimental)
1401 .unwrap_or_else(|| FnAttrs::default().experimental);
1403
1404 let include_in_feature_tree = attrs
1406 .as_ref()
1407 .map(|a| a.include_in_feature_tree)
1408 .unwrap_or_else(|| FnAttrs::default().include_in_feature_tree);
1410 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
1411 && (attrs.impl_ == annotations::Impl::Rust
1412 || attrs.impl_ == annotations::Impl::RustConstrainable
1413 || attrs.impl_ == annotations::Impl::RustConstraint)
1414 {
1415 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
1416 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
1417 (
1418 KclValue::Function {
1419 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
1420 meta: vec![metadata.to_owned()],
1421 },
1422 None,
1423 )
1424 } else {
1425 return Err(KclError::new_semantic(KclErrorDetails::new(
1426 "Rust implementation of functions is restricted to the standard library".to_owned(),
1427 vec![metadata.source_range],
1428 )));
1429 }
1430 } else {
1431 let std_props = function_expression
1432 .name_str()
1433 .and_then(|name| exec_state.mod_local.path.build_std_fully_qualified_name(name))
1434 .map(|name| StdFnProps::default(&name));
1435 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
1439 let dummy = EnvironmentRef::dummy();
1442 (dummy, Some(dummy))
1443 } else {
1444 (exec_state.mut_stack().snapshot(), None)
1445 };
1446 (
1447 KclValue::Function {
1448 value: Box::new(FunctionSource::kcl(
1449 function_expression.clone(),
1450 env_ref,
1451 KclFunctionSourceParams {
1452 std_props,
1453 experimental,
1454 include_in_feature_tree,
1455 },
1456 )),
1457 meta: vec![metadata.to_owned()],
1458 },
1459 placeholder_env_ref,
1460 )
1461 };
1462
1463 if let Some(fn_name) = &function_expression.name {
1466 if let Some(placeholder_env_ref) = placeholder_env_ref {
1470 closure = exec_state.mut_stack().add_recursive_closure(
1471 fn_name.name.to_owned(),
1472 closure,
1473 placeholder_env_ref,
1474 metadata.source_range,
1475 )?;
1476 } else {
1477 exec_state
1479 .mut_stack()
1480 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
1481 }
1482 }
1483
1484 closure.continue_()
1485 }
1486 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
1487 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
1488 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
1489 StatementKind::Declaration { name } => {
1490 let message = format!(
1491 "you cannot declare variable {name} as %, because % can only be used in function calls"
1492 );
1493
1494 return Err(KclError::new_semantic(KclErrorDetails::new(
1495 message,
1496 vec![pipe_substitution.into()],
1497 )));
1498 }
1499 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
1500 Some(x) => x.continue_(),
1501 None => {
1502 return Err(KclError::new_semantic(KclErrorDetails::new(
1503 "cannot use % outside a pipe expression".to_owned(),
1504 vec![pipe_substitution.into()],
1505 )));
1506 }
1507 },
1508 },
1509 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
1510 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
1511 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
1512 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
1513 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
1514 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
1515 Expr::LabelledExpression(expr) => {
1516 let value_cf = self
1517 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
1518 .await?;
1519 let value = control_continue!(value_cf);
1520 exec_state
1521 .mut_stack()
1522 .add(expr.label.name.clone(), value.clone(), init.into())?;
1523 value.continue_()
1525 }
1526 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
1527 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
1528 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
1529 };
1530 Ok(item)
1531 }
1532}
1533
1534fn sketch_mode_should_skip(expr: &Expr) -> bool {
1537 match expr {
1538 Expr::SketchBlock(sketch_block) => !sketch_block.is_being_edited,
1539 _ => true,
1540 }
1541}
1542
1543fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
1546 let KclError::UndefinedValue { name, mut details } = e else {
1547 return e;
1548 };
1549 if let (Some(name0), Some(name1)) = (&being_declared, &name)
1553 && name0 == name1
1554 {
1555 details.message = format!(
1556 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
1557 );
1558 }
1559 KclError::UndefinedValue { details, name }
1560}
1561
1562impl Node<AscribedExpression> {
1563 #[async_recursion]
1564 pub(super) async fn get_result(
1565 &self,
1566 exec_state: &mut ExecState,
1567 ctx: &ExecutorContext,
1568 ) -> Result<KclValueControlFlow, KclError> {
1569 let metadata = Metadata {
1570 source_range: SourceRange::from(self),
1571 };
1572 let result = ctx
1573 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
1574 .await?;
1575 let result = control_continue!(result);
1576 apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
1577 }
1578}
1579
1580impl Node<SketchBlock> {
1581 pub(super) async fn get_result(
1582 &self,
1583 exec_state: &mut ExecState,
1584 ctx: &ExecutorContext,
1585 ) -> Result<KclValueControlFlow, KclError> {
1586 if exec_state.mod_local.sketch_block.is_some() {
1587 return Err(KclError::new_semantic(KclErrorDetails::new(
1589 "Cannot execute a sketch block from within another sketch block".to_owned(),
1590 vec![SourceRange::from(self)],
1591 )));
1592 }
1593
1594 let range = SourceRange::from(self);
1595
1596 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
1598 Ok(x) => x,
1599 Err(cf_error) => match cf_error {
1600 EarlyReturn::Value(cf_value) => return Ok(cf_value),
1602 EarlyReturn::Error(err) => return Err(err),
1603 },
1604 };
1605 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
1606 object_id
1607 } else {
1608 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
1609 debug_assert!(false, "{message}");
1610 return Err(internal_err(message, range));
1611 };
1612 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
1613 let sketch_block_artifact_id = {
1614 use crate::execution::CodeRef;
1615 use crate::execution::SketchBlock;
1616 use crate::front::Plane;
1617 use crate::front::SourceRef;
1618
1619 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
1620
1621 let plane_artifact_id = on_object.map(|object| object.artifact_id);
1623
1624 let standard_plane = match &sketch_ctor_on {
1625 Plane::Default(plane) => Some(*plane),
1626 Plane::Object(_) => None,
1627 };
1628
1629 let artifact_id = ArtifactId::from(exec_state.next_uuid());
1630 let sketch_scene_object = Object {
1632 id: sketch_id,
1633 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1634 args: crate::front::SketchCtor { on: sketch_ctor_on },
1635 plane: on_object_id,
1636 segments: Default::default(),
1637 constraints: Default::default(),
1638 }),
1639 label: Default::default(),
1640 comments: Default::default(),
1641 artifact_id,
1642 source: SourceRef::new(self.into(), self.node_path.clone()),
1643 };
1644 exec_state.set_scene_object(sketch_scene_object);
1645
1646 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1648 id: artifact_id,
1649 standard_plane,
1650 plane_id: plane_artifact_id,
1651 path_id: None,
1655 code_ref: CodeRef::placeholder(range),
1656 sketch_id,
1657 }));
1658
1659 exec_state.push_op(Operation::GroupBegin {
1660 group: Group::SketchBlock { sketch_id },
1661 node_path: NodePath::placeholder(),
1662 source_range: range,
1663 });
1664 artifact_id
1665 };
1666
1667 let (return_result, variables, sketch_block_state) = {
1668 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1670
1671 let initial_sketch_block_state = {
1673 SketchBlockState {
1674 sketch_id: Some(sketch_id),
1675 ..Default::default()
1676 }
1677 };
1678
1679 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
1680
1681 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
1684
1685 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
1690 Ok(()) => {
1691 let parent = exec_state.mut_stack().snapshot();
1692 exec_state.mut_stack().push_new_env_for_call(parent);
1693 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1694 let block_variables = exec_state
1695 .stack()
1696 .find_all_in_current_env()
1697 .map(|(name, value)| (name.clone(), value.clone()))
1698 .collect::<IndexMap<_, _>>();
1699 exec_state.mut_stack().pop_env();
1700 (result, block_variables)
1701 }
1702 Err(err) => (Err(err), IndexMap::new()),
1703 };
1704
1705 exec_state.mod_local.sketch_mode = original_sketch_mode;
1706
1707 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1708
1709 exec_state.mut_stack().pop_env();
1711
1712 (result, block_variables, sketch_block_state)
1713 };
1714
1715 return_result?;
1717 let Some(sketch_block_state) = sketch_block_state else {
1718 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1719 return Err(internal_err(
1720 "Sketch block state should still be set to Some from just above",
1721 self,
1722 ));
1723 };
1724 let mut sketch_block_state = sketch_block_state;
1725
1726 let constraints = sketch_block_state
1728 .solver_constraints
1729 .iter()
1730 .cloned()
1731 .map(ezpz::ConstraintRequest::highest_priority)
1732 .chain(
1733 sketch_block_state
1735 .solver_optional_constraints
1736 .iter()
1737 .cloned()
1738 .map(|c| ezpz::ConstraintRequest::new(c, 1)),
1739 )
1740 .collect::<Vec<_>>();
1741 let initial_guesses = sketch_block_state
1742 .sketch_vars
1743 .iter()
1744 .map(|v| {
1745 let Some(sketch_var) = v.as_sketch_var() else {
1746 return Err(internal_err("Expected sketch variable", self));
1747 };
1748 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1749 let number_value = KclValue::Number {
1751 value: sketch_var.initial_value,
1752 ty: sketch_var.ty,
1753 meta: sketch_var.meta.clone(),
1754 };
1755 let initial_guess_value = normalize_to_solver_distance_unit(
1756 &number_value,
1757 v.into(),
1758 exec_state,
1759 "sketch variable initial value",
1760 )?;
1761 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1762 n.n
1763 } else {
1764 let message = format!(
1765 "Expected number after coercion, but found {}",
1766 initial_guess_value.human_friendly_type()
1767 );
1768 debug_assert!(false, "{}", &message);
1769 return Err(internal_err(message, self));
1770 };
1771 Ok((constraint_id, initial_guess))
1772 })
1773 .collect::<Result<Vec<_>, KclError>>()?;
1774 let config = ezpz::Config::default()
1776 .with_max_iterations(50)
1777 .with_convergence_tolerance(SOLVER_CONVERGENCE_TOLERANCE);
1778 let solve_result = if exec_state.mod_local.freedom_analysis {
1779 ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1780 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1781 (outcome.outcome, Some(freedom_analysis))
1782 })
1783 } else {
1784 ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1785 };
1786 let num_required_constraints = sketch_block_state.solver_constraints.len();
1788 let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
1789 .solver_constraints
1790 .iter()
1791 .cloned()
1792 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1793 .collect();
1794
1795 let (solve_outcome, solve_analysis) = match solve_result {
1796 Ok((solved, freedom)) => {
1797 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1798 (outcome, freedom)
1799 }
1800 Err(failure) => {
1801 match &failure.error {
1802 NonLinearSystemError::FaerMatrix { .. }
1803 | NonLinearSystemError::Faer { .. }
1804 | NonLinearSystemError::FaerSolve { .. }
1805 | NonLinearSystemError::FaerSvd(..)
1806 | NonLinearSystemError::DidNotConverge => {
1807 exec_state.warn(
1810 CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
1811 annotations::WARN_SOLVER,
1812 );
1813 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1814 (
1815 Solved {
1816 final_values,
1817 iterations: Default::default(),
1818 warnings: failure.warnings,
1819 priority_solved: Default::default(),
1820 variables_in_conflicts: Default::default(),
1821 },
1822 None,
1823 )
1824 }
1825 NonLinearSystemError::EmptySystemNotAllowed
1826 | NonLinearSystemError::WrongNumberGuesses { .. }
1827 | NonLinearSystemError::MissingGuess { .. }
1828 | NonLinearSystemError::NotFound(..) => {
1829 #[cfg(target_arch = "wasm32")]
1832 web_sys::console::error_1(
1833 &format!("Internal error from constraint solver: {}", &failure.error).into(),
1834 );
1835 return Err(internal_err(
1836 format!("Internal error from constraint solver: {}", &failure.error),
1837 self,
1838 ));
1839 }
1840 _ => {
1841 return Err(internal_err(
1843 format!("Error from constraint solver: {}", &failure.error),
1844 self,
1845 ));
1846 }
1847 }
1848 }
1849 };
1850 for warning in &solve_outcome.warnings {
1852 let message = if let Some(index) = warning.about_constraint.as_ref() {
1853 format!("{}; constraint index {}", &warning.content, index)
1854 } else {
1855 format!("{}", &warning.content)
1856 };
1857 exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
1858 }
1859 let sketch_engine_id = exec_state.next_uuid();
1861 let solution_ty = solver_numeric_type(exec_state);
1862 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1863 for unsolved_segment in &sketch_block_state.needed_by_engine {
1864 solved_segments.push(substitute_sketch_var_in_segment(
1865 unsolved_segment.clone(),
1866 &sketch_surface,
1867 sketch_engine_id,
1868 None,
1869 &solve_outcome,
1870 solver_numeric_type(exec_state),
1871 solve_analysis.as_ref(),
1872 )?);
1873 }
1874 exec_state.mod_local.artifacts.var_solutions =
1880 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1881
1882 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1884
1885 let sketch = create_segments_in_engine(
1887 &sketch_surface,
1888 sketch_engine_id,
1889 &mut solved_segments,
1890 &sketch_block_state.segment_tags,
1891 ctx,
1892 exec_state,
1893 range,
1894 )
1895 .await?;
1896
1897 if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
1899 if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
1900 exec_state.artifact_mut(sketch_block_artifact_id)
1901 {
1902 sketch_block_artifact.path_id = Some(sketch_artifact_id);
1903 } else {
1904 let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
1905 debug_assert!(false, "{message}");
1906 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1907 }
1908 }
1909
1910 let variables = substitute_sketch_vars(
1915 variables,
1916 &sketch_surface,
1917 sketch_engine_id,
1918 sketch.as_ref(),
1919 &solve_outcome,
1920 solution_ty,
1921 solve_analysis.as_ref(),
1922 )?;
1923
1924 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1925 for scene_object in scene_objects {
1926 segment_object_ids.push(scene_object.id);
1927 exec_state.set_scene_object(scene_object);
1929 }
1930 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1932 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1933 debug_assert!(false, "{}", &message);
1934 return Err(internal_err(message, range));
1935 };
1936 let ObjectKind::Sketch(front_sketch) = &mut sketch_object.kind else {
1937 let message = format!(
1938 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1939 sketch_id, sketch_object
1940 );
1941 debug_assert!(
1942 false,
1943 "{}; scene_objects={:#?}",
1944 &message, &exec_state.mod_local.artifacts.scene_objects
1945 );
1946 return Err(internal_err(message, range));
1947 };
1948 front_sketch.segments.extend(segment_object_ids);
1949 front_sketch
1951 .constraints
1952 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1953
1954 exec_state.push_op(Operation::GroupEnd);
1956
1957 if exec_state.mod_local.freedom_analysis {
1961 let status = {
1962 let scene_objects = &exec_state.mod_local.artifacts.scene_objects;
1963 scene_objects
1964 .get(sketch_id.0)
1965 .and_then(|obj| sketch_constraint_status_for_sketch(scene_objects, obj))
1966 };
1967 if let Some(status) = status
1968 && status.status == ConstraintKind::OverConstrained
1969 {
1970 let description = if status.conflict_count == 1 {
1971 "segment has"
1972 } else {
1973 "segments have"
1974 };
1975 let message = format!(
1976 "Sketch is over-constrained: {} {description} conflicting constraints",
1977 status.conflict_count,
1978 );
1979 exec_state.warn(
1980 CompilationIssue::err(range, message),
1981 annotations::WARN_OVER_CONSTRAINED_SKETCH,
1982 );
1983 }
1984 }
1985
1986 let properties = self.sketch_properties(sketch, variables);
1987 let metadata = Metadata {
1988 source_range: SourceRange::from(self),
1989 };
1990 let return_value = KclValue::Object {
1991 value: properties,
1992 constrainable: Default::default(),
1993 meta: vec![metadata],
1994 };
1995 Ok(if self.is_being_edited {
1996 return_value.exit()
1999 } else {
2000 return_value.continue_()
2001 })
2002 }
2003
2004 async fn exec_arguments(
2014 &self,
2015 exec_state: &mut ExecState,
2016 ctx: &ExecutorContext,
2017 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2018 let range = SourceRange::from(self);
2019
2020 if !exec_state.sketch_mode() {
2021 let mut labeled = IndexMap::new();
2027 for labeled_arg in &self.arguments {
2028 let source_range = SourceRange::from(labeled_arg.arg.clone());
2029 let metadata = Metadata { source_range };
2030 let value_cf = ctx
2031 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
2032 .await?;
2033 let value = early_return!(value_cf);
2034 let arg = Arg::new(value, source_range);
2035 match &labeled_arg.label {
2036 Some(label) => {
2037 labeled.insert(label.name.clone(), arg);
2038 }
2039 None => {
2040 let name = labeled_arg.arg.ident_name();
2041 if let Some(name) = name {
2042 labeled.insert(name.to_owned(), arg);
2043 } else {
2044 return Err(KclError::new_semantic(KclErrorDetails::new(
2045 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
2046 vec![SourceRange::from(&labeled_arg.arg)],
2047 ))
2048 .into());
2049 }
2050 }
2051 }
2052 }
2053 let mut args = Args::new_no_args(
2054 range,
2055 self.node_path.clone(),
2056 ctx.clone(),
2057 Some("sketch block".to_owned()),
2058 );
2059 args.labeled = labeled;
2060
2061 let arg_on_value: KclValue =
2062 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
2063
2064 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2065 let message =
2066 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2067 debug_assert!(false, "{message}");
2068 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2069 };
2070 let mut sketch_surface = arg_on.into_sketch_surface();
2071
2072 match &mut sketch_surface {
2075 SketchSurface::Plane(plane) => {
2076 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
2078 }
2079 SketchSurface::Face(_) => {
2080 }
2082 }
2083
2084 let sketch_id = exec_state.next_object_id();
2090 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2091 let on_cache_name = sketch_on_cache_name(sketch_id);
2092 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
2094
2095 Ok((sketch_id, sketch_surface))
2096 } else {
2097 let sketch_id = exec_state.next_object_id();
2104 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2105 let on_cache_name = sketch_on_cache_name(sketch_id);
2106 let arg_on_value = exec_state.stack().get(&on_cache_name, range)?.clone();
2107
2108 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2109 let message =
2110 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2111 debug_assert!(false, "{message}");
2112 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2113 };
2114 let mut sketch_surface = arg_on.into_sketch_surface();
2115
2116 if sketch_surface.object_id().is_none() {
2119 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
2122 return Err(internal_err(
2123 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
2124 range,
2125 )
2126 .into());
2127 };
2128 sketch_surface.set_object_id(last_object.id);
2129 }
2130
2131 Ok((sketch_id, sketch_surface))
2132 }
2133 }
2134
2135 async fn load_sketch2_into_current_scope(
2136 &self,
2137 exec_state: &mut ExecState,
2138 ctx: &ExecutorContext,
2139 source_range: SourceRange,
2140 ) -> Result<(), KclError> {
2141 let path = vec!["std".to_owned(), "solver".to_owned()];
2142 let resolved_path = ModulePath::from_std_import_path(&path)?;
2143 let module_id = ctx
2144 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
2145 .await?;
2146 let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
2147
2148 for name in exports {
2149 let value = exec_state
2150 .stack()
2151 .memory
2152 .get_from(&name, env_ref, source_range, 0)?
2153 .clone();
2154 exec_state.mut_stack().add(name, value, source_range)?;
2155 }
2156 Ok(())
2157 }
2158
2159 pub(crate) fn sketch_properties(
2163 &self,
2164 sketch: Option<Sketch>,
2165 variables: HashMap<String, KclValue>,
2166 ) -> HashMap<String, KclValue> {
2167 let Some(sketch) = sketch else {
2168 return variables;
2171 };
2172
2173 let mut properties = variables;
2174
2175 let sketch_value = KclValue::Sketch {
2176 value: Box::new(sketch),
2177 };
2178 let mut meta_map = HashMap::with_capacity(1);
2179 meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
2180 let meta_value = KclValue::Object {
2181 value: meta_map,
2182 constrainable: false,
2183 meta: vec![Metadata {
2184 source_range: SourceRange::from(self),
2185 }],
2186 };
2187
2188 properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
2189
2190 properties
2191 }
2192}
2193
2194impl SketchBlock {
2195 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
2196 exec_state.mut_stack().push_new_env_for_call(parent);
2197 }
2198}
2199
2200impl Node<SketchVar> {
2201 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2202 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2203 return Err(KclError::new_semantic(KclErrorDetails::new(
2204 "Cannot use a sketch variable outside of a sketch block".to_owned(),
2205 vec![SourceRange::from(self)],
2206 )));
2207 };
2208 let id = sketch_block_state.next_sketch_var_id();
2209 let sketch_var = if let Some(initial) = &self.initial {
2210 KclValue::from_sketch_var_literal(initial, id, exec_state)
2211 } else {
2212 let metadata = Metadata {
2213 source_range: SourceRange::from(self),
2214 };
2215
2216 KclValue::SketchVar {
2217 value: Box::new(super::SketchVar {
2218 id,
2219 initial_value: 0.0,
2220 ty: NumericType::default(),
2221 meta: vec![metadata],
2222 }),
2223 }
2224 };
2225
2226 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2227 return Err(KclError::new_semantic(KclErrorDetails::new(
2228 "Cannot use a sketch variable outside of a sketch block".to_owned(),
2229 vec![SourceRange::from(self)],
2230 )));
2231 };
2232 sketch_block_state.sketch_vars.push(sketch_var.clone());
2233
2234 Ok(sketch_var)
2235 }
2236}
2237
2238fn apply_ascription(
2239 value: &KclValue,
2240 ty: &Node<Type>,
2241 exec_state: &mut ExecState,
2242 source_range: SourceRange,
2243) -> Result<KclValue, KclError> {
2244 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
2245 .map_err(|e| KclError::new_semantic(e.into()))?;
2246
2247 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
2248 exec_state.clear_units_warnings(&source_range);
2249 }
2250
2251 value.coerce(&ty, false, exec_state).map_err(|_| {
2252 let suggestion = if ty == RuntimeType::length() {
2253 ", you might try coercing to a fully specified numeric type such as `mm`"
2254 } else if ty == RuntimeType::angle() {
2255 ", you might try coercing to a fully specified numeric type such as `deg`"
2256 } else {
2257 ""
2258 };
2259 let ty_str = if let Some(ty) = value.principal_type() {
2260 format!("(with type `{ty}`) ")
2261 } else {
2262 String::new()
2263 };
2264 KclError::new_semantic(KclErrorDetails::new(
2265 format!(
2266 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
2267 value.human_friendly_type()
2268 ),
2269 vec![source_range],
2270 ))
2271 })
2272}
2273
2274impl BinaryPart {
2275 #[async_recursion]
2276 pub(super) async fn get_result(
2277 &self,
2278 exec_state: &mut ExecState,
2279 ctx: &ExecutorContext,
2280 ) -> Result<KclValueControlFlow, KclError> {
2281 match self {
2282 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
2283 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
2284 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
2285 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
2286 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
2287 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
2288 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
2289 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
2290 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
2291 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
2292 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
2293 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
2294 }
2295 }
2296}
2297
2298impl Node<Name> {
2299 pub(super) async fn get_result<'a>(
2300 &self,
2301 exec_state: &'a mut ExecState,
2302 ctx: &ExecutorContext,
2303 ) -> Result<&'a KclValue, KclError> {
2304 let being_declared = exec_state.mod_local.being_declared.clone();
2305 self.get_result_inner(exec_state, ctx)
2306 .await
2307 .map_err(|e| var_in_own_ref_err(e, &being_declared))
2308 }
2309
2310 async fn get_result_inner<'a>(
2311 &self,
2312 exec_state: &'a mut ExecState,
2313 ctx: &ExecutorContext,
2314 ) -> Result<&'a KclValue, KclError> {
2315 if self.abs_path {
2316 return Err(KclError::new_semantic(KclErrorDetails::new(
2317 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
2318 self.as_source_ranges(),
2319 )));
2320 }
2321
2322 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
2323
2324 if self.path.is_empty() {
2325 let item_value = exec_state.stack().get(&self.name.name, self.into());
2326 if item_value.is_ok() {
2327 return item_value;
2328 }
2329 return exec_state.stack().get(&mod_name, self.into());
2330 }
2331
2332 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
2333 for p in &self.path {
2334 let value = match mem_spec {
2335 Some((env, exports)) => {
2336 if !exports.contains(&p.name) {
2337 return Err(KclError::new_semantic(KclErrorDetails::new(
2338 format!("Item {} not found in module's exported items", p.name),
2339 p.as_source_ranges(),
2340 )));
2341 }
2342
2343 exec_state
2344 .stack()
2345 .memory
2346 .get_from(&p.name, env, p.as_source_range(), 0)?
2347 }
2348 None => exec_state
2349 .stack()
2350 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
2351 };
2352
2353 let KclValue::Module { value: module_id, .. } = value else {
2354 return Err(KclError::new_semantic(KclErrorDetails::new(
2355 format!(
2356 "Identifier in path must refer to a module, found {}",
2357 value.human_friendly_type()
2358 ),
2359 p.as_source_ranges(),
2360 )));
2361 };
2362
2363 mem_spec = Some(
2364 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
2365 .await?,
2366 );
2367 }
2368
2369 let (env, exports) = mem_spec.unwrap();
2370
2371 let item_exported = exports.contains(&self.name.name);
2372 let item_value = exec_state
2373 .stack()
2374 .memory
2375 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
2376
2377 if item_exported && item_value.is_ok() {
2379 return item_value;
2380 }
2381
2382 let mod_exported = exports.contains(&mod_name);
2383 let mod_value = exec_state
2384 .stack()
2385 .memory
2386 .get_from(&mod_name, env, self.name.as_source_range(), 0);
2387
2388 if mod_exported && mod_value.is_ok() {
2390 return mod_value;
2391 }
2392
2393 if item_value.is_err() && mod_value.is_err() {
2395 return item_value;
2396 }
2397
2398 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
2400 Err(KclError::new_semantic(KclErrorDetails::new(
2401 format!("Item {} not found in module's exported items", self.name.name),
2402 self.name.as_source_ranges(),
2403 )))
2404 }
2405}
2406
2407impl Node<MemberExpression> {
2408 async fn get_result(
2409 &self,
2410 exec_state: &mut ExecState,
2411 ctx: &ExecutorContext,
2412 ) -> Result<KclValueControlFlow, KclError> {
2413 let meta = Metadata {
2414 source_range: SourceRange::from(self),
2415 };
2416 let property = Property::try_from(
2419 self.computed,
2420 self.property.clone(),
2421 exec_state,
2422 self.into(),
2423 ctx,
2424 &meta,
2425 &[],
2426 StatementKind::Expression,
2427 )
2428 .await?;
2429 let object_cf = ctx
2430 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
2431 .await?;
2432 let object = control_continue!(object_cf);
2433
2434 match (object, property, self.computed) {
2436 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
2437 "at" => match &segment.repr {
2438 SegmentRepr::Unsolved { segment } => {
2439 match &segment.kind {
2440 UnsolvedSegmentKind::Point { position, .. } => {
2441 Ok(KclValue::HomArray {
2443 value: vec![
2444 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
2445 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
2446 ],
2447 ty: RuntimeType::any(),
2448 }
2449 .continue_())
2450 }
2451 _ => Err(KclError::new_undefined_value(
2452 KclErrorDetails::new(
2453 format!("Property '{property}' not found in segment"),
2454 vec![self.clone().into()],
2455 ),
2456 None,
2457 )),
2458 }
2459 }
2460 SegmentRepr::Solved { segment } => {
2461 match &segment.kind {
2462 SegmentKind::Point { position, .. } => {
2463 Ok(KclValue::array_from_point2d(
2465 [position[0].n, position[1].n],
2466 position[0].ty,
2467 segment.meta.clone(),
2468 )
2469 .continue_())
2470 }
2471 _ => Err(KclError::new_undefined_value(
2472 KclErrorDetails::new(
2473 format!("Property '{property}' not found in segment"),
2474 vec![self.clone().into()],
2475 ),
2476 None,
2477 )),
2478 }
2479 }
2480 },
2481 "start" => match &segment.repr {
2482 SegmentRepr::Unsolved { segment } => match &segment.kind {
2483 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2484 KclErrorDetails::new(
2485 format!("Property '{property}' not found in point segment"),
2486 vec![self.clone().into()],
2487 ),
2488 None,
2489 )),
2490 UnsolvedSegmentKind::Line {
2491 start,
2492 ctor,
2493 start_object_id,
2494 ..
2495 } => Ok(KclValue::Segment {
2496 value: Box::new(AbstractSegment {
2497 repr: SegmentRepr::Unsolved {
2498 segment: Box::new(UnsolvedSegment {
2499 id: segment.id,
2500 object_id: *start_object_id,
2501 kind: UnsolvedSegmentKind::Point {
2502 position: start.clone(),
2503 ctor: Box::new(PointCtor {
2504 position: ctor.start.clone(),
2505 }),
2506 },
2507 tag: segment.tag.clone(),
2508 node_path: segment.node_path.clone(),
2509 meta: segment.meta.clone(),
2510 }),
2511 },
2512 meta: segment.meta.clone(),
2513 }),
2514 }
2515 .continue_()),
2516 UnsolvedSegmentKind::Arc {
2517 start,
2518 ctor,
2519 start_object_id,
2520 ..
2521 } => Ok(KclValue::Segment {
2522 value: Box::new(AbstractSegment {
2523 repr: SegmentRepr::Unsolved {
2524 segment: Box::new(UnsolvedSegment {
2525 id: segment.id,
2526 object_id: *start_object_id,
2527 kind: UnsolvedSegmentKind::Point {
2528 position: start.clone(),
2529 ctor: Box::new(PointCtor {
2530 position: ctor.start.clone(),
2531 }),
2532 },
2533 tag: segment.tag.clone(),
2534 node_path: segment.node_path.clone(),
2535 meta: segment.meta.clone(),
2536 }),
2537 },
2538 meta: segment.meta.clone(),
2539 }),
2540 }
2541 .continue_()),
2542 UnsolvedSegmentKind::Circle {
2543 start,
2544 ctor,
2545 start_object_id,
2546 ..
2547 } => Ok(KclValue::Segment {
2548 value: Box::new(AbstractSegment {
2549 repr: SegmentRepr::Unsolved {
2550 segment: Box::new(UnsolvedSegment {
2551 id: segment.id,
2552 object_id: *start_object_id,
2553 kind: UnsolvedSegmentKind::Point {
2554 position: start.clone(),
2555 ctor: Box::new(PointCtor {
2556 position: ctor.start.clone(),
2557 }),
2558 },
2559 tag: segment.tag.clone(),
2560 node_path: segment.node_path.clone(),
2561 meta: segment.meta.clone(),
2562 }),
2563 },
2564 meta: segment.meta.clone(),
2565 }),
2566 }
2567 .continue_()),
2568 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2569 KclErrorDetails::new(
2570 format!("Property '{property}' not found in segment"),
2571 vec![self.clone().into()],
2572 ),
2573 None,
2574 )),
2575 },
2576 SegmentRepr::Solved { segment } => match &segment.kind {
2577 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2578 KclErrorDetails::new(
2579 format!("Property '{property}' not found in point segment"),
2580 vec![self.clone().into()],
2581 ),
2582 None,
2583 )),
2584 SegmentKind::Line {
2585 start,
2586 ctor,
2587 start_object_id,
2588 start_freedom,
2589 ..
2590 } => Ok(KclValue::Segment {
2591 value: Box::new(AbstractSegment {
2592 repr: SegmentRepr::Solved {
2593 segment: Box::new(Segment {
2594 id: segment.id,
2595 object_id: *start_object_id,
2596 kind: SegmentKind::Point {
2597 position: start.clone(),
2598 ctor: Box::new(PointCtor {
2599 position: ctor.start.clone(),
2600 }),
2601 freedom: *start_freedom,
2602 },
2603 surface: segment.surface.clone(),
2604 sketch_id: segment.sketch_id,
2605 sketch: segment.sketch.clone(),
2606 tag: segment.tag.clone(),
2607 node_path: segment.node_path.clone(),
2608 meta: segment.meta.clone(),
2609 }),
2610 },
2611 meta: segment.meta.clone(),
2612 }),
2613 }
2614 .continue_()),
2615 SegmentKind::Arc {
2616 start,
2617 ctor,
2618 start_object_id,
2619 start_freedom,
2620 ..
2621 } => Ok(KclValue::Segment {
2622 value: Box::new(AbstractSegment {
2623 repr: SegmentRepr::Solved {
2624 segment: Box::new(Segment {
2625 id: segment.id,
2626 object_id: *start_object_id,
2627 kind: SegmentKind::Point {
2628 position: start.clone(),
2629 ctor: Box::new(PointCtor {
2630 position: ctor.start.clone(),
2631 }),
2632 freedom: *start_freedom,
2633 },
2634 surface: segment.surface.clone(),
2635 sketch_id: segment.sketch_id,
2636 sketch: segment.sketch.clone(),
2637 tag: segment.tag.clone(),
2638 node_path: segment.node_path.clone(),
2639 meta: segment.meta.clone(),
2640 }),
2641 },
2642 meta: segment.meta.clone(),
2643 }),
2644 }
2645 .continue_()),
2646 SegmentKind::Circle {
2647 start,
2648 ctor,
2649 start_object_id,
2650 start_freedom,
2651 ..
2652 } => Ok(KclValue::Segment {
2653 value: Box::new(AbstractSegment {
2654 repr: SegmentRepr::Solved {
2655 segment: Box::new(Segment {
2656 id: segment.id,
2657 object_id: *start_object_id,
2658 kind: SegmentKind::Point {
2659 position: start.clone(),
2660 ctor: Box::new(PointCtor {
2661 position: ctor.start.clone(),
2662 }),
2663 freedom: *start_freedom,
2664 },
2665 surface: segment.surface.clone(),
2666 sketch_id: segment.sketch_id,
2667 sketch: segment.sketch.clone(),
2668 tag: segment.tag.clone(),
2669 node_path: segment.node_path.clone(),
2670 meta: segment.meta.clone(),
2671 }),
2672 },
2673 meta: segment.meta.clone(),
2674 }),
2675 }
2676 .continue_()),
2677 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2678 KclErrorDetails::new(
2679 format!("Property '{property}' not found in segment"),
2680 vec![self.clone().into()],
2681 ),
2682 None,
2683 )),
2684 },
2685 },
2686 "end" => match &segment.repr {
2687 SegmentRepr::Unsolved { segment } => match &segment.kind {
2688 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2689 KclErrorDetails::new(
2690 format!("Property '{property}' not found in point segment"),
2691 vec![self.clone().into()],
2692 ),
2693 None,
2694 )),
2695 UnsolvedSegmentKind::Line {
2696 end,
2697 ctor,
2698 end_object_id,
2699 ..
2700 } => Ok(KclValue::Segment {
2701 value: Box::new(AbstractSegment {
2702 repr: SegmentRepr::Unsolved {
2703 segment: Box::new(UnsolvedSegment {
2704 id: segment.id,
2705 object_id: *end_object_id,
2706 kind: UnsolvedSegmentKind::Point {
2707 position: end.clone(),
2708 ctor: Box::new(PointCtor {
2709 position: ctor.end.clone(),
2710 }),
2711 },
2712 tag: segment.tag.clone(),
2713 node_path: segment.node_path.clone(),
2714 meta: segment.meta.clone(),
2715 }),
2716 },
2717 meta: segment.meta.clone(),
2718 }),
2719 }
2720 .continue_()),
2721 UnsolvedSegmentKind::Arc {
2722 end,
2723 ctor,
2724 end_object_id,
2725 ..
2726 } => Ok(KclValue::Segment {
2727 value: Box::new(AbstractSegment {
2728 repr: SegmentRepr::Unsolved {
2729 segment: Box::new(UnsolvedSegment {
2730 id: segment.id,
2731 object_id: *end_object_id,
2732 kind: UnsolvedSegmentKind::Point {
2733 position: end.clone(),
2734 ctor: Box::new(PointCtor {
2735 position: ctor.end.clone(),
2736 }),
2737 },
2738 tag: segment.tag.clone(),
2739 node_path: segment.node_path.clone(),
2740 meta: segment.meta.clone(),
2741 }),
2742 },
2743 meta: segment.meta.clone(),
2744 }),
2745 }
2746 .continue_()),
2747 UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2748 KclErrorDetails::new(
2749 format!("Property '{property}' not found in segment"),
2750 vec![self.into()],
2751 ),
2752 None,
2753 )),
2754 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2755 KclErrorDetails::new(
2756 format!("Property '{property}' not found in segment"),
2757 vec![self.clone().into()],
2758 ),
2759 None,
2760 )),
2761 },
2762 SegmentRepr::Solved { segment } => match &segment.kind {
2763 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2764 KclErrorDetails::new(
2765 format!("Property '{property}' not found in point segment"),
2766 vec![self.clone().into()],
2767 ),
2768 None,
2769 )),
2770 SegmentKind::Line {
2771 end,
2772 ctor,
2773 end_object_id,
2774 end_freedom,
2775 ..
2776 } => Ok(KclValue::Segment {
2777 value: Box::new(AbstractSegment {
2778 repr: SegmentRepr::Solved {
2779 segment: Box::new(Segment {
2780 id: segment.id,
2781 object_id: *end_object_id,
2782 kind: SegmentKind::Point {
2783 position: end.clone(),
2784 ctor: Box::new(PointCtor {
2785 position: ctor.end.clone(),
2786 }),
2787 freedom: *end_freedom,
2788 },
2789 surface: segment.surface.clone(),
2790 sketch_id: segment.sketch_id,
2791 sketch: segment.sketch.clone(),
2792 tag: segment.tag.clone(),
2793 node_path: segment.node_path.clone(),
2794 meta: segment.meta.clone(),
2795 }),
2796 },
2797 meta: segment.meta.clone(),
2798 }),
2799 }
2800 .continue_()),
2801 SegmentKind::Arc {
2802 end,
2803 ctor,
2804 end_object_id,
2805 end_freedom,
2806 ..
2807 } => Ok(KclValue::Segment {
2808 value: Box::new(AbstractSegment {
2809 repr: SegmentRepr::Solved {
2810 segment: Box::new(Segment {
2811 id: segment.id,
2812 object_id: *end_object_id,
2813 kind: SegmentKind::Point {
2814 position: end.clone(),
2815 ctor: Box::new(PointCtor {
2816 position: ctor.end.clone(),
2817 }),
2818 freedom: *end_freedom,
2819 },
2820 surface: segment.surface.clone(),
2821 sketch_id: segment.sketch_id,
2822 sketch: segment.sketch.clone(),
2823 tag: segment.tag.clone(),
2824 node_path: segment.node_path.clone(),
2825 meta: segment.meta.clone(),
2826 }),
2827 },
2828 meta: segment.meta.clone(),
2829 }),
2830 }
2831 .continue_()),
2832 SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2833 KclErrorDetails::new(
2834 format!("Property '{property}' not found in segment"),
2835 vec![self.into()],
2836 ),
2837 None,
2838 )),
2839 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2840 KclErrorDetails::new(
2841 format!("Property '{property}' not found in segment"),
2842 vec![self.clone().into()],
2843 ),
2844 None,
2845 )),
2846 },
2847 },
2848 "center" => match &segment.repr {
2849 SegmentRepr::Unsolved { segment } => match &segment.kind {
2850 UnsolvedSegmentKind::Arc {
2851 center,
2852 ctor,
2853 center_object_id,
2854 ..
2855 } => Ok(KclValue::Segment {
2856 value: Box::new(AbstractSegment {
2857 repr: SegmentRepr::Unsolved {
2858 segment: Box::new(UnsolvedSegment {
2859 id: segment.id,
2860 object_id: *center_object_id,
2861 kind: UnsolvedSegmentKind::Point {
2862 position: center.clone(),
2863 ctor: Box::new(PointCtor {
2864 position: ctor.center.clone(),
2865 }),
2866 },
2867 tag: segment.tag.clone(),
2868 node_path: segment.node_path.clone(),
2869 meta: segment.meta.clone(),
2870 }),
2871 },
2872 meta: segment.meta.clone(),
2873 }),
2874 }
2875 .continue_()),
2876 UnsolvedSegmentKind::Circle {
2877 center,
2878 ctor,
2879 center_object_id,
2880 ..
2881 } => Ok(KclValue::Segment {
2882 value: Box::new(AbstractSegment {
2883 repr: SegmentRepr::Unsolved {
2884 segment: Box::new(UnsolvedSegment {
2885 id: segment.id,
2886 object_id: *center_object_id,
2887 kind: UnsolvedSegmentKind::Point {
2888 position: center.clone(),
2889 ctor: Box::new(PointCtor {
2890 position: ctor.center.clone(),
2891 }),
2892 },
2893 tag: segment.tag.clone(),
2894 node_path: segment.node_path.clone(),
2895 meta: segment.meta.clone(),
2896 }),
2897 },
2898 meta: segment.meta.clone(),
2899 }),
2900 }
2901 .continue_()),
2902 _ => Err(KclError::new_undefined_value(
2903 KclErrorDetails::new(
2904 format!("Property '{property}' not found in segment"),
2905 vec![self.clone().into()],
2906 ),
2907 None,
2908 )),
2909 },
2910 SegmentRepr::Solved { segment } => match &segment.kind {
2911 SegmentKind::Arc {
2912 center,
2913 ctor,
2914 center_object_id,
2915 center_freedom,
2916 ..
2917 } => Ok(KclValue::Segment {
2918 value: Box::new(AbstractSegment {
2919 repr: SegmentRepr::Solved {
2920 segment: Box::new(Segment {
2921 id: segment.id,
2922 object_id: *center_object_id,
2923 kind: SegmentKind::Point {
2924 position: center.clone(),
2925 ctor: Box::new(PointCtor {
2926 position: ctor.center.clone(),
2927 }),
2928 freedom: *center_freedom,
2929 },
2930 surface: segment.surface.clone(),
2931 sketch_id: segment.sketch_id,
2932 sketch: segment.sketch.clone(),
2933 tag: segment.tag.clone(),
2934 node_path: segment.node_path.clone(),
2935 meta: segment.meta.clone(),
2936 }),
2937 },
2938 meta: segment.meta.clone(),
2939 }),
2940 }
2941 .continue_()),
2942 SegmentKind::Circle {
2943 center,
2944 ctor,
2945 center_object_id,
2946 center_freedom,
2947 ..
2948 } => Ok(KclValue::Segment {
2949 value: Box::new(AbstractSegment {
2950 repr: SegmentRepr::Solved {
2951 segment: Box::new(Segment {
2952 id: segment.id,
2953 object_id: *center_object_id,
2954 kind: SegmentKind::Point {
2955 position: center.clone(),
2956 ctor: Box::new(PointCtor {
2957 position: ctor.center.clone(),
2958 }),
2959 freedom: *center_freedom,
2960 },
2961 surface: segment.surface.clone(),
2962 sketch_id: segment.sketch_id,
2963 sketch: segment.sketch.clone(),
2964 tag: segment.tag.clone(),
2965 node_path: segment.node_path.clone(),
2966 meta: segment.meta.clone(),
2967 }),
2968 },
2969 meta: segment.meta.clone(),
2970 }),
2971 }
2972 .continue_()),
2973 _ => Err(KclError::new_undefined_value(
2974 KclErrorDetails::new(
2975 format!("Property '{property}' not found in segment"),
2976 vec![self.clone().into()],
2977 ),
2978 None,
2979 )),
2980 },
2981 },
2982 "controls" => match &segment.repr {
2983 SegmentRepr::Unsolved { segment } => match &segment.kind {
2984 UnsolvedSegmentKind::ControlPointSpline {
2985 controls,
2986 ctor,
2987 control_object_ids,
2988 ..
2989 } => Ok(KclValue::HomArray {
2990 value: controls
2991 .iter()
2992 .zip(control_object_ids.iter())
2993 .zip(ctor.points.iter())
2994 .map(|((position, object_id), ctor_point)| KclValue::Segment {
2995 value: Box::new(AbstractSegment {
2996 repr: SegmentRepr::Unsolved {
2997 segment: Box::new(UnsolvedSegment {
2998 id: segment.id,
2999 object_id: *object_id,
3000 kind: UnsolvedSegmentKind::Point {
3001 position: position.clone(),
3002 ctor: Box::new(PointCtor {
3003 position: ctor_point.clone(),
3004 }),
3005 },
3006 tag: segment.tag.clone(),
3007 node_path: segment.node_path.clone(),
3008 meta: segment.meta.clone(),
3009 }),
3010 },
3011 meta: segment.meta.clone(),
3012 }),
3013 })
3014 .collect(),
3015 ty: RuntimeType::segment(),
3016 }
3017 .continue_()),
3018 _ => Err(KclError::new_undefined_value(
3019 KclErrorDetails::new(
3020 format!("Property '{property}' not found in segment"),
3021 vec![self.clone().into()],
3022 ),
3023 None,
3024 )),
3025 },
3026 SegmentRepr::Solved { segment } => match &segment.kind {
3027 SegmentKind::ControlPointSpline {
3028 controls,
3029 ctor,
3030 control_object_ids,
3031 control_freedoms,
3032 ..
3033 } => Ok(KclValue::HomArray {
3034 value: controls
3035 .iter()
3036 .zip(control_object_ids.iter())
3037 .zip(control_freedoms.iter())
3038 .zip(ctor.points.iter())
3039 .map(|(((position, object_id), freedom), ctor_point)| KclValue::Segment {
3040 value: Box::new(AbstractSegment {
3041 repr: SegmentRepr::Solved {
3042 segment: Box::new(Segment {
3043 id: segment.id,
3044 object_id: *object_id,
3045 kind: SegmentKind::Point {
3046 position: position.clone(),
3047 ctor: Box::new(PointCtor {
3048 position: ctor_point.clone(),
3049 }),
3050 freedom: *freedom,
3051 },
3052 surface: segment.surface.clone(),
3053 sketch_id: segment.sketch_id,
3054 sketch: segment.sketch.clone(),
3055 tag: segment.tag.clone(),
3056 node_path: segment.node_path.clone(),
3057 meta: segment.meta.clone(),
3058 }),
3059 },
3060 meta: segment.meta.clone(),
3061 }),
3062 })
3063 .collect(),
3064 ty: RuntimeType::segment(),
3065 }
3066 .continue_()),
3067 _ => Err(KclError::new_undefined_value(
3068 KclErrorDetails::new(
3069 format!("Property '{property}' not found in segment"),
3070 vec![self.clone().into()],
3071 ),
3072 None,
3073 )),
3074 },
3075 },
3076 "edges" => match &segment.repr {
3077 SegmentRepr::Unsolved { segment } => match &segment.kind {
3078 UnsolvedSegmentKind::ControlPointSpline {
3079 controls,
3080 ctor,
3081 control_object_ids,
3082 control_polygon_edge_object_ids,
3083 construction,
3084 ..
3085 } => Ok(KclValue::HomArray {
3086 value: control_polygon_edge_object_ids
3087 .iter()
3088 .enumerate()
3089 .map(|(index, object_id)| KclValue::Segment {
3090 value: Box::new(AbstractSegment {
3091 repr: SegmentRepr::Unsolved {
3092 segment: Box::new(UnsolvedSegment {
3093 id: segment.id,
3094 object_id: *object_id,
3095 kind: UnsolvedSegmentKind::Line {
3096 start: controls[index].clone(),
3097 end: controls[index + 1].clone(),
3098 ctor: Box::new(LineCtor {
3099 start: ctor.points[index].clone(),
3100 end: ctor.points[index + 1].clone(),
3101 construction: Some(*construction),
3102 }),
3103 start_object_id: control_object_ids[index],
3104 end_object_id: control_object_ids[index + 1],
3105 construction: *construction,
3106 },
3107 tag: segment.tag.clone(),
3108 node_path: segment.node_path.clone(),
3109 meta: segment.meta.clone(),
3110 }),
3111 },
3112 meta: segment.meta.clone(),
3113 }),
3114 })
3115 .collect(),
3116 ty: RuntimeType::segment(),
3117 }
3118 .continue_()),
3119 _ => Err(KclError::new_undefined_value(
3120 KclErrorDetails::new(
3121 format!("Property '{property}' not found in segment"),
3122 vec![self.clone().into()],
3123 ),
3124 None,
3125 )),
3126 },
3127 SegmentRepr::Solved { segment } => match &segment.kind {
3128 SegmentKind::ControlPointSpline {
3129 controls,
3130 ctor,
3131 control_object_ids,
3132 control_polygon_edge_object_ids,
3133 control_freedoms,
3134 construction,
3135 ..
3136 } => Ok(KclValue::HomArray {
3137 value: control_polygon_edge_object_ids
3138 .iter()
3139 .enumerate()
3140 .map(|(index, object_id)| KclValue::Segment {
3141 value: Box::new(AbstractSegment {
3142 repr: SegmentRepr::Solved {
3143 segment: Box::new(Segment {
3144 id: segment.id,
3145 object_id: *object_id,
3146 kind: SegmentKind::Line {
3147 start: controls[index].clone(),
3148 end: controls[index + 1].clone(),
3149 ctor: Box::new(LineCtor {
3150 start: ctor.points[index].clone(),
3151 end: ctor.points[index + 1].clone(),
3152 construction: Some(*construction),
3153 }),
3154 start_object_id: control_object_ids[index],
3155 end_object_id: control_object_ids[index + 1],
3156 start_freedom: control_freedoms[index],
3157 end_freedom: control_freedoms[index + 1],
3158 construction: *construction,
3159 },
3160 surface: segment.surface.clone(),
3161 sketch_id: segment.sketch_id,
3162 sketch: segment.sketch.clone(),
3163 tag: segment.tag.clone(),
3164 node_path: segment.node_path.clone(),
3165 meta: segment.meta.clone(),
3166 }),
3167 },
3168 meta: segment.meta.clone(),
3169 }),
3170 })
3171 .collect(),
3172 ty: RuntimeType::segment(),
3173 }
3174 .continue_()),
3175 _ => Err(KclError::new_undefined_value(
3176 KclErrorDetails::new(
3177 format!("Property '{property}' not found in segment"),
3178 vec![self.clone().into()],
3179 ),
3180 None,
3181 )),
3182 },
3183 },
3184 other => Err(KclError::new_undefined_value(
3185 KclErrorDetails::new(
3186 format!("Property '{other}' not found in segment"),
3187 vec![self.clone().into()],
3188 ),
3189 None,
3190 )),
3191 },
3192 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
3193 "zAxis" => {
3194 let (p, u) = plane.info.z_axis.as_3_dims();
3195 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
3196 }
3197 "yAxis" => {
3198 let (p, u) = plane.info.y_axis.as_3_dims();
3199 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
3200 }
3201 "xAxis" => {
3202 let (p, u) = plane.info.x_axis.as_3_dims();
3203 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
3204 }
3205 "origin" => {
3206 let (p, u) = plane.info.origin.as_3_dims();
3207 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
3208 }
3209 other => Err(KclError::new_undefined_value(
3210 KclErrorDetails::new(
3211 format!("Property '{other}' not found in plane"),
3212 vec![self.clone().into()],
3213 ),
3214 None,
3215 )),
3216 },
3217 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
3218 if let Some(value) = map.get(&property) {
3219 Ok(value.to_owned().continue_())
3220 } else {
3221 Err(KclError::new_undefined_value(
3222 KclErrorDetails::new(
3223 format!("Property '{property}' not found in object"),
3224 vec![self.clone().into()],
3225 ),
3226 None,
3227 ))
3228 }
3229 }
3230 (KclValue::Object { .. }, Property::String(property), true) => {
3231 Err(KclError::new_semantic(KclErrorDetails::new(
3232 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
3233 vec![self.clone().into()],
3234 )))
3235 }
3236 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
3237 if i == 0
3238 && let Some(value) = map.get("x")
3239 {
3240 return Ok(value.to_owned().continue_());
3241 }
3242 if i == 1
3243 && let Some(value) = map.get("y")
3244 {
3245 return Ok(value.to_owned().continue_());
3246 }
3247 if i == 2
3248 && let Some(value) = map.get("z")
3249 {
3250 return Ok(value.to_owned().continue_());
3251 }
3252 let t = p.type_name();
3253 let article = article_for(t);
3254 Err(KclError::new_semantic(KclErrorDetails::new(
3255 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
3256 vec![self.clone().into()],
3257 )))
3258 }
3259 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
3260 let value_of_arr = arr.get(index);
3261 if let Some(value) = value_of_arr {
3262 Ok(value.to_owned().continue_())
3263 } else {
3264 Err(KclError::new_undefined_value(
3265 KclErrorDetails::new(
3266 format!("The array doesn't have any item at index {index}"),
3267 vec![self.clone().into()],
3268 ),
3269 None,
3270 ))
3271 }
3272 }
3273 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
3276 (KclValue::HomArray { .. }, p, _) => {
3277 let t = p.type_name();
3278 let article = article_for(t);
3279 Err(KclError::new_semantic(KclErrorDetails::new(
3280 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
3281 vec![self.clone().into()],
3282 )))
3283 }
3284 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
3285 let Some(sketch) = value.sketch() else {
3286 return Err(KclError::new_semantic(KclErrorDetails::new(
3287 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
3288 vec![self.clone().into()],
3289 )));
3290 };
3291 Ok(KclValue::Sketch {
3292 value: Box::new(sketch.clone()),
3293 }
3294 .continue_())
3295 }
3296 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
3297 Err(KclError::new_semantic(KclErrorDetails::new(
3299 format!(
3300 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
3301 geometry.human_friendly_type()
3302 ),
3303 vec![self.clone().into()],
3304 )))
3305 }
3306 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
3307 meta: vec![Metadata {
3308 source_range: SourceRange::from(self.clone()),
3309 }],
3310 value: sk
3311 .tags
3312 .iter()
3313 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
3314 .collect(),
3315 constrainable: false,
3316 }
3317 .continue_()),
3318 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
3319 Err(KclError::new_semantic(KclErrorDetails::new(
3320 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
3321 vec![self.clone().into()],
3322 )))
3323 }
3324 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
3325 format!(
3326 "Only objects can have members accessed with dot notation, but you're trying to access {}",
3327 being_indexed.human_friendly_type()
3328 ),
3329 vec![self.clone().into()],
3330 ))),
3331 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
3332 format!(
3333 "Only arrays can be indexed, but you're trying to index {}",
3334 being_indexed.human_friendly_type()
3335 ),
3336 vec![self.clone().into()],
3337 ))),
3338 }
3339 }
3340}
3341
3342impl Node<BinaryExpression> {
3343 pub(super) async fn get_result(
3344 &self,
3345 exec_state: &mut ExecState,
3346 ctx: &ExecutorContext,
3347 ) -> Result<KclValueControlFlow, KclError> {
3348 enum State {
3349 EvaluateLeft(Node<BinaryExpression>),
3350 FromLeft {
3351 node: Node<BinaryExpression>,
3352 },
3353 EvaluateRight {
3354 node: Node<BinaryExpression>,
3355 left: KclValue,
3356 },
3357 FromRight {
3358 node: Node<BinaryExpression>,
3359 left: KclValue,
3360 },
3361 }
3362
3363 let mut stack = vec![State::EvaluateLeft(self.clone())];
3364 let mut last_result: Option<KclValue> = None;
3365
3366 while let Some(state) = stack.pop() {
3367 match state {
3368 State::EvaluateLeft(node) => {
3369 let left_part = node.left.clone();
3370 match left_part {
3371 BinaryPart::BinaryExpression(child) => {
3372 stack.push(State::FromLeft { node });
3373 stack.push(State::EvaluateLeft(*child));
3374 }
3375 part => {
3376 let left_value = part.get_result(exec_state, ctx).await?;
3377 let left_value = control_continue!(left_value);
3378 stack.push(State::EvaluateRight { node, left: left_value });
3379 }
3380 }
3381 }
3382 State::FromLeft { node } => {
3383 let Some(left_value) = last_result.take() else {
3384 return Err(Self::missing_result_error(&node));
3385 };
3386 stack.push(State::EvaluateRight { node, left: left_value });
3387 }
3388 State::EvaluateRight { node, left } => {
3389 let right_part = node.right.clone();
3390 match right_part {
3391 BinaryPart::BinaryExpression(child) => {
3392 stack.push(State::FromRight { node, left });
3393 stack.push(State::EvaluateLeft(*child));
3394 }
3395 part => {
3396 let right_value = part.get_result(exec_state, ctx).await?;
3397 let right_value = control_continue!(right_value);
3398 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3399 last_result = Some(result);
3400 }
3401 }
3402 }
3403 State::FromRight { node, left } => {
3404 let Some(right_value) = last_result.take() else {
3405 return Err(Self::missing_result_error(&node));
3406 };
3407 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3408 last_result = Some(result);
3409 }
3410 }
3411 }
3412
3413 last_result
3414 .map(KclValue::continue_)
3415 .ok_or_else(|| Self::missing_result_error(self))
3416 }
3417
3418 async fn apply_operator(
3419 &self,
3420 exec_state: &mut ExecState,
3421 ctx: &ExecutorContext,
3422 left_value: KclValue,
3423 right_value: KclValue,
3424 ) -> Result<KclValue, KclError> {
3425 let mut meta = left_value.metadata();
3426 meta.extend(right_value.metadata());
3427
3428 if self.operator == BinaryOperator::Add
3430 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
3431 (&left_value, &right_value)
3432 {
3433 return Ok(KclValue::String {
3434 value: format!("{left}{right}"),
3435 meta,
3436 });
3437 }
3438
3439 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
3441 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3442 let args = Args::new_no_args(
3443 self.into(),
3444 self.node_path.clone(),
3445 ctx.clone(),
3446 Some("union".to_owned()),
3447 );
3448 let result = crate::std::csg::inner_union(
3449 vec![*left.clone(), *right.clone()],
3450 Default::default(),
3451 crate::std::csg::CsgAlgorithm::Latest,
3452 exec_state,
3453 args,
3454 )
3455 .await?;
3456 return Ok(result.into());
3457 }
3458 } else if self.operator == BinaryOperator::Sub {
3459 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3461 let args = Args::new_no_args(
3462 self.into(),
3463 self.node_path.clone(),
3464 ctx.clone(),
3465 Some("subtract".to_owned()),
3466 );
3467 let result = crate::std::csg::inner_subtract(
3468 vec![*left.clone()],
3469 vec![*right.clone()],
3470 Default::default(),
3471 crate::std::csg::CsgAlgorithm::Latest,
3472 exec_state,
3473 args,
3474 )
3475 .await?;
3476 return Ok(result.into());
3477 }
3478 } else if self.operator == BinaryOperator::And
3479 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
3480 {
3481 let args = Args::new_no_args(
3483 self.into(),
3484 self.node_path.clone(),
3485 ctx.clone(),
3486 Some("intersect".to_owned()),
3487 );
3488 let result = crate::std::csg::inner_intersect(
3489 vec![*left.clone(), *right.clone()],
3490 Default::default(),
3491 crate::std::csg::CsgAlgorithm::Latest,
3492 exec_state,
3493 args,
3494 )
3495 .await?;
3496 return Ok(result.into());
3497 }
3498
3499 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
3501 let KclValue::Bool { value: left_value, .. } = left_value else {
3502 return Err(KclError::new_semantic(KclErrorDetails::new(
3503 format!(
3504 "Cannot apply logical operator to non-boolean value: {}",
3505 left_value.human_friendly_type()
3506 ),
3507 vec![self.left.clone().into()],
3508 )));
3509 };
3510 let KclValue::Bool { value: right_value, .. } = right_value else {
3511 return Err(KclError::new_semantic(KclErrorDetails::new(
3512 format!(
3513 "Cannot apply logical operator to non-boolean value: {}",
3514 right_value.human_friendly_type()
3515 ),
3516 vec![self.right.clone().into()],
3517 )));
3518 };
3519 let raw_value = match self.operator {
3520 BinaryOperator::Or => left_value || right_value,
3521 BinaryOperator::And => left_value && right_value,
3522 _ => unreachable!(),
3523 };
3524 return Ok(KclValue::Bool { value: raw_value, meta });
3525 }
3526
3527 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
3529 match (&left_value, &right_value) {
3530 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
3532 if left_value.id == right_value.id =>
3533 {
3534 return Ok(KclValue::none());
3535 }
3536 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
3538 let constraint = Constraint::ScalarEqual(
3539 var0.id.to_constraint_id(self.as_source_range())?,
3540 var1.id.to_constraint_id(self.as_source_range())?,
3541 );
3542 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3543 let message = "Being inside a sketch block should have already been checked above".to_owned();
3544 debug_assert!(false, "{}", &message);
3545 return Err(internal_err(message, self));
3546 };
3547 sketch_block_state.solver_constraints.push(constraint);
3548 return Ok(KclValue::none());
3549 }
3550 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
3552 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
3553 let number_value = normalize_to_solver_distance_unit(
3554 input_number,
3555 input_number.into(),
3556 exec_state,
3557 "fixed constraint value",
3558 )?;
3559 let Some(n) = number_value.as_ty_f64() else {
3560 let message = format!(
3561 "Expected number after coercion, but found {}",
3562 number_value.human_friendly_type()
3563 );
3564 debug_assert!(false, "{}", &message);
3565 return Err(internal_err(message, self));
3566 };
3567 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
3568 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3569 let message = "Being inside a sketch block should have already been checked above".to_owned();
3570 debug_assert!(false, "{}", &message);
3571 return Err(internal_err(message, self));
3572 };
3573 sketch_block_state.solver_constraints.push(constraint);
3574 exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
3575 return Ok(KclValue::none());
3576 }
3577 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
3579 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
3580 let number_value = match constraint.kind {
3581 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
3583 input_number,
3584 input_number.into(),
3585 exec_state,
3586 "fixed constraint value",
3587 )?,
3588 SketchConstraintKind::Distance { .. }
3590 | SketchConstraintKind::PointLineDistance { .. }
3591 | SketchConstraintKind::LineLineDistance { .. }
3592 | SketchConstraintKind::PointCircularDistance { .. }
3593 | SketchConstraintKind::LineCircularDistance { .. }
3594 | SketchConstraintKind::CircularCircularDistance { .. }
3595 | SketchConstraintKind::Radius { .. }
3596 | SketchConstraintKind::Diameter { .. }
3597 | SketchConstraintKind::HorizontalDistance { .. }
3598 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
3599 input_number,
3600 input_number.into(),
3601 exec_state,
3602 "fixed constraint value",
3603 )?,
3604 };
3605 let Some(n) = number_value.as_ty_f64() else {
3606 let message = format!(
3607 "Expected number after coercion, but found {}",
3608 number_value.human_friendly_type()
3609 );
3610 debug_assert!(false, "{}", &message);
3611 return Err(internal_err(message, self));
3612 };
3613 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
3615 &self.right
3616 } else {
3617 &self.left
3618 };
3619 let source = {
3620 use crate::unparser::ExprContext;
3621 let mut buf = String::new();
3622 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
3623 crate::frontend::sketch::ConstraintSource {
3624 expr: buf,
3625 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
3626 }
3627 };
3628
3629 match &constraint.kind {
3630 SketchConstraintKind::Angle { line0, line1 } => {
3631 let range = self.as_source_range();
3632 let ax = line0.vars[0].x.to_constraint_id(range)?;
3635 let ay = line0.vars[0].y.to_constraint_id(range)?;
3636 let bx = line0.vars[1].x.to_constraint_id(range)?;
3637 let by = line0.vars[1].y.to_constraint_id(range)?;
3638 let cx = line1.vars[0].x.to_constraint_id(range)?;
3639 let cy = line1.vars[0].y.to_constraint_id(range)?;
3640 let dx = line1.vars[1].x.to_constraint_id(range)?;
3641 let dy = line1.vars[1].y.to_constraint_id(range)?;
3642 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
3643 ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
3644 ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
3645 );
3646 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
3647 ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
3648 ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
3649 );
3650 let desired_angle = match n.ty {
3651 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
3652 | NumericType::Default {
3653 len: _,
3654 angle: kcmc::units::UnitAngle::Degrees,
3655 } => ezpz::datatypes::Angle::from_degrees(n.n),
3656 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
3657 | NumericType::Default {
3658 len: _,
3659 angle: kcmc::units::UnitAngle::Radians,
3660 } => ezpz::datatypes::Angle::from_radians(n.n),
3661 NumericType::Known(crate::exec::UnitType::Count)
3662 | NumericType::Known(crate::exec::UnitType::GenericLength)
3663 | NumericType::Known(crate::exec::UnitType::GenericAngle)
3664 | NumericType::Known(crate::exec::UnitType::Length(_))
3665 | NumericType::Unknown
3666 | NumericType::Any => {
3667 let message = format!("Expected angle but found {:?}", n);
3668 debug_assert!(false, "{}", &message);
3669 return Err(internal_err(message, self));
3670 }
3671 };
3672 let solver_constraint = Constraint::LinesAtAngle(
3673 solver_line0,
3674 solver_line1,
3675 ezpz::datatypes::AngleKind::Other(desired_angle),
3676 );
3677 let constraint_id = exec_state.next_object_id();
3678 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3679 let message =
3680 "Being inside a sketch block should have already been checked above".to_owned();
3681 debug_assert!(false, "{}", &message);
3682 return Err(internal_err(message, self));
3683 };
3684 sketch_block_state.solver_constraints.push(solver_constraint);
3685 use crate::execution::Artifact;
3686 use crate::execution::CodeRef;
3687 use crate::execution::SketchBlockConstraint;
3688 use crate::execution::SketchBlockConstraintType;
3689 use crate::front::Angle;
3690 use crate::front::SourceRef;
3691
3692 let Some(sketch_id) = sketch_block_state.sketch_id else {
3693 let message = "Sketch id missing for constraint artifact".to_owned();
3694 debug_assert!(false, "{}", &message);
3695 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3696 };
3697 let sketch_constraint = crate::front::Constraint::Angle(Angle {
3698 lines: vec![line0.object_id, line1.object_id],
3699 angle: n.try_into().map_err(|_| {
3700 internal_err("Failed to convert angle units numeric suffix:", range)
3701 })?,
3702 source,
3703 });
3704 sketch_block_state.sketch_constraints.push(constraint_id);
3705 let artifact_id = exec_state.next_artifact_id();
3706 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3707 id: artifact_id,
3708 sketch_id,
3709 constraint_id,
3710 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3711 code_ref: CodeRef::placeholder(range),
3712 }));
3713 exec_state.add_scene_object(
3714 Object {
3715 id: constraint_id,
3716 kind: ObjectKind::Constraint {
3717 constraint: sketch_constraint,
3718 },
3719 label: Default::default(),
3720 comments: Default::default(),
3721 artifact_id,
3722 source: SourceRef::new(range, self.node_path.clone()),
3723 },
3724 range,
3725 );
3726 }
3727 SketchConstraintKind::Distance { points, label_position } => {
3728 let range = self.as_source_range();
3729 let p0 = &points[0];
3730 let p1 = &points[1];
3731 let sketch_var_ty = solver_numeric_type(exec_state);
3732 let constraint_id = exec_state.next_object_id();
3733 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3734 let message =
3735 "Being inside a sketch block should have already been checked above".to_owned();
3736 debug_assert!(false, "{}", &message);
3737 return Err(internal_err(message, self));
3738 };
3739 match (p0, p1) {
3740 (
3741 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3742 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3743 ) => {
3744 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3745 p0.vars.x.to_constraint_id(range)?,
3746 p0.vars.y.to_constraint_id(range)?,
3747 );
3748 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3749 p1.vars.x.to_constraint_id(range)?,
3750 p1.vars.y.to_constraint_id(range)?,
3751 );
3752 sketch_block_state
3753 .solver_constraints
3754 .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
3755 }
3756 (
3757 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3758 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3759 )
3760 | (
3761 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3762 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3763 ) => {
3764 let origin_x_id = sketch_block_state.next_sketch_var_id();
3765 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3766 value: Box::new(crate::execution::SketchVar {
3767 id: origin_x_id,
3768 initial_value: 0.0,
3769 ty: sketch_var_ty,
3770 meta: vec![],
3771 }),
3772 });
3773 let origin_y_id = sketch_block_state.next_sketch_var_id();
3774 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3775 value: Box::new(crate::execution::SketchVar {
3776 id: origin_y_id,
3777 initial_value: 0.0,
3778 ty: sketch_var_ty,
3779 meta: vec![],
3780 }),
3781 });
3782 let origin_x = origin_x_id.to_constraint_id(range)?;
3783 let origin_y = origin_y_id.to_constraint_id(range)?;
3784 sketch_block_state
3785 .solver_constraints
3786 .push(Constraint::Fixed(origin_x, 0.0));
3787 sketch_block_state
3788 .solver_constraints
3789 .push(Constraint::Fixed(origin_y, 0.0));
3790 let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3791 point.vars.x.to_constraint_id(range)?,
3792 point.vars.y.to_constraint_id(range)?,
3793 );
3794 let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
3795 sketch_block_state.solver_constraints.push(Constraint::Distance(
3796 solver_point,
3797 origin_point,
3798 n.n,
3799 ));
3800 }
3801 (
3802 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3803 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3804 ) => {
3805 return Err(internal_err(
3806 "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3807 range,
3808 ));
3809 }
3810 }
3811 use crate::execution::Artifact;
3812 use crate::execution::CodeRef;
3813 use crate::execution::SketchBlockConstraint;
3814 use crate::execution::SketchBlockConstraintType;
3815 use crate::front::Distance;
3816 use crate::front::SourceRef;
3817 use crate::frontend::sketch::ConstraintSegment;
3818
3819 let Some(sketch_id) = sketch_block_state.sketch_id else {
3820 let message = "Sketch id missing for constraint artifact".to_owned();
3821 debug_assert!(false, "{}", &message);
3822 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3823 };
3824 let sketch_constraint = crate::front::Constraint::Distance(Distance {
3825 points: vec![
3826 match p0 {
3827 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3828 ConstraintSegment::from(point.object_id)
3829 }
3830 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3831 ConstraintSegment::ORIGIN
3832 }
3833 },
3834 match p1 {
3835 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3836 ConstraintSegment::from(point.object_id)
3837 }
3838 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3839 ConstraintSegment::ORIGIN
3840 }
3841 },
3842 ],
3843 distance: n.try_into().map_err(|_| {
3844 internal_err("Failed to convert distance units numeric suffix:", range)
3845 })?,
3846 label_position: label_position.clone(),
3847 source,
3848 });
3849 sketch_block_state.sketch_constraints.push(constraint_id);
3850 let artifact_id = exec_state.next_artifact_id();
3851 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3852 id: artifact_id,
3853 sketch_id,
3854 constraint_id,
3855 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3856 code_ref: CodeRef::placeholder(range),
3857 }));
3858 exec_state.add_scene_object(
3859 Object {
3860 id: constraint_id,
3861 kind: ObjectKind::Constraint {
3862 constraint: sketch_constraint,
3863 },
3864 label: Default::default(),
3865 comments: Default::default(),
3866 artifact_id,
3867 source: SourceRef::new(range, self.node_path.clone()),
3868 },
3869 range,
3870 );
3871 }
3872 SketchConstraintKind::PointLineDistance {
3873 point,
3874 line,
3875 input_object_ids,
3876 label_position,
3877 } => {
3878 let range = self.as_source_range();
3879 let sketch_var_ty = solver_numeric_type(exec_state);
3880 let sketch_vars = exec_state
3881 .mod_local
3882 .sketch_block
3883 .as_ref()
3884 .ok_or_else(|| {
3885 internal_err(
3886 "Being inside a sketch block should have already been checked above",
3887 self,
3888 )
3889 })?
3890 .sketch_vars
3891 .clone();
3892 let support_initial =
3893 projected_point_on_line_initial_position(&sketch_vars, point, line, exec_state, range)?;
3894 let solver_line = datum_line_from_constrainable(line, range)?;
3895
3896 let constraint_id = exec_state.next_object_id();
3897 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3898 let message =
3899 "Being inside a sketch block should have already been checked above".to_owned();
3900 debug_assert!(false, "{}", &message);
3901 return Err(internal_err(message, self));
3902 };
3903
3904 let solver_point = datum_point_from_constrainable_or_origin(
3910 sketch_block_state,
3911 sketch_var_ty,
3912 point,
3913 range,
3914 )?;
3915 let support_x_id = sketch_block_state.next_sketch_var_id();
3916 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3917 value: Box::new(crate::execution::SketchVar {
3918 id: support_x_id,
3919 initial_value: support_initial[0],
3920 ty: sketch_var_ty,
3921 meta: vec![],
3922 }),
3923 });
3924 let support_y_id = sketch_block_state.next_sketch_var_id();
3925 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3926 value: Box::new(crate::execution::SketchVar {
3927 id: support_y_id,
3928 initial_value: support_initial[1],
3929 ty: sketch_var_ty,
3930 meta: vec![],
3931 }),
3932 });
3933 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3934 support_x_id.to_constraint_id(range)?,
3935 support_y_id.to_constraint_id(range)?,
3936 );
3937 let support_line =
3938 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
3939
3940 sketch_block_state
3941 .solver_constraints
3942 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
3943 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
3944 support_line,
3945 solver_line,
3946 ezpz::datatypes::AngleKind::Perpendicular,
3947 ));
3948 sketch_block_state.solver_constraints.push(Constraint::Distance(
3949 solver_point,
3950 support_point,
3951 n.n,
3952 ));
3953
3954 use crate::execution::Artifact;
3955 use crate::execution::CodeRef;
3956 use crate::execution::SketchBlockConstraint;
3957 use crate::execution::SketchBlockConstraintType;
3958 use crate::front::Distance;
3959 use crate::front::SourceRef;
3960 use crate::frontend::sketch::ConstraintSegment;
3961
3962 let Some(sketch_id) = sketch_block_state.sketch_id else {
3963 let message = "Sketch id missing for constraint artifact".to_owned();
3964 debug_assert!(false, "{}", &message);
3965 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3966 };
3967 let sketch_constraint = crate::front::Constraint::Distance(Distance {
3968 points: input_object_ids
3969 .iter()
3970 .copied()
3971 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
3972 .collect(),
3973 distance: n.try_into().map_err(|_| {
3974 internal_err("Failed to convert distance units numeric suffix:", range)
3975 })?,
3976 label_position: label_position.clone(),
3977 source,
3978 });
3979 sketch_block_state.sketch_constraints.push(constraint_id);
3980 let artifact_id = exec_state.next_artifact_id();
3981 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3982 id: artifact_id,
3983 sketch_id,
3984 constraint_id,
3985 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3986 code_ref: CodeRef::placeholder(range),
3987 }));
3988 exec_state.add_scene_object(
3989 Object {
3990 id: constraint_id,
3991 kind: ObjectKind::Constraint {
3992 constraint: sketch_constraint,
3993 },
3994 label: Default::default(),
3995 comments: Default::default(),
3996 artifact_id,
3997 source: SourceRef::new(range, self.node_path.clone()),
3998 },
3999 range,
4000 );
4001 }
4002 SketchConstraintKind::LineLineDistance {
4003 line0,
4004 line1,
4005 input_object_ids,
4006 label_position,
4007 } => {
4008 let range = self.as_source_range();
4009 let reference_point = crate::execution::ConstrainablePoint2d {
4010 vars: line0.vars[0].clone(),
4011 object_id: line0.object_id,
4012 };
4013 let sketch_var_ty = solver_numeric_type(exec_state);
4014 let sketch_vars = exec_state
4015 .mod_local
4016 .sketch_block
4017 .as_ref()
4018 .ok_or_else(|| {
4019 internal_err(
4020 "Being inside a sketch block should have already been checked above",
4021 self,
4022 )
4023 })?
4024 .sketch_vars
4025 .clone();
4026 let support_initial = projected_point_on_line_initial_position(
4027 &sketch_vars,
4028 &crate::execution::ConstrainablePoint2dOrOrigin::Point(reference_point.clone()),
4029 line1,
4030 exec_state,
4031 range,
4032 )?;
4033 let solver_point = datum_point_from_constrainable(&reference_point, range)?;
4034 let solver_line0 = datum_line_from_constrainable(line0, range)?;
4035 let solver_line1 = datum_line_from_constrainable(line1, range)?;
4036
4037 let constraint_id = exec_state.next_object_id();
4038 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4039 let message =
4040 "Being inside a sketch block should have already been checked above".to_owned();
4041 debug_assert!(false, "{}", &message);
4042 return Err(internal_err(message, self));
4043 };
4044
4045 let support_x_id = sketch_block_state.next_sketch_var_id();
4051 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4052 value: Box::new(crate::execution::SketchVar {
4053 id: support_x_id,
4054 initial_value: support_initial[0],
4055 ty: sketch_var_ty,
4056 meta: vec![],
4057 }),
4058 });
4059 let support_y_id = sketch_block_state.next_sketch_var_id();
4060 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4061 value: Box::new(crate::execution::SketchVar {
4062 id: support_y_id,
4063 initial_value: support_initial[1],
4064 ty: sketch_var_ty,
4065 meta: vec![],
4066 }),
4067 });
4068 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4069 support_x_id.to_constraint_id(range)?,
4070 support_y_id.to_constraint_id(range)?,
4071 );
4072 let support_line =
4073 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
4074
4075 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4076 solver_line0,
4077 solver_line1,
4078 ezpz::datatypes::AngleKind::Parallel,
4079 ));
4080 sketch_block_state
4081 .solver_constraints
4082 .push(Constraint::PointLineDistance(support_point, solver_line1, 0.0));
4083 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4084 support_line,
4085 solver_line1,
4086 ezpz::datatypes::AngleKind::Perpendicular,
4087 ));
4088 sketch_block_state.solver_constraints.push(Constraint::Distance(
4089 solver_point,
4090 support_point,
4091 n.n,
4092 ));
4093
4094 use crate::execution::Artifact;
4095 use crate::execution::CodeRef;
4096 use crate::execution::SketchBlockConstraint;
4097 use crate::execution::SketchBlockConstraintType;
4098 use crate::front::Distance;
4099 use crate::front::SourceRef;
4100 use crate::frontend::sketch::ConstraintSegment;
4101
4102 let Some(sketch_id) = sketch_block_state.sketch_id else {
4103 let message = "Sketch id missing for constraint artifact".to_owned();
4104 debug_assert!(false, "{}", &message);
4105 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4106 };
4107 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4108 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4109 distance: n.try_into().map_err(|_| {
4110 internal_err("Failed to convert distance units numeric suffix:", range)
4111 })?,
4112 label_position: label_position.clone(),
4113 source,
4114 });
4115 sketch_block_state.sketch_constraints.push(constraint_id);
4116 let artifact_id = exec_state.next_artifact_id();
4117 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4118 id: artifact_id,
4119 sketch_id,
4120 constraint_id,
4121 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4122 code_ref: CodeRef::placeholder(range),
4123 }));
4124 exec_state.add_scene_object(
4125 Object {
4126 id: constraint_id,
4127 kind: ObjectKind::Constraint {
4128 constraint: sketch_constraint,
4129 },
4130 label: Default::default(),
4131 comments: Default::default(),
4132 artifact_id,
4133 source: SourceRef::new(range, self.node_path.clone()),
4134 },
4135 range,
4136 );
4137 }
4138 SketchConstraintKind::PointCircularDistance {
4139 point,
4140 center,
4141 start,
4142 end,
4143 input_object_ids,
4144 label_position,
4145 } => {
4146 let range = self.as_source_range();
4147 let sketch_var_ty = solver_numeric_type(exec_state);
4148 let sketch_vars = exec_state
4149 .mod_local
4150 .sketch_block
4151 .as_ref()
4152 .ok_or_else(|| {
4153 internal_err(
4154 "Being inside a sketch block should have already been checked above",
4155 self,
4156 )
4157 })?
4158 .sketch_vars
4159 .clone();
4160 let circular =
4161 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
4162
4163 let constraint_id = exec_state.next_object_id();
4164 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4165 let message =
4166 "Being inside a sketch block should have already been checked above".to_owned();
4167 debug_assert!(false, "{}", &message);
4168 return Err(internal_err(message, self));
4169 };
4170
4171 let target_point = datum_point_from_constrainable_or_origin(
4176 sketch_block_state,
4177 sketch_var_ty,
4178 point,
4179 range,
4180 )?;
4181 push_circular_distance_constraints(
4182 sketch_block_state,
4183 sketch_var_ty,
4184 target_point,
4185 circular,
4186 n.n,
4187 range,
4188 )?;
4189
4190 use crate::execution::Artifact;
4191 use crate::execution::CodeRef;
4192 use crate::execution::SketchBlockConstraint;
4193 use crate::execution::SketchBlockConstraintType;
4194 use crate::front::Distance;
4195 use crate::front::SourceRef;
4196 use crate::frontend::sketch::ConstraintSegment;
4197
4198 let Some(sketch_id) = sketch_block_state.sketch_id else {
4199 let message = "Sketch id missing for constraint artifact".to_owned();
4200 debug_assert!(false, "{}", &message);
4201 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4202 };
4203 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4204 points: input_object_ids
4205 .iter()
4206 .copied()
4207 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
4208 .collect(),
4209 distance: n.try_into().map_err(|_| {
4210 internal_err("Failed to convert distance units numeric suffix:", range)
4211 })?,
4212 label_position: label_position.clone(),
4213 source,
4214 });
4215 sketch_block_state.sketch_constraints.push(constraint_id);
4216 let artifact_id = exec_state.next_artifact_id();
4217 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4218 id: artifact_id,
4219 sketch_id,
4220 constraint_id,
4221 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4222 code_ref: CodeRef::placeholder(range),
4223 }));
4224 exec_state.add_scene_object(
4225 Object {
4226 id: constraint_id,
4227 kind: ObjectKind::Constraint {
4228 constraint: sketch_constraint,
4229 },
4230 label: Default::default(),
4231 comments: Default::default(),
4232 artifact_id,
4233 source: SourceRef::new(range, self.node_path.clone()),
4234 },
4235 range,
4236 );
4237 }
4238 SketchConstraintKind::LineCircularDistance {
4239 line,
4240 center,
4241 start,
4242 end,
4243 input_object_ids,
4244 label_position,
4245 } => {
4246 let range = self.as_source_range();
4247 let sketch_var_ty = solver_numeric_type(exec_state);
4248 let sketch_vars = exec_state
4249 .mod_local
4250 .sketch_block
4251 .as_ref()
4252 .ok_or_else(|| {
4253 internal_err(
4254 "Being inside a sketch block should have already been checked above",
4255 self,
4256 )
4257 })?
4258 .sketch_vars
4259 .clone();
4260 let support_initial = projected_point_on_line_initial_position(
4261 &sketch_vars,
4262 &crate::execution::ConstrainablePoint2dOrOrigin::Point(center.clone()),
4263 line,
4264 exec_state,
4265 range,
4266 )?;
4267 let solver_line = datum_line_from_constrainable(line, range)?;
4268 let circular =
4269 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
4270
4271 let constraint_id = exec_state.next_object_id();
4272 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4273 let message =
4274 "Being inside a sketch block should have already been checked above".to_owned();
4275 debug_assert!(false, "{}", &message);
4276 return Err(internal_err(message, self));
4277 };
4278
4279 let support_x_id = sketch_block_state.next_sketch_var_id();
4285 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4286 value: Box::new(crate::execution::SketchVar {
4287 id: support_x_id,
4288 initial_value: support_initial[0],
4289 ty: sketch_var_ty,
4290 meta: vec![],
4291 }),
4292 });
4293 let support_y_id = sketch_block_state.next_sketch_var_id();
4294 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4295 value: Box::new(crate::execution::SketchVar {
4296 id: support_y_id,
4297 initial_value: support_initial[1],
4298 ty: sketch_var_ty,
4299 meta: vec![],
4300 }),
4301 });
4302 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4303 support_x_id.to_constraint_id(range)?,
4304 support_y_id.to_constraint_id(range)?,
4305 );
4306 let support_line =
4307 ezpz::datatypes::inputs::DatumLineSegment::new(circular.center, support_point);
4308
4309 sketch_block_state
4310 .solver_constraints
4311 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
4312 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4313 support_line,
4314 solver_line,
4315 ezpz::datatypes::AngleKind::Perpendicular,
4316 ));
4317 push_circular_distance_constraints(
4318 sketch_block_state,
4319 sketch_var_ty,
4320 support_point,
4321 circular,
4322 n.n,
4323 range,
4324 )?;
4325
4326 use crate::execution::Artifact;
4327 use crate::execution::CodeRef;
4328 use crate::execution::SketchBlockConstraint;
4329 use crate::execution::SketchBlockConstraintType;
4330 use crate::front::Distance;
4331 use crate::front::SourceRef;
4332 use crate::frontend::sketch::ConstraintSegment;
4333
4334 let Some(sketch_id) = sketch_block_state.sketch_id else {
4335 let message = "Sketch id missing for constraint artifact".to_owned();
4336 debug_assert!(false, "{}", &message);
4337 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4338 };
4339 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4340 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4341 distance: n.try_into().map_err(|_| {
4342 internal_err("Failed to convert distance units numeric suffix:", range)
4343 })?,
4344 label_position: label_position.clone(),
4345 source,
4346 });
4347 sketch_block_state.sketch_constraints.push(constraint_id);
4348 let artifact_id = exec_state.next_artifact_id();
4349 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4350 id: artifact_id,
4351 sketch_id,
4352 constraint_id,
4353 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4354 code_ref: CodeRef::placeholder(range),
4355 }));
4356 exec_state.add_scene_object(
4357 Object {
4358 id: constraint_id,
4359 kind: ObjectKind::Constraint {
4360 constraint: sketch_constraint,
4361 },
4362 label: Default::default(),
4363 comments: Default::default(),
4364 artifact_id,
4365 source: SourceRef::new(range, self.node_path.clone()),
4366 },
4367 range,
4368 );
4369 }
4370 SketchConstraintKind::CircularCircularDistance {
4371 center0,
4372 start0,
4373 end0,
4374 center1,
4375 start1,
4376 end1,
4377 input_object_ids,
4378 label_position,
4379 } => {
4380 let range = self.as_source_range();
4381 let sketch_var_ty = solver_numeric_type(exec_state);
4382 let sketch_vars = exec_state
4383 .mod_local
4384 .sketch_block
4385 .as_ref()
4386 .ok_or_else(|| {
4387 internal_err(
4388 "Being inside a sketch block should have already been checked above",
4389 self,
4390 )
4391 })?
4392 .sketch_vars
4393 .clone();
4394 let circular0 = circular_distance_datums(
4395 &sketch_vars,
4396 center0,
4397 start0,
4398 end0.as_ref(),
4399 exec_state,
4400 range,
4401 )?;
4402 let circular1 = circular_distance_datums(
4403 &sketch_vars,
4404 center1,
4405 start1,
4406 end1.as_ref(),
4407 exec_state,
4408 range,
4409 )?;
4410 let support_initial = circular_circular_support_initial_position(
4411 &sketch_vars,
4412 center0,
4413 center1,
4414 circular0.radius_initial_value,
4415 n.n,
4416 exec_state,
4417 range,
4418 )?;
4419
4420 let constraint_id = exec_state.next_object_id();
4421 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4422 let message =
4423 "Being inside a sketch block should have already been checked above".to_owned();
4424 debug_assert!(false, "{}", &message);
4425 return Err(internal_err(message, self));
4426 };
4427
4428 let circular_target0 =
4434 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular0, range)?;
4435 let circular_target1 =
4436 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular1, range)?;
4437
4438 let support_x_id = sketch_block_state.next_sketch_var_id();
4439 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4440 value: Box::new(crate::execution::SketchVar {
4441 id: support_x_id,
4442 initial_value: support_initial[0],
4443 ty: sketch_var_ty,
4444 meta: vec![],
4445 }),
4446 });
4447 let support_y_id = sketch_block_state.next_sketch_var_id();
4448 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4449 value: Box::new(crate::execution::SketchVar {
4450 id: support_y_id,
4451 initial_value: support_initial[1],
4452 ty: sketch_var_ty,
4453 meta: vec![],
4454 }),
4455 });
4456 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4457 support_x_id.to_constraint_id(range)?,
4458 support_y_id.to_constraint_id(range)?,
4459 );
4460
4461 let support_radius_id = sketch_block_state.next_sketch_var_id();
4462 let support_radius_value = n.n / 2.0;
4463 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4464 value: Box::new(crate::execution::SketchVar {
4465 id: support_radius_id,
4466 initial_value: support_radius_value,
4467 ty: sketch_var_ty,
4468 meta: vec![],
4469 }),
4470 });
4471 let support_radius =
4472 ezpz::datatypes::inputs::DatumDistance::new(support_radius_id.to_constraint_id(range)?);
4473 let support_circle = ezpz::datatypes::inputs::DatumCircle {
4474 center: support_point,
4475 radius: support_radius,
4476 };
4477 let center_line = ezpz::datatypes::inputs::DatumLineSegment::new(
4478 circular_target0.center,
4479 circular_target1.center,
4480 );
4481
4482 sketch_block_state
4483 .solver_constraints
4484 .push(Constraint::Fixed(support_radius.id, support_radius_value));
4485 sketch_block_state
4486 .solver_constraints
4487 .push(Constraint::PointLineDistance(support_point, center_line, 0.0));
4488 sketch_block_state
4489 .solver_constraints
4490 .push(Constraint::CircleTangentToCircle(
4491 circular_target0,
4492 support_circle,
4493 ezpz::CircleSide::Exterior,
4494 ));
4495 sketch_block_state
4496 .solver_constraints
4497 .push(Constraint::CircleTangentToCircle(
4498 support_circle,
4499 circular_target1,
4500 ezpz::CircleSide::Exterior,
4501 ));
4502
4503 use crate::execution::Artifact;
4504 use crate::execution::CodeRef;
4505 use crate::execution::SketchBlockConstraint;
4506 use crate::execution::SketchBlockConstraintType;
4507 use crate::front::Distance;
4508 use crate::front::SourceRef;
4509 use crate::frontend::sketch::ConstraintSegment;
4510
4511 let Some(sketch_id) = sketch_block_state.sketch_id else {
4512 let message = "Sketch id missing for constraint artifact".to_owned();
4513 debug_assert!(false, "{}", &message);
4514 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4515 };
4516 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4517 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4518 distance: n.try_into().map_err(|_| {
4519 internal_err("Failed to convert distance units numeric suffix:", range)
4520 })?,
4521 label_position: label_position.clone(),
4522 source,
4523 });
4524 sketch_block_state.sketch_constraints.push(constraint_id);
4525 let artifact_id = exec_state.next_artifact_id();
4526 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4527 id: artifact_id,
4528 sketch_id,
4529 constraint_id,
4530 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4531 code_ref: CodeRef::placeholder(range),
4532 }));
4533 exec_state.add_scene_object(
4534 Object {
4535 id: constraint_id,
4536 kind: ObjectKind::Constraint {
4537 constraint: sketch_constraint,
4538 },
4539 label: Default::default(),
4540 comments: Default::default(),
4541 artifact_id,
4542 source: SourceRef::new(range, self.node_path.clone()),
4543 },
4544 range,
4545 );
4546 }
4547 SketchConstraintKind::Radius { .. } | SketchConstraintKind::Diameter { .. } => {
4548 #[derive(Clone, Copy)]
4549 enum CircularSegmentConstraintTarget {
4550 Arc {
4551 object_id: ObjectId,
4552 end: [crate::execution::SketchVarId; 2],
4553 },
4554 Circle {
4555 object_id: ObjectId,
4556 },
4557 }
4558
4559 fn sketch_var_initial_value(
4560 sketch_vars: &[KclValue],
4561 id: crate::execution::SketchVarId,
4562 exec_state: &mut ExecState,
4563 range: SourceRange,
4564 ) -> Result<f64, KclError> {
4565 sketch_vars
4566 .get(id.0)
4567 .and_then(KclValue::as_sketch_var)
4568 .map(|sketch_var| {
4569 sketch_var
4570 .initial_value_to_solver_units(
4571 exec_state,
4572 range,
4573 "circle radius initial value",
4574 )
4575 .map(|value| value.n)
4576 })
4577 .transpose()?
4578 .ok_or_else(|| {
4579 internal_err(
4580 format!("Missing sketch variable initial value for id {}", id.0),
4581 range,
4582 )
4583 })
4584 }
4585
4586 let (points, label_position) = match &constraint.kind {
4587 SketchConstraintKind::Radius { points, label_position } => {
4588 (points, label_position.clone())
4589 }
4590 SketchConstraintKind::Diameter { points, label_position } => {
4591 (points, label_position.clone())
4592 }
4593 _ => unreachable!(),
4594 };
4595 let range = self.as_source_range();
4596 let center = &points[0];
4597 let start = &points[1];
4598 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
4599 return Err(internal_err(
4600 "Being inside a sketch block should have already been checked above",
4601 self,
4602 ));
4603 };
4604 let (constraint_name, is_diameter) = match &constraint.kind {
4605 SketchConstraintKind::Radius { .. } => ("radius", false),
4606 SketchConstraintKind::Diameter { .. } => ("diameter", true),
4607 _ => unreachable!(),
4608 };
4609 let sketch_vars = sketch_block_state.sketch_vars.clone();
4610 let target_segment = sketch_block_state
4611 .needed_by_engine
4612 .iter()
4613 .find_map(|seg| match &seg.kind {
4614 UnsolvedSegmentKind::Arc {
4615 center_object_id,
4616 start_object_id,
4617 end,
4618 ..
4619 } if *center_object_id == center.object_id
4620 && *start_object_id == start.object_id =>
4621 {
4622 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
4623 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
4624 (*end_x, *end_y)
4625 }
4626 _ => return None,
4627 };
4628 Some(CircularSegmentConstraintTarget::Arc {
4629 object_id: seg.object_id,
4630 end: [end_x_var, end_y_var],
4631 })
4632 }
4633 UnsolvedSegmentKind::Circle {
4634 center_object_id,
4635 start_object_id,
4636 ..
4637 } if *center_object_id == center.object_id
4638 && *start_object_id == start.object_id =>
4639 {
4640 Some(CircularSegmentConstraintTarget::Circle {
4641 object_id: seg.object_id,
4642 })
4643 }
4644 _ => None,
4645 })
4646 .ok_or_else(|| {
4647 internal_err(
4648 format!("Could not find circular segment for {} constraint", constraint_name),
4649 range,
4650 )
4651 })?;
4652 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
4653 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4654 center.vars.x.to_constraint_id(range)?,
4655 center.vars.y.to_constraint_id(range)?,
4656 );
4657 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4658 start.vars.x.to_constraint_id(range)?,
4659 start.vars.y.to_constraint_id(range)?,
4660 );
4661 let solver_constraint = match target_segment {
4662 CircularSegmentConstraintTarget::Arc { end, .. } => {
4663 let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
4664 center: center_point,
4665 start: start_point,
4666 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
4667 end[0].to_constraint_id(range)?,
4668 end[1].to_constraint_id(range)?,
4669 ),
4670 };
4671 Constraint::ArcRadius(solver_arc, radius_value)
4672 }
4673 CircularSegmentConstraintTarget::Circle { .. } => {
4674 let sketch_var_ty = solver_numeric_type(exec_state);
4675 let start_x =
4676 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
4677 let start_y =
4678 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
4679 let center_x =
4680 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
4681 let center_y =
4682 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
4683
4684 let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
4686
4687 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4688 let message =
4689 "Being inside a sketch block should have already been checked above"
4690 .to_owned();
4691 debug_assert!(false, "{}", &message);
4692 return Err(internal_err(message, self));
4693 };
4694 let radius_id = sketch_block_state.next_sketch_var_id();
4695 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4696 value: Box::new(crate::execution::SketchVar {
4697 id: radius_id,
4698 initial_value: radius_initial_value,
4699 ty: sketch_var_ty,
4700 meta: vec![],
4701 }),
4702 });
4703 let radius =
4704 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
4705 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
4706 center: center_point,
4707 radius,
4708 };
4709 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
4710 start_point,
4711 center_point,
4712 radius,
4713 ));
4714 Constraint::CircleRadius(solver_circle, radius_value)
4715 }
4716 };
4717
4718 let constraint_id = exec_state.next_object_id();
4719 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4720 let message =
4721 "Being inside a sketch block should have already been checked above".to_owned();
4722 debug_assert!(false, "{}", &message);
4723 return Err(internal_err(message, self));
4724 };
4725 sketch_block_state.solver_constraints.push(solver_constraint);
4726 use crate::execution::Artifact;
4727 use crate::execution::CodeRef;
4728 use crate::execution::SketchBlockConstraint;
4729 use crate::execution::SketchBlockConstraintType;
4730 use crate::front::SourceRef;
4731 let segment_object_id = match target_segment {
4732 CircularSegmentConstraintTarget::Arc { object_id, .. }
4733 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
4734 };
4735
4736 let constraint = if is_diameter {
4737 use crate::frontend::sketch::Diameter;
4738 crate::front::Constraint::Diameter(Diameter {
4739 arc: segment_object_id,
4740 diameter: n.try_into().map_err(|_| {
4741 internal_err("Failed to convert diameter units numeric suffix:", range)
4742 })?,
4743 label_position,
4744 source,
4745 })
4746 } else {
4747 use crate::frontend::sketch::Radius;
4748 crate::front::Constraint::Radius(Radius {
4749 arc: segment_object_id,
4750 radius: n.try_into().map_err(|_| {
4751 internal_err("Failed to convert radius units numeric suffix:", range)
4752 })?,
4753 label_position,
4754 source,
4755 })
4756 };
4757 sketch_block_state.sketch_constraints.push(constraint_id);
4758 let Some(sketch_id) = sketch_block_state.sketch_id else {
4759 let message = "Sketch id missing for constraint artifact".to_owned();
4760 debug_assert!(false, "{}", &message);
4761 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4762 };
4763 let artifact_id = exec_state.next_artifact_id();
4764 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4765 id: artifact_id,
4766 sketch_id,
4767 constraint_id,
4768 constraint_type: SketchBlockConstraintType::from(&constraint),
4769 code_ref: CodeRef::placeholder(range),
4770 }));
4771 exec_state.add_scene_object(
4772 Object {
4773 id: constraint_id,
4774 kind: ObjectKind::Constraint { constraint },
4775 label: Default::default(),
4776 comments: Default::default(),
4777 artifact_id,
4778 source: SourceRef::new(range, self.node_path.clone()),
4779 },
4780 range,
4781 );
4782 }
4783 SketchConstraintKind::HorizontalDistance { points, label_position } => {
4784 let range = self.as_source_range();
4785 let p0 = &points[0];
4786 let p1 = &points[1];
4787 let constraint_id = exec_state.next_object_id();
4788 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4789 let message =
4790 "Being inside a sketch block should have already been checked above".to_owned();
4791 debug_assert!(false, "{}", &message);
4792 return Err(internal_err(message, self));
4793 };
4794 match (p0, p1) {
4795 (
4796 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
4797 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
4798 ) => {
4799 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4800 p0.vars.x.to_constraint_id(range)?,
4801 p0.vars.y.to_constraint_id(range)?,
4802 );
4803 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4804 p1.vars.x.to_constraint_id(range)?,
4805 p1.vars.y.to_constraint_id(range)?,
4806 );
4807 sketch_block_state
4808 .solver_constraints
4809 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
4810 }
4811 (
4812 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4813 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4814 ) => {
4815 sketch_block_state
4817 .solver_constraints
4818 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
4819 }
4820 (
4821 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4822 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4823 ) => {
4824 sketch_block_state
4826 .solver_constraints
4827 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
4828 }
4829 (
4830 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4831 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4832 ) => {
4833 return Err(internal_err(
4834 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
4835 range,
4836 ));
4837 }
4838 }
4839 use crate::execution::Artifact;
4840 use crate::execution::CodeRef;
4841 use crate::execution::SketchBlockConstraint;
4842 use crate::execution::SketchBlockConstraintType;
4843 use crate::front::Distance;
4844 use crate::front::SourceRef;
4845 use crate::frontend::sketch::ConstraintSegment;
4846
4847 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
4848 points: vec![
4849 match p0 {
4850 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4851 ConstraintSegment::from(point.object_id)
4852 }
4853 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4854 ConstraintSegment::ORIGIN
4855 }
4856 },
4857 match p1 {
4858 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4859 ConstraintSegment::from(point.object_id)
4860 }
4861 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4862 ConstraintSegment::ORIGIN
4863 }
4864 },
4865 ],
4866 distance: n.try_into().map_err(|_| {
4867 internal_err("Failed to convert distance units numeric suffix:", range)
4868 })?,
4869 label_position: label_position.clone(),
4870 source,
4871 });
4872 sketch_block_state.sketch_constraints.push(constraint_id);
4873 let Some(sketch_id) = sketch_block_state.sketch_id else {
4874 let message = "Sketch id missing for constraint artifact".to_owned();
4875 debug_assert!(false, "{}", &message);
4876 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4877 };
4878 let artifact_id = exec_state.next_artifact_id();
4879 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4880 id: artifact_id,
4881 sketch_id,
4882 constraint_id,
4883 constraint_type: SketchBlockConstraintType::from(&constraint),
4884 code_ref: CodeRef::placeholder(range),
4885 }));
4886 exec_state.add_scene_object(
4887 Object {
4888 id: constraint_id,
4889 kind: ObjectKind::Constraint { constraint },
4890 label: Default::default(),
4891 comments: Default::default(),
4892 artifact_id,
4893 source: SourceRef::new(range, self.node_path.clone()),
4894 },
4895 range,
4896 );
4897 }
4898 SketchConstraintKind::VerticalDistance { points, label_position } => {
4899 let range = self.as_source_range();
4900 let p0 = &points[0];
4901 let p1 = &points[1];
4902 let constraint_id = exec_state.next_object_id();
4903 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4904 let message =
4905 "Being inside a sketch block should have already been checked above".to_owned();
4906 debug_assert!(false, "{}", &message);
4907 return Err(internal_err(message, self));
4908 };
4909 match (p0, p1) {
4910 (
4911 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
4912 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
4913 ) => {
4914 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4915 p0.vars.x.to_constraint_id(range)?,
4916 p0.vars.y.to_constraint_id(range)?,
4917 );
4918 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4919 p1.vars.x.to_constraint_id(range)?,
4920 p1.vars.y.to_constraint_id(range)?,
4921 );
4922 sketch_block_state
4923 .solver_constraints
4924 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
4925 }
4926 (
4927 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4928 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4929 ) => {
4930 sketch_block_state
4931 .solver_constraints
4932 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
4933 }
4934 (
4935 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4936 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4937 ) => {
4938 sketch_block_state
4939 .solver_constraints
4940 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
4941 }
4942 (
4943 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4944 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4945 ) => {
4946 return Err(internal_err(
4947 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
4948 range,
4949 ));
4950 }
4951 }
4952 use crate::execution::Artifact;
4953 use crate::execution::CodeRef;
4954 use crate::execution::SketchBlockConstraint;
4955 use crate::execution::SketchBlockConstraintType;
4956 use crate::front::Distance;
4957 use crate::front::SourceRef;
4958 use crate::frontend::sketch::ConstraintSegment;
4959
4960 let constraint = crate::front::Constraint::VerticalDistance(Distance {
4961 points: vec![
4962 match p0 {
4963 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4964 ConstraintSegment::from(point.object_id)
4965 }
4966 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4967 ConstraintSegment::ORIGIN
4968 }
4969 },
4970 match p1 {
4971 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4972 ConstraintSegment::from(point.object_id)
4973 }
4974 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4975 ConstraintSegment::ORIGIN
4976 }
4977 },
4978 ],
4979 distance: n.try_into().map_err(|_| {
4980 internal_err("Failed to convert distance units numeric suffix:", range)
4981 })?,
4982 label_position: label_position.clone(),
4983 source,
4984 });
4985 sketch_block_state.sketch_constraints.push(constraint_id);
4986 let Some(sketch_id) = sketch_block_state.sketch_id else {
4987 let message = "Sketch id missing for constraint artifact".to_owned();
4988 debug_assert!(false, "{}", &message);
4989 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4990 };
4991 let artifact_id = exec_state.next_artifact_id();
4992 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4993 id: artifact_id,
4994 sketch_id,
4995 constraint_id,
4996 constraint_type: SketchBlockConstraintType::from(&constraint),
4997 code_ref: CodeRef::placeholder(range),
4998 }));
4999 exec_state.add_scene_object(
5000 Object {
5001 id: constraint_id,
5002 kind: ObjectKind::Constraint { constraint },
5003 label: Default::default(),
5004 comments: Default::default(),
5005 artifact_id,
5006 source: SourceRef::new(range, self.node_path.clone()),
5007 },
5008 range,
5009 );
5010 }
5011 }
5012 return Ok(KclValue::none());
5013 }
5014 _ => {
5015 return Err(KclError::new_semantic(KclErrorDetails::new(
5016 format!(
5017 "Cannot create an equivalence constraint between values of these types: {} and {}",
5018 left_value.human_friendly_type(),
5019 right_value.human_friendly_type()
5020 ),
5021 vec![self.into()],
5022 )));
5023 }
5024 }
5025 }
5026
5027 let left = number_as_f64(&left_value, self.left.clone().into())?;
5028 let right = number_as_f64(&right_value, self.right.clone().into())?;
5029
5030 let value = match self.operator {
5031 BinaryOperator::Add => {
5032 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
5033 self.warn_on_unknown(&ty, "Adding", exec_state);
5034 KclValue::Number { value: l + r, meta, ty }
5035 }
5036 BinaryOperator::Sub => {
5037 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
5038 self.warn_on_unknown(&ty, "Subtracting", exec_state);
5039 KclValue::Number { value: l - r, meta, ty }
5040 }
5041 BinaryOperator::Mul => {
5042 let (l, r, ty) = NumericType::combine_mul(left, right);
5043 self.warn_on_unknown(&ty, "Multiplying", exec_state);
5044 KclValue::Number { value: l * r, meta, ty }
5045 }
5046 BinaryOperator::Div => {
5047 let (l, r, ty) = NumericType::combine_div(left, right);
5048 self.warn_on_unknown(&ty, "Dividing", exec_state);
5049 KclValue::Number { value: l / r, meta, ty }
5050 }
5051 BinaryOperator::Mod => {
5052 let (l, r, ty) = NumericType::combine_mod(left, right);
5053 self.warn_on_unknown(&ty, "Modulo of", exec_state);
5054 KclValue::Number { value: l % r, meta, ty }
5055 }
5056 BinaryOperator::Pow => KclValue::Number {
5057 value: libm::pow(left.n, right.n),
5058 meta,
5059 ty: exec_state.current_default_units(),
5060 },
5061 BinaryOperator::Neq => {
5062 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5063 self.warn_on_unknown(&ty, "Comparing", exec_state);
5064 KclValue::Bool { value: l != r, meta }
5065 }
5066 BinaryOperator::Gt => {
5067 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5068 self.warn_on_unknown(&ty, "Comparing", exec_state);
5069 KclValue::Bool { value: l > r, meta }
5070 }
5071 BinaryOperator::Gte => {
5072 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5073 self.warn_on_unknown(&ty, "Comparing", exec_state);
5074 KclValue::Bool { value: l >= r, meta }
5075 }
5076 BinaryOperator::Lt => {
5077 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5078 self.warn_on_unknown(&ty, "Comparing", exec_state);
5079 KclValue::Bool { value: l < r, meta }
5080 }
5081 BinaryOperator::Lte => {
5082 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5083 self.warn_on_unknown(&ty, "Comparing", exec_state);
5084 KclValue::Bool { value: l <= r, meta }
5085 }
5086 BinaryOperator::Eq => {
5087 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5088 self.warn_on_unknown(&ty, "Comparing", exec_state);
5089 KclValue::Bool { value: l == r, meta }
5090 }
5091 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
5092 };
5093
5094 Ok(value)
5095 }
5096
5097 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
5098 internal_err("missing result while evaluating binary expression", node)
5099 }
5100
5101 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
5102 if ty == &NumericType::Unknown {
5103 let sr = self.as_source_range();
5104 exec_state.clear_units_warnings(&sr);
5105 let mut err = CompilationIssue::err(
5106 sr,
5107 format!(
5108 "{verb} numbers which have unknown or incompatible units.\nYou can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`."
5109 ),
5110 );
5111 err.tag = crate::errors::Tag::UnknownNumericUnits;
5112 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
5113 }
5114 }
5115}
5116
5117impl Node<UnaryExpression> {
5118 pub(super) async fn get_result(
5119 &self,
5120 exec_state: &mut ExecState,
5121 ctx: &ExecutorContext,
5122 ) -> Result<KclValueControlFlow, KclError> {
5123 match self.operator {
5124 UnaryOperator::Not => {
5125 let value = self.argument.get_result(exec_state, ctx).await?;
5126 let value = control_continue!(value);
5127 let KclValue::Bool {
5128 value: bool_value,
5129 meta: _,
5130 } = value
5131 else {
5132 return Err(KclError::new_semantic(KclErrorDetails::new(
5133 format!(
5134 "Cannot apply unary operator ! to non-boolean value: {}",
5135 value.human_friendly_type()
5136 ),
5137 vec![self.into()],
5138 )));
5139 };
5140 let meta = vec![Metadata {
5141 source_range: self.into(),
5142 }];
5143 let negated = KclValue::Bool {
5144 value: !bool_value,
5145 meta,
5146 };
5147
5148 Ok(negated.continue_())
5149 }
5150 UnaryOperator::Neg => {
5151 let value = self.argument.get_result(exec_state, ctx).await?;
5152 let value = control_continue!(value);
5153 let err = || {
5154 KclError::new_semantic(KclErrorDetails::new(
5155 format!(
5156 "You can only negate numbers, planes, or lines, but this is a {}",
5157 value.human_friendly_type()
5158 ),
5159 vec![self.into()],
5160 ))
5161 };
5162 match &value {
5163 KclValue::Number { value, ty, .. } => {
5164 let meta = vec![Metadata {
5165 source_range: self.into(),
5166 }];
5167 Ok(KclValue::Number {
5168 value: -value,
5169 meta,
5170 ty: *ty,
5171 }
5172 .continue_())
5173 }
5174 KclValue::Plane { value } => {
5175 let mut plane = value.clone();
5176 if plane.info.x_axis.x != 0.0 {
5177 plane.info.x_axis.x *= -1.0;
5178 }
5179 if plane.info.x_axis.y != 0.0 {
5180 plane.info.x_axis.y *= -1.0;
5181 }
5182 if plane.info.x_axis.z != 0.0 {
5183 plane.info.x_axis.z *= -1.0;
5184 }
5185
5186 plane.id = exec_state.next_uuid();
5187 plane.object_id = None;
5188 Ok(KclValue::Plane { value: plane }.continue_())
5189 }
5190 KclValue::Object {
5191 value: values, meta, ..
5192 } => {
5193 let Some(direction) = values.get("direction") else {
5195 return Err(err());
5196 };
5197
5198 let direction = match direction {
5199 KclValue::Tuple { value: values, meta } => {
5200 let values = values
5201 .iter()
5202 .map(|v| match v {
5203 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
5204 value: *value * -1.0,
5205 ty: *ty,
5206 meta: meta.clone(),
5207 }),
5208 _ => Err(err()),
5209 })
5210 .collect::<Result<Vec<_>, _>>()?;
5211
5212 KclValue::Tuple {
5213 value: values,
5214 meta: meta.clone(),
5215 }
5216 }
5217 KclValue::HomArray {
5218 value: values,
5219 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
5220 } => {
5221 let values = values
5222 .iter()
5223 .map(|v| match v {
5224 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
5225 value: *value * -1.0,
5226 ty: *ty,
5227 meta: meta.clone(),
5228 }),
5229 _ => Err(err()),
5230 })
5231 .collect::<Result<Vec<_>, _>>()?;
5232
5233 KclValue::HomArray {
5234 value: values,
5235 ty: ty.clone(),
5236 }
5237 }
5238 _ => return Err(err()),
5239 };
5240
5241 let mut value = values.clone();
5242 value.insert("direction".to_owned(), direction);
5243 Ok(KclValue::Object {
5244 value,
5245 meta: meta.clone(),
5246 constrainable: false,
5247 }
5248 .continue_())
5249 }
5250 _ => Err(err()),
5251 }
5252 }
5253 UnaryOperator::Plus => {
5254 let operand = self.argument.get_result(exec_state, ctx).await?;
5255 let operand = control_continue!(operand);
5256 match operand {
5257 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
5258 _ => Err(KclError::new_semantic(KclErrorDetails::new(
5259 format!(
5260 "You can only apply unary + to numbers or planes, but this is a {}",
5261 operand.human_friendly_type()
5262 ),
5263 vec![self.into()],
5264 ))),
5265 }
5266 }
5267 }
5268 }
5269}
5270
5271pub(crate) async fn execute_pipe_body(
5272 exec_state: &mut ExecState,
5273 body: &[Expr],
5274 source_range: SourceRange,
5275 ctx: &ExecutorContext,
5276) -> Result<KclValueControlFlow, KclError> {
5277 let Some((first, body)) = body.split_first() else {
5278 return Err(KclError::new_semantic(KclErrorDetails::new(
5279 "Pipe expressions cannot be empty".to_owned(),
5280 vec![source_range],
5281 )));
5282 };
5283 let meta = Metadata {
5288 source_range: SourceRange::from(first),
5289 };
5290 let output = ctx
5291 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
5292 .await?;
5293 let output = control_continue!(output);
5294
5295 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
5299 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
5301 exec_state.mod_local.pipe_value = previous_pipe_value;
5303
5304 result
5305}
5306
5307#[async_recursion]
5310async fn inner_execute_pipe_body(
5311 exec_state: &mut ExecState,
5312 body: &[Expr],
5313 ctx: &ExecutorContext,
5314) -> Result<KclValueControlFlow, KclError> {
5315 for expression in body {
5316 if let Expr::TagDeclarator(_) = expression {
5317 return Err(KclError::new_semantic(KclErrorDetails::new(
5318 format!("This cannot be in a PipeExpression: {expression:?}"),
5319 vec![expression.into()],
5320 )));
5321 }
5322 let metadata = Metadata {
5323 source_range: SourceRange::from(expression),
5324 };
5325 let output = ctx
5326 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
5327 .await?;
5328 let output = control_continue!(output);
5329 exec_state.mod_local.pipe_value = Some(output);
5330 }
5331 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
5333 Ok(final_output.continue_())
5334}
5335
5336impl Node<TagDeclarator> {
5337 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
5338 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
5339 value: self.name.clone(),
5340 info: Vec::new(),
5341 meta: vec![Metadata {
5342 source_range: self.into(),
5343 }],
5344 }));
5345
5346 exec_state
5347 .mut_stack()
5348 .add(self.name.clone(), memory_item, self.into())?;
5349
5350 Ok(self.into())
5351 }
5352}
5353
5354impl Node<ArrayExpression> {
5355 #[async_recursion]
5356 pub(super) async fn execute(
5357 &self,
5358 exec_state: &mut ExecState,
5359 ctx: &ExecutorContext,
5360 ) -> Result<KclValueControlFlow, KclError> {
5361 let mut results = Vec::with_capacity(self.elements.len());
5362
5363 for element in &self.elements {
5364 let metadata = Metadata::from(element);
5365 let value = ctx
5368 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
5369 .await?;
5370 let value = control_continue!(value);
5371
5372 results.push(value);
5373 }
5374
5375 Ok(KclValue::HomArray {
5376 value: results,
5377 ty: RuntimeType::Primitive(PrimitiveType::Any),
5378 }
5379 .continue_())
5380 }
5381}
5382
5383impl Node<ArrayRangeExpression> {
5384 #[async_recursion]
5385 pub(super) async fn execute(
5386 &self,
5387 exec_state: &mut ExecState,
5388 ctx: &ExecutorContext,
5389 ) -> Result<KclValueControlFlow, KclError> {
5390 let metadata = Metadata::from(&self.start_element);
5391 let start_val = ctx
5392 .execute_expr(
5393 &self.start_element,
5394 exec_state,
5395 &metadata,
5396 &[],
5397 StatementKind::Expression,
5398 )
5399 .await?;
5400 let start_val = control_continue!(start_val);
5401 let start = start_val
5402 .as_ty_f64()
5403 .ok_or(KclError::new_semantic(KclErrorDetails::new(
5404 format!(
5405 "Expected number for range start but found {}",
5406 start_val.human_friendly_type()
5407 ),
5408 vec![self.into()],
5409 )))?;
5410 let metadata = Metadata::from(&self.end_element);
5411 let end_val = ctx
5412 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
5413 .await?;
5414 let end_val = control_continue!(end_val);
5415 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
5416 format!(
5417 "Expected number for range end but found {}",
5418 end_val.human_friendly_type()
5419 ),
5420 vec![self.into()],
5421 )))?;
5422
5423 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
5424 let Some(start) = crate::try_f64_to_i64(start) else {
5425 return Err(KclError::new_semantic(KclErrorDetails::new(
5426 format!("Range start must be an integer, but found {start}"),
5427 vec![self.into()],
5428 )));
5429 };
5430 let Some(end) = crate::try_f64_to_i64(end) else {
5431 return Err(KclError::new_semantic(KclErrorDetails::new(
5432 format!("Range end must be an integer, but found {end}"),
5433 vec![self.into()],
5434 )));
5435 };
5436
5437 if end < start {
5438 return Err(KclError::new_semantic(KclErrorDetails::new(
5439 format!("Range start is greater than range end: {start} .. {end}"),
5440 vec![self.into()],
5441 )));
5442 }
5443
5444 let range: Vec<_> = if self.end_inclusive {
5445 (start..=end).collect()
5446 } else {
5447 (start..end).collect()
5448 };
5449
5450 let meta = vec![Metadata {
5451 source_range: self.into(),
5452 }];
5453
5454 Ok(KclValue::HomArray {
5455 value: range
5456 .into_iter()
5457 .map(|num| KclValue::Number {
5458 value: num as f64,
5459 ty,
5460 meta: meta.clone(),
5461 })
5462 .collect(),
5463 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
5464 }
5465 .continue_())
5466 }
5467}
5468
5469impl Node<ObjectExpression> {
5470 #[async_recursion]
5471 pub(super) async fn execute(
5472 &self,
5473 exec_state: &mut ExecState,
5474 ctx: &ExecutorContext,
5475 ) -> Result<KclValueControlFlow, KclError> {
5476 let mut object = HashMap::with_capacity(self.properties.len());
5477 for property in &self.properties {
5478 let metadata = Metadata::from(&property.value);
5479 let result = ctx
5480 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
5481 .await?;
5482 let result = control_continue!(result);
5483 object.insert(property.key.name.clone(), result);
5484 }
5485
5486 Ok(KclValue::Object {
5487 value: object,
5488 meta: vec![Metadata {
5489 source_range: self.into(),
5490 }],
5491 constrainable: false,
5492 }
5493 .continue_())
5494 }
5495}
5496
5497fn article_for<S: AsRef<str>>(s: S) -> &'static str {
5498 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
5500 "an"
5501 } else {
5502 "a"
5503 }
5504}
5505
5506fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
5507 v.as_ty_f64().ok_or_else(|| {
5508 let actual_type = v.human_friendly_type();
5509 KclError::new_semantic(KclErrorDetails::new(
5510 format!("Expected a number, but found {actual_type}",),
5511 vec![source_range],
5512 ))
5513 })
5514}
5515
5516impl Node<IfExpression> {
5517 #[async_recursion]
5518 pub(super) async fn get_result(
5519 &self,
5520 exec_state: &mut ExecState,
5521 ctx: &ExecutorContext,
5522 ) -> Result<KclValueControlFlow, KclError> {
5523 let cond_value = ctx
5525 .execute_expr(
5526 &self.cond,
5527 exec_state,
5528 &Metadata::from(self),
5529 &[],
5530 StatementKind::Expression,
5531 )
5532 .await?;
5533 let cond_value = control_continue!(cond_value);
5534 if cond_value.get_bool()? {
5535 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
5536 return Ok(block_result.unwrap());
5540 }
5541
5542 for else_if in &self.else_ifs {
5544 let cond_value = ctx
5545 .execute_expr(
5546 &else_if.cond,
5547 exec_state,
5548 &Metadata::from(self),
5549 &[],
5550 StatementKind::Expression,
5551 )
5552 .await?;
5553 let cond_value = control_continue!(cond_value);
5554 if cond_value.get_bool()? {
5555 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
5556 return Ok(block_result.unwrap());
5560 }
5561 }
5562
5563 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
5565 .await
5566 .map(|expr| expr.unwrap())
5567 }
5568}
5569
5570#[derive(Debug)]
5571enum Property {
5572 UInt(usize),
5573 String(String),
5574}
5575
5576impl Property {
5577 #[allow(clippy::too_many_arguments)]
5578 async fn try_from<'a>(
5579 computed: bool,
5580 value: Expr,
5581 exec_state: &mut ExecState,
5582 sr: SourceRange,
5583 ctx: &ExecutorContext,
5584 metadata: &Metadata,
5585 annotations: &[Node<Annotation>],
5586 statement_kind: StatementKind<'a>,
5587 ) -> Result<Self, KclError> {
5588 let property_sr = vec![sr];
5589 if !computed {
5590 let Expr::Name(identifier) = value else {
5591 return Err(KclError::new_semantic(KclErrorDetails::new(
5593 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
5594 .to_owned(),
5595 property_sr,
5596 )));
5597 };
5598 return Ok(Property::String(identifier.to_string()));
5599 }
5600
5601 let prop_value = ctx
5602 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
5603 .await?;
5604 let prop_value = match prop_value.control {
5605 ControlFlowKind::Continue => prop_value.into_value(),
5606 ControlFlowKind::Exit => {
5607 let message = "Early return inside array brackets is currently not supported".to_owned();
5608 debug_assert!(false, "{}", &message);
5609 return Err(internal_err(message, sr));
5610 }
5611 };
5612 match prop_value {
5613 KclValue::Number { value, ty, meta: _ } => {
5614 if !matches!(
5615 ty,
5616 NumericType::Unknown
5617 | NumericType::Default { .. }
5618 | NumericType::Known(crate::exec::UnitType::Count)
5619 ) {
5620 return Err(KclError::new_semantic(KclErrorDetails::new(
5621 format!(
5622 "{value} is not a valid index, indices must be non-dimensional numbers. If you're sure this is correct, you can add `: number(Count)` to tell KCL this number is an index"
5623 ),
5624 property_sr,
5625 )));
5626 }
5627 if let Some(x) = crate::try_f64_to_usize(value) {
5628 Ok(Property::UInt(x))
5629 } else {
5630 Err(KclError::new_semantic(KclErrorDetails::new(
5631 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
5632 property_sr,
5633 )))
5634 }
5635 }
5636 _ => Err(KclError::new_semantic(KclErrorDetails::new(
5637 "Only numbers (>= 0) can be indexes".to_owned(),
5638 vec![sr],
5639 ))),
5640 }
5641 }
5642}
5643
5644impl Property {
5645 fn type_name(&self) -> &'static str {
5646 match self {
5647 Property::UInt(_) => "number",
5648 Property::String(_) => "string",
5649 }
5650 }
5651}
5652
5653impl Node<PipeExpression> {
5654 #[async_recursion]
5655 pub(super) async fn get_result(
5656 &self,
5657 exec_state: &mut ExecState,
5658 ctx: &ExecutorContext,
5659 ) -> Result<KclValueControlFlow, KclError> {
5660 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
5661 }
5662}
5663
5664#[cfg(test)]
5665mod test {
5666 use std::sync::Arc;
5667
5668 use tokio::io::AsyncWriteExt;
5669
5670 use super::*;
5671 use crate::ExecutorSettings;
5672 use crate::errors::Severity;
5673 use crate::exec::UnitType;
5674 use crate::execution::ContextType;
5675 use crate::execution::parse_execute;
5676
5677 #[tokio::test(flavor = "multi_thread")]
5678 async fn ascription() {
5679 let program = r#"
5680a = 42: number
5681b = a: number
5682p = {
5683 origin = { x = 0, y = 0, z = 0 },
5684 xAxis = { x = 1, y = 0, z = 0 },
5685 yAxis = { x = 0, y = 1, z = 0 },
5686 zAxis = { x = 0, y = 0, z = 1 }
5687}: Plane
5688arr1 = [42]: [number(cm)]
5689"#;
5690
5691 let result = parse_execute(program).await.unwrap();
5692 let mem = result.exec_state.stack();
5693 assert!(matches!(
5694 mem.memory
5695 .get_from("p", result.mem_env, SourceRange::default(), 0)
5696 .unwrap(),
5697 KclValue::Plane { .. }
5698 ));
5699 let arr1 = mem
5700 .memory
5701 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
5702 .unwrap();
5703 if let KclValue::HomArray { value, ty } = arr1 {
5704 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
5705 assert_eq!(
5706 *ty,
5707 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
5708 );
5709 if let KclValue::Number { value, ty, .. } = &value[0] {
5711 assert_eq!(*value, 42.0);
5713 assert_eq!(
5714 *ty,
5715 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
5716 );
5717 } else {
5718 panic!("Expected a number; found {:?}", value[0]);
5719 }
5720 } else {
5721 panic!("Expected HomArray; found {arr1:?}");
5722 }
5723
5724 let program = r#"
5725a = 42: string
5726"#;
5727 let result = parse_execute(program).await;
5728 let err = result.unwrap_err();
5729 assert!(
5730 err.to_string()
5731 .contains("could not coerce a number (with type `number`) to type `string`"),
5732 "Expected error but found {err:?}"
5733 );
5734
5735 let program = r#"
5736a = 42: Plane
5737"#;
5738 let result = parse_execute(program).await;
5739 let err = result.unwrap_err();
5740 assert!(
5741 err.to_string()
5742 .contains("could not coerce a number (with type `number`) to type `Plane`"),
5743 "Expected error but found {err:?}"
5744 );
5745
5746 let program = r#"
5747arr = [0]: [string]
5748"#;
5749 let result = parse_execute(program).await;
5750 let err = result.unwrap_err();
5751 assert!(
5752 err.to_string().contains(
5753 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
5754 ),
5755 "Expected error but found {err:?}"
5756 );
5757
5758 let program = r#"
5759mixedArr = [0, "a"]: [number(mm)]
5760"#;
5761 let result = parse_execute(program).await;
5762 let err = result.unwrap_err();
5763 assert!(
5764 err.to_string().contains(
5765 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5766 ),
5767 "Expected error but found {err:?}"
5768 );
5769
5770 let program = r#"
5771mixedArr = [0, "a"]: [mm]
5772"#;
5773 let result = parse_execute(program).await;
5774 let err = result.unwrap_err();
5775 assert!(
5776 err.to_string().contains(
5777 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5778 ),
5779 "Expected error but found {err:?}"
5780 );
5781 }
5782
5783 #[tokio::test(flavor = "multi_thread")]
5784 async fn neg_plane() {
5785 let program = r#"
5786p = {
5787 origin = { x = 0, y = 0, z = 0 },
5788 xAxis = { x = 1, y = 0, z = 0 },
5789 yAxis = { x = 0, y = 1, z = 0 },
5790}: Plane
5791p2 = -p
5792"#;
5793
5794 let result = parse_execute(program).await.unwrap();
5795 let mem = result.exec_state.stack();
5796 match mem
5797 .memory
5798 .get_from("p2", result.mem_env, SourceRange::default(), 0)
5799 .unwrap()
5800 {
5801 KclValue::Plane { value } => {
5802 assert_eq!(value.info.x_axis.x, -1.0);
5803 assert_eq!(value.info.x_axis.y, 0.0);
5804 assert_eq!(value.info.x_axis.z, 0.0);
5805 }
5806 _ => unreachable!(),
5807 }
5808 }
5809
5810 #[tokio::test(flavor = "multi_thread")]
5811 async fn multiple_returns() {
5812 let program = r#"fn foo() {
5813 return 0
5814 return 42
5815}
5816
5817a = foo()
5818"#;
5819
5820 let result = parse_execute(program).await;
5821 assert!(result.unwrap_err().to_string().contains("return"));
5822 }
5823
5824 #[tokio::test(flavor = "multi_thread")]
5825 async fn load_all_modules() {
5826 let program_a_kcl = r#"
5828export a = 1
5829"#;
5830 let program_b_kcl = r#"
5832import a from 'a.kcl'
5833
5834export b = a + 1
5835"#;
5836 let program_c_kcl = r#"
5838import a from 'a.kcl'
5839
5840export c = a + 2
5841"#;
5842
5843 let main_kcl = r#"
5845import b from 'b.kcl'
5846import c from 'c.kcl'
5847
5848d = b + c
5849"#;
5850
5851 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
5852 .parse_errs_as_err()
5853 .unwrap();
5854
5855 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
5856
5857 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
5858 .await
5859 .unwrap()
5860 .write_all(main_kcl.as_bytes())
5861 .await
5862 .unwrap();
5863
5864 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
5865 .await
5866 .unwrap()
5867 .write_all(program_a_kcl.as_bytes())
5868 .await
5869 .unwrap();
5870
5871 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
5872 .await
5873 .unwrap()
5874 .write_all(program_b_kcl.as_bytes())
5875 .await
5876 .unwrap();
5877
5878 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
5879 .await
5880 .unwrap()
5881 .write_all(program_c_kcl.as_bytes())
5882 .await
5883 .unwrap();
5884
5885 let exec_ctxt = ExecutorContext {
5886 engine: Arc::new(Box::new(
5887 crate::engine::conn_mock::EngineConnection::new()
5888 .map_err(|err| {
5889 internal_err(
5890 format!("Failed to create mock engine connection: {err}"),
5891 SourceRange::default(),
5892 )
5893 })
5894 .unwrap(),
5895 )),
5896 engine_batch: crate::engine::EngineBatchContext::default(),
5897 fs: Arc::new(crate::fs::FileManager::new()),
5898 settings: ExecutorSettings {
5899 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
5900 ..Default::default()
5901 },
5902 context_type: ContextType::Mock,
5903 };
5904 let mut exec_state = ExecState::new(&exec_ctxt);
5905
5906 exec_ctxt
5907 .run(
5908 &crate::Program {
5909 ast: main.clone(),
5910 original_file_contents: "".to_owned(),
5911 },
5912 &mut exec_state,
5913 )
5914 .await
5915 .unwrap();
5916 }
5917
5918 #[tokio::test(flavor = "multi_thread")]
5919 async fn user_coercion() {
5920 let program = r#"fn foo(x: Axis2d) {
5921 return 0
5922}
5923
5924foo(x = { direction = [0, 0], origin = [0, 0]})
5925"#;
5926
5927 parse_execute(program).await.unwrap();
5928
5929 let program = r#"fn foo(x: Axis3d) {
5930 return 0
5931}
5932
5933foo(x = { direction = [0, 0], origin = [0, 0]})
5934"#;
5935
5936 parse_execute(program).await.unwrap_err();
5937 }
5938
5939 #[tokio::test(flavor = "multi_thread")]
5940 async fn coerce_return() {
5941 let program = r#"fn foo(): number(mm) {
5942 return 42
5943}
5944
5945a = foo()
5946"#;
5947
5948 parse_execute(program).await.unwrap();
5949
5950 let program = r#"fn foo(): mm {
5951 return 42
5952}
5953
5954a = foo()
5955"#;
5956
5957 parse_execute(program).await.unwrap();
5958
5959 let program = r#"fn foo(): number(mm) {
5960 return { bar: 42 }
5961}
5962
5963a = foo()
5964"#;
5965
5966 parse_execute(program).await.unwrap_err();
5967
5968 let program = r#"fn foo(): mm {
5969 return { bar: 42 }
5970}
5971
5972a = foo()
5973"#;
5974
5975 parse_execute(program).await.unwrap_err();
5976 }
5977
5978 #[tokio::test(flavor = "multi_thread")]
5979 async fn test_sensible_error_when_missing_equals_in_kwarg() {
5980 for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
5981 .into_iter()
5982 .enumerate()
5983 {
5984 let program = format!(
5985 "fn foo() {{ return 0 }}
5986z = 0
5987fn f(x, y, z) {{ return 0 }}
5988{call}"
5989 );
5990 let err = parse_execute(&program).await.unwrap_err();
5991 let msg = err.message();
5992 assert!(
5993 msg.contains("This argument needs a label, but it doesn't have one"),
5994 "failed test {i}: {msg}"
5995 );
5996 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
5997 if i == 0 {
5998 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
5999 }
6000 }
6001 }
6002
6003 #[tokio::test(flavor = "multi_thread")]
6004 async fn default_param_for_unlabeled() {
6005 let ast = r#"fn myExtrude(@sk, length) {
6008 return extrude(sk, length)
6009}
6010sketch001 = startSketchOn(XY)
6011 |> circle(center = [0, 0], radius = 93.75)
6012 |> myExtrude(length = 40)
6013"#;
6014
6015 parse_execute(ast).await.unwrap();
6016 }
6017
6018 #[tokio::test(flavor = "multi_thread")]
6019 async fn dont_use_unlabelled_as_input() {
6020 let ast = r#"length = 10
6022startSketchOn(XY)
6023 |> circle(center = [0, 0], radius = 93.75)
6024 |> extrude(length)
6025"#;
6026
6027 parse_execute(ast).await.unwrap();
6028 }
6029
6030 #[tokio::test(flavor = "multi_thread")]
6031 async fn ascription_in_binop() {
6032 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
6033 parse_execute(ast).await.unwrap();
6034
6035 let ast = r#"foo = tan(0): rad - 4deg"#;
6036 parse_execute(ast).await.unwrap();
6037 }
6038
6039 #[tokio::test(flavor = "multi_thread")]
6040 async fn neg_sqrt() {
6041 let ast = r#"bad = sqrt(-2)"#;
6042
6043 let e = parse_execute(ast).await.unwrap_err();
6044 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
6046 }
6047
6048 #[tokio::test(flavor = "multi_thread")]
6049 async fn non_array_fns() {
6050 let ast = r#"push(1, item = 2)
6051pop(1)
6052map(1, f = fn(@x) { return x + 1 })
6053reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
6054
6055 parse_execute(ast).await.unwrap();
6056 }
6057
6058 #[tokio::test(flavor = "multi_thread")]
6059 async fn non_array_indexing() {
6060 let good = r#"a = 42
6061good = a[0]
6062"#;
6063 let result = parse_execute(good).await.unwrap();
6064 let mem = result.exec_state.stack();
6065 let num = mem
6066 .memory
6067 .get_from("good", result.mem_env, SourceRange::default(), 0)
6068 .unwrap()
6069 .as_ty_f64()
6070 .unwrap();
6071 assert_eq!(num.n, 42.0);
6072
6073 let bad = r#"a = 42
6074bad = a[1]
6075"#;
6076
6077 parse_execute(bad).await.unwrap_err();
6078 }
6079
6080 #[tokio::test(flavor = "multi_thread")]
6081 async fn coerce_unknown_to_length() {
6082 let ast = r#"x = 2mm * 2mm
6083y = x: number(Length)"#;
6084 let e = parse_execute(ast).await.unwrap_err();
6085 assert!(
6086 e.message().contains("could not coerce"),
6087 "Error message: '{}'",
6088 e.message()
6089 );
6090
6091 let ast = r#"x = 2mm
6092y = x: number(Length)"#;
6093 let result = parse_execute(ast).await.unwrap();
6094 let mem = result.exec_state.stack();
6095 let num = mem
6096 .memory
6097 .get_from("y", result.mem_env, SourceRange::default(), 0)
6098 .unwrap()
6099 .as_ty_f64()
6100 .unwrap();
6101 assert_eq!(num.n, 2.0);
6102 assert_eq!(num.ty, NumericType::mm());
6103 }
6104
6105 #[tokio::test(flavor = "multi_thread")]
6106 async fn one_warning_unknown() {
6107 let ast = r#"
6108// Should warn once
6109a = PI * 2
6110// Should warn once
6111b = (PI * 2) / 3
6112// Should not warn
6113c = ((PI * 2) / 3): number(deg)
6114"#;
6115
6116 let result = parse_execute(ast).await.unwrap();
6117 assert_eq!(result.exec_state.issues().len(), 2);
6118 }
6119
6120 #[tokio::test(flavor = "multi_thread")]
6121 async fn non_count_indexing() {
6122 let ast = r#"x = [0, 0]
6123y = x[1mm]
6124"#;
6125 parse_execute(ast).await.unwrap_err();
6126
6127 let ast = r#"x = [0, 0]
6128y = 1deg
6129z = x[y]
6130"#;
6131 parse_execute(ast).await.unwrap_err();
6132
6133 let ast = r#"x = [0, 0]
6134y = x[0mm + 1]
6135"#;
6136 parse_execute(ast).await.unwrap_err();
6137 }
6138
6139 #[tokio::test(flavor = "multi_thread")]
6140 async fn getting_property_of_plane() {
6141 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
6142 parse_execute(&ast).await.unwrap();
6143 }
6144
6145 #[tokio::test(flavor = "multi_thread")]
6146 async fn no_artifacts_from_within_hole_call() {
6147 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
6152 let out = parse_execute(&ast).await.unwrap();
6153
6154 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6156
6157 let expected = 5;
6161 assert_eq!(
6162 actual_operations.len(),
6163 expected,
6164 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6165 actual_operations.len(),
6166 );
6167 }
6168
6169 #[tokio::test(flavor = "multi_thread")]
6170 async fn feature_tree_annotation_on_user_defined_kcl() {
6171 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
6174 let out = parse_execute(&ast).await.unwrap();
6175
6176 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6178
6179 let expected = 0;
6180 assert_eq!(
6181 actual_operations.len(),
6182 expected,
6183 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6184 actual_operations.len(),
6185 );
6186 }
6187
6188 #[tokio::test(flavor = "multi_thread")]
6189 async fn no_feature_tree_annotation_on_user_defined_kcl() {
6190 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
6193 let out = parse_execute(&ast).await.unwrap();
6194
6195 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6197
6198 let expected = 2;
6199 assert_eq!(
6200 actual_operations.len(),
6201 expected,
6202 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6203 actual_operations.len(),
6204 );
6205 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
6206 assert!(matches!(actual_operations[1], Operation::GroupEnd));
6207 }
6208
6209 #[tokio::test(flavor = "multi_thread")]
6210 async fn custom_warning() {
6211 let warn = r#"
6212a = PI * 2
6213"#;
6214 let result = parse_execute(warn).await.unwrap();
6215 assert_eq!(result.exec_state.issues().len(), 1);
6216 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
6217
6218 let allow = r#"
6219@warnings(allow = unknownUnits)
6220a = PI * 2
6221"#;
6222 let result = parse_execute(allow).await.unwrap();
6223 assert_eq!(result.exec_state.issues().len(), 0);
6224
6225 let deny = r#"
6226@warnings(deny = [unknownUnits])
6227a = PI * 2
6228"#;
6229 let result = parse_execute(deny).await.unwrap();
6230 assert_eq!(result.exec_state.issues().len(), 1);
6231 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
6232 }
6233
6234 #[tokio::test(flavor = "multi_thread")]
6235 async fn sketch_block_unqualified_functions_use_sketch2() {
6236 let ast = r#"
6237s = sketch(on = XY) {
6238 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
6239 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
6240 coincident([line1.end, line2.start])
6241}
6242"#;
6243 let result = parse_execute(ast).await.unwrap();
6244 let mem = result.exec_state.stack();
6245 let sketch_value = mem
6246 .memory
6247 .get_from("s", result.mem_env, SourceRange::default(), 0)
6248 .unwrap();
6249
6250 let KclValue::Object { value, .. } = sketch_value else {
6251 panic!("Expected sketch block to return an object, got {sketch_value:?}");
6252 };
6253
6254 assert!(value.contains_key("line1"));
6255 assert!(value.contains_key("line2"));
6256 assert!(!value.contains_key("line"));
6259 assert!(!value.contains_key("coincident"));
6260 }
6261
6262 #[tokio::test(flavor = "multi_thread")]
6263 async fn solver_module_is_not_available_outside_sketch_blocks() {
6264 let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
6265 assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
6266
6267 let err = parse_execute(
6268 r#"@settings(experimentalFeatures = allow)
6269
6270import "std::solver""#,
6271 )
6272 .await
6273 .unwrap_err();
6274 assert!(
6275 err.message().contains("only available inside sketch blocks"),
6276 "Error message: '{}'",
6277 err.message()
6278 );
6279 }
6280
6281 #[tokio::test(flavor = "multi_thread")]
6282 async fn cannot_solid_extrude_an_open_profile() {
6283 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
6286 let program = crate::Program::parse_no_errs(&code).expect("should parse");
6287 let exec_ctxt = ExecutorContext::new_mock(None).await;
6288 let mut exec_state = ExecState::new(&exec_ctxt);
6289
6290 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
6291 assert!(matches!(err, KclError::Semantic { .. }));
6292 exec_ctxt.close().await;
6293 }
6294}