From 1cfb85622429f6b5a7ad61f6908a106018d3dd00 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 21 Jul 2026 00:19:51 +0300 Subject: [PATCH] refactor(dwg): use the first-class SectionSymbol and ViewBorder entities Section marks and drawing-view borders are now real acadrust entity types instead of Unknown-with-extras, so the renderer dispatches on the variant directly, the arrow-direction resolver reads the active viewport off the ViewBorder entity, and selection / properties show "Section Symbol" and "View Border" instead of "Unknown". View borders are non-plotting and tessellate to nothing; both count toward the lossy Save-As warning since they round-trip via their raw records. Bumps acadrust for the entity promotion. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 2 +- src/app/helpers.rs | 2 ++ src/entities/names.rs | 4 +++ src/entities/traits.rs | 2 ++ src/io/mod.rs | 7 ++++- src/scene/cache/properties.rs | 2 ++ src/scene/convert/tess.rs | 58 +++++++++++++++++++---------------- 7 files changed, 48 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7dff35def..9f745f18d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#8f2cf9543af65353c5fe3f7afbe5139de4ad711c" +source = "git+https://github.com/OpenAEC-Foundation/acadifc?branch=main#6ddff17beca4413df1bad0d441eff9d6aaad298b" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/src/app/helpers.rs b/src/app/helpers.rs index d34ad4c91..5b5ca9a49 100644 --- a/src/app/helpers.rs +++ b/src/app/helpers.rs @@ -346,6 +346,8 @@ pub(super) fn entity_type_key(entity: &acadrust::EntityType) -> String { AttributeEntity(_) => "attrib", Ole2Frame(_) => "ole2frame", Light(_) => "light", + SectionSymbol(_) => "sectionsymbol", + ViewBorder(_) => "viewborder", Seqend(_) => "seqend", Unknown(_) => "unknown", } diff --git a/src/entities/names.rs b/src/entities/names.rs index 0b910eb8f..ac84abfed 100644 --- a/src/entities/names.rs +++ b/src/entities/names.rs @@ -55,6 +55,8 @@ pub fn ui_name(e: &EntityType) -> &'static str { EntityType::AttributeEntity(_) => "Attribute", EntityType::Ole2Frame(_) => "OLE Frame", EntityType::Light(_) => "Light", + EntityType::SectionSymbol(_) => "Section Symbol", + EntityType::ViewBorder(_) => "View Border", EntityType::Seqend(_) => "Seqend", EntityType::Unknown(_) => "Unknown", } @@ -103,6 +105,8 @@ pub fn dxf_name(e: &EntityType) -> &'static str { EntityType::Surface(_) => "SURFACE", EntityType::Mesh(_) => "MESH", EntityType::Light(_) => "LIGHT", + EntityType::SectionSymbol(_) => "SECTIONLINE", + EntityType::ViewBorder(_) => "DRAWINGVIEW", _ => "ENTITY", } } diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 1649e5b32..0c16127e7 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -143,6 +143,8 @@ pub fn entity_type_name(et: &EntityType) -> &'static str { EntityType::Ole2Frame(_) => "Ole2Frame", EntityType::PolygonMesh(_) => "PolygonMesh", EntityType::Light(_) => "Light", + EntityType::SectionSymbol(_) => "SectionSymbol", + EntityType::ViewBorder(_) => "ViewBorder", EntityType::Unknown(_) => "Unknown", } } diff --git a/src/io/mod.rs b/src/io/mod.rs index 9db937934..4a3342328 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -338,7 +338,12 @@ pub fn dropped_on_save_count( }) .count(); for e in doc.entities() { - if matches!(e, acadrust::EntityType::Unknown(_)) { + if matches!( + e, + acadrust::EntityType::Unknown(_) + | acadrust::EntityType::SectionSymbol(_) + | acadrust::EntityType::ViewBorder(_) + ) { n += 1; } } diff --git a/src/scene/cache/properties.rs b/src/scene/cache/properties.rs index 31622b85f..8f7d4c1b5 100644 --- a/src/scene/cache/properties.rs +++ b/src/scene/cache/properties.rs @@ -113,6 +113,8 @@ pub fn visualization_section(entity: &EntityType) -> Option { | EntityType::Seqend(_) | EntityType::Leader(_) | EntityType::Unknown(_) + // Non-plotting drawing-view border: never rendered, no properties. + | EntityType::ViewBorder(_) ) { return None; } diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index 7b8f1c323..749c13b90 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -24,8 +24,8 @@ fn fade_if_locked( /// 2. The parent's refs name the section view's `AcDbViewRep` (any ref that /// owns an `AcDbViewRepSectionDefinition`), falling back to the document's /// section views. -/// 3. Each view's refs include its `AcDbViewBorder`, whose first handle is the -/// view's *active* viewport — the entity carrying the real camera. +/// 3. Each view's refs include its `AcDbViewBorder` entity, which carries the +/// view's *active* viewport — the entity holding the real camera. /// 4. The section camera's sight (−view_direction) is projected onto the parent /// camera's right/up basis (same convention as `camera_from_view`), giving /// the on-paper arrow direction. @@ -40,20 +40,23 @@ fn section_arrow_dir_from_views( if s.view_rep_handle == 0 { return None; } + // A view's active viewport, via its border entity among the ViewRep refs. + let border_vp = |h: &AHandle| -> Option { + match document.get_entity(*h)? { + EntityType::ViewBorder(b) if !b.active_viewport.is_null() => Some(b.active_viewport), + _ => None, + } + }; let parent_vr = AHandle::from(s.view_rep_handle); let parent_refs = document.view_rep_refs.get(&parent_vr)?; - let parent_vp_h = parent_refs - .iter() - .find_map(|h| document.view_border_viewport.get(h))?; + let parent_vp_h = parent_refs.iter().find_map(border_vp)?; let sect_vr = parent_refs .iter() .find(|h| **h != parent_vr && document.section_view_reps.contains(h)) .or_else(|| document.section_view_reps.iter().find(|&&h| h != parent_vr)) .copied()?; let sect_refs = document.view_rep_refs.get(§_vr)?; - let sect_vp_h = sect_refs - .iter() - .find_map(|h| document.view_border_viewport.get(h))?; + let sect_vp_h = sect_refs.iter().find_map(border_vp)?; let viewport = |h: &AHandle| { document.entities().find_map(|e| match e { @@ -61,8 +64,8 @@ fn section_arrow_dir_from_views( _ => None, }) }; - let pvp = viewport(parent_vp_h)?; - let svp = viewport(sect_vp_h)?; + let pvp = viewport(&parent_vp_h)?; + let svp = viewport(§_vp_h)?; // Section sight: view_direction points target→camera, sight is its inverse. let sight = -glam::DVec3::new( @@ -606,23 +609,24 @@ pub(crate) fn tessellate_entity( // ── Section symbol (AcDbSectionSymbol): draw the "A-A" cut mark ────────── // - // Model-Documentation section marks arrive as `Unknown` (class 825); the - // DWG reader decodes the two cut-line endpoints, the signed end ticks and - // the identifier. Synthesize the cut line + end ticks + label glyphs so the - // mark is visible on the layout, the way AutoCAD draws it. The raw record is - // still preserved for lossless write-back. - if let EntityType::Unknown(u) = e { - if let Some(s) = u.section_symbol.as_ref() { - return section_symbol_wires( - document, - s, - document.section_view_style.as_ref(), - h, - entity_color, - sel, - line_weight_px, - ); - } + // The DWG reader decodes the two cut-line endpoints, the signed end ticks + // and the identifier. Synthesize the end ticks + arrowheads + label glyphs + // so the mark is visible on the layout, the way AutoCAD draws it. The raw + // record is still preserved for lossless write-back. + if let EntityType::SectionSymbol(s) = e { + return section_symbol_wires( + document, + s, + document.section_view_style.as_ref(), + h, + entity_color, + sel, + line_weight_px, + ); + } + // Drawing-view borders are non-plotting aids: nothing is drawn. + if let EntityType::ViewBorder(_) = e { + return vec![]; } // ── Dimension baked-block fast path ─────────────────────────────────────