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)