23
23
# ##### END GPL LICENSE BLOCK #####
26
26
"name": "Material Utils",
27
27
"author": "michaelw",
31
30
"location": "View3D > Q key",
32
"description": "Menu of material tools (assign, select by etc) in the 3D View",
34
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
35
"Scripts/3D interaction/Materials Utils",
36
"tracker_url": "https://projects.blender.org/tracker/index.php?"\
37
"func=detail&aid=22140&group_id=153&atid=469",
31
"description": "Menu of material tools (assign, select..) in the 3D View",
32
"warning": "Buggy, Broken in Cycles mode",
33
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
34
"Scripts/3D interaction/Materials Utils",
35
"tracker_url": "https://projects.blender.org/tracker/index.php?"
36
"func=detail&aid=22140",
38
37
"category": "3D View"}
41
40
This script has several functions and operators... grouped for convenience
43
offers the user a list of ALL the materials in the blend file and an additional "new" entry
44
the chosen material will be assigned to all the selected objects in object mode.
46
in edit mode the selected faces get the selected material applied.
48
if the user chose "new" the new material can be renamed using the "last operator" section of the toolbox
49
After assigning the material "clean material slots" and "material to texface" are auto run to keep things tidy (see description bellow)
42
offers the user a list of ALL the materials in the blend file and an
43
additional "new" entry the chosen material will be assigned to all the
44
selected objects in object mode.
46
in edit mode the selected polygons get the selected material applied.
48
if the user chose "new" the new material can be renamed using the
49
"last operator" section of the toolbox.
50
After assigning the material "clean material slots" and
51
"material to texface" are auto run to keep things tidy
52
(see description bellow)
52
55
* select by material
53
in object mode this offers the user a menu of all materials in the blend file
54
any objects using the selected material will become selected, any objects without the material will be removed from selection.
56
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.
56
in object mode this offers the user a menu of all materials in the blend
57
file any objects using the selected material will become selected, any
58
objects without the material will be removed from selection.
60
in edit mode: the menu offers only the materials attached to the current
61
object. It will select the polygons that use the material and deselect those
58
64
* clean material slots
59
for all selected objects any empty material slots or material slots with materials that are not used by the mesh faces will be removed.
65
for all selected objects any empty material slots or material slots with
66
materials that are not used by the mesh polygons will be removed.
61
* Any un-used materials and slots will be removed
68
* Any un-used materials and slots will be removed
66
73
from bpy.props import*
69
def replace_material(m1 , m2, all_objects = False):
70
#replace material named m1 with material named m2
71
#m1 is the name of original material
72
#m2 is the name of the material to replace it with
73
#'all' will replace throughout the blend file
75
matorg = bpy.data.materials[m1]
76
matrep = bpy.data.materials[m2]
76
def replace_material(m1, m2, all_objects=False):
77
# replace material named m1 with material named m2
78
# m1 is the name of original material
79
# m2 is the name of the material to replace it with
80
# 'all' will replace throughout the blend file
82
matorg = bpy.data.materials.get(m1)
83
matrep = bpy.data.materials.get(m2)
79
86
#store active object
80
87
scn = bpy.context.scene
81
88
ob_active = bpy.context.active_object
84
91
objs = bpy.data.objects
87
94
objs = bpy.context.selected_editable_objects
90
97
if ob.type == 'MESH':
91
98
scn.objects.active = ob
93
ms = ob.material_slots.values()
100
for m in ob.material_slots.values():
96
101
if m.material == matorg:
97
102
m.material = matrep
98
#don't break the loop as the material can be
103
# don't break the loop as the material can be
99
104
# ref'd more than once
101
#restore active object
102
scn.objects.active = ob_active
104
107
print('no match to replace')
106
def select_material_by_name(find_mat):
107
#in object mode selects all objects with material find_mat
108
#in edit mode selects all faces with material find_mat
110
def select_material_by_name(find_mat_name):
111
#in object mode selects all objects with material find_mat_name
112
#in edit mode selects all polygons with material find_mat_name
114
find_mat = bpy.data.materials.get(find_mat_name)
110
119
#check for editmode
113
122
scn = bpy.context.scene
124
#set selection mode to polygons
125
scn.tool_settings.mesh_select_mode = False, False, True
114
127
actob = bpy.context.active_object
115
128
if actob.mode == 'EDIT':
117
130
bpy.ops.object.mode_set()
121
objs = bpy.data.objects
133
objs = bpy.data.objects
123
if ob.type == 'MESH':
135
if ob.type in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}:
124
136
ms = ob.material_slots.values()
126
if m.material.name == find_mat:
138
if m.material == find_mat:
128
#the active object may not have the mat!
129
#set it to one that does!
140
# the active object may not have the mat!
141
# set it to one that does!
130
142
scn.objects.active = ob
133
145
ob.select = False
137
149
ob.select = False
140
#it's editmode, so select the faces
152
#it's editmode, so select the polygons
142
154
ms = ob.material_slots.values()
144
156
#same material can be on multiple slots
159
# found = False # UNUSED
149
if m.material.name == find_mat:
161
if m.material == find_mat:
150
162
slot_indeces.append(i)
163
# found = True # UNUSED
166
for f in me.polygons:
155
167
if f.material_index in slot_indeces:
161
bpy.ops.object.mode_set(mode = 'EDIT')
173
bpy.ops.object.mode_set(mode='EDIT')
163
176
def mat_to_texface():
164
#assigns the first image in each material to the faces in the active uvlayer
165
#for all selected objects
177
# assigns the first image in each material to the polygons in the active
178
# uvlayer for all selected objects
167
180
#check for editmode
170
183
actob = bpy.context.active_object
171
184
if actob.mode == 'EDIT':
173
186
bpy.ops.object.mode_set()
175
188
for ob in bpy.context.selected_editable_objects:
176
#get the materials from slots
177
ms = ob.material_slots.values()
179
#build a list of images, one per material
181
#get the textures from the mats
184
textures = m.material.texture_slots.values()
185
if len(textures) >= 1:
189
if tex.type == 'IMAGE':
196
print('noimage on', m.name)
199
#now we have the images
200
#applythem to the uvlayer
205
if not me.uv_textures:
206
scn = bpy.context.scene
207
scn.objects.active = ob
208
bpy.ops.mesh.uv_texture_add()
209
scn.objects.active = actob
212
for t in me.uv_textures:
214
uvtex = t.data.values()
216
#check that material had an image!
217
if images[f.material_index] != None:
218
uvtex[f.index].image = images[f.material_index]
219
uvtex[f.index].use_image = True
221
uvtex[f.index].use_image = False
189
if ob.type == 'MESH':
190
#get the materials from slots
191
ms = ob.material_slots.values()
193
#build a list of images, one per material
195
#get the textures from the mats
198
textures = m.material.texture_slots.values()
199
if len(textures) >= 1:
203
if tex.type == 'IMAGE':
210
print('noimage on', m.name)
213
# now we have the images
214
# applythem to the uvlayer
218
if not me.uv_textures:
219
scn = bpy.context.scene
220
scn.objects.active = ob
221
bpy.ops.mesh.uv_texture_add()
222
scn.objects.active = actob
225
for t in me.uv_textures:
227
uvtex = t.data.values()
228
for f in me.polygons:
229
#check that material had an image!
230
if images[f.material_index] != None:
231
uvtex[f.index].image = images[f.material_index]
233
uvtex[f.index].image = None
227
bpy.ops.object.mode_set(mode = 'EDIT')
238
bpy.ops.object.mode_set(mode='EDIT')
231
241
def assignmatslots(ob, matlist):
232
242
#given an object and a list of material names
260
268
actob = bpy.context.active_object
261
269
if actob.mode == 'EDIT':
263
271
bpy.ops.object.mode_set()
266
273
objs = bpy.context.selected_editable_objects
270
mats = ob.material_slots.keys()
272
#check the faces on the mesh to build a list of used materials
273
usedMatIndex =[] #we'll store used materials indices here
277
#get the material index for this face...
278
faceindex = f.material_index
280
#indices will be lost: Store face mat use by name
281
currentfacemat = mats[faceindex]
282
faceMats.append(currentfacemat)
285
#check if index is already listed as used or not
287
for m in usedMatIndex:
293
#add this index to the list
294
usedMatIndex.append(faceindex)
296
#re-assign the used mats to the mesh and leave out the unused
299
for u in usedMatIndex:
301
#we'll need a list of names to get the face indices...
302
mnames.append(mats[u])
304
assignmatslots(ob, ml)
307
#restore face indices:
310
matindex = mnames.index(faceMats[i])
311
f.material_index = matindex
276
if ob.type == 'MESH':
277
mats = ob.material_slots.keys()
279
#check the polygons on the mesh to build a list of used materials
280
usedMatIndex = [] # we'll store used materials indices here
283
for f in me.polygons:
284
#get the material index for this face...
285
faceindex = f.material_index
287
#indices will be lost: Store face mat use by name
288
currentfacemat = mats[faceindex]
289
faceMats.append(currentfacemat)
291
# check if index is already listed as used or not
293
for m in usedMatIndex:
299
#add this index to the list
300
usedMatIndex.append(faceindex)
302
#re-assign the used mats to the mesh and leave out the unused
305
for u in usedMatIndex:
307
#we'll need a list of names to get the face indices...
308
mnames.append(mats[u])
310
assignmatslots(ob, ml)
312
# restore face indices:
314
for f in me.polygons:
315
matindex = mnames.index(faceMats[i])
316
f.material_index = matindex
315
bpy.ops.object.mode_set(mode = 'EDIT')
321
def assign_mat(matname="Default"):
322
#get active object so we can restore it later
320
bpy.ops.object.mode_set(mode='EDIT')
323
def assign_mat(matname="Default"):
324
# get active object so we can restore it later
323
325
actob = bpy.context.active_object
325
#check if material exists, if it doesn't then create it
326
mats =bpy.data.materials
327
# check if material exists, if it doesn't then create it
329
for m in bpy.data.materials:
329
330
if m.name == matname:
334
335
target = bpy.data.materials.new(matname)
337
#if objectmodeset all faces
337
# if objectmode then set all polygons
340
340
if actob.mode == 'EDIT':
343
343
bpy.ops.object.mode_set()
345
345
objs = bpy.context.selected_editable_objects
348
#set the active object to our object
348
# set the active object to our object
349
349
scn = bpy.context.scene
350
350
scn.objects.active = ob
353
#check if the material is on the object already
355
#check material slots for matname material
352
if ob.type in {'CURVE', 'SURFACE', 'FONT', 'META'}:
355
for m in bpy.data.materials:
356
if m.name == matname:
364
assignmatslots(ob, targetlist)
366
elif ob.type == 'MESH':
367
# check material slots for matname material
358
370
mats = ob.material_slots
360
372
if m.name == matname:
363
375
#make slot active
364
376
ob.active_material_index = i
370
#the material is not attached to the object
374
bpy.ops.object.material_slot_add()
377
ob.active_material_index = i
379
#and assign material to slot
380
ob.material_slots.values()[i].material = target
382
#the material is not attached to the object
383
ob.data.materials.append(target)
381
385
#now assign the material:
388
for f in me.polygons:
385
389
f.material_index = index
386
elif allfaces == False:
390
elif allpolygons == False:
391
for f in me.polygons:
389
393
f.material_index = index
392
396
#restore the active object
393
397
bpy.context.scene.objects.active = actob
395
bpy.ops.object.mode_set(mode = 'EDIT')
399
def check_texture(img,mat):
399
bpy.ops.object.mode_set(mode='EDIT')
402
def check_texture(img, mat):
400
403
#finds a texture from an image
401
404
#makes a texture if needed
402
405
#adds it to the material if it isn't there already
449
faceindex.append(None)
453
faceindex.append(None)
453
#check materials for images exist; create if needed
455
# check materials for images exist; create if needed
455
457
for i in unique_images:
459
460
m = bpy.data.materials[i.name]
462
m = bpy.data.materials.new(name = i.name)
462
m = bpy.data.materials.new(name=i.name)
466
466
matlist.append(m.name)
467
467
# add textures if needed
470
#set up the object material slots
470
# set up the object material slots
471
471
assignmatslots(ob, matlist)
473
473
#set texface indices to material slot indices..
477
477
for f in faceindex:
479
me.faces[i].material_index = f
479
me.polygons[i].material_index = f
482
bpy.ops.object.mode_set(mode = 'EDIT')
486
#---------------------------------------------------------------------
482
bpy.ops.object.mode_set(mode='EDIT')
485
# -----------------------------------------------------------------------------
488
488
class VIEW3D_OT_texface_to_material(bpy.types.Operator):
490
bl_idname = "texface_to_material"
489
'''Create texture materials for images assigned in UV editor'''
490
bl_idname = "view3d.texface_to_material"
491
491
bl_label = "MW Texface Images to Material/Texture"
492
492
bl_options = {'REGISTER', 'UNDO'}
501
501
return {'FINISHED'}
503
self.report({'WARNING'}, "No editable selected objects, could not finish")
503
self.report({'WARNING'},
504
"No editable selected objects, could not finish")
504
505
return {'CANCELLED'}
506
508
class VIEW3D_OT_assign_material(bpy.types.Operator):
507
'''assign a material to the selection'''
508
bl_idname = "assign_material"
509
'''Assign a material to the selection'''
510
bl_idname = "view3d.assign_material"
509
511
bl_label = "MW Assign Material"
510
512
bl_options = {'REGISTER', 'UNDO'}
512
matname = StringProperty(name = 'Material Name',
513
description = 'Name of Material to Assign',
514
default = "", maxlen = 21)
514
matname = StringProperty(
515
name='Material Name',
516
description='Name of Material to Assign',
517
522
def poll(cls, context):
518
523
return context.active_object != None
576
586
class VIEW3D_OT_replace_material(bpy.types.Operator):
577
'''assign a material to the selection'''
578
bl_idname = "replace_material"
587
'''Replace a material by name'''
588
bl_idname = "view3d.replace_material"
579
589
bl_label = "MW Replace Material"
580
590
bl_options = {'REGISTER', 'UNDO'}
582
matorg = StringProperty(name = 'Material to Replace',
583
description = 'Name of Material to Assign',
584
default = "", maxlen = 21)
586
matrep = StringProperty(name = 'Replacement material',
587
description = 'Name of Material to Assign',
588
default = "", maxlen = 21)
590
all_objects = BoolProperty(name ='all_objects',
591
description="replace for all objects in this blend file",
592
matorg = StringProperty(
593
name='Material to Replace',
594
description="Name of Material to Assign",
597
matrep = StringProperty(name="Replacement material",
598
description='Name of Material to Assign',
601
all_objects = BoolProperty(
603
description="Replace for all objects in this blend file",
595
608
def poll(cls, context):
596
609
return context.active_object != None
614
629
layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN')
615
630
layout.menu("VIEW3D_MT_select_material", icon='HAND')
616
631
layout.separator()
617
layout.operator("clean_material_slots",
618
text = 'Clean Material Slots', icon='CANCEL')
619
layout.operator("material_to_texface",
620
text = 'Material to Texface',icon='FACESEL_HLT')
621
layout.operator("texface_to_material",
622
text = 'Texface to Material',icon='FACESEL_HLT')
632
layout.operator("view3d.clean_material_slots",
633
text='Clean Material Slots',
635
layout.operator("view3d.material_to_texface",
636
text='Material to Texface',
637
icon='polygonsEL_HLT')
638
layout.operator("view3d.texface_to_material",
639
text="Texface to Material",
640
icon='polygonsEL_HLT')
624
642
layout.separator()
625
layout.operator("replace_material",
626
text = 'Replace Material', icon='ARROW_LEFTRIGHT')
643
layout.operator("view3d.replace_material",
644
text='Replace Material',
645
icon='ARROW_LEFTRIGHT')
630
648
class VIEW3D_MT_assign_material(bpy.types.Menu):
655
671
ob = context.object
657
673
if ob.mode == 'OBJECT':
658
#show all materials in entire blend file
659
for i in range (len(bpy.data.materials)):
661
layout.operator("select_material_by_name",
662
text=bpy.data.materials[i].name,
663
icon='MATERIAL_DATA').matname = bpy.data.materials[i].name
674
#show all used materials in entire blend file
675
for material_name, material in bpy.data.materials.items():
676
if material.users > 0:
677
layout.operator("view3d.select_material_by_name",
679
icon='MATERIAL_DATA',
680
).matname = material_name
666
682
elif ob.mode == 'EDIT':
667
683
#show only the materials on this object
668
684
mats = ob.material_slots.keys()
670
layout.operator("select_material_by_name",
686
layout.operator("view3d.select_material_by_name",
672
688
icon='MATERIAL_DATA').matname = m
676
km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
677
kmi = km.items.new('wm.call_menu', 'Q', 'PRESS')
678
kmi.properties.name = "VIEW3D_MT_master_material"
692
bpy.utils.register_module(__name__)
694
kc = bpy.context.window_manager.keyconfigs.addon
696
km = kc.keymaps.new(name="3D View", space_type="VIEW_3D")
697
kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS')
698
kmi.properties.name = "VIEW3D_MT_master_material"
680
701
def unregister():
681
km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
683
if kmi.idname == 'wm.call_menu':
684
if kmi.properties.name == "VIEW3D_MT_master_material":
702
bpy.utils.unregister_module(__name__)
704
kc = bpy.context.window_manager.keyconfigs.addon
706
km = kc.keymaps["3D View"]
707
for kmi in km.keymap_items:
708
if kmi.idname == 'wm.call_menu':
709
if kmi.properties.name == "VIEW3D_MT_master_material":
710
km.keymap_items.remove(kmi)
688
713
if __name__ == "__main__":