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}
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    /// Each requested view starts from the saved presentation, not its predecessor.
77    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