Skip to main content

brep_render/engine_state/
wire_harness_ops.rs

1use super::*;
2use brep_kernel::{WireHarnessConnection, WireHarnessReport, WireHarnessState};
3
4/// A patch the panel applies to one connection: every field optional, only
5/// the present ones change.
6#[derive(Debug, Default, Clone, PartialEq)]
7pub struct ConnectionPatch {
8    pub name: Option<String>,
9    pub from: Option<String>,
10    pub to: Option<String>,
11    pub diameter: Option<f64>,
12}
13
14impl EngineState {
15    // --- read surface ------------------------------------------------------
16
17    /// The routing report of the last APPLIED run: endpoints (every port in
18    /// the model), the harness segments, one route per connection, and the
19    /// bundles. `None` before the first run.
20    pub fn wire_harness_report(&self) -> Option<&WireHarnessReport> {
21        self.wire_harness_report.as_ref()
22    }
23
24    /// The document's `wireHarness` block as typed state (the default — no
25    /// connections — when the document carries none).
26    pub fn wire_harness_state(&self) -> WireHarnessState {
27        self.history
28            .wire_harness_block()
29            .and_then(|block| serde_json::from_value(block.clone()).ok())
30            .unwrap_or_default()
31    }
32
33    /// The panel's verifier global: the block, the report, and the endpoint
34    /// choices, as one JSON object.
35    pub fn wire_harness_state_json(&self) -> String {
36        self.wire_harness_state_value().to_string()
37    }
38
39    /// [`Self::wire_harness_state_json`] as a JSON value.
40    pub fn wire_harness_state_value(&self) -> serde_json::Value {
41        let state = self.wire_harness_state();
42        serde_json::json!({
43            "connections": state.connections,
44            "buildBundles": state.build_bundles,
45            "cutMargin": state.cut_margin,
46            "report": self.wire_harness_report,
47        })
48    }
49
50    // --- mutations (checkpointed document edits + re-run) -------------------
51
52    /// Write `state` as the document's block (checkpointed) and re-run the
53    /// history so the tail routes it. An empty block (no connections, a fresh
54    /// counter and no cut margin) is removed from the document so a part that
55    /// never had a harness saves byte-identically.
56    fn write_wire_harness_state(&mut self, state: WireHarnessState) -> String {
57        let block = if state.connections.is_empty() && state.id_counter == 0 && state.cut_margin == 0.0 {
58            None
59        } else {
60            serde_json::to_value(&state).ok()
61        };
62        self.history.set_wire_harness_block(block);
63        self.rerun_history()
64    }
65
66    /// Add a connection between two port ids (either may be empty — the panel
67    /// fills them in) and return its minted id (`wire-N`, named `Wire N`).
68    pub fn wire_harness_add_connection(&mut self, from: &str, to: &str, diameter: f64) -> String {
69        let mut state = self.wire_harness_state();
70        let id = state.next_id();
71        let name = format!("Wire {}", state.id_counter);
72        state.connections.push(WireHarnessConnection {
73            id: id.clone(),
74            name,
75            from: from.to_string(),
76            to: to.to_string(),
77            diameter: if diameter.is_finite() && diameter > 0.0 { diameter } else { 1.0 },
78        });
79        self.write_wire_harness_state(state);
80        id
81    }
82
83    /// Apply a patch to one connection. An unknown id is an error; a
84    /// non-positive diameter is refused (the connection keeps its diameter).
85    pub fn wire_harness_update_connection(
86        &mut self,
87        id: &str,
88        patch: &ConnectionPatch,
89    ) -> Result<(), String> {
90        let mut state = self.wire_harness_state();
91        let connection = state
92            .connections
93            .iter_mut()
94            .find(|connection| connection.id == id)
95            .ok_or_else(|| format!("no harness connection '{id}'"))?;
96        if let Some(name) = &patch.name {
97            connection.name = name.trim().to_string();
98        }
99        if let Some(from) = &patch.from {
100            connection.from = from.trim().to_string();
101        }
102        if let Some(to) = &patch.to {
103            connection.to = to.trim().to_string();
104        }
105        if let Some(diameter) = patch.diameter {
106            if !(diameter.is_finite() && diameter > 0.0) {
107                return Err(format!("wire diameter must be positive, got {diameter}"));
108            }
109            connection.diameter = diameter;
110        }
111        self.write_wire_harness_state(state);
112        Ok(())
113    }
114
115    /// Remove one connection. An unknown id is an error.
116    pub fn wire_harness_remove_connection(&mut self, id: &str) -> Result<(), String> {
117        let mut state = self.wire_harness_state();
118        let before = state.connections.len();
119        state.connections.retain(|connection| connection.id != id);
120        if state.connections.len() == before {
121            return Err(format!("no harness connection '{id}'"));
122        }
123        self.write_wire_harness_state(state);
124        Ok(())
125    }
126
127    /// Switch the bundle solids on / off (the routing report stays either way).
128    pub fn wire_harness_set_build_bundles(&mut self, on: bool) {
129        let mut state = self.wire_harness_state();
130        if state.build_bundles == on {
131            return;
132        }
133        state.build_bundles = on;
134        self.write_wire_harness_state(state);
135    }
136
137    /// Set the cut margin every wire's BOM cut length adds to its routed
138    /// length (see [`WireHarnessState::cut_margin`]). A negative or non-finite
139    /// margin is refused and the block is left alone; an unchanged one writes
140    /// nothing, so it takes no undo step. The re-run replays the harness from
141    /// its cache — the router does not read the margin.
142    pub fn wire_harness_set_cut_margin(&mut self, margin: f64) -> Result<(), String> {
143        if !(margin.is_finite() && margin >= 0.0) {
144            return Err(format!("cut margin must be zero or more, got {margin}"));
145        }
146        let mut state = self.wire_harness_state();
147        if state.cut_margin == margin {
148            return Ok(());
149        }
150        state.cut_margin = margin;
151        self.write_wire_harness_state(state);
152        Ok(())
153    }
154
155    // --- hover ----------------------------------------------------------------
156
157    /// Highlight a connection under the panel's pointer: its two ports (their
158    /// sheet solids are keyed by the port feature id) and the bundle solids of
159    /// every segment its route crosses. An unknown id highlights nothing.
160    pub fn wire_harness_hover_connection(&mut self, id: &str) {
161        let state = self.wire_harness_state();
162        let mut names: Vec<String> = Vec::new();
163        if let Some(connection) = state.connections.iter().find(|c| c.id == id) {
164            for port in [&connection.from, &connection.to] {
165                if !port.is_empty() && self.scene.solid(port).is_some() {
166                    names.push(port.clone());
167                }
168            }
169        }
170        if let Some(report) = &self.wire_harness_report {
171            if let Some(route) = report.routes.iter().find(|route| route.connection_id == id) {
172                for segment in &route.segment_ids {
173                    if let Some(bundle) = report.bundles.iter().find(|b| &b.segment_id == segment) {
174                        if !bundle.solid_name.is_empty() {
175                            names.push(bundle.solid_name.clone());
176                        }
177                    }
178                }
179            }
180        }
181        self.clear_hover();
182        self.emphasis.hovered_solids.extend(names);
183        self.emphasis.generation = self.emphasis.generation.wrapping_add(1);
184        self.dirty = true;
185    }
186
187    /// The pointer left the panel's rows: drop the highlight.
188    pub fn wire_harness_hover_end(&mut self) {
189        if self.clear_hover() {
190            self.dirty = true;
191        }
192    }
193}
194