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