diff --git a/src/app/mod.rs b/src/app/mod.rs index c1cf5a8d..ee106c96 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1289,6 +1289,9 @@ pub enum Message { /// Extend the current selection with every entity in the active /// layout that matches a selected entity by (type, layer). SelectSimilar, + /// Replace the current selection with every other selectable object in + /// the active layout (the complement of what's selected now). + InvertSelection, /// Keyboard modifier state changed — tracks whether Shift is held so the /// pick path can do subtractive (Shift+click) selection. SetShiftDown(bool), diff --git a/src/app/update.rs b/src/app/update.rs index 9d870fd2..d2994ac5 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -4971,6 +4971,16 @@ impl OpenCADStudio { Task::none() } + Message::InvertSelection => { + let i = self.active_tab; + self.tabs[i].scene.selection.borrow_mut().context_menu = None; + let count = self.tabs[i].scene.invert_selection(); + self.command_line + .push_output(&format!("Invert Selection: {} object(s) selected.", count)); + self.refresh_properties(); + Task::none() + } + Message::QSelectOpen => { let i = self.active_tab; self.tabs[i].scene.selection.borrow_mut().context_menu = None; diff --git a/src/app/view.rs b/src/app/view.rs index 698f39de..59e68875 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -2907,6 +2907,10 @@ fn viewport_context_menu_overlay( )); items.push(sep()); items.push(item("Select Similar".to_string(), Message::SelectSimilar)); + items.push(item( + "Invert Selection".to_string(), + Message::InvertSelection, + )); } if isolation_active { items.push(item( diff --git a/src/scene/mod.rs b/src/scene/mod.rs index eac8ef1a..5684e8cf 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4896,6 +4896,27 @@ impl Scene { added } + /// Replace the selection with its complement: every selectable object + /// in the active layout that isn't currently selected. The candidate + /// set is the visible wire set, so objects on off/frozen layers (which + /// can't be picked anyway) are excluded. Returns the new count. + pub fn invert_selection(&mut self) -> usize { + let prev: rustc_hash::FxHashSet = self.selected.iter().copied().collect(); + let all: Vec = self + .entity_wires() + .iter() + .filter_map(|w| Self::handle_from_wire_name(&w.name)) + .collect(); + self.selected.clear(); + for h in all { + if !prev.contains(&h) { + self.selected.insert(h); + } + } + self.bump_selection(); + self.selected.len() + } + /// Replaces (or extends, when `append` is true) the current /// selection with every entity in the active layout that matches /// the filter. Returns the number of newly-matching entities.