feat(select): Select Similar + Quick Select panel (#28)

Two ways to extend the current selection by entity type + layer:

* `Select Similar` (right-click → Select Similar, or `SELECTSIMILAR` /
  `SELSIM`): for every (type, layer) pair in the current selection, add
  every entity in the active layout that matches the pair. The seed
  selection stays selected; matches already selected are not
  double-counted.
* `Quick Select` (right-click → Quick Select…, or `QSELECT` / `QS`):
  opens a centred floating panel with Object-type and Layer pick_lists
  (populated only with values that actually exist in the active layout)
  and an "Append to current selection" checkbox. `(Any)` in either slot
  skips that filter. Apply replaces the selection (or extends it with
  Append on) and reports the match count on the command line. Cancel,
  Esc, and outside-click all dismiss without applying. When opened with
  a selection present, the panel pre-fills both filters from the first
  selected entity so QSelect doubles as an editable Select Similar.

Scoped to entities owned by the active layout's block-record so a paper
layout's QSELECT never reaches into model-space entities (and vice
versa). Entity-type names live in a single `entity_type_name` helper in
`entities::traits` to keep the UI strings and the filter keys aligned.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-29 20:30:56 +03:00
1 parent cf925be861
commit 9db206f310
6 files changed
+436

No files matched your search

+32
View File
@@ -55,6 +55,16 @@ pub struct GripPendingValue {
pub action: crate::scene::object::GripMenuAction,
pub label: &'static str,
}
/// Open Quick Select panel state. `type_filter` / `layer_filter` of
/// `None` match every entity in that field; otherwise the filter is the
/// literal name to compare against.
#[derive(Clone, Debug)]
pub struct QSelectState {
pub type_filter: Option<String>,
pub layer_filter: Option<String>,
pub append: bool,
}
use crate::snap::Snapper;
use crate::ui::{AppMenu, CommandLine, Ribbon, StatusBar};
use acadrust::types::{Color as AcadColor, LineWeight};
@@ -135,6 +145,10 @@ pub(super) struct OpenCADStudio {
/// until dismissed (click outside, ESC, cursor leaves the grip).
grip_popup: Option<GripPopup>,
grip_pending: Option<GripPendingValue>,
/// Open Quick Select panel state. `None` = panel closed. Filters are
/// applied via `Message::QSelectApply`; the panel is dismissed on
/// Apply / Cancel / Esc / outside-click.
qselect: Option<QSelectState>,
/// Show the UCS icon in the bottom-left corner of model space (UCSICON).
show_ucs_icon: bool,
/// Whether the ViewCube 3D gizmo is visible in model space (NAVVCUBE).
@@ -627,6 +641,23 @@ pub enum Message {
// ── About window ────────────────────────────────────────────────────
AboutOpen,
AboutCopyInfo,
// ── Quick Select / Select Similar ───────────────────────────────────
/// Extend the current selection with every entity in the active
/// layout that matches a selected entity by (type, layer).
SelectSimilar,
/// Open the Quick Select panel. Initialises filters from the current
/// selection's first entity (type + layer) when one is selected.
QSelectOpen,
/// Close the Quick Select panel without applying.
QSelectClose,
/// Type filter — `None` means "any type".
QSelectSetType(Option<String>),
/// Layer filter — `None` means "any layer".
QSelectSetLayer(Option<String>),
/// Append-to-current-selection toggle.
QSelectSetAppend(bool),
/// Apply the current filter and close the panel.
QSelectApply,
/// The user clicked the title-bar ✕ (fires before the window closes).
WindowCloseRequested(window::Id),
/// A window was fully closed (fires after `window::close()` is called).
@@ -818,6 +849,7 @@ impl OpenCADStudio {
grip_hover: None,
grip_popup: None,
grip_pending: None,
qselect: None,
show_ucs_icon: true,
show_viewcube: true,
show_properties: true,