mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-16 15:35:05 +00:00
714 lines
22 KiB
Python
714 lines
22 KiB
Python
#(c) 2010 Michael Williamson (michaelw)
|
|
#ported from original by Michael Williamsn
|
|
#
|
|
#tested r28370
|
|
#
|
|
#
|
|
# ##### 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": "Material Utils",
|
|
"author": "michaelw",
|
|
"version": (1, 3),
|
|
"blender": (2, 5, 6),
|
|
"api": 35324,
|
|
"location": "View3D > Q key",
|
|
"description": "Menu of material tools (assign, select by etc) in the 3D View",
|
|
"warning": "",
|
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
|
|
"Scripts/3D interaction/Materials Utils",
|
|
"tracker_url": "https://projects.blender.org/tracker/index.php?"\
|
|
"func=detail&aid=22140",
|
|
"category": "3D View"}
|
|
|
|
"""
|
|
This script has several functions and operators... grouped for convenience
|
|
* assign material:
|
|
offers the user a list of ALL the materials in the blend file and an additional "new" entry
|
|
the chosen material will be assigned to all the selected objects in object mode.
|
|
|
|
in edit mode the selected faces get the selected material applied.
|
|
|
|
if the user chose "new" the new material can be renamed using the "last operator" section of the toolbox
|
|
After assigning the material "clean material slots" and "material to texface" are auto run to keep things tidy (see description bellow)
|
|
|
|
|
|
* select by material
|
|
in object mode this offers the user a menu of all materials in the blend file
|
|
any objects using the selected material will become selected, any objects without the material will be removed from selection.
|
|
|
|
in edit mode: the menu offers only the materials attached to the current object. It will select the faces that use the material and deselect those that do not.
|
|
|
|
* clean material slots
|
|
for all selected objects any empty material slots or material slots with materials that are not used by the mesh faces will be removed.
|
|
|
|
* Any un-used materials and slots will be removed
|
|
"""
|
|
|
|
|
|
import bpy
|
|
from bpy.props import*
|
|
|
|
|
|
def replace_material(m1 , m2, all_objects = False):
|
|
#replace material named m1 with material named m2
|
|
#m1 is the name of original material
|
|
#m2 is the name of the material to replace it with
|
|
#'all' will replace throughout the blend file
|
|
try:
|
|
matorg = bpy.data.materials[m1]
|
|
matrep = bpy.data.materials[m2]
|
|
|
|
|
|
#store active object
|
|
scn = bpy.context.scene
|
|
ob_active = bpy.context.active_object
|
|
|
|
if all_objects:
|
|
objs = bpy.data.objects
|
|
|
|
else:
|
|
objs = bpy.context.selected_editable_objects
|
|
|
|
for ob in objs:
|
|
if ob.type == 'MESH':
|
|
scn.objects.active = ob
|
|
print(ob.name)
|
|
ms = ob.material_slots.values()
|
|
|
|
for m in ms:
|
|
if m.material == matorg:
|
|
m.material = matrep
|
|
#don't break the loop as the material can be
|
|
# ref'd more than once
|
|
|
|
#restore active object
|
|
scn.objects.active = ob_active
|
|
except:
|
|
print('no match to replace')
|
|
|
|
def select_material_by_name(find_mat):
|
|
#in object mode selects all objects with material find_mat
|
|
#in edit mode selects all faces with material find_mat
|
|
|
|
#check for editmode
|
|
editmode = False
|
|
|
|
scn = bpy.context.scene
|
|
|
|
#set selection mode to faces
|
|
scn.tool_settings.mesh_select_mode =[False,False,True]
|
|
|
|
actob = bpy.context.active_object
|
|
if actob.mode == 'EDIT':
|
|
editmode =True
|
|
bpy.ops.object.mode_set()
|
|
|
|
|
|
if not editmode:
|
|
objs = bpy.data.objects
|
|
for ob in objs:
|
|
typ = ['MESH','CURVE', 'SURFACE', 'FONT', 'META']
|
|
if ob.type in typ:
|
|
ms = ob.material_slots.values()
|
|
for m in ms:
|
|
if m.material.name == find_mat:
|
|
ob.select = True
|
|
#the active object may not have the mat!
|
|
#set it to one that does!
|
|
scn.objects.active = ob
|
|
break
|
|
else:
|
|
ob.select = False
|
|
|
|
#deselect non-meshes
|
|
else:
|
|
ob.select = False
|
|
|
|
else:
|
|
#it's editmode, so select the faces
|
|
ob = actob
|
|
ms = ob.material_slots.values()
|
|
|
|
#same material can be on multiple slots
|
|
slot_indeces =[]
|
|
i = 0
|
|
found = False
|
|
for m in ms:
|
|
if m.material.name == find_mat:
|
|
slot_indeces.append(i)
|
|
found = True
|
|
i += 1
|
|
me = ob.data
|
|
for f in me.faces:
|
|
if f.material_index in slot_indeces:
|
|
f.select = True
|
|
else:
|
|
f.select = False
|
|
me.update()
|
|
if editmode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
def mat_to_texface():
|
|
#assigns the first image in each material to the faces in the active uvlayer
|
|
#for all selected objects
|
|
|
|
#check for editmode
|
|
editmode = False
|
|
|
|
actob = bpy.context.active_object
|
|
if actob.mode == 'EDIT':
|
|
editmode =True
|
|
bpy.ops.object.mode_set()
|
|
|
|
for ob in bpy.context.selected_editable_objects:
|
|
if ob.type == 'MESH':
|
|
#get the materials from slots
|
|
ms = ob.material_slots.values()
|
|
|
|
#build a list of images, one per material
|
|
images=[]
|
|
#get the textures from the mats
|
|
for m in ms:
|
|
gotimage = False
|
|
textures = m.material.texture_slots.values()
|
|
if len(textures) >= 1:
|
|
for t in textures:
|
|
if t != None:
|
|
tex = t.texture
|
|
if tex.type == 'IMAGE':
|
|
img = tex.image
|
|
images.append(img)
|
|
gotimage =True
|
|
break
|
|
|
|
if not gotimage:
|
|
print('noimage on', m.name)
|
|
images.append(None)
|
|
|
|
#now we have the images
|
|
#applythem to the uvlayer
|
|
|
|
|
|
me = ob.data
|
|
#got uvs?
|
|
if not me.uv_textures:
|
|
scn = bpy.context.scene
|
|
scn.objects.active = ob
|
|
bpy.ops.mesh.uv_texture_add()
|
|
scn.objects.active = actob
|
|
|
|
#get active uvlayer
|
|
for t in me.uv_textures:
|
|
if t.active:
|
|
uvtex = t.data.values()
|
|
for f in me.faces:
|
|
#check that material had an image!
|
|
if images[f.material_index] != None:
|
|
uvtex[f.index].image = images[f.material_index]
|
|
uvtex[f.index].use_image = True
|
|
else:
|
|
uvtex[f.index].use_image = False
|
|
|
|
me.update()
|
|
|
|
|
|
if editmode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
|
|
|
|
def assignmatslots(ob, matlist):
|
|
#given an object and a list of material names
|
|
#removes all material slots form the object
|
|
#adds new ones for each material in matlist
|
|
#adds the materials to the slots as well.
|
|
|
|
scn = bpy.context.scene
|
|
ob_active = bpy.context.active_object
|
|
scn.objects.active = ob
|
|
|
|
for s in ob.material_slots:
|
|
bpy.ops.object.material_slot_remove()
|
|
|
|
|
|
#re-add them and assign material
|
|
i = 0
|
|
for m in matlist:
|
|
mat = bpy.data.materials[m]
|
|
ob.data.materials.append(mat)
|
|
i += 1
|
|
|
|
#restore active object:
|
|
scn.objects.active = ob_active
|
|
|
|
|
|
def cleanmatslots():
|
|
#check for edit mode
|
|
editmode = False
|
|
actob = bpy.context.active_object
|
|
if actob.mode == 'EDIT':
|
|
editmode =True
|
|
bpy.ops.object.mode_set()
|
|
|
|
|
|
objs = bpy.context.selected_editable_objects
|
|
|
|
for ob in objs:
|
|
if ob.type == 'MESH':
|
|
mats = ob.material_slots.keys()
|
|
|
|
#check the faces on the mesh to build a list of used materials
|
|
usedMatIndex =[] #we'll store used materials indices here
|
|
faceMats =[]
|
|
me = ob.data
|
|
for f in me.faces:
|
|
#get the material index for this face...
|
|
faceindex = f.material_index
|
|
|
|
#indices will be lost: Store face mat use by name
|
|
currentfacemat = mats[faceindex]
|
|
faceMats.append(currentfacemat)
|
|
|
|
|
|
#check if index is already listed as used or not
|
|
found = 0
|
|
for m in usedMatIndex:
|
|
if m == faceindex:
|
|
found = 1
|
|
#break
|
|
|
|
if found == 0:
|
|
#add this index to the list
|
|
usedMatIndex.append(faceindex)
|
|
|
|
#re-assign the used mats to the mesh and leave out the unused
|
|
ml = []
|
|
mnames = []
|
|
for u in usedMatIndex:
|
|
ml.append( mats[u] )
|
|
#we'll need a list of names to get the face indices...
|
|
mnames.append(mats[u])
|
|
|
|
assignmatslots(ob, ml)
|
|
|
|
|
|
#restore face indices:
|
|
i = 0
|
|
for f in me.faces:
|
|
matindex = mnames.index(faceMats[i])
|
|
f.material_index = matindex
|
|
i += 1
|
|
|
|
if editmode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
|
|
|
|
|
|
|
|
def assign_mat(matname="Default"):
|
|
#get active object so we can restore it later
|
|
actob = bpy.context.active_object
|
|
|
|
#check if material exists, if it doesn't then create it
|
|
mats =bpy.data.materials
|
|
found = False
|
|
for m in mats:
|
|
if m.name == matname:
|
|
target = m
|
|
found = True
|
|
break
|
|
if not found:
|
|
target = bpy.data.materials.new(matname)
|
|
|
|
|
|
#if objectmode then set all faces
|
|
editmode = False
|
|
allfaces = True
|
|
if actob.mode == 'EDIT':
|
|
editmode =True
|
|
allfaces = False
|
|
bpy.ops.object.mode_set()
|
|
|
|
objs = bpy.context.selected_editable_objects
|
|
|
|
for ob in objs:
|
|
#set the active object to our object
|
|
scn = bpy.context.scene
|
|
scn.objects.active = ob
|
|
|
|
|
|
other = ['CURVE', 'SURFACE', 'FONT', 'META']
|
|
if ob.type in other:
|
|
found=False
|
|
i = 0
|
|
mats = bpy.data.materials
|
|
for m in mats:
|
|
if m.name == matname:
|
|
found =True
|
|
index = i
|
|
break
|
|
i += 1
|
|
if not found:
|
|
index = i-1
|
|
targetlist =[index]
|
|
assignmatslots(ob, targetlist)
|
|
|
|
elif ob.type =='MESH':
|
|
#check material slots for matname material
|
|
found=False
|
|
i = 0
|
|
mats = ob.material_slots
|
|
for m in mats:
|
|
if m.name == matname:
|
|
found =True
|
|
index = i
|
|
#make slot active
|
|
ob.active_material_index = i
|
|
break
|
|
i += 1
|
|
|
|
if not found:
|
|
index=i
|
|
#the material is not attached to the object
|
|
ob.data.materials.append(target)
|
|
|
|
#now assign the material:
|
|
me =ob.data
|
|
if allfaces:
|
|
for f in me.faces:
|
|
f.material_index = index
|
|
elif allfaces == False:
|
|
for f in me.faces:
|
|
if f.select:
|
|
f.material_index = index
|
|
me.update()
|
|
|
|
|
|
|
|
#restore the active object
|
|
bpy.context.scene.objects.active = actob
|
|
if editmode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
|
|
|
|
def check_texture(img,mat):
|
|
#finds a texture from an image
|
|
#makes a texture if needed
|
|
#adds it to the material if it isn't there already
|
|
|
|
tex = bpy.data.textures.get(img.name)
|
|
|
|
if tex is None:
|
|
tex = bpy.data.textures.new(name=img.name, type='IMAGE')
|
|
|
|
tex.image = img
|
|
|
|
#see if the material already uses this tex
|
|
#add it if needed
|
|
found = False
|
|
for m in mat.texture_slots:
|
|
if m and m.texture == tex:
|
|
found = True
|
|
break
|
|
if not found and mat:
|
|
mtex = mat.texture_slots.add()
|
|
mtex.texture = tex
|
|
mtex.texture_coords = 'UV'
|
|
mtex.use_map_color_diffuse = True
|
|
|
|
def texface_to_mat():
|
|
# editmode check here!
|
|
editmode = False
|
|
ob = bpy.context.object
|
|
if ob.mode =='EDIT':
|
|
editmode = True
|
|
bpy.ops.object.mode_set()
|
|
|
|
for ob in bpy.context.selected_editable_objects:
|
|
|
|
faceindex = []
|
|
unique_images = []
|
|
|
|
# get the texface images and store indices
|
|
if (ob.data.uv_textures):
|
|
for f in ob.data.uv_textures.active.data:
|
|
if f.image:
|
|
img = f.image
|
|
#build list of unique images
|
|
if img not in unique_images:
|
|
unique_images.append(img)
|
|
faceindex.append(unique_images.index(img))
|
|
|
|
else:
|
|
img = None
|
|
faceindex.append(None)
|
|
|
|
|
|
|
|
#check materials for images exist; create if needed
|
|
matlist = []
|
|
for i in unique_images:
|
|
if i:
|
|
print(i.name)
|
|
try:
|
|
m = bpy.data.materials[i.name]
|
|
|
|
except:
|
|
m = bpy.data.materials.new(name = i.name)
|
|
continue
|
|
|
|
finally:
|
|
matlist.append(m.name)
|
|
# add textures if needed
|
|
check_texture(i,m)
|
|
|
|
#set up the object material slots
|
|
assignmatslots(ob, matlist)
|
|
|
|
#set texface indices to material slot indices..
|
|
me = ob.data
|
|
|
|
i = 0
|
|
for f in faceindex:
|
|
if f != None:
|
|
me.faces[i].material_index = f
|
|
i += 1
|
|
if editmode:
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
|
|
|
|
#operator classes:
|
|
#---------------------------------------------------------------------
|
|
|
|
class VIEW3D_OT_texface_to_material(bpy.types.Operator):
|
|
''''''
|
|
bl_idname = "view3d.texface_to_material"
|
|
bl_label = "MW Texface Images to Material/Texture"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
if context.selected_editable_objects:
|
|
texface_to_mat()
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "No editable selected objects, could not finish")
|
|
return {'CANCELLED'}
|
|
|
|
class VIEW3D_OT_assign_material(bpy.types.Operator):
|
|
'''assign a material to the selection'''
|
|
bl_idname = "view3d.assign_material"
|
|
bl_label = "MW Assign Material"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
matname = StringProperty(name = 'Material Name',
|
|
description = 'Name of Material to Assign',
|
|
default = "", maxlen = 21)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
mn = self.matname
|
|
print(mn)
|
|
assign_mat(mn)
|
|
cleanmatslots()
|
|
mat_to_texface()
|
|
return {'FINISHED'}
|
|
|
|
class VIEW3D_OT_clean_material_slots(bpy.types.Operator):
|
|
'''removes any material slots from the
|
|
selected objects that are not used by the mesh'''
|
|
bl_idname = "view3d.clean_material_slots"
|
|
bl_label = "MW Clean Material Slots"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
cleanmatslots()
|
|
return {'FINISHED'}
|
|
|
|
class VIEW3D_OT_material_to_texface(bpy.types.Operator):
|
|
''''''
|
|
bl_idname = "view3d.material_to_texface"
|
|
bl_label = "MW Material Images to Texface"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
mat_to_texface()
|
|
return {'FINISHED'}
|
|
|
|
class VIEW3D_OT_select_material_by_name(bpy.types.Operator):
|
|
''''''
|
|
bl_idname = "view3d.select_material_by_name"
|
|
bl_label = "MW Select Material By Name"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
matname = StringProperty(name = 'Material Name',
|
|
description = 'Name of Material to Select',
|
|
default = "", maxlen = 21)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
mn = self.matname
|
|
select_material_by_name(mn)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class VIEW3D_OT_replace_material(bpy.types.Operator):
|
|
'''assign a material to the selection'''
|
|
bl_idname = "view3d.replace_material"
|
|
bl_label = "MW Replace Material"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
matorg = StringProperty(name = 'Material to Replace',
|
|
description = 'Name of Material to Assign',
|
|
default = "", maxlen = 21)
|
|
|
|
matrep = StringProperty(name = 'Replacement material',
|
|
description = 'Name of Material to Assign',
|
|
default = "", maxlen = 21)
|
|
|
|
all_objects = BoolProperty(name ='all_objects',
|
|
description="replace for all objects in this blend file",
|
|
default = True)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None
|
|
|
|
def execute(self, context):
|
|
m1 = self.matorg
|
|
m2 = self.matrep
|
|
all = self.all_objects
|
|
replace_material(m1,m2,all)
|
|
return {'FINISHED'}
|
|
|
|
#menu classes
|
|
#-------------------------------------------------------------------------------
|
|
class VIEW3D_MT_master_material(bpy.types.Menu):
|
|
bl_label = "Master Material Menu"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN')
|
|
layout.menu("VIEW3D_MT_select_material", icon='HAND')
|
|
layout.separator()
|
|
layout.operator("view3d.clean_material_slots",
|
|
text = 'Clean Material Slots', icon='CANCEL')
|
|
layout.operator("view3d.material_to_texface",
|
|
text = 'Material to Texface',icon='FACESEL_HLT')
|
|
layout.operator("view3d.texface_to_material",
|
|
text = 'Texface to Material',icon='FACESEL_HLT')
|
|
|
|
layout.separator()
|
|
layout.operator("view3d.replace_material",
|
|
text = 'Replace Material', icon='ARROW_LEFTRIGHT')
|
|
|
|
|
|
|
|
class VIEW3D_MT_assign_material(bpy.types.Menu):
|
|
bl_label = "Assign Material"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
ob = context
|
|
layout.label
|
|
for i in range (len(bpy.data.materials)):
|
|
|
|
layout.operator("view3d.assign_material",
|
|
text=bpy.data.materials[i].name,
|
|
icon='MATERIAL_DATA').matname = bpy.data.materials[i].name
|
|
|
|
layout.operator("view3d.assign_material",text="Add New",
|
|
icon='ZOOMIN')
|
|
|
|
class VIEW3D_MT_select_material(bpy.types.Menu):
|
|
bl_label = "Select by Material"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
|
|
ob = context.object
|
|
layout.label
|
|
if ob.mode == 'OBJECT':
|
|
#show all used materials in entire blend file
|
|
for i in range (len(bpy.data.materials)):
|
|
if bpy.data.materials[i].users > 0:
|
|
layout.operator("view3d.select_material_by_name",
|
|
text=bpy.data.materials[i].name,
|
|
icon='MATERIAL_DATA').matname = bpy.data.materials[i].name
|
|
|
|
|
|
elif ob.mode == 'EDIT':
|
|
#show only the materials on this object
|
|
mats = ob.material_slots.keys()
|
|
for m in mats:
|
|
layout.operator("view3d.select_material_by_name",
|
|
text=m,
|
|
icon='MATERIAL_DATA').matname = m
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_module(__name__)
|
|
|
|
kc = bpy.data.window_managers[0].keyconfigs.default
|
|
km = kc.keymaps.get("3D View")
|
|
if km is None:
|
|
km = kc.keymaps.new(name="3D View")
|
|
|
|
kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS')
|
|
kmi.properties.name = "VIEW3D_MT_master_material"
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_module(__name__)
|
|
|
|
kc = bpy.data.window_managers[0].keyconfigs.default
|
|
km = kc.keymaps["3D View"]
|
|
for kmi in km.keymap_items:
|
|
if kmi.idname == 'wm.call_menu':
|
|
if kmi.properties.name == "VIEW3D_MT_master_material":
|
|
km.keymap_items.remove(kmi)
|
|
break
|
|
|
|
if __name__ == "__main__":
|
|
register()
|
|
|