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 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
philipp-tch
2026-10-24 01:54:36 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent 9d9e4c3b0d
commit 7db656fdc7
2 changed files with 32 additions and 2 deletions
+11 -2
View File
@@ -1629,6 +1629,15 @@ fn paper_views(d: &Drawing) -> HashMap<Handle, usize> {
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<Tag> {
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<Tag> {
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);
}
}
@@ -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<String> = 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}");
}
}