diff --git a/src/app/commands.rs b/src/app/commands.rs index 1a93d90e6..fecefdd1c 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -332,6 +332,44 @@ impl OpenCADStudio { } } + // ISOLATEOBJECTS — hide every object except the current selection + "ISOLATEOBJECTS" => { + if self.tabs[i].scene.selected.is_empty() { + self.command_line + .push_error("ISOLATEOBJECTS: select the objects to isolate first."); + } else { + let n = self.tabs[i].scene.selected.len(); + self.tabs[i].scene.isolate_selected(); + self.command_line.push_info(&format!( + "Isolated {n} object(s). UNISOLATEOBJECTS to restore." + )); + } + } + + // HIDEOBJECTS — hide the current selection + "HIDEOBJECTS" => { + if self.tabs[i].scene.selected.is_empty() { + self.command_line + .push_error("HIDEOBJECTS: select the objects to hide first."); + } else { + let n = self.tabs[i].scene.selected.len(); + self.tabs[i].scene.hide_selected(); + self.command_line + .push_info(&format!("Hid {n} object(s). UNISOLATEOBJECTS to restore.")); + } + } + + // UNISOLATEOBJECTS — bring back everything hidden by Isolate / Hide + "UNISOLATEOBJECTS" => { + if self.tabs[i].scene.is_isolation_active() { + self.tabs[i].scene.end_isolation(); + self.command_line + .push_info("Isolation ended — all objects shown."); + } else { + self.command_line.push_info("No hidden objects."); + } + } + // LAYUNISO — restore all layers that were turned off by LAYISO (turn all on) "LAYUNISO" => { self.push_undo_snapshot(i, "LAYUNISO"); diff --git a/src/app/view.rs b/src/app/view.rs index 44cdf99ac..017afb7c4 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -1159,6 +1159,7 @@ impl OpenCADStudio { self.clean_screen, tab.scene.document.header.insertion_units, self.units_popup_open, + tab.scene.is_isolation_active(), &self.statusbar_config, ) }) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index c6c5ed65e..2eda19745 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -549,6 +549,10 @@ pub struct Scene { pub document: CadDocument, /// Currently selected entity handles. pub selected: HashSet, + /// Entity handles hidden by Isolate / Hide. Empty = nothing hidden. + /// `tessellate_block`'s visibility test skips these, so they neither + /// render nor hit-test until isolation ends. + pub hidden: HashSet, /// In-progress preview wires while a command is active (rubber-band + object ghosts). pub preview_wires: Vec, /// Committed-segment wire drawn during multi-point commands (normal colour). @@ -693,6 +697,7 @@ impl Scene { selection: Rc::new(RefCell::new(SelectionState::default())), document: CadDocument::new(), selected: HashSet::new(), + hidden: HashSet::new(), preview_wires: vec![], interim_wire: None, camera_generation: 0, @@ -1316,6 +1321,48 @@ impl Scene { .count() } + /// True when any entities are hidden by Isolate / Hide. + pub fn is_isolation_active(&self) -> bool { + !self.hidden.is_empty() + } + + /// Hide every drawable entity except the current selection (Isolate). + pub fn isolate_selected(&mut self) { + if self.selected.is_empty() { + return; + } + let keep = self.selected.clone(); + self.hidden = self + .document + .entities() + .map(|e| e.common().handle) + .filter(|h| !h.is_null() && !keep.contains(h)) + .collect(); + self.selected.clear(); + self.bump_geometry(); + } + + /// Hide the current selection (Hide Objects). + pub fn hide_selected(&mut self) { + if self.selected.is_empty() { + return; + } + for h in self.selected.iter().copied() { + self.hidden.insert(h); + } + self.selected.clear(); + self.bump_geometry(); + } + + /// Clear isolation — bring every hidden entity back (End Isolation). + pub fn end_isolation(&mut self) { + if self.hidden.is_empty() { + return; + } + self.hidden.clear(); + self.bump_geometry(); + } + /// True if any currently selected entity is a Viewport. /// Used to enable the scale picker when a viewport is selected in paper space. pub fn has_selected_viewport(&self) -> bool { @@ -1869,6 +1916,10 @@ impl Scene { if c.invisible { return false; } + // Isolate / Hide: skip entities the user has hidden. + if !self.hidden.is_empty() && self.hidden.contains(&c.handle) { + return false; + } // Block/BlockEnd are block-defn sentinels, not drawable geometry. // Without this skip they fall through to fallback_geometry's `_` // arm and emit a 1-unit phantom segment at world_offset that diff --git a/src/ui/statusbar.rs b/src/ui/statusbar.rs index e1fd2907c..ad45d1946 100644 --- a/src/ui/statusbar.rs +++ b/src/ui/statusbar.rs @@ -59,6 +59,8 @@ impl StatusBar { insertion_units: i16, // True while the drawing-units picker is open. units_popup_open: bool, + // True when objects are hidden by Isolate / Hide. + isolation_active: bool, // Which pills the user has chosen to show on the bar. config: &'a StatusBarConfig, ) -> Element<'a, Message> { @@ -173,6 +175,16 @@ impl StatusBar { "Drawing Units (INSUNITS)\nClick to change", )); } + if vis(StatusPill::Isolate) { + right_status = right_status.push(tip( + toggle_pill( + "ISO", + isolation_active, + Message::Command("UNISOLATEOBJECTS".into()), + ), + "Isolate Objects\nISOLATEOBJECTS / HIDEOBJECTS to hide; click to restore", + )); + } if vis(StatusPill::Vp) && !vp_label.is_empty() { right_status = right_status.push(tip( status_pill(vp_label).into(), diff --git a/src/ui/statusbar_config.rs b/src/ui/statusbar_config.rs index 4c3fd2589..f6ba3edc4 100644 --- a/src/ui/statusbar_config.rs +++ b/src/ui/statusbar_config.rs @@ -23,6 +23,7 @@ pub enum StatusPill { Space, Scale, Units, + Isolate, Vp, CleanScreen, } @@ -43,6 +44,7 @@ impl StatusPill { StatusPill::Space, StatusPill::Scale, StatusPill::Units, + StatusPill::Isolate, StatusPill::Vp, StatusPill::CleanScreen, ]; @@ -62,6 +64,7 @@ impl StatusPill { StatusPill::Space => "space", StatusPill::Scale => "scale", StatusPill::Units => "units", + StatusPill::Isolate => "isolate", StatusPill::Vp => "vp", StatusPill::CleanScreen => "cleanscreen", } @@ -82,6 +85,7 @@ impl StatusPill { StatusPill::Space => "Model/Paper Space", StatusPill::Scale => "Annotation Scale", StatusPill::Units => "Drawing Units", + StatusPill::Isolate => "Isolate Objects", StatusPill::Vp => "Viewport Count", StatusPill::CleanScreen => "Clean Screen", }