6
$\begingroup$

I am working with a very large scene file that was imported as .obj. This gave me basic blender internal materials with colors and textures. I then used an addon to convert all of the BI materials to Cycles ones (https://blenderartists.org/forum/showthread.php?394600-Materials-Specials-amp-conversion)

What I have now is every material is a Diffuse Node with a color, or with an image texture node hooked up to it with the appropriate texture.

What I would like to do now is make a more complex node setup (likely in a group), and then copy it to all the objects in the file, but have them keep their own diffuse color and texture.

Are there any existing addons or scripts that can do this?

$\endgroup$

4 Answers 4

6
+50
$\begingroup$

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.

  1. Select all desired objects in the '3d View' (allows to limit the selection)
  2. In the 'Node Editor' select that Material Node, you'd like to replace
  3. From the header menu, select Node > Switch Shader Node
  4. 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')
$\endgroup$
4
  • 2
    $\begingroup$ This is great! I just need a way to handle the texture and color preservation and hookup now. It seems the full answer is split across a couple of the answers here. $\endgroup$
    – Ascalon
    Commented May 18, 2017 at 20:10
  • $\begingroup$ Ooops, forgot to copy the color... Done. What do you mean by texture preservation? No experience with BI, can you share a test file to play with? Thanks @Drudge $\endgroup$
    – brockmann
    Commented May 19, 2017 at 14:21
  • $\begingroup$ I just mean keeping the texture node (if there is one) and reconnecting it to the color input. $\endgroup$
    – Ascalon
    Commented May 19, 2017 at 16:31
  • $\begingroup$ returning to this after a long time, I can confirm that this script works great for switching the selected node type to my Group without breaking connections to Texture nodes. However, it does not carry over the color of the Diffuse node I'm replacing. I need a way to create an RGB node in each material that has a diffuse, have it copy the Diffuse's color, and then hook the RGB to the diffuse. Any ideas? $\endgroup$
    – Ascalon
    Commented Sep 6, 2017 at 2:57
5
$\begingroup$
import bpy
for material in bpy.data.materials:
    if (material.use_nodes):
        nodes = material.node_tree.nodes
        links = material.node_tree.links
        for node in nodes:
            if (node.name=='Material Output'):                            
                out = node
            elif  (node.name=='Image Texture'):
                img = node
            elif: (node.name=='Diffuse BSDF'):
                diff = node
                # if diffuse node is not linked,then save it's colorvalue
                if not(node.inputs[0].is_linked):
                    color = node.inputs[0].default_value
                else:
                # else we should set color to None, and be aware
                    color = None
            else:
                nodes.remove(node)

        hold = nodes.new('ShaderNodeHoldout')
        geo = nodes.new('ShaderNodeNewGeometry')
        mix = nodes.new('ShaderNodeMixShader')
        RGB = nodes.new('ShaderNodeRGB')
        if (color==None):
            if (img==None):
                #No image and diffuse was not empty, set color to grey
                RGB.outputs[0].default_value = (0.7,0.7,0.7)
                img = RGB
        else:    
            RGB.outputs[0].default_value = color
            img = RGB

        links.new(out.inputs[0],mix.outputs[0])
        links.new(mix.inputs[0],geo.outputs[6])
        links.new(mix.inputs[1],diff.outputs[0])
        links.new(mix.inputs[2],hold.outputs[0])
        links.new(diff.inputs[0],img.outputs[0])

This code does almost what you want. It saves one image texture node, one material output node, and deletes everything else, and then adds emission and holdout node and connects them via backfacing. As for part of your own shader, i think you can save it as a node group and append it into node setup via python too

$\endgroup$
2
  • $\begingroup$ Do you know how to make it take the color in the diffuse node and make an RGB node that matches it, then hook that to a node group input? $\endgroup$
    – Ascalon
    Commented May 18, 2017 at 7:45
  • $\begingroup$ updated code in my answer a bit $\endgroup$
    – Vii
    Commented May 19, 2017 at 9:25
1
$\begingroup$

I don't know if that's exactly what do you want, if you have your material already made just:

  1. select all the objects you want to have that material, and select for last the one that has the material.
  2. It should have an orange border, while the other ones should have a red border.
  3. Now press Ctrl + l and click on "materials", and that should do it.
$\endgroup$
0
$\begingroup$

You would need to create a script that looped through each material in the scene. Here's generally what the script should do.

First create your group node with an RGB input.

Then have your script loop through each material in the scene and have it:

  1. Add your new custom group node to the material.
  2. If the material has an image texture, connect the output of the image node into the group node. If the material only has a diffuse node, copy the RGB value from the node, create an RGB node, set the color and then plug it into the group node.
  3. Delete the original Diffuse node.

I don't have time to write out the script right now, but those are the steps that should happen.

If you are really stuck, I could be hired to write this for you.

$\endgroup$

You must log in to answer this question.

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