kcl_lib/named_views/
resolver.rs1use indexmap::IndexMap;
2use kcl_api::artifact::NamedViewArtifact;
3use uuid::Uuid;
4
5use super::Artifact;
6use super::ArtifactCameraView;
7use super::ArtifactGraph;
8use super::ArtifactId;
9use super::ModuleId;
10use super::ModulePath;
11use super::Operation;
12use super::visibility;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct NamedViewInfo {
17 pub artifact_id: ArtifactId,
18 pub name: String,
19 pub module_id: ModuleId,
20 pub module_path: ModulePath,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum ViewTarget {
26 Default,
28 Named { name: String, module_id: ModuleId },
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
33pub enum ViewResolutionError {
34 #[error("No module path is available for named-view module {module_id}")]
35 MissingModulePath { module_id: ModuleId },
36 #[error("No named view matches the requested selection")]
37 NotFound,
38 #[error("The requested selection matches multiple named views")]
40 Ambiguous { candidates: Vec<NamedViewInfo> },
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum ResolvedView {
46 Default { visibility: IndexMap<Uuid, bool> },
49 Named {
51 visibility: IndexMap<Uuid, bool>,
52 camera_intent: Box<ArtifactCameraView>,
53 },
54}
55
56impl ResolvedView {
57 pub fn visibility(&self) -> &IndexMap<Uuid, bool> {
59 match self {
60 Self::Default { visibility } | Self::Named { visibility, .. } => visibility,
61 }
62 }
63}
64
65#[derive(Debug)]
67pub struct NamedViewResolver<'a> {
68 artifact_graph: &'a ArtifactGraph,
69 operations: &'a IndexMap<ModuleId, Vec<Operation>>,
70 named_views: Vec<(&'a NamedViewArtifact, NamedViewInfo)>,
71}
72
73impl<'a> NamedViewResolver<'a> {
74 pub fn new(
76 artifact_graph: &'a ArtifactGraph,
77 operations: &'a IndexMap<ModuleId, Vec<Operation>>,
78 filenames: &IndexMap<ModuleId, ModulePath>,
79 ) -> Result<Self, ViewResolutionError> {
80 let mut named_views = Vec::new();
81 for artifact in artifact_graph.values() {
82 let Artifact::NamedView(view) = artifact else {
83 continue;
84 };
85 let module_id = view.declaring_module_id;
86 let module_path = filenames
87 .get(&module_id)
88 .ok_or(ViewResolutionError::MissingModulePath { module_id })?;
89 named_views.push((
90 view,
91 NamedViewInfo {
92 artifact_id: view.id,
93 name: view.name.clone(),
94 module_id,
95 module_path: module_path.clone(),
96 },
97 ));
98 }
99 Ok(Self {
100 artifact_graph,
101 operations,
102 named_views,
103 })
104 }
105
106 pub fn list(&self) -> Vec<NamedViewInfo> {
112 self.named_views.iter().map(|(_, info)| info.clone()).collect()
113 }
114
115 pub fn resolve(&self, target: &ViewTarget) -> Result<ResolvedView, ViewResolutionError> {
116 match target {
117 ViewTarget::Default => Ok(ResolvedView::Default {
118 visibility: visibility::visibility_for_default(self.artifact_graph, self.operations),
119 }),
120 ViewTarget::Named { name, module_id } => {
121 let candidates: Vec<_> = self
122 .named_views
123 .iter()
124 .filter(|(_, info)| info.name == *name && info.module_id == *module_id)
125 .collect();
126
127 match candidates.as_slice() {
128 [] => Err(ViewResolutionError::NotFound),
129 [(view, _)] => Ok(ResolvedView::Named {
130 visibility: visibility::visibility_for_view(self.artifact_graph, view),
131 camera_intent: Box::new(view.camera.clone()),
132 }),
133 _ => Err(ViewResolutionError::Ambiguous {
134 candidates: candidates.iter().map(|(_, info)| info.clone()).collect(),
135 }),
136 }
137 }
138 }
139 }
140}