Recurse the tree.
In question code, the major rate determining step will be checking the list for dupe to append, often quicker to add them all and use a set if only unique members are of interest.
Suggest can use list comprehension, or recursively walk the tree.
Here is an example, pass a node tree to a method and walk its nodes, if it has an image property yield it, if it has a node tree yield from it.
Personally I like the simplicity of yield syntax usage for traversing a tree, and the low memory hogging nature of generators, however they need a little tweaking to optimize for speed, see below
for node in node_tree.nodes:
if hasattr(node, "image"):
if hasattr(node, "node_tree"):
yield from images_in_tree(node.node_tree)
for mat in bpy.data.materials:
Note could yield above
getattr(node, "image", None) to avoid testing and filter out the Nones, however it may be a better idea to yield the node_tree, and the node. The ID data of the node_tree
node_tree.id_data will be a material or a node group.
Just the number of unique images in tree.
In answer to
Keep track of how many images are in use in a Material (Or node_tree)
in real time, printed on the UI, without having to press buttons or
Instead of yielding the images returns only a count.
Used a boolean default dictionary to tag items. Any item is given the default value false
In response to
fyi, you can't use
tag here. Or any optimization like caching.
Have tagged both the nodegroups and images with one dict to make the method directly usable in a draw method, instead of using say a getter of a material property,
bpy.types.Material.image_users = IntProperty(get=num_images)
In which case could lay it out with
and using the
collection.tag(False) would be Ok in this context, and removes the need for the default dictionary.
As noted yielding and recursion can be slow in python, however using
functools.lru_cache seriously fixes this see similar gain here https://blender.stackexchange.com/a/199075/15543 as well as other optimizations used. See lru_cache link re expiring or clearing cache on some interval
Basically it is caching the result of a call to memory.
from collections import defaultdict
from functools import lru_cache
@lru_cache(16) # play with this
tag = defaultdict(bool)
img = getattr(node, "image", None)
tag[img] = True
return img is not None
for node_tree in node_trees:
tag[node_tree] = True
yield sum(unique(n) for n in node_tree.nodes)
yield from images_in_tree((n.node_tree for n in node_tree.nodes if hasattr(n, "node_tree")))
# make an int material property
bpy.types.Material.image_users = bpy.props.IntProperty(get=num_images.__wrapped__)
# tack a draw method to text editor footer to test
from random import random
def draw(self, context):
mat = context.object.active_material
if random() <= 0.5:
The material property uses an uncached version of the method above,
num_images.__wrapped__ the label uses the cached method. The cache is cleared each time a random is below a threshold. The expectation above is that it is only calculated approximately every 2nd time.
Other methods to consider would be using a counter on the UI class, clearing in a
draw, poll or __init__ method of classes in different regions, a draw call back.. (Or as mentioned prior expire the cache) or simply not use it at all.
Extra image tacked into test file, showing 8 images in material, removed shows 7, undo back to 8