Skip to main content
added 146 characters in body
Source Link
Martynas Žiemys
  • 25.9k
  • 2
  • 36
  • 78

I use this as an add-on to deal with duplicated materials, node groups and textures ending in .001, .002, .003... after copying objects from one file and pasting them to another.

You can save it to .py file and install it as an add-on or just run it in the Text Editor and you will have a menu that you can call with ctrl+alt+shift+z with options to remove duplicate materials. This does a bit more. It also removes duplicate textures, and duplicate node groups in materials as well(this can cause trouble if you have different textures with the same name or different node groups with default names, please see the info in the Info Panel after you use the functions and make sure you are OK with the textures and node groups replaced) It also checks for differences in the node trees of the materials so if the materials are different it can leave them alone as it was made to deal with duplicated materials that are the same after copy-pasting objects from one .blend file to another.

I use this as an add-on. You can save it to .py file and install as an add-on or just run it in the Text Editor and you will have a menu that you can call with ctrl+alt+shift+z with options to remove duplicate materials. This does a bit more. It also removes duplicate textures, and duplicate node groups in materials (this can cause trouble if you have different textures with the same name or different node groups with default names, please see the info in the Info Panel after you use the functions and make sure you are OK with the textures and node groups replaced) It also checks for differences in the node trees of the materials so if the materials are different it can leave them alone as it was made to deal with duplicated materials that are the same after copy-pasting objects from one .blend file to another.

I use this as an add-on to deal with duplicated materials, node groups and textures ending in .001, .002, .003... after copying objects from one file and pasting them to another.

You can save it to .py file and install it as an add-on or just run it in the Text Editor and you will have a menu that you can call with ctrl+alt+shift+z with options to remove duplicate materials. It also removes duplicate textures, and duplicate node groups in materials as well(this can cause trouble if you have different textures with the same name or different node groups with default names, please see the info in the Info Panel after you use the functions and make sure you are OK with the textures and node groups replaced) It also checks for differences in the node trees of the materials so if the materials are different it can leave them alone as it was made to deal with duplicated materials that are the same after copy-pasting objects from one .blend file to another.

Source Link
Martynas Žiemys
  • 25.9k
  • 2
  • 36
  • 78

I use this as an add-on. You can save it to .py file and install as an add-on or just run it in the Text Editor and you will have a menu that you can call with ctrl+alt+shift+z with options to remove duplicate materials. This does a bit more. It also removes duplicate textures, and duplicate node groups in materials (this can cause trouble if you have different textures with the same name or different node groups with default names, please see the info in the Info Panel after you use the functions and make sure you are OK with the textures and node groups replaced) It also checks for differences in the node trees of the materials so if the materials are different it can leave them alone as it was made to deal with duplicated materials that are the same after copy-pasting objects from one .blend file to another.

bl_info = {
    "name": "MZ Addon",
    "author": "Martynas Žiemys",
    "version": (0, 4),
    "blender": (2, 78, 0),
    "location": "alt+ctrl+z",
    "description": "Operators",
    "warning": "",
    "wiki_url": "",
    "category": "Operators",
    }

import bpy
selection = list()

        
def materials_same(mat1,mat2):
    #Check if nodes Match
    if mat1.use_nodes and mat2.use_nodes:
        if len(mat1.node_tree.nodes) != len(mat2.node_tree.nodes):
            return False
        for everynode in mat1.node_tree.nodes:
            if not(everynode.name in mat2.node_tree.nodes):
                return False             
        #if nodes match carry on to closer inspection      
        for everynode in mat1.node_tree.nodes:
            for everyinput in everynode.inputs:
                if hasattr(everyinput, 'default_value'):
                    if hasattr(everyinput.default_value, "__len__") :
                        for i in range(len(everyinput.default_value)):
                            if everyinput.default_value[i] != mat2.node_tree.nodes[everynode.name].inputs[everyinput.name].default_value[i]:
                                return False
                    elif everyinput.default_value != mat2.node_tree.nodes[everynode.name].inputs[everyinput.name].default_value:
                        return False
            #lets check links
            n=0
            if len(mat1.node_tree.links) != len(mat2.node_tree.links):
                return False
            else:
                for everylink in mat1.node_tree.links:  
                    if (mat1.node_tree.links[n].from_socket.name != mat2.node_tree.links[n].from_socket.name
                        or mat1.node_tree.links[n].to_node.name != mat2.node_tree.links[n].to_node.name
                        or mat1.node_tree.links[n].to_socket.name != mat2.node_tree.links[n].to_socket.name):
                        return False
                n=n+1 
    if mat1.use_nodes != mat2.use_nodes:
        return False
    
    print('Material \"' + str(mat1.name) + '\" is the same as \"' + str(mat2.name) + '\"')           
    return True

################ Operators ################################################

