separate by attribute

This commit is contained in:
Nicolas Predella 2026-06-03 19:44:09 +03:00
parent 90a2c61fb4
commit 0cf055876c
3 changed files with 100 additions and 4 deletions

View file

@ -2,9 +2,9 @@ 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 from ..bpy_ns_types import NfBoolProperty, NfEnumProperty, notify_tree_changed
from ....registry import register_class from ....registry import register_class
from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup, SlicingObject
@register_class @register_class
class SLICENODE_GetMesh(SliceNode): class SLICENODE_GetMesh(SliceNode):
@ -63,3 +63,97 @@ class SLICENODE_JoinMeshGroups(SliceNodeJoiner):
group.collections[coll.objects[0].name] = coll group.collections[coll.objects[0].name] = coll
return group return group
@register_class
class SLICENODE_SeparateMesh(SliceNode):
bl_label = "Separate Mesh"
comparison: NfEnumProperty(
name="Comparison",
items=[
('EQUAL', "Equal", "=="),
('NOT_EQUAL', "Not Equal", "!="),
('GREATER_THAN', "Greater Than", ">"),
('LESS_THAN', "Less Than", "<"),
('GREATER_EQUAL', "Greater Equal", ">="),
('LESS_EQUAL', "Less Equal", "<="),
],
default='EQUAL',
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_input("SLICESOCK_Mesh")
self.add_input("SLICESOCK_String", "Attribute")
self.add_input("SLICESOCK_Float", "Value")
self.add_output("SLICESOCK_Mesh", "Selection")
self.add_output("SLICESOCK_Mesh", "Inverted")
def draw_buttons(self, context, layout):
assert layout
layout.prop(self, "comparison", text="")
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | None:
coll: SlicingCollection | None = self.resolve("Mesh")
attr_name: str = self.resolve("Attribute")
value: float = self.resolve("Value")
if not coll: return None
res_selection = SlicingCollection([], coll.name + "_selection")
res_inverted = SlicingCollection([], coll.name + "_inverted")
for so in coll.objects:
if attr_name not in so.attributes:
# If attribute missing, entire object goes to inverted
res_inverted.objects.append(so)
continue
attr = so.attributes[attr_name]
vals = attr["values"]
mask = np.zeros(len(so.mesh), dtype=bool)
if self.comparison == 'EQUAL': mask = vals == value
elif self.comparison == 'NOT_EQUAL': mask = vals != value
elif self.comparison == 'GREATER_THAN': mask = vals > value
elif self.comparison == 'LESS_THAN': mask = vals < value
elif self.comparison == 'GREATER_EQUAL': mask = vals >= value
elif self.comparison == 'LESS_EQUAL': mask = vals <= value
if np.all(mask):
res_selection.objects.append(so)
elif not np.any(mask):
res_inverted.objects.append(so)
else:
# Split the object
so_sel = SlicingObject()
so_sel.name = so.name + "_sel"
so_sel.parent = so.parent
so_sel.object_type = so.object_type
so_sel.extruder = so.extruder
so_sel.mesh = so.mesh[mask]
for k, v in so.attributes.items():
so_sel.attributes[k] = {
"domain": v["domain"],
"type": v["type"],
"values": v["values"][mask]
}
res_selection.objects.append(so_sel)
so_inv = SlicingObject()
so_inv.name = so.name + "_inv"
so_inv.parent = so.parent
so_inv.object_type = so.object_type
so_inv.extruder = so.extruder
so_inv.mesh = so.mesh[~mask]
for k, v in so.attributes.items():
so_inv.attributes[k] = {
"domain": v["domain"],
"type": v["type"],
"values": v["values"][~mask]
}
res_inverted.objects.append(so_inv)
if socket_name == "Selection":
return res_selection
return res_inverted

View file

@ -496,6 +496,7 @@ class SLICENODE_MT_Geometry(Menu):
layout = self.layout layout = self.layout
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="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"

View file

@ -29,15 +29,16 @@ class SlicingObject():
def __init__(self, obj: Object | None = None, parent: str = "") -> None: def __init__(self, obj: Object | None = None, parent: str = "") -> None:
if not bpy.context.scene: raise Exception('No scene currently open!') if not bpy.context.scene: raise Exception('No scene currently open!')
if obj: self.name = str(obj.name) self.name = str(obj.name) if obj else ""
self.parent = parent self.parent = parent
self.attributes = {} self.attributes = {}
self.modifiers = []
depsgraph = bpy.context.evaluated_depsgraph_get() depsgraph = bpy.context.evaluated_depsgraph_get()
assert bpy.context.scene.unit_settings assert bpy.context.scene.unit_settings
scene_scale: float = bpy.context.scene.unit_settings.scale_length scene_scale: float = bpy.context.scene.unit_settings.scale_length
if ns := getattr(obj, 'nodeslicer', None): if obj and (ns := getattr(obj, 'nodeslicer', None)):
self.object_type = ns.object_type self.object_type = ns.object_type
self.extruder = ns.extruder self.extruder = ns.extruder
self.modifiers = [{'param_id': m.field, 'param_value': m.value_str} for m in ns.modifiers] self.modifiers = [{'param_id': m.field, 'param_value': m.value_str} for m in ns.modifiers]