1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8pub use kcl_api::KclVersion;
9use kcl_api::UnitAngle;
10use kcl_api::UnitLength;
11use serde::Deserialize;
12use serde::Serialize;
13use uuid::Uuid;
14
15use crate::CompilationIssue;
16use crate::ExecutorContext;
17use crate::KclErrorWithOutputs;
18use crate::MockConfig;
19use crate::NodePath;
20use crate::SegmentDragAnchor;
21use crate::SourceRange;
22use crate::collections::AhashIndexSet;
23use crate::engine::engine_manager::EngineManager;
24use crate::errors::KclError;
25use crate::errors::KclErrorDetails;
26use crate::errors::Severity;
27use crate::exec::DefaultPlanes;
28use crate::execution::Artifact;
29use crate::execution::ArtifactCommand;
30use crate::execution::ArtifactGraph;
31use crate::execution::ArtifactId;
32use crate::execution::ConstrainableLine2d;
33use crate::execution::EnvironmentRef;
34use crate::execution::ExecOutcome;
35use crate::execution::ExecutorSettings;
36use crate::execution::KclValue;
37use crate::execution::KclValueView;
38use crate::execution::OperationCallbackArgs;
39use crate::execution::OperationsByModule;
40use crate::execution::ProgramLookup;
41use crate::execution::SketchVarId;
42use crate::execution::UnsolvedSegment;
43use crate::execution::annotations;
44use crate::execution::cad_op::Operation;
45use crate::execution::id_generator::IdGenerator;
46#[cfg(test)]
47use crate::execution::memory::MemoryBackendKind;
48use crate::execution::memory::ProgramMemory;
49use crate::execution::memory::Stack;
50use crate::execution::sketch_solve::Solved;
51use crate::execution::types::NumericType;
52use crate::front::Number;
53use crate::front::Object;
54use crate::front::ObjectId;
55use crate::front::ObjectKind;
56use crate::id::IncIdGenerator;
57use crate::modules::ModuleId;
58use crate::modules::ModuleInfo;
59use crate::modules::ModuleLoader;
60use crate::modules::ModulePath;
61use crate::modules::ModuleRepr;
62use crate::modules::ModuleSource;
63use crate::parsing::ast::types::Annotation;
64use crate::parsing::ast::types::NodeRef;
65use crate::parsing::ast::types::TagNode;
66
67#[derive(Debug, Clone)]
69pub struct ExecState {
70 pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
71 pub(super) global: GlobalState,
72 pub(super) mod_local: ModuleState,
73}
74
75pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
76
77#[derive(Debug, Clone)]
78pub(super) struct GlobalState {
79 pub(crate) machine_depth_high_water: usize,
86 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
88 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
90 pub module_infos: ModuleInfoMap,
92 pub mod_loader: ModuleLoader,
94 pub issues: Vec<CompilationIssue>,
96 pub deprecation_version_override: Option<String>,
100 pub entry_point_kcl_version: Option<KclVersion>,
108 pub artifacts: ArtifactState,
110 pub root_module_artifacts: ModuleArtifactState,
112 pub segment_ids_edited: AhashIndexSet<ObjectId>,
114 pub drag_anchors: Vec<SegmentDragAnchor>,
116 pub sketch_mode: bool,
121}
122
123impl GlobalState {
124 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
125 let mut operations = OperationsByModule::default();
126 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
127
128 for (module_id, module_info) in &self.module_infos {
129 match &module_info.repr {
130 ModuleRepr::Root => {}
131 ModuleRepr::Kcl(_, Some(outcome)) => {
132 operations.insert(*module_id, outcome.artifacts.operations.clone());
133 }
134 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
135 operations.insert(*module_id, artifacts.operations.clone());
136 }
137 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
138 }
139 }
140
141 operations
142 }
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
146pub(crate) enum ConstraintKey {
147 LineCircle([usize; 10]),
148 CircleCircle([usize; 12]),
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub(crate) enum TangencyMode {
153 LineCircle(ezpz::LineSide),
154 CircleCircle(ezpz::CircleSide),
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub(crate) enum ConstraintState {
159 Tangency(TangencyMode),
160}
161
162#[derive(Debug, Clone, Default)]
163pub(super) struct ArtifactState {
164 pub artifacts: IndexMap<ArtifactId, Artifact>,
167 pub graph: ArtifactGraph,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
173#[ts(export)]
174#[serde(rename_all = "camelCase")]
175pub enum EdgeRefactorStdlibFn {
176 GetOppositeEdge,
177 GetNextAdjacentEdge,
178 GetPreviousAdjacentEdge,
179 GetCommonEdge,
180 EdgeId,
181}
182
183#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
185#[ts(export)]
186#[serde(rename_all = "camelCase")]
187pub struct EdgeRefactorMeta {
188 pub edge_id: Uuid,
189 pub face_ids: [Uuid; 2],
190 #[serde(default, skip_serializing_if = "Vec::is_empty")]
191 pub end_face_ids: Vec<Uuid>,
192 pub source_range: SourceRange,
193 pub stdlib_fn: EdgeRefactorStdlibFn,
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
199pub(crate) struct PendingEdgeRefactorMeta {
200 pub edge_id: Uuid,
201 pub source_range: SourceRange,
202 pub stdlib_fn: EdgeRefactorStdlibFn,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
207#[ts(export)]
208#[serde(rename_all = "camelCase")]
209pub struct DirectTagFilletTagEntry {
210 pub tag_identifier: String,
211 pub edge_id: Uuid,
212 pub face_ids: [Uuid; 2],
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
217#[ts(export)]
218#[serde(rename_all = "camelCase")]
219pub struct DirectTagFilletMeta {
220 pub call_source_range: SourceRange,
221 pub tags: Vec<DirectTagFilletTagEntry>,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
227#[ts(export)]
228#[serde(rename_all = "camelCase")]
229pub struct LegacyAngleRefactorMeta {
230 pub source_range: SourceRange,
231 pub sector: u8,
232 pub inverse: bool,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
237#[ts(export)]
238#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
239pub enum RefactorMetadata {
240 EdgeRefactor(Box<EdgeRefactorMeta>),
241 DirectTagFillet(DirectTagFilletMeta),
242 LegacyAngle(LegacyAngleRefactorMeta),
243}
244
245#[derive(Debug, Clone)]
246pub(crate) struct PendingLegacyAngleRefactorMeta {
247 pub source_range: SourceRange,
248 pub lines: [ConstrainableLine2d; 2],
249 pub desired_angle_radians: f64,
250}
251
252#[derive(Debug, Clone, Default, PartialEq, Serialize)]
254pub struct ModuleArtifactState {
255 pub artifacts: IndexMap<ArtifactId, Artifact>,
257 #[serde(skip)]
260 pub unprocessed_commands: Vec<ArtifactCommand>,
261 pub commands: Vec<ArtifactCommand>,
263 #[cfg(feature = "snapshot-engine-responses")]
265 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
266 pub operations: Vec<Operation>,
269 pub object_id_generator: IncIdGenerator<usize>,
271 pub scene_objects: Vec<Object>,
273 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
276 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
278 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
280 pub refactor_metadata: Vec<RefactorMetadata>,
282 #[serde(skip)]
285 pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
286}
287
288#[derive(Debug, Clone)]
289pub(super) struct ModuleState {
290 pub module_id: ModuleId,
292 pub id_generator: IdGenerator,
294 pub stack: Stack,
295 pub(super) call_stack_size: usize,
299 pub(crate) machine_call_depth: usize,
302 pub pipe_value: Option<KclValue>,
305 pub being_declared: Option<String>,
309 pub sketch_block: Option<SketchBlockState>,
311 pub inside_stdlib: bool,
314 pub stdlib_entry_source_range: Option<SourceRange>,
316 pub module_exports: Vec<String>,
318 pub settings: MetaSettings,
320 pub sketch_mode: bool,
323 pub freedom_analysis: bool,
327 pub(super) explicit_length_units: bool,
328 pub(super) path: ModulePath,
329 pub artifacts: ModuleArtifactState,
331 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
335
336 pub(super) allowed_warnings: Vec<&'static str>,
337 pub(super) denied_warnings: Vec<&'static str>,
338
339 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
344 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
350 pub(super) consumed_regions: AHashMap<Uuid, ConsumedRegionInfo>,
354}
355
356#[derive(Debug, Clone, Copy)]
358pub(crate) struct ConsumedRegionInfo {
359 operation: ConsumedRegionOperation,
360}
361
362impl ConsumedRegionInfo {
363 pub(crate) fn new(operation: ConsumedRegionOperation) -> Self {
364 Self { operation }
365 }
366
367 pub(crate) fn operation(self) -> ConsumedRegionOperation {
368 self.operation
369 }
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub(crate) enum ConsumedRegionOperation {
374 Extrude,
375 Revolve,
376 Sweep,
377 Delete,
378}
379
380impl std::fmt::Display for ConsumedRegionOperation {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 match self {
383 Self::Extrude => f.write_str("extrude"),
384 Self::Revolve => f.write_str("revolve"),
385 Self::Sweep => f.write_str("sweep"),
386 Self::Delete => f.write_str("delete"),
387 }
388 }
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
393pub(crate) struct ConsumedSolidKey {
394 engine_id: Uuid,
396 instance_id: Uuid,
399}
400
401impl ConsumedSolidKey {
402 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
403 Self { engine_id, instance_id }
404 }
405
406 pub(crate) fn engine_id(&self) -> Uuid {
407 self.engine_id
408 }
409
410 pub(crate) fn instance_id(&self) -> Uuid {
411 self.instance_id
412 }
413}
414
415#[derive(Debug, Clone)]
419pub(crate) struct ConsumedSolidInfo {
420 operation: ConsumedSolidOperation,
422 suggested_replacement_key: Option<ConsumedSolidKey>,
426 returned_solid_keys: Vec<ConsumedSolidKey>,
429}
430
431impl ConsumedSolidInfo {
432 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
433 Self {
434 operation,
435 suggested_replacement_key: returned_solid_keys.first().copied(),
436 returned_solid_keys,
437 }
438 }
439
440 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
441 self.operation
442 }
443
444 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
445 self.suggested_replacement_key
446 }
447
448 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
449 !self.returned_solid_keys.contains(&key)
450 }
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454pub(crate) enum ConsumedSolidOperation {
455 Union,
456 Intersect,
457 Subtract,
458 Split,
459 JoinSurfaces,
460}
461
462impl ConsumedSolidOperation {
463 pub(crate) fn indefinite_article(self) -> &'static str {
464 match self {
465 Self::Intersect => "an",
466 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
467 }
468 }
469}
470
471impl std::fmt::Display for ConsumedSolidOperation {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 match self {
474 Self::Union => f.write_str("union"),
475 Self::Intersect => f.write_str("intersect"),
476 Self::Subtract => f.write_str("subtract"),
477 Self::Split => f.write_str("split"),
478 Self::JoinSurfaces => f.write_str("joinSurfaces"),
479 }
480 }
481}
482
483#[derive(Debug, Clone, Default)]
484pub(crate) struct SketchBlockState {
485 pub sketch_vars: Vec<KclValue>,
486 pub sketch_id: Option<ObjectId>,
487 pub sketch_constraints: Vec<ObjectId>,
488 pub solver_constraints: Vec<ezpz::Constraint>,
489 pub solver_optional_constraints: Vec<ezpz::Constraint>,
490 pub needed_by_engine: Vec<UnsolvedSegment>,
491 pub segment_tags: IndexMap<ObjectId, TagNode>,
492 pub pending_legacy_angle_refactor_metadata: Vec<PendingLegacyAngleRefactorMeta>,
493}
494
495impl ExecState {
496 pub fn new(exec_context: &super::ExecutorContext) -> Self {
497 ExecState {
498 execution_callbacks: exec_context.execution_callbacks.clone(),
499 global: GlobalState::new(&exec_context.settings, Default::default()),
500 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
501 }
502 }
503
504 #[cfg(test)]
505 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
506 ExecState {
507 execution_callbacks: exec_context.execution_callbacks.clone(),
508 global: GlobalState::new(&exec_context.settings, Default::default()),
509 mod_local: ModuleState::new(
510 ModulePath::Main,
511 ProgramMemory::new_with_backend(backend),
512 Default::default(),
513 false,
514 true,
515 ),
516 }
517 }
518
519 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
520 let segment_ids_edited = mock_config.segment_ids_edited.clone();
521 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
522 global.drag_anchors = mock_config.drag_anchors.clone();
523 global.sketch_mode = mock_config.sketch_block_id.is_some();
524 ExecState {
525 execution_callbacks: exec_context.execution_callbacks.clone(),
526 global,
527 mod_local: ModuleState::new(
528 ModulePath::Main,
529 ProgramMemory::new(),
530 Default::default(),
531 mock_config.sketch_block_id.is_some(),
532 mock_config.freedom_analysis,
533 ),
534 }
535 }
536
537 #[cfg(test)]
538 pub(crate) fn new_mock_with_memory_backend(
539 exec_context: &super::ExecutorContext,
540 mock_config: &MockConfig,
541 backend: MemoryBackendKind,
542 ) -> Self {
543 let segment_ids_edited = mock_config.segment_ids_edited.clone();
544 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
545 global.drag_anchors = mock_config.drag_anchors.clone();
546 global.sketch_mode = mock_config.sketch_block_id.is_some();
547 ExecState {
548 execution_callbacks: exec_context.execution_callbacks.clone(),
549 global,
550 mod_local: ModuleState::new(
551 ModulePath::Main,
552 ProgramMemory::new_with_backend(backend),
553 Default::default(),
554 mock_config.sketch_block_id.is_some(),
555 mock_config.freedom_analysis,
556 ),
557 }
558 }
559
560 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
561 let global = GlobalState::new(&exec_context.settings, Default::default());
562
563 *self = ExecState {
564 execution_callbacks: exec_context.execution_callbacks.clone(),
565 global,
566 mod_local: ModuleState::new(
567 self.mod_local.path.clone(),
568 ProgramMemory::new(),
569 Default::default(),
570 false,
571 true,
572 ),
573 };
574 }
575
576 pub fn err(&mut self, e: CompilationIssue) {
578 self.global.issues.push(e);
579 }
580
581 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
583 debug_assert!(annotations::WARN_VALUES.contains(&name));
584
585 if self.mod_local.allowed_warnings.contains(&name) {
586 return;
587 }
588
589 if self.mod_local.denied_warnings.contains(&name) {
590 e.severity = Severity::Error;
591 } else {
592 e.severity = Severity::Warning;
593 }
594
595 self.global.issues.push(e);
596 }
597
598 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
599 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
600 return;
601 };
602 let error = CompilationIssue {
603 source_range,
604 message: format!("Use of {feature_name} is experimental and may change or be removed."),
605 suggestion: None,
606 severity,
607 tag: crate::errors::Tag::None,
608 };
609
610 self.global.issues.push(error);
611 }
612
613 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
614 self.global.issues = std::mem::take(&mut self.global.issues)
615 .into_iter()
616 .filter(|e| {
617 e.severity != Severity::Warning
618 || !source_range.contains_range(&e.source_range)
619 || e.tag != crate::errors::Tag::UnknownNumericUnits
620 })
621 .collect();
622 }
623
624 pub fn issues(&self) -> &[CompilationIssue] {
625 &self.global.issues
626 }
627
628 pub(crate) fn deprecation_version(&self) -> &str {
629 self.global
630 .deprecation_version_override
631 .as_deref()
632 .unwrap_or(self.mod_local.settings.kcl_version.as_str())
633 }
634
635 #[cfg(test)]
636 pub(crate) fn set_deprecation_version_override(&mut self, version: Option<&str>) {
637 self.global.deprecation_version_override = version.map(str::to_owned);
638 }
639
640 #[cfg(test)]
641 pub(crate) fn program_memory_for_tests(
642 &self,
643 main_ref: EnvironmentRef,
644 ) -> Result<IndexMap<String, KclValue>, KclError> {
645 self.mod_local.variables(main_ref)
646 }
647
648 pub async fn into_exec_outcome(
652 self,
653 main_ref: EnvironmentRef,
654 ctx: &ExecutorContext,
655 ) -> Result<ExecOutcome, KclError> {
656 let variables = self.mod_local.variables(main_ref)?;
659 #[cfg(test)]
660 let test_program_memory = variables.clone();
661 let variables = variables
662 .into_iter()
663 .map(|(key, value)| (key, KclValueView::from(value)))
664 .collect();
665 Ok(ExecOutcome {
666 variables,
667 filenames: self.global.filenames(),
668 operations: self.global.operations_by_module(),
669 artifact_graph: self.global.artifacts.graph,
670 scene_objects: self.global.root_module_artifacts.scene_objects,
671 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
672 var_solutions: self.global.root_module_artifacts.var_solutions,
673 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
674 issues: self.global.issues,
675 source_files: self.global.id_to_source,
676 default_planes: ctx.engine.get_default_planes().read().await.clone(),
677 #[cfg(test)]
678 test_program_memory,
679 })
680 }
681
682 #[cfg(feature = "snapshot-engine-responses")]
683 pub(crate) fn take_root_module_responses(
684 &mut self,
685 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
686 std::mem::take(&mut self.global.root_module_artifacts.responses)
687 }
688
689 pub(crate) fn stack(&self) -> &Stack {
690 &self.mod_local.stack
691 }
692
693 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
694 &mut self.mod_local.stack
695 }
696
697 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
700 const LIMIT: usize = 50;
703 if self.mod_local.call_stack_size >= LIMIT {
704 return Err(KclError::new_max_call_stack(KclErrorDetails::new(
705 format!(
706 "Call depth limit ({LIMIT}) exceeded. This usually means a function is recursing without a base case."
707 ),
708 vec![range],
709 )));
710 }
711 self.mod_local.call_stack_size += 1;
712 Ok(())
713 }
714
715 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
718 if self.mod_local.call_stack_size == 0 {
720 let message = "call stack size below zero".to_owned();
721 debug_assert!(false, "{message}");
722 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
723 }
724 self.mod_local.call_stack_size -= 1;
725 Ok(())
726 }
727
728 #[allow(dead_code)]
734 pub(crate) fn machine_depth_high_water(&self) -> usize {
735 self.global.machine_depth_high_water
736 }
737
738 pub(crate) fn sketch_mode(&self) -> bool {
743 self.mod_local.sketch_mode
744 && match &self.mod_local.path {
745 ModulePath::Main => true,
746 ModulePath::Local { .. } => true,
747 ModulePath::Std { .. } => false,
748 }
749 }
750
751 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
755 self.global.sketch_mode
756 }
757
758 pub fn next_object_id(&mut self) -> ObjectId {
759 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
760 }
761
762 pub fn peek_object_id(&self) -> ObjectId {
763 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
764 }
765
766 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
767 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
768 map.get(key).copied()
769 }
770
771 pub(crate) fn set_constraint_state(
772 &mut self,
773 sketch_block_id: ObjectId,
774 key: ConstraintKey,
775 state: ConstraintState,
776 ) {
777 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
778 map.insert(key, state);
779 }
780
781 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
782 let id = obj.id;
783 debug_assert!(
784 id.0 == self.mod_local.artifacts.scene_objects.len(),
785 "Adding scene object with ID {} but next ID is {}",
786 id.0,
787 self.mod_local.artifacts.scene_objects.len()
788 );
789 let artifact_id = obj.artifact_id;
790 self.mod_local.artifacts.scene_objects.push(obj);
791 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
792 self.mod_local
793 .artifacts
794 .artifact_id_to_scene_object
795 .insert(artifact_id, id);
796 id
797 }
798
799 pub fn add_placeholder_scene_object(
802 &mut self,
803 id: ObjectId,
804 source_range: SourceRange,
805 node_path: Option<NodePath>,
806 ) -> ObjectId {
807 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
808 self.mod_local
809 .artifacts
810 .scene_objects
811 .push(Object::placeholder(id, source_range, node_path));
812 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
813 id
814 }
815
816 pub fn set_scene_object(&mut self, object: Object) {
818 let id = object.id;
819 let artifact_id = object.artifact_id;
820 self.mod_local.artifacts.scene_objects[id.0] = object;
821 self.mod_local
822 .artifacts
823 .artifact_id_to_scene_object
824 .insert(artifact_id, id);
825 }
826
827 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
828 self.mod_local
829 .artifacts
830 .artifact_id_to_scene_object
831 .get(&artifact_id)
832 .cloned()
833 }
834
835 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
836 self.global.segment_ids_edited.contains(object_id)
837 }
838
839 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
840 self.global
841 .drag_anchors
842 .iter()
843 .find(|anchor| &anchor.segment_id == object_id)
844 .map(|anchor| &anchor.target)
845 }
846
847 pub(super) fn is_in_sketch_block(&self) -> bool {
848 self.mod_local.sketch_block.is_some()
849 }
850
851 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
852 self.mod_local.sketch_block.as_mut()
853 }
854
855 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
856 self.mod_local.sketch_block.as_ref()
857 }
858
859 pub fn next_uuid(&mut self) -> Uuid {
860 self.mod_local.id_generator.next_uuid()
861 }
862
863 pub fn next_artifact_id(&mut self) -> ArtifactId {
864 self.mod_local.id_generator.next_artifact_id()
865 }
866
867 pub fn id_generator(&mut self) -> &mut IdGenerator {
868 &mut self.mod_local.id_generator
869 }
870
871 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
873 self.mod_local.consumed_solids.insert(consumed_key, info);
874 }
875
876 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
879 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
880 }
881
882 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
885 self.mod_local.consumed_solids.get(key)
886 }
887
888 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
891 self.mod_local.consumed_solid_ids.get(id)
892 }
893
894 pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
895 self.mod_local.consumed_regions.insert(id, info);
896 }
897
898 pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
899 self.mod_local.consumed_regions.get(id).copied()
900 }
901
902 pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
906 fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
907 match value {
908 KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
909 KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
910 value.iter().any(|value| contains_region_id(value, target_id))
911 }
912 KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
913 _ => false,
914 }
915 }
916
917 self.mod_local
918 .stack
919 .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
920 }
921
922 pub(crate) fn latest_consumed_output(
925 &self,
926 suggested_replacement_key: Option<ConsumedSolidKey>,
927 ) -> Option<ConsumedSolidKey> {
928 let mut latest = suggested_replacement_key?;
929 let mut seen = AhashIndexSet::default();
930
931 while seen.insert(latest) {
932 let Some(next) = self
933 .mod_local
934 .consumed_solids
935 .get(&latest)
936 .and_then(|info| info.suggested_replacement_key())
937 else {
938 break;
939 };
940 latest = next;
941 }
942
943 Some(latest)
944 }
945
946 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
950 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
951 match value {
952 KclValue::Solid { value } => {
953 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
954 }
955 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
956 _ => false,
957 }
958 }
959 self.mod_local
960 .stack
961 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
962 }
963
964 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
965 let id = artifact.id();
966 self.mod_local.artifacts.artifacts.insert(id, artifact);
967 }
968
969 pub(crate) fn registered_named_views(&self) -> impl Iterator<Item = (ModuleId, &str)> {
990 self.mod_local
991 .artifacts
992 .artifacts
993 .values()
994 .chain(self.global.artifacts.artifacts.values())
995 .filter_map(|artifact| match artifact {
996 Artifact::NamedView(view) => Some((view.code_ref.range.module_id(), view.name.as_str())),
997 _ => None,
998 })
999 }
1000
1001 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
1002 self.mod_local.artifacts.artifacts.get_mut(&id)
1003 }
1004
1005 pub(crate) fn is_sketch_block_path(&self, path_id: ArtifactId) -> bool {
1006 self.mod_local
1007 .artifacts
1008 .artifacts
1009 .values()
1010 .chain(self.global.artifacts.artifacts.values())
1011 .any(|artifact| {
1012 matches!(artifact, Artifact::SketchBlock(sketch_block) if sketch_block.path_id == Some(path_id))
1013 })
1014 }
1015
1016 pub(crate) fn push_op(&mut self, op: Operation) {
1017 let index = self.mod_local.artifacts.operations.len();
1018 self.mod_local.artifacts.operations.push(op);
1019 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
1020 && let Some(callbacks) = &self.execution_callbacks
1021 {
1022 callbacks.on_operation(OperationCallbackArgs {
1023 module_id: self.mod_local.module_id,
1024 operation,
1025 index,
1026 });
1027 }
1028 }
1029
1030 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
1031 self.mod_local.artifacts.unprocessed_commands.push(command);
1032 }
1033
1034 pub(super) fn next_module_id(&self) -> ModuleId {
1035 ModuleId::from_usize(self.global.path_to_source_id.len())
1036 }
1037
1038 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
1039 self.global.path_to_source_id.get(path).cloned()
1040 }
1041
1042 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
1043 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
1044 self.global.path_to_source_id.insert(path, id);
1045 }
1046
1047 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
1048 let root_id = ModuleId::default();
1049 let path = self
1051 .global
1052 .path_to_source_id
1053 .iter()
1054 .find(|(_, v)| **v == root_id)
1055 .unwrap()
1056 .0
1057 .clone();
1058 self.add_id_to_source(
1059 root_id,
1060 ModuleSource {
1061 path,
1062 source: program.original_file_contents.to_string(),
1063 },
1064 );
1065 }
1066
1067 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
1068 self.global.id_to_source.insert(id, source);
1069 }
1070
1071 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
1072 debug_assert!(self.global.path_to_source_id.contains_key(&path));
1073 let module_info = ModuleInfo { id, repr, path };
1074 self.global.module_infos.insert(id, module_info);
1075 }
1076
1077 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
1078 self.global.module_infos.get(&id)
1079 }
1080
1081 #[cfg(test)]
1082 pub(crate) fn modules(&self) -> &ModuleInfoMap {
1083 &self.global.module_infos
1084 }
1085
1086 #[cfg(test)]
1087 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
1088 &self.global.root_module_artifacts
1089 }
1090
1091 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
1096 self.mod_local
1097 .artifacts
1098 .refactor_metadata
1099 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
1100 }
1101
1102 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
1103 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
1104 }
1105
1106 pub(crate) fn pending_edge_refactor_meta(
1107 &self,
1108 edge_id: Uuid,
1109 argument_source_range: SourceRange,
1110 ) -> Option<PendingEdgeRefactorMeta> {
1111 if let Some(pending) = self
1112 .mod_local
1113 .artifacts
1114 .pending_edge_refactor_metadata
1115 .iter()
1116 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1117 {
1118 return Some(pending.clone());
1119 }
1120
1121 let mut matches = self
1124 .mod_local
1125 .artifacts
1126 .pending_edge_refactor_metadata
1127 .iter()
1128 .filter(|meta| meta.edge_id == edge_id);
1129 let pending = matches.next()?.clone();
1130 matches.next().is_none().then_some(pending)
1131 }
1132
1133 pub(crate) fn record_edge_refactor_meta_from_pending(
1134 &mut self,
1135 edge_id: Uuid,
1136 source_range: SourceRange,
1137 face_ids: [Uuid; 2],
1138 ) -> bool {
1139 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1140 matches!(
1141 meta,
1142 RefactorMetadata::EdgeRefactor(meta)
1143 if meta.edge_id == edge_id && meta.source_range == source_range
1144 )
1145 }) {
1146 return true;
1147 }
1148
1149 let exact_pending_meta = self
1150 .mod_local
1151 .artifacts
1152 .pending_edge_refactor_metadata
1153 .iter()
1154 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1155 .cloned();
1156
1157 let edge_pending_meta = || {
1158 let mut matches = self
1159 .mod_local
1160 .artifacts
1161 .pending_edge_refactor_metadata
1162 .iter()
1163 .filter(|meta| meta.edge_id == edge_id);
1164 let pending_meta = matches.next()?.clone();
1165 matches.next().is_none().then_some(pending_meta)
1166 };
1167
1168 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1169 return false;
1170 };
1171
1172 self.record_edge_refactor_meta(EdgeRefactorMeta {
1173 edge_id,
1174 face_ids,
1175 end_face_ids: Vec::new(),
1176 source_range: pending_meta.source_range,
1177 stdlib_fn: pending_meta.stdlib_fn,
1178 });
1179
1180 true
1181 }
1182
1183 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1188 self.mod_local
1189 .artifacts
1190 .refactor_metadata
1191 .push(RefactorMetadata::DirectTagFillet(meta));
1192 }
1193
1194 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1196 self.global
1197 .root_module_artifacts
1198 .refactor_metadata
1199 .iter()
1200 .filter_map(|m| match m {
1201 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1202 RefactorMetadata::DirectTagFillet(_) | RefactorMetadata::LegacyAngle(_) => None,
1203 })
1204 .collect()
1205 }
1206
1207 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1209 self.global
1210 .root_module_artifacts
1211 .refactor_metadata
1212 .iter()
1213 .filter_map(|m| match m {
1214 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::LegacyAngle(_) => None,
1215 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1216 })
1217 .collect()
1218 }
1219
1220 pub fn current_default_units(&self) -> NumericType {
1221 NumericType::Default {
1222 len: self.length_unit(),
1223 angle: self.angle_unit(),
1224 }
1225 }
1226
1227 pub fn length_unit(&self) -> UnitLength {
1228 self.mod_local.settings.default_length_units
1229 }
1230
1231 pub fn angle_unit(&self) -> UnitAngle {
1232 self.mod_local.settings.default_angle_units
1233 }
1234
1235 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1236 KclError::new_import_cycle(KclErrorDetails::new(
1237 format!(
1238 "circular import of modules is not allowed: {} -> {}",
1239 self.global
1240 .mod_loader
1241 .import_stack
1242 .iter()
1243 .map(|p| p.to_string_lossy())
1244 .collect::<Vec<_>>()
1245 .join(" -> "),
1246 path,
1247 ),
1248 vec![source_range],
1249 ))
1250 }
1251
1252 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1253 self.mod_local.pipe_value.as_ref()
1254 }
1255
1256 pub(crate) fn error_with_outputs(
1257 &self,
1258 error: KclError,
1259 main_ref: Option<EnvironmentRef>,
1260 default_planes: Option<DefaultPlanes>,
1261 ) -> KclErrorWithOutputs {
1262 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1263 .global
1264 .path_to_source_id
1265 .iter()
1266 .map(|(k, v)| ((*v), k.clone()))
1267 .collect();
1268
1269 KclErrorWithOutputs::new(
1270 error,
1271 self.issues().to_vec(),
1272 main_ref
1273 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1274 .unwrap_or_default(),
1275 self.global.operations_by_module(),
1276 Default::default(),
1277 self.global.artifacts.graph.clone(),
1278 self.global.root_module_artifacts.scene_objects.clone(),
1279 self.global.root_module_artifacts.source_range_to_object.clone(),
1280 self.global.root_module_artifacts.var_solutions.clone(),
1281 self.global.root_module_artifacts.refactor_metadata.clone(),
1282 module_id_to_module_path,
1283 self.global.id_to_source.clone(),
1284 default_planes,
1285 )
1286 }
1287
1288 pub(crate) fn build_program_lookup(
1289 &self,
1290 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1291 ) -> ProgramLookup {
1292 ProgramLookup::new(current, self.global.module_infos.clone())
1293 }
1294
1295 pub(crate) async fn build_artifact_graph(
1296 &mut self,
1297 engine: &Arc<EngineManager>,
1298 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1299 ) -> Result<(), KclError> {
1300 let mut new_commands = Vec::new();
1301 let mut new_exec_artifacts = IndexMap::new();
1302 for module in self.global.module_infos.values_mut() {
1303 match &mut module.repr {
1304 ModuleRepr::Kcl(_, Some(outcome)) => {
1305 new_commands.extend(outcome.artifacts.process_commands());
1306 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1307 }
1308 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1309 new_commands.extend(module_artifacts.process_commands());
1310 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1311 }
1312 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1313 }
1314 }
1315 new_commands.extend(self.global.root_module_artifacts.process_commands());
1318 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1321 let new_responses = engine.take_responses().await;
1322
1323 for (id, exec_artifact) in new_exec_artifacts {
1326 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1330 }
1331
1332 let initial_graph = self.global.artifacts.graph.clone();
1333
1334 let programs = self.build_program_lookup(program.clone());
1336 let graph_result = crate::execution::artifact::build_artifact_graph(
1337 &new_commands,
1338 &new_responses,
1339 program,
1340 &mut self.global.artifacts.artifacts,
1341 initial_graph,
1342 &programs,
1343 &self.global.module_infos,
1344 );
1345
1346 #[cfg(feature = "snapshot-engine-responses")]
1347 {
1348 self.global.root_module_artifacts.responses.extend(new_responses);
1350 }
1351
1352 let artifact_graph = graph_result?;
1353 self.global.artifacts.graph = artifact_graph;
1354
1355 Ok(())
1356 }
1357
1358 pub(crate) fn kcl_version(&self) -> KclVersion {
1365 self.global
1366 .entry_point_kcl_version
1367 .unwrap_or_else(|| self.legacy_caller_kcl_version())
1368 }
1369
1370 pub(crate) fn legacy_caller_kcl_version(&self) -> KclVersion {
1379 self.mod_local.settings.kcl_version
1380 }
1381
1382 pub(crate) fn entry_point_version_is_v3_or_higher(&self) -> bool {
1387 self.global
1388 .entry_point_kcl_version
1389 .is_some_and(|v| v >= KclVersion::V3Preview)
1390 }
1391
1392 pub(crate) fn set_entry_point_kcl_version(&mut self, program: &crate::Program) {
1399 let declared = program.meta_settings().ok().flatten().map(|s| s.kcl_version);
1400 self.global.entry_point_kcl_version = match declared {
1401 Some(v) if v >= KclVersion::V3Preview => Some(v),
1402 _ => None,
1403 };
1404 }
1405}
1406
1407impl GlobalState {
1408 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1409 let mut global = GlobalState {
1410 machine_depth_high_water: 0,
1411 path_to_source_id: Default::default(),
1412 module_infos: Default::default(),
1413 artifacts: Default::default(),
1414 root_module_artifacts: Default::default(),
1415 mod_loader: Default::default(),
1416 issues: Default::default(),
1417 deprecation_version_override: None,
1418 entry_point_kcl_version: None,
1419 id_to_source: Default::default(),
1420 segment_ids_edited,
1421 drag_anchors: Vec::new(),
1422 sketch_mode: false,
1423 };
1424
1425 let root_id = ModuleId::default();
1426 let root_path = settings.current_file.clone().unwrap_or_default();
1427 global.module_infos.insert(
1428 root_id,
1429 ModuleInfo {
1430 id: root_id,
1431 path: ModulePath::Local {
1432 value: root_path.clone(),
1433 original_import_path: None,
1434 },
1435 repr: ModuleRepr::Root,
1436 },
1437 );
1438 global.path_to_source_id.insert(
1439 ModulePath::Local {
1440 value: root_path,
1441 original_import_path: None,
1442 },
1443 root_id,
1444 );
1445 global
1446 }
1447
1448 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1449 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1450 }
1451
1452 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1453 self.id_to_source.get(&id)
1454 }
1455}
1456
1457impl ArtifactState {
1458 pub fn cached_body_items(&self) -> usize {
1459 self.graph.item_count()
1460 }
1461
1462 pub(crate) fn clear(&mut self) {
1463 self.artifacts.clear();
1464 self.graph.clear();
1465 }
1466}
1467
1468impl ModuleArtifactState {
1469 pub fn legacy_angle_refactor_metadata(&self) -> Vec<LegacyAngleRefactorMeta> {
1470 self.refactor_metadata
1471 .iter()
1472 .filter_map(|metadata| match metadata {
1473 RefactorMetadata::LegacyAngle(metadata) => Some(*metadata),
1474 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::DirectTagFillet(_) => None,
1475 })
1476 .collect()
1477 }
1478
1479 pub(crate) fn clear(&mut self) {
1480 self.artifacts.clear();
1481 self.unprocessed_commands.clear();
1482 self.commands.clear();
1483 self.operations.clear();
1484 self.refactor_metadata.clear();
1485 }
1486
1487 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1488 self.scene_objects = scene_objects.to_vec();
1489 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1490 self.source_range_to_object.clear();
1491 self.artifact_id_to_scene_object.clear();
1492
1493 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1494 debug_assert_eq!(
1495 object.id.0, expected_id,
1496 "Restored cached scene object ID {} does not match its position {}",
1497 object.id.0, expected_id
1498 );
1499
1500 match &object.kind {
1501 ObjectKind::Wall(wall) => {
1502 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1503 }
1504 ObjectKind::Cap(cap) => {
1505 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1506 }
1507 _ => match &object.source {
1508 crate::front::SourceRef::Simple { range, node_path: _ } => {
1509 self.source_range_to_object.insert(*range, object.id);
1510 }
1511 crate::front::SourceRef::BackTrace { ranges } => {
1512 if let Some((range, _)) = ranges.first() {
1515 self.source_range_to_object.insert(*range, object.id);
1516 }
1517 }
1518 },
1519 }
1520
1521 if object.artifact_id != ArtifactId::placeholder() {
1523 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1524 }
1525 }
1526 }
1527
1528 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1530 self.artifacts.extend(other.artifacts);
1531 self.unprocessed_commands.extend(other.unprocessed_commands);
1532 self.commands.extend(other.commands);
1533 self.operations.extend(other.operations);
1534 if other.scene_objects.len() > self.scene_objects.len() {
1535 self.scene_objects
1536 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1537 }
1538 self.source_range_to_object.extend(other.source_range_to_object);
1539 self.artifact_id_to_scene_object
1540 .extend(other.artifact_id_to_scene_object);
1541 self.var_solutions.extend(other.var_solutions);
1542 self.refactor_metadata.extend(other.refactor_metadata);
1543 }
1544
1545 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1549 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1550 let new_module_commands = unprocessed.clone();
1551 self.commands.extend(unprocessed);
1552 new_module_commands
1553 }
1554
1555 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1556 debug_assert!(
1557 id.0 < self.scene_objects.len(),
1558 "Requested object ID {} but only have {} objects",
1559 id.0,
1560 self.scene_objects.len()
1561 );
1562 self.scene_objects.get(id.0)
1563 }
1564
1565 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1566 debug_assert!(
1567 id.0 < self.scene_objects.len(),
1568 "Requested object ID {} but only have {} objects",
1569 id.0,
1570 self.scene_objects.len()
1571 );
1572 self.scene_objects.get_mut(id.0)
1573 }
1574}
1575
1576impl ModuleState {
1577 pub(super) fn new(
1578 path: ModulePath,
1579 memory: Arc<ProgramMemory>,
1580 module_id: Option<ModuleId>,
1581 sketch_mode: bool,
1582 freedom_analysis: bool,
1583 ) -> Self {
1584 let state_module_id = module_id.unwrap_or_default();
1585 ModuleState {
1586 module_id: state_module_id,
1587 id_generator: IdGenerator::new(module_id),
1588 stack: memory.new_stack(),
1589 call_stack_size: 0,
1590 machine_call_depth: 0,
1591 pipe_value: Default::default(),
1592 being_declared: Default::default(),
1593 sketch_block: Default::default(),
1594 stdlib_entry_source_range: Default::default(),
1595 module_exports: Default::default(),
1596 explicit_length_units: false,
1597 path,
1598 settings: Default::default(),
1599 sketch_mode,
1600 freedom_analysis,
1601 artifacts: Default::default(),
1602 constraint_state: Default::default(),
1603 allowed_warnings: Vec::new(),
1604 denied_warnings: Vec::new(),
1605 consumed_solids: AHashMap::default(),
1606 consumed_solid_ids: AHashMap::default(),
1607 consumed_regions: AHashMap::default(),
1608 inside_stdlib: false,
1609 }
1610 }
1611
1612 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1613 self.stack.find_all_in_env_owned(main_ref)
1614 }
1615}
1616
1617impl SketchBlockState {
1618 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1619 SketchVarId(self.sketch_vars.len())
1620 }
1621
1622 pub(crate) fn var_solutions(
1625 &self,
1626 solve_outcome: &Solved,
1627 solution_ty: NumericType,
1628 sketch_block_range: SourceRange,
1629 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1630 self.sketch_vars
1631 .iter()
1632 .map(|v| {
1633 let Some(sketch_var) = v.as_sketch_var() else {
1634 return Err(KclError::new_internal(KclErrorDetails::new(
1635 "Expected sketch variable".to_owned(),
1636 vec![sketch_block_range],
1637 )));
1638 };
1639 let var_index = sketch_var.id.0;
1640 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1641 let message = format!("No solution for sketch variable with id {}", var_index);
1642 debug_assert!(false, "{}", &message);
1643 KclError::new_internal(KclErrorDetails::new(
1644 message,
1645 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1646 ))
1647 })?;
1648 let solved_value = Number {
1649 value: *solved_n,
1650 units: solution_ty.try_into().map_err(|_| {
1651 KclError::new_internal(KclErrorDetails::new(
1652 "Failed to convert numeric type to units".to_owned(),
1653 vec![sketch_block_range],
1654 ))
1655 })?,
1656 };
1657 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1658 return Ok(None);
1659 };
1660 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1661 })
1662 .filter_map(Result::transpose)
1663 .collect::<Result<Vec<_>, KclError>>()
1664 }
1665}
1666
1667#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1668#[ts(export)]
1669#[serde(rename_all = "camelCase")]
1670pub struct MetaSettings {
1671 pub default_length_units: UnitLength,
1672 pub default_angle_units: UnitAngle,
1673 pub experimental_features: annotations::WarningLevel,
1674 pub kcl_version: KclVersion,
1675}
1676
1677impl Default for MetaSettings {
1678 fn default() -> Self {
1679 MetaSettings {
1680 default_length_units: UnitLength::Millimeters,
1681 default_angle_units: UnitAngle::Degrees,
1682 experimental_features: annotations::WarningLevel::Deny,
1683 kcl_version: KclVersion::default(),
1684 }
1685 }
1686}
1687
1688impl MetaSettings {
1689 pub(crate) fn update_from_annotation(
1690 &mut self,
1691 annotation: &crate::parsing::ast::types::Node<Annotation>,
1692 ) -> Result<(bool, bool), KclError> {
1693 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1694
1695 let mut updated_len = false;
1696 let mut updated_angle = false;
1697 for p in properties {
1698 match &*p.inner.key.name {
1699 annotations::SETTINGS_UNIT_LENGTH => {
1700 let value = annotations::expect_ident(&p.inner.value)?;
1701 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1702 self.default_length_units = value;
1703 updated_len = true;
1704 }
1705 annotations::SETTINGS_UNIT_ANGLE => {
1706 let value = annotations::expect_ident(&p.inner.value)?;
1707 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1708 self.default_angle_units = value;
1709 updated_angle = true;
1710 }
1711 annotations::SETTINGS_VERSION => {
1712 let value = annotations::expect_kcl_version(&p.inner.value)?;
1713 self.kcl_version = value.parse()?;
1714 }
1715 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1716 let value = annotations::expect_ident(&p.inner.value)?;
1717 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1718 KclError::new_semantic(KclErrorDetails::new(
1719 format!(
1720 "Invalid value for {} settings property, expected one of: {}",
1721 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1722 annotations::WARN_LEVELS.join(", ")
1723 ),
1724 annotation.as_source_ranges(),
1725 ))
1726 })?;
1727 self.experimental_features = value;
1728 }
1729 name => {
1730 return Err(KclError::new_semantic(KclErrorDetails::new(
1731 format!(
1732 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1733 annotations::SETTINGS_UNIT_LENGTH,
1734 annotations::SETTINGS_UNIT_ANGLE
1735 ),
1736 vec![annotation.as_source_range()],
1737 )));
1738 }
1739 }
1740 }
1741
1742 Ok((updated_len, updated_angle))
1743 }
1744}
1745
1746#[cfg(test)]
1747mod tests {
1748
1749 use uuid::Uuid;
1750
1751 use super::KclVersion;
1752 use super::ModuleArtifactState;
1753 use crate::NodePath;
1754 use crate::NodePathExt;
1755 use crate::SourceRange;
1756 use crate::execution::ArtifactId;
1757 use crate::front::Object;
1758 use crate::front::ObjectId;
1759 use crate::front::ObjectKind;
1760 use crate::front::Plane;
1761 use crate::front::SourceRef;
1762
1763 #[test]
1764 fn kcl_version_serializes_as_canonical_setting_value() {
1765 assert_eq!(serde_json::to_string(&KclVersion::V1).unwrap(), r#""1.0""#);
1766 assert_eq!(serde_json::to_string(&KclVersion::V2).unwrap(), r#""2.0""#);
1767 assert_eq!(
1768 serde_json::to_string(&KclVersion::V3Preview).unwrap(),
1769 r#""3.0-preview""#
1770 );
1771 }
1772
1773 #[test]
1774 fn restore_scene_objects_rebuilds_lookup_maps() {
1775 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1776 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1777 let plane_range = SourceRange::from([1, 4, 0]);
1778 let plane_node_path = Some(NodePath::placeholder());
1779 let sketch_ranges = vec![
1780 (SourceRange::from([5, 9, 0]), None),
1781 (SourceRange::from([10, 12, 0]), None),
1782 ];
1783 let cached_objects = vec![
1784 Object {
1785 id: ObjectId(0),
1786 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1787 label: Default::default(),
1788 comments: Default::default(),
1789 artifact_id: plane_artifact_id,
1790 source: SourceRef::new(plane_range, plane_node_path),
1791 },
1792 Object {
1793 id: ObjectId(1),
1794 kind: ObjectKind::Nil,
1795 label: Default::default(),
1796 comments: Default::default(),
1797 artifact_id: sketch_artifact_id,
1798 source: SourceRef::BackTrace {
1799 ranges: sketch_ranges.clone(),
1800 },
1801 },
1802 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1803 ];
1804
1805 let mut artifacts = ModuleArtifactState::default();
1806 artifacts.restore_scene_objects(&cached_objects);
1807
1808 assert_eq!(artifacts.scene_objects, cached_objects);
1809 assert_eq!(
1810 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1811 Some(&ObjectId(0))
1812 );
1813 assert_eq!(
1814 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1815 Some(&ObjectId(1))
1816 );
1817 assert_eq!(
1818 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1819 None
1820 );
1821 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1822 assert_eq!(
1823 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1824 Some(&ObjectId(1))
1825 );
1826 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1828 }
1829}