Here is a little script I wrote to add a node group to many materials. The script ...
- it loops over all objects in the blend file
- checks the material slots of the objects
- and adds a node group ("Dust") to the material's node tree if the node group is not already present.
- it also connects the first output of the group to the Principled BSDF's Sheen input and sets a few values.
If you want to modify only objects that are in a certain collection you need to change line 5 to fill the objects set (How to list all collections and their objects?). Note, that checking if a collection is active/enabled in the outliner is a bit more complicated because the View Layer controls this (see How can I check which of my collections are hidden in viewport?). So I kept it simple.
Don't forget to make a backup before you run the script. In the system console (main menu Window > Toggle System Console), you can check what the script is doing. Ctrl+C in the system console window will stop it.
import bpy
from mathutils import Vector
'list' (set) of objects to check
objects = set(obj for obj in bpy.data.objects)
keep track of the materials done
materials_done = set()
def material_uses_group(material, node_group_to_check) -> bool:
for node in material.node_tree.nodes:
# check whether the node is a node group and has the same name as the group to be added.
# Note: node.name is an internal name (e.g. 'Group') as shown in the side panel.
# It's not the name of the node as shown on the node, it's node.node_tree.name
# (e.g. 'Dust'). It can be overwritten by node.label.
# Only groups have node.node_tree attribute, can be checked with hasattr()
if node.bl_idname == 'ShaderNodeGroup':
#print(f"DEBUG: node.bl_idname = {node.bl_idname}, node.node_tree.name = {node.node_tree.name}, node_group.name = {node_group_to_check.name}")
if hasattr(node, 'node_tree') and node.node_tree.name == node_group_to_check.name:
return True
return False
def add_node_group_to_material(material, node_group_to_add) -> bool:
# find the Principled BSDF
print(f" - looking for Principled BSDF node in material {material.name}")
pbsdf = None
for node in material.node_tree.nodes:
if node.bl_idname == 'ShaderNodeBsdfPrincipled':
pbsdf = node
break
# add node group if found
if pbsdf != None:
print(f" + found it, adding group to material {material.name}")
new_group = material.node_tree.nodes.new('ShaderNodeGroup')
new_group.node_tree = node_group_to_add
new_group.location = pbsdf.location - Vector([240, 200]) # <----- position, left of the P.BSDF
# linking and values
material.node_tree.links.new(pbsdf.inputs['Sheen Roughness'], new_group.outputs[0])
pbsdf.inputs['Sheen Weight'].default_value = 0.250 # <------- value
pbsdf.inputs['Sheen Tint'].default_value = [0.5, 0.5, 0.5, 1.0] # <---- RGBA
return True
else:
print(f" - no Principled BSDF node found in material {material.name}, ignored")
return False
def main() -> int:
# actually, this is a node tree (my_node_group.bl_idname == 'ShaderNodeTree').
# The group used in the material is 'ShaderNodeGroup'.
my_node_group = bpy.data.node_groups['Dust'] # <---- the node group to add to the materials
# main loop
for obj in objects:
if obj.type != 'MESH':
print(f"Skipping object {obj.name} (type {obj.type})")
continue
print(f"Checking materials of {obj.name} ")
# check material slots
for idx, slot in enumerate(obj.material_slots):
if slot.material is None:
print(f"- WARN: found no material {obj.name}, empty slot #{idx}, skip")
continue
if slot.material.name in materials_done: # already done?
print(f"- found again material {slot.material.name} in slot #{idx}, skip")
continue
if not slot.material.use_nodes:
print(f"- found material {slot.material.name} in slot #{idx} but it does not use nodes, skip")
continue
print(f"+ found material {slot.material.name} in slot #{idx}, trying to add node group")
if material_uses_group(slot.material, my_node_group):
print(f" - no, node group is already in material, skip")
materials_done.add(slot.material.name)
continue
if add_node_group_to_material(slot.material, my_node_group):
print(" OK, node group added")
materials_done.add(slot.material.name)
if name == "main":
main()
print("-- DONE --")
Related questions: