6
$\begingroup$

I want to execute the node tree continuously which will execute every node.

enter image description here

Currently to execute the node tree I have to run this command bpy.data.node_groups['NodeTree'].execute(bpy.context) every time.

enter image description here

import bpy
from bpy.types import NodeTree, Node, NodeSocket
from bpy.props import *
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem


class CustomNodeTree(NodeTree):
    '''A custom node tree type that will show up in the editor type list'''
    bl_label = 'Custom Node Tree'
    bl_icon = 'NODETREE'

    def execute(self, context):
        for node in self.nodes:
            node.execute(context)


class BaseNode:
    @classmethod
    def poll(cls, ntree):
        return ntree.bl_idname == 'CustomNodeTree'

    def execute(self, context):
        pass


class CustomNodeText(Node, BaseNode):
    bl_label = 'Text'

    text : StringProperty(name='',)

    def init(self, context):
        self.outputs.new('NodeSocketString', 'Text')

    def draw_buttons(self, context, layout):
        layout.prop(self, 'text')

    def execute(self, context):
        self.outputs['Text'].default_value = self.text


class CustomNodeFloat(Node, BaseNode):
    bl_label = 'Float'

    float : FloatProperty(name='',)

    def init(self, context):
        self.outputs.new('NodeSocketFloat', 'Float')

    def draw_buttons(self, context, layout):
        layout.prop(self, 'float')

    def execute(self, context):
        self.outputs['Float'].default_value = self.float

    def execute(self, context):
        self.outputs['Float'].default_value = self.float


class CustomNodePrint(Node, BaseNode):
    bl_label = 'Print'

    def init(self, context):
        self.inputs.new('NodeSocketVirtual', 'Print')

    def draw_buttons(self, context, layout):
        if self.inputs['Print'].is_linked:
            print_value = self.inputs['Print'].links[0].from_socket.default_value
            layout.label(text=f'{print_value}')


class CustomNodeCategory(NodeCategory):
    @classmethod
    def poll(cls, context):
        return context.space_data.tree_type == 'CustomNodeTree'


# all categories in a list
node_categories = [
    # identifier, label, items list
    CustomNodeCategory('UTILITIES', 'Utilities', items=[
        NodeItem('CustomNodeText'),
        NodeItem('CustomNodeFloat'),
        NodeItem('CustomNodePrint'),
    ]),
]


classes = (
    CustomNodeTree,
    CustomNodeText,
    CustomNodeFloat,
    CustomNodePrint,
)


def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    nodeitems_utils.register_node_categories('CUSTOM', node_categories)


def unregister():
    nodeitems_utils.unregister_node_categories('CUSTOM')

    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)

Added node Join

It's not updating live when I change the value.

import bpy
from bpy.types import NodeTree, Node, NodeSocket
from bpy.props import *
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem


class CustomNodeTree(NodeTree):
    '''A custom node tree type that will show up in the editor type list'''
    bl_label = 'Custom Node Tree'
    bl_icon = 'NODETREE'

    def execute(self, context):
        for node in self.nodes:
            node.execute(context)


class BaseNode:
    @classmethod
    def poll(cls, ntree):
        return ntree.bl_idname == 'CustomNodeTree'

    def execute(self, context):
        pass


class CustomNodeText(Node, BaseNode):
    bl_label = 'Text'

    text : StringProperty(name='',)

    def init(self, context):
        self.outputs.new('NodeSocketString', 'Text')

    def draw_buttons(self, context, layout):
        layout.prop(self, 'text')

    def execute(self, context):
        self.outputs['Text'].default_value = self.text


class CustomNodeFloat(Node, BaseNode):
    bl_label = 'Float'

    float : FloatProperty(name='',)

    def init(self, context):
        self.outputs.new('NodeSocketFloat', 'Float')

    def draw_buttons(self, context, layout):
        layout.prop(self, 'float')

    def execute(self, context):
        self.outputs['Float'].default_value = self.float


class CustomNodeJoin(Node, BaseNode):
    bl_label = 'Join'

    def init(self, context):
        self.inputs.new('NodeSocketString', 'Value1')
        self.inputs.new('NodeSocketString', 'Value2')

        self.outputs.new('NodeSocketString', 'Value')

    def draw_buttons(self, context, layout):
        pass

    def update(self):
        if self.inputs['Value1'].is_linked and self.inputs['Value2'].is_linked:
            text = self.inputs['Value1'].links[0].from_socket.default_value + self.inputs['Value2'].links[0].from_socket.default_value
        elif self.inputs['Value1'].is_linked and not self.inputs['Value2'].is_linked:
            text = self.inputs['Value1'].links[0].from_socket.default_value + self.inputs['Value2'].default_value
        elif self.inputs['Value2'].is_linked and not self.inputs['Value1'].is_linked:
            text = self.inputs['Value1'].default_value + self.inputs['Value2'].links[0].from_socket.default_value
        else:
            text = self.inputs['Value1'].default_value + self.inputs['Value2'].default_value

        self.outputs['Value'].default_value = text


class CustomNodePrint(Node, BaseNode):
    bl_label = 'Print'

    def init(self, context):
        self.inputs.new('NodeSocketString', 'Print')

    def draw_buttons(self, context, layout):
        if self.inputs['Print'].is_linked:
            print_value = self.inputs['Print'].links[0].from_socket.default_value
            layout.label(text=f'{print_value}')


class CustomNodeCategory(NodeCategory):
    @classmethod
    def poll(cls, context):
        return context.space_data.tree_type == 'CustomNodeTree'


# all categories in a list
node_categories = [
    # identifier, label, items list
    CustomNodeCategory('UTILITIES', 'Utilities', items=[
        NodeItem('CustomNodeText'),
        NodeItem('CustomNodeFloat'),
        NodeItem('CustomNodeJoin'),
        NodeItem('CustomNodePrint'),
    ]),
]


classes = (
    CustomNodeTree,
    CustomNodeText,
    CustomNodeFloat,
    CustomNodeJoin,
    CustomNodePrint,
)


def execute_active_node_tree():
    node_editor = next((a for a in bpy.context.screen.areas if a.type == 'NODE_EDITOR'), None)
    if (node_editor == None): return
    for space in node_editor.spaces:
        node_tree = getattr(space, 'node_tree')
        if (node_tree):
            node_tree.execute(bpy.context)
            break

CUSTOM_NODE_TEXT_HANDLE = 0
CUSTOM_NODE_FLOAT_HANDLE = 1
CUSTOM_NODE_JOIN_HANDLE = 2


def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

    nodeitems_utils.register_node_categories('CUSTOM', node_categories)

    bpy.msgbus.subscribe_rna(
        key=CustomNodeText,
        owner=CUSTOM_NODE_TEXT_HANDLE,
        args = (),
        notify=execute_active_node_tree,
        options={"PERSISTENT",}
    )
    bpy.msgbus.subscribe_rna(
        key=CustomNodeFloat,
        owner=CUSTOM_NODE_FLOAT_HANDLE,
        args = (),
        notify=execute_active_node_tree,
        options={"PERSISTENT",}
    )
    bpy.msgbus.subscribe_rna(
        key=CustomNodeJoin,
        owner=CUSTOM_NODE_JOIN_HANDLE,
        args = (),
        notify=execute_active_node_tree,
        options={"PERSISTENT",}
    )


def unregister():
    bpy.msgbus.clear_by_owner(CUSTOM_NODE_JOIN_HANDLE)
    bpy.msgbus.clear_by_owner(CUSTOM_NODE_FLOAT_HANDLE)
    bpy.msgbus.clear_by_owner(CUSTOM_NODE_TEXT_HANDLE)

    nodeitems_utils.unregister_node_categories('CUSTOM')

    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)
$\endgroup$
10
  • $\begingroup$ as far as i know you can't create your own custom nodes, am i wrong? can you share some documentation? the "custom nodes" i know are group nodes. $\endgroup$ Jul 8, 2022 at 1:49
  • $\begingroup$ Yes, you can create custom node_tree and nodes. Go Scripting > Templates > Python > Custom Nodes $\endgroup$
    – Karan
    Jul 8, 2022 at 2:11
  • $\begingroup$ yes there may be that template but the devs probably did not implement this yet in blender? were you able to create any visual custom nodes? if not, then probably because they havn't implemented this feature. if you were able to display anything, can you share your blend file? $\endgroup$ Jul 8, 2022 at 2:16
  • $\begingroup$ I want to create a custom node editor, node_tree, and nodes similar to other Node Editors i.e. Geometry, Shader etc. $\endgroup$
    – Karan
    Jul 8, 2022 at 2:20
  • 1
    $\begingroup$ Nodes alone don't do nothing. You need a node parser to create an execution plan, and hook up changes to nodes in order to update the execution. $\endgroup$
    – Secrop
    Jul 8, 2022 at 7:41

1 Answer 1

4
$\begingroup$

Thanks for the very clear example on custom nodes!

I was able to make it work by using bpy.msgbus.subscribe_rna. Apparently when subscribed to a Node rna, it triggers callbacks whenever a node output changes.

On my tests, it didn't work when registering the Node type, probably because Blender creates a new RNA for subclasses. So you need to register/unregister callbacks for each type of Node.

Also, you need to keep an integer for each handle, so you can unregister them, as explained here.

The execute_active_node_tree might be improved/cleaned.

enter image description here

def execute_active_node_tree():
    node_editor = next((a for a in bpy.context.screen.areas if a.type == 'NODE_EDITOR'), None)
    if (node_editor == None): return
    for space in node_editor.spaces:
        node_tree = getattr(space, 'node_tree')
        if (node_tree):
            node_tree.execute(bpy.context)
            break

CUSTOM_NODE_TEXT_HANDLE = 0
CUSTOM_NODE_FLOAT_HANDLE = 1

def register():
    #...
    bpy.msgbus.subscribe_rna(
        key=CustomNodeText,
        owner=CUSTOM_NODE_TEXT_HANDLE,
        args = (),
        notify=execute_active_node_tree,
        options={"PERSISTENT",}
    )
    bpy.msgbus.subscribe_rna(
        key=CustomNodeFloat,
        owner=CUSTOM_NODE_TEXT_HANDLE,
        args = (),
        notify=execute_active_node_tree,
        options={"PERSISTENT",}
    )


def unregister():
    # ...
    bpy.msgbus.clear_by_owner(CUSTOM_NODE_FLOAT_HANDLE)
    bpy.msgbus.clear_by_owner(CUSTOM_NODE_TEXT_HANDLE)
$\endgroup$
1
  • $\begingroup$ I made a new node Join but it is not updating live. Changes are made in the question. $\endgroup$
    – Karan
    Mar 8 at 9:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .