forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
cf925be861
commit
9db206f310
6 files changed
+436
No files matched your search
@@ -1738,6 +1738,17 @@ impl OpenCADStudio {
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
"SELECTSIMILAR" | "SELSIM" => {
|
||||
let added = self.tabs[i].scene.select_similar();
|
||||
self.command_line
|
||||
.push_output(&format!("Select Similar: {} added.", added));
|
||||
self.refresh_properties();
|
||||
}
|
||||
|
||||
"QSELECT" | "QS" => {
|
||||
return Task::done(Message::QSelectOpen);
|
||||
}
|
||||
|
||||
// ── LIST — entity info ────────────────────────────────────────
|
||||
"LIST" | "LI" => {
|
||||
let selected: Vec<_> = self.tabs[i].scene.selected_entities();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1242,6 +1242,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
// Cancel layout rename / context menus first, then fall through.
|
||||
let i_e = self.active_tab;
|
||||
if self.qselect.take().is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
{
|
||||
let mut sel = self.tabs[i_e].scene.selection.borrow_mut();
|
||||
if sel.context_menu.is_some() {
|
||||
@@ -3376,6 +3379,82 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::SelectSimilar => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
let added = self.tabs[i].scene.select_similar();
|
||||
self.command_line
|
||||
.push_output(&format!("Select Similar: {} added.", added));
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectOpen => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
// Seed filters from the first selected entity (if any) so
|
||||
// a right-click → Quick Select on a known object pre-fills
|
||||
// the panel — same idea as Select Similar but exposes the
|
||||
// pairing for editing.
|
||||
let mut type_filter: Option<String> = None;
|
||||
let mut layer_filter: Option<String> = None;
|
||||
if let Some(&h) = self.tabs[i].scene.selected.iter().next() {
|
||||
if let Some(e) = self.tabs[i].scene.document.get_entity(h) {
|
||||
use crate::entities::traits::entity_type_name;
|
||||
type_filter = Some(entity_type_name(e).to_string());
|
||||
layer_filter = Some(e.as_entity().layer().to_string());
|
||||
}
|
||||
}
|
||||
self.qselect = Some(crate::app::QSelectState {
|
||||
type_filter,
|
||||
layer_filter,
|
||||
append: false,
|
||||
});
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectClose => {
|
||||
self.qselect = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectSetType(t) => {
|
||||
if let Some(state) = self.qselect.as_mut() {
|
||||
state.type_filter = t;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectSetLayer(l) => {
|
||||
if let Some(state) = self.qselect.as_mut() {
|
||||
state.layer_filter = l;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectSetAppend(b) => {
|
||||
if let Some(state) = self.qselect.as_mut() {
|
||||
state.append = b;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::QSelectApply => {
|
||||
let Some(state) = self.qselect.take() else {
|
||||
return Task::none();
|
||||
};
|
||||
let i = self.active_tab;
|
||||
let matched = self.tabs[i].scene.qselect(
|
||||
state.type_filter.as_deref(),
|
||||
state.layer_filter.as_deref(),
|
||||
state.append,
|
||||
);
|
||||
self.command_line
|
||||
.push_output(&format!("QSELECT: {} object(s) selected.", matched));
|
||||
self.refresh_properties();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
// ── Properties panel messages ─────────────────────────────────
|
||||
Message::PropSelectionGroupChanged(group) => {
|
||||
self.tabs[self.active_tab].properties.selected_group = Some(group);
|
||||
|
||||
+152
@@ -910,6 +910,14 @@ impl OpenCADStudio {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let qselect_layer: Element<'_, Message> = if let Some(state) = &self.qselect {
|
||||
let types = tab.scene.entity_type_names_in_layout();
|
||||
let layers = tab.scene.entity_layers_in_layout();
|
||||
qselect_overlay(state, &types, &layers)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let open_progress_layer: Element<'_, Message> = if let Some(p) = &self.opening {
|
||||
crate::ui::open_progress::view(p, iced::time::Instant::now())
|
||||
} else {
|
||||
@@ -923,6 +931,7 @@ impl OpenCADStudio {
|
||||
scale_layer,
|
||||
dropdown_layer,
|
||||
layout_ctx_layer,
|
||||
qselect_layer,
|
||||
open_progress_layer,
|
||||
]
|
||||
.into()
|
||||
@@ -1384,11 +1393,13 @@ fn viewport_context_menu_overlay(
|
||||
Message::Command("COPY".to_string()),
|
||||
));
|
||||
items.push(sep());
|
||||
items.push(item("Select Similar".to_string(), Message::SelectSimilar));
|
||||
}
|
||||
items.push(item(
|
||||
"Select All".to_string(),
|
||||
Message::Command("SELECTALL".to_string()),
|
||||
));
|
||||
items.push(item("Quick Select...".to_string(), Message::QSelectOpen));
|
||||
items.push(item(
|
||||
"Zoom Extents".to_string(),
|
||||
Message::Command("ZOOM".to_string()),
|
||||
@@ -1503,6 +1514,147 @@ fn layout_context_menu_overlay(name: &str) -> Element<'_, Message> {
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
||||
// ── Quick Select panel ─────────────────────────────────────────────────────
|
||||
|
||||
const QSELECT_ANY: &str = "(Any)";
|
||||
|
||||
/// Floating panel for the Quick Select feature. Two pick_lists (object
|
||||
/// type, layer) filter the candidate set down to entities matching both;
|
||||
/// "(Any)" in either slot skips that filter. The panel returns to the
|
||||
/// stored state on every render, so editing one filter doesn't reset the
|
||||
/// other. Outside-click and Cancel both dismiss without applying.
|
||||
fn qselect_overlay<'a>(
|
||||
state: &'a crate::app::QSelectState,
|
||||
types: &[&'static str],
|
||||
layers: &[String],
|
||||
) -> Element<'a, Message> {
|
||||
use iced::widget::{checkbox, pick_list};
|
||||
const BG: Color = Color { r: 0.12, g: 0.12, b: 0.12, a: 0.98 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const TEXT: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const BTN_OK: Color = Color { r: 0.22, g: 0.42, b: 0.68, a: 1.0 };
|
||||
const BTN_OK_HOV: Color = Color { r: 0.30, g: 0.52, b: 0.80, a: 1.0 };
|
||||
const BTN_BG: Color = Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 };
|
||||
const BTN_HOV: Color = Color { r: 0.30, g: 0.30, b: 0.30, a: 1.0 };
|
||||
|
||||
let mut type_options: Vec<String> = vec![QSELECT_ANY.to_string()];
|
||||
type_options.extend(types.iter().map(|s| (*s).to_string()));
|
||||
let mut layer_options: Vec<String> = vec![QSELECT_ANY.to_string()];
|
||||
layer_options.extend(layers.iter().cloned());
|
||||
|
||||
let type_sel = state
|
||||
.type_filter
|
||||
.clone()
|
||||
.unwrap_or_else(|| QSELECT_ANY.to_string());
|
||||
let layer_sel = state
|
||||
.layer_filter
|
||||
.clone()
|
||||
.unwrap_or_else(|| QSELECT_ANY.to_string());
|
||||
|
||||
let label = |s: &'static str| text(s).size(12).color(TEXT).width(iced::Length::Fixed(90.0));
|
||||
|
||||
let btn = |lbl: &'static str, msg: Message, base: Color, hov: Color| {
|
||||
button(text(lbl).size(12).color(TEXT))
|
||||
.on_press(msg)
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(
|
||||
if matches!(st, button::Status::Hovered | button::Status::Pressed) {
|
||||
hov
|
||||
} else {
|
||||
base
|
||||
},
|
||||
)),
|
||||
text_color: TEXT,
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.padding([4, 14])
|
||||
};
|
||||
|
||||
let panel_body = column![
|
||||
text("Quick Select").size(14).color(TEXT),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
label("Object type:"),
|
||||
pick_list(type_options, Some(type_sel), |s: String| {
|
||||
if s == QSELECT_ANY {
|
||||
Message::QSelectSetType(None)
|
||||
} else {
|
||||
Message::QSelectSetType(Some(s))
|
||||
}
|
||||
})
|
||||
.width(Fill),
|
||||
]
|
||||
.align_y(iced::Alignment::Center)
|
||||
.spacing(8),
|
||||
Space::new().height(6),
|
||||
row![
|
||||
label("Layer:"),
|
||||
pick_list(layer_options, Some(layer_sel), |s: String| {
|
||||
if s == QSELECT_ANY {
|
||||
Message::QSelectSetLayer(None)
|
||||
} else {
|
||||
Message::QSelectSetLayer(Some(s))
|
||||
}
|
||||
})
|
||||
.width(Fill),
|
||||
]
|
||||
.align_y(iced::Alignment::Center)
|
||||
.spacing(8),
|
||||
Space::new().height(10),
|
||||
row![
|
||||
checkbox(state.append)
|
||||
.on_toggle(Message::QSelectSetAppend)
|
||||
.size(14),
|
||||
Space::new().width(6),
|
||||
text("Append to current selection").size(12).color(TEXT),
|
||||
]
|
||||
.align_y(iced::Alignment::Center),
|
||||
Space::new().height(14),
|
||||
row![
|
||||
Space::new().width(Fill),
|
||||
btn("Cancel", Message::QSelectClose, BTN_BG, BTN_HOV),
|
||||
Space::new().width(8),
|
||||
btn("Apply", Message::QSelectApply, BTN_OK, BTN_OK_HOV),
|
||||
]
|
||||
.align_y(iced::Alignment::Center),
|
||||
]
|
||||
.spacing(0);
|
||||
|
||||
let panel = container(panel_body)
|
||||
.padding(16)
|
||||
.width(iced::Length::Fixed(360.0))
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(BG)),
|
||||
border: Border {
|
||||
color: BORDER,
|
||||
width: 1.0,
|
||||
radius: 6.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Outside-click catcher — fills the whole screen, sits below the
|
||||
// panel. The panel itself is rendered above and absorbs its own
|
||||
// clicks via standard widget event handling.
|
||||
let catcher = mouse_area(
|
||||
container(iced::widget::Space::new().width(Fill).height(Fill))
|
||||
.width(Fill)
|
||||
.height(Fill),
|
||||
)
|
||||
.on_press(Message::QSelectClose)
|
||||
.on_right_press(Message::QSelectClose);
|
||||
|
||||
let centered = container(iced::widget::opaque(panel))
|
||||
.center(Fill);
|
||||
|
||||
stack![catcher, centered].into()
|
||||
}
|
||||
|
||||
/// Content for the floating "Unsaved Changes" OS window.
|
||||
const SAVE_FORMAT_OPTIONS: &[&str] = &[
|
||||
"DWG 2018", "DWG 2013", "DWG 2010", "DWG 2007", "DWG 2004", "DWG 2000", "DWG R14", "DXF 2018",
|
||||
|
||||
@@ -92,6 +92,56 @@ pub trait TextContent {
|
||||
fn replace_text(&mut self, search: &str, rep: &str);
|
||||
}
|
||||
|
||||
/// Human-readable variant name used by Quick Select / Select Similar
|
||||
/// filtering. Stable across releases — the strings here are the canonical
|
||||
/// type identifiers throughout the UI.
|
||||
pub fn entity_type_name(et: &EntityType) -> &'static str {
|
||||
match et {
|
||||
EntityType::Point(_) => "Point",
|
||||
EntityType::Line(_) => "Line",
|
||||
EntityType::Circle(_) => "Circle",
|
||||
EntityType::Arc(_) => "Arc",
|
||||
EntityType::Ellipse(_) => "Ellipse",
|
||||
EntityType::Polyline(_) => "Polyline",
|
||||
EntityType::Polyline2D(_) => "Polyline2D",
|
||||
EntityType::Polyline3D(_) => "Polyline3D",
|
||||
EntityType::LwPolyline(_) => "LwPolyline",
|
||||
EntityType::Text(_) => "Text",
|
||||
EntityType::MText(_) => "MText",
|
||||
EntityType::Spline(_) => "Spline",
|
||||
EntityType::Dimension(_) => "Dimension",
|
||||
EntityType::Hatch(_) => "Hatch",
|
||||
EntityType::Solid(_) => "Solid",
|
||||
EntityType::Face3D(_) => "3DFace",
|
||||
EntityType::Insert(_) => "Insert",
|
||||
EntityType::Block(_) => "Block",
|
||||
EntityType::BlockEnd(_) => "BlockEnd",
|
||||
EntityType::Ray(_) => "Ray",
|
||||
EntityType::XLine(_) => "XLine",
|
||||
EntityType::Viewport(_) => "Viewport",
|
||||
EntityType::AttributeDefinition(_) => "AttributeDefinition",
|
||||
EntityType::AttributeEntity(_) => "Attribute",
|
||||
EntityType::Leader(_) => "Leader",
|
||||
EntityType::MultiLeader(_) => "MultiLeader",
|
||||
EntityType::MLine(_) => "MLine",
|
||||
EntityType::Mesh(_) => "Mesh",
|
||||
EntityType::RasterImage(_) => "RasterImage",
|
||||
EntityType::Solid3D(_) => "Solid3D",
|
||||
EntityType::Region(_) => "Region",
|
||||
EntityType::Body(_) => "Body",
|
||||
EntityType::Table(_) => "Table",
|
||||
EntityType::Tolerance(_) => "Tolerance",
|
||||
EntityType::PolyfaceMesh(_) => "PolyfaceMesh",
|
||||
EntityType::Wipeout(_) => "Wipeout",
|
||||
EntityType::Shape(_) => "Shape",
|
||||
EntityType::Underlay(_) => "Underlay",
|
||||
EntityType::Seqend(_) => "Seqend",
|
||||
EntityType::Ole2Frame(_) => "Ole2Frame",
|
||||
EntityType::PolygonMesh(_) => "PolygonMesh",
|
||||
EntityType::Unknown(_) => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EntityTypeOps {
|
||||
fn to_truck_entity(&self, document: &CadDocument) -> Option<TruckEntity>;
|
||||
fn grips(&self) -> Vec<GripDef>;
|
||||
|
||||
@@ -3713,6 +3713,118 @@ impl Scene {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Iterates every entity owned by the current layout's block-record.
|
||||
/// Returns an empty vec when the block-record is missing or holds no
|
||||
/// entity handles (legacy DXF without group-code 330 — we err on the
|
||||
/// side of "no candidates" instead of scanning the whole document, so
|
||||
/// model-block entities don't leak into a paper-layout selection).
|
||||
fn current_layout_entity_handles(&self) -> Vec<Handle> {
|
||||
let block = self.current_layout_block_handle();
|
||||
self.document
|
||||
.block_records
|
||||
.iter()
|
||||
.find(|br| br.handle == block)
|
||||
.map(|br| br.entity_handles.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Extends the current selection with every entity in the active
|
||||
/// layout that matches one of the selected entities by `(variant,
|
||||
/// layer)`. The seed selection stays selected. No-op when nothing is
|
||||
/// selected. Returns the number of newly-added entities.
|
||||
pub fn select_similar(&mut self) -> usize {
|
||||
use crate::entities::traits::entity_type_name;
|
||||
if self.selected.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let pairs: std::collections::HashSet<(&'static str, String)> = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|h| self.document.get_entity(*h))
|
||||
.map(|e| (entity_type_name(e), e.as_entity().layer().to_string()))
|
||||
.collect();
|
||||
let handles = self.current_layout_entity_handles();
|
||||
let mut added = 0;
|
||||
for h in handles {
|
||||
if self.selected.contains(&h) {
|
||||
continue;
|
||||
}
|
||||
if let Some(e) = self.document.get_entity(h) {
|
||||
let key = (entity_type_name(e), e.as_entity().layer().to_string());
|
||||
if pairs.contains(&key) {
|
||||
self.selected.insert(h);
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if added > 0 {
|
||||
self.bump_geometry();
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
/// Replaces (or extends, when `append` is true) the current selection
|
||||
/// with every entity in the active layout that matches the optional
|
||||
/// `type_name` and `layer` filters. A `None` filter means "any".
|
||||
/// Returns the number of matching entities (i.e. the post-call
|
||||
/// selection size if `append` was false, or the new total if it was
|
||||
/// true and previously-selected matches are not double-counted).
|
||||
pub fn qselect(
|
||||
&mut self,
|
||||
type_name: Option<&str>,
|
||||
layer: Option<&str>,
|
||||
append: bool,
|
||||
) -> usize {
|
||||
use crate::entities::traits::entity_type_name;
|
||||
if !append {
|
||||
self.selected.clear();
|
||||
}
|
||||
let handles = self.current_layout_entity_handles();
|
||||
let mut matched = 0;
|
||||
for h in handles {
|
||||
let Some(e) = self.document.get_entity(h) else {
|
||||
continue;
|
||||
};
|
||||
let type_ok = type_name.is_none_or(|t| entity_type_name(e) == t);
|
||||
let layer_ok = layer.is_none_or(|l| e.as_entity().layer() == l);
|
||||
if type_ok && layer_ok {
|
||||
self.selected.insert(h);
|
||||
matched += 1;
|
||||
}
|
||||
}
|
||||
self.bump_geometry();
|
||||
matched
|
||||
}
|
||||
|
||||
/// Returns the sorted set of entity-type names present in the active
|
||||
/// layout. Used to populate the Quick Select "Object type" dropdown
|
||||
/// with only the types that actually exist in the drawing.
|
||||
pub fn entity_type_names_in_layout(&self) -> Vec<&'static str> {
|
||||
use crate::entities::traits::entity_type_name;
|
||||
let mut names: std::collections::BTreeSet<&'static str> =
|
||||
std::collections::BTreeSet::new();
|
||||
for h in self.current_layout_entity_handles() {
|
||||
if let Some(e) = self.document.get_entity(h) {
|
||||
names.insert(entity_type_name(e));
|
||||
}
|
||||
}
|
||||
names.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Returns the sorted set of layer names referenced by entities in
|
||||
/// the active layout. Used to populate the Quick Select "Layer"
|
||||
/// dropdown.
|
||||
pub fn entity_layers_in_layout(&self) -> Vec<String> {
|
||||
let mut names: std::collections::BTreeSet<String> =
|
||||
std::collections::BTreeSet::new();
|
||||
for h in self.current_layout_entity_handles() {
|
||||
if let Some(e) = self.document.get_entity(h) {
|
||||
names.insert(e.as_entity().layer().to_string());
|
||||
}
|
||||
}
|
||||
names.into_iter().collect()
|
||||
}
|
||||
|
||||
// ── Erase ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub fn erase_entities(&mut self, handles: &[Handle]) {
|
||||
|
||||
Reference in new issue
Block a user