Sorry late to the party but I had a related question yesterday and I think this solves your issue.
This approach is based on this great answer as well as this great answer (thanks to @zeffii and @p2or). It's a simple operator, you can either install it like an Add-on or paste and run it in blenders text editor per session.
- Select all desired objects in the '3d View' (allows to limit the selection)
- In the 'Node Editor' select that Material Node, you'd like to replace
- From the header menu, select Node > Switch Shader Node
- Select your desired Node Type from the list
SwitchShaderNode.py
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "Switch Shader Node Type",
"author": "brockmann",
"description": "Switch active shader node within all shaders of selected objects",
"version": (0, 0, 1),
"blender": (2, 90, 0),
"category": "Node"
}
import bpy
import itertools
from bpy.props import EnumProperty
from bpy.types import Operator
# ------------------------------------------------------------------------
# Replace the node (by zeffii and p2or)
# based on https://blender.stackexchange.com/q/42306/3710
# ------------------------------------------------------------------------
def replace_node(node, new_node_type):
node_tree = node.id_data
props_to_copy = 'name location height width'.split(' ')
reconnections = []
mappings = itertools.chain.from_iterable([node.inputs, node.outputs])
for i in (i for i in mappings if i.is_linked):
for L in i.links:
reconnections.append([L.from_socket.path_from_id(), L.to_socket.path_from_id()])
props = {j: getattr(node, j) for j in props_to_copy}
# get first color attribute
if hasattr(node, "inputs"):
for socket_input in node.inputs:
if socket_input.type == 'RGBA':
clr = socket_input.default_value
break
ids = ([node.bl_rna.identifier for node in bpy.types.ShaderNode.__subclasses__()])
if new_node_type not in ids:
new_node = node_tree.nodes.new("ShaderNodeGroup")
new_node.node_tree = bpy.data.node_groups[new_node_type]
else:
new_node = node_tree.nodes.new(type=new_node_type)
for prop in props_to_copy:
setattr(new_node, prop, props[prop])
# set first color attribute
if hasattr(new_node, "inputs"):
for new_socket_input in new_node.inputs:
if new_socket_input.type == 'RGBA':
#setattr(new_socket_input, "default_value", clr)
new_socket_input.default_value = clr
break
nodes = node_tree.nodes
temp_name = node.name
nodes.remove(node)
new_node.name = new_node_type
reconnections_new = []
for i in reconnections:
reconnections_new.append([s.replace(temp_name, new_node.name) for s in i])
for str_from, str_to in reconnections_new:
try:
node_tree.links.new(eval(str_from), eval(str_to))
except:
pass
node_tree.nodes.active = new_node
# ------------------------------------------------------------------------
# Search Popup Operator
# based on https://blender.stackexchange.com/a/45069/3710
# ------------------------------------------------------------------------
def shader_node_callback(scene, context):
names = ([node.bl_rna.name for node in bpy.types.ShaderNode.__subclasses__()])
ids = ([node.bl_rna.identifier for node in bpy.types.ShaderNode.__subclasses__()])
d = dict(zip(ids,names))
for i in bpy.data.node_groups: d.update({i.name:i.name})
return [(key, val, "") for key, val in d.items()]
class NODE_OT_switchShaderNode(Operator):
"""Switch Shader Node for all Objects in Selection"""
bl_idname = "node.switch_shader_node"
bl_label = "Switch Shader Node (Selected Objects)"
bl_property = "shader_node_selection"
bl_options = {'REGISTER', 'UNDO'}
#bl_property = "shader_node_selection"
shader_node_selection: EnumProperty(
name="Switch Shader Node",
description="",
items=shader_node_callback)
@classmethod
def poll(cls, context):
space = context.space_data
return space.type == 'NODE_EDITOR'
def execute(self, context):
space = context.space_data
node_active = context.active_node.type
node_selected = context.selected_nodes
if not (len(node_selected) == 1) and node_active:
self.report({'ERROR'}, "1 node must be selected")
return {'FINISHED'}
rel_mats = []
for o in context.selected_objects:
if o.type not in ["EMPTY", "LAMP", "SPEAKER", "CAMERA"]:
for m in o.material_slots:
rel_mats.append(m.material)
replaced_nodes = []
for i in rel_mats:
nodes = i.node_tree.nodes
for n in nodes:
if n.type == node_active:
replace_node(n, self.shader_node_selection)
replaced_nodes.append(i.name)
out = [node_active, self.shader_node_selection, len(replaced_nodes)]
out_str = "Replaced {0} by {1} for {2} Materials".format(out[0], out[1], out[2])
self.report({'INFO'}, (out_str))
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.invoke_search_popup(self)
return {'FINISHED'}
def draw_switch_op(self, context):
layout = self.layout
layout.separator()
layout.operator(SwitchShaderNodeOperator.bl_idname, icon='AUTO')
# ------------------------------------------------------------------------
# Register
# ------------------------------------------------------------------------
def register():
bpy.utils.register_class(NODE_OT_switchShaderNode)
bpy.types.NODE_MT_node.append(draw_switch_op)
def unregister():
bpy.utils.unregister_class(NODE_OT_switchShaderNode)
bpy.types.NODE_MT_node.remove(draw_switch_op)
if __name__ == "__main__":
register()
# test call
#bpy.ops.node.switch_shader_node('INVOKE_DEFAULT')