diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 5b5e28ae4..370a4d5e9 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -366,6 +366,24 @@ impl OpenCADStudio { self.commit_undo_delta(i, pd); } } + CmdResult::CommitEntities(entities) => { + let label = self.history_label_from_active_cmd(i, "ENTITY"); + let delta_safe = entities + .iter() + .all(|entity| self.delta_add_safe(i, entity)); + let pending = self.begin_undo(i, label, entities.len(), delta_safe); + for entity in entities { + self.commit_entity(entity); + } + self.tabs[i].dirty = true; + let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt()); + if let Some(p) = prompt { + self.command_line.push_info(&p); + } + if let Some(pd) = pending { + self.commit_undo_delta(i, pd); + } + } CmdResult::TransformSelected(handles, transform) => { let label = self.history_label_from_active_cmd(i, "MOVE"); // A move/rotate/scale/mirror mutates only the selected entities diff --git a/src/command.rs b/src/command.rs index 841f56318..24b78fdc0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -806,6 +806,8 @@ pub enum CmdResult { Preview(WireModel), /// Commit an acadrust entity to the document; keep the command active. CommitEntity(EntityType), + /// Commit several acadrust entities in one undo step; keep the command active. + CommitEntities(Vec), /// Commit an acadrust entity to the document and end the command. CommitAndExit(EntityType), /// Commit a Model-tab 3D solid: the acadrust entity (for selection / diff --git a/src/modules/draw/modify/offset.rs b/src/modules/draw/modify/offset.rs index 36cb64652..dd961f217 100644 --- a/src/modules/draw/modify/offset.rs +++ b/src/modules/draw/modify/offset.rs @@ -7,7 +7,8 @@ // Steps: // 1. Text input: "Specify offset distance :" → enter float or Enter for default // 2. Pick object to offset (Line, Arc, Circle, LwPolyline) -// 3. Pick a point on the side to offset toward +// 3. Pick a point on the side to offset toward, or choose Multiple +// to keep offsetting the newly created result at the same distance use std::f64::consts::TAU; @@ -687,6 +688,9 @@ enum Step { /// True when the targets came from the pre-selection: the commit ends /// the command instead of looping back for another pick. from_selection: bool, + /// Keep the side-pick step active and use each new result as the source + /// for the next offset. + multiple: bool, }, } @@ -747,6 +751,7 @@ impl OffsetCommand { targets: std::mem::take(&mut self.preselected), locked, from_selection: true, + multiple: false, }; } CmdResult::NeedPoint @@ -767,15 +772,28 @@ impl CadCommand for OffsetCommand { Step::SelectObject { .. } => { "OFFSET Select object to offset (Enter to finish):".into() } - Step::PickSide { targets, locked, .. } => { + Step::PickSide { + targets, + locked, + multiple, + .. + } => { let n = if targets.len() > 1 { format!(" ({} objects)", targets.len()) } else { String::new() }; - match locked { - Some(d) => format!("OFFSET{n} Click side [distance {d:.4}]:"), - None => format!("OFFSET{n} Click through point:"), + match (locked, multiple) { + (Some(d), false) => { + format!("OFFSET{n} Click side or [Multiple] [distance {d:.4}]:") + } + (Some(d), true) => { + format!("OFFSET{n} Multiple Click next side [distance {d:.4}]:") + } + (None, false) => { + format!("OFFSET{n} Click through point or [Multiple]:") + } + (None, true) => format!("OFFSET{n} Multiple Click next through point:"), } } } @@ -790,6 +808,9 @@ impl CadCommand for OffsetCommand { defaults::get_offset_dist() )), ], + Step::PickSide { + multiple: false, .. + } => vec![crate::command::CmdOption::new("Multiple", "M")], _ => Vec::new(), } } @@ -819,6 +840,7 @@ impl CadCommand for OffsetCommand { targets: vec![e], locked, from_selection: false, + multiple: false, }; CmdResult::NeedPoint } @@ -866,7 +888,15 @@ impl CadCommand for OffsetCommand { } Some(CmdResult::NeedPoint) } - Step::PickSide { locked, .. } => { + Step::PickSide { + locked, + multiple, + .. + } => { + if t.eq_ignore_ascii_case("m") || t.eq_ignore_ascii_case("multiple") { + *multiple = true; + return Some(CmdResult::NeedPoint); + } if !t.is_empty() { if let Ok(d) = t.parse::() { let d = d.abs().max(1e-9); @@ -902,12 +932,13 @@ impl CadCommand for OffsetCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { - let (locked, targets, from_selection) = match &self.step { + let (locked, targets, from_selection, multiple) = match &self.step { Step::PickSide { locked, targets, from_selection, - } => (*locked, targets.clone(), *from_selection), + multiple, + } => (*locked, targets.clone(), *from_selection, *multiple), _ => return CmdResult::NeedPoint, }; // Each target offsets by its own through-distance (or the locked @@ -925,6 +956,22 @@ impl CadCommand for OffsetCommand { if news.is_empty() { return CmdResult::NeedPoint; } + if multiple { + // Multiple mode chains from the result just created, matching + // OFFSET's repeated fixed-distance behavior (parallel/concentric + // series instead of duplicate copies at the first offset). + self.step = Step::PickSide { + targets: news.clone(), + locked, + from_selection, + multiple: true, + }; + return if news.len() == 1 { + CmdResult::CommitEntity(news.pop().unwrap()) + } else { + CmdResult::CommitEntities(news) + }; + } if from_selection { // Pre-selection commit ends the command in one undo step. return CmdResult::ReplaceMany(vec![], news);