diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index ce590d73e..9a1d3f220 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -241,7 +241,7 @@ impl OpenCADStudio { } CmdResult::BatchCopy(handles, transforms) => { let label = self.history_label_from_active_cmd(i, "ARRAY"); - self.push_undo_snapshot(i, label); + self.push_undo_snapshot(i, label.clone()); let count = transforms.len(); for t in &transforms { self.tabs[i].scene.copy_entities(&handles, t); @@ -251,8 +251,9 @@ impl OpenCADStudio { self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; self.restore_pre_cmd_tangent(); + let noun = if count == 1 { "copy" } else { "copies" }; self.command_line - .push_output(&format!("ARRAY: {count} copies created.")); + .push_output(&format!("{label}: {count} {noun} created.")); self.refresh_properties(); } CmdResult::ReplaceMany(replacements, additions) => { diff --git a/src/modules/home/modify/mirror.rs b/src/modules/home/modify/mirror.rs index e6a4f8358..cb0c65c81 100644 --- a/src/modules/home/modify/mirror.rs +++ b/src/modules/home/modify/mirror.rs @@ -3,7 +3,10 @@ // Command: MIRROR (MI) // Requires at least one entity selected. // Step 1: pick first mirror-line point -// Step 2: pick second mirror-line point → mirrors +// Step 2: pick second mirror-line point +// Step 3: "Erase source objects? [Yes/No] " +// No → keep the original, add a mirrored copy +// Yes → flip the original in place (no copy kept) use acadrust::Handle; use glam::Vec3; @@ -24,6 +27,8 @@ pub fn tool() -> ToolDef { enum Step { P1, P2(Vec3), + /// Both mirror-line points fixed; waiting on the erase-source answer. + AskErase { p1: Vec3, p2: Vec3 }, } pub struct MirrorCommand { @@ -57,6 +62,7 @@ impl CadCommand for MirrorCommand { "MIRROR Specify second point [p1={:.2},{:.2}]:", p1.x, p1.y ), + Step::AskErase { .. } => "MIRROR Erase source objects? [Yes/No] :".to_string(), } } @@ -67,40 +73,80 @@ impl CadCommand for MirrorCommand { CmdResult::NeedPoint } Step::P2(p1) => { - let p1 = *p1; - CmdResult::TransformSelected( - self.handles.clone(), - EntityTransform::Mirror { p1, p2: pt }, - ) + self.step = Step::AskErase { p1: *p1, p2: pt }; + CmdResult::NeedPoint } + // Second point is fixed; further clicks ignored until the + // erase-source question is answered via the command line. + Step::AskErase { .. } => CmdResult::NeedPoint, } } fn on_enter(&mut self) -> CmdResult { - CmdResult::Cancel + // Enter at the erase prompt accepts the default (No → keep source). + match &self.step { + Step::AskErase { p1, p2 } => self.finish(*p1, *p2, false), + _ => CmdResult::Cancel, + } } fn on_escape(&mut self) -> CmdResult { CmdResult::Cancel } + fn wants_text_input(&self) -> bool { + matches!(self.step, Step::AskErase { .. }) + } + + fn on_text_input(&mut self, text: &str) -> Option { + let Step::AskErase { p1, p2 } = &self.step else { + return None; + }; + let (p1, p2) = (*p1, *p2); + let t = text.trim().to_ascii_lowercase(); + let erase = match t.as_str() { + "y" | "yes" => true, + // Empty input (bare Enter) or an explicit No keeps the source. + "" | "n" | "no" => false, + // Unrecognised input: re-ask without committing. + _ => return Some(CmdResult::NeedPoint), + }; + Some(self.finish(p1, p2, erase)) + } + fn on_preview_wires(&mut self, pt: Vec3) -> Vec { - let Step::P2(p1) = &self.step else { - return vec![]; + // While picking the second point the ghost tracks the cursor; once it + // is fixed (erase prompt) the ghost freezes at the chosen axis. + let (p1, p2) = match &self.step { + Step::P2(p1) => (*p1, pt), + Step::AskErase { p1, p2 } => (*p1, *p2), + _ => return vec![], }; - let p1 = *p1; // Mirrored ghosts of all selected objects. let mut out: Vec = self .wire_models .iter() - .map(|w| w.mirrored(p1, pt)) + .map(|w| w.mirrored(p1, p2)) .collect(); // Mirror-axis line (rubber-band). out.push(WireModel::solid( "rubber_band".into(), - vec![[p1.x, p1.y, p1.z], [pt.x, pt.y, pt.z]], + vec![[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]], WireModel::CYAN, false, )); out } } + +impl MirrorCommand { + /// Commit the mirror. `erase` true flips the originals in place; false + /// keeps them and adds a mirrored copy. Either way the command ends. + fn finish(&self, p1: Vec3, p2: Vec3, erase: bool) -> CmdResult { + let xform = EntityTransform::Mirror { p1, p2 }; + if erase { + CmdResult::TransformSelected(self.handles.clone(), xform) + } else { + CmdResult::BatchCopy(self.handles.clone(), vec![xform]) + } + } +}