diff --git a/space_view3d_materials_utils.py b/space_view3d_materials_utils.py index c34b01d7b87b11173664b99ae07307f8cbfd63f6..39813c7ad3d8373886156f2378ad769aa905a009 100644 --- a/space_view3d_materials_utils.py +++ b/space_view3d_materials_utils.py @@ -29,36 +29,44 @@ bl_info = { "blender": (2, 5, 6), "api": 35324, "location": "View3D > Q key", - "description": "Menu of material tools (assign, select by etc) in the 3D View", + "description": "Menu of material tools (assign, select..) 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", + "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. - + 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) + 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. + 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. + 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 +* Any un-used materials and slots will be removed """ @@ -66,92 +74,92 @@ 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] - - +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 + + matorg = bpy.data.materials.get(m1) + matrep = bpy.data.materials.get(m2) + + if matorg and matrep: #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: + + for m in ob.material_slots.values(): if m.material == matorg: m.material = matrep - #don't break the loop as the material can be + # don't break the loop as the material can be # ref'd more than once - - #restore active object - scn.objects.active = ob_active - except: + + else: 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 - + +def select_material_by_name(find_mat_name): + #in object mode selects all objects with material find_mat_name + #in edit mode selects all faces with material find_mat_name + + find_mat = bpy.data.materials.get(find_mat_name) + + if find_mat is None: + return + #check for editmode editmode = False scn = bpy.context.scene - + #set selection mode to faces - scn.tool_settings.mesh_select_mode =[False,False,True] - + scn.tool_settings.mesh_select_mode = False, False, True + actob = bpy.context.active_object if actob.mode == 'EDIT': - editmode =True + editmode = True bpy.ops.object.mode_set() - - + if not editmode: - objs = bpy.data.objects + objs = bpy.data.objects for ob in objs: - typ = ['MESH','CURVE', 'SURFACE', 'FONT', 'META'] - if ob.type in typ: + if ob.type in {'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'}: ms = ob.material_slots.values() for m in ms: - if m.material.name == find_mat: + if m.material == find_mat: ob.select = True - #the active object may not have the mat! - #set it to one that does! + # 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 + + #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 =[] + slot_indeces = [] i = 0 # found = False # UNUSED for m in ms: - if m.material.name == find_mat: + if m.material == find_mat: slot_indeces.append(i) # found = True # UNUSED i += 1 @@ -161,29 +169,30 @@ def select_material_by_name(find_mat): f.select = True else: f.select = False - me.update() + me.update() if editmode: - bpy.ops.object.mode_set(mode = 'EDIT') + 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 - + # 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 + 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=[] + images = [] #get the textures from the mats for m in ms: gotimage = False @@ -195,17 +204,16 @@ def mat_to_texface(): if tex.type == 'IMAGE': img = tex.image images.append(img) - gotimage =True + gotimage = True break - + if not gotimage: print('noimage on', m.name) images.append(None) - - #now we have the images - #applythem to the uvlayer - - + + # now we have the images + # applythem to the uvlayer + me = ob.data #got uvs? if not me.uv_textures: @@ -213,7 +221,7 @@ def mat_to_texface(): 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: @@ -224,14 +232,12 @@ def mat_to_texface(): uvtex[f.index].image = images[f.material_index] else: uvtex[f.index].image = None - + me.update() - - + if editmode: - bpy.ops.object.mode_set(mode = 'EDIT') - - + bpy.ops.object.mode_set(mode='EDIT') + def assignmatslots(ob, matlist): #given an object and a list of material names @@ -246,15 +252,14 @@ def assignmatslots(ob, matlist): for s in ob.material_slots: bpy.ops.object.material_slot_remove() - - #re-add them and assign material + # 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: + # restore active object: scn.objects.active = ob_active @@ -263,52 +268,49 @@ def cleanmatslots(): editmode = False actob = bpy.context.active_object if actob.mode == 'EDIT': - editmode =True + 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 =[] + 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 + + # 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 + #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] ) + 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: + + # restore face indices: i = 0 for f in me.faces: matindex = mnames.index(faceMats[i]) @@ -316,81 +318,73 @@ def cleanmatslots(): i += 1 if editmode: - bpy.ops.object.mode_set(mode = 'EDIT') - - - + bpy.ops.object.mode_set(mode='EDIT') -def assign_mat(matname="Default"): - #get active object so we can restore it later +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 + + # check if material exists, if it doesn't then create it found = False - for m in mats: + for m in bpy.data.materials: if m.name == matname: target = m found = True break if not found: target = bpy.data.materials.new(matname) - - - #if objectmode then set all faces + + # if objectmode then set all faces editmode = False allfaces = True if actob.mode == 'EDIT': - editmode =True - allfaces = False + 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 + + 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 + + if ob.type in {'CURVE', 'SURFACE', 'FONT', 'META'}: + found = False i = 0 - mats = bpy.data.materials - for m in mats: + for m in bpy.data.materials: if m.name == matname: - found =True + found = True index = i break i += 1 if not found: - index = i-1 - targetlist =[index] + index = i - 1 + targetlist = [index] assignmatslots(ob, targetlist) - - elif ob.type =='MESH': - #check material slots for matname material - found=False + + 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 + 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) - + index = i + #the material is not attached to the object + ob.data.materials.append(target) + #now assign the material: - me =ob.data + me = ob.data if allfaces: for f in me.faces: f.material_index = index @@ -399,17 +393,14 @@ def assign_mat(matname="Default"): 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') + bpy.ops.object.mode_set(mode='EDIT') - -def check_texture(img,mat): +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 @@ -434,11 +425,12 @@ def check_texture(img,mat): 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': + if ob.mode == 'EDIT': editmode = True bpy.ops.object.mode_set() @@ -446,11 +438,11 @@ def texface_to_mat(): 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: + if f.image: img = f.image #build list of unique images if img not in unique_images: @@ -459,30 +451,26 @@ def texface_to_mat(): else: img = None - faceindex.append(None) - - + faceindex.append(None) - #check materials for images exist; create if needed + # 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) + m = bpy.data.materials.new(name=i.name) continue finally: matlist.append(m.name) # add textures if needed - check_texture(i,m) + check_texture(i, m) - #set up the object material slots + # set up the object material slots assignmatslots(ob, matlist) - + #set texface indices to material slot indices.. me = ob.data @@ -492,11 +480,11 @@ def texface_to_mat(): me.faces[i].material_index = f i += 1 if editmode: - bpy.ops.object.mode_set(mode = 'EDIT') + bpy.ops.object.mode_set(mode='EDIT') -#operator classes: -#--------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +# operator classes: class VIEW3D_OT_texface_to_material(bpy.types.Operator): '''''' @@ -513,19 +501,24 @@ class VIEW3D_OT_texface_to_material(bpy.types.Operator): texface_to_mat() return {'FINISHED'} else: - self.report({'WARNING'}, "No editable selected objects, could not finish") + 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) - + matname = StringProperty( + name='Material Name', + description='Name of Material to Assign', + default="", + maxlen=21, + ) + @classmethod def poll(cls, context): return context.active_object != None @@ -538,8 +531,9 @@ class VIEW3D_OT_assign_material(bpy.types.Operator): mat_to_texface() return {'FINISHED'} + class VIEW3D_OT_clean_material_slots(bpy.types.Operator): - '''removes any material slots from the + '''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" @@ -553,6 +547,7 @@ class VIEW3D_OT_clean_material_slots(bpy.types.Operator): cleanmatslots() return {'FINISHED'} + class VIEW3D_OT_material_to_texface(bpy.types.Operator): '''''' bl_idname = "view3d.material_to_texface" @@ -567,14 +562,17 @@ class VIEW3D_OT_material_to_texface(bpy.types.Operator): 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) + matname = StringProperty( + name='Material Name', + description='Name of Material to Select', + maxlen=21, + ) @classmethod def poll(cls, context): @@ -592,18 +590,21 @@ class VIEW3D_OT_replace_material(bpy.types.Operator): 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) + matorg = StringProperty( + name='Material to Replace', + description="Name of Material to Assign", + maxlen=21, + ) + matrep = StringProperty(name="Replacement material", + description='Name of Material to Assign', + maxlen=21, + ) + all_objects = BoolProperty( + name="all_objects", + description="replace for all objects in this blend file", + default=True, + ) - 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 @@ -612,11 +613,13 @@ class VIEW3D_OT_replace_material(bpy.types.Operator): m1 = self.matorg m2 = self.matrep all = self.all_objects - replace_material(m1,m2,all) + replace_material(m1, m2, all) return {'FINISHED'} -#menu classes -#------------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# menu classes + class VIEW3D_MT_master_material(bpy.types.Menu): bl_label = "Master Material Menu" @@ -627,17 +630,20 @@ class VIEW3D_MT_master_material(bpy.types.Menu): 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.clean_material_slots", + text='Clean Material Slots', + icon='CANCEL') layout.operator("view3d.material_to_texface", - text = 'Material to Texface',icon='FACESEL_HLT') + text='Material to Texface', + icon='FACESEL_HLT') layout.operator("view3d.texface_to_material", - text = 'Texface to Material',icon='FACESEL_HLT') + text="Texface to Material", + icon='FACESEL_HLT') layout.separator() - layout.operator("view3d.replace_material", - text = 'Replace Material', icon='ARROW_LEFTRIGHT') - + layout.operator("view3d.replace_material", + text='Replace Material', + icon='ARROW_LEFTRIGHT') class VIEW3D_MT_assign_material(bpy.types.Menu): @@ -646,14 +652,15 @@ class VIEW3D_MT_assign_material(bpy.types.Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - for i in range (len(bpy.data.materials)): - + for material_name in bpy.data.materials.keys(): layout.operator("view3d.assign_material", - text=bpy.data.materials[i].name, - icon='MATERIAL_DATA').matname = bpy.data.materials[i].name + text=material_name, + icon='MATERIAL_DATA').matname = material_name + + layout.operator("view3d.assign_material", + text="Add New", + icon='ZOOMIN') - layout.operator("view3d.assign_material",text="Add New", - icon='ZOOMIN') class VIEW3D_MT_select_material(bpy.types.Menu): bl_label = "Select by Material" @@ -666,30 +673,31 @@ class VIEW3D_MT_select_material(bpy.types.Menu): 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: + for material_name, material in bpy.data.materials.items(): + if material.users > 0: layout.operator("view3d.select_material_by_name", - text=bpy.data.materials[i].name, - icon='MATERIAL_DATA').matname = bpy.data.materials[i].name - + text=material_name, + icon='MATERIAL_DATA', + ).matname = material_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, + text=m, icon='MATERIAL_DATA').matname = m def register(): bpy.utils.register_module(__name__) - + kc = bpy.context.window_manager.keyconfigs.addon km = kc.keymaps.new(name="3D View", space_type="VIEW_3D") kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS') kmi.properties.name = "VIEW3D_MT_master_material" + def unregister(): bpy.utils.unregister_module(__name__) @@ -697,10 +705,9 @@ def unregister(): 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": + if kmi.properties.name == "VIEW3D_MT_master_material": km.keymap_items.remove(kmi) break if __name__ == "__main__": register() -