Skip to main content

brep_app/
delete_selection.rs

1//! THE delete-selection command: what the Delete / Backspace key, the context
2//! bar's Delete entries and MCP `selection_delete` all run.
3//!
4//! One question — what does "delete the selection" mean right now? — answered
5//! once (`target`), then one dispatch (`apply`) to the delete each kind
6//! already has: the same call the row ✕ / menu Delete makes, so the same (no)
7//! confirmation and the same single undo step. The modes are exclusive, in the
8//! order a user is inside them: an open sketch owns the key, then a 3D sketch,
9//! an eCAD editor, an open drawing sheet, the PMI workbench; otherwise the 3D
10//! selection is an assembly constraint (label-picked), one component (its
11//! member solids picked in the view or its BOM row), or one feature (its
12//! history-tree row). A sheet or PMI mode with nothing selected deletes
13//! nothing — it never falls through to a feature the user cannot see.
14
15use crate::document::Documents;
16use brep_render::engine_state::EngineState;
17
18/// What the delete command acts on.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub enum DeleteTarget {
21    /// The open sketch's selected geometry / points / constraints.
22    Sketch,
23    /// The open 3D sketch's selection.
24    Sketch3d,
25    /// The eCAD editor on the central tile (its own selection).
26    Ecad(crate::workbench::ecad::Target),
27    /// A held object on the open drawing sheet (view, dimension, note…).
28    SheetObject(String),
29    /// The selected PMI annotation.
30    PmiAnnotation(String),
31    /// The label-selected assembly constraint.
32    Constraint(String),
33    /// One assembly component (its ACOMP feature).
34    Component(String),
35    /// One history feature.
36    Feature(String),
37}
38
39impl DeleteTarget {
40    /// The kind word MCP reports.
41    pub fn kind(&self) -> &'static str {
42        match self {
43            DeleteTarget::Sketch => "sketch",
44            DeleteTarget::Sketch3d => "sketch3d",
45            DeleteTarget::Ecad(_) => "ecad",
46            DeleteTarget::SheetObject(_) => "sheetObject",
47            DeleteTarget::PmiAnnotation(_) => "pmiAnnotation",
48            DeleteTarget::Constraint(_) => "constraint",
49            DeleteTarget::Component(_) => "component",
50            DeleteTarget::Feature(_) => "feature",
51        }
52    }
53
54    /// The id deleted, for the kinds that name one.
55    pub fn id(&self) -> Option<&str> {
56        match self {
57            DeleteTarget::SheetObject(id)
58            | DeleteTarget::PmiAnnotation(id)
59            | DeleteTarget::Constraint(id)
60            | DeleteTarget::Component(id)
61            | DeleteTarget::Feature(id) => Some(id),
62            _ => None,
63        }
64    }
65}
66
67/// What "delete the selection" means for `docs` now; `None` when nothing is
68/// deletable.
69pub fn target(docs: &Documents) -> Option<DeleteTarget> {
70    let engine = docs.engine();
71    if engine.ref_select_active() {
72        // A reference pick owns the selection: Delete never edits the model
73        // under a picking form.
74        return None;
75    }
76    if engine.sketch_mode() {
77        return Some(DeleteTarget::Sketch);
78    }
79    if engine.sketch3d_mode() {
80        return Some(DeleteTarget::Sketch3d);
81    }
82    if let Some(target) = crate::workbench::ecad::Target::of_workbench(&engine.settings.workbench) {
83        return Some(DeleteTarget::Ecad(target));
84    }
85    if engine.sheet_open().is_some() {
86        return engine.sheet_selected_object().map(|id| DeleteTarget::SheetObject(id.to_string()));
87    }
88    if engine.pmi_workbench_entered() {
89        return engine.pmi_selected_annotation().map(|id| DeleteTarget::PmiAnnotation(id.to_string()));
90    }
91    model_target(engine)
92}
93
94/// The 3D-model part of [`target`]: constraint, then component, then feature.
95pub(crate) fn model_target(engine: &EngineState) -> Option<DeleteTarget> {
96    let constraints_shown = crate::workbench::panel_visible(
97        &engine.settings.workbench,
98        crate::workbench::assembly::CONSTRAINTS_PANEL_ID,
99        &crate::workbench::ButtonState::of(engine),
100    );
101    if let Some(id) = engine.selected_constraint().filter(|_| constraints_shown) {
102        return Some(DeleteTarget::Constraint(id));
103    }
104    if let Some(id) = crate::panels::context_bar::component_action_target_of(engine) {
105        return Some(DeleteTarget::Component(id));
106    }
107    engine.selected_feature().map(|id| DeleteTarget::Feature(id.to_string()))
108}
109
110/// Delete `target` through the delete its kind already has. `Err` carries the
111/// refusal the row action would have shown.
112pub fn apply(docs: &mut Documents, target: &DeleteTarget) -> Result<(), String> {
113    match target {
114        DeleteTarget::Ecad(target) => {
115            docs.active_mut().ecad.selection_key(*target, true);
116            Ok(())
117        }
118        other => apply_engine(docs.engine_mut(), other),
119    }
120}
121
122/// [`apply`] for every kind the engine alone holds (all but eCAD, which lives
123/// on the document) — what an engine-only surface such as the context bar
124/// calls, so its Delete entries are this command too.
125pub(crate) fn apply_engine(engine: &mut EngineState, target: &DeleteTarget) -> Result<(), String> {
126    match target {
127        DeleteTarget::Sketch => {
128            engine.sketch_delete_selection();
129            Ok(())
130        }
131        DeleteTarget::Sketch3d => {
132            engine.sketch3d_delete_selection();
133            Ok(())
134        }
135        DeleteTarget::Ecad(_) => Err("the eCAD editor's selection is deleted in its editor".into()),
136        DeleteTarget::SheetObject(id) => crate::panels::sheets::delete_held_object(engine, id),
137        DeleteTarget::PmiAnnotation(id) => crate::panels::pmi::delete_annotation(engine, id),
138        DeleteTarget::Constraint(id) => {
139            let result = engine.assembly_remove_constraint(id);
140            engine.constraint_deselect();
141            result
142        }
143        DeleteTarget::Component(id) => {
144            if id.contains(':') {
145                return Err(format!(
146                    "'{id}' is a part inside a sub-assembly: open that sub-assembly to delete it"
147                ));
148            }
149            crate::panels::component_actions::run_component_action(
150                engine,
151                crate::panels::component_actions::ComponentAction::Delete,
152                id,
153            );
154            Ok(())
155        }
156        DeleteTarget::Feature(id) => {
157            engine.delete_feature(id);
158            Ok(())
159        }
160    }
161}
162
163/// [`target`] then [`apply`]; `Ok(None)` when nothing was deletable.
164pub fn delete_selection(docs: &mut Documents) -> Result<Option<DeleteTarget>, String> {
165    let Some(target) = target(docs) else {
166        return Ok(None);
167    };
168    apply(docs, &target)?;
169    Ok(Some(target))
170}
171