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