diff --git a/src/app/commands.rs b/src/app/commands.rs index 16e43971d..3ea63dc3e 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -37,7 +37,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYOFF"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -62,7 +62,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYFRZ"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -87,7 +87,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYLCK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -110,7 +110,7 @@ impl H7CAD { "LAYMCUR" => { let entities = self.tabs[i].scene.selected_entities(); if entities.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYMCUR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -156,7 +156,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYULK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -177,7 +177,7 @@ impl H7CAD { } "LAYMATCH"|"LAYMCH" => { - use crate::command::laymatch::LayMatchCommand; + use crate::modules::home::layers::match_layer::LayMatchCommand; let dest: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); let cmd = LayMatchCommand::new(dest); @@ -186,7 +186,7 @@ impl H7CAD { } "MATCHPROP"|"MA" => { - use crate::command::matchprop::MatchPropCommand; + use crate::modules::home::properties::match_prop::MatchPropCommand; self.tabs[i].scene.deselect_all(); let cmd = MatchPropCommand::new(); self.command_line.push_info(&cmd.prompt()); @@ -197,13 +197,13 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("GROUP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let auto_name = super::helpers::next_group_auto_name(&self.tabs[i].scene); - use crate::command::group::GroupCommand; + use crate::modules::home::groups::group::GroupCommand; let cmd = GroupCommand::new(handles, auto_name); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -214,7 +214,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::ungroup::UngroupCommand; + use crate::modules::home::groups::ungroup::UngroupCommand; let cmd = UngroupCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -234,7 +234,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("COPYCLIP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -256,7 +256,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("CUTCLIP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -285,7 +285,7 @@ impl H7CAD { self.command_line.push_error("Clipboard is empty."); } else { let wires = self.tabs[i].scene.wires_for_entities(&self.clipboard); - use crate::command::paste::PasteCommand; + use crate::modules::home::clipboard::paste::PasteCommand; let cmd = PasteCommand::new(wires, self.clipboard_centroid); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -296,7 +296,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("BLOCK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -480,7 +480,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("MOVE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -497,7 +497,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("COPY"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -514,7 +514,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ROTATE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -590,7 +590,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("SCALE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -607,7 +607,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("MIRROR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -624,7 +624,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ERASE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -697,7 +697,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("STRETCH"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -727,7 +727,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYRECT"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -744,7 +744,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYPOLAR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -761,7 +761,7 @@ impl H7CAD { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYPATH"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); @@ -798,7 +798,7 @@ impl H7CAD { let entities: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().collect(); if entities.is_empty() { - use crate::command::select::SelectObjectsCommand; + use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("EXPLODE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); diff --git a/src/command/laymatch.rs b/src/command/laymatch.rs deleted file mode 100644 index 0ad447f62..000000000 --- a/src/command/laymatch.rs +++ /dev/null @@ -1,69 +0,0 @@ -// LayMatchCommand — two-phase layer match. -// -// Phase 1: user selects destination objects (whose layer will be changed). -// Phase 2: user selects a single source object (whose layer will be applied). - -use acadrust::Handle; -use glam::Vec3; - -use crate::command::{CadCommand, CmdResult}; -use crate::scene::wire_model::WireModel; - -pub struct LayMatchCommand { - dest_handles: Vec, -} - -impl LayMatchCommand { - pub fn new(dest: Vec) -> Self { - Self { dest_handles: dest } - } -} - -impl CadCommand for LayMatchCommand { - fn name(&self) -> &'static str { - "LAYMATCH" - } - - fn prompt(&self) -> String { - if self.dest_handles.is_empty() { - "LAYMATCH Select objects to change layer:".into() - } else { - "LAYMATCH Select source object to match layer from:".into() - } - } - - fn is_selection_gathering(&self) -> bool { - true - } - - fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { - if handles.is_empty() { - return CmdResult::NeedPoint; - } - if self.dest_handles.is_empty() { - // Phase 1 complete — store destinations, move to phase 2. - self.dest_handles = handles; - CmdResult::NeedPoint - } else { - // Phase 2 complete — first handle is the source object. - let src = handles[0]; - CmdResult::MatchEntityLayer { - dest: std::mem::take(&mut self.dest_handles), - src, - } - } - } - - fn on_point(&mut self, _pt: Vec3) -> CmdResult { - CmdResult::NeedPoint - } - fn on_enter(&mut self) -> CmdResult { - CmdResult::Cancel - } - fn on_escape(&mut self) -> CmdResult { - CmdResult::Cancel - } - fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { - vec![] - } -} diff --git a/src/command/matchprop.rs b/src/command/matchprop.rs deleted file mode 100644 index 57390e7f7..000000000 --- a/src/command/matchprop.rs +++ /dev/null @@ -1,82 +0,0 @@ -// MatchPropCommand — two-phase match properties. -// -// Phase 1: user picks the SOURCE object (single entity pick). -// Phase 2: user selects DESTINATION objects (multi-select, Enter to finish). -// Result: CmdResult::MatchProperties { dest, src } - -use acadrust::Handle; -use glam::Vec3; - -use crate::command::{CadCommand, CmdResult}; -use crate::scene::wire_model::WireModel; - -pub struct MatchPropCommand { - /// Source handle; NULL until phase 1 is complete. - src: Handle, -} - -impl MatchPropCommand { - pub fn new() -> Self { - Self { src: Handle::NULL } - } - - fn phase1_done(&self) -> bool { - !self.src.is_null() - } -} - -impl CadCommand for MatchPropCommand { - fn name(&self) -> &'static str { - "MATCHPROP" - } - - fn prompt(&self) -> String { - if !self.phase1_done() { - "MATCHPROP Select source object:".into() - } else { - "MATCHPROP Select destination objects (Enter to apply):".into() - } - } - - // ── Phase 1: single entity pick ─────────────────────────────────────── - - fn needs_entity_pick(&self) -> bool { - !self.phase1_done() - } - - fn on_entity_pick(&mut self, handle: Handle, _pt: Vec3) -> CmdResult { - if handle.is_null() { - return CmdResult::NeedPoint; // nothing hit, keep waiting - } - self.src = handle; - CmdResult::NeedPoint // move to phase 2 (selection gathering) - } - - // ── Phase 2: destination selection gathering ────────────────────────── - - fn is_selection_gathering(&self) -> bool { - self.phase1_done() - } - - fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { - if handles.is_empty() { - return CmdResult::NeedPoint; - } - CmdResult::MatchProperties { - dest: handles, - src: self.src, - } - } - - fn on_enter(&mut self) -> CmdResult { - CmdResult::Cancel - } - - fn on_point(&mut self, _pt: Vec3) -> CmdResult { - CmdResult::NeedPoint - } - - fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { - vec![] - } -} diff --git a/src/command/mod.rs b/src/command/mod.rs index 0cfa0d9a3..3a7b0b051 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -5,13 +5,6 @@ // viewport events from main.rs and returns `CmdResult` tokens that tell // the host what to do next. -pub mod group; -pub mod laymatch; -pub mod matchprop; -pub mod paste; -pub mod select; -pub mod ungroup; - use crate::scene::hatch_model::HatchModel; use crate::scene::wire_model::WireModel; use acadrust::{EntityType, Handle}; diff --git a/src/command/paste.rs b/src/command/paste.rs deleted file mode 100644 index 1200e61ec..000000000 --- a/src/command/paste.rs +++ /dev/null @@ -1,50 +0,0 @@ -// PasteCommand — picks one insertion point then fires CmdResult::PasteClipboard. -// -// Holds pre-computed wire previews (from clipboard entities) and the centroid -// so it can show a translated rubber-band preview while the user moves the mouse. - -use acadrust::Handle; -use glam::Vec3; - -use crate::command::{CadCommand, CmdResult}; -use crate::scene::wire_model::WireModel; - -pub struct PasteCommand { - /// Wire models of the clipboard entities (used for preview). - wires: Vec, - /// Centroid of the clipboard entities (offset origin for translation). - centroid: Vec3, -} - -impl PasteCommand { - pub fn new(wires: Vec, centroid: Vec3) -> Self { - Self { wires, centroid } - } -} - -impl CadCommand for PasteCommand { - fn name(&self) -> &'static str { - "PASTECLIP" - } - - fn prompt(&self) -> String { - "PASTECLIP Pick insertion point:".into() - } - - fn on_point(&mut self, pt: Vec3) -> CmdResult { - CmdResult::PasteClipboard { base_pt: pt } - } - - fn on_enter(&mut self) -> CmdResult { - CmdResult::Cancel - } - - fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { - vec![] - } - - fn on_preview_wires(&mut self, pt: Vec3) -> Vec { - let delta = pt - self.centroid; - self.wires.iter().map(|w| w.translated(delta)).collect() - } -} diff --git a/src/modules/home/clipboard/paste.rs b/src/modules/home/clipboard/paste.rs index 5b25b7d66..d12ebb240 100644 --- a/src/modules/home/clipboard/paste.rs +++ b/src/modules/home/clipboard/paste.rs @@ -1,3 +1,5 @@ +// Paste tool — ribbon definition + CadCommand implementation. + use crate::modules::{IconKind, ModuleEvent, ToolDef}; pub fn tool() -> ToolDef { @@ -8,3 +10,51 @@ pub fn tool() -> ToolDef { event: ModuleEvent::Command("PASTECLIP".to_string()), } } + +// ── CadCommand implementation ───────────────────────────────────────────── + +use acadrust::Handle; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; +use crate::scene::wire_model::WireModel; + +pub struct PasteCommand { + /// Wire models of the clipboard entities (used for preview). + wires: Vec, + /// Centroid of the clipboard entities (offset origin for translation). + centroid: Vec3, +} + +impl PasteCommand { + pub fn new(wires: Vec, centroid: Vec3) -> Self { + Self { wires, centroid } + } +} + +impl CadCommand for PasteCommand { + fn name(&self) -> &'static str { + "PASTECLIP" + } + + fn prompt(&self) -> String { + "PASTECLIP Pick insertion point:".into() + } + + fn on_point(&mut self, pt: Vec3) -> CmdResult { + CmdResult::PasteClipboard { base_pt: pt } + } + + fn on_enter(&mut self) -> CmdResult { + CmdResult::Cancel + } + + fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { + vec![] + } + + fn on_preview_wires(&mut self, pt: Vec3) -> Vec { + let delta = pt - self.centroid; + self.wires.iter().map(|w| w.translated(delta)).collect() + } +} diff --git a/src/command/group.rs b/src/modules/home/groups/group.rs similarity index 73% rename from src/command/group.rs rename to src/modules/home/groups/group.rs index 489eb2a4b..99b3c8e22 100644 --- a/src/command/group.rs +++ b/src/modules/home/groups/group.rs @@ -1,8 +1,17 @@ -// GroupCommand — text-input phase after objects are selected. -// -// Receives the pre-selected handles from dispatch_command and asks the user -// for a group name. Pressing Enter without typing uses the auto-generated name. -// Result: CmdResult::CreateGroup { handles, name } +// Group tool — ribbon definition + CadCommand implementation. + +use crate::modules::{IconKind, ModuleEvent, ToolDef}; + +pub fn tool() -> ToolDef { + ToolDef { + id: "GROUP", + label: "Group", + icon: IconKind::Svg(include_bytes!("../../../../assets/icons/group.svg")), + event: ModuleEvent::Command("GROUP".to_string()), + } +} + +// ── CadCommand implementation ───────────────────────────────────────────── use acadrust::Handle; use glam::Vec3; diff --git a/src/modules/home/groups/mod.rs b/src/modules/home/groups/mod.rs new file mode 100644 index 000000000..23bf5f74e --- /dev/null +++ b/src/modules/home/groups/mod.rs @@ -0,0 +1,2 @@ +pub mod group; +pub mod ungroup; diff --git a/src/command/ungroup.rs b/src/modules/home/groups/ungroup.rs similarity index 64% rename from src/command/ungroup.rs rename to src/modules/home/groups/ungroup.rs index b9dd6db15..774c0d757 100644 --- a/src/command/ungroup.rs +++ b/src/modules/home/groups/ungroup.rs @@ -1,6 +1,17 @@ -// UngroupCommand — selection gathering then dissolve all groups that -// contain any of the selected handles. -// Result: CmdResult::DeleteGroups { handles } +// Ungroup tool — ribbon definition + CadCommand implementation. + +use crate::modules::{IconKind, ModuleEvent, ToolDef}; + +pub fn tool() -> ToolDef { + ToolDef { + id: "UNGROUP", + label: "Ungroup", + icon: IconKind::Svg(include_bytes!("../../../../assets/icons/ungroup.svg")), + event: ModuleEvent::Command("UNGROUP".to_string()), + } +} + +// ── CadCommand implementation ───────────────────────────────────────────── use acadrust::Handle; use glam::Vec3; diff --git a/src/modules/home/layers/match_layer.rs b/src/modules/home/layers/match_layer.rs index 702f24e05..946378c7f 100644 --- a/src/modules/home/layers/match_layer.rs +++ b/src/modules/home/layers/match_layer.rs @@ -1,3 +1,5 @@ +// Match Layer tool — ribbon definition + CadCommand implementation. + use crate::modules::{IconKind, ModuleEvent, ToolDef}; pub fn tool() -> ToolDef { @@ -8,3 +10,70 @@ pub fn tool() -> ToolDef { event: ModuleEvent::Command("LAYMATCH".to_string()), } } + +// ── CadCommand implementation ───────────────────────────────────────────── + +use acadrust::Handle; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; +use crate::scene::wire_model::WireModel; + +pub struct LayMatchCommand { + dest_handles: Vec, +} + +impl LayMatchCommand { + pub fn new(dest: Vec) -> Self { + Self { dest_handles: dest } + } +} + +impl CadCommand for LayMatchCommand { + fn name(&self) -> &'static str { + "LAYMATCH" + } + + fn prompt(&self) -> String { + if self.dest_handles.is_empty() { + "LAYMATCH Select objects to change layer:".into() + } else { + "LAYMATCH Select source object to match layer from:".into() + } + } + + fn is_selection_gathering(&self) -> bool { + true + } + + fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { + if handles.is_empty() { + return CmdResult::NeedPoint; + } + if self.dest_handles.is_empty() { + // Phase 1 complete — store destinations, move to phase 2. + self.dest_handles = handles; + CmdResult::NeedPoint + } else { + // Phase 2 complete — first handle is the source object. + let src = handles[0]; + CmdResult::MatchEntityLayer { + dest: std::mem::take(&mut self.dest_handles), + src, + } + } + } + + fn on_point(&mut self, _pt: Vec3) -> CmdResult { + CmdResult::NeedPoint + } + fn on_enter(&mut self) -> CmdResult { + CmdResult::Cancel + } + fn on_escape(&mut self) -> CmdResult { + CmdResult::Cancel + } + fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { + vec![] + } +} diff --git a/src/modules/home/layers/mod.rs b/src/modules/home/layers/mod.rs index 4d73a9e11..e1038df68 100644 --- a/src/modules/home/layers/mod.rs +++ b/src/modules/home/layers/mod.rs @@ -6,5 +6,5 @@ pub(super) mod layon; pub(super) mod laythw; pub(super) mod layulk; pub(super) mod make_current; -pub(super) mod match_layer; +pub mod match_layer; pub(super) mod panel; diff --git a/src/modules/home/mod.rs b/src/modules/home/mod.rs index f6335a890..2f2d2bc2c 100644 --- a/src/modules/home/mod.rs +++ b/src/modules/home/mod.rs @@ -1,12 +1,14 @@ // Home module — Draw, Modify, Annotation and Layer tools. -mod clipboard; +pub mod clipboard; pub mod defaults; mod donate; pub mod draw; -mod layers; +pub mod groups; +pub mod layers; pub mod modify; -mod properties; +pub mod properties; +pub mod select; use crate::modules::{CadModule, RibbonGroup, RibbonItem}; @@ -27,9 +29,10 @@ impl CadModule for HomeModule { use donate; use draw::{arc, circle, ellipse, hatch, line, polyline, shapes}; use layers::{layfrz, laylck, layoff, layon, laythw, layulk, make_current, match_layer, panel}; + use groups::{group, ungroup}; use modify::{ - array, copy, delete, explode, fillet, group, mirror, offset, rotate, scale, stretch, - translate, trim, ungroup, + array, copy, delete, explode, fillet, mirror, offset, rotate, scale, stretch, + translate, trim, }; use properties::match_prop; diff --git a/src/modules/home/modify/group.rs b/src/modules/home/modify/group.rs deleted file mode 100644 index 16c8d84f3..000000000 --- a/src/modules/home/modify/group.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Group tool — ribbon definition. - -use crate::modules::{IconKind, ModuleEvent, ToolDef}; - -pub fn tool() -> ToolDef { - ToolDef { - id: "GROUP", - label: "Group", - icon: IconKind::Svg(include_bytes!("../../../../assets/icons/group.svg")), - event: ModuleEvent::Command("GROUP".to_string()), - } -} diff --git a/src/modules/home/modify/mod.rs b/src/modules/home/modify/mod.rs index 1e2d4e851..da0ea9431 100644 --- a/src/modules/home/modify/mod.rs +++ b/src/modules/home/modify/mod.rs @@ -4,7 +4,6 @@ pub mod delete; pub mod explode; mod extend; pub mod fillet; -pub mod group; pub mod mirror; pub mod offset; pub mod rotate; @@ -12,4 +11,3 @@ pub mod scale; pub mod stretch; pub mod translate; pub mod trim; -pub mod ungroup; diff --git a/src/modules/home/modify/ungroup.rs b/src/modules/home/modify/ungroup.rs deleted file mode 100644 index 8c4aa5934..000000000 --- a/src/modules/home/modify/ungroup.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Ungroup tool — ribbon definition. - -use crate::modules::{IconKind, ModuleEvent, ToolDef}; - -pub fn tool() -> ToolDef { - ToolDef { - id: "UNGROUP", - label: "Ungroup", - icon: IconKind::Svg(include_bytes!("../../../../assets/icons/ungroup.svg")), - event: ModuleEvent::Command("UNGROUP".to_string()), - } -} diff --git a/src/modules/home/properties/match_prop.rs b/src/modules/home/properties/match_prop.rs index 93bb8598e..51c12e9db 100644 --- a/src/modules/home/properties/match_prop.rs +++ b/src/modules/home/properties/match_prop.rs @@ -1,4 +1,4 @@ -// Match Properties tool — ribbon definition. +// Match Properties tool — ribbon definition + CadCommand implementation. use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -10,3 +10,82 @@ pub fn tool() -> ToolDef { event: ModuleEvent::Command("MATCHPROP".to_string()), } } + +// ── CadCommand implementation ───────────────────────────────────────────── + +use acadrust::Handle; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; +use crate::scene::wire_model::WireModel; + +pub struct MatchPropCommand { + /// Source handle; NULL until phase 1 is complete. + src: Handle, +} + +impl MatchPropCommand { + pub fn new() -> Self { + Self { src: Handle::NULL } + } + + fn phase1_done(&self) -> bool { + !self.src.is_null() + } +} + +impl CadCommand for MatchPropCommand { + fn name(&self) -> &'static str { + "MATCHPROP" + } + + fn prompt(&self) -> String { + if !self.phase1_done() { + "MATCHPROP Select source object:".into() + } else { + "MATCHPROP Select destination objects (Enter to apply):".into() + } + } + + // ── Phase 1: single entity pick ─────────────────────────────────────── + + fn needs_entity_pick(&self) -> bool { + !self.phase1_done() + } + + fn on_entity_pick(&mut self, handle: Handle, _pt: Vec3) -> CmdResult { + if handle.is_null() { + return CmdResult::NeedPoint; // nothing hit, keep waiting + } + self.src = handle; + CmdResult::NeedPoint // move to phase 2 (selection gathering) + } + + // ── Phase 2: destination selection gathering ────────────────────────── + + fn is_selection_gathering(&self) -> bool { + self.phase1_done() + } + + fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { + if handles.is_empty() { + return CmdResult::NeedPoint; + } + CmdResult::MatchProperties { + dest: handles, + src: self.src, + } + } + + fn on_enter(&mut self) -> CmdResult { + CmdResult::Cancel + } + + fn on_point(&mut self, _pt: Vec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_hover_entity(&mut self, _handle: Handle, _pt: Vec3) -> Vec { + vec![] + } +} diff --git a/src/modules/home/properties/mod.rs b/src/modules/home/properties/mod.rs index dc0468766..e877792c5 100644 --- a/src/modules/home/properties/mod.rs +++ b/src/modules/home/properties/mod.rs @@ -1 +1 @@ -pub(super) mod match_prop; +pub mod match_prop; diff --git a/src/command/select.rs b/src/modules/home/select.rs similarity index 100% rename from src/command/select.rs rename to src/modules/home/select.rs