From 7db656fdc7fc45df43d0a5377ed328cd8b8dfb0b Mon Sep 17 00:00:00 2001 From: philipp-tch <113692434+philipp-tch@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:54:36 +0200 Subject: [PATCH] Write a layout's paper-space viewport (id 1) ahead of its other viewports (#1782) DWG stores no viewport ids; readers number a layout's viewports in order. A drawing whose draw order (SORTENTS) put the paper-space viewport behind a model viewport came back from DWG with that model viewport as the sheet. Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/io/src/dxf_write.rs | 13 +++++++++++-- crates/io/tests/it/dwg_roundtrip_losses.rs | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/io/src/dxf_write.rs b/crates/io/src/dxf_write.rs index 0434c706..6af79908 100644 --- a/crates/io/src/dxf_write.rs +++ b/crates/io/src/dxf_write.rs @@ -1629,6 +1629,15 @@ fn paper_views(d: &Drawing) -> HashMap { out } +/// A layout's entities with its paper-space viewport (id 1) first. DWG stores no viewport ids and +/// readers number a layout's viewports in order, so the sheet must lead even when the draw order +/// (SORTENTS) put it behind a model viewport; otherwise that viewport becomes the sheet. +fn sheet_first(l: &Layout) -> Vec<&Entity> { + let sheet = |e: &&Entity| matches!(&e.kind, EntityKind::Viewport(v) if v.id == 1); + let all = || l.entities.iter().map(|e| e.as_ref()); + all().filter(sheet).chain(all().filter(|e| !sheet(e))).collect() +} + /// The paper-space viewport (id 1) of the layout whose first viewport is `e`, if it needs one /// ([`paper_views`]): the whole sheet, viewed 1:1. Marked with CADCraft xdata so that our reader /// leaves it implicit again. @@ -2340,7 +2349,7 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec { if i == 0 { block(&mut w, n, "", h, Vec3::ZERO, 0, &mut std::iter::empty(), true); } else { - block(&mut w, n, "", h, Vec3::ZERO, 0, &mut l.entities.iter().map(|e| e.as_ref()), true); + block(&mut w, n, "", h, Vec3::ZERO, 0, &mut sheet_first(l).into_iter(), true); } } for (n, h, b) in &user_blocks { @@ -2371,7 +2380,7 @@ fn tags_for(d: &Drawing, version: Version, target: Version) -> Vec { entity(&mut w, d, e, &ms_br, false, &cx); } if let Some((_, h, l)) = ps_brs.first() { - for e in l.entities.iter() { + for e in sheet_first(l) { entity(&mut w, d, e, h, true, &cx); } } diff --git a/crates/io/tests/it/dwg_roundtrip_losses.rs b/crates/io/tests/it/dwg_roundtrip_losses.rs index c0852eff..ec9c2b93 100644 --- a/crates/io/tests/it/dwg_roundtrip_losses.rs +++ b/crates/io/tests/it/dwg_roundtrip_losses.rs @@ -138,3 +138,24 @@ fn viewport_ids_survive_dwg_and_the_paper_viewport_stays_implicit() { assert!(!text.contains("PAPERVIEW")); assert_eq!(viewport_ids(&cadcraft_io::read_dxf(text.as_bytes()).unwrap().layouts[0]), [1, 2]); } + +/// The paper-space viewport (id 1) of a drawing from another program can come after a model +/// viewport in the layout, e.g. when the draw order (SORTENTS) put it behind. DWG numbers in +/// order, so the writer puts it first: otherwise the model viewport became the sheet on reopen. +#[test] +fn a_paper_viewport_behind_a_model_viewport_stays_id_1_through_dwg() { + let mut d = Drawing::new_metric(); + let names: Vec = d.layouts.iter().map(|l| l.name.clone()).collect(); + for name in &names { + let space = Space::Paper(name.clone()); + let model = d.add(&space, Common::default(), viewport(2)).unwrap(); + let sheet = d.add(&space, Common::default(), viewport(1)).unwrap(); + let back = through_dwg(&d); + let l = back.layouts.iter().find(|l| &l.name == name).unwrap(); + let id = |h: Handle| match &l.entities.get(h).map(|e| &e.kind) { + Some(EntityKind::Viewport(v)) => Some(v.id), + _ => None, + }; + assert_eq!((id(model), id(sheet)), (Some(2), Some(1)), "layout {name}"); + } +}