From 9c144a746555deddfc740c390c99294788993fda Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 3 Jun 2026 19:56:06 +0300 Subject: [PATCH] separate by range --- core/nodeslicer/nodes/mesh.py | 90 ++++++++++++++++++++++++++++++++++- core/nodeslicer/tree.py | 1 + 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/core/nodeslicer/nodes/mesh.py b/core/nodeslicer/nodes/mesh.py index 8e78d38..46d630e 100644 --- a/core/nodeslicer/nodes/mesh.py +++ b/core/nodeslicer/nodes/mesh.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any import numpy as np -from ..bpy_ns_types import NfBoolProperty, NfEnumProperty, notify_tree_changed +from ..bpy_ns_types import NfBoolProperty, NfEnumProperty, NfIntProperty, notify_tree_changed from ....registry import register_class from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup, SlicingObject @@ -157,3 +157,91 @@ class SLICENODE_SeparateMesh(SliceNode): if socket_name == "Selection": return res_selection return res_inverted + +@register_class +class SLICENODE_SeparateMeshByRange(SliceNode): + bl_label = "Separate Mesh by Range" + + start_val: NfIntProperty(name="Start", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + end_val: NfIntProperty(name="End", default=1, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + + def init(self, context): + self.add_input("SLICESOCK_Mesh") + self.add_input("SLICESOCK_String", "Attribute") + self._update_outputs() + + def update(self): + super().update() + self._update_outputs() + + def _update_outputs(self): + # Ensure end >= start + start = self.start_val + end = max(start, self.end_val) + expected_ids = [str(i) for i in range(start, end + 1)] + + # Remove outputs that are no longer in the range + # We use a list to avoid modification-during-iteration issues + to_remove = [o for o in self.outputs if o.identifier not in expected_ids] + for o in to_remove: + self.outputs.remove(o) + + # Add new outputs + for id_str in expected_ids: + if id_str not in self.outputs: + self.add_output("SLICESOCK_Mesh", id_str, identifier=id_str) + + # Re-order to match range + for i, id_str in enumerate(expected_ids): + current_index = self.outputs.find(id_str) + if current_index != -1 and current_index != i: + self.outputs.move(current_index, i) + + def draw_buttons(self, context, layout): + col = layout.column(align=True) + col.prop(self, "start_val") + col.prop(self, "end_val") + + def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | None: + coll: SlicingCollection | None = self.resolve("Mesh") + attr_name: str = self.resolve("Attribute") + + if not coll: return None + + # Check if we can parse the socket name as an integer + try: + target_val = int(socket_name) + except ValueError: + return None + + res = SlicingCollection([], f"{coll.name}_{target_val}") + + for so in coll.objects: + if attr_name not in so.attributes: + continue + + attr = so.attributes[attr_name] + vals = attr["values"] + + # Attribute values might be floats or ints, we check for equality + mask = vals == target_val + + if np.all(mask): + res.objects.append(so) + elif np.any(mask): + # Split the object + so_new = SlicingObject() + so_new.name = f"{so.name}_{target_val}" + so_new.parent = so.parent + so_new.object_type = so.object_type + so_new.extruder = so.extruder + so_new.mesh = so.mesh[mask] + for k, v in so.attributes.items(): + so_new.attributes[k] = { + "domain": v["domain"], + "type": v["type"], + "values": v["values"][mask] + } + res.objects.append(so_new) + + return res diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index 8131bea..95d176f 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -497,6 +497,7 @@ class SLICENODE_MT_Geometry(Menu): assert layout layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh" layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh" + layout.operator("node.add_node", text="Separate Mesh by Range").type = "SLICENODE_SeparateMeshByRange" layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths" layout.separator() layout.operator("node.add_node", text="Slice Shells").type = "SLICENODE_SliceShells"