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        let state = self.wire_harness_state();
37        serde_json::json!({
38            "connections": state.connections,
39            "buildBundles": state.build_bundles,
40            "report": self.wire_harness_report,
41        })
42        .to_string()
43    }
44
45    // --- mutations (checkpointed document edits + re-run) -------------------
46
47    /// Write `state` as the document's block (checkpointed) and re-run the
48    /// history so the tail routes it. An empty block (no connections) is
49    /// removed from the document so a part that never had a harness saves
50    /// byte-identically.
51    fn write_wire_harness_state(&mut self, state: WireHarnessState) -> String {
52        let block = if state.connections.is_empty() && state.id_counter == 0 {
53            None
54        } else {
55            serde_json::to_value(&state).ok()
56        };
57        self.history.set_wire_harness_block(block);
58        self.rerun_history()
59    }
60
61    /// Add a connection between two port ids (either may be empty — the panel
62    /// fills them in) and return its minted id (`wire-N`, named `Wire N`).
63    pub fn wire_harness_add_connection(&mut self, from: &str, to: &str, diameter: f64) -> String {
64        let mut state = self.wire_harness_state();
65        let id = state.next_id();
66        let name = format!("Wire {}", state.id_counter);
67        state.connections.push(WireHarnessConnection {
68            id: id.clone(),
69            name,
70            from: from.to_string(),
71            to: to.to_string(),
72            diameter: if diameter.is_finite() && diameter > 0.0 { diameter } else { 1.0 },
73        });
74        self.write_wire_harness_state(state);
75        id
76    }
77
78    /// Apply a patch to one connection. An unknown id is an error; a
79    /// non-positive diameter is refused (the connection keeps its diameter).
80    pub fn wire_harness_update_connection(
81        &mut self,
82        id: &str,
83        patch: &ConnectionPatch,
84    ) -> Result<(), String> {
85        let mut state = self.wire_harness_state();
86        let connection = state
87            .connections
88            .iter_mut()
89            .find(|connection| connection.id == id)
90            .ok_or_else(|| format!("no harness connection '{id}'"))?;
91        if let Some(name) = &patch.name {
92            connection.name = name.trim().to_string();
93        }
94        if let Some(from) = &patch.from {
95            connection.from = from.trim().to_string();
96        }
97        if let Some(to) = &patch.to {
98            connection.to = to.trim().to_string();
99        }
100        if let Some(diameter) = patch.diameter {
101            if !(diameter.is_finite() && diameter > 0.0) {
102                return Err(format!("wire diameter must be positive, got {diameter}"));
103            }
104            connection.diameter = diameter;
105        }
106        self.write_wire_harness_state(state);
107        Ok(())
108    }
109
110    /// Remove one connection. An unknown id is an error.
111    pub fn wire_harness_remove_connection(&mut self, id: &str) -> Result<(), String> {
112        let mut state = self.wire_harness_state();
113        let before = state.connections.len();
114        state.connections.retain(|connection| connection.id != id);
115        if state.connections.len() == before {
116            return Err(format!("no harness connection '{id}'"));
117        }
118        self.write_wire_harness_state(state);
119        Ok(())
120    }
121
122    /// Switch the bundle solids on / off (the routing report stays either way).
123    pub fn wire_harness_set_build_bundles(&mut self, on: bool) {
124        let mut state = self.wire_harness_state();
125        if state.build_bundles == on {
126            return;
127        }
128        state.build_bundles = on;
129        self.write_wire_harness_state(state);
130    }
131
132    // --- hover ----------------------------------------------------------------
133
134    /// Highlight a connection under the panel's pointer: its two ports (their
135    /// sheet solids are keyed by the port feature id) and the bundle solids of
136    /// every segment its route crosses. An unknown id highlights nothing.
137    pub fn wire_harness_hover_connection(&mut self, id: &str) {
138        let state = self.wire_harness_state();
139        let mut names: Vec<String> = Vec::new();
140        if let Some(connection) = state.connections.iter().find(|c| c.id == id) {
141            for port in [&connection.from, &connection.to] {
142                if !port.is_empty() && self.scene.solid(port).is_some() {
143                    names.push(port.clone());
144                }
145            }
146        }
147        if let Some(report) = &self.wire_harness_report {
148            if let Some(route) = report.routes.iter().find(|route| route.connection_id == id) {
149                for segment in &route.segment_ids {
150                    if let Some(bundle) = report.bundles.iter().find(|b| &b.segment_id == segment) {
151                        if !bundle.solid_name.is_empty() {
152                            names.push(bundle.solid_name.clone());
153                        }
154                    }
155                }
156            }
157        }
158        self.clear_hover();
159        self.emphasis.hovered_solids.extend(names);
160        self.emphasis.generation = self.emphasis.generation.wrapping_add(1);
161        self.dirty = true;
162    }
163
164    /// The pointer left the panel's rows: drop the highlight.
165    pub fn wire_harness_hover_end(&mut self) {
166        if self.clear_hover() {
167            self.dirty = true;
168        }
169    }
170}
171
172// BREP private tests: 0fb23dcbb3ad0d91