diff --git a/core/nodeslicer/nodes/inputs.py b/core/nodeslicer/nodes/inputs.py index f9e8b72..5653c46 100644 --- a/core/nodeslicer/nodes/inputs.py +++ b/core/nodeslicer/nodes/inputs.py @@ -32,6 +32,50 @@ class SLICENODE_InputObject(InputNode): input_type = "Object" bl_icon = "OBJECT_DATA" +@register_class +class SLICENODE_CollectionObjects(SliceNode): + bl_label = "Collection Objects" + + collection: bpy.props.PointerProperty( + name="Collection", + type=bpy.types.Collection, + update=notify_tree_changed + ) # pyright: ignore[reportInvalidTypeForm] + + def init(self, context): + self._update_outputs() + + def update(self): + super().update() + self._update_outputs() + + def draw_buttons(self, context, layout): + assert layout + layout.prop(self, "collection", text="") + + def _update_outputs(self): + if not self.collection: + self.outputs.clear() + return + + objs = [obj for obj in self.collection.objects if obj.type == 'MESH'] + expected_names = [obj.name for obj in objs] + + # Remove outputs that are no longer in the collection + to_remove = [o for o in self.outputs if o.name not in expected_names] + for o in to_remove: + self.outputs.remove(o) + + # Add new outputs + for name in expected_names: + if name not in self.outputs: + self.add_output("SLICESOCK_ObjectRo", name, identifier=name) + + def evaluate_output(self, socket_name: str, **kwargs) -> Any: + if self.collection and socket_name in bpy.data.objects: + return bpy.data.objects[socket_name] + return None + @register_class class SLICENODE_InputSettings(InputNode): input_type = "Settings" diff --git a/core/nodeslicer/sockets.py b/core/nodeslicer/sockets.py index 78f9612..e5f5245 100644 --- a/core/nodeslicer/sockets.py +++ b/core/nodeslicer/sockets.py @@ -44,8 +44,7 @@ class ReadonlySocket(): def draw(self, context: Any, layout: Any, node: Any, text: str | None): return self._draw_as_ro(context, layout, node, text) # pyright: ignore[reportAttributeAccessIssue] -@register_class -class SLICESOCK_Object(InputNodeSocket): +class SLICESOCK_ObjectCommon(InputNodeSocket): bl_label = "Object" value: NfPointerProperty( @@ -55,6 +54,10 @@ class SLICESOCK_Object(InputNodeSocket): ) # pyright: ignore[reportInvalidTypeForm] bl_color = (0.9, 0.7, 0.2, 1.0) +@register_class +class SLICESOCK_ObjectRo(ReadonlySocket, SLICESOCK_ObjectCommon): ... +@register_class +class SLICESOCK_Object(SLICESOCK_ObjectCommon): ... class SLICESOCK_Float_Common(InputNodeSocket): bl_label = "Float" diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index a565144..9b5068e 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -124,6 +124,11 @@ def depsgraph_update_handler(scene, depsgraph): node.sync_sockets() except Exception: pass + elif node.bl_idname == "SLICENODE_CollectionObjects" and hasattr(node, "_update_outputs"): + try: + node._update_outputs() + except Exception: + pass @register_class @@ -141,27 +146,33 @@ class SLICENODE_OT_GroupToggle(Operator): bl_description = "Enter or exit the selected node group" bl_options = {'REGISTER', 'UNDO'} + node_name: bpy.props.StringProperty() # pyright: ignore[reportInvalidTypeForm] + @classmethod def poll(cls, context): assert context.space_data return (context.space_data.type == 'NODE_EDITOR' and context.space_data.tree_type == "SLICETREE_NodeTree") # pyright: ignore[reportAttributeAccessIssue] - def execute(self, context) -> set[OperatorReturnItems]: + def execute(self, context) -> set['OperatorReturnItems']: space = context.space_data path = getattr(space, 'path', None) if not path: return {'CANCELLED'} + + tree = getattr(space, 'edit_tree', None) - # If we are inside a group, popping returns us to the parent - # We want to exit if: 1. No node is selected, OR 2. We press Tab while editing inside a group - active_node = context.active_node + target_node = None + if self.node_name and tree and self.node_name in tree.nodes: + target_node = tree.nodes[self.node_name] + else: + target_node = context.active_node - # If we have an active group node, we try to enter it - if active_node and active_node.bl_idname == "SLICENODE_Group": - group_tree = getattr(active_node, "slicer_tree", None) + # If we have a target group node, we try to enter it + if target_node and target_node.bl_idname == "SLICENODE_Group": + group_tree = getattr(target_node, "slicer_tree", None) if group_tree: - path.append(group_tree, node=active_node) + path.append(group_tree, node=target_node) return {'FINISHED'} # Otherwise, if we are nested, we pop out @@ -171,6 +182,7 @@ class SLICENODE_OT_GroupToggle(Operator): return {'CANCELLED'} + @register_class class SLICENODE_OT_GroupMake(Operator): bl_idname = "slicenode.group_make" @@ -451,6 +463,7 @@ class SLICENODE_MT_DataInput(Menu): layout.operator("node.add_node", text="Vector").type = "SLICENODE_InputVector" layout.separator() layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject" + layout.operator("node.add_node", text="Collection Objects").type = "SLICENODE_CollectionObjects" layout.operator("node.add_node", text="Settings").type = "SLICENODE_InputSettings" layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf" layout.separator() @@ -542,6 +555,8 @@ class SLICENODE_MT_Group(Menu): layout.operator("node.group_ungroup", text="Ungroup") layout.operator("slicenode.group_toggle", text="Edit Group") layout.separator() + layout.operator("node.add_node", text="Add Group Node").type = "SLICENODE_Group" + layout.separator() layout.operator("node.add_node", text="Group Input").type = "NodeGroupInput" layout.operator("node.add_node", text="Group Output").type = "NodeGroupOutput" diff --git a/infra/blender/blender_slice_nodes_generic.py b/infra/blender/blender_slice_nodes_generic.py index 4de1293..f68f686 100644 --- a/infra/blender/blender_slice_nodes_generic.py +++ b/infra/blender/blender_slice_nodes_generic.py @@ -179,6 +179,7 @@ def _get_current_group_node() -> bpy.types.Node | None: @register_class class SLICENODE_Group(bpy.types.NodeCustomGroup, AutoBlIdName): bl_label = "Group" + bl_icon = 'NODETREE' def _tree_updated(self, context): try: @@ -260,7 +261,11 @@ class SLICENODE_Group(bpy.types.NodeCustomGroup, AutoBlIdName): def draw_buttons(self, context, layout): assert layout - layout.prop(self, "slicer_tree", text="") + row = layout.row(align=True) + row.prop(self, "slicer_tree", text="") + if self.slicer_tree: + op = row.operator("slicenode.group_toggle", text="", icon='NODETREE') + op.node_name = self.name def draw_label(self): if self.slicer_tree: