1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10use serde::Deserialize;
11use serde::Serialize;
12use uuid::Uuid;
13
14use crate::CompilationIssue;
15use crate::ExecutorContext;
16use crate::KclErrorWithOutputs;
17use crate::MockConfig;
18use crate::NodePath;
19use crate::SegmentDragAnchor;
20use crate::SourceRange;
21use crate::collections::AhashIndexSet;
22use crate::engine::engine_manager::EngineManager;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27use crate::execution::Artifact;
28use crate::execution::ArtifactCommand;
29use crate::execution::ArtifactGraph;
30use crate::execution::ArtifactId;
31use crate::execution::EnvironmentRef;
32use crate::execution::ExecOutcome;
33use crate::execution::ExecutorSettings;
34use crate::execution::KclValue;
35use crate::execution::KclValueView;
36use crate::execution::OperationCallbackArgs;
37use crate::execution::OperationsByModule;
38use crate::execution::ProgramLookup;
39use crate::execution::SketchVarId;
40use crate::execution::UnsolvedSegment;
41use crate::execution::annotations;
42use crate::execution::cad_op::Operation;
43use crate::execution::id_generator::IdGenerator;
44#[cfg(test)]
45use crate::execution::memory::MemoryBackendKind;
46use crate::execution::memory::ProgramMemory;
47use crate::execution::memory::Stack;
48use crate::execution::sketch_solve::Solved;
49use crate::execution::types::NumericType;
50use crate::front::Number;
51use crate::front::Object;
52use crate::front::ObjectId;
53use crate::front::ObjectKind;
54use crate::id::IncIdGenerator;
55use crate::modules::ModuleId;
56use crate::modules::ModuleInfo;
57use crate::modules::ModuleLoader;
58use crate::modules::ModulePath;
59use crate::modules::ModuleRepr;
60use crate::modules::ModuleSource;
61use crate::parsing::ast::types::Annotation;
62use crate::parsing::ast::types::NodeRef;
63use crate::parsing::ast::types::TagNode;
64
65#[derive(Debug, Clone)]
67pub struct ExecState {
68 pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
69 pub(super) global: GlobalState,
70 pub(super) mod_local: ModuleState,
71}
72
73pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
74
75#[derive(Debug, Clone)]
76pub(super) struct GlobalState {
77 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
79 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
81 pub module_infos: ModuleInfoMap,
83 pub mod_loader: ModuleLoader,
85 pub issues: Vec<CompilationIssue>,
87 pub deprecation_version_override: Option<String>,
91 pub artifacts: ArtifactState,
93 pub root_module_artifacts: ModuleArtifactState,
95 pub segment_ids_edited: AhashIndexSet<ObjectId>,
97 pub drag_anchors: Vec<SegmentDragAnchor>,
99 pub sketch_mode: bool,
104}
105
106impl GlobalState {
107 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
108 let mut operations = OperationsByModule::default();
109 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
110
111 for (module_id, module_info) in &self.module_infos {
112 match &module_info.repr {
113 ModuleRepr::Root => {}
114 ModuleRepr::Kcl(_, Some(outcome)) => {
115 operations.insert(*module_id, outcome.artifacts.operations.clone());
116 }
117 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
118 operations.insert(*module_id, artifacts.operations.clone());
119 }
120 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
121 }
122 }
123
124 operations
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129pub(crate) enum ConstraintKey {
130 LineCircle([usize; 10]),
131 CircleCircle([usize; 12]),
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub(crate) enum TangencyMode {
136 LineCircle(ezpz::LineSide),
137 CircleCircle(ezpz::CircleSide),
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub(crate) enum ConstraintState {
142 Tangency(TangencyMode),
143}
144
145#[derive(Debug, Clone, Default)]
146pub(super) struct ArtifactState {
147 pub artifacts: IndexMap<ArtifactId, Artifact>,
150 pub graph: ArtifactGraph,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
156#[ts(export)]
157#[serde(rename_all = "camelCase")]
158pub enum EdgeRefactorStdlibFn {
159 GetOppositeEdge,
160 GetNextAdjacentEdge,
161 GetPreviousAdjacentEdge,
162 GetCommonEdge,
163 EdgeId,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
168#[ts(export)]
169#[serde(rename_all = "camelCase")]
170pub struct EdgeRefactorMeta {
171 pub edge_id: Uuid,
172 pub face_ids: [Uuid; 2],
173 #[serde(default, skip_serializing_if = "Vec::is_empty")]
174 pub end_face_ids: Vec<Uuid>,
175 pub source_range: SourceRange,
176 pub stdlib_fn: EdgeRefactorStdlibFn,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
182pub(crate) struct PendingEdgeRefactorMeta {
183 pub edge_id: Uuid,
184 pub source_range: SourceRange,
185 pub stdlib_fn: EdgeRefactorStdlibFn,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
190#[ts(export)]
191#[serde(rename_all = "camelCase")]
192pub struct DirectTagFilletTagEntry {
193 pub tag_identifier: String,
194 pub edge_id: Uuid,
195 pub face_ids: [Uuid; 2],
196}
197
198#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
200#[ts(export)]
201#[serde(rename_all = "camelCase")]
202pub struct DirectTagFilletMeta {
203 pub call_source_range: SourceRange,
204 pub tags: Vec<DirectTagFilletTagEntry>,
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
209#[ts(export)]
210#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
211pub enum RefactorMetadata {
212 EdgeRefactor(Box<EdgeRefactorMeta>),
213 DirectTagFillet(DirectTagFilletMeta),
214}
215
216#[derive(Debug, Clone, Default, PartialEq, Serialize)]
218pub struct ModuleArtifactState {
219 pub artifacts: IndexMap<ArtifactId, Artifact>,
221 #[serde(skip)]
224 pub unprocessed_commands: Vec<ArtifactCommand>,
225 pub commands: Vec<ArtifactCommand>,
227 #[cfg(feature = "snapshot-engine-responses")]
229 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
230 pub operations: Vec<Operation>,
233 pub object_id_generator: IncIdGenerator<usize>,
235 pub scene_objects: Vec<Object>,
237 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
240 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
242 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
244 pub refactor_metadata: Vec<RefactorMetadata>,
246 #[serde(skip)]
249 pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
250}
251
252#[derive(Debug, Clone)]
253pub(super) struct ModuleState {
254 pub module_id: ModuleId,
256 pub id_generator: IdGenerator,
258 pub stack: Stack,
259 pub(super) call_stack_size: usize,
263 pub pipe_value: Option<KclValue>,
266 pub being_declared: Option<String>,
270 pub sketch_block: Option<SketchBlockState>,
272 pub inside_stdlib: bool,
275 pub stdlib_entry_source_range: Option<SourceRange>,
277 pub module_exports: Vec<String>,
279 pub settings: MetaSettings,
281 pub sketch_mode: bool,
284 pub freedom_analysis: bool,
288 pub(super) explicit_length_units: bool,
289 pub(super) path: ModulePath,
290 pub artifacts: ModuleArtifactState,
292 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
296
297 pub(super) allowed_warnings: Vec<&'static str>,
298 pub(super) denied_warnings: Vec<&'static str>,
299
300 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
305 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
311 pub(super) consumed_regions: AHashMap<Uuid, ConsumedRegionInfo>,
315}
316
317#[derive(Debug, Clone, Copy)]
319pub(crate) struct ConsumedRegionInfo {
320 operation: ConsumedRegionOperation,
321}
322
323impl ConsumedRegionInfo {
324 pub(crate) fn new(operation: ConsumedRegionOperation) -> Self {
325 Self { operation }
326 }
327
328 pub(crate) fn operation(self) -> ConsumedRegionOperation {
329 self.operation
330 }
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334pub(crate) enum ConsumedRegionOperation {
335 Extrude,
336 Revolve,
337 Sweep,
338 Delete,
339}
340
341impl std::fmt::Display for ConsumedRegionOperation {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 match self {
344 Self::Extrude => f.write_str("extrude"),
345 Self::Revolve => f.write_str("revolve"),
346 Self::Sweep => f.write_str("sweep"),
347 Self::Delete => f.write_str("delete"),
348 }
349 }
350}
351
352#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
354pub(crate) struct ConsumedSolidKey {
355 engine_id: Uuid,
357 instance_id: Uuid,
360}
361
362impl ConsumedSolidKey {
363 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
364 Self { engine_id, instance_id }
365 }
366
367 pub(crate) fn engine_id(&self) -> Uuid {
368 self.engine_id
369 }
370
371 pub(crate) fn instance_id(&self) -> Uuid {
372 self.instance_id
373 }
374}
375
376#[derive(Debug, Clone)]
380pub(crate) struct ConsumedSolidInfo {
381 operation: ConsumedSolidOperation,
383 suggested_replacement_key: Option<ConsumedSolidKey>,
387 returned_solid_keys: Vec<ConsumedSolidKey>,
390}
391
392impl ConsumedSolidInfo {
393 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
394 Self {
395 operation,
396 suggested_replacement_key: returned_solid_keys.first().copied(),
397 returned_solid_keys,
398 }
399 }
400
401 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
402 self.operation
403 }
404
405 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
406 self.suggested_replacement_key
407 }
408
409 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
410 !self.returned_solid_keys.contains(&key)
411 }
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
415pub(crate) enum ConsumedSolidOperation {
416 Union,
417 Intersect,
418 Subtract,
419 Split,
420 JoinSurfaces,
421}
422
423impl ConsumedSolidOperation {
424 pub(crate) fn indefinite_article(self) -> &'static str {
425 match self {
426 Self::Intersect => "an",
427 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
428 }
429 }
430}
431
432impl std::fmt::Display for ConsumedSolidOperation {
433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
434 match self {
435 Self::Union => f.write_str("union"),
436 Self::Intersect => f.write_str("intersect"),
437 Self::Subtract => f.write_str("subtract"),
438 Self::Split => f.write_str("split"),
439 Self::JoinSurfaces => f.write_str("joinSurfaces"),
440 }
441 }
442}
443
444#[derive(Debug, Clone, Default)]
445pub(crate) struct SketchBlockState {
446 pub sketch_vars: Vec<KclValue>,
447 pub sketch_id: Option<ObjectId>,
448 pub sketch_constraints: Vec<ObjectId>,
449 pub solver_constraints: Vec<ezpz::Constraint>,
450 pub solver_optional_constraints: Vec<ezpz::Constraint>,
451 pub needed_by_engine: Vec<UnsolvedSegment>,
452 pub segment_tags: IndexMap<ObjectId, TagNode>,
453}
454
455impl ExecState {
456 pub fn new(exec_context: &super::ExecutorContext) -> Self {
457 ExecState {
458 execution_callbacks: exec_context.execution_callbacks.clone(),
459 global: GlobalState::new(&exec_context.settings, Default::default()),
460 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
461 }
462 }
463
464 #[cfg(test)]
465 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
466 ExecState {
467 execution_callbacks: exec_context.execution_callbacks.clone(),
468 global: GlobalState::new(&exec_context.settings, Default::default()),
469 mod_local: ModuleState::new(
470 ModulePath::Main,
471 ProgramMemory::new_with_backend(backend),
472 Default::default(),
473 false,
474 true,
475 ),
476 }
477 }
478
479 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
480 let segment_ids_edited = mock_config.segment_ids_edited.clone();
481 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
482 global.drag_anchors = mock_config.drag_anchors.clone();
483 global.sketch_mode = mock_config.sketch_block_id.is_some();
484 ExecState {
485 execution_callbacks: exec_context.execution_callbacks.clone(),
486 global,
487 mod_local: ModuleState::new(
488 ModulePath::Main,
489 ProgramMemory::new(),
490 Default::default(),
491 mock_config.sketch_block_id.is_some(),
492 mock_config.freedom_analysis,
493 ),
494 }
495 }
496
497 #[cfg(test)]
498 pub(crate) fn new_mock_with_memory_backend(
499 exec_context: &super::ExecutorContext,
500 mock_config: &MockConfig,
501 backend: MemoryBackendKind,
502 ) -> Self {
503 let segment_ids_edited = mock_config.segment_ids_edited.clone();
504 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
505 global.drag_anchors = mock_config.drag_anchors.clone();
506 global.sketch_mode = mock_config.sketch_block_id.is_some();
507 ExecState {
508 execution_callbacks: exec_context.execution_callbacks.clone(),
509 global,
510 mod_local: ModuleState::new(
511 ModulePath::Main,
512 ProgramMemory::new_with_backend(backend),
513 Default::default(),
514 mock_config.sketch_block_id.is_some(),
515 mock_config.freedom_analysis,
516 ),
517 }
518 }
519
520 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
521 let global = GlobalState::new(&exec_context.settings, Default::default());
522
523 *self = ExecState {
524 execution_callbacks: exec_context.execution_callbacks.clone(),
525 global,
526 mod_local: ModuleState::new(
527 self.mod_local.path.clone(),
528 ProgramMemory::new(),
529 Default::default(),
530 false,
531 true,
532 ),
533 };
534 }
535
536 pub fn err(&mut self, e: CompilationIssue) {
538 self.global.issues.push(e);
539 }
540
541 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
543 debug_assert!(annotations::WARN_VALUES.contains(&name));
544
545 if self.mod_local.allowed_warnings.contains(&name) {
546 return;
547 }
548
549 if self.mod_local.denied_warnings.contains(&name) {
550 e.severity = Severity::Error;
551 } else {
552 e.severity = Severity::Warning;
553 }
554
555 self.global.issues.push(e);
556 }
557
558 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
559 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
560 return;
561 };
562 let error = CompilationIssue {
563 source_range,
564 message: format!("Use of {feature_name} is experimental and may change or be removed."),
565 suggestion: None,
566 severity,
567 tag: crate::errors::Tag::None,
568 };
569
570 self.global.issues.push(error);
571 }
572
573 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
574 self.global.issues = std::mem::take(&mut self.global.issues)
575 .into_iter()
576 .filter(|e| {
577 e.severity != Severity::Warning
578 || !source_range.contains_range(&e.source_range)
579 || e.tag != crate::errors::Tag::UnknownNumericUnits
580 })
581 .collect();
582 }
583
584 pub fn issues(&self) -> &[CompilationIssue] {
585 &self.global.issues
586 }
587
588 pub(crate) fn deprecation_version(&self) -> &str {
589 self.global
590 .deprecation_version_override
591 .as_deref()
592 .unwrap_or(&self.mod_local.settings.kcl_version)
593 }
594
595 #[cfg(test)]
596 pub(crate) fn set_deprecation_version_override(&mut self, version: Option<&str>) {
597 self.global.deprecation_version_override = version.map(str::to_owned);
598 }
599
600 pub async fn into_exec_outcome(
604 self,
605 main_ref: EnvironmentRef,
606 ctx: &ExecutorContext,
607 ) -> Result<ExecOutcome, KclError> {
608 let variables = self
611 .mod_local
612 .variables(main_ref)?
613 .into_iter()
614 .map(|(key, value)| (key, KclValueView::from(value)))
615 .collect();
616 Ok(ExecOutcome {
617 variables,
618 filenames: self.global.filenames(),
619 operations: self.global.operations_by_module(),
620 artifact_graph: self.global.artifacts.graph,
621 scene_objects: self.global.root_module_artifacts.scene_objects,
622 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
623 var_solutions: self.global.root_module_artifacts.var_solutions,
624 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
625 issues: self.global.issues,
626 default_planes: ctx.engine.get_default_planes().read().await.clone(),
627 })
628 }
629
630 #[cfg(feature = "snapshot-engine-responses")]
631 pub(crate) fn take_root_module_responses(
632 &mut self,
633 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
634 std::mem::take(&mut self.global.root_module_artifacts.responses)
635 }
636
637 pub(crate) fn stack(&self) -> &Stack {
638 &self.mod_local.stack
639 }
640
641 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
642 &mut self.mod_local.stack
643 }
644
645 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
648 if self.mod_local.call_stack_size >= 50 {
651 return Err(KclError::MaxCallStack {
652 details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
653 });
654 }
655 self.mod_local.call_stack_size += 1;
656 Ok(())
657 }
658
659 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
662 if self.mod_local.call_stack_size == 0 {
664 let message = "call stack size below zero".to_owned();
665 debug_assert!(false, "{message}");
666 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
667 }
668 self.mod_local.call_stack_size -= 1;
669 Ok(())
670 }
671
672 pub(crate) fn sketch_mode(&self) -> bool {
677 self.mod_local.sketch_mode
678 && match &self.mod_local.path {
679 ModulePath::Main => true,
680 ModulePath::Local { .. } => true,
681 ModulePath::Std { .. } => false,
682 }
683 }
684
685 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
689 self.global.sketch_mode
690 }
691
692 pub fn next_object_id(&mut self) -> ObjectId {
693 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
694 }
695
696 pub fn peek_object_id(&self) -> ObjectId {
697 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
698 }
699
700 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
701 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
702 map.get(key).copied()
703 }
704
705 pub(crate) fn set_constraint_state(
706 &mut self,
707 sketch_block_id: ObjectId,
708 key: ConstraintKey,
709 state: ConstraintState,
710 ) {
711 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
712 map.insert(key, state);
713 }
714
715 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
716 let id = obj.id;
717 debug_assert!(
718 id.0 == self.mod_local.artifacts.scene_objects.len(),
719 "Adding scene object with ID {} but next ID is {}",
720 id.0,
721 self.mod_local.artifacts.scene_objects.len()
722 );
723 let artifact_id = obj.artifact_id;
724 self.mod_local.artifacts.scene_objects.push(obj);
725 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
726 self.mod_local
727 .artifacts
728 .artifact_id_to_scene_object
729 .insert(artifact_id, id);
730 id
731 }
732
733 pub fn add_placeholder_scene_object(
736 &mut self,
737 id: ObjectId,
738 source_range: SourceRange,
739 node_path: Option<NodePath>,
740 ) -> ObjectId {
741 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
742 self.mod_local
743 .artifacts
744 .scene_objects
745 .push(Object::placeholder(id, source_range, node_path));
746 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
747 id
748 }
749
750 pub fn set_scene_object(&mut self, object: Object) {
752 let id = object.id;
753 let artifact_id = object.artifact_id;
754 self.mod_local.artifacts.scene_objects[id.0] = object;
755 self.mod_local
756 .artifacts
757 .artifact_id_to_scene_object
758 .insert(artifact_id, id);
759 }
760
761 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
762 self.mod_local
763 .artifacts
764 .artifact_id_to_scene_object
765 .get(&artifact_id)
766 .cloned()
767 }
768
769 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
770 self.global.segment_ids_edited.contains(object_id)
771 }
772
773 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
774 self.global
775 .drag_anchors
776 .iter()
777 .find(|anchor| &anchor.segment_id == object_id)
778 .map(|anchor| &anchor.target)
779 }
780
781 pub(super) fn is_in_sketch_block(&self) -> bool {
782 self.mod_local.sketch_block.is_some()
783 }
784
785 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
786 self.mod_local.sketch_block.as_mut()
787 }
788
789 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
790 self.mod_local.sketch_block.as_ref()
791 }
792
793 pub fn next_uuid(&mut self) -> Uuid {
794 self.mod_local.id_generator.next_uuid()
795 }
796
797 pub fn next_artifact_id(&mut self) -> ArtifactId {
798 self.mod_local.id_generator.next_artifact_id()
799 }
800
801 pub fn id_generator(&mut self) -> &mut IdGenerator {
802 &mut self.mod_local.id_generator
803 }
804
805 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
807 self.mod_local.consumed_solids.insert(consumed_key, info);
808 }
809
810 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
813 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
814 }
815
816 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
819 self.mod_local.consumed_solids.get(key)
820 }
821
822 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
825 self.mod_local.consumed_solid_ids.get(id)
826 }
827
828 pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
829 self.mod_local.consumed_regions.insert(id, info);
830 }
831
832 pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
833 self.mod_local.consumed_regions.get(id).copied()
834 }
835
836 pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
840 fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
841 match value {
842 KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
843 KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
844 value.iter().any(|value| contains_region_id(value, target_id))
845 }
846 KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
847 _ => false,
848 }
849 }
850
851 self.mod_local
852 .stack
853 .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
854 }
855
856 pub(crate) fn latest_consumed_output(
859 &self,
860 suggested_replacement_key: Option<ConsumedSolidKey>,
861 ) -> Option<ConsumedSolidKey> {
862 let mut latest = suggested_replacement_key?;
863 let mut seen = AhashIndexSet::default();
864
865 while seen.insert(latest) {
866 let Some(next) = self
867 .mod_local
868 .consumed_solids
869 .get(&latest)
870 .and_then(|info| info.suggested_replacement_key())
871 else {
872 break;
873 };
874 latest = next;
875 }
876
877 Some(latest)
878 }
879
880 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
884 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
885 match value {
886 KclValue::Solid { value } => {
887 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
888 }
889 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
890 _ => false,
891 }
892 }
893 self.mod_local
894 .stack
895 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
896 }
897
898 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
899 let id = artifact.id();
900 self.mod_local.artifacts.artifacts.insert(id, artifact);
901 }
902
903 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
904 self.mod_local.artifacts.artifacts.get_mut(&id)
905 }
906
907 pub(crate) fn push_op(&mut self, op: Operation) {
908 let index = self.mod_local.artifacts.operations.len();
909 self.mod_local.artifacts.operations.push(op);
910 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
911 && let Some(callbacks) = &self.execution_callbacks
912 {
913 callbacks.on_operation(OperationCallbackArgs {
914 module_id: self.mod_local.module_id,
915 operation,
916 index,
917 });
918 }
919 }
920
921 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
922 self.mod_local.artifacts.unprocessed_commands.push(command);
923 }
924
925 pub(super) fn next_module_id(&self) -> ModuleId {
926 ModuleId::from_usize(self.global.path_to_source_id.len())
927 }
928
929 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
930 self.global.path_to_source_id.get(path).cloned()
931 }
932
933 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
934 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
935 self.global.path_to_source_id.insert(path, id);
936 }
937
938 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
939 let root_id = ModuleId::default();
940 let path = self
942 .global
943 .path_to_source_id
944 .iter()
945 .find(|(_, v)| **v == root_id)
946 .unwrap()
947 .0
948 .clone();
949 self.add_id_to_source(
950 root_id,
951 ModuleSource {
952 path,
953 source: program.original_file_contents.to_string(),
954 },
955 );
956 }
957
958 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
959 self.global.id_to_source.insert(id, source);
960 }
961
962 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
963 debug_assert!(self.global.path_to_source_id.contains_key(&path));
964 let module_info = ModuleInfo { id, repr, path };
965 self.global.module_infos.insert(id, module_info);
966 }
967
968 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
969 self.global.module_infos.get(&id)
970 }
971
972 #[cfg(test)]
973 pub(crate) fn modules(&self) -> &ModuleInfoMap {
974 &self.global.module_infos
975 }
976
977 #[cfg(test)]
978 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
979 &self.global.root_module_artifacts
980 }
981
982 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
987 self.mod_local
988 .artifacts
989 .refactor_metadata
990 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
991 }
992
993 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
994 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
995 }
996
997 pub(crate) fn pending_edge_refactor_meta(
998 &self,
999 edge_id: Uuid,
1000 argument_source_range: SourceRange,
1001 ) -> Option<PendingEdgeRefactorMeta> {
1002 if let Some(pending) = self
1003 .mod_local
1004 .artifacts
1005 .pending_edge_refactor_metadata
1006 .iter()
1007 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1008 {
1009 return Some(pending.clone());
1010 }
1011
1012 let mut matches = self
1015 .mod_local
1016 .artifacts
1017 .pending_edge_refactor_metadata
1018 .iter()
1019 .filter(|meta| meta.edge_id == edge_id);
1020 let pending = matches.next()?.clone();
1021 matches.next().is_none().then_some(pending)
1022 }
1023
1024 pub(crate) fn record_edge_refactor_meta_from_pending(
1025 &mut self,
1026 edge_id: Uuid,
1027 source_range: SourceRange,
1028 face_ids: [Uuid; 2],
1029 ) -> bool {
1030 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1031 matches!(
1032 meta,
1033 RefactorMetadata::EdgeRefactor(meta)
1034 if meta.edge_id == edge_id && meta.source_range == source_range
1035 )
1036 }) {
1037 return true;
1038 }
1039
1040 let exact_pending_meta = self
1041 .mod_local
1042 .artifacts
1043 .pending_edge_refactor_metadata
1044 .iter()
1045 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1046 .cloned();
1047
1048 let edge_pending_meta = || {
1049 let mut matches = self
1050 .mod_local
1051 .artifacts
1052 .pending_edge_refactor_metadata
1053 .iter()
1054 .filter(|meta| meta.edge_id == edge_id);
1055 let pending_meta = matches.next()?.clone();
1056 matches.next().is_none().then_some(pending_meta)
1057 };
1058
1059 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1060 return false;
1061 };
1062
1063 self.record_edge_refactor_meta(EdgeRefactorMeta {
1064 edge_id,
1065 face_ids,
1066 end_face_ids: Vec::new(),
1067 source_range: pending_meta.source_range,
1068 stdlib_fn: pending_meta.stdlib_fn,
1069 });
1070
1071 true
1072 }
1073
1074 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1079 self.mod_local
1080 .artifacts
1081 .refactor_metadata
1082 .push(RefactorMetadata::DirectTagFillet(meta));
1083 }
1084
1085 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1087 self.global
1088 .root_module_artifacts
1089 .refactor_metadata
1090 .iter()
1091 .filter_map(|m| match m {
1092 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1093 RefactorMetadata::DirectTagFillet(_) => None,
1094 })
1095 .collect()
1096 }
1097
1098 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1100 self.global
1101 .root_module_artifacts
1102 .refactor_metadata
1103 .iter()
1104 .filter_map(|m| match m {
1105 RefactorMetadata::EdgeRefactor(_) => None,
1106 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1107 })
1108 .collect()
1109 }
1110
1111 pub fn current_default_units(&self) -> NumericType {
1112 NumericType::Default {
1113 len: self.length_unit(),
1114 angle: self.angle_unit(),
1115 }
1116 }
1117
1118 pub fn length_unit(&self) -> UnitLength {
1119 self.mod_local.settings.default_length_units
1120 }
1121
1122 pub fn angle_unit(&self) -> UnitAngle {
1123 self.mod_local.settings.default_angle_units
1124 }
1125
1126 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1127 KclError::new_import_cycle(KclErrorDetails::new(
1128 format!(
1129 "circular import of modules is not allowed: {} -> {}",
1130 self.global
1131 .mod_loader
1132 .import_stack
1133 .iter()
1134 .map(|p| p.to_string_lossy())
1135 .collect::<Vec<_>>()
1136 .join(" -> "),
1137 path,
1138 ),
1139 vec![source_range],
1140 ))
1141 }
1142
1143 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1144 self.mod_local.pipe_value.as_ref()
1145 }
1146
1147 pub(crate) fn error_with_outputs(
1148 &self,
1149 error: KclError,
1150 main_ref: Option<EnvironmentRef>,
1151 default_planes: Option<DefaultPlanes>,
1152 ) -> KclErrorWithOutputs {
1153 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1154 .global
1155 .path_to_source_id
1156 .iter()
1157 .map(|(k, v)| ((*v), k.clone()))
1158 .collect();
1159
1160 KclErrorWithOutputs::new(
1161 error,
1162 self.issues().to_vec(),
1163 main_ref
1164 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1165 .unwrap_or_default(),
1166 self.global.operations_by_module(),
1167 Default::default(),
1168 self.global.artifacts.graph.clone(),
1169 self.global.root_module_artifacts.scene_objects.clone(),
1170 self.global.root_module_artifacts.source_range_to_object.clone(),
1171 self.global.root_module_artifacts.var_solutions.clone(),
1172 self.global.root_module_artifacts.refactor_metadata.clone(),
1173 module_id_to_module_path,
1174 self.global.id_to_source.clone(),
1175 default_planes,
1176 )
1177 }
1178
1179 pub(crate) fn build_program_lookup(
1180 &self,
1181 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1182 ) -> ProgramLookup {
1183 ProgramLookup::new(current, self.global.module_infos.clone())
1184 }
1185
1186 pub(crate) async fn build_artifact_graph(
1187 &mut self,
1188 engine: &Arc<EngineManager>,
1189 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1190 ) -> Result<(), KclError> {
1191 let mut new_commands = Vec::new();
1192 let mut new_exec_artifacts = IndexMap::new();
1193 for module in self.global.module_infos.values_mut() {
1194 match &mut module.repr {
1195 ModuleRepr::Kcl(_, Some(outcome)) => {
1196 new_commands.extend(outcome.artifacts.process_commands());
1197 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1198 }
1199 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1200 new_commands.extend(module_artifacts.process_commands());
1201 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1202 }
1203 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1204 }
1205 }
1206 new_commands.extend(self.global.root_module_artifacts.process_commands());
1209 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1212 let new_responses = engine.take_responses().await;
1213
1214 for (id, exec_artifact) in new_exec_artifacts {
1217 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1221 }
1222
1223 let initial_graph = self.global.artifacts.graph.clone();
1224
1225 let programs = self.build_program_lookup(program.clone());
1227 let graph_result = crate::execution::artifact::build_artifact_graph(
1228 &new_commands,
1229 &new_responses,
1230 program,
1231 &mut self.global.artifacts.artifacts,
1232 initial_graph,
1233 &programs,
1234 &self.global.module_infos,
1235 );
1236
1237 #[cfg(feature = "snapshot-engine-responses")]
1238 {
1239 self.global.root_module_artifacts.responses.extend(new_responses);
1241 }
1242
1243 let artifact_graph = graph_result?;
1244 self.global.artifacts.graph = artifact_graph;
1245
1246 Ok(())
1247 }
1248
1249 pub(crate) fn kcl_version(&self) -> KclVersion {
1250 self.mod_local.settings.kcl_version.parse().unwrap_or_default()
1251 }
1252}
1253
1254#[derive(Default)]
1255pub enum KclVersion {
1256 #[default]
1257 V1,
1258 V2,
1259}
1260
1261impl FromStr for KclVersion {
1262 type Err = KclError;
1263
1264 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1265 match s {
1266 "1" | "1.0" | "1.0.0" => Ok(Self::V1),
1267 "2" | "2.0" | "2.0.0" => Ok(Self::V2),
1268 other => Err(KclError::new_semantic(KclErrorDetails {
1269 source_ranges: Default::default(),
1270 backtrace: Default::default(),
1271 message: format!("Unrecognized version {other}. Valid versions are 1.0 and 2.0"),
1272 })),
1273 }
1274 }
1275}
1276
1277impl GlobalState {
1278 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1279 let mut global = GlobalState {
1280 path_to_source_id: Default::default(),
1281 module_infos: Default::default(),
1282 artifacts: Default::default(),
1283 root_module_artifacts: Default::default(),
1284 mod_loader: Default::default(),
1285 issues: Default::default(),
1286 deprecation_version_override: None,
1287 id_to_source: Default::default(),
1288 segment_ids_edited,
1289 drag_anchors: Vec::new(),
1290 sketch_mode: false,
1291 };
1292
1293 let root_id = ModuleId::default();
1294 let root_path = settings.current_file.clone().unwrap_or_default();
1295 global.module_infos.insert(
1296 root_id,
1297 ModuleInfo {
1298 id: root_id,
1299 path: ModulePath::Local {
1300 value: root_path.clone(),
1301 original_import_path: None,
1302 },
1303 repr: ModuleRepr::Root,
1304 },
1305 );
1306 global.path_to_source_id.insert(
1307 ModulePath::Local {
1308 value: root_path,
1309 original_import_path: None,
1310 },
1311 root_id,
1312 );
1313 global
1314 }
1315
1316 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1317 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1318 }
1319
1320 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1321 self.id_to_source.get(&id)
1322 }
1323}
1324
1325impl ArtifactState {
1326 pub fn cached_body_items(&self) -> usize {
1327 self.graph.item_count()
1328 }
1329
1330 pub(crate) fn clear(&mut self) {
1331 self.artifacts.clear();
1332 self.graph.clear();
1333 }
1334}
1335
1336impl ModuleArtifactState {
1337 pub(crate) fn clear(&mut self) {
1338 self.artifacts.clear();
1339 self.unprocessed_commands.clear();
1340 self.commands.clear();
1341 self.operations.clear();
1342 self.refactor_metadata.clear();
1343 }
1344
1345 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1346 self.scene_objects = scene_objects.to_vec();
1347 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1348 self.source_range_to_object.clear();
1349 self.artifact_id_to_scene_object.clear();
1350
1351 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1352 debug_assert_eq!(
1353 object.id.0, expected_id,
1354 "Restored cached scene object ID {} does not match its position {}",
1355 object.id.0, expected_id
1356 );
1357
1358 match &object.kind {
1359 ObjectKind::Wall(wall) => {
1360 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1361 }
1362 ObjectKind::Cap(cap) => {
1363 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1364 }
1365 _ => match &object.source {
1366 crate::front::SourceRef::Simple { range, node_path: _ } => {
1367 self.source_range_to_object.insert(*range, object.id);
1368 }
1369 crate::front::SourceRef::BackTrace { ranges } => {
1370 if let Some((range, _)) = ranges.first() {
1373 self.source_range_to_object.insert(*range, object.id);
1374 }
1375 }
1376 },
1377 }
1378
1379 if object.artifact_id != ArtifactId::placeholder() {
1381 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1382 }
1383 }
1384 }
1385
1386 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1388 self.artifacts.extend(other.artifacts);
1389 self.unprocessed_commands.extend(other.unprocessed_commands);
1390 self.commands.extend(other.commands);
1391 self.operations.extend(other.operations);
1392 if other.scene_objects.len() > self.scene_objects.len() {
1393 self.scene_objects
1394 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1395 }
1396 self.source_range_to_object.extend(other.source_range_to_object);
1397 self.artifact_id_to_scene_object
1398 .extend(other.artifact_id_to_scene_object);
1399 self.var_solutions.extend(other.var_solutions);
1400 self.refactor_metadata.extend(other.refactor_metadata);
1401 }
1402
1403 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1407 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1408 let new_module_commands = unprocessed.clone();
1409 self.commands.extend(unprocessed);
1410 new_module_commands
1411 }
1412
1413 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1414 debug_assert!(
1415 id.0 < self.scene_objects.len(),
1416 "Requested object ID {} but only have {} objects",
1417 id.0,
1418 self.scene_objects.len()
1419 );
1420 self.scene_objects.get(id.0)
1421 }
1422
1423 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1424 debug_assert!(
1425 id.0 < self.scene_objects.len(),
1426 "Requested object ID {} but only have {} objects",
1427 id.0,
1428 self.scene_objects.len()
1429 );
1430 self.scene_objects.get_mut(id.0)
1431 }
1432}
1433
1434impl ModuleState {
1435 pub(super) fn new(
1436 path: ModulePath,
1437 memory: Arc<ProgramMemory>,
1438 module_id: Option<ModuleId>,
1439 sketch_mode: bool,
1440 freedom_analysis: bool,
1441 ) -> Self {
1442 let state_module_id = module_id.unwrap_or_default();
1443 ModuleState {
1444 module_id: state_module_id,
1445 id_generator: IdGenerator::new(module_id),
1446 stack: memory.new_stack(),
1447 call_stack_size: 0,
1448 pipe_value: Default::default(),
1449 being_declared: Default::default(),
1450 sketch_block: Default::default(),
1451 stdlib_entry_source_range: Default::default(),
1452 module_exports: Default::default(),
1453 explicit_length_units: false,
1454 path,
1455 settings: Default::default(),
1456 sketch_mode,
1457 freedom_analysis,
1458 artifacts: Default::default(),
1459 constraint_state: Default::default(),
1460 allowed_warnings: Vec::new(),
1461 denied_warnings: Vec::new(),
1462 consumed_solids: AHashMap::default(),
1463 consumed_solid_ids: AHashMap::default(),
1464 consumed_regions: AHashMap::default(),
1465 inside_stdlib: false,
1466 }
1467 }
1468
1469 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1470 self.stack.find_all_in_env_owned(main_ref)
1471 }
1472}
1473
1474impl SketchBlockState {
1475 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1476 SketchVarId(self.sketch_vars.len())
1477 }
1478
1479 pub(crate) fn var_solutions(
1482 &self,
1483 solve_outcome: &Solved,
1484 solution_ty: NumericType,
1485 sketch_block_range: SourceRange,
1486 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1487 self.sketch_vars
1488 .iter()
1489 .map(|v| {
1490 let Some(sketch_var) = v.as_sketch_var() else {
1491 return Err(KclError::new_internal(KclErrorDetails::new(
1492 "Expected sketch variable".to_owned(),
1493 vec![sketch_block_range],
1494 )));
1495 };
1496 let var_index = sketch_var.id.0;
1497 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1498 let message = format!("No solution for sketch variable with id {}", var_index);
1499 debug_assert!(false, "{}", &message);
1500 KclError::new_internal(KclErrorDetails::new(
1501 message,
1502 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1503 ))
1504 })?;
1505 let solved_value = Number {
1506 value: *solved_n,
1507 units: solution_ty.try_into().map_err(|_| {
1508 KclError::new_internal(KclErrorDetails::new(
1509 "Failed to convert numeric type to units".to_owned(),
1510 vec![sketch_block_range],
1511 ))
1512 })?,
1513 };
1514 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1515 return Ok(None);
1516 };
1517 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1518 })
1519 .filter_map(Result::transpose)
1520 .collect::<Result<Vec<_>, KclError>>()
1521 }
1522}
1523
1524#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1525#[ts(export)]
1526#[serde(rename_all = "camelCase")]
1527pub struct MetaSettings {
1528 pub default_length_units: UnitLength,
1529 pub default_angle_units: UnitAngle,
1530 pub experimental_features: annotations::WarningLevel,
1531 pub kcl_version: String,
1532}
1533
1534impl Default for MetaSettings {
1535 fn default() -> Self {
1536 MetaSettings {
1537 default_length_units: UnitLength::Millimeters,
1538 default_angle_units: UnitAngle::Degrees,
1539 experimental_features: annotations::WarningLevel::Deny,
1540 kcl_version: "1.0".to_owned(),
1541 }
1542 }
1543}
1544
1545impl MetaSettings {
1546 pub(crate) fn update_from_annotation(
1547 &mut self,
1548 annotation: &crate::parsing::ast::types::Node<Annotation>,
1549 ) -> Result<(bool, bool), KclError> {
1550 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1551
1552 let mut updated_len = false;
1553 let mut updated_angle = false;
1554 for p in properties {
1555 match &*p.inner.key.name {
1556 annotations::SETTINGS_UNIT_LENGTH => {
1557 let value = annotations::expect_ident(&p.inner.value)?;
1558 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1559 self.default_length_units = value;
1560 updated_len = true;
1561 }
1562 annotations::SETTINGS_UNIT_ANGLE => {
1563 let value = annotations::expect_ident(&p.inner.value)?;
1564 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1565 self.default_angle_units = value;
1566 updated_angle = true;
1567 }
1568 annotations::SETTINGS_VERSION => {
1569 let value = annotations::expect_number(&p.inner.value)?;
1570 self.kcl_version = value;
1571 }
1572 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1573 let value = annotations::expect_ident(&p.inner.value)?;
1574 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1575 KclError::new_semantic(KclErrorDetails::new(
1576 format!(
1577 "Invalid value for {} settings property, expected one of: {}",
1578 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1579 annotations::WARN_LEVELS.join(", ")
1580 ),
1581 annotation.as_source_ranges(),
1582 ))
1583 })?;
1584 self.experimental_features = value;
1585 }
1586 name => {
1587 return Err(KclError::new_semantic(KclErrorDetails::new(
1588 format!(
1589 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1590 annotations::SETTINGS_UNIT_LENGTH,
1591 annotations::SETTINGS_UNIT_ANGLE
1592 ),
1593 vec![annotation.as_source_range()],
1594 )));
1595 }
1596 }
1597 }
1598
1599 Ok((updated_len, updated_angle))
1600 }
1601}
1602
1603#[cfg(test)]
1604mod tests {
1605 use uuid::Uuid;
1606
1607 use super::ModuleArtifactState;
1608 use crate::NodePath;
1609 use crate::NodePathExt;
1610 use crate::SourceRange;
1611 use crate::execution::ArtifactId;
1612 use crate::front::Object;
1613 use crate::front::ObjectId;
1614 use crate::front::ObjectKind;
1615 use crate::front::Plane;
1616 use crate::front::SourceRef;
1617
1618 #[test]
1619 fn restore_scene_objects_rebuilds_lookup_maps() {
1620 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1621 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1622 let plane_range = SourceRange::from([1, 4, 0]);
1623 let plane_node_path = Some(NodePath::placeholder());
1624 let sketch_ranges = vec![
1625 (SourceRange::from([5, 9, 0]), None),
1626 (SourceRange::from([10, 12, 0]), None),
1627 ];
1628 let cached_objects = vec![
1629 Object {
1630 id: ObjectId(0),
1631 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1632 label: Default::default(),
1633 comments: Default::default(),
1634 artifact_id: plane_artifact_id,
1635 source: SourceRef::new(plane_range, plane_node_path),
1636 },
1637 Object {
1638 id: ObjectId(1),
1639 kind: ObjectKind::Nil,
1640 label: Default::default(),
1641 comments: Default::default(),
1642 artifact_id: sketch_artifact_id,
1643 source: SourceRef::BackTrace {
1644 ranges: sketch_ranges.clone(),
1645 },
1646 },
1647 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1648 ];
1649
1650 let mut artifacts = ModuleArtifactState::default();
1651 artifacts.restore_scene_objects(&cached_objects);
1652
1653 assert_eq!(artifacts.scene_objects, cached_objects);
1654 assert_eq!(
1655 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1656 Some(&ObjectId(0))
1657 );
1658 assert_eq!(
1659 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1660 Some(&ObjectId(1))
1661 );
1662 assert_eq!(
1663 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1664 None
1665 );
1666 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1667 assert_eq!(
1668 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1669 Some(&ObjectId(1))
1670 );
1671 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1673 }
1674}