# ##### 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 ##### # (c) 2016 meta-androcto, parts based on work by Saidenka, lijenstina # Materials Utils: by MichaleW, lijenstina, # (some code thanks to: CoDEmanX, SynaGl0w, # ideasman42) # Materials Conversion: Silvio Falcinelli, johnzero7#, # fixes by angavrilov and others # Link to base names: Sybren, Texture renamer: Yadoob bl_info = { "name": "Materials Utils Specials", "author": "Community", "version": (1, 0, 0), "blender": (2, 77, 0), "location": "Materials Properties Specials/Shift Q", "description": "Materials Utils & Convertors", "warning": "", "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6" "/Py/Scripts", "tracker_url": "", "category": "Material"} if "bpy" in locals(): import importlib importlib.reload(material_converter) importlib.reload(materials_cycles_converter) importlib.reload(texture_rename) importlib.reload(warning_messages_utils) else: from . import material_converter from . import materials_cycles_converter from . import texture_rename from . import warning_messages_utils import bpy import os from os import ( path as os_path, access as os_access, remove as os_remove, ) from bpy.props import ( StringProperty, BoolProperty, EnumProperty, ) from bpy.types import ( Menu, Operator, Panel, AddonPreferences, PropertyGroup, ) from .warning_messages_utils import ( warning_messages, c_is_cycles_addon_enabled, c_data_has_materials, ) # ----------------------------------------------------------------------------- # Globals # # set the default name of the new added material MAT_DEFAULT_NAME = "New Material" # ----------------------------------------------------------------------------- # Functions # def fake_user_set(fake_user='ON', materials='UNUSED', operator=None): warn_mesg, w_mesg = '', "" if materials == 'ALL': mats = (mat for mat in bpy.data.materials if mat.library is None) w_mesg = "(All Materials in this .blend file)" elif materials == 'UNUSED': mats = (mat for mat in bpy.data.materials if mat.library is None and mat.users == 0) w_mesg = "(Unused Materials - Active/Selected Objects)" else: mats = [] if materials == 'ACTIVE': objs = [bpy.context.active_object] w_mesg = "(All Materials on Active Object)" elif materials == 'SELECTED': objs = bpy.context.selected_objects w_mesg = "(All Materials on Selected Objects)" elif materials == 'SCENE': objs = bpy.context.scene.objects w_mesg = "(All Scene Objects)" else: # used materials objs = bpy.data.objects w_mesg = "(All Used Materials)" mats = (mat for ob in objs if hasattr(ob.data, "materials") for mat in ob.data.materials if mat.library is None) # collect mat names for warning_messages matnames = [] warn_mesg = ('FAKE_SET_ON' if fake_user == 'ON' else 'FAKE_SET_OFF') for mat in mats: mat.use_fake_user = (fake_user == 'ON') matnames.append(getattr(mat, "name", "NO NAME")) if operator: if matnames: warning_messages(operator, warn_mesg, matnames, 'MAT', w_mesg) else: warning_messages(operator, 'FAKE_NO_MAT') for area in bpy.context.screen.areas: if area.type in ('PROPERTIES', 'NODE_EDITOR', 'OUTLINER'): area.tag_redraw() def replace_material(m1, m2, all_objects=False, update_selection=False, operator=None): # 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 != matrep and None not in (matorg, matrep): # store active object if all_objects: objs = bpy.data.objects else: objs = bpy.context.selected_editable_objects for ob in objs: if ob.type == 'MESH': match = False for m in ob.material_slots: if m.material == matorg: m.material = matrep # don't break the loop as the material can be # ref'd more than once # Indicate which objects were affected if update_selection: ob.select = True match = True if update_selection and not match: ob.select = False else: if operator: warning_messages(operator, "REP_MAT_NONE") def select_material_by_name(find_mat_name): # in object mode selects all objects with material find_mat_name # in edit mode selects all polygons 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 polygons 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: if included_object_types(ob.type): ms = ob.material_slots for m in ms: if m.material == 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 polygons ob = actob ms = ob.material_slots # same material can be on multiple slots slot_indeces = [] i = 0 for m in ms: if m.material == find_mat: slot_indeces.append(i) i += 1 me = ob.data for f in me.polygons: 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(operator=None): # assigns the first image in each material to the polygons 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() # collect object names for warning messages message_a = [] # Flag if there are non MESH objects selected mixed_obj = False for ob in bpy.context.selected_editable_objects: if ob.type == 'MESH': # get the materials from slots ms = ob.material_slots # build a list of images, one per material images = [] # get the textures from the mats for m in ms: if m.material is None: continue gotimage = False textures = zip(m.material.texture_slots, m.material.use_textures) for t, enabled in textures: if enabled and t is not None: tex = t.texture if tex.type == 'IMAGE': img = tex.image images.append(img) gotimage = True break if not gotimage: images.append(None) # check materials for warning messages mats = ob.material_slots.keys() if operator and not mats and mixed_obj is False: message_a.append(ob.name) # now we have the images # apply them 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 for f in me.polygons: # check that material had an image! if images and images[f.material_index] is not None: uvtex[f.index].image = images[f.material_index] else: uvtex[f.index].image = None me.update() else: message_a.append(ob.name) mixed_obj = True if editmode: bpy.ops.object.mode_set(mode='EDIT') if operator and message_a: warn_mess = ('MAT_TEX_NO_MESH' if mixed_obj is True else 'MAT_TEX_NO_MAT') warning_messages(operator, warn_mess, message_a) def assignmatslots(ob, matlist): # given an object and a list of material names # removes all material slots from 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 if matlist: for m in matlist: try: mat = bpy.data.materials[m] ob.data.materials.append(mat) except: # there is no material with that name in data # or an empty mat is for some reason assigned # to face indices, mat tries to get an '' as mat index pass # restore active object: scn.objects.active = ob_active def cleanmatslots(operator=None): # check for edit mode editmode = False actob = bpy.context.active_object # active object? if actob: if actob.mode == 'EDIT': editmode = True bpy.ops.object.mode_set() # is active object selected ? selected = bool(actob.select) actob.select = True objs = bpy.context.selected_editable_objects # collect all object names for warning_messages message_a = [] # Flag if there are non MESH objects selected mixed_obj = False for ob in objs: if ob.type == 'MESH': mats = ob.material_slots.keys() # if mats is empty then mats[faceindex] will be out of range if mats: # check the polygons 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.polygons: # 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 = False for m in usedMatIndex: if m == faceindex: found = True # break if found is False: # 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.polygons: matindex = mnames.index(faceMats[i]) f.material_index = matindex i += 1 else: message_a.append(getattr(ob, "name", "NO NAME")) continue else: message_a.append(getattr(ob, "name", "NO NAME")) if mixed_obj is False: mixed_obj = True continue if message_a and operator: warn_mess = ('C_OB_MIX_NO_MAT' if mixed_obj is True else 'C_OB_NO_MAT') warning_messages(operator, warn_mess, message_a) if actob: # restore selection state actob.select = selected if editmode: bpy.ops.object.mode_set(mode='EDIT') # separate edit mode mesh function # (faster than iterating through all faces) def assign_mat_mesh_edit(matname="Default", operator=None): actob = bpy.context.active_object found = False 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 (actob.type in {'MESH'} and actob.mode in {'EDIT'}): # check material slots for matname material found = False i = 0 mats = actob.material_slots for m in mats: if m.name == matname: found = True # make slot active actob.active_material_index = i break i += 1 if not found: # the material is not attached to the object actob.data.materials.append(target) # is selected ? selected = bool(actob.select) # select active object actob.select = True # activate the chosen material actob.active_material_index = i # assign the material to the object bpy.ops.object.material_slot_assign() actob.data.update() # restore selection state actob.select = selected if operator: mat_names = ("A New Untitled" if matname in ("", None) else matname) warning_messages(operator, 'A_MAT_NAME_EDIT', mat_names, 'MAT') def assign_mat(matname="Default", operator=None): # get active object so we can restore it later actob = bpy.context.active_object # is active object selected ? selected = bool(actob.select) actob.select = True # check if material exists, if it doesn't then create it found = False 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 polygons editmode = False allpolygons = True if actob.mode == 'EDIT': editmode = True allpolygons = False bpy.ops.object.mode_set() objs = bpy.context.selected_editable_objects # collect non mesh object names message_a = [] for ob in objs: # skip the objects that can't have mats if not included_object_types(ob.type): message_a.append(ob.name) continue else: # set the active object to our object scn = bpy.context.scene scn.objects.active = ob if ob.type in {'CURVE', 'SURFACE', 'FONT', 'META'}: found = False i = 0 for m in bpy.data.materials: 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 allpolygons: for f in me.polygons: f.material_index = index elif allpolygons is False: for f in me.polygons: if f.select: f.material_index = index me.update() # restore the active object bpy.context.scene.objects.active = actob # restore selection state actob.select = selected if editmode: bpy.ops.object.mode_set(mode='EDIT') if operator and message_a: warning_messages(operator, 'A_OB_MIX_NO_MAT', message_a) 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(operator=None): # 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 = [] # collect object names for warning messages message_a = [] # check if object has UV and texture data and active image in Editor if check_texface_to_mat(ob): # get the texface images and store indices 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) else: message_a.append(ob.name) continue # check materials for images exist; create if needed matlist = [] for i in unique_images: if i: 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 is not None: me.polygons[i].material_index = f i += 1 if editmode: bpy.ops.object.mode_set(mode='EDIT') if operator and message_a: warning_messages(operator, "TEX_MAT_NO_CRT", message_a) def remove_materials(operator=None, setting="SLOT"): # Remove material slots from active object # SLOT - removes the object's active material # ALL - removes the all the object's materials actob = bpy.context.active_object actob_name = getattr(actob, "name", "NO NAME") if actob: if not included_object_types(actob.type): if operator: warning_messages(operator, 'OB_CANT_MAT', actob_name) else: if (hasattr(actob.data, "materials") and len(actob.data.materials) > 0): if setting == "SLOT": bpy.ops.object.material_slot_remove() elif setting == "ALL": for mat in actob.data.materials: try: bpy.ops.object.material_slot_remove() except: pass if operator: warn_mess = ('R_ACT_MAT_ALL' if setting == "ALL" else 'R_ACT_MAT') warning_messages(operator, warn_mess, actob_name) elif operator: warning_messages(operator, 'R_OB_NO_MAT', actob_name) def remove_materials_all(operator=None): # Remove material slots from all selected objects # counter for material slots warning messages, collect errors mat_count, collect_mess = False, [] for ob in bpy.context.selected_editable_objects: if not included_object_types(ob.type): continue else: # code from blender stackexchange (by CoDEmanX) ob.active_material_index = 0 if (hasattr(ob.data, "materials") and len(ob.material_slots) >= 1): mat_count = True # Ctx - copy the context for operator override Ctx = bpy.context.copy() # for this operator it needs only the active object replaced Ctx['object'] = ob for i in range(len(ob.material_slots)): try: bpy.ops.object.material_slot_remove(Ctx) except: ob_name = getattr(ob, "name", "NO NAME") collect_mess.append(ob_name) pass if operator: warn_msg = ('R_ALL_NO_MAT' if mat_count is False else 'R_ALL_SL_MAT') if not collect_mess: warning_messages(operator, warn_msg) else: warning_messages(operator, 'R_OB_FAIL_MAT', collect_mess) # ----------------------------------------------------------------------------- # Operator Classes # class VIEW3D_OT_show_mat_preview(Operator): bl_label = "Preview Active Material" bl_idname = "view3d.show_mat_preview" bl_description = ("Show the preview of Active Material \n" "and context related settings") bl_options = {'REGISTER', 'UNDO'} is_not_undo = False # prevent drawing props on undo @classmethod def poll(cls, context): return (context.active_object is not None and context.object.active_material is not None and included_object_types(context.object.type)) def invoke(self, context, event): self.is_not_undo = True return context.window_manager.invoke_props_dialog(self, width=200) def draw(self, context): layout = self.layout ob = context.active_object prw_size = size_preview() if self.is_not_undo is True: if ob and hasattr(ob, "active_material"): mat = ob.active_material is_opaque = (True if (ob and hasattr(ob, "show_transparent") and ob.show_transparent is True) else False) is_opaque_bi = (True if (mat and hasattr(mat, "use_transparency") and mat.use_transparency is True) else False) is_mesh = (True if ob.type == 'MESH' else False) if size_type_is_preview(): layout.template_ID_preview(ob, "active_material", new="material.new", rows=prw_size['Width'], cols=prw_size['Height']) else: layout.template_ID(ob, "active_material", new="material.new") layout.separator() if c_render_engine("Both"): layout.prop(mat, "use_nodes", icon='NODETREE') if c_need_of_viewport_colors(): color_txt = ("Viewport Color:" if c_render_engine("Cycles") else "Diffuse") spec_txt = ("Viewport Specular:" if c_render_engine("Cycles") else "Specular") col = layout.column(align=True) col.label(color_txt) col.prop(mat, "diffuse_color", text="") if c_render_engine("BI"): # Blender Render col.prop(mat, "diffuse_intensity", text="Intensity") col.separator() col.label(spec_txt) col.prop(mat, "specular_color", text="") col.prop(mat, "specular_hardness") if (c_render_engine("BI") and not c_context_use_nodes()): # Blender Render col.separator() col.prop(mat, "use_transparency") col.separator() if is_opaque_bi: col.prop(mat, "transparency_method", text="") col.separator() col.prop(mat, "alpha") elif (c_render_engine("Cycles") and is_mesh): # Cycles col.separator() col.prop(ob, "show_transparent", text="Transparency") if is_opaque: col.separator() col.prop(mat, "alpha") col.separator() col.label("Viewport Alpha:") col.prop(mat.game_settings, "alpha_blend", text="") layout.separator() else: other_render = ("*Unavailable with this Renderer*" if not c_render_engine("Both") else "*Unavailable in this Context*") no_col_label = ("*Only available in Solid Shading*" if c_render_engine("Cycles") else other_render) layout.label(no_col_label, icon="INFO") else: layout.label(text="**Only Undo is available**", icon="INFO") def check(self, context): if self.is_not_undo is True: return True def execute(self, context): self.is_not_undo = False return {'FINISHED'} class VIEW3D_OT_copy_material_to_selected(Operator): bl_idname = "view3d.copy_material_to_selected" bl_label = "Copy Materials to others" bl_description = ("Copy Material From Active to Selected objects \n" "Works on Object's Data linked Materials") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return (c_data_has_materials() and context.active_object is not None and included_object_types(context.active_object.type) and context.object.active_material is not None and context.selected_editable_objects) def execute(self, context): warn_mess = "DEFAULT" if (len(context.selected_editable_objects) < 2): warn_mess = 'CPY_MAT_ONE_OB' else: if check_is_excluded_obj_types(context): warn_mess = 'CPY_MAT_MIX_OB' try: bpy.ops.object.material_slot_copy() warn_mess = 'CPY_MAT_DONE' except: warning_messages(self, 'CPY_MAT_FAIL') return {'CANCELLED'} warning_messages(self, warn_mess) return {'FINISHED'} class VIEW3D_OT_texface_to_material(Operator): bl_idname = "view3d.texface_to_material" bl_label = "Texface Images to Material/Texture" bl_description = ("Create texture materials for images assigned in UV editor \n" "Needs an UV Unwrapped Mesh and an image active in the \n" "UV/Image Editor for each Selected Object") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return context.active_object is not None def invoke(self, context, event): return context.window_manager.invoke_confirm(self, event) def execute(self, context): if context.selected_editable_objects: texface_to_mat(self) return {'FINISHED'} else: warning_messages(self, 'TEX_MAT_NO_SL') return {'CANCELLED'} class VIEW3D_OT_assign_material(Operator): bl_idname = "view3d.assign_material" bl_label = "Assign Material" bl_description = "Assign a material to the selection" bl_options = {'REGISTER', 'UNDO'} is_edit = False matname = StringProperty( name="Material Name", description="Name of Material to Assign", default=MAT_DEFAULT_NAME, maxlen=128, ) @classmethod def poll(cls, context): return context.active_object is not None def execute(self, context): actob = context.active_object mn = self.matname if (actob.type in {'MESH'} and actob.mode in {'EDIT'}): assign_mat_mesh_edit(mn, self) else: assign_mat(mn, self) if use_cleanmat_slots(): cleanmatslots() mat_to_texface() # reset the passing string self.matname = "" return {'FINISHED'} class VIEW3D_OT_clean_material_slots(Operator): bl_idname = "view3d.clean_material_slots" bl_label = "Clean Material Slots" bl_description = ("Removes any unused material slots \n" "from selected objects in Object mode") bl_options = {'REGISTER', 'UNDO'} @classmethod # materials can't be removed in Edit mode def poll(cls, context): return (c_data_has_materials() and context.active_object is not None and not context.object.mode == 'EDIT') def execute(self, context): cleanmatslots(self) return {'FINISHED'} class VIEW3D_OT_material_to_texface(Operator): bl_idname = "view3d.material_to_texface" bl_label = "Material Images to Texface" bl_description = ("Transfer material assignments to UV editor \n" "Works on a Mesh Object with a Material and Texture\n" "assigned. Used primarily with MultiTexture Shading") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): return (c_data_has_materials() and context.active_object is not None) def execute(self, context): if context.selected_editable_objects: mat_to_texface(self) return {'FINISHED'} else: warning_messages(self, "MAT_TEX_NO_SL") return {'CANCELLED'} class VIEW3D_OT_material_remove_slot(Operator): bl_idname = "view3d.material_remove_slot" bl_label = "Remove Active Slot (Active Object)" bl_description = ("Remove active material slot from active object\n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @classmethod # materials can't be removed in Edit mode def poll(cls, context): return (c_data_has_materials() and context.active_object is not None and not context.object.mode == 'EDIT') def execute(self, context): if context.selected_editable_objects: remove_materials(self, "SLOT") return {'FINISHED'} else: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} class VIEW3D_OT_material_remove_object(Operator): bl_idname = "view3d.material_remove_object" bl_label = "Remove all Slots (Active Object)" bl_description = ("Remove all material slots from active object\n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @classmethod # materials can't be removed in Edit mode def poll(cls, context): return (c_data_has_materials() and context.active_object is not None and not context.object.mode == 'EDIT') def execute(self, context): if context.selected_editable_objects: remove_materials(self, "ALL") return {'FINISHED'} else: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} class VIEW3D_OT_material_remove_all(Operator): bl_idname = "view3d.material_remove_all" bl_label = "Remove All Material Slots" bl_description = ("Remove all material slots from all selected objects \n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @classmethod # materials can't be removed in Edit mode def poll(cls, context): return (c_data_has_materials() and context.active_object is not None and not context.object.mode == 'EDIT') def invoke(self, context, event): return context.window_manager.invoke_confirm(self, event) def execute(self, context): if context.selected_editable_objects: remove_materials_all(self) return {'FINISHED'} else: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} class VIEW3D_OT_select_material_by_name(Operator): bl_idname = "view3d.select_material_by_name" bl_label = "Select Material By Name" bl_description = "Select geometry with this material assigned to it" bl_options = {'REGISTER', 'UNDO'} matname = StringProperty( name='Material Name', description='Name of Material to Select', maxlen=63, ) @classmethod def poll(cls, context): return (c_data_has_materials() and context.active_object is not None) def execute(self, context): mn = self.matname select_material_by_name(mn) warning_messages(self, 'SL_MAT_BY_NAME', mn) return {'FINISHED'} class VIEW3D_OT_replace_material(Operator): bl_idname = "view3d.replace_material" bl_label = "Replace Material" bl_description = "Replace a material by name" bl_options = {'REGISTER', 'UNDO'} matorg = StringProperty( name="Original", description="Material to replace", maxlen=63, ) matrep = StringProperty( name="Replacement", description="Replacement material", maxlen=63, ) all_objects = BoolProperty( name="All objects", description="Replace for all objects in this blend file", default=True, ) update_selection = BoolProperty( name="Update Selection", description="Select affected objects and deselect unaffected", default=True, ) @classmethod def poll(cls, context): return c_data_has_materials() def draw(self, context): layout = self.layout layout.prop_search(self, "matorg", bpy.data, "materials") layout.prop_search(self, "matrep", bpy.data, "materials") layout.prop(self, "all_objects") layout.prop(self, "update_selection") def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): replace_material(self.matorg, self.matrep, self.all_objects, self.update_selection, self) self.matorg, self.matrep = "", "" return {'FINISHED'} class VIEW3D_OT_fake_user_set(Operator): bl_idname = "view3d.fake_user_set" bl_label = "Set Fake User" bl_description = "Enable/disable fake user for materials" bl_options = {'REGISTER', 'UNDO'} fake_user = EnumProperty( name="Fake User", description="Turn fake user on or off", items=(('ON', "On", "Enable fake user"), ('OFF', "Off", "Disable fake user")), default='ON', ) materials = EnumProperty( name="Materials", description="Which materials of objects to affect", items=(('ACTIVE', "Active object", "Materials of active object only"), ('SELECTED', "Selected objects", "Materials of selected objects"), ('SCENE', "Scene objects", "Materials of objects in current scene"), ('USED', "Used", "All materials used by objects"), ('UNUSED', "Unused", "Currently unused materials"), ('ALL', "All", "All materials in this blend file")), default='UNUSED', ) @classmethod def poll(cls, context): return c_data_has_materials() def draw(self, context): layout = self.layout layout.prop(self, "fake_user", expand=True) layout.prop(self, "materials") def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): fake_user_set(self.fake_user, self.materials, self) return {'FINISHED'} class MATERIAL_OT_set_transparent_back_side(Operator): bl_idname = "material.set_transparent_back_side" bl_label = "Transparent back (BI)" bl_description = ("Creates BI nodes with Alpha output connected to" "Front/Back Geometry node \n" "on Object's Active Material Slot") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): obj = context.active_object if (not obj): return False mat = obj.active_material if (not mat): return False if (mat.node_tree): if (len(mat.node_tree.nodes) == 0): return True if (not mat.use_nodes): return True return False def execute(self, context): obj = context.active_object mat = obj.active_material try: mat.use_nodes = True if (mat.node_tree): for node in mat.node_tree.nodes: if (node): mat.node_tree.nodes.remove(node) mat.use_transparency = True node_mat = mat.node_tree.nodes.new('ShaderNodeMaterial') node_out = mat.node_tree.nodes.new('ShaderNodeOutput') node_geo = mat.node_tree.nodes.new('ShaderNodeGeometry') node_mat.material = mat node_out.location = [node_out.location[0] + 500, node_out.location[1]] node_geo.location = [node_geo.location[0] + 150, node_geo.location[1] - 150] mat.node_tree.links.new(node_mat.outputs[0], node_out.inputs[0]) mat.node_tree.links.new(node_geo.outputs[8], node_out.inputs[1]) except: warning_messages(self, 'E_MAT_TRNSP_BACK') return {'CANCELLED'} if hasattr(mat, "name"): warning_messages(self, 'MAT_TRNSP_BACK', mat.name, 'MAT') return {'FINISHED'} class MATERIAL_OT_move_slot_top(Operator): bl_idname = "material.move_material_slot_top" bl_label = "Slot to the top" bl_description = "Move the active material slot on top" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): obj = context.active_object if (not obj): return False if (len(obj.material_slots) <= 2): return False if (obj.active_material_index <= 0): return False return True def execute(self, context): activeObj = context.active_object for i in range(activeObj.active_material_index): bpy.ops.object.material_slot_move(direction='UP') active_mat = context.object.active_material if active_mat and hasattr(active_mat, "name"): warning_messages(self, 'MOVE_SLOT_UP', active_mat.name, 'MAT') return {'FINISHED'} class MATERIAL_OT_move_slot_bottom(Operator): bl_idname = "material.move_material_slot_bottom" bl_label = "Slots to the bottom" bl_description = "Move the active material slot to the bottom" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): obj = context.active_object if (not obj): return False if (len(obj.material_slots) <= 2): return False if (len(obj.material_slots) - 1 <= obj.active_material_index): return False return True def execute(self, context): activeObj = context.active_object lastSlotIndex = len(activeObj.material_slots) - 1 for i in range(lastSlotIndex - activeObj.active_material_index): bpy.ops.object.material_slot_move(direction='DOWN') active_mat = context.object.active_material if active_mat and hasattr(active_mat, "name"): warning_messages(self, 'MOVE_SLOT_DOWN', active_mat.name, 'MAT') return {'FINISHED'} class MATERIAL_OT_link_to_base_names(Operator): bl_idname = "material.link_to_base_names" bl_label = "Merge Base Names" bl_description = ("Replace .001, .002 slots with Original \n" "Material/Name on All Materials/Objects") bl_options = {'REGISTER', 'UNDO'} mat_keep = StringProperty( name="Material to keep", default="", ) is_auto = BoolProperty( name="Auto Rename/Replace", description=("Automatically Replace names " "by stripping numerical suffix"), default=False, ) mat_error = [] # collect mat for warning messages is_not_undo = False # prevent drawing props on undo check_no_name = True # check if no name is passed @classmethod def poll(cls, context): return (c_data_has_materials() and context.active_object is not None) def draw(self, context): layout = self.layout if self.is_not_undo is True: boxee = layout.box() boxee.prop_search(self, "mat_keep", bpy.data, "materials") boxee.enabled = not self.is_auto layout.separator() boxs = layout.box() boxs.prop(self, "is_auto", text="Auto Rename/Replace", icon="SYNTAX_ON") else: layout.label(text="**Only Undo is available**", icon="INFO") def invoke(self, context, event): self.is_not_undo = True return context.window_manager.invoke_props_dialog(self) def replace_name(self): # use the chosen material as a base one # check if there is a name self.check_no_name = (False if self.mat_keep in {""} else True) if self.check_no_name is True: for mat in bpy.data.materials: name = mat.name if name == self.mat_keep: try: base, suffix = name.rsplit('.', 1) # trigger the except num = int(suffix, 10) self.mat_keep = base mat.name = self.mat_keep return except ValueError: if name not in self.mat_error: self.mat_error.append(name) return return def split_name(self, material): name = material.name if '.' not in name: return name, None base, suffix = name.rsplit('.', 1) try: # trigger the except num = int(suffix, 10) except ValueError: # Not a numeric suffix if name not in self.mat_error: self.mat_error.append(name) return name, None if self.is_auto is False: if base == self.mat_keep: return base, suffix else: return name, None return base, suffix def fixup_slot(self, slot): if not slot.material: return base, suffix = self.split_name(slot.material) if suffix is None: return try: base_mat = bpy.data.materials[base] except KeyError: print("Link to base names: Base material %r not found" % base) return slot.material = base_mat def check(self, context): return self.is_not_undo def main_loop(self, context): for ob in context.scene.objects: for slot in ob.material_slots: self.fixup_slot(slot) def execute(self, context): if self.is_auto is False: self.replace_name() if self.check_no_name is True: self.main_loop(context) else: warning_messages(self, 'MAT_LINK_NO_NAME') self.is_not_undo = False return {'CANCELLED'} self.main_loop(context) if use_cleanmat_slots(): cleanmatslots() if self.mat_error: warning_messages(self, 'MAT_LINK_ERROR', self.mat_error, 'MAT') self.is_not_undo = False return {'FINISHED'} class MATERIAL_OT_check_converter_path(Operator): bl_idname = "material.check_converter_path" bl_label = "Check Converters images/data save path" bl_description = ("Checks if the given path is writeable \n" "(has OS writing privileges)") bl_options = {'REGISTER', 'INTERNAL'} @classmethod def poll(cls, context): return True def check_valid_path(self, context): sc = context.scene paths = bpy.path.abspath(sc.mat_specials.conv_path) if os_path.exists(paths): if os_access(paths, os.W_OK | os.X_OK): try: path_test = os_path.join(paths, "XYfoobartestXY.txt") with open(path_test, 'w') as f: f.closed os_remove(path_test) return True except (OSError, IOError): warning_messages(self, 'DIR_PATH_W_ERROR') return False else: warning_messages(self, 'DIR_PATH_A_ERROR') return False else: warning_messages(self, 'DIR_PATH_N_ERROR') return False return True def execute(self, context): if not self.check_valid_path(context): return {'CANCELLED'} else: warning_messages(self, 'DIR_PATH_W_OK') return {'FINISHED'} # ----------------------------------------------------------------------------- # Menu classes # class VIEW3D_MT_assign_material(Menu): bl_label = "Assign Material" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' if c_data_has_materials(): # no materials for material_name in bpy.data.materials.keys(): layout.operator("view3d.assign_material", text=material_name, icon='MATERIAL_DATA').matname = material_name use_separator(self, context) if (not context.active_object): # info why the add material is innactive layout.label(text="*No active Object in the Scene*", icon="INFO") use_separator(self, context) layout.operator("view3d.assign_material", text="Add New", icon='ZOOMIN') class VIEW3D_MT_select_material(Menu): bl_label = "Select by Material" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' ob = context.object if (not c_data_has_materials()): # (sad mall music is playing nearby) layout.label(text="*No Materials in the data*", icon="INFO") elif (not ob): # don't worry, i don't like the default cubes, lamps and cameras too layout.label(text="*No Objects to select*", icon="INFO") else: # we did what we could, now you're at the mercy of universe's entropy if ob.mode == 'OBJECT': # show all used materials in entire blend file for material_name, material in bpy.data.materials.items(): if (material.users > 0): layout.operator("view3d.select_material_by_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, icon='MATERIAL_DATA').matname = m class VIEW3D_MT_remove_material(Menu): bl_label = "Clean Slots" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("view3d.clean_material_slots", text="Clean Material Slots", icon='COLOR_BLUE') use_separator(self, context) if not c_render_engine("Lux"): layout.operator("view3d.material_remove_slot", icon='COLOR_GREEN') layout.operator("view3d.material_remove_object", icon='COLOR_RED') if use_remove_mat_all(): use_separator(self, context) layout.operator("view3d.material_remove_all", text="Remove Material Slots " "(All Selected Objects)", icon='CANCEL') else: layout.label(text="Sorry, other Menu functions are", icon="INFO") layout.label(text="unvailable with Lux Renderer") class VIEW3D_MT_master_material(Menu): bl_label = "Material Specials Menu" def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' if use_mat_preview() is True: layout.operator("view3d.show_mat_preview", icon="VISIBLE_IPO_ON") use_separator(self, context) layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') layout.menu("VIEW3D_MT_select_material", icon='HAND') use_separator(self, context) layout.operator("view3d.copy_material_to_selected", icon="COPY_ID") use_separator(self, context) layout.menu("VIEW3D_MT_remove_material", icon="COLORSET_10_VEC") use_separator(self, context) layout.operator("view3d.replace_material", text='Replace Material', icon='ARROW_LEFTRIGHT') layout.operator("view3d.fake_user_set", text='Set Fake User', icon='UNPINNED') use_separator(self, context) layout.menu("VIEW3D_MT_mat_special", icon="SOLO_ON") class VIEW3D_MT_mat_special(Menu): bl_label = "Specials" def draw(self, context): layout = self.layout if c_render_engine("Cycles"): if (enable_converters() is True and converter_type('BI_CONV')): ml_restore_1 = layout.operator("ml.restore", text='To BI Nodes Off', icon="BLENDER") ml_restore_1.switcher = False ml_restore_1.renderer = "BI" ml_restore_2 = layout.operator("ml.restore", text='To BI Nodes On', icon="APPEND_BLEND") ml_restore_2.switcher = True ml_restore_2.renderer = "BI" use_separator(self, context) elif c_render_engine("BI"): if (enable_converters() is True and converter_type('CYC_CONV')): layout.operator("ml.refresh_active", text='Convert Active to Cycles', icon='NODE_INSERT_OFF') layout.operator("ml.refresh", text='Convert All to Cycles', icon='NODE_INSERT_ON') use_separator(self, context) ml_restore_1 = layout.operator("ml.restore", text='To Cycles Nodes Off', icon="SOLID") ml_restore_1.switcher = False ml_restore_1.renderer = "CYCLES" ml_restore_2 = layout.operator("ml.restore", text='To Cycles Nodes On', icon="IMGDISPLAY") ml_restore_2.switcher = True ml_restore_2.renderer = "CYCLES" use_separator(self, context) layout.operator("material.set_transparent_back_side", icon='IMAGE_RGB_ALPHA', text="Transparent back (BI)") layout.operator("view3d.material_to_texface", text="Material to Texface", icon='MATERIAL_DATA') layout.operator("view3d.texface_to_material", text="Texface to Material", icon='TEXTURE_SHADED') use_separator(self, context) layout.operator("material.link_to_base_names", icon="KEYTYPE_BREAKDOWN_VEC") use_separator(self, context) layout.operator("texture.patern_rename", text='Rename Image As Texture', icon='TEXTURE') # Specials Menu's # def menu_func(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' use_separator(self, context) layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') layout.menu("VIEW3D_MT_select_material", icon='HAND') layout.operator("view3d.replace_material", text='Replace Material', icon='ARROW_LEFTRIGHT') use_separator(self, context) layout.menu("VIEW3D_MT_remove_material", icon="COLORSET_10_VEC") use_separator(self, context) layout.operator("view3d.fake_user_set", text='Set Fake User', icon='UNPINNED') use_separator(self, context) layout.menu("VIEW3D_MT_mat_special", icon="SOLO_ON") def menu_move(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' layout.operator("material.move_material_slot_top", icon='TRIA_UP', text="Slot to top") layout.operator("material.move_material_slot_bottom", icon='TRIA_DOWN', text="Slot to bottom") use_separator(self, context) # Converters Menu's # class MATERIAL_MT_scenemassive_opt(Menu): bl_idname = "scenemassive.opt" bl_description = "Additional Options for Convert BI to Cycles" bl_label = "Options" bl_options = {'REGISTER'} def draw(self, context): layout = self.layout sc = context.scene layout.prop(sc.mat_specials, "EXTRACT_ALPHA", text="Extract Alpha Textures (slow)") use_separator(self, context) layout.prop(sc.mat_specials, "EXTRACT_PTEX", text="Extract Procedural Textures (slow)") use_separator(self, context) layout.prop(sc.mat_specials, "EXTRACT_OW", text="Re-extract Textures") use_separator(self, context) layout.prop(sc.mat_specials, "SET_FAKE_USER", text="Set Fake User on unused images") use_separator(self, context) layout.label("Set the Bake Resolution") res = str(sc.mat_specials.img_bake_size) layout.label("Current Setting is : %s" % (res + "x" + res), icon='INFO') use_separator(self, context) layout.prop(sc.mat_specials, "img_bake_size", icon='NODE_SEL', expand=True) class MATERIAL_PT_scenemassive(Panel): bl_label = "Convert BI Materials to Cycles" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return (enable_converters() is True and converter_type('BI_CONV')) def draw(self, context): layout = self.layout sc = context.scene row = layout.row() box = row.box() split = box.box().split(0.5) split.operator("ml.refresh", text="Convert All to Cycles", icon='MATERIAL') split.operator("ml.refresh_active", text="Convert Active to Cycles", icon='MATERIAL') box = box.box() ml_restore = box.operator("ml.restore", text="To BI Nodes Off", icon='MATERIAL') ml_restore.switcher = False ml_restore.renderer = "BI" row = layout.row() box = row.box() box.menu("scenemassive.opt", text="Advanced Options", icon='SCRIPTWIN') box = row.box() box.menu("help.biconvert", text="Usage Information Guide", icon='MOD_EXPLODE') box = layout.box() box.label("Save Directory") split = box.split(0.85) split.prop(sc.mat_specials, "conv_path", text="", icon="RENDER_RESULT") split.operator("material.check_converter_path", text="", icon="EXTERNAL_DATA") class MATERIAL_PT_xps_convert(Panel): bl_label = "Convert to BI and Cycles Nodes" bl_space_type = "PROPERTIES" bl_region_type = "WINDOW" bl_context = "material" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): return (enable_converters() is True and converter_type('CYC_CONV')) def draw(self, context): layout = self.layout row = layout.row() box = row.box() box.label(text="Multi Image Support (Imports)") split = box.box().split(0.5) split.operator("xps_tools.convert_to_cycles_all", text="Convert All to Nodes", icon="TEXTURE") split.operator("xps_tools.convert_to_cycles_selected", text="Convert Selected to Nodes", icon="TEXTURE") col = layout.column() row = col.row() box = row.box() ml_restore = box.operator("ml.restore", text="To BI Nodes ON", icon='MATERIAL') ml_restore.switcher = True ml_restore.renderer = "BI" box = row.box() box.menu("help.nodeconvert", text="Usage Information Guide", icon="MOD_EXPLODE") # Converters Help # class MATERIAL_MT_biconv_help(Menu): bl_idname = "help.biconvert" bl_description = "Read Instructions & Current Limitations" bl_label = "Usage Information Guide" bl_options = {'REGISTER'} def draw(self, context): layout = self.layout layout.label(text="Save Your Work Often", icon="ERROR") use_separator(self, context) layout.label(text="Select the texture loaded in the image node") layout.label(text="Press Ctrl/T to create the image nodes") layout.label(text="In the Node Editor, Select the Diffuse Node") layout.label(text="Enable Node Wrangler addon", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="The default path is the folder where the current .blend is") layout.label(text="During Baking, the script will check writting privileges") layout.label(text="Set the save path for extracting images with full access") layout.label(text="May Require Run As Administrator on Windows OS", icon="ERROR") layout.label(text="Converts Bi Textures to Image Files:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="The Converter report can point out to some failures") layout.label(text="Some material combinations are unsupported") layout.label(text="Single BI Texture/Image per convert is only supported") layout.label(text="Converts Basic BI non node materials to Cycles") use_separator(self, context) layout.label(text="Convert Bi Materials to Cycles Nodes:", icon="INFO") class MATERIAL_MT_nodeconv_help(Menu): bl_idname = "help.nodeconvert" bl_description = "Read Instructions & Current Limitations" bl_label = "Usage Information Guide" bl_options = {'REGISTER'} def draw(self, context): layout = self.layout layout.label(text="Save Your Work Often", icon="ERROR") use_separator(self, context) layout.label(text="Relinking and removing some not needed nodes") layout.label(text="The result Node tree will need some cleaning up") use_separator(self, context) layout.label(text="Select the texture loaded in the image node") layout.label(text="Press Ctrl/T to create the image nodes") layout.label(text="In the Node Editor, Select the Diffuse Node") layout.label(text="Enable Node Wrangler addon", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="Generated images (i.e. Noise and others) are not converted") layout.label(text="The Converter report can point out to some failures") layout.label(text="Not all Files will produce good results", icon="ERROR") layout.label(text="fbx, .dae, .obj, .3ds, .xna and more") layout.label(text="**Supports Imported Files**:", icon="IMPORT") use_separator(self, context) layout.label(text="For some file types") layout.label(text="Supports Alpha, Normals, Specular and Diffuse") layout.label(text="Then Converts BI Nodes to Cycles Nodes") layout.label(text="Converts BI non node materials to BI Nodes") use_separator(self, context) layout.label(text="Convert Materials/Image Textures from Imports:", icon="INFO") # Make Report # class material_converter_report(Operator): bl_idname = "mat_converter.reports" bl_label = "Material Converter Report" bl_description = "Report about done Material Conversions" bl_options = {'REGISTER', 'INTERNAL'} message = StringProperty(maxlen=8192) def draw(self, context): layout = self.layout box = layout.box() box.label(text="Converter Report", icon='INFO') if self.message and type(self.message) is str: list_string = self.message.split("*") for line in range(len(list_string)): box.label(text=str(list_string[line])) def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self, width=500) def execute(self, context): return {'FINISHED'} # ----------------------------------------------------------------------------- # Scene Properties class material_specials_scene_props(PropertyGroup): conv_path = StringProperty( name="Save Directory", description=("Path to save images during conversion \n" "Default is the location of the blend file"), default="//", subtype='DIR_PATH', ) EXTRACT_ALPHA = BoolProperty( attr="EXTRACT_ALPHA", default=False, ) SET_FAKE_USER = BoolProperty( attr="SET_FAKE_USER", default=False, ) EXTRACT_PTEX = BoolProperty( attr="EXTRACT_PTEX", default=False, ) EXTRACT_OW = BoolProperty( attr="Overwrite", default=False, description="Extract textures again instead of re-using priorly extracted textures", ) img_bake_size = EnumProperty( name="Bake Image Size", description="Set the resolution size of baked images \n", items=(('512', "Set : 512 x 512", "Bake Resolution 512 x 512"), ('1024', "Set : 1024 x 1024", "Bake Resolution 1024 x 1024"), ('2048', "Set : 2048 x 2048", "Bake Resolution 2048 x 2048")), default='1024', ) # ----------------------------------------------------------------------------- # Addon Preferences class VIEW3D_MT_material_utils_pref(AddonPreferences): bl_idname = __name__ show_warnings = BoolProperty( name="Enable Warning messages", default=False, description="Show warning messages \n" "when an action is executed or failed.\n \n" "Advisable if you don't know how the tool works", ) show_remove_mat = BoolProperty( name="Enable Remove all Materials", default=False, description="Enable Remove all Materials \n" "for all Selected Objects \n \n" "Use with care - if you want to keep materials after \n" "closing \ reloading Blender Set Fake User for them", ) show_mat_preview = BoolProperty( name="Enable Material Preview", default=True, description="Material Preview of the Active Object \n" "Contains the preview of the active Material, \n" "Use nodes, Color, Specular and Transparency \n" "settings depending on the Context and Preferences", ) set_cleanmatslots = BoolProperty( name="Enable Auto Clean", default=True, description="Enable Automatic Removal of unused Material Slots \n" "called together with the Assign Material menu option. \n \n" "Apart from preference and the cases when it affects \n" "adding materials, enabling it can have some \n" "performance impact on very dense meshes", ) show_separators = BoolProperty( name="Use Separators in the menus", default=True, description="Use separators in the menus, a trade-off between \n" "readability vs. using more space for displaying items", ) show_converters = BoolProperty( name="Enable Converters", default=True, description=" \n ", ) set_preview_size = EnumProperty( name="Preview Menu Size", description="Set the preview menu size \n" "depending on the number of materials \n" "in the scene (width and height)", items=(('2x2', "Size 2x2", "Width 2 Height 2"), ('2x3', "Size 2x3", "Width 3 Height 2"), ('3x3', "Size 3x3", "Width 3 Height 3"), ('3x4', "Size 3x4", "Width 4 Height 3"), ('4x4', "Size 4x4", "Width 4 Height 4"), ('5x5', "Size 5x5", "Width 5 Height 5"), ('6x6', "Size 6x6", "Width 6 Height 6"), ('0x0', "List", "Display as a List")), default='3x3', ) set_preview_type = EnumProperty( name="Preview Menu Type", description="Set the the Preview menu type \n", items=(('LIST', "Classic", "Display as a Classic List like in Blender Propreties. \n \n" "Preview of Active Material not available"), ('PREVIEW', "Preview Display", "Display as a preview of Thumbnails \n" "It can have some performance issues with \n" "scenes containing a lot of materials \n \n" "Preview of Active Material available")), default='PREVIEW', ) set_experimental_type = EnumProperty( name="Experimental Features", description=" \n", items=(('ALL', "All Converters", "Enable all Converters"), ('CYC_CONV', "BI and Cycles Nodes", "Enable Cycles related Convert"), ('BI_CONV', "BI To Cycles", "Enable Blender Internal related Converters")), default='ALL', ) def draw(self, context): layout = self.layout box = layout.box() split = box.split(align=True) col = split.column() col.prop(self, "show_warnings") cola = split.column() cola.alignment = 'RIGHT' cola.prop(self, "set_cleanmatslots") cola.prop(self, "show_separators") col.prop(self, "show_remove_mat") boxie = box.box() row = boxie.row() row.prop(self, "show_mat_preview") rowsy = row.split() rowsy.enabled = (True if self.show_mat_preview else False) rowsy.alignment = 'CENTER' rowsy.prop(self, "set_preview_type", text="") rowsa = rowsy.row() rowsa.enabled = (True if self.set_preview_type in {'PREVIEW'} else False) rowsa.alignment = 'CENTER' rowsa.prop(self, "set_preview_size", text="") boxif = box.box() rowf = boxif.row() rowf.prop(self, "show_converters") rowsf = rowf.split() rowsf.enabled = (True if self.show_converters else False) rowsf.alignment = 'RIGHT' rowsf.prop(self, "set_experimental_type", text="") # ----------------------------------------------------------------------------- # utility functions: def included_object_types(objects): # Pass the bpy.data.objects.type to avoid needless assigning/removing # included - type that can have materials included = ['MESH', 'CURVE', 'SURFACE', 'FONT', 'META'] obj = objects return bool(obj and obj in included) def check_is_excluded_obj_types(contxt): # pass the context to check if selected objects have excluded types if contxt and contxt.selected_editable_objects: for obj in contxt.selected_editable_objects: if not included_object_types(obj.type): return True return False def check_texface_to_mat(obj): # check for UV data presence if obj: if hasattr(obj.data, "uv_textures"): if hasattr(obj.data.uv_textures, "active"): if hasattr(obj.data.uv_textures.active, "data"): return True return False def c_context_mat_preview(): # returns the type of viewport shading # needed for using the optional UI elements (the context gets lost) # code from BA user SynaGl0w # if there are multiple 3d views return the biggest screen area one views_3d = [area for area in bpy.context.screen.areas if area.type == 'VIEW_3D' and area.spaces.active] if views_3d: main_view_3d = max(views_3d, key=lambda area: area.width * area.height) return main_view_3d.spaces.active.viewport_shade return "NONE" def c_context_use_nodes(): # checks if Use Nodes is ticked on actob = bpy.context.active_object u_node = (actob.active_material.use_nodes if hasattr(actob, "active_material") else False) return bool(u_node) def c_render_engine(cyc=None): # valid cyc inputs "Cycles", "BI", "Both", "Lux" scene = bpy.context.scene render_engine = scene.render.engine r_engines = {"Cycles": 'CYCLES', "BI": 'BLENDER_RENDER', "Both": ('CYCLES', 'BLENDER_RENDER'), "Lux": 'LUXRENDER_RENDER'} if cyc: return (True if cyc in r_engines and render_engine in r_engines[cyc] else False) return render_engine def c_need_of_viewport_colors(): # check the context where using Viewport color and friends are needed # Cycles and BI are supported if c_render_engine("Cycles"): if c_context_use_nodes() and c_context_mat_preview() == 'SOLID': return True elif c_context_mat_preview() in ('SOLID', 'TEXTURED', 'MATERIAL'): return True elif (c_render_engine("BI") and not c_context_use_nodes()): return True return False # Draw Separator # def use_separator(operator, context): # pass the preferences show_separators bool to enable/disable them pref = return_preferences() useSep = pref.show_separators if useSep: operator.layout.separator() # preferences utilities # def return_preferences(): return bpy.context.user_preferences.addons[__name__].preferences def use_remove_mat_all(): pref = return_preferences() show_rmv_mat = pref.show_remove_mat return bool(show_rmv_mat) def use_mat_preview(): pref = return_preferences() show_mat_prw = pref.show_mat_preview return bool(show_mat_prw) def use_cleanmat_slots(): pref = return_preferences() use_mat_clean = pref.set_cleanmatslots return bool(use_mat_clean) def size_preview(): pref = return_preferences() set_size_prw = pref.set_preview_size cell_w = int(set_size_prw[0]) cell_h = int(set_size_prw[-1]) cell_tbl = {'Width': cell_w, 'Height': cell_h} return cell_tbl def size_type_is_preview(): pref = return_preferences() set_prw_type = pref.set_preview_type return bool(set_prw_type in {'PREVIEW'}) def enable_converters(): pref = return_preferences() shw_conv = pref.show_converters return shw_conv def converter_type(types='ALL'): # checks the type of the preferences 'ALL', 'CYC_CONV', 'BI_CONV' pref = return_preferences() set_exp_type = pref.set_experimental_type return bool(set_exp_type in {'ALL'} or types == set_exp_type) def register(): bpy.utils.register_module(__name__) warning_messages_utils.MAT_SPEC_NAME = __name__ # Register Scene Properties bpy.types.Scene.mat_specials = bpy.props.PointerProperty( type=material_specials_scene_props ) kc = bpy.context.window_manager.keyconfigs.addon if kc: km = kc.keymaps.new(name="3D View", space_type="VIEW_3D") kmi = km.keymap_items.new('wm.call_menu', 'Q', 'PRESS', shift=True) kmi.properties.name = "VIEW3D_MT_master_material" bpy.types.MATERIAL_MT_specials.prepend(menu_move) bpy.types.MATERIAL_MT_specials.append(menu_func) def unregister(): kc = bpy.context.window_manager.keyconfigs.addon if kc: 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 bpy.types.MATERIAL_MT_specials.remove(menu_move) bpy.types.MATERIAL_MT_specials.remove(menu_func) del bpy.types.Scene.mat_specials bpy.utils.unregister_module(__name__) if __name__ == "__main__": register()