brep_render/engine_state/
illustration.rs1use super::*;
3use serde_json::{json, Value};
4
5#[derive(Clone)]
6pub(super) struct IllustrationSnapshot {
7 camera: crate::view::ViewCamera,
8 settings: crate::style::RenderSettings,
9 emphasis: crate::style::Emphasis,
10 displays: Vec<crate::scene::SolidDisplay>,
11 active_view: Option<String>,
12 open_view: Option<String>,
13 modeling: Option<super::pmi_ops::PmiModelingSnapshot>,
14 explode: std::collections::HashMap<String, crate::scene::SolidDisplay>,
15 section_hidden: Vec<String>,
16}
17
18impl EngineState {
19 pub fn illustration_begin(&mut self) -> Result<(), String> {
20 if self.illustration_snapshot.is_some() {
21 return Err("an illustration workflow is already active".into());
22 }
23 if self.sketch_edit.is_some() {
24 return Err("finish sketch editing before an assembly illustration workflow".into());
25 }
26 if self.run_pending() {
27 return Err("wait for the rebuild before capturing".into());
28 }
29 self.illustration_snapshot = Some(IllustrationSnapshot {
30 camera: self.camera.clone(),
31 settings: self.settings.clone(),
32 emphasis: self.emphasis.clone(),
33 displays: self.scene.solids().to_vec(),
34 active_view: self.pmi_active_view.clone(),
35 open_view: self.pmi_open_view.clone(),
36 modeling: self.pmi_modeling.clone(),
37 explode: self.pmi_explode_originals.clone(),
38 section_hidden: self.pmi_section_hidden.clone(),
39 });
40 Ok(())
41 }
42
43 fn illustration_reset(&mut self) -> Result<(), String> {
44 let snapshot = self
45 .illustration_snapshot
46 .clone()
47 .ok_or("no active illustration workflow")?;
48 self.pmi_restore_explode();
49 for display in snapshot.displays {
50 self.scene.set_visible(&display.name, display.visible);
51 if let Some(live) = self.scene.solid_mut(&display.name) {
52 *live = display;
53 }
54 }
55 self.camera = snapshot.camera;
56 self.settings = snapshot.settings;
57 self.settings_generation += 1;
58 self.widgets
59 .set_viewcube_size(self.settings.viewcube_size_px);
60 let generation = self.emphasis.generation;
61 self.emphasis = snapshot.emphasis;
62 self.emphasis.generation = generation + 1;
63 self.pmi_active_view = snapshot.active_view;
64 self.pmi_open_view = snapshot.open_view;
65 self.pmi_modeling = snapshot.modeling;
66 self.pmi_explode_originals = snapshot.explode;
67 self.pmi_section_hidden = snapshot.section_hidden;
68 self.refresh_pmi_overlay();
69 self.dirty = true;
70 Ok(())
71 }
72
73 pub fn illustration_end(&mut self) -> Result<(), String> {
74 self.illustration_reset()?;
75 self.illustration_snapshot = None;
76 Ok(())
77 }
78
79 pub fn illustration_apply(&mut self, view: &Value) -> Result<Value, String> {
81 self.illustration_reset()?;
82 let result = (|| {
83 if let Some(id) = view["saved_view"].as_str() {
84 self.pmi_activate_view(id)?;
85 }
86 let components: Vec<String> = if let Some(ids) = view.get("components") {
87 serde_json::from_value(ids.clone()).map_err(|e| format!("components: {e}"))?
88 } else {
89 self.component_ids()
90 };
91 let mut solids = Vec::new();
92 for id in &components {
93 let info = self
94 .component_info(id)
95 .ok_or_else(|| format!("unknown component {id}"))?;
96 if info.members.is_empty() {
97 return Err(format!("component {id} has no evaluated geometry"));
98 }
99 solids.extend(info.members);
100 }
101 if view.get("components").is_some() {
102 let all: Vec<String> = self.scene.solids().iter().map(|s| s.name.clone()).collect();
103 for name in all {
104 self.scene.set_visible(&name, solids.contains(&name));
105 }
106 }
107 if let Some(visibility) = view.get("visibility") {
108 for (id, visible) in visibility
109 .as_object()
110 .ok_or("visibility must map component ids to booleans")?
111 {
112 let visible = visible
113 .as_bool()
114 .ok_or("visibility values must be boolean")?;
115 let info = self
116 .component_info(id)
117 .ok_or_else(|| format!("unknown component {id}"))?;
118 for name in info.members {
119 self.scene.set_visible(&name, visible);
120 }
121 }
122 }
123 if let Some(explode) = view.get("explode") {
124 for (id, value) in explode
125 .as_object()
126 .ok_or("explode must map component ids to translations in mm")?
127 {
128 let translation: [f64; 3] = serde_json::from_value(value.clone())
129 .map_err(|e| format!("explode.{id}: {e}"))?;
130 if !translation.iter().all(|v| v.is_finite()) {
131 return Err("explode translation must be finite".into());
132 }
133 let info = self
134 .component_info(id)
135 .ok_or_else(|| format!("unknown component {id}"))?;
136 for name in info.members {
137 if let Some(display) = self.scene.solid_mut(&name) {
138 super::pmi_ops::transform_display(
139 display,
140 [0.0; 3],
141 translation,
142 [0.0; 3],
143 [1.0; 3],
144 );
145 }
146 }
147 }
148 }
149 if let Some(settings) = view.get("settings") {
150 if settings.get("lodFactor").is_some() {
151 return Err("capture settings cannot change lodFactor; set display LOD before starting the workflow".into());
152 }
153 self.apply_settings_json(&settings.to_string())?;
154 }
155 if view["clear_selection"].as_bool().unwrap_or(true) {
156 let generation = self.emphasis.generation + 1;
157 self.emphasis = Default::default();
158 self.emphasis.generation = generation;
159 }
160 if let Some(camera) = view.get("camera") {
161 self.apply_camera_state_json(&camera.to_string())?;
162 } else if let Some(name) = view["standard_view"].as_str() {
163 if !self.standard_view(&name.to_ascii_uppercase()) {
164 return Err(format!("unknown standard view {name}"));
165 }
166 }
167 let visible_solids: Vec<&str> = self
168 .scene
169 .solids()
170 .iter()
171 .filter(|solid| solid.visible)
172 .map(|solid| solid.name.as_str())
173 .collect();
174 let visible_components: Vec<String> = self
175 .component_ids()
176 .into_iter()
177 .filter(|id| {
178 self.component_info(id).is_some_and(|info| {
179 info.members
180 .iter()
181 .any(|name| visible_solids.contains(&name.as_str()))
182 })
183 })
184 .collect();
185 self.dirty = true;
186 Ok(
187 json!({"components":components, "solids":solids, "visibleComponents":visible_components, "visibleSolids":visible_solids, "camera":serde_json::from_str::<Value>(&self.camera_state_json()).unwrap_or_default()}),
188 )
189 })();
190 if result.is_err() {
191 self.illustration_reset()?;
192 }
193 result
194 }
195}
196