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