14
$\begingroup$

Is it possible to find nodes connected to a node?

For example, could you trace your way back through a node tree from the material output node by finding the node connected to the material output node, then finding the node(s) connected to that node, then the nodes connected to those nodes, etc.?

How is this possible?

$\endgroup$

2 Answers 2

26
$\begingroup$

The python path to the links between each node is

bpy.data.materials[].node_tree.nodes[].inputs[].links[].from_node

So for each material we want to start at the Material Output node then loop through each array of inputs[] and links[] to move through the connections. Going this way (using from_node) there should only be one link for each input, but if you want to reverse the order (by using to_node) you can have multiple links for each output.

import bpy

def followLinks(node_in):
    for n_inputs in node_in.inputs:
        for node_links in n_inputs.links:
            print("going to " + node_links.from_node.name)
            followLinks(node_links.from_node)

for mat in bpy.data.materials:
    print("Traversing " + mat.name)
    for mat_node in mat.node_tree.nodes:
        if mat_node.type == 'OUTPUT_MATERIAL':
            # we start at the material output node
            print("Starting at " + mat_node.name)
            followLinks(mat_node)
$\endgroup$
4
  • $\begingroup$ I have thousands of objects for which I need to update their materials' nodes/links using Blender's Python API. I am pretty new to nodes stuff in Cycles and I haven't been able to wrap my head around how I can do that. It seems that some version of your solution here should work for what I want to do. I wonder, could you please take a look at my question here? It would be great if you know how I can do what I want. $\endgroup$
    – Amir
    Commented Mar 8, 2018 at 16:39
  • $\begingroup$ Hi, could you show a code sample how to get the inputs index of connected node to the output of a given node? For example, if I have a normalMap node connected to Principled BSDF, I can get the Principled BSDF by mat.node_tree.nodes['Normal Map'].outputs[0].links[0].to_node, but is there a way to find it's connected to inputs[19] of Principled BSDF? $\endgroup$
    – June Wang
    Commented Jul 10, 2021 at 13:31
  • $\begingroup$ @JuneWang you would be better asking that as a new question $\endgroup$
    – sambler
    Commented Jul 13, 2021 at 6:10
  • 1
    $\begingroup$ Just in case it will help someone, this is also true for geometry nodes. $\endgroup$
    – ofekp
    Commented Feb 15, 2022 at 20:42
2
$\begingroup$

Hello from 2024.

Here's a non-recursive (as in the function doesn't call itself over and over. Nodes are retrieved recursively through a stack), customizeable variant. Works with the latest Blender (4.1.1), can traverse insanely large materials.

Basically, this is a "pro" way of traversing trees. Hope this helps.

import bpy


# material =       Material to traverse
# starting_point = Start at this node
# yield_reroute =  Whether to yield reroute nodes

# 'material' can be none, if 'starting_point' is provided
def walk_node_tree_backwards(
    material=None,
    starting_point=None,
    yield_reroute=False
):
    # Realistically, most materials only have one 'Material Output'.
    # Don't traverse potentially large trees twice, if possible.
    root = starting_point or material.node_tree.nodes.get('Material Output')

    # Couldn't get the starting point node the easy way.
    # Find the material output manually.
    if not root:
        for node in material.node_tree.nodes:
            if node.type == 'OUTPUT_MATERIAL':
                 root = node
                 break
    
    # If still no starting point - give up
    if not root:
        raise LookupError(
            f"""Could not get the starting point. Current starting point is: >{root}<. """
            f"""There's either no starting point in material >{material}< """
            f"""or provided starting point >{starting_point}< is invalid """
            """OR BOTH material and provided starting point are somehow invalid..."""
        )

    # Walk DOWN the tree
    stack = [root]
    while stack:
        node = stack.pop()

        if node.type == 'REROUTE' and not yield_reroute:
            pass
        else:
            yield node

        for input in reversed(node.inputs):
            for link in input.links:
                stack.append(link.from_node)


if __name__ == '__main__':
    # Sample usage.
    
    # Go through every material in the blend file.
    for mat in bpy.data.materials:

        # A material can be not node-based
        if not mat.node_tree:
            print(f'=== Material >{mat.name}< is not node-based. Skipping ===')
            continue

        print(f'=== Traversing >{mat.name}< ===')
        for node in walk_node_tree_backwards(material=mat):
            print('\t', node)
$\endgroup$
4
  • 1
    $\begingroup$ I'm also not a fan of recursion it's just not efficient, a programming noob-trap like linked-lists. Also in Blender Python the limit of depth is only 995. In many languages the limit is much bigger but it's still hard to avoid a hard crash using it - whereas putting a limit on the stack size or detecting if you run out of memory for that stack is trivial by comparison… BTW, both this and sambler's answers have a potential problem of hanging, if a node tree has a circular reference - unlikely, because such a node tree is "invalid" (red link error), but possible. $\endgroup$ Commented Jul 7 at 21:24
  • 1
    $\begingroup$ Consider using root = next(n for n in material.node_tree.nodes if n.type == 'OUTPUT_MATERIAL'), without a 2nd "default" argument it will error out if nothing is found, though probably you still want to catch the error and redefine a more readable one as you do… BTW what's the point of printing the root in the error message if it's always going to be None? if <condition>: pass else: <block> can be simplified to if not (<condition>): <block>, and in this case further simplify to if yield_reroute or node.type != 'REROUTE': $\endgroup$ Commented Jul 7 at 21:25
  • $\begingroup$ @MarkusvonBroady $\endgroup$
    – Mr.Kleiner
    Commented Jul 8 at 3:52
  • 1
    $\begingroup$ @MarkusvonBroady Thanks for slimmer solutions for certain areas. If the one using this python snippet needs to catch super edge cases, then chances are they can totally modify these 20 lines of code or literally just only leave the traversing mechanism and supply the starting point themselves. As for printing root... Nature of habit, I guess... I always print everything, because otherwise I start developing schizophrenia, such as "am I TOTALLY sure it's what I think it is !?", "Is it ACTUALLY None or is it some other rubbish somehow ?!"... you got the point... $\endgroup$
    – Mr.Kleiner
    Commented Jul 8 at 3:52

You must log in to answer this question.

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