From a5f965ead4cb116c3aad0f1c1115afa8c767cd9f Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 16 Oct 2026 23:12:45 +0300 Subject: [PATCH] Group nested DGN complex elements under their own headers The element collector keeps a stack of open complex elements instead of one. A chain, text node or cell inside a cell is gathered on its own and, once complete, counts as one component of the cell, rather than closing the cell early and leaving its remaining components at the top level. A B-spline curve whose poles cannot be read still takes its place in the open cell, so the cell does not absorb the element after it. An element that is not a component ends every complex element still open, so a header whose count overstates its components cannot swallow the rest of the model. Co-Authored-By: Claude Opus 5.5 --- src/scene/model/underlay_vector/dgn7.rs | 5 +++ src/scene/model/underlay_vector/dgn8.rs | 14 ++++++- src/scene/model/underlay_vector/model.rs | 53 +++++++++++++++++------- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/scene/model/underlay_vector/dgn7.rs b/src/scene/model/underlay_vector/dgn7.rs index f9be987e6..d5b8612d3 100644 --- a/src/scene/model/underlay_vector/dgn7.rs +++ b/src/scene/model/underlay_vector/dgn7.rs @@ -254,6 +254,11 @@ fn read_run(run: &[&[u8]], colors: &[[u8; 3]], d3: bool, depth: usize) -> Raw { if e[1] & 0x80 != 0 || e.len() < 36 { continue; } + // An element that is no component ends the complex elements still + // open. + if e[0] & 0x80 == 0 { + collect.close(); + } let symbology = uint16(e, 34).unwrap_or(0); let color = colors[(symbology >> 8) as usize]; let element = |geom: DgnGeom| DgnElement { diff --git a/src/scene/model/underlay_vector/dgn8.rs b/src/scene/model/underlay_vector/dgn8.rs index ca2f5f63b..fd03152f4 100644 --- a/src/scene/model/underlay_vector/dgn8.rs +++ b/src/scene/model/underlay_vector/dgn8.rs @@ -686,6 +686,11 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { // An open B-spline curve: order, pole count, closure and its header. let mut spline: Option<(usize, usize, bool, DgnElement)> = None; for (index, e) in run.iter().enumerate() { + // An element that is no component ends the complex elements still + // open. + if !is_component(e) { + collect.close(); + } if e.len() < 104 || styles.hidden.contains(&u32_at(e, 12).unwrap_or(0)) { continue; } @@ -1007,12 +1012,17 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { continue; }; let flags = u16::from_le_bytes([lo, hi]); - spline = Some(( + // A curve before it that never got its poles still takes + // its place in an open cell. + let unread = spline.replace(( ((flags & 0x0f) + 2) as usize, u32_at(e, 112).unwrap_or(0) as usize, flags & 0x0180 != 0, element(DgnGeom::Chain(Vec::new())), )); + if unread.is_some() { + collect.skip(); + } } 21 => { let n = u32_at(e, 104).unwrap_or(0) as usize; @@ -1042,6 +1052,8 @@ fn read_run(run: &[&[u8]], styles: &Styles, xf: &Xf) -> Raw { ); } collect.push(DgnElement { geom, ..header }); + } else { + collect.skip(); } } } diff --git a/src/scene/model/underlay_vector/model.rs b/src/scene/model/underlay_vector/model.rs index ea98c7d81..ed63a9163 100644 --- a/src/scene/model/underlay_vector/model.rs +++ b/src/scene/model/underlay_vector/model.rs @@ -728,26 +728,33 @@ impl DgnGeom { } /// Collects elements in file order, gathering the components of a complex -/// chain, shape or text node into their header's element. +/// chain, shape, text node or cell into their header's element. #[derive(Default)] pub struct DgnCollector { pub elements: Vec, - /// The open complex element and how many components it still takes. - open: Option<(usize, DgnElement)>, + /// The open complex elements, innermost last, each with how many + /// components it still takes. A complex element inside another is one + /// component of it once it is complete. + open: Vec<(usize, DgnElement)>, } impl DgnCollector { - /// A complex chain or shape (or a text node) header with `count` + /// A complex chain or shape, a text node or a cell header with `count` /// components. pub fn open(&mut self, count: usize, header: DgnElement) { - self.close(); if count > 0 { - self.open = Some((count, header)); + self.open.push((count, header)); + } else { + self.skip(); } } - pub fn push(&mut self, element: DgnElement) { - if let Some((left, header)) = self.open.as_mut() { + pub fn push(&mut self, mut element: DgnElement) { + loop { + let Some((left, header)) = self.open.last_mut() else { + self.elements.push(element); + return; + }; match &mut header.geom { DgnGeom::Chain(parts) | DgnGeom::TextNode { lines: parts, .. } => { parts.push(element.geom) @@ -756,18 +763,34 @@ impl DgnCollector { _ => {} } *left -= 1; - if *left == 0 { - self.close(); + if *left > 0 { + return; } - return; + let Some((_, done)) = self.open.pop() else { + return; + }; + element = done; } - self.elements.push(element); } - /// Ends a complex element cut short with the components it has. + /// A component that converts to nothing (a curve that could not be + /// read) still takes its place in the open complex element. + pub fn skip(&mut self) { + if let Some((left, _)) = self.open.last_mut() { + *left -= 1; + if *left == 0 { + if let Some((_, done)) = self.open.pop() { + self.push(done); + } + } + } + } + + /// Ends the complex elements cut short with the components they have + /// (an element that is no component ends them all). pub fn close(&mut self) { - if let Some((_, header)) = self.open.take() { - self.elements.push(header); + while let Some((_, header)) = self.open.pop() { + self.push(header); } } }