graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: viewport shows only the network editor's current level
The scene walk (network_sphere_vertices_with_errors) and the meta overlay
walk (collect_meta_overlays) take a start node: the walk iterates the
current directory's children while name resolution stays rooted at
fs_root, so chains inside the displayed level resolve exactly as before.
Navigation already funnels through on_path_changed -> rebuild_scene_geometry
(interactive and MCP enter/up alike), so the viewport re-scopes on every
enter/exit. Thumbnails keep drawing the whole scene from the root; the
status line's OpenCL note now reflects the displayed level.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/geometry.rs | 37 +++++++++++++++++++++++++++++++++++--
src/main.rs | 6 +++---
src/render.rs | 19 +++++++++++++++----
src/thumbnail.rs | 4 +++-
4 files changed, 56 insertions(+), 10 deletions(-)
diff --git a/src/geometry.rs b/src/geometry.rs
index 8062a2e..f948ce8 100644
--- a/src/geometry.rs
+++ b/src/geometry.rs
@@ -1567,11 +1567,18 @@ pub fn network_sphere_vertices(root: &FsNode) -> Geometry {
let mut err = None;
let mut cache = SimCache::default();
let mut sim = EvalSim::new(0, 0, &mut cache);
- network_sphere_vertices_with_errors(root, &mut err, &mut sim)
+ network_sphere_vertices_with_errors(root, root, &mut err, &mut sim)
}
+/// Collect the scene's geometry by walking `start`'s children. `start` is the
+/// network level the viewport displays — the network editor's current
+/// directory — while `root` stays the evaluation root: resolvers look inputs
+/// up by name from `root`, so a chain inside the displayed level still
+/// resolves references exactly as it does when drawn from the top. Pass
+/// `root` for both to draw the whole scene (thumbnails do).
pub fn network_sphere_vertices_with_errors(
root: &FsNode,
+ start: &FsNode,
ocl_error: &mut Option<String>,
sim: &mut EvalSim,
) -> Geometry {
@@ -1678,7 +1685,7 @@ pub fn network_sphere_vertices_with_errors(
let mut out = Geometry::new();
let mut count = 0;
- for child in &root.children {
+ for child in &start.children {
visit(root, child, true, &mut count, &mut out, ocl_error, sim);
}
out
@@ -2582,6 +2589,32 @@ mod simnet_tests {
}
}
+ /// The scene walk draws the level it is STARTED at — the network editor's
+ /// current directory — while evaluation stays rooted at the tree root.
+ /// From the root a subnet's internals draw (recursion); started at the
+ /// subnet, only its own children do.
+ #[test]
+ fn test_scene_walk_scoped_to_start_level() {
+ let outer = node("id-outer", "Sphere 1", "sphere", vec![param("Radius", "0.5")], vec![]);
+ let inner = node("id-inner", "Sphere 2", "sphere", vec![param("Radius", "0.5")], vec![]);
+ let sub = node("id-sub", "Sub 1", "node", vec![], vec![inner]);
+ let root = node("id-root", "root", "node", vec![], vec![outer, sub]);
+
+ const SPHERE: usize = 16 * 24 * 6;
+ let mut err = None;
+ let mut cache = SimCache::default();
+ let mut sim = EvalSim::new(0, 0, &mut cache);
+ let all = network_sphere_vertices_with_errors(&root, &root, &mut err, &mut sim);
+ assert_eq!(all.vertices.len(), 2 * SPHERE);
+
+ let mut err = None;
+ let mut cache = SimCache::default();
+ let mut sim = EvalSim::new(0, 0, &mut cache);
+ let scoped =
+ network_sphere_vertices_with_errors(&root, &root.children[1], &mut err, &mut sim);
+ assert_eq!(scoped.vertices.len(), SPHERE);
+ }
+
/// A simnet whose chain is one Transform: each step shifts the geometry by
/// the same offset, so the solved position reads back the step COUNT. Uses
/// transform, not an OpenCL node, so the test is pure CPU.
diff --git a/src/main.rs b/src/main.rs
index 5246276..5ae26cc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1305,7 +1305,7 @@ mod tests {
// Overlays: nothing while the prefs are off…
let mut cache = crate::geometry::SimCache::default();
let (markers, labels, wires, normals) = crate::render::collect_meta_overlays(
- &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
+ &root, &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
assert!(markers.is_empty() && labels.is_empty() && wires.is_empty() && normals.is_empty());
// …all four overlays for the flagged sphere: 240 marker verts per
@@ -1320,7 +1320,7 @@ mod tests {
assert!(crate::app::meta_pref(&root.children[0], "Wireframe"));
let mut cache = crate::geometry::SimCache::default();
let (markers, labels, wires, normals) = crate::render::collect_meta_overlays(
- &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
+ &root, &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
assert!(!labels.is_empty() && labels.len() < 16 * 24 * 6);
assert_eq!(markers.len(), labels.len() * 240);
assert!(labels.iter().any(|(_, i)| *i > 0));
@@ -1348,7 +1348,7 @@ mod tests {
root.children[0].geometry_visible = false;
let mut cache = crate::geometry::SimCache::default();
let (markers, labels, wires, normals) = crate::render::collect_meta_overlays(
- &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
+ &root, &root, 0.02, [1.0, 0.5, 0.0], &mut crate::geometry::EvalSim::new(0, 0, &mut cache));
assert!(markers.is_empty() && labels.is_empty() && wires.is_empty() && normals.is_empty());
}
diff --git a/src/render.rs b/src/render.rs
index 202c485..db6b02a 100644
--- a/src/render.rs
+++ b/src/render.rs
@@ -809,7 +809,11 @@ impl State {
let mut sim_cache = std::mem::take(&mut self.sim_cache);
let geom = {
let mut sim = crate::geometry::EvalSim::new(frame, start, &mut sim_cache);
- network_sphere_vertices_with_errors(&self.fs_root, &mut ocl_error, &mut sim)
+ // The viewport shows the network editor's current level, not the
+ // whole tree: the walk starts at the current directory while name
+ // resolution stays rooted at fs_root. Navigation re-scopes this
+ // through on_path_changed, which lands here.
+ network_sphere_vertices_with_errors(&self.fs_root, self.current_dir(), &mut ocl_error, &mut sim)
};
self.sim_cache = sim_cache;
@@ -825,9 +829,10 @@ impl State {
false
}
+ let displayed_opencl = has_visible_opencl(self.current_dir());
if let Some(e) = ocl_error {
self.update_status_text(&format!("OpenCL Error: {}", e));
- } else if has_visible_opencl(&self.fs_root) {
+ } else if displayed_opencl {
self.update_status_text("OpenCL kernel executed successfully.");
} else {
self.update_status_text("Geometry updated successfully.");
@@ -849,7 +854,9 @@ impl State {
let mut sim_cache = std::mem::take(&mut self.sim_cache);
let (markers, labels, wires, normals) = {
let mut sim = crate::geometry::EvalSim::new(frame, start, &mut sim_cache);
- collect_meta_overlays(&self.fs_root, self.meta_marker_size, self.meta_marker_color, &mut sim)
+ // Same scoping as the scene walk above: overlays annotate what is
+ // on screen, so they walk the same current level.
+ collect_meta_overlays(&self.fs_root, self.current_dir(), self.meta_marker_size, self.meta_marker_color, &mut sim)
};
self.sim_cache = sim_cache;
self.meta_marker_verts = markers;
@@ -887,8 +894,12 @@ impl State {
/// node and collect marker geometry and/or (position, vertex index) labels.
/// Positions dedupe the triangle soup's repeats; a label keeps the FIRST
/// index at its position, matching the spreadsheet's vertex numbering.
+/// `start` scopes the walk to the displayed network level (the scene walk's
+/// contract — pass `root` for both to cover the whole tree); evaluation
+/// stays rooted at `root`.
pub(crate) fn collect_meta_overlays(
root: &FsNode,
+ start: &FsNode,
point_size: f32,
marker_color: [f32; 3],
sim: &mut crate::geometry::EvalSim,
@@ -1012,7 +1023,7 @@ pub(crate) fn collect_meta_overlays(
visit(root, c, is_visible, point_size, marker_color, markers, labels, wires, normals, sim);
}
}
- for c in &root.children {
+ for c in &start.children {
visit(root, c, true, point_size, marker_color, &mut markers, &mut labels, &mut wires, &mut normals, sim);
}
// A dense mesh can label tens of thousands of points; the text pass is
diff --git a/src/thumbnail.rs b/src/thumbnail.rs
index a3ec2ac..2a44dae 100644
--- a/src/thumbnail.rs
+++ b/src/thumbnail.rs
@@ -34,7 +34,9 @@ pub fn run(project: &Path, out: &Path, size: u32, samples: Option<u32>) -> Resul
// A headless thumbnail has no timeline: simnets render at their seed.
let mut sim_cache = crate::geometry::SimCache::default();
let mut sim = crate::geometry::EvalSim::new(0, 0, &mut sim_cache);
- let geom = network_sphere_vertices_with_errors(&proj.root, &mut ocl_error, &mut sim);
+ // Thumbnails always show the whole scene from the top, regardless of the
+ // network level the project was saved at: root as both eval and walk root.
+ let geom = network_sphere_vertices_with_errors(&proj.root, &proj.root, &mut ocl_error, &mut sim);
if let Some(e) = ocl_error {
// Non-fatal: OpenCL nodes just contribute nothing, like the viewport.
eprintln!("thumbnail: OpenCL error (geometry partially skipped): {e}");