feat(statusbar): isolate / hide objects with status pill

Add a hidden-entity set to the scene that the tessellation visibility
test skips, so hidden objects neither render nor hit-test. New commands
ISOLATEOBJECTS (show only the selection), HIDEOBJECTS (hide the
selection) and UNISOLATEOBJECTS (restore). An ISO status pill lights up
while anything is hidden and restores everything on click.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-04 22:51:23 +03:00
commit e0e7897952
5 changed files with 106 additions and 0 deletions

View file

@ -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 — restore all layers that were turned off by LAYISO (turn all on)
"LAYUNISO" => { "LAYUNISO" => {
self.push_undo_snapshot(i, "LAYUNISO"); self.push_undo_snapshot(i, "LAYUNISO");

View file

@ -1159,6 +1159,7 @@ impl OpenCADStudio {
self.clean_screen, self.clean_screen,
tab.scene.document.header.insertion_units, tab.scene.document.header.insertion_units,
self.units_popup_open, self.units_popup_open,
tab.scene.is_isolation_active(),
&self.statusbar_config, &self.statusbar_config,
) )
}) })

View file

@ -549,6 +549,10 @@ pub struct Scene {
pub document: CadDocument, pub document: CadDocument,
/// Currently selected entity handles. /// Currently selected entity handles.
pub selected: HashSet<Handle>, pub selected: HashSet<Handle>,
/// 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<Handle>,
/// In-progress preview wires while a command is active (rubber-band + object ghosts). /// In-progress preview wires while a command is active (rubber-band + object ghosts).
pub preview_wires: Vec<WireModel>, pub preview_wires: Vec<WireModel>,
/// Committed-segment wire drawn during multi-point commands (normal colour). /// Committed-segment wire drawn during multi-point commands (normal colour).
@ -693,6 +697,7 @@ impl Scene {
selection: Rc::new(RefCell::new(SelectionState::default())), selection: Rc::new(RefCell::new(SelectionState::default())),
document: CadDocument::new(), document: CadDocument::new(),
selected: HashSet::new(), selected: HashSet::new(),
hidden: HashSet::new(),
preview_wires: vec![], preview_wires: vec![],
interim_wire: None, interim_wire: None,
camera_generation: 0, camera_generation: 0,
@ -1316,6 +1321,48 @@ impl Scene {
.count() .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. /// True if any currently selected entity is a Viewport.
/// Used to enable the scale picker when a viewport is selected in paper space. /// Used to enable the scale picker when a viewport is selected in paper space.
pub fn has_selected_viewport(&self) -> bool { pub fn has_selected_viewport(&self) -> bool {
@ -1869,6 +1916,10 @@ impl Scene {
if c.invisible { if c.invisible {
return false; 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. // Block/BlockEnd are block-defn sentinels, not drawable geometry.
// Without this skip they fall through to fallback_geometry's `_` // Without this skip they fall through to fallback_geometry's `_`
// arm and emit a 1-unit phantom segment at world_offset that // arm and emit a 1-unit phantom segment at world_offset that

View file

@ -59,6 +59,8 @@ impl StatusBar {
insertion_units: i16, insertion_units: i16,
// True while the drawing-units picker is open. // True while the drawing-units picker is open.
units_popup_open: bool, 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. // Which pills the user has chosen to show on the bar.
config: &'a StatusBarConfig, config: &'a StatusBarConfig,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
@ -173,6 +175,16 @@ impl StatusBar {
"Drawing Units (INSUNITS)\nClick to change", "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() { if vis(StatusPill::Vp) && !vp_label.is_empty() {
right_status = right_status.push(tip( right_status = right_status.push(tip(
status_pill(vp_label).into(), status_pill(vp_label).into(),

View file

@ -23,6 +23,7 @@ pub enum StatusPill {
Space, Space,
Scale, Scale,
Units, Units,
Isolate,
Vp, Vp,
CleanScreen, CleanScreen,
} }
@ -43,6 +44,7 @@ impl StatusPill {
StatusPill::Space, StatusPill::Space,
StatusPill::Scale, StatusPill::Scale,
StatusPill::Units, StatusPill::Units,
StatusPill::Isolate,
StatusPill::Vp, StatusPill::Vp,
StatusPill::CleanScreen, StatusPill::CleanScreen,
]; ];
@ -62,6 +64,7 @@ impl StatusPill {
StatusPill::Space => "space", StatusPill::Space => "space",
StatusPill::Scale => "scale", StatusPill::Scale => "scale",
StatusPill::Units => "units", StatusPill::Units => "units",
StatusPill::Isolate => "isolate",
StatusPill::Vp => "vp", StatusPill::Vp => "vp",
StatusPill::CleanScreen => "cleanscreen", StatusPill::CleanScreen => "cleanscreen",
} }
@ -82,6 +85,7 @@ impl StatusPill {
StatusPill::Space => "Model/Paper Space", StatusPill::Space => "Model/Paper Space",
StatusPill::Scale => "Annotation Scale", StatusPill::Scale => "Annotation Scale",
StatusPill::Units => "Drawing Units", StatusPill::Units => "Drawing Units",
StatusPill::Isolate => "Isolate Objects",
StatusPill::Vp => "Viewport Count", StatusPill::Vp => "Viewport Count",
StatusPill::CleanScreen => "Clean Screen", StatusPill::CleanScreen => "Clean Screen",
} }