entget gives TEXT strings as the file has them, like MTEXT

Decoding the carets in entity_groups broke entmod (it reads the groups
as a file, so `A^ B` came back as `A^B`) and reached into an ATTDEF's
embedded MTEXT. entity_groups is left as on main: it gives the file's
groups, as it already does for MTEXT, and entmod reads them back
unchanged (test entmod_of_entget_keeps_the_strings).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Adiakys
2026-10-31 20:40:35 +01:00
co-authored by Claude Opus 5.5
parent a6c1fc6341
commit e61b4dd3d3
3 changed files with 23 additions and 36 deletions
+1 -13
View File
@@ -152,17 +152,5 @@ pub fn entity_groups(d: &Drawing, e: &Entity) -> (String, Vec<Tag>) {
entity(&mut w, d, e, "0", paper, &cx);
let mut tags = w.t.into_iter();
let kind = tags.next().filter(|t| t.code == 0).map(|t| t.value.as_str()).unwrap_or_default();
// Strings as AutoLISP sees them: `10^2`, not the file's `10^ 2`. Only the groups written
// escaped: the text (1) and an ATTDEF's prompt (3), not a DIMENSION's style name (3).
let escaped = |code: i32| match kind.as_str() {
"TEXT" | "ATTRIB" | "DIMENSION" => code == 1,
"ATTDEF" => matches!(code, 1 | 3),
_ => false,
};
let tags = tags
.take_while(|t| t.code != 0)
.filter(|t| t.code != 330)
.map(|t| if escaped(t.code) { Tag::s(t.code, crate::dxf_read::mtext_dxf::unescape_carets(&t.value.as_str())) } else { t })
.collect();
(kind, tags)
(kind, tags.take_while(|t| t.code != 0).filter(|t| t.code != 330).collect())
}
+21 -22
View File
@@ -55,21 +55,21 @@ fn drawing() -> Drawing {
modes: Default::default(),
};
d.add(&Space::Model, Common::default(), EntityKind::AttDef(attdef)).unwrap();
let dm = Dimension { style: "A^ B".into(), ..dimension("<> x10^3") };
d.add(&Space::Model, Common::default(), EntityKind::Dimension(dm)).unwrap();
d.add(&Space::Model, Common::default(), EntityKind::Dimension(dimension("<> x10^3"))).unwrap();
d
}
fn strings(d: &Drawing) -> Vec<String> {
d.model
.iter()
.flat_map(|e| match &e.kind {
EntityKind::Text(t) => vec![t.value.clone()],
EntityKind::AttDef(a) => vec![a.text.value.clone(), a.prompt.clone()],
EntityKind::Dimension(dm) => vec![dm.text.clone()],
_ => Vec::new(),
})
.collect()
fn strings(k: &EntityKind) -> Vec<String> {
match k {
EntityKind::Text(t) => vec![t.value.clone()],
EntityKind::AttDef(a) => vec![a.text.value.clone(), a.prompt.clone()],
EntityKind::Dimension(dm) => vec![dm.text.clone()],
_ => Vec::new(),
}
}
fn all_strings(d: &Drawing) -> Vec<String> {
d.model.iter().flat_map(|e| strings(&e.kind)).collect()
}
#[test]
@@ -80,20 +80,19 @@ fn carets_are_written_escaped_and_read_back() {
assert!(dxf.contains(escaped), "{escaped}");
}
let back = read_dxf(dxf.as_bytes()).unwrap();
assert_eq!(strings(&back), ["10^2 m, A^JB", "1^3", "Area in m^2", "<> x10^3"]);
assert_eq!(all_strings(&back), ["10^2 m, A^JB", "1^3", "Area in m^2", "<> x10^3"]);
// Older files have bare carets: kept as they are.
let old = read_dxf(dxf.replace("10^ 2 m", "10^2 m").as_bytes()).unwrap();
assert_eq!(strings(&old)[0], "10^2 m, A^JB");
assert_eq!(all_strings(&old)[0], "10^2 m, A^JB");
}
#[test]
fn entget_shows_plain_carets() {
fn entmod_of_entget_keeps_the_strings() {
// entget gives the groups as the file has them (as for MTEXT), and entmod reads them back.
let d = drawing();
let groups = |i: usize| cadcraft_io::entity_groups(&d, d.model.iter().nth(i).unwrap()).1;
let has = |g: &[cadcraft_dxf::Tag], code: i32, value: &str| g.iter().any(|t| t.code == code && t.value.as_str() == value);
assert!(has(&groups(0), 1, "10^2 m, A^JB"));
assert!(has(&groups(1), 3, "Area in m^2"));
// A DIMENSION's group 3 is its style name, written as it is.
let dim = groups(2);
assert!(has(&dim, 1, "<> x10^3") && has(&dim, 3, "A^ B"), "{dim:?}");
for e in d.model.iter() {
let (kind, groups) = cadcraft_io::entity_groups(&d, e);
let (_, back) = cadcraft_io::entity_from_groups(&kind, &groups).unwrap();
assert_eq!(strings(&back), strings(&e.kind), "{kind}");
}
}
+1 -1
View File
@@ -140,7 +140,7 @@ our output in AutoCAD black-box.
| Date | Change | Summary |
|---|---|---|
| 2026-10-31 | minor | A caret in a TEXT, ATTDEF, ATTRIB or DIMENSION string is written as `^` and a space and read back, as the DXF Reference has it: AutoCAD read the bare `^2` of `10^2` as a control character; AutoLISP's entget shows the plain caret |
| 2026-10-31 | minor | A caret in a TEXT, ATTDEF, ATTRIB or DIMENSION string is written as `^` and a space and read back, as the DXF Reference has it: AutoCAD read the bare `^2` of `10^2` as a control character |
| 2026-10-29 | minor | R2000 DXF: CLASS records written without group 91 (instance count), which came with the 2004 format; AutoCAD LT 2026 stopped responding on every R2000 DXF CADCraft saved (checked black-box in AutoCAD LT 2026) |
| 2026-10-26 | minor | CC-046: ATTEDIT and -ATTEDIT value changes invalidate derived justified baselines before DXF/DWG export; preserve alignment anchors, unchanged values and Aligned/Fit endpoints. Review follow-up: baseline recomputation preserves insertion elevation independently of alignment elevation. The shared writer correction also applies to justified TEXT. Independent of attribute editor UI; reverting the follow-up restores elevation loss on recomputation, while reverting the value setter restores stale XY baselines. |
| 2026-10-25 | minor | LAYOUT SAveas (`layout.saveas`) writes a layout as a drawing template: its page setup and paper space objects, no model space objects or other layouts, and only the named objects they use |