From 282ad11e60f22d4d7bc93d300eeff985a508df8a Mon Sep 17 00:00:00 2001 From: SBaksa Date: Sat, 31 Oct 2026 12:28:21 +1100 Subject: [PATCH] Save note: a DWG loses SAT-text solids from 2013 on, and the note says so (#2334) A real DWG write and read-back keeps a SAT-text REGION's groups up to 2010 and returns it without them at 2013 and 2018, so the SAT-text skip in not_saved_as applies before 2013 only. The DWG note now says solids are kept in a DXF only when saved as 2010 or earlier. --- crates/engine/src/cmd/file.rs | 11 ++++++---- crates/engine/tests/save_reports_not_saved.rs | 14 ++++++------- crates/io/tests/it/acis_data_store.rs | 21 +++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index 0bccbfb4f..683bd26f8 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -550,10 +550,10 @@ pub fn not_saved_as(d: &Drawing, version: Version) -> std::collections::BTreeMap let stores = std::iter::once(&d.model).chain(d.layouts.iter().map(|l| &l.entities)).chain(d.blocks.values().map(|b| &b.entities)); for e in stores.flat_map(|s| s.iter()) { if let cadcraft_doc::EntityKind::Unknown(u) = &e.kind { - // A DWG writer converts SAT text to its own form at any version, and from 2013 a data - // store record keeps the geometry too: neither loses the solid. + // A DWG before 2013 writes a solid's SAT text as it is; from 2013 the geometry has to be + // in the data store, and SAT text alone is lost (#2334). if MODELER_TYPES.iter().any(|t| u.dxf_type.eq_ignore_ascii_case(t)) - && (stored.contains(&e.handle) || cadcraft_doc::foreign::has_sat_text(&u.tags)) + && (stored.contains(&e.handle) || (version < Version::R2013 && cadcraft_doc::foreign::has_sat_text(&u.tags))) { continue; } @@ -570,7 +570,10 @@ pub fn not_saved_message(lost: &std::collections::BTreeMap) -> Op return None; } let list: Vec = lost.iter().map(|(t, n)| format!("{n} {t}")).collect(); - Some(format!("Not saved in the DWG file (its writer can't write these object types yet; DXF keeps them): {}", list.join(", "))) + Some(format!( + "Not saved in the DWG file (its writer can't write these object types yet; DXF keeps them, solids only when saved as 2010 or earlier): {}", + list.join(", ") + )) } /// Whether saving to `path` leaves out the entities [`not_saved`] counts: DWG does (the DWG diff --git a/crates/engine/tests/save_reports_not_saved.rs b/crates/engine/tests/save_reports_not_saved.rs index d50b1a503..295877d5c 100644 --- a/crates/engine/tests/save_reports_not_saved.rs +++ b/crates/engine/tests/save_reports_not_saved.rs @@ -29,7 +29,7 @@ fn save_lists_the_entities_the_file_does_not_keep() { let r = s.execute("saveas", &json!({ "path": path.to_string_lossy() })).unwrap(); let _ = std::fs::remove_file(&path); assert_eq!(r["notSaved"], json!({ "3DSOLID": 1, "MLINE": 2, "REGION": 1 })); - let msg = "Not saved in the DWG file (its writer can't write these object types yet; DXF keeps them): 1 3DSOLID, 2 MLINE, 1 REGION"; + let msg = "Not saved in the DWG file (its writer can't write these object types yet; DXF keeps them, solids only when saved as 2010 or earlier): 1 3DSOLID, 2 MLINE, 1 REGION"; assert!(s.log.iter().any(|l| l == msg), "{:?}", s.log); // DXF keeps them. @@ -145,17 +145,17 @@ fn the_note_counts_the_host_drawing_while_a_block_is_edited() { assert_eq!(r["notSaved"], json!({ "REGION": 1 })); } -/// A DWG writer converts a solid's SAT text itself (at any version), so a DWG save doesn't list -/// solids that hold it as left out; ones with neither SAT text nor a data store record are (#2334). +/// A DWG before 2013 writes a solid's SAT text as it is, so a save there doesn't list a solid that +/// holds it; from 2013 the geometry must be in the data store and SAT text alone is lost, so the +/// save lists it. A solid with neither is always listed (#2334). #[test] -fn a_dwg_keeps_solids_that_hold_sat_text() { +fn a_dwg_keeps_sat_text_solids_only_before_2013() { use cadcraft_engine::cmd::file::{Version, not_saved_as}; let tag = |code: i32, value: &str| RawTag { code, value: value.into() }; let solid = |tags: Vec| EntityKind::Unknown(Unknown { dxf_type: "3DSOLID".into(), tags }); let mut d = Drawing::new_imperial(); d.add(&Space::Model, Common::default(), solid(vec![tag(100, "AcDbModelerGeometry"), tag(1, "400 1 1 0")])).unwrap(); d.add(&Space::Model, Common::default(), solid(vec![tag(100, "AcDbModelerGeometry"), tag(70, "1")])).unwrap(); - for v in [Version::R2010, Version::R2018] { - assert_eq!(not_saved_as(&d, v), [("3DSOLID".to_string(), 1)].into_iter().collect(), "{v:?}"); - } + assert_eq!(not_saved_as(&d, Version::R2010), [("3DSOLID".to_string(), 1)].into_iter().collect(), "2010 keeps the SAT-text solid"); + assert_eq!(not_saved_as(&d, Version::R2018), [("3DSOLID".to_string(), 2)].into_iter().collect(), "2018 loses it"); } diff --git a/crates/io/tests/it/acis_data_store.rs b/crates/io/tests/it/acis_data_store.rs index 5bb4bc8ee..f0ad2039c 100644 --- a/crates/io/tests/it/acis_data_store.rs +++ b/crates/io/tests/it/acis_data_store.rs @@ -196,3 +196,24 @@ fn a_data_store_record_stores_only_its_first_entity() { let store = Foreign { data_store: vec![tag(0, "ACDSRECORD"), tag(320, "2B"), tag(320, "2C"), tag(2, "ASM_Data")], ..Foreign::default() }; assert_eq!(store.data_store_entities(), [Handle(0x2B)].into()); } + +/// A real DWG round trip of a SAT-text REGION: the DWG writer keeps the SAT groups up to 2010, and +/// from 2013 they come back empty (the solid's geometry is not in the file), which the DWG save +/// note has to say (#2334). +#[test] +fn a_dwg_keeps_a_sat_text_region_only_before_2013() { + let sat = "0\nSECTION\n2\nHEADER\n9\n$ACADVER\n1\nAC1015\n0\nENDSEC\n0\nSECTION\n2\nENTITIES\n\ + 0\nREGION\n5\n2B\n100\nAcDbEntity\n8\n0\n100\nAcDbModelerGeometry\n70\n1\n1\n400 1 1 0\n3\nEnd-of-ACIS-data\n0\nENDSEC\n0\nEOF\n"; + let d = cadcraft_io::read_dxf(sat.as_bytes()).unwrap(); + let holds_sat = |v: cadcraft_io::Version| { + let dwg = cadcraft_io::write_with(&d, "x.dwg", &cadcraft_io::SaveOptions { version: Some(v), binary: false }).unwrap(); + let back = cadcraft_io::read(&dwg, "x.dwg").unwrap(); + let region = back.model.iter().find_map(|e| match &e.kind { + EntityKind::Unknown(u) if u.dxf_type == "REGION" => Some(u.clone()), + _ => None, + }); + region.is_some_and(|u| cadcraft_doc::foreign::has_sat_text(&u.tags)) + }; + assert!(holds_sat(cadcraft_io::Version::R2010), "2010 keeps the SAT text"); + assert!(!holds_sat(cadcraft_io::Version::R2018), "2018 comes back without it"); +}