class MZRemoveDuplicates(bpy.types.Operator):
    """Remove Duplicate Materials, Textures and Node Groups That End in .001, .002,..."""
    bl_idname = "material.mz_remove_duplicates"
    bl_label = "Remove Duplicate Materials, Textures and Node Groups"
    bl_options = {"REGISTER", "UNDO"}
    ignore_changes = bpy.props.BoolProperty(
        name = "Ignore Material Cahnges",
        description = "Ignore difference in materials and replace all matching names with the first one",
        default = False
    )
    @classmethod
    def poll(cls, context):
        return context.object is not None
    def execute(self, context):
        mats_removed = 0
        mats_unnamed = 0 
        node_grp_removed = 0
        texture_nodes_removed = 0
        for every_object in bpy.data.objects:
            for every_slot in every_object.material_slots:
                part = every_slot.name.rpartition('.')
                if every_slot.name[-3:].isdigit():
                    if not(every_slot.name[:-4] in bpy.data.materials):
                        bpy.data.materials[every_slot.name].name = every_slot.name[:-4]
                        continue
                    if materials_same(bpy.data.materials[every_slot.name], bpy.data.materials[every_slot.name[:-4]]) or self.ignore_changes:
                        every_slot.material = bpy.data.materials.get(every_slot.name[:-4])
                        mats_removed += 1
                    else:
                        every_slot.material.name = str(every_slot.name[:-4]) + ' unnamed!'
                        mats_unnamed += 1
        for mat in bpy.data.materials:
            if mat.node_tree :
                for n in mat.node_tree.nodes:
                    if n.type == 'TEX_IMAGE':
                        if n.image == None:
                            print(mat.name,'has an image node with no image')
                        elif n.image.name[-3:].isdigit():
                            if not(n.image.name[:-4] in bpy.data.images):   # in case there is no more original without numbers
                                  n.image.name = n.image.name[:-4]          # rename the first one
                                  continue                                  # and carry on to the next
                            n.image = bpy.data.images[n.image.name[:-4]]
                            texture_nodes_removed += 1
                            
        for every_node_group in bpy.data.node_groups:
            print(every_node_group.name)
            if every_node_group.name[-3:].isdigit():
                if not(every_node_group.name[:-4] in bpy.data.node_groups): # in case there is no more original without numbers
                    every_node_group.name = every_node_group.name[:-4]      # rename the first one  
                    continue                                                # and carry on to the next
                bpy.data.node_groups[every_node_group.name].user_remap(bpy.data.node_groups[every_node_group.name[:-4]])
                node_grp_removed += 1
        if mats_removed + mats_unnamed + node_grp_removed + texture_nodes_removed == 0:
            self.report({'INFO'}, 'No duplicating materials, node groups or textures found.')
        else:    
            self.report({'INFO'}, str(mats_removed) + ' material(s) replaced. ' + str(mats_unnamed) + ' different material(s) had the same name. ' + str(node_grp_removed) + ' node group(s) replaced. ' + str(texture_nodes_removed) + ' matching texture file(s) replaced.')
        return {'FINISHED'}
            
################ UI ################################################
 
class MZ_menu(bpy.types.Menu):
    bl_label = "MZ Menu"
    bl_idname = "MZ_menu"
    def draw(self, context):
        layout = self.layout
        layout.operator("material.mz_remove_duplicates", text = 'Remove Duplicate Materials, Textures and Node Groups').ignore_changes=False 
        layout.operator("material.mz_remove_duplicates", text = "Remove Duplicates - Ignore Material Difference").ignore_changes=True
  
def draw_item(self, context):
    layout = self.layout
    layout.menu(MZ_menu.bl_idname)
    
########### REGISTRATION ###############################

def register_MZkeymaps():
    global MZ_keymaps
    MZ_keymaps = []
    new_keymap = bpy.context.window_manager.keyconfigs.addon.keymaps.new(name = "Window",space_type='EMPTY', region_type='WINDOW')
    new_keymap_item = new_keymap.keymap_items.new("wm.call_menu", type = "Z",alt=True, ctrl=True, shift=True, value = "PRESS")
    new_keymap_item.properties.name = "MZ_menu"
    MZ_keymaps.append(new_keymap)
    
def unregister_MZkeymaps():
    register_MZkeymaps()
    for everykeymap in MZ_keymaps:
        bpy.context.window_manager.keyconfigs.addon.keymaps.remove(everykeymap)
        del MZ_keymaps[:]
    
def register():
    register_MZkeymaps()
    bpy.utils.register_class(MZRemoveDuplicates)
    bpy.utils.register_class(MZ_menu)



def unregister():
    unregister_MZkeymaps()
    bpy.utils.unregister_class(MZRemoveDuplicates)
    bpy.utils.unregister_class(MZ_menu)


if __name__ == "__main__":
    register()

Sorry about it not following pep8 guides. It's quite messy, but it saves a lot of time for me, so I hope it's useful.