Skip to main content

brep_render/engine_state/
illustration.rs

1//! A presentation-only loan. No history edits or temporary documents are needed.
2use 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    /// Each requested view starts from the saved presentation, not its predecessor.
80    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