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 pub async fn into_exec_outcome(
644 self,
645 main_ref: EnvironmentRef,
646 ctx: &ExecutorContext,
647 ) -> Result<ExecOutcome, KclError> {
648 let variables = self
651 .mod_local
652 .variables(main_ref)?
653 .into_iter()
654 .map(|(key, value)| (key, KclValueView::from(value)))
655 .collect();
656 Ok(ExecOutcome {
657 variables,
658 filenames: self.global.filenames(),
659 operations: self.global.operations_by_module(),
660 artifact_graph: self.global.artifacts.graph,
661 scene_objects: self.global.root_module_artifacts.scene_objects,
662 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
663 var_solutions: self.global.root_module_artifacts.var_solutions,
664 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
665 issues: self.global.issues,
666 source_files: self.global.id_to_source,
667 default_planes: ctx.engine.get_default_planes().read().await.clone(),
668 })
669 }
670
671 #[cfg(feature = "snapshot-engine-responses")]
672 pub(crate) fn take_root_module_responses(
673 &mut self,
674 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
675 std::mem::take(&mut self.global.root_module_artifacts.responses)
676 }
677
678 pub(crate) fn stack(&self) -> &Stack {
679 &self.mod_local.stack
680 }
681
682 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
683 &mut self.mod_local.stack
684 }
685
686 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
689 const LIMIT: usize = 50;
692 if self.mod_local.call_stack_size >= LIMIT {
693 return Err(KclError::new_max_call_stack(KclErrorDetails::new(
694 format!(
695 "Call depth limit ({LIMIT}) exceeded. This usually means a function is recursing without a base case."
696 ),
697 vec![range],
698 )));
699 }
700 self.mod_local.call_stack_size += 1;
701 Ok(())
702 }
703
704 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
707 if self.mod_local.call_stack_size == 0 {
709 let message = "call stack size below zero".to_owned();
710 debug_assert!(false, "{message}");
711 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
712 }
713 self.mod_local.call_stack_size -= 1;
714 Ok(())
715 }
716
717 #[allow(dead_code)]
723 pub(crate) fn machine_depth_high_water(&self) -> usize {
724 self.global.machine_depth_high_water
725 }
726
727 pub(crate) fn sketch_mode(&self) -> bool {
732 self.mod_local.sketch_mode
733 && match &self.mod_local.path {
734 ModulePath::Main => true,
735 ModulePath::Local { .. } => true,
736 ModulePath::Std { .. } => false,
737 }
738 }
739
740 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
744 self.global.sketch_mode
745 }
746
747 pub fn next_object_id(&mut self) -> ObjectId {
748 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
749 }
750
751 pub fn peek_object_id(&self) -> ObjectId {
752 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
753 }
754
755 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
756 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
757 map.get(key).copied()
758 }
759
760 pub(crate) fn set_constraint_state(
761 &mut self,
762 sketch_block_id: ObjectId,
763 key: ConstraintKey,
764 state: ConstraintState,
765 ) {
766 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
767 map.insert(key, state);
768 }
769
770 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
771 let id = obj.id;
772 debug_assert!(
773 id.0 == self.mod_local.artifacts.scene_objects.len(),
774 "Adding scene object with ID {} but next ID is {}",
775 id.0,
776 self.mod_local.artifacts.scene_objects.len()
777 );
778 let artifact_id = obj.artifact_id;
779 self.mod_local.artifacts.scene_objects.push(obj);
780 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
781 self.mod_local
782 .artifacts
783 .artifact_id_to_scene_object
784 .insert(artifact_id, id);
785 id
786 }
787
788 pub fn add_placeholder_scene_object(
791 &mut self,
792 id: ObjectId,
793 source_range: SourceRange,
794 node_path: Option<NodePath>,
795 ) -> ObjectId {
796 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
797 self.mod_local
798 .artifacts
799 .scene_objects
800 .push(Object::placeholder(id, source_range, node_path));
801 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
802 id
803 }
804
805 pub fn set_scene_object(&mut self, object: Object) {
807 let id = object.id;
808 let artifact_id = object.artifact_id;
809 self.mod_local.artifacts.scene_objects[id.0] = object;
810 self.mod_local
811 .artifacts
812 .artifact_id_to_scene_object
813 .insert(artifact_id, id);
814 }
815
816 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
817 self.mod_local
818 .artifacts
819 .artifact_id_to_scene_object
820 .get(&artifact_id)
821 .cloned()
822 }
823
824 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
825 self.global.segment_ids_edited.contains(object_id)
826 }
827
828 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
829 self.global
830 .drag_anchors
831 .iter()
832 .find(|anchor| &anchor.segment_id == object_id)
833 .map(|anchor| &anchor.target)
834 }
835
836 pub(super) fn is_in_sketch_block(&self) -> bool {
837 self.mod_local.sketch_block.is_some()
838 }
839
840 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
841 self.mod_local.sketch_block.as_mut()
842 }
843
844 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
845 self.mod_local.sketch_block.as_ref()
846 }
847
848 pub fn next_uuid(&mut self) -> Uuid {
849 self.mod_local.id_generator.next_uuid()
850 }
851
852 pub fn next_artifact_id(&mut self) -> ArtifactId {
853 self.mod_local.id_generator.next_artifact_id()
854 }
855
856 pub fn id_generator(&mut self) -> &mut IdGenerator {
857 &mut self.mod_local.id_generator
858 }
859
860 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
862 self.mod_local.consumed_solids.insert(consumed_key, info);
863 }
864
865 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
868 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
869 }
870
871 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
874 self.mod_local.consumed_solids.get(key)
875 }
876
877 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
880 self.mod_local.consumed_solid_ids.get(id)
881 }
882
883 pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
884 self.mod_local.consumed_regions.insert(id, info);
885 }
886
887 pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
888 self.mod_local.consumed_regions.get(id).copied()
889 }
890
891 pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
895 fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
896 match value {
897 KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
898 KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
899 value.iter().any(|value| contains_region_id(value, target_id))
900 }
901 KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
902 _ => false,
903 }
904 }
905
906 self.mod_local
907 .stack
908 .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
909 }
910
911 pub(crate) fn latest_consumed_output(
914 &self,
915 suggested_replacement_key: Option<ConsumedSolidKey>,
916 ) -> Option<ConsumedSolidKey> {
917 let mut latest = suggested_replacement_key?;
918 let mut seen = AhashIndexSet::default();
919
920 while seen.insert(latest) {
921 let Some(next) = self
922 .mod_local
923 .consumed_solids
924 .get(&latest)
925 .and_then(|info| info.suggested_replacement_key())
926 else {
927 break;
928 };
929 latest = next;
930 }
931
932 Some(latest)
933 }
934
935 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
939 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
940 match value {
941 KclValue::Solid { value } => {
942 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
943 }
944 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
945 _ => false,
946 }
947 }
948 self.mod_local
949 .stack
950 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
951 }
952
953 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
954 let id = artifact.id();
955 self.mod_local.artifacts.artifacts.insert(id, artifact);
956 }
957
958 pub(crate) fn registered_named_views(&self) -> impl Iterator<Item = (ModuleId, &str)> {
979 self.mod_local
980 .artifacts
981 .artifacts
982 .values()
983 .chain(self.global.artifacts.artifacts.values())
984 .filter_map(|artifact| match artifact {
985 Artifact::NamedView(view) => Some((view.code_ref.range.module_id(), view.name.as_str())),
986 _ => None,
987 })
988 }
989
990 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
991 self.mod_local.artifacts.artifacts.get_mut(&id)
992 }
993
994 pub(crate) fn push_op(&mut self, op: Operation) {
995 let index = self.mod_local.artifacts.operations.len();
996 self.mod_local.artifacts.operations.push(op);
997 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
998 && let Some(callbacks) = &self.execution_callbacks
999 {
1000 callbacks.on_operation(OperationCallbackArgs {
1001 module_id: self.mod_local.module_id,
1002 operation,
1003 index,
1004 });
1005 }
1006 }
1007
1008 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
1009 self.mod_local.artifacts.unprocessed_commands.push(command);
1010 }
1011
1012 pub(super) fn next_module_id(&self) -> ModuleId {
1013 ModuleId::from_usize(self.global.path_to_source_id.len())
1014 }
1015
1016 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
1017 self.global.path_to_source_id.get(path).cloned()
1018 }
1019
1020 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
1021 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
1022 self.global.path_to_source_id.insert(path, id);
1023 }
1024
1025 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
1026 let root_id = ModuleId::default();
1027 let path = self
1029 .global
1030 .path_to_source_id
1031 .iter()
1032 .find(|(_, v)| **v == root_id)
1033 .unwrap()
1034 .0
1035 .clone();
1036 self.add_id_to_source(
1037 root_id,
1038 ModuleSource {
1039 path,
1040 source: program.original_file_contents.to_string(),
1041 },
1042 );
1043 }
1044
1045 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
1046 self.global.id_to_source.insert(id, source);
1047 }
1048
1049 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
1050 debug_assert!(self.global.path_to_source_id.contains_key(&path));
1051 let module_info = ModuleInfo { id, repr, path };
1052 self.global.module_infos.insert(id, module_info);
1053 }
1054
1055 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
1056 self.global.module_infos.get(&id)
1057 }
1058
1059 #[cfg(test)]
1060 pub(crate) fn modules(&self) -> &ModuleInfoMap {
1061 &self.global.module_infos
1062 }
1063
1064 #[cfg(test)]
1065 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
1066 &self.global.root_module_artifacts
1067 }
1068
1069 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
1074 self.mod_local
1075 .artifacts
1076 .refactor_metadata
1077 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
1078 }
1079
1080 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
1081 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
1082 }
1083
1084 pub(crate) fn pending_edge_refactor_meta(
1085 &self,
1086 edge_id: Uuid,
1087 argument_source_range: SourceRange,
1088 ) -> Option<PendingEdgeRefactorMeta> {
1089 if let Some(pending) = self
1090 .mod_local
1091 .artifacts
1092 .pending_edge_refactor_metadata
1093 .iter()
1094 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1095 {
1096 return Some(pending.clone());
1097 }
1098
1099 let mut matches = self
1102 .mod_local
1103 .artifacts
1104 .pending_edge_refactor_metadata
1105 .iter()
1106 .filter(|meta| meta.edge_id == edge_id);
1107 let pending = matches.next()?.clone();
1108 matches.next().is_none().then_some(pending)
1109 }
1110
1111 pub(crate) fn record_edge_refactor_meta_from_pending(
1112 &mut self,
1113 edge_id: Uuid,
1114 source_range: SourceRange,
1115 face_ids: [Uuid; 2],
1116 ) -> bool {
1117 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1118 matches!(
1119 meta,
1120 RefactorMetadata::EdgeRefactor(meta)
1121 if meta.edge_id == edge_id && meta.source_range == source_range
1122 )
1123 }) {
1124 return true;
1125 }
1126
1127 let exact_pending_meta = self
1128 .mod_local
1129 .artifacts
1130 .pending_edge_refactor_metadata
1131 .iter()
1132 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1133 .cloned();
1134
1135 let edge_pending_meta = || {
1136 let mut matches = self
1137 .mod_local
1138 .artifacts
1139 .pending_edge_refactor_metadata
1140 .iter()
1141 .filter(|meta| meta.edge_id == edge_id);
1142 let pending_meta = matches.next()?.clone();
1143 matches.next().is_none().then_some(pending_meta)
1144 };
1145
1146 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1147 return false;
1148 };
1149
1150 self.record_edge_refactor_meta(EdgeRefactorMeta {
1151 edge_id,
1152 face_ids,
1153 end_face_ids: Vec::new(),
1154 source_range: pending_meta.source_range,
1155 stdlib_fn: pending_meta.stdlib_fn,
1156 });
1157
1158 true
1159 }
1160
1161 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1166 self.mod_local
1167 .artifacts
1168 .refactor_metadata
1169 .push(RefactorMetadata::DirectTagFillet(meta));
1170 }
1171
1172 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1174 self.global
1175 .root_module_artifacts
1176 .refactor_metadata
1177 .iter()
1178 .filter_map(|m| match m {
1179 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1180 RefactorMetadata::DirectTagFillet(_) | RefactorMetadata::LegacyAngle(_) => None,
1181 })
1182 .collect()
1183 }
1184
1185 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1187 self.global
1188 .root_module_artifacts
1189 .refactor_metadata
1190 .iter()
1191 .filter_map(|m| match m {
1192 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::LegacyAngle(_) => None,
1193 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1194 })
1195 .collect()
1196 }
1197
1198 pub fn current_default_units(&self) -> NumericType {
1199 NumericType::Default {
1200 len: self.length_unit(),
1201 angle: self.angle_unit(),
1202 }
1203 }
1204
1205 pub fn length_unit(&self) -> UnitLength {
1206 self.mod_local.settings.default_length_units
1207 }
1208
1209 pub fn angle_unit(&self) -> UnitAngle {
1210 self.mod_local.settings.default_angle_units
1211 }
1212
1213 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1214 KclError::new_import_cycle(KclErrorDetails::new(
1215 format!(
1216 "circular import of modules is not allowed: {} -> {}",
1217 self.global
1218 .mod_loader
1219 .import_stack
1220 .iter()
1221 .map(|p| p.to_string_lossy())
1222 .collect::<Vec<_>>()
1223 .join(" -> "),
1224 path,
1225 ),
1226 vec![source_range],
1227 ))
1228 }
1229
1230 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1231 self.mod_local.pipe_value.as_ref()
1232 }
1233
1234 pub(crate) fn error_with_outputs(
1235 &self,
1236 error: KclError,
1237 main_ref: Option<EnvironmentRef>,
1238 default_planes: Option<DefaultPlanes>,
1239 ) -> KclErrorWithOutputs {
1240 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1241 .global
1242 .path_to_source_id
1243 .iter()
1244 .map(|(k, v)| ((*v), k.clone()))
1245 .collect();
1246
1247 KclErrorWithOutputs::new(
1248 error,
1249 self.issues().to_vec(),
1250 main_ref
1251 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1252 .unwrap_or_default(),
1253 self.global.operations_by_module(),
1254 Default::default(),
1255 self.global.artifacts.graph.clone(),
1256 self.global.root_module_artifacts.scene_objects.clone(),
1257 self.global.root_module_artifacts.source_range_to_object.clone(),
1258 self.global.root_module_artifacts.var_solutions.clone(),
1259 self.global.root_module_artifacts.refactor_metadata.clone(),
1260 module_id_to_module_path,
1261 self.global.id_to_source.clone(),
1262 default_planes,
1263 )
1264 }
1265
1266 pub(crate) fn build_program_lookup(
1267 &self,
1268 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1269 ) -> ProgramLookup {
1270 ProgramLookup::new(current, self.global.module_infos.clone())
1271 }
1272
1273 pub(crate) async fn build_artifact_graph(
1274 &mut self,
1275 engine: &Arc<EngineManager>,
1276 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1277 ) -> Result<(), KclError> {
1278 let mut new_commands = Vec::new();
1279 let mut new_exec_artifacts = IndexMap::new();
1280 for module in self.global.module_infos.values_mut() {
1281 match &mut module.repr {
1282 ModuleRepr::Kcl(_, Some(outcome)) => {
1283 new_commands.extend(outcome.artifacts.process_commands());
1284 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1285 }
1286 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1287 new_commands.extend(module_artifacts.process_commands());
1288 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1289 }
1290 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1291 }
1292 }
1293 new_commands.extend(self.global.root_module_artifacts.process_commands());
1296 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1299 let new_responses = engine.take_responses().await;
1300
1301 for (id, exec_artifact) in new_exec_artifacts {
1304 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1308 }
1309
1310 let initial_graph = self.global.artifacts.graph.clone();
1311
1312 let programs = self.build_program_lookup(program.clone());
1314 let graph_result = crate::execution::artifact::build_artifact_graph(
1315 &new_commands,
1316 &new_responses,
1317 program,
1318 &mut self.global.artifacts.artifacts,
1319 initial_graph,
1320 &programs,
1321 &self.global.module_infos,
1322 );
1323
1324 #[cfg(feature = "snapshot-engine-responses")]
1325 {
1326 self.global.root_module_artifacts.responses.extend(new_responses);
1328 }
1329
1330 let artifact_graph = graph_result?;
1331 self.global.artifacts.graph = artifact_graph;
1332
1333 Ok(())
1334 }
1335
1336 pub(crate) fn kcl_version(&self) -> KclVersion {
1343 self.global
1344 .entry_point_kcl_version
1345 .unwrap_or_else(|| self.legacy_caller_kcl_version())
1346 }
1347
1348 pub(crate) fn legacy_caller_kcl_version(&self) -> KclVersion {
1357 self.mod_local.settings.kcl_version
1358 }
1359
1360 pub(crate) fn use_kcl_v3_control_flow(&self) -> bool {
1365 self.global
1366 .entry_point_kcl_version
1367 .is_some_and(|v| v >= KclVersion::V3Preview)
1368 }
1369
1370 pub(crate) fn set_entry_point_kcl_version(&mut self, program: &crate::Program) {
1377 let declared = program.meta_settings().ok().flatten().map(|s| s.kcl_version);
1378 self.global.entry_point_kcl_version = match declared {
1379 Some(v) if v >= KclVersion::V3Preview => Some(v),
1380 _ => None,
1381 };
1382 }
1383}
1384
1385impl GlobalState {
1386 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1387 let mut global = GlobalState {
1388 machine_depth_high_water: 0,
1389 path_to_source_id: Default::default(),
1390 module_infos: Default::default(),
1391 artifacts: Default::default(),
1392 root_module_artifacts: Default::default(),
1393 mod_loader: Default::default(),
1394 issues: Default::default(),
1395 deprecation_version_override: None,
1396 entry_point_kcl_version: None,
1397 id_to_source: Default::default(),
1398 segment_ids_edited,
1399 drag_anchors: Vec::new(),
1400 sketch_mode: false,
1401 };
1402
1403 let root_id = ModuleId::default();
1404 let root_path = settings.current_file.clone().unwrap_or_default();
1405 global.module_infos.insert(
1406 root_id,
1407 ModuleInfo {
1408 id: root_id,
1409 path: ModulePath::Local {
1410 value: root_path.clone(),
1411 original_import_path: None,
1412 },
1413 repr: ModuleRepr::Root,
1414 },
1415 );
1416 global.path_to_source_id.insert(
1417 ModulePath::Local {
1418 value: root_path,
1419 original_import_path: None,
1420 },
1421 root_id,
1422 );
1423 global
1424 }
1425
1426 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1427 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1428 }
1429
1430 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1431 self.id_to_source.get(&id)
1432 }
1433}
1434
1435impl ArtifactState {
1436 pub fn cached_body_items(&self) -> usize {
1437 self.graph.item_count()
1438 }
1439
1440 pub(crate) fn clear(&mut self) {
1441 self.artifacts.clear();
1442 self.graph.clear();
1443 }
1444}
1445
1446impl ModuleArtifactState {
1447 pub fn legacy_angle_refactor_metadata(&self) -> Vec<LegacyAngleRefactorMeta> {
1448 self.refactor_metadata
1449 .iter()
1450 .filter_map(|metadata| match metadata {
1451 RefactorMetadata::LegacyAngle(metadata) => Some(*metadata),
1452 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::DirectTagFillet(_) => None,
1453 })
1454 .collect()
1455 }
1456
1457 pub(crate) fn clear(&mut self) {
1458 self.artifacts.clear();
1459 self.unprocessed_commands.clear();
1460 self.commands.clear();
1461 self.operations.clear();
1462 self.refactor_metadata.clear();
1463 }
1464
1465 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1466 self.scene_objects = scene_objects.to_vec();
1467 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1468 self.source_range_to_object.clear();
1469 self.artifact_id_to_scene_object.clear();
1470
1471 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1472 debug_assert_eq!(
1473 object.id.0, expected_id,
1474 "Restored cached scene object ID {} does not match its position {}",
1475 object.id.0, expected_id
1476 );
1477
1478 match &object.kind {
1479 ObjectKind::Wall(wall) => {
1480 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1481 }
1482 ObjectKind::Cap(cap) => {
1483 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1484 }
1485 _ => match &object.source {
1486 crate::front::SourceRef::Simple { range, node_path: _ } => {
1487 self.source_range_to_object.insert(*range, object.id);
1488 }
1489 crate::front::SourceRef::BackTrace { ranges } => {
1490 if let Some((range, _)) = ranges.first() {
1493 self.source_range_to_object.insert(*range, object.id);
1494 }
1495 }
1496 },
1497 }
1498
1499 if object.artifact_id != ArtifactId::placeholder() {
1501 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1502 }
1503 }
1504 }
1505
1506 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1508 self.artifacts.extend(other.artifacts);
1509 self.unprocessed_commands.extend(other.unprocessed_commands);
1510 self.commands.extend(other.commands);
1511 self.operations.extend(other.operations);
1512 if other.scene_objects.len() > self.scene_objects.len() {
1513 self.scene_objects
1514 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1515 }
1516 self.source_range_to_object.extend(other.source_range_to_object);
1517 self.artifact_id_to_scene_object
1518 .extend(other.artifact_id_to_scene_object);
1519 self.var_solutions.extend(other.var_solutions);
1520 self.refactor_metadata.extend(other.refactor_metadata);
1521 }
1522
1523 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1527 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1528 let new_module_commands = unprocessed.clone();
1529 self.commands.extend(unprocessed);
1530 new_module_commands
1531 }
1532
1533 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1534 debug_assert!(
1535 id.0 < self.scene_objects.len(),
1536 "Requested object ID {} but only have {} objects",
1537 id.0,
1538 self.scene_objects.len()
1539 );
1540 self.scene_objects.get(id.0)
1541 }
1542
1543 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1544 debug_assert!(
1545 id.0 < self.scene_objects.len(),
1546 "Requested object ID {} but only have {} objects",
1547 id.0,
1548 self.scene_objects.len()
1549 );
1550 self.scene_objects.get_mut(id.0)
1551 }
1552}
1553
1554impl ModuleState {
1555 pub(super) fn new(
1556 path: ModulePath,
1557 memory: Arc<ProgramMemory>,
1558 module_id: Option<ModuleId>,
1559 sketch_mode: bool,
1560 freedom_analysis: bool,
1561 ) -> Self {
1562 let state_module_id = module_id.unwrap_or_default();
1563 ModuleState {
1564 module_id: state_module_id,
1565 id_generator: IdGenerator::new(module_id),
1566 stack: memory.new_stack(),
1567 call_stack_size: 0,
1568 machine_call_depth: 0,
1569 pipe_value: Default::default(),
1570 being_declared: Default::default(),
1571 sketch_block: Default::default(),
1572 stdlib_entry_source_range: Default::default(),
1573 module_exports: Default::default(),
1574 explicit_length_units: false,
1575 path,
1576 settings: Default::default(),
1577 sketch_mode,
1578 freedom_analysis,
1579 artifacts: Default::default(),
1580 constraint_state: Default::default(),
1581 allowed_warnings: Vec::new(),
1582 denied_warnings: Vec::new(),
1583 consumed_solids: AHashMap::default(),
1584 consumed_solid_ids: AHashMap::default(),
1585 consumed_regions: AHashMap::default(),
1586 inside_stdlib: false,
1587 }
1588 }
1589
1590 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1591 self.stack.find_all_in_env_owned(main_ref)
1592 }
1593}
1594
1595impl SketchBlockState {
1596 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1597 SketchVarId(self.sketch_vars.len())
1598 }
1599
1600 pub(crate) fn var_solutions(
1603 &self,
1604 solve_outcome: &Solved,
1605 solution_ty: NumericType,
1606 sketch_block_range: SourceRange,
1607 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1608 self.sketch_vars
1609 .iter()
1610 .map(|v| {
1611 let Some(sketch_var) = v.as_sketch_var() else {
1612 return Err(KclError::new_internal(KclErrorDetails::new(
1613 "Expected sketch variable".to_owned(),
1614 vec![sketch_block_range],
1615 )));
1616 };
1617 let var_index = sketch_var.id.0;
1618 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1619 let message = format!("No solution for sketch variable with id {}", var_index);
1620 debug_assert!(false, "{}", &message);
1621 KclError::new_internal(KclErrorDetails::new(
1622 message,
1623 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1624 ))
1625 })?;
1626 let solved_value = Number {
1627 value: *solved_n,
1628 units: solution_ty.try_into().map_err(|_| {
1629 KclError::new_internal(KclErrorDetails::new(
1630 "Failed to convert numeric type to units".to_owned(),
1631 vec![sketch_block_range],
1632 ))
1633 })?,
1634 };
1635 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1636 return Ok(None);
1637 };
1638 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1639 })
1640 .filter_map(Result::transpose)
1641 .collect::<Result<Vec<_>, KclError>>()
1642 }
1643}
1644
1645#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1646#[ts(export)]
1647#[serde(rename_all = "camelCase")]
1648pub struct MetaSettings {
1649 pub default_length_units: UnitLength,
1650 pub default_angle_units: UnitAngle,
1651 pub experimental_features: annotations::WarningLevel,
1652 pub kcl_version: KclVersion,
1653}
1654
1655impl Default for MetaSettings {
1656 fn default() -> Self {
1657 MetaSettings {
1658 default_length_units: UnitLength::Millimeters,
1659 default_angle_units: UnitAngle::Degrees,
1660 experimental_features: annotations::WarningLevel::Deny,
1661 kcl_version: KclVersion::default(),
1662 }
1663 }
1664}
1665
1666impl MetaSettings {
1667 pub(crate) fn update_from_annotation(
1668 &mut self,
1669 annotation: &crate::parsing::ast::types::Node<Annotation>,
1670 ) -> Result<(bool, bool), KclError> {
1671 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1672
1673 let mut updated_len = false;
1674 let mut updated_angle = false;
1675 for p in properties {
1676 match &*p.inner.key.name {
1677 annotations::SETTINGS_UNIT_LENGTH => {
1678 let value = annotations::expect_ident(&p.inner.value)?;
1679 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1680 self.default_length_units = value;
1681 updated_len = true;
1682 }
1683 annotations::SETTINGS_UNIT_ANGLE => {
1684 let value = annotations::expect_ident(&p.inner.value)?;
1685 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1686 self.default_angle_units = value;
1687 updated_angle = true;
1688 }
1689 annotations::SETTINGS_VERSION => {
1690 let value = annotations::expect_kcl_version(&p.inner.value)?;
1691 self.kcl_version = value.parse()?;
1692 }
1693 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1694 let value = annotations::expect_ident(&p.inner.value)?;
1695 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1696 KclError::new_semantic(KclErrorDetails::new(
1697 format!(
1698 "Invalid value for {} settings property, expected one of: {}",
1699 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1700 annotations::WARN_LEVELS.join(", ")
1701 ),
1702 annotation.as_source_ranges(),
1703 ))
1704 })?;
1705 self.experimental_features = value;
1706 }
1707 name => {
1708 return Err(KclError::new_semantic(KclErrorDetails::new(
1709 format!(
1710 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1711 annotations::SETTINGS_UNIT_LENGTH,
1712 annotations::SETTINGS_UNIT_ANGLE
1713 ),
1714 vec![annotation.as_source_range()],
1715 )));
1716 }
1717 }
1718 }
1719
1720 Ok((updated_len, updated_angle))
1721 }
1722}
1723
1724#[cfg(test)]
1725mod tests {
1726
1727 use uuid::Uuid;
1728
1729 use super::KclVersion;
1730 use super::ModuleArtifactState;
1731 use crate::NodePath;
1732 use crate::NodePathExt;
1733 use crate::SourceRange;
1734 use crate::execution::ArtifactId;
1735 use crate::front::Object;
1736 use crate::front::ObjectId;
1737 use crate::front::ObjectKind;
1738 use crate::front::Plane;
1739 use crate::front::SourceRef;
1740
1741 #[test]
1742 fn kcl_version_serializes_as_canonical_setting_value() {
1743 assert_eq!(serde_json::to_string(&KclVersion::V1).unwrap(), r#""1.0""#);
1744 assert_eq!(serde_json::to_string(&KclVersion::V2).unwrap(), r#""2.0""#);
1745 assert_eq!(
1746 serde_json::to_string(&KclVersion::V3Preview).unwrap(),
1747 r#""3.0-preview""#
1748 );
1749 }
1750
1751 #[test]
1752 fn restore_scene_objects_rebuilds_lookup_maps() {
1753 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1754 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1755 let plane_range = SourceRange::from([1, 4, 0]);
1756 let plane_node_path = Some(NodePath::placeholder());
1757 let sketch_ranges = vec![
1758 (SourceRange::from([5, 9, 0]), None),
1759 (SourceRange::from([10, 12, 0]), None),
1760 ];
1761 let cached_objects = vec![
1762 Object {
1763 id: ObjectId(0),
1764 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1765 label: Default::default(),
1766 comments: Default::default(),
1767 artifact_id: plane_artifact_id,
1768 source: SourceRef::new(plane_range, plane_node_path),
1769 },
1770 Object {
1771 id: ObjectId(1),
1772 kind: ObjectKind::Nil,
1773 label: Default::default(),
1774 comments: Default::default(),
1775 artifact_id: sketch_artifact_id,
1776 source: SourceRef::BackTrace {
1777 ranges: sketch_ranges.clone(),
1778 },
1779 },
1780 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1781 ];
1782
1783 let mut artifacts = ModuleArtifactState::default();
1784 artifacts.restore_scene_objects(&cached_objects);
1785
1786 assert_eq!(artifacts.scene_objects, cached_objects);
1787 assert_eq!(
1788 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1789 Some(&ObjectId(0))
1790 );
1791 assert_eq!(
1792 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1793 Some(&ObjectId(1))
1794 );
1795 assert_eq!(
1796 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1797 None
1798 );
1799 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1800 assert_eq!(
1801 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1802 Some(&ObjectId(1))
1803 );
1804 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1806 }
1807}