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:
parent
243a0bc923
commit
e0e7897952
5 changed files with 106 additions and 0 deletions
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue