From caafb9b99df13bf99f27a505a97c94bac0665735 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 19 Jun 2026 19:57:19 +0300 Subject: [PATCH] fix(layers): honor layer off/freeze for block sub-entities Block-internal entities ignored their own layer state: 2D subs were only filtered by the invisible flag in the block-defn cache, and 3D solids render through a separate mesh path that applied no layer filter at all. Both now skip subs on off/frozen layers, matching top-level entities. Caches rebuild on every toggle, so visibility updates live. Fixes #133 Co-Authored-By: Claude Opus 4.8 --- src/scene/cache/block_cache.rs | 15 ++++++++++++ src/scene/mod.rs | 44 +++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index 3746015a9..1cecf77c4 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -253,6 +253,14 @@ fn collect_referenced_blocks(doc: &CadDocument) -> Vec { seen.into_iter().collect() } +/// True when `layer` is turned off or frozen — entities on it never render. +fn layer_hidden(doc: &CadDocument, layer: &str) -> bool { + doc.layers + .get(layer) + .map(|l| l.flags.off || l.flags.frozen) + .unwrap_or(false) +} + fn build_defn( doc: &CadDocument, block_name: &str, @@ -360,6 +368,13 @@ fn build_defn( if entity.common().invisible { continue; } + // A sub-entity on a layer that is off or frozen must not render, same + // as a top-level entity on that layer. The defn cache is rebuilt on + // every layer off/freeze toggle (bump_geometry bumps block_epoch), so + // baking the visibility here stays in sync. + if layer_hidden(doc, &entity.common().layer) { + continue; + } match entity { EntityType::Block(_) | EntityType::BlockEnd(_) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index adc5a21db..80920dad6 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2031,7 +2031,14 @@ impl Scene { } } } - let mut all: Vec = self.meshes.values().cloned().collect(); + // Top-level solids: drop those whose layer is off/frozen or that are + // flagged invisible / isolated-hidden, mirroring the 2D wire path. + let mut all: Vec = self + .meshes + .iter() + .filter(|(&h, _)| self.mesh_entity_visible(h)) + .map(|(_, set)| set.clone()) + .collect(); // Block-definition solids are instanced per model-space INSERT so a // block placed at an INSERT scale renders at the right size. (#123) all.extend(self.instanced_block_meshes(self.model_space_block_handle())); @@ -2040,6 +2047,31 @@ impl Scene { arc } + /// True when `layer` is turned off or frozen — entities on it never render. + fn layer_hidden(&self, layer: &str) -> bool { + self.document + .layers + .get(layer) + .map(|l| l.flags.off || l.flags.frozen) + .unwrap_or(false) + } + + /// Visibility test for a solid mesh entity, mirroring the 2D wire path: + /// honour the invisible flag, the isolate/hide set, and the layer's + /// off/frozen state. + fn mesh_entity_visible(&self, handle: Handle) -> bool { + let Some(c) = self.document.get_entity(handle).map(|e| e.common()) else { + return false; + }; + if c.invisible { + return false; + } + if !self.hidden.is_empty() && self.hidden.contains(&handle) { + return false; + } + !self.layer_hidden(&c.layer) + } + /// One transformed mesh per block-definition solid instance reached from an /// INSERT owned by `layout_block`. Nested INSERTs accumulate their /// transform. Empty when no block solids exist. (#123) @@ -2054,6 +2086,11 @@ impl Scene { continue; } if let EntityType::Insert(ins) = e { + // INSERT on an off/frozen (or invisible) layer hides the whole + // instance, block-internal solids included. + if !self.mesh_entity_visible(ins.common.handle) { + continue; + } self.expand_block_meshes(&ins.block_name, &ins.get_transform(), 0, woff, &mut out); } } @@ -2081,6 +2118,11 @@ impl Scene { let Some(e) = self.document.get_entity(h) else { continue; }; + // A block-internal solid / nested INSERT on an off/frozen layer + // (or flagged invisible) must not render, same as a top-level one. + if !self.mesh_entity_visible(h) { + continue; + } if let EntityType::Insert(ins) = e { let composed = ins.get_transform().then(accum); self.expand_block_meshes(&ins.block_name, &composed, depth + 1, woff, out);