separate by range

This commit is contained in:
Nicolas Predella 2026-06-03 19:56:06 +03:00
parent 0cf055876c
commit 9c144a7465
2 changed files with 90 additions and 1 deletions

View file

@ -2,7 +2,7 @@ from __future__ import annotations
from typing import Any from typing import Any
import numpy as np 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 ....registry import register_class
from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup, SlicingObject from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup, SlicingObject
@ -157,3 +157,91 @@ class SLICENODE_SeparateMesh(SliceNode):
if socket_name == "Selection": if socket_name == "Selection":
return res_selection return res_selection
return res_inverted 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

View file

@ -497,6 +497,7 @@ class SLICENODE_MT_Geometry(Menu):
assert layout assert layout
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh" 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").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.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths"
layout.separator() layout.separator()
layout.operator("node.add_node", text="Slice Shells").type = "SLICENODE_SliceShells" layout.operator("node.add_node", text="Slice Shells").type = "SLICENODE_SliceShells"