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 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-19 19:57:19 +03:00
1 parent 0dc537b669
commit caafb9b99d
2 files changed
+58 -1

No files matched your search

+15
View File
@@ -253,6 +253,14 @@ fn collect_referenced_blocks(doc: &CadDocument) -> Vec<String> {
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(_)
+43 -1
View File
@@ -2031,7 +2031,14 @@ impl Scene {
}
}
}
let mut all: Vec<MeshLodSet> = 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<MeshLodSet> = 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);