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
import bpy
def images_in_tree(node_tree):
for node in node_tree.nodes:
if hasattr(node, "image"):
yield node.image
if hasattr(node, "node_tree"):
yield from images_in_tree(node.node_tree)
for mat in bpy.data.materials:
if mat.use_nodes:
print(set(filter(None, images_in_tree(mat.node_tree))))
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
the like
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
layout.prop(mat, "image_users")
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.
import bpy
from collections import defaultdict
from functools import lru_cache
@lru_cache(16) # play with this
def num_images(mat):
tag = defaultdict(bool)
def unique(node):
img = getattr(node, "image", None)
if tag[img]:
return False
tag[img] = True
return img is not None
def images_in_tree(node_trees):
for node_tree in node_trees:
if tag[node_tree]:
continue
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")))
return sum(images_in_tree((mat.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 mat:
self.layout.prop(mat, "image_users")
if random() <= 0.5:
num_images.cache_clear()
self.layout.label(text=f"{num_images(mat)}")
bpy.types.TEXT_HT_footer.prepend(draw)
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
[im.users for im in D.images]
copy a material, recreate list. Any that have incremented user count are in use by the material. Then remove dupe. Similarly with materials using node groups using images. $\endgroup$im.users
is just user count I think and is a simple integer. are there any other ways to get users? $\endgroup$