1
$\begingroup$

My script prints every node group name, how many nodes are inside it, and its users. The node groups are sorted by how many nodes are inside them so I can find possible redundant/duplicated node groups in my project.

--------------------------------------------------
NodeGroup.015
7 nodes
3 users
    Material: Marble Faux Brick
    Material: Slit Guide
--------------------------------------------------
NodeGroup.025
7 nodes
1 users
    Material: [Guard Rails] Travertine Blocks
--------------------------------------------------
NodeGroup.029
7 nodes
2 users
    Material: [Stands] Marble Stands
    Material: [Temple Stairs] Marble Steps NEW
--------------------------------------------------

But searching for the printed node group in its material brings no results since they go by different names in materials. So while bpy.data.node_groups gives Nodegroup.015, it's actually Group.008 when in a material.

enter image description here enter image description here

How do I match Nodegroup.015 to Group.008 so I can print this alternative name? (Why are there two different unique identifiers?) Or is my whole approach wrong?

import re

import bpy
from bpy.types import bpy_prop_collection


# No idea what this does, stole it from here: 
# https://blender.stackexchange.com/questions/230605/how-to-find-the-users-of-a-material
def search(ID):
    def users(col):
        ret =  tuple(repr(o) for o in col if o.user_of_id(ID))
        return ret if ret else None 
    return filter(None, (
        users(getattr(bpy.data, p)) 
        for p in  dir(bpy.data) 
        if isinstance(
                getattr(bpy.data, p, None), 
                bpy_prop_collection
                )                
        )
        )


def show_materials(user):
    material_pattern = re.search(r"(?<=bpy\.data\.materials\[').+", user)
    if material_pattern:
        material_name = material_pattern.group(0)[:-2]
        print(" " * 4 + "Material:", material_name)
        return material_name


nodes = bpy.data.node_groups
node_group_names = [node_group.name for node_group in nodes]
contained_nodes = [len(node_group.nodes) for node_group in nodes]
sorted_dictionary = {key: value for key, value in sorted(dict(zip(node_group_names, contained_nodes)).items(), key=lambda item: item[1])}

for node_group, value in sorted_dictionary.items():
    print("-" * 50)
    print(node_group)
    print(value, "nodes")
    print(nodes[node_group].users, "users")
    for collection_of_users in search(nodes[node_group]):
        for user in collection_of_users:
            material_name = show_materials(user)
            if material_name:
                print(bpy.data.materials[material_name].node_tree.nodes[??]) # ??
$\endgroup$
3
  • $\begingroup$ Hello. Node groups have a unique identifier for retrieving them from bpy.data.node_groups, then they have a unique identifier to each material's node tree from bpy.data.materials["your_material].node_tree.nodes, then they have a label which can be shard by any number of nodes $\endgroup$
    – Gorgious
    Nov 27, 2023 at 6:47
  • $\begingroup$ to say that another way, a node group is used inside a material by a node that refers the node group. That node and the node group have different names. $\endgroup$
    – lemon
    Nov 27, 2023 at 6:55
  • $\begingroup$ @lemon Ah ok that clears things up. But still, how would I match the node group to the node that references it? $\endgroup$ Nov 27, 2023 at 7:43

1 Answer 1

2
$\begingroup$

Gorgious has just posted an answer that will simplify your life, here.

As you can:

dict_of_group_to_users = bpy.data.user_map(subset=bpy.data.node_groups)

But for some explanation concerning group nodes (the one you see in materials) and node groups (the ones that can be referenced by the group nodes):

# loop over all materials that have a node tree defined
for mat in (m for m in bpy.data.materials if m.node_tree):
    # search for nodes that refer a node group
    groups = [node for node in mat.node_tree.nodes if node.type == 'GROUP']
    # print the node name and the refered node group name
    # group.node_tree is effectively the node group
    for group in groups:
        print(group.name, group.node_tree.name)

Or from node groups:

# loop over all node groups
for ng in bpy.data.node_groups:
    # loop over the materials
    # and print node group name, material name, count of group usage inside the material
    for mat in bpy.data.materials:
        print(ng.name, mat.name, mat.user_of_id(ng))

So I think that using the result of following, you have all you need:

dict_of_group_to_users = bpy.data.user_map(subset=bpy.data.node_groups)

as you can just loop over the keys and for each key loop over the values. And for each value, search its node tree for nodes that are of type 'group' and reference the key as node tree.


Concerning the search(ID) function from batFINGER here, some explanation:

def search(ID):
    def users(col):
        # Make a tuple of all objects that are user of this ID
        ret =  tuple(repr(o) for o in col if o.user_of_id(ID))
        # Returns None if no user
        return ret if ret else None
    # filter all elements that are None below
    return filter(None, (
        # For all that satisfy the conditions below, returns users(col)
        users(getattr(bpy.data, p)) 
        # get all the properties/attributes of bpy.data
        for p in  dir(bpy.data) 
        # keeping only the one that are a subtype of bpy_prop_collection
        if isinstance(
                getattr(bpy.data, p, None), 
                bpy_prop_collection
                )                
        )
        )
$\endgroup$
1
  • $\begingroup$ Thank you, this works great. $\endgroup$ Nov 27, 2023 at 12:34

You must log in to answer this question.

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