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