fix(scene): align compute_world_offset filter with belongs_to_visible_block

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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-15 22:08:09 +03:00
1 parent 59da4f35a9
commit 7ee93f6aea
1 file changed
+50 -1
+50 -1
View File
@@ -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<std::collections::HashSet<Handle>> = 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<Handle> = 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