From 7ee93f6aea2b580174130f87678474019b97872a Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 15 May 2026 22:08:09 +0300 Subject: [PATCH] fix(scene): align compute_world_offset filter with belongs_to_visible_block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compute_world_offset rejected any entity whose owner_handle didn't equal model_block, but belongs_to_visible_block (the render-time filter) treats owner_handle = null as a legitimate MSPACE entity whenever block_record.entity_handles is empty and the file enumerates no block contents anywhere. DXF writers commonly omit group-code 330, so on those files the render path drew the entities while the offset scan missed them — direct WCS-coordinate wires then drove world_offset to the header (sentinel → [0,0,0]) and f32 precision collapsed for UTM-authored content. Replicate belongs_to_visible_block's decision exactly: * If model_block's BlockRecord enumerates entity_handles, use that set as the authoritative MSPACE membership list. * Otherwise: owner==model_block always counts; owner==null counts only when no other block_record enumerated either (legacy DXF without 330 codes anywhere); a non-null owner pointing elsewhere or an entity enumerated by some other block is always rejected. Co-Authored-By: Claude Opus 4.7 --- src/scene/mod.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 534c1af30..0bbaed0b8 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -217,13 +217,62 @@ fn compute_world_offset( // are treated as corruption rather than precision-relevant geometry. const SANE_EXTENT: f64 = 1.0e8; + // The filter here MUST agree with `belongs_to_visible_block` (the + // render-time filter): if rendering treats an entity as MSPACE but + // we skip it here, our offset misses the geometry that's actually on + // screen, and direct WCS-coordinate wires drag f32 precision to its + // knees. Likewise, including block-defn entities that the render + // path strictly drops would pull the centroid toward block-local + // origins. + // + // Authoritative path: if the model BlockRecord enumerates its + // entities via `entity_handles`, use that set directly. Falls back + // to the legacy permissive interpretation when no BlockRecord + // enumerates anything (legacy DXF without group-code 330) — match + // `belongs_to_visible_block`'s permissive default for that case. + let model_br = doc + .block_records + .iter() + .find(|br| br.handle == model_block); + let mspace_set: Option> = model_br + .filter(|br| !br.entity_handles.is_empty()) + .map(|br| br.entity_handles.iter().copied().collect()); + let any_enumerated = doc + .block_records + .iter() + .any(|br| !br.entity_handles.is_empty()); + let owned_by_other_block: std::collections::HashSet = if mspace_set.is_none() { + doc.block_records + .iter() + .filter(|br| br.handle != model_block) + .flat_map(|br| br.entity_handles.iter().copied()) + .collect() + } else { + std::collections::HashSet::new() + }; + // ── Pass 1: scan MSPACE entity bounding boxes ──────────────────────── let mut emin = [f64::INFINITY; 3]; let mut emax = [f64::NEG_INFINITY; 3]; let mut have = [false; 3]; for e in doc.entities() { let c = e.common(); - if c.owner_handle != model_block { + let h = c.handle; + let include = if let Some(ref set) = mspace_set { + set.contains(&h) + } else if c.owner_handle == model_block { + true + } else if !c.owner_handle.is_null() { + false + } else if owned_by_other_block.contains(&h) { + false + } else { + // owner null + h not enumerated by any block: legacy permissive + // when no block enumerated at all, strict drop otherwise (same + // as belongs_to_visible_block). + !any_enumerated + }; + if !include { continue; } // Skip block-defn sentinels and AttributeDefinition — same as