diff --git a/crates/doc/src/foreign.rs b/crates/doc/src/foreign.rs index e0ebb81a4..80ebbc3a3 100644 --- a/crates/doc/src/foreign.rs +++ b/crates/doc/src/foreign.rs @@ -75,6 +75,10 @@ pub struct Foreign { /// surfaces, which from 2013 on those entities don't carry themselves: each `ACDSRECORD` /// names its entity with a 320 handle (see [`Foreign::data_store_entities`]). pub data_store: Vec, + /// The `$ACADVER` of the file all this was read from: its groups are in that version's form. + /// The drawing's own `$ACADVER` is the version it is saved as, which SAVEAS changes, so a + /// later save must not take it for this one (#2471). + pub version: Option, } impl Foreign { diff --git a/crates/dwg/src/lib.rs b/crates/dwg/src/lib.rs index cd260baa4..3157ea68e 100644 --- a/crates/dwg/src/lib.rs +++ b/crates/dwg/src/lib.rs @@ -1298,21 +1298,6 @@ mod tests { } /// A kept version not newer than the declared one, a binary DXF and a DXF without `$ACADVER` - /// The header is read as group code and value pairs up to the first `0`/`ENDSEC`: a `$ACADVER` - /// name whose value would come after it, or a file that never ends its HEADER, is no version - /// to replace, and `\r\n` lines keep the value's offset right. - #[cfg(not(target_arch = "wasm32"))] - #[test] - fn declare_version_reads_the_header_in_pairs_up_to_endsec() { - let cut = b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 0\r\nENDSEC\r\n 1\r\nAC1015\r\n"; - assert_eq!(native::declare_version(cut, acadrust::DxfVersion::AC1032), None); - let endless = vec![b'\n'; 1 << 20]; - assert_eq!(native::declare_version(&endless, acadrust::DxfVersion::AC1032), None); - let crlf = b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 1\r\n AC1015\r\n 0\r\nENDSEC\r\n"; - let out = native::declare_version(crlf, acadrust::DxfVersion::AC1032).unwrap(); - assert_eq!(out, b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 1\r\n AC1032\r\n 0\r\nENDSEC\r\n"); - } - /// are read as they are. #[cfg(not(target_arch = "wasm32"))] #[test] @@ -1328,6 +1313,21 @@ mod tests { assert_eq!(native::declare_version(late, acadrust::DxfVersion::AC1032), None); } + /// The header is read as group code and value pairs up to the first `0`/`ENDSEC`: a `$ACADVER` + /// name whose value would come after it, or a file that never ends its HEADER, is no version + /// to replace, and `\r\n` lines keep the value's offset right. + #[cfg(not(target_arch = "wasm32"))] + #[test] + fn declare_version_reads_the_header_in_pairs_up_to_endsec() { + let cut = b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 0\r\nENDSEC\r\n 1\r\nAC1015\r\n"; + assert_eq!(native::declare_version(cut, acadrust::DxfVersion::AC1032), None); + let endless = vec![b'\n'; 1 << 20]; + assert_eq!(native::declare_version(&endless, acadrust::DxfVersion::AC1032), None); + let crlf = b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 1\r\n AC1015\r\n 0\r\nENDSEC\r\n"; + let out = native::declare_version(crlf, acadrust::DxfVersion::AC1032).unwrap(); + assert_eq!(out, b" 0\r\nSECTION\r\n 2\r\nHEADER\r\n 9\r\n$ACADVER\r\n 1\r\n AC1032\r\n 0\r\nENDSEC\r\n"); + } + /// Declared as 2007 or later, a DXF is UTF-8: code page text is decoded first. Declared as an /// older version, the code page bytes stay as they are. #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/io/src/dxf_raw.rs b/crates/io/src/dxf_raw.rs index 9b755f80f..9f6718c08 100644 --- a/crates/io/src/dxf_raw.rs +++ b/crates/io/src/dxf_raw.rs @@ -452,6 +452,7 @@ pub(crate) fn read(secs: &[Section], d: &mut Drawing) { f.data_store = ds.tags.iter().take(MAX_DATA_STORE_GROUPS).map(raw).collect(); } if !f.is_empty() { + f.version = d.header.get("ACADVER").and_then(cadcraft_doc::HVal::as_str).map(str::to_string); d.foreign = Arc::new(f); } } diff --git a/crates/io/src/lib.rs b/crates/io/src/lib.rs index c643d4b29..9796b49e3 100644 --- a/crates/io/src/lib.rs +++ b/crates/io/src/lib.rs @@ -102,11 +102,12 @@ pub fn write_with(d: &Drawing, name: impl AsRef, opts: &SaveOptions) -> Re // one, at least 2004: R2000 DWG has no true colours, transparency, table styles or // gradients, so a drawing from an R2000 DXF doesn't lose them by default. The objects // CADCraft keeps without modelling them are still in the groups of the file they came - // from (`$ACADVER`), and the bridge reads them as that version. + // from (`Foreign::version`, not `$ACADVER`, which SAVEAS changes), and the bridge reads + // them as that version. Some(Format::Dwg) if !opts.binary => { let version = opts.version.unwrap_or_else(|| version.max(Version::R2004)); let bridge = dxf_write::dwg_bridge(d, version); - match d.header.get("ACADVER").and_then(cadcraft_doc::HVal::as_str) { + match d.foreign.version.as_deref() { Some(kept) => cadcraft_dwg::dxf_to_dwg_kept_as(bridge.as_bytes(), version.code(), kept), None => cadcraft_dwg::dxf_to_dwg_as(bridge.as_bytes(), version.code()), } diff --git a/crates/io/tests/dwg_kept_object_versions.rs b/crates/io/tests/dwg_kept_object_versions.rs index ad07c6952..8d03c1d75 100644 --- a/crates/io/tests/dwg_kept_object_versions.rs +++ b/crates/io/tests/dwg_kept_object_versions.rs @@ -235,6 +235,31 @@ fn kept_objects_survive(version: &str, target: Version) { assert!(wrong.is_empty(), "{version} → {target:?}:\n{}", wrong.join("\n")); } +/// A drawing saved once as an older DWG has that version as its `$ACADVER` (SAVEAS sets it, so a +/// later QSAVE writes it again), but its kept objects are still in the 2018 file's groups. The +/// next save must still read them as 2018: read as the saved version, the mental ray settings +/// came out a class version too high (the ODA refuses the DWG) and a 2004 field lost its cached +/// value. +#[test] +fn kept_groups_keep_their_version_across_saves() { + let dxf = synthetic("AC1032"); + for (first, second) in [(Version::R2004, Version::R2004), (Version::R2010, Version::R2018), (Version::R2018, Version::R2018)] { + let mut source = acadrust::DxfReader::from_reader(Cursor::new(dxf.clone().into_bytes())).unwrap().read().unwrap(); + source.version = acad(second); + let reference = kept(&acadrust::DwgReader::from_stream(Cursor::new(acadrust::DwgWriter::write_to_vec(&source).unwrap())).read().unwrap()); + + let mut d = read(dxf.as_bytes(), "kept.dxf").unwrap(); + write_with(&d, "kept.dwg", &SaveOptions { version: Some(first), binary: false }).unwrap(); + // What SAVEAS does to the drawing after writing it. + d.header.set_str("ACADVER", first.code()); + let dwg = write_with(&d, "kept.dwg", &SaveOptions { version: Some(second), binary: false }).unwrap(); + let written = kept(&acadrust::DwgReader::from_stream(Cursor::new(dwg)).read().unwrap()); + let field = |k: &Kept| k.field.as_ref().map(|f| (f.value.clone(), f.value_string.clone())); + assert_eq!(written.mental_ray, reference.mental_ray, "{first:?} then {second:?}: mental ray render settings"); + assert_eq!(field(&written), field(&reference), "{first:?} then {second:?}: field"); + } +} + #[test] fn a_2018_files_kept_objects_reach_a_2018_dwg() { kept_objects_survive("AC1032", Version::R2018);