Looking for a way to copy materials from one object to another. so I have an object that I imported. a daz character to be specific. I have made shader for all of the materials. This character is now setup and ready to go. Now I have to import him multiple times into blender in different poses etc. so i am looking for a way to apply all the materials I have already made to the new import. Since the new import has the same material names as the old blender appends everything .001 so I now have torso and torso.001 as available materials. So Im looking for a script which will go through all the material slots on the character and set the active material from the .001 to the original one. can anyone help me out here?
2 Answers
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.
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.
-
$\begingroup$ FWIW not a fan of using every as a name for an iterator. A variable named
every_object
conjures up a collection with every object, likeevery_object = bpy.data.objects[:]
., whereas each iteration the variableevery_object
is only one object. Wouldn't usefor each_object in some_collection:
either. Why not something simple likefor ob in some_object_collection
.? $\endgroup$ Commented Sep 26, 2018 at 13:21 -
$\begingroup$ Yeah you may be correct there :D . It is just easier for myself to follow the code this way. Its personal. Like I said, I apologize it is messy. I did not write it with sharing in mind at the time. I am sure it could be improved and make more sense not only with variable names. The terribly long lines hurt my eyes as well now that I look at it. But it works. I hope it might be useful for somebody. $\endgroup$ Commented Sep 26, 2018 at 13:47
This is what I came up with. Select the object with the "wrong" materials first (like 'torso.001',...) and the object with the "original" materials last (like 'torso',...). Then run the script. It compares all material names and if one of the "wrong" materials starts with the name of one of the "right" materials, it will be replaced by this mat. (So the material 'torso.001' would be replaced by the material 'torso').
The script does not update the 3dView, so be sure to do some action (like zoom in, etc.) to update the viewport after you run the script!
Also this might not work correctly, if some names are combined of other names. E.g. if you have two materials 'torsoUpper' and 'torso'. If that is a problem, I can edit the script.
Hope this helps.
import bpy
c = bpy.context
# Select multiple objects.
# The last selected should be the object with the "original" colors
# The script loops through all materials of the other objects and compares
# them to the materials of the "original" object. If their names start with the same
# letters, the material is replaced with the original.
# HINT: The script does not update the 3dView!
print("---RUNNING SCRIPT---")
src = c.active_object
for i in c.selected_objects:
if (i == src):
continue
for src_id in range(len(src.data.materials)):
for replace_id in range(len(i.data.materials)):
src_name = src.data.materials[src_id].name
rep_name = i.data.materials[replace_id].name
if (rep_name.startswith(src_name)):
i.data.materials[replace_id] = src.data.materials[src_id]
print("Replaced "+rep_name+" by "+src_name)
break
-
1$\begingroup$ Recommend using
context.active_object
forsrc
rather than the last member of the selected objects list. This is usual blender MO to change settings of selected objects with those of the active. @steven850 if the answer works for you can accept and upvote. $\endgroup$ Commented Sep 26, 2018 at 13:04 -
$\begingroup$ @batFINGER Thank you, I edited the script. $\endgroup$– EctrasCommented Sep 27, 2018 at 14:25
CTRL
+L
> Materials (provided they already have the same number of material slots) $\endgroup$