Make DXF import symbol-name uniquing linear

Fixes #3589

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-23 11:20:15 +07:00
committed by bflatastic
co-authored by Claude Haiku 5.5
parent 6e1db2acd7
commit b16f44d87f
2 changed files with 40 additions and 6 deletions
+19 -6
View File
@@ -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<String, bool>,
/// Symbol names by block and what it inherits (`None`: the block has no art).
symbols: HashMap<(String, Inherit), Option<String>>,
/// Names of the symbols made so far, and the next suffix to try for each label.
symbol_names: HashSet<String>,
next_suffix: HashMap<String, usize>,
/// Blocks being drawn, outermost first.
stack: Vec<String>,
/// 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<String, usize>, taken: &HashSet<String>) -> 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;
}
}
}
+21
View File
@@ -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 = [