6
$\begingroup$

I'm trying to register a PointerProperty to a node in a material's node tree.

After hours of trying a number of things, I suspect this is impossible and I think it's related to nodes not having their own bpy.data entry.

Basically I want to avoid having to repeatedly iterate over the nodes of a materials's node tree. So, once I found my node, I want to store in a PointerProperty.

I'm doing something similar with certain objects:

myobj = PointerProperty(name="My Obj", type=bpy.types.Object)

Which allows me to assign an object like so:

bpy.data.objects[0].MyPropGroup.myobj = bpy.data.objects[1]

And access the object like this:

bpy.data.objects[0].MyPropGroup.myobj.select = False
bpy.data.objects[0].MyPropGroup.myobj.hide_render = True

So far so good. I'd like to do the same with nodes, set the property like so

bpy.data.materials[0].MyPropGroup.mynode = bpy.data.materials[0].node_tree.nodes['MyNode']

And access it like this:

bpy.data.materials[0].MyPropGroup.mynode.mute = True

Unfortunately, all my attempts to register the pointer failed. I've tried these and a buch more.

mynode = PointerProperty(name="My Node", type=bpy.types.Node)
mynode = PointerProperty(name="My Node", type=bpy.types.NodeGroup)
mynode = PointerProperty(name="My Node", type=bpy.types.ShaderNode)
mynode = PointerProperty(name="My Node", type=bpy.types.ShaderNodeGroup)

All of them failed with

ValueError: bpy_struct "MyPropertyGroup" registration error: mynode could not register

It is acually a node group I am trying to point to, but would hope to do it for other node types as well.

I've succeeded registering

mynode = PointerProperty(name="My Node", type=bpy.types.ShaderNodeTree)

Which I then can assign the group node to. However I can't access it to manipulate node properties (like mute for instance). I can only access the group node tree of the group.

Any pointers - no pun intended - are appreciated. Although I could select the node group in question by name, instead of iterating over the node tree, I'd like to avoid it.

$\endgroup$

1 Answer 1

6
$\begingroup$

You cannot do this directly. With a PointerProperty you can only refer to a subclass of bpy.types.PropertyGroup or bpy.types.ID.

A Node is none of the above. Best way to solve this is to use a PointerProperty to point to the NodeTree (what is a subclass of bpy.types.ID) and use an additional bpy.props.StringProperty to select the specific node in that tree.

Note: Renaming the node will make the StringProperty Invalid.

Version 2.79

class MyPropertyGroup(bpy.types.PropertyGroup):
    material = bpy.props.PointerProperty(
        name="Material",
        type=bpy.types.Material,
    )
    node_name = bpy.props.StringProperty(
        default="",
        name="Node",
        description="Name of the node"
    )
    @property
    def node(self):
        mat = self.material
        if mat:
            return mat.node_tree.nodes.get(self.node_name)


# draw function
def draw(self, context)
    layout = self.layout
    
    # NOTE: item is an instance of MyPropertyGroup

    layout.prop(item, "material")
    node_selection_active = item.material is not None and item.material.use_nodes
    row = layout.row()
    row.enabled = node_selection_active
    if node_selection_active:
        row.prop_search(item, "node_name", item.material.node_tree, "nodes")
    else:
        row.prop(item, "node_name")

Version 2.80+ (Use :instead of = for properties)

class MyPropertyGroup(bpy.types.PropertyGroup):
    material: bpy.props.PointerProperty(
        name="Material",
        type=bpy.types.Material,
    )
    node_name: bpy.props.StringProperty(
        default="",
        name="Node",
        description="Name of the node"
    )
    @property
    def node(self):
        mat = self.material
        if mat:
            return mat.node_tree.nodes.get(self.node_name)


# draw function
def draw(self, context)
    layout = self.layout
    
    # NOTE: item is an instance of MyPropertyGroup

    layout.prop(item, "material")
    node_selection_active = item.material is not None and item.material.use_nodes
    row = layout.row()
    row.enabled = node_selection_active
    if node_selection_active:
        row.prop_search(item, "node_name", item.material.node_tree, "nodes")
    else:
        row.prop(item, "node_name")
$\endgroup$
4
  • 1
    $\begingroup$ Thanks, that's what i feared. I've gone with the ŚtringProp, but I really don't like it. As you said, it's prone to break if sb. where to mess with the node naming. I like your implrementation, much appreciated! Accepting this as the answer. $\endgroup$
    – MACHIN3
    Feb 26, 2018 at 11:25
  • $\begingroup$ Hey @MACHIN3 I don't know if it's still a topic, but at least for my own nodes I like to add a random value or a time-stamp as signature. Then I track that instead of the name. Sadly I don't know how to do that with builtins, but I think you could grab a 'post-draw' hook to re-check for changes. Thats messy, but it should work. $\endgroup$
    – Teck-freak
    Aug 27, 2018 at 16:17
  • $\begingroup$ For built-ins you could grab a postdraw-hook and check if your pointer still is valid. If it's not, the active node should be the searched one. That might be faster. Still faster might be just refetching and storing it each time when you asked for it and it breaks. $\endgroup$
    – Teck-freak
    Aug 27, 2018 at 16:26
  • $\begingroup$ Also wanted to say thank you for that approach of storing a reference to nodes. Much more elegent then I did before! $\endgroup$
    – ndee
    Feb 21, 2021 at 12:47

You must log in to answer this question.

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