mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-14 14:26:45 -04:00
fix(io): write DXF CLASS instance counts and preserve current leader style (#635)
Squash of PR #635 by @The-Great-Nothing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
4e276db858
commit
2fa967db65
5 files changed
+65
-7
No files matched your search
@@ -6,7 +6,8 @@
|
||||
//!
|
||||
//! CADCraft's own data lives under the registered application `CADCRAFT` (xdata) and the
|
||||
//! named-object-dictionary entries `CADCRAFT_CONSTRAINTS`, `CADCRAFT_LAYERSTATES`,
|
||||
//! `CADCRAFT_PLOTSTYLES` and `CADCRAFT_HANDSEED` (XRECORDs). Other readers keep or ignore them.
|
||||
//! `CADCRAFT_PLOTSTYLES`, `CADCRAFT_HANDSEED` and `CADCRAFT_MLEADERSTYLE` (XRECORDs). Other
|
||||
//! readers keep or ignore them.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -32,6 +33,8 @@ pub(crate) const ANNOTATION_KEY: &str = "CADCRAFT_ANNOTATION";
|
||||
pub(crate) const PLOT_STYLE_NAMES_KEY: &str = "ACAD_PLOTSTYLENAME";
|
||||
/// Named-object-dictionary key of the XRECORD holding the drawing's next entity handle (hex).
|
||||
pub(crate) const HANDSEED_KEY: &str = "CADCRAFT_HANDSEED";
|
||||
/// Named-object-dictionary key of the XRECORD holding the current multileader style.
|
||||
pub(crate) const MLEADER_STYLE_KEY: &str = "CADCRAFT_MLEADERSTYLE";
|
||||
/// Most plot style tables and plot style names read from a file.
|
||||
pub(crate) const MAX_PLOT_STYLE_TABLES: usize = 1000;
|
||||
pub(crate) const MAX_PLOT_STYLE_NAMES: usize = 100_000;
|
||||
|
||||
@@ -1843,6 +1843,9 @@ impl Objects {
|
||||
}
|
||||
|
||||
fn apply(self, d: &mut Drawing, rx: &Rx) {
|
||||
if let Some(style) = self.xrecord_text(crate::dxf_ext::MLEADER_STYLE_KEY).filter(|s| !s.trim().is_empty()) {
|
||||
d.header.set_str("CMLEADERSTYLE", &style);
|
||||
}
|
||||
// Table styles (named by their ACAD_TABLESTYLE dictionary entries).
|
||||
for (h, tags) in &self.table_styles {
|
||||
let Some(name) = self.names.get(h).filter(|n| !n.is_empty()) else { continue };
|
||||
|
||||
@@ -1555,6 +1555,9 @@ pub fn tags(d: &Drawing, version: Version) -> Vec<Tag> {
|
||||
// The drawing's own next handle: table records and objects are numbered above it, so
|
||||
// reading it back (instead of $HANDSEED) keeps their handles across saves (#495).
|
||||
xrecords.push((dxf_ext::HANDSEED_KEY, w.h(), vec![format!("{:X}", d.handseed)]));
|
||||
if let Some(name) = d.header.get("CMLEADERSTYLE").and_then(HVal::as_str) {
|
||||
xrecords.push((dxf_ext::MLEADER_STYLE_KEY, w.h(), vec![name.to_string()]));
|
||||
}
|
||||
if let Some(chunks) = dxf_ext::constraint_chunks(&d.constraints, &d.parametric) {
|
||||
xrecords.push((dxf_ext::CONSTRAINTS_KEY, w.h(), chunks));
|
||||
}
|
||||
@@ -1601,11 +1604,6 @@ pub fn tags(d: &Drawing, version: Version) -> Vec<Tag> {
|
||||
w.p(10, ext.max.to3(0.0));
|
||||
}
|
||||
header_vars(&mut w, d);
|
||||
// The current multileader style (an R2007 variable; older readers skip it).
|
||||
if let Some(name) = d.header.get("CMLEADERSTYLE").and_then(HVal::as_str) {
|
||||
w.s(9, "$CMLEADERSTYLE");
|
||||
w.s(2, name);
|
||||
}
|
||||
let seed_pos = w.t.len();
|
||||
w.s(9, "$HANDSEED");
|
||||
w.s(5, "0");
|
||||
@@ -2255,10 +2253,45 @@ pub fn tags(d: &Drawing, version: Version) -> Vec<Tag> {
|
||||
*t = Tag::s(5, seed);
|
||||
}
|
||||
crate::dxf_raw::finish(&mut w.t, d);
|
||||
add_class_instance_counts(&mut w.t);
|
||||
mark_xrefs(&mut w.t, d);
|
||||
w.t
|
||||
}
|
||||
|
||||
/// Write DXF Reference group 91 on every CLASS record. Count records after `dxf_raw::finish`
|
||||
/// restores foreign classes and objects so every class gets its actual instance count.
|
||||
fn add_class_instance_counts(tags: &mut Vec<Tag>) {
|
||||
let mut counts: HashMap<String, i64> = HashMap::new();
|
||||
for t in tags.iter().filter(|t| t.code == 0) {
|
||||
let kind = t.str();
|
||||
if kind != "CLASS" {
|
||||
*counts.entry(kind.to_ascii_uppercase()).or_default() += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut start = 0;
|
||||
while start < tags.len() {
|
||||
if tags.get(start).is_none_or(|t| t.code != 0 || t.str() != "CLASS") {
|
||||
start += 1;
|
||||
continue;
|
||||
}
|
||||
let end = tags[start + 1..].iter().position(|t| t.code == 0).map_or(tags.len(), |i| start + 1 + i);
|
||||
let name = tags[start + 1..end].iter().find(|t| t.code == 1).map(Tag::str).unwrap_or_default().to_ascii_uppercase();
|
||||
let count = counts.get(&name).copied().unwrap_or(0);
|
||||
let inserted = if let Some(i) = tags[start + 1..end].iter().position(|t| t.code == 91) {
|
||||
tags[start + 1 + i] = Tag::i(91, count);
|
||||
false
|
||||
} else if let Some(i) = tags[start + 1..end].iter().position(|t| t.code == 90) {
|
||||
tags.insert(start + 2 + i, Tag::i(91, count));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// Inserting the group shifts the next record's start by one.
|
||||
start = end + usize::from(inserted);
|
||||
}
|
||||
}
|
||||
|
||||
/// Xref blocks (DXF Reference, BLOCK): bit 4 of group 70 and the external drawing's path in
|
||||
/// group 1, set on the written BLOCK records of blocks that have an `xref_path`.
|
||||
fn mark_xrefs(tags: &mut [Tag], d: &Drawing) {
|
||||
|
||||
@@ -878,6 +878,21 @@ fn saving_twice_writes_the_same_records() {
|
||||
assert_eq!(a.counts(), b.counts());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_records_have_instance_counts_for_generated_and_preserved_records() {
|
||||
let (_, a, b) = saves(&fixture());
|
||||
for out in [&a, &b] {
|
||||
let counts = out.counts();
|
||||
for class in out.in_section("CLASSES", "CLASS") {
|
||||
let name = s(class, 1);
|
||||
let instance_count = class.iter().find(|t| t.code == 91).map(Tag::i64);
|
||||
assert_eq!(instance_count, counts.get(&name).map(|n| *n as i64), "CLASS {name}");
|
||||
}
|
||||
assert!(out.in_section("CLASSES", "CLASS").iter().any(|c| s(c, 1) == "ACME_OBJECT"));
|
||||
assert_eq!(counts.get("ACME_OBJECT"), Some(&1));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn erased_entities_take_their_extension_data_with_them() {
|
||||
let mut d = read_dxf(fixture().as_bytes()).expect("read");
|
||||
|
||||
@@ -73,7 +73,11 @@ fn multileader_styles_and_current_style_round_trip() {
|
||||
};
|
||||
d.mleader_styles.push(callout);
|
||||
d.header.set_str("CMLEADERSTYLE", "Callout");
|
||||
let r = round_trip(&d);
|
||||
let text = write_dxf(&d);
|
||||
let tags = cadcraft_dxf::parse(text.as_bytes()).expect("parse written DXF");
|
||||
let header = cadcraft_dxf::sections(&tags).into_iter().find(|s| s.name == "HEADER").expect("HEADER");
|
||||
assert!(header.tags.iter().all(|t| t.code != 9 || t.str() != "$CMLEADERSTYLE"));
|
||||
let r = read_dxf(text.as_bytes()).expect("reopen");
|
||||
assert_eq!(r.mleader_styles, d.mleader_styles);
|
||||
assert_eq!(r.header.str("CMLEADERSTYLE", ""), "Callout");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user