diff --git a/Cargo.lock b/Cargo.lock index 043ca01e..3eb9cacc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -663,6 +663,7 @@ dependencies = [ name = "cadcraft-io" version = "0.9.0" dependencies = [ + "acadrust", "cadcraft-color", "cadcraft-doc", "cadcraft-dwg", diff --git a/crates/dwg/src/lib.rs b/crates/dwg/src/lib.rs index 7600d63e..c5d17cd0 100644 --- a/crates/dwg/src/lib.rs +++ b/crates/dwg/src/lib.rs @@ -423,6 +423,7 @@ mod native { None if doc.version < MIN_DWG_VERSION => doc.version = MIN_DWG_VERSION, None => {} } + other_layout_owners(&mut doc); table_style_rows(&mut doc, true); drop_unwritable(&mut doc); current_dimstyle(&mut doc); @@ -430,6 +431,31 @@ mod native { .map_err(|_| "the DWG writer failed on this drawing".to_string())? .map_err(|e| format!("DWG write: {e}")) } + + /// Entities of a layout other than the one in `*Paper_Space` are written owned by their block + /// (entity mode 0, with its handle). acadrust writes every paper-space entity with mode 1, + /// "paper space", which AutoCAD reads as the `*Paper_Space` block's: everything drawn on a + /// second layout landed on the first one, and the second stayed empty (#1057). + fn other_layout_owners(doc: &mut acadrust::CadDocument) { + let others: std::collections::HashSet = doc + .block_records + .iter() + .filter(|br| { + let n = br.name.to_ascii_uppercase(); + n.len() > 12 && n.starts_with("*PAPER_SPACE") && n[12..].bytes().all(|b| b.is_ascii_digit()) + }) + .map(|br| br.handle) + .collect(); + if others.is_empty() { + return; + } + for e in doc.entities_mut() { + let c = e.common_mut(); + if others.contains(&c.owner_handle) { + c.entity_mode = Some(0); + } + } + } } #[cfg(not(target_arch = "wasm32"))] diff --git a/crates/io/Cargo.toml b/crates/io/Cargo.toml index 151e1ec9..84adfec4 100644 --- a/crates/io/Cargo.toml +++ b/crates/io/Cargo.toml @@ -23,4 +23,6 @@ miniz_oxide = "0.8" quick-xml = { workspace = true } [dev-dependencies] +# Reads CADCraft's DWG output as written (tests only; the same version as cadcraft-dwg's). +acadrust = "0.6.4" tiny-skia = { workspace = true } diff --git a/crates/io/tests/dwg_layout_owner.rs b/crates/io/tests/dwg_layout_owner.rs new file mode 100644 index 00000000..45de12cb --- /dev/null +++ b/crates/io/tests/dwg_layout_owner.rs @@ -0,0 +1,52 @@ +//! #1057: in a DWG, entities of a layout other than the first (`*Paper_Space`) are written owned +//! by their own block (entity mode 0 and its handle). Written as "paper space" (mode 1), AutoCAD +//! put them on the first layout and left theirs empty. + +use std::io::Cursor; + +use cadcraft_doc::{Circle, Common, Drawing, EntityKind, Layout, Line, Space}; +use cadcraft_geom::Vec3; +use cadcraft_io::{SaveOptions, Version, read, write_with}; + +#[test] +fn entities_of_a_second_layout_are_owned_by_its_block() { + let mut d = Drawing::new_metric(); + let order = d.layouts.iter().map(|l| l.tab_order).max().unwrap_or(0) + 1; + d.layouts.push(Layout::new("SHEET_A", order)); + let line = |a: f64| EntityKind::Line(Line { a: Vec3::new(a, 0.0, 0.0), b: Vec3::new(a + 10.0, 10.0, 0.0) }); + d.add(&Space::Model, Common::default(), EntityKind::Circle(Circle { center: Vec3::ZERO, radius: 5.0 })).unwrap(); + let first = d.layouts.iter().min_by_key(|l| l.tab_order).unwrap().name.clone(); + d.add(&Space::Paper(first.clone()), Common::default(), line(0.0)).unwrap(); + d.add(&Space::Paper("SHEET_A".into()), Common::default(), line(20.0)).unwrap(); + d.add(&Space::Paper("SHEET_A".into()), Common::default(), line(40.0)).unwrap(); + let dwg = write_with(&d, "case.dwg", &SaveOptions { version: Some(Version::R2018), binary: false }).unwrap(); + + // As written: entity mode and owner of each entity. + let doc = acadrust::DwgReader::from_stream(Cursor::new(dwg.clone())).read().unwrap(); + let block_of = |h: acadrust::Handle| doc.block_records.iter().find(|b| b.handle == h).map(|b| b.name.to_ascii_uppercase()); + let sheet_a = doc + .objects + .iter() + .find_map(|(_, o)| match o { + acadrust::objects::ObjectType::Layout(l) if l.name == "SHEET_A" => Some(l.block_record), + _ => None, + }) + .unwrap(); + let mut on_sheet_a = 0; + for e in doc.entities() { + let c = e.common(); + let block = block_of(c.owner_handle).unwrap_or_default(); + if c.owner_handle == sheet_a { + on_sheet_a += 1; + assert_eq!(c.entity_mode, Some(0), "an entity of SHEET_A ({block}) is owned by its block"); + } else if block == "*PAPER_SPACE" { + assert_eq!(c.entity_mode, Some(1), "the first layout's entities stay paper space"); + } + } + assert_eq!(on_sheet_a, 2); + + // Read back, each layout has its own. + let back = read(&dwg, "case.dwg").unwrap(); + let count = |n: &str| back.layouts.iter().find(|l| l.name == n).map_or(0, |l| l.entities.len()); + assert_eq!((count(&first), count("SHEET_A"), back.model.len()), (1, 2, 1)); +}