From b704a438729717a8d7e68322d4a216d0f13c5082 Mon Sep 17 00:00:00 2001 From: gianlucafiore Date: Fri, 7 Aug 2026 19:50:11 -0300 Subject: [PATCH] Fix TCOUNT numbering and placement options --- src/app/commands/dim.rs | 88 +++++++++++++++++----- src/command.rs | 161 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 19 deletions(-) diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 2d8435441..81a8680f9 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -425,22 +425,44 @@ impl OpenCADStudio { // TCOUNT [start] — prefix selected single-line text with sequential // numbers in reading order (top-to-bottom, then left-to-right). "TCOUNT" => { - use crate::command::SelectThenValueCommand; + use crate::command::TCountCommand; + let has_sel = !self.tabs[i].scene.selected_entities().is_empty(); - let c = SelectThenValueCommand::new( - "TCOUNT", - "TCOUNT starting number (Enter for 1):", - has_sel, - ); + let c = TCountCommand::new(has_sel); + self.command_line.push_info(&c.prompt()); self.tabs[i].active_cmd = Some(Box::new(c)); } cmd if cmd.starts_with("TCOUNT ") => { - let start: i64 = cmd - .split_whitespace() - .nth(1) + let mut args = cmd.split_whitespace().skip(1); + + let start: i64 = args + .next() .and_then(|s| s.parse().ok()) .unwrap_or(1); + + let increment: i64 = args + .next() + .and_then(|s| s.parse().ok()) + .unwrap_or(1); + + let placement = args + .next() + .unwrap_or("O") + .to_uppercase(); + + let placement = match placement.as_str() { + "O" | "OVERWRITE" => "O", + "P" | "PREFIX" => "P", + "S" | "SUFFIX" => "S", + _ => { + self.command_line.push_error( + "TCOUNT: placement must be Overwrite, Prefix, or Suffix.", + ); + return Some(Task::none()); + } + }; + let mut texts: Vec<(acadrust::Handle, f64, f64)> = self.tabs[i] .scene .selected_entities() @@ -452,19 +474,28 @@ impl OpenCADStudio { _ => None, }) .collect(); + if texts.is_empty() { self.command_line - .push_error(crate::t!("TCOUNT: select single-line text first.").as_ref()); + .push_error("TCOUNT: select single-line text first."); return Some(Task::none()); } - // Reading order: higher Y first, then smaller X. + + // Keep the existing reading order: + // higher Y first, then smaller X. texts.sort_by(|a, b| { b.2.partial_cmp(&a.2) .unwrap_or(std::cmp::Ordering::Equal) - .then(a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)) + .then( + a.1.partial_cmp(&b.1) + .unwrap_or(std::cmp::Ordering::Equal), + ) }); + self.push_undo_snapshot(i, "TCOUNT"); + let mut num = start; + for (h, _, _) in &texts { if let Some(acadrust::EntityType::Text(t)) = self.tabs[i] .scene @@ -472,20 +503,39 @@ impl OpenCADStudio { .entities_mut() .find(|e| e.common().handle == *h) { - t.value = format!("{num} {}", t.value); - num += 1; + t.value = match placement { + "P" => format!("{num} {}", t.value), + "S" => format!("{} {num}", t.value), + _ => num.to_string(), + }; + + num += increment; } } + self.tabs[i].dirty = true; + let changes: Vec<_> = texts .iter() - .map(|(handle, _, _)| (*handle, crate::scene::ChangeKind::Modified)) + .map(|(handle, _, _)| { + (*handle, crate::scene::ChangeKind::Modified) + }) .collect(); + self.tabs[i].scene.bump_entities(&changes); - self.command_line.push_output(crate::tf!( - "TCOUNT: numbered {} text object(s) from {start}.", - texts.len() - ).as_ref()); + + let placement_name = match placement { + "P" => "Prefix", + "S" => "Suffix", + _ => "Overwrite", + }; + + self.command_line.push_output(&format!( + "TCOUNT: numbered {} text object(s) from {} by {} ({placement_name}).", + texts.len(), + start, + increment, + )); } "MLEADER" => { diff --git a/src/command.rs b/src/command.rs index 9640eab10..330c9ffb0 100644 --- a/src/command.rs +++ b/src/command.rs @@ -873,7 +873,168 @@ impl CadCommand for SelectThenValueCommand { CmdResult::Dispatch(format!("{} ", self.name)) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum TCountStep { + Start, + Increment, + Placement, +} +/// Interactive front-end for TCOUNT. +/// +/// After gathering the text selection it asks for: +/// 1. starting number, +/// 2. increment, +/// 3. placement mode (Overwrite / Prefix / Suffix). +/// +/// The actual entity modification remains in the inline TCOUNT handler. +pub struct TCountCommand { + gathering: bool, + selected: Vec, + step: TCountStep, + start: i64, + increment: i64, +} + +impl TCountCommand { + pub fn new(has_selection: bool) -> Self { + Self { + gathering: !has_selection, + selected: Vec::new(), + step: TCountStep::Start, + start: 1, + increment: 1, + } + } + + fn dispatch(&self, placement: &str) -> CmdResult { + CmdResult::Dispatch(format!( + "TCOUNT {} {} {}", + self.start, self.increment, placement + )) + } +} + +impl CadCommand for TCountCommand { + fn name(&self) -> &'static str { + "TCOUNT" + } + + fn prompt(&self) -> String { + if self.gathering { + return "TCOUNT select text objects, then press Enter:".to_string(); + } + + match self.step { + TCountStep::Start => { + format!("TCOUNT starting number <{}>:", self.start) + } + TCountStep::Increment => { + format!("TCOUNT increment <{}>:", self.increment) + } + TCountStep::Placement => { + "TCOUNT placement [Overwrite/Prefix/Suffix] :".to_string() + } + } + } + + fn options(&self) -> Vec { + if self.gathering || self.step != TCountStep::Placement { + return Vec::new(); + } + + vec![ + CmdOption::new("Overwrite", "O"), + CmdOption::new("Prefix", "P"), + CmdOption::new("Suffix", "S"), + ] + } + + fn wants_text_input(&self) -> bool { + !self.gathering + } + + fn is_selection_gathering(&self) -> bool { + self.gathering + } + + fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { + self.selected = handles; + CmdResult::NeedPoint + } + + fn on_text_input(&mut self, text: &str) -> Option { + if self.gathering { + return None; + } + + let t = text.trim(); + + if t.is_empty() { + return None; + } + + match self.step { + TCountStep::Start => { + if let Ok(value) = t.parse::() { + self.start = value; + self.step = TCountStep::Increment; + } + Some(CmdResult::NeedPoint) + } + + TCountStep::Increment => { + if let Ok(value) = t.parse::() { + self.increment = value; + self.step = TCountStep::Placement; + } + Some(CmdResult::NeedPoint) + } + + TCountStep::Placement => { + let placement = match t.to_uppercase().as_str() { + "O" | "OVERWRITE" => "O", + "P" | "PREFIX" => "P", + "S" | "SUFFIX" => "S", + _ => return Some(CmdResult::NeedPoint), + }; + + Some(self.dispatch(placement)) + } + } + } + + fn on_point(&mut self, _pt: DVec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_enter(&mut self) -> CmdResult { + if self.gathering { + if self.selected.is_empty() { + return CmdResult::Cancel; + } + + self.gathering = false; + return CmdResult::NeedPoint; + } + + match self.step { + TCountStep::Start => { + self.start = 1; + self.step = TCountStep::Increment; + CmdResult::NeedPoint + } + + TCountStep::Increment => { + self.increment = 1; + self.step = TCountStep::Placement; + CmdResult::NeedPoint + } + + TCountStep::Placement => self.dispatch("O"), + } + } +} /// Generic front-end for a two-value command that operates on the current /// selection (POLYSOLID width + height on a selected polyline). Gathers a /// selection first when none is set, prompts for two values, and dispatches