fix(purge): PURGE ALL no longer hangs on unnamed shape styles

PURGE repeats until a pass removes nothing. A drawing whose complex
linetypes use shape files carries unnamed STYLE records for them; the
table holds those under keys the empty name does not reach, so every pass
listed them again and removed nothing, and the loop never ended.

- Unnamed and shape-file styles, and styles a complex linetype reaches by
  handle, are never listed for purging.
- In-use checks compare names case-insensitively, as the tables do, so a
  layer, style or linetype referenced as WALL is not purged as Wall.
- Counts are what the tables actually removed, and a pass that removes
  nothing ends the loop.

Fixes #1662

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-08 14:41:38 +03:00
1 parent 7d008c4914
commit ff5bfc8c47
1 file changed
+45 -19
+45 -19
View File
@@ -279,7 +279,7 @@ impl OpenCADStudio {
if name.is_empty() {
None
} else {
Some(name.clone())
Some(name.to_uppercase())
}
})
.collect();
@@ -310,6 +310,17 @@ impl OpenCADStudio {
.map(|ds| ds.dimtxsty.clone()),
)
.filter(|s| !s.is_empty())
.map(|s| s.to_uppercase())
.collect();
// Complex linetypes reach their text and shape styles by handle.
let linetype_styles: rustc_hash::FxHashSet<codec::Handle> = self.tabs[i]
.scene
.document
.line_types
.iter()
.flat_map(|lt| lt.elements.iter())
.filter_map(|element| element.complex.as_ref())
.map(|complex| complex.style_handle)
.collect();
// Linetypes: entity references plus every layer's own
// linetype — a ByLayer entity reaches it through the layer.
@@ -334,6 +345,7 @@ impl OpenCADStudio {
.map(|l| l.line_type.clone()),
)
.filter(|s| !s.is_empty())
.map(|s| s.to_uppercase())
.collect();
// Live blocks are reachable from layouts and style roots
@@ -403,7 +415,9 @@ impl OpenCADStudio {
.document
.layers
.iter()
.filter(|l| l.name != "0" && !used_layers.contains(&l.name))
.filter(|l| {
l.name != "0" && !used_layers.contains(&l.name.to_uppercase())
})
.map(|l| l.name.clone())
.collect()
} else {
@@ -415,8 +429,14 @@ impl OpenCADStudio {
.document
.text_styles
.iter()
// Unnamed shape-file records serve complex linetypes
// and are never listed for purging.
.filter(|s| {
s.name != "Standard" && !used_text_styles.contains(&s.name)
!s.name.is_empty()
&& !s.is_shape_file
&& !s.name.eq_ignore_ascii_case("Standard")
&& !used_text_styles.contains(&s.name.to_uppercase())
&& !linetype_styles.contains(&s.handle)
})
.map(|s| s.name.clone())
.collect()
@@ -432,7 +452,7 @@ impl OpenCADStudio {
.iter()
.filter(|lt| {
!standard.iter().any(|s| s.eq_ignore_ascii_case(&lt.name))
&& !used_linetypes.contains(&lt.name)
&& !used_linetypes.contains(&lt.name.to_uppercase())
})
.map(|lt| lt.name.clone())
.collect()
@@ -479,16 +499,17 @@ impl OpenCADStudio {
snapshot_pushed = true;
}
// Apply removals (mutable)
for name in &layer_remove {
self.tabs[i].scene.document.layers.remove(name);
}
for name in &style_remove {
self.tabs[i].scene.document.text_styles.remove(name);
}
for name in &lt_remove {
self.tabs[i].scene.document.line_types.remove(name);
}
// Apply removals (mutable). Count what actually went: an
// entry the table cannot remove by name would otherwise be
// listed again on every pass and never let the loop end.
let doc = &mut self.tabs[i].scene.document;
let layers_gone =
layer_remove.iter().filter(|n| doc.layers.remove(n).is_some()).count();
let styles_gone =
style_remove.iter().filter(|n| doc.text_styles.remove(n).is_some()).count();
let lts_gone =
lt_remove.iter().filter(|n| doc.line_types.remove(n).is_some()).count();
let mut blocks_gone = 0usize;
for name in &block_remove {
// Drop the block definition's member entities (and the
// BLOCK/ENDBLK delimiters) before the record so no orphaned
@@ -506,12 +527,17 @@ impl OpenCADStudio {
for h in handles {
self.tabs[i].scene.document.remove_entity(h);
}
self.tabs[i].scene.document.block_records.remove(name);
if self.tabs[i].scene.document.block_records.remove(name).is_some() {
blocks_gone += 1;
}
}
n_layers += layers_gone;
n_styles += styles_gone;
n_lts += lts_gone;
n_blocks += blocks_gone;
if layers_gone + styles_gone + lts_gone + blocks_gone == 0 {
break;
}
n_layers += layer_remove.len();
n_styles += style_remove.len();
n_lts += lt_remove.len();
n_blocks += block_remove.len();
}
// Remove draw-order tables whose owning blocks are gone.