From b16f44d87feb0733f14f04fcb9f189cbccad1133 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:55:01 +0200 Subject: [PATCH] Make DXF import symbol-name uniquing linear Fixes #3589 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/cad/src/import/build.rs | 25 +++++++++++++++++++------ crates/cad/src/import/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/crates/cad/src/import/build.rs b/crates/cad/src/import/build.rs index b729d8000..ff2e84bb8 100644 --- a/crates/cad/src/import/build.rs +++ b/crates/cad/src/import/build.rs @@ -3,7 +3,7 @@ //! dimensions, as groups), and colours, lineweights, linetypes and transparency resolved through //! layers and blocks. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::rc::Rc; use std::sync::Arc; @@ -52,6 +52,9 @@ pub(crate) struct Build<'d, 'a> { sensitive: HashMap, /// Symbol names by block and what it inherits (`None`: the block has no art). symbols: HashMap<(String, Inherit), Option>, + /// Names of the symbols made so far, and the next suffix to try for each label. + symbol_names: HashSet, + next_suffix: HashMap, /// Blocks being drawn, outermost first. stack: Vec, /// Document space per drawing unit, and its symbols' (no translation). @@ -108,6 +111,8 @@ impl<'d, 'a> Build<'d, 'a> { bounds: HashMap::new(), sensitive: HashMap::new(), symbols: HashMap::new(), + symbol_names: HashSet::new(), + next_suffix: HashMap::new(), stack: vec![], map: Affine::IDENTITY, symbol_map: Affine::IDENTITY, @@ -584,7 +589,8 @@ impl<'d, 'a> Build<'d, 'a> { let art = self.art(name, map, &key.1, depth); let made = art.map(|art| { let label = self.d.blocks.get(name).map_or(name, |b| b.name.as_str()); - let symbol = unique(label, |n| self.doc.symbols.iter().any(|s| s.name == n)); + let symbol = unique(label, &mut self.next_suffix, &self.symbol_names); + self.symbol_names.insert(symbol.clone()); self.doc.symbols.push(Symbol { name: symbol.clone(), art: Arc::new(art) }); symbol }); @@ -611,10 +617,17 @@ fn rgb_color([r, g, b]: [u8; 3]) -> Color { Color::rgb8(r, g, b) } -/// `base`, or `base 2`, `base 3`… when taken. -fn unique(base: &str, taken: impl Fn(&str) -> bool) -> String { - if !taken(base) { +/// `base`, or `base 2`, `base 3`… when taken; `next` remembers where each base left off. +fn unique(base: &str, next: &mut HashMap, taken: &HashSet) -> String { + if !taken.contains(base) { return base.to_string(); } - (2..).map(|i| format!("{base} {i}")).find(|n| !taken(n)).unwrap_or_else(|| base.to_string()) + let i = next.entry(base.to_string()).or_insert(2); + loop { + let name = format!("{base} {i}"); + *i += 1; + if !taken.contains(&name) { + return name; + } + } } diff --git a/crates/cad/src/import/tests.rs b/crates/cad/src/import/tests.rs index 895b70226..b22fdc4df 100644 --- a/crates/cad/src/import/tests.rs +++ b/crates/cad/src/import/tests.rs @@ -712,6 +712,27 @@ fn by_block_colours_make_a_symbol_per_look() { assert_eq!(doc.symbols.len(), 1); } +#[test] +fn many_by_block_looks_import_in_linear_time() { + let by_block: &[(i32, &str)] = &[(0, "LINE"), (8, "0"), (62, "0"), (10, "5"), (20, "5"), (11, "15"), (21, "5")]; + let n = 4000; + let mut inserts: Vec<(i32, &'static str)> = vec![]; + for i in 0..n { + let colour: &'static str = Box::leak((i + 1).to_string().into_boxed_str()); + inserts.extend([(0, "INSERT"), (2, "Door"), (420, colour), (10, "0"), (20, "0")]); + } + let text = with_door(by_block, &inserts); + let start = std::time::Instant::now(); + let doc = open(&text, &points()).document; + let took = start.elapsed(); + let mut names: Vec<&str> = doc.symbols.iter().map(|s| s.name.as_str()).collect(); + assert_eq!(names.len(), n); + names.sort_unstable(); + names.dedup(); + assert_eq!(names.len(), n, "names stay unique"); + assert!(took < std::time::Duration::from_secs(4), "import took {took:?}"); +} + #[test] fn array_inserts_attributes_and_dimensions() { let inserts = [