diff options
author | lijenstina <lijenstina@gmail.com> | 2018-02-25 00:44:17 +0300 |
---|---|---|
committer | lijenstina <lijenstina@gmail.com> | 2018-02-25 00:44:17 +0300 |
commit | abc7a07914f3b7273ca7717d2691ac2b8dec2de4 (patch) | |
tree | c7378dbad49faff8fa82f58f8174404d98af5d1c /materials_utils | |
parent | cc56b808ca8338fe5d2b3ff1aba65429025cacf5 (diff) |
Materials Utils: Update to version 1.0.4
Bump version to 1.0.4
Some style tweaks
Refactor some of the if - else loops
Correct some of the tooltips
Various UI tweaks
Improvements:
- Introduce a pop-up menu for assigning and selecting materials
- Add support for removing empty material slots for non mesh objects
- Activate the material slot more reliably after creation, applying
- Texture Renamer: add option for rename all objects in the data
- Warning Messages: better formatting of console prints
Diffstat (limited to 'materials_utils')
-rw-r--r-- | materials_utils/__init__.py | 1434 | ||||
-rw-r--r-- | materials_utils/material_converter.py | 16 | ||||
-rw-r--r-- | materials_utils/materials_cycles_converter.py | 106 | ||||
-rw-r--r-- | materials_utils/texture_rename.py | 76 | ||||
-rw-r--r-- | materials_utils/warning_messages_utils.py | 36 |
5 files changed, 1030 insertions, 638 deletions
diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py index 3bc94ddf..19be24d2 100644 --- a/materials_utils/__init__.py +++ b/materials_utils/__init__.py @@ -26,7 +26,7 @@ bl_info = { "name": "Materials Utils Specials", "author": "Community", - "version": (1, 0, 3), + "version": (1, 0, 4), "blender": (2, 77, 0), "location": "Materials Properties Specials > Shift Q", "description": "Materials Utils and Convertors", @@ -34,7 +34,7 @@ bl_info = { "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" "Scripts/3D_interaction/Materials_Utils", "category": "Material" - } +} if "bpy" in locals(): import importlib @@ -51,27 +51,35 @@ else: import bpy import os from os import ( - path as os_path, - access as os_access, - remove as os_remove, - ) + path as os_path, + access as os_access, + remove as os_remove, +) from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - PointerProperty, - ) + BoolProperty, + CollectionProperty, + EnumProperty, + IntProperty, + StringProperty, + PointerProperty, +) from bpy.types import ( - Menu, - Operator, - Panel, - AddonPreferences, - PropertyGroup, - ) + AddonPreferences, + Menu, + Operator, + Panel, + PropertyGroup, + UIList, +) from .warning_messages_utils import ( - warning_messages, - c_data_has_materials, - ) + warning_messages, + c_data_has_materials, + c_obj_data_has_materials, +) + +# Globals +UNDO_MESSAGE = "*Only Undo is available*" +COLUMN_SPLIT = 20 # Functions @@ -146,7 +154,7 @@ def replace_material(m1, m2, all_objects=False, update_selection=False, operator if m.material == matorg: m.material = matrep # don't break the loop as the material can be - # ref'd more than once + # referenced more than once # Indicate which objects were affected if update_selection: @@ -169,7 +177,7 @@ def select_material_by_name(find_mat_name): if find_mat is None: return - # check for editmode + # check for edit mode editmode = False scn = bpy.context.scene @@ -200,7 +208,7 @@ def select_material_by_name(find_mat_name): else: ob.select = False else: - # it's editmode, so select the polygons + # it's edit mode, so select the polygons ob = actob ms = ob.material_slots @@ -227,7 +235,7 @@ def select_material_by_name(find_mat_name): def mat_to_texface(operator=None): # assigns the first image in each material to the polygons in the active - # uvlayer for all selected objects + # uv layer for all selected objects # check for editmode editmode = False @@ -282,7 +290,7 @@ def mat_to_texface(operator=None): bpy.ops.mesh.uv_texture_add() scn.objects.active = actob - # get active uvlayer + # get active uv layer for t in me.uv_textures: if t.active: uvtex = t.data @@ -316,7 +324,7 @@ def assignmatslots(ob, matlist): scn.objects.active = ob for s in ob.material_slots: - bpy.ops.object.material_slot_remove() + remove_material_slot() # re-add them and assign material if matlist: @@ -330,7 +338,7 @@ def assignmatslots(ob, matlist): # to face indices, mat tries to get an '' as mat index pass - # restore active object: + # restore active object scn.objects.active = ob_active @@ -352,65 +360,97 @@ def cleanmatslots(operator=None): 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 + # Flags if there are non MESH objects selected + mixed_obj, mixed_obj_slot = False, 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: + if ob.type != 'MESH': + mat_empty = [] message_a.append(getattr(ob, "name", "NO NAME")) if mixed_obj is False: mixed_obj = True + + # at least try to remove empty material slots + if ob.type in {'CURVE', 'SURFACE', 'FONT', 'META'}: + mats = ob.material_slots + mat_empty = [i for i, slot in enumerate(mats) if not slot.material] + + if not mat_empty: + continue + + if mixed_obj_slot is False: + mixed_obj_slot = 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 index in mat_empty: + try: + ob.active_material_index = index + bpy.ops.object.material_slot_remove(Ctx) + except: + continue + continue + + mats = ob.material_slots.keys() + maxindex = len(mats) - 1 # indices start from zero + + # if mats is empty then mats[faceindex] will be out of range + if not mats: + message_a.append(getattr(ob, "name", "NO NAME")) continue + # check the polygons on the mesh to build a list of used materials + usedMatIndex = [] # we'll store used materials indices here + faceMats = [] + badIndices = set() # collect face indices that are out material slot's range + me = ob.data + for f in me.polygons: + # get the material index for this face... + faceindex = f.material_index + # check if the mats[faceindex] is not out of range + if faceindex > maxindex: + badIndices.add(faceindex) + continue + + # 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 + + 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: + if u not in badIndices: + 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: + if i not in badIndices: + matindex = mnames.index(faceMats[i]) + f.material_index = matindex + i += 1 + if message_a and operator: - warn_mess = ('C_OB_MIX_NO_MAT' if mixed_obj is True else 'C_OB_NO_MAT') + warn_s = 'C_OB_MIX_NO_MAT' if mixed_obj is True else 'C_OB_NO_MAT' + warn_mess = 'C_OB_MIX_SLOT_MAT' if mixed_obj_slot else warn_s warning_messages(operator, warn_mess, message_a) if actob: @@ -426,12 +466,11 @@ def cleanmatslots(operator=None): 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: + + # check if material exists, if it doesn't then create it + target = bpy.data.materials.get(matname) + + if not target: target = bpy.data.materials.new(matname) if (actob.type in {'MESH'} and actob.mode in {'EDIT'}): @@ -447,8 +486,8 @@ def assign_mat_mesh_edit(matname="Default", operator=None): break i += 1 + # the material is not attached to the object if not found: - # the material is not attached to the object actob.data.materials.append(target) # is selected ? @@ -472,7 +511,7 @@ def assign_mat_mesh_edit(matname="Default", operator=None): def assign_mat(matname="Default", operator=None): - # get active object so we can restore it later + # get the active object so we can restore it later actob = bpy.context.active_object # is active object selected ? @@ -480,19 +519,15 @@ def assign_mat(matname="Default", operator=None): 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 + target = bpy.data.materials.get(matname) - if not found: + if not target: target = bpy.data.materials.new(matname) - # if objectmode then set all polygons + # if object mode then set all polygons editmode = False allpolygons = True + if actob.mode == 'EDIT': editmode = True allpolygons = False @@ -596,7 +631,7 @@ def check_texture(img, mat): def texface_to_mat(operator=None): - # editmode check here! + # edit mode check here! editmode = False ob = bpy.context.object if ob.mode == 'EDIT': @@ -604,7 +639,6 @@ def texface_to_mat(operator=None): bpy.ops.object.mode_set() for ob in bpy.context.selected_editable_objects: - faceindex = [] unique_images = [] # collect object names for warning messages @@ -668,27 +702,30 @@ def remove_materials(operator=None, setting="SLOT"): 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) + if not actob: + return + + if not included_object_types(actob.type): + if operator: + warning_messages(operator, 'OB_CANT_MAT', actob_name) + return + + if (hasattr(actob.data, "materials") and len(actob.data.materials) > 0): + if setting == "SLOT": + remove_material_slot() + elif setting == "ALL": + for mat in actob.data.materials: + try: + remove_material_slot() + 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): @@ -700,11 +737,10 @@ def remove_materials_all(operator=None): if not included_object_types(ob.type): continue else: - # code from blender stackexchange (by CoDEmanX) + # code from blender stack exchange (by CoDEmanX) ob.active_material_index = 0 - if (hasattr(ob.data, "materials") and - len(ob.material_slots) >= 1): + if (hasattr(ob.data, "materials") and len(ob.material_slots) >= 1): mat_count = True # Ctx - copy the context for operator override @@ -740,9 +776,10 @@ class VIEW3D_OT_show_mat_preview(Operator): @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)) + obj = context.active_object + return (obj is not None and + obj.active_material is not None and + included_object_types(obj.type)) def invoke(self, context, event): self.is_not_undo = True @@ -753,92 +790,107 @@ class VIEW3D_OT_show_mat_preview(Operator): 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") + if self.is_not_undo is False: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + if not (ob and hasattr(ob, "active_material")): + layout.label(text="No Active Object or Active Material", icon="INFO") + return + + 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.label(text="**Only Undo is available**", icon="INFO") + layout.template_ID(ob, "active_material", new="material.new") + layout.separator() + + if c_render_engine("Both"): + layout.prop(mat, "use_nodes", icon='NODETREE') + + if not c_need_of_viewport_colors(): + 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") + return + + 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() def check(self, context): return self.is_not_undo 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" + bl_description = ("Copy Material From Active to Selected objects\n" + "In case of multiple materials, only the first slot is assigned\n" "Works on Object's Data linked Materials") bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): + obj = context.active_object 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 + obj is not None and + included_object_types(obj.type) and + obj.active_material is not None and context.selected_editable_objects) def execute(self, context): @@ -856,6 +908,7 @@ class VIEW3D_OT_copy_material_to_selected(Operator): return {'CANCELLED'} warning_messages(self, warn_mess) + return {'FINISHED'} @@ -875,13 +928,14 @@ class VIEW3D_OT_texface_to_material(Operator): return context.window_manager.invoke_confirm(self, event) def execute(self, context): - if context.selected_editable_objects: - texface_to_mat(self) - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'TEX_MAT_NO_SL') return {'CANCELLED'} + texface_to_mat(self) + + return {'FINISHED'} + class VIEW3D_OT_set_new_material_name(Operator): bl_idname = "view3d.set_new_material_name" @@ -904,6 +958,7 @@ class VIEW3D_OT_set_new_material_name(Operator): layout.prop(scene, "use_tweak") def execute(self, context): + return {'FINISHED'} @@ -911,34 +966,61 @@ class VIEW3D_OT_assign_material(Operator): bl_idname = "view3d.assign_material" bl_label = "Assign Material" bl_description = "Assign a material to the selection" + bl_property = "matname" bl_options = {'REGISTER', 'UNDO'} is_existing = BoolProperty( name="Is it a new Material", options={'HIDDEN'}, default=True, - ) + ) matname = StringProperty( name="Material Name", description="Name of the Material to Assign", options={'HIDDEN'}, default="Material_New", maxlen=128, - ) + ) + is_not_undo = False # prevent drawing props on undo @classmethod def poll(cls, context): return context.active_object is not None def invoke(self, context, event): - return self.execute(context) + self.is_not_undo = True + + if not self.is_existing or use_mat_menu_type() not in {'POPUP'}: + return self.execute(context) + + materials_lists_fill_names(context, refresh=True, is_object=False) + return context.window_manager.invoke_props_dialog(self, width=400, height=200) + + def check(self, context): + return self.is_not_undo + + def draw(self, context): + draw_ui_list_popups(self, context, obj_data=False) def execute(self, context): actob = context.active_object - mn = self.matname scene = context.scene.mat_specials + mn = self.matname tweak = scene.use_tweak + if use_mat_menu_type() == 'POPUP' and self.is_existing: + mats_col = context.scene.mat_specials_mats + len_mats = len(mats_col) + mat_index = scene.index_mat + + if not (len_mats > 0 and mat_index is not None and mat_index <= len_mats): + self.report({'WARNING'}, + "No Materials in the Scene. Please use the Add New option") + return {"CANCELLED"} + + mats_up = mats_col[mat_index].name + mn = mats_up + if not self.is_existing: new_name = check_mat_name_unique(scene.set_material_name) mn = new_name @@ -954,6 +1036,8 @@ class VIEW3D_OT_assign_material(Operator): mat_to_texface() self.is_not_undo = False + activate_mat_slot(actob, mn) + if tweak and not self.is_existing: try: bpy.ops.view3d.show_mat_preview('INVOKE_DEFAULT') @@ -979,6 +1063,7 @@ class VIEW3D_OT_clean_material_slots(Operator): def execute(self, context): cleanmatslots(self) + return {'FINISHED'} @@ -996,18 +1081,19 @@ class VIEW3D_OT_material_to_texface(Operator): context.active_object is not None) def execute(self, context): - if context.selected_editable_objects: - mat_to_texface(self) - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, "MAT_TEX_NO_SL") return {'CANCELLED'} + mat_to_texface(self) + + return {'FINISHED'} + 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" + bl_description = ("Remove active material slot from active object \n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @@ -1016,21 +1102,22 @@ class VIEW3D_OT_material_remove_slot(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_object.mode == 'EDIT') def execute(self, context): - if context.selected_editable_objects: - remove_materials(self, "SLOT") - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials(self, "SLOT") + + return {'FINISHED'} + 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" + bl_description = ("Remove all material slots from active object \n" "Can't be used in Edit Mode") bl_options = {'REGISTER', 'UNDO'} @@ -1039,16 +1126,17 @@ class VIEW3D_OT_material_remove_object(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_object.mode == 'EDIT') def execute(self, context): - if context.selected_editable_objects: - remove_materials(self, "ALL") - return {'FINISHED'} - else: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials(self, "ALL") + + return {'FINISHED'} + class VIEW3D_OT_material_remove_all(Operator): bl_idname = "view3d.material_remove_all" @@ -1062,19 +1150,20 @@ class VIEW3D_OT_material_remove_all(Operator): # materials can't be removed in Edit mode return (c_data_has_materials() and context.active_object is not None and - not context.object.mode == 'EDIT') + not context.active_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: + if not context.selected_editable_objects: warning_messages(self, 'R_NO_SL_MAT') return {'CANCELLED'} + remove_materials_all(self) + + return {'FINISHED'} + class VIEW3D_OT_select_material_by_name(Operator): bl_idname = "view3d.select_material_by_name" @@ -1083,20 +1172,59 @@ class VIEW3D_OT_select_material_by_name(Operator): bl_options = {'REGISTER', 'UNDO'} matname = StringProperty( - name='Material Name', - description='Name of Material to Select', - maxlen=63, - ) + name="Material Name", + description="Name of Material to Select", + maxlen=63, + ) + is_not_undo = False + is_edit = False @classmethod def poll(cls, context): + obj = context.active_object return (c_data_has_materials() and - context.active_object is not None) + obj is not None and obj.mode in {"OBJECT", "EDIT"}) + + def invoke(self, context, event): + self.is_not_undo = True + + if use_mat_menu_type() not in {'POPUP'}: + return self.execute(context) + + obj = context.active_object + self.is_edit = bool(obj.mode == 'EDIT') + materials_lists_fill_names(context, refresh=True, is_object=self.is_edit) + + return context.window_manager.invoke_props_dialog(self, width=400, height=200) + + def check(self, context): + return self.is_not_undo + + def draw(self, context): + draw_ui_list_popups(self, context, obj_data=self.is_edit) def execute(self, context): - mn = self.matname + if use_mat_menu_type() == 'POPUP': + mats_col = context.scene.mat_specials_mats + scene = context.scene.mat_specials + len_mats = len(mats_col) + mat_index = scene.index_mat + + if not (len_mats > 0 and mat_index is not None and mat_index <= len_mats): + self.report({'WARNING'}, + "No materials found. Operation Cancelled") + return {"CANCELLED"} + + mats_up = mats_col[mat_index].name + mn = mats_up + else: + mn = self.matname + select_material_by_name(mn) - warning_messages(self, 'SL_MAT_BY_NAME', mn) + message = 'SL_MAT_EDIT_BY_NAME' if self.is_edit else 'SL_MAT_BY_NAME' + warning_messages(self, message, mn) + self.is_not_undo = False + return {'FINISHED'} @@ -1107,25 +1235,26 @@ class VIEW3D_OT_replace_material(Operator): bl_options = {'REGISTER', 'UNDO'} matorg = StringProperty( - name="Original", - description="Material to replace", - maxlen=63, - ) + name="Original", + description="Material to replace", + maxlen=63, + ) matrep = StringProperty( - name="Replacement", - description="Replacement material", - maxlen=63, - ) + name="Replacement", + description="Replacement material", + maxlen=63, + ) all_objects = BoolProperty( - name="All objects", - description="Replace for all objects in this blend file", - default=True, - ) + name="All objects", + description="If enabled, replace for all objects in this blend file\n" + "If disabled, only selected objects will be affected", + default=False, + ) update_selection = BoolProperty( - name="Update Selection", - description="Select affected objects and deselect unaffected", - default=True, - ) + name="Update Selection", + description="Select affected objects and deselect unaffected", + default=True, + ) @classmethod def poll(cls, context): @@ -1142,9 +1271,12 @@ class VIEW3D_OT_replace_material(Operator): 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) + replace_material( + self.matorg, self.matrep, self.all_objects, + self.update_selection, self + ) self.matorg, self.matrep = "", "" + return {'FINISHED'} @@ -1155,22 +1287,22 @@ class VIEW3D_OT_fake_user_set(Operator): 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', - ) + 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="Chose what objects and materials 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', - ) + name="Materials", + description="Chose what objects and materials 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): @@ -1186,6 +1318,7 @@ class VIEW3D_OT_fake_user_set(Operator): def execute(self, context): fake_user_set(self.fake_user, self.materials, self) + return {'FINISHED'} @@ -1199,15 +1332,15 @@ class MATERIAL_OT_set_transparent_back_side(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False mat = obj.active_material - if (not mat): + if not mat: return False - if (mat.node_tree): + if mat.node_tree: if (len(mat.node_tree.nodes) == 0): return True - if (not mat.use_nodes): + if not mat.use_nodes: return True return False @@ -1249,7 +1382,7 @@ class MATERIAL_OT_move_slot_top(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False if (len(obj.material_slots) <= 2): return False @@ -1279,7 +1412,7 @@ class MATERIAL_OT_move_slot_bottom(Operator): @classmethod def poll(cls, context): obj = context.active_object - if (not obj): + if not obj: return False if (len(obj.material_slots) <= 2): return False @@ -1309,15 +1442,14 @@ class MATERIAL_OT_link_to_base_names(Operator): bl_options = {'REGISTER', 'UNDO'} mat_keep = StringProperty( - name="Material to keep", - default="", - ) + name="Material to keep", + default="", + ) is_auto = BoolProperty( - name="Auto Rename/Replace", - description=("Automatically Replace names " - "by stripping numerical suffix"), - default=False, - ) + 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 @@ -1328,16 +1460,18 @@ class MATERIAL_OT_link_to_base_names(Operator): 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") + + if self.is_not_undo is False: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + 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") def invoke(self, context, event): self.is_not_undo = True @@ -1433,13 +1567,14 @@ class MATERIAL_OT_link_to_base_names(Operator): 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 = "Check if the given path is writeable (has OS writing privileges)" + bl_description = "Check if the given path is writable (has OS writing privileges)" bl_options = {'REGISTER', 'INTERNAL'} def check_valid_path(self, context): @@ -1450,24 +1585,24 @@ class MATERIAL_OT_check_converter_path(Operator): warning_messages(self, "DIR_PATH_EMPTY", override=True) return False - 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', override=True) - return False - else: - warning_messages(self, 'DIR_PATH_A_ERROR', override=True) - return False - else: + if not os_path.exists(paths): warning_messages(self, 'DIR_PATH_N_ERROR', override=True) return False + if not os_access(paths, os.W_OK | os.X_OK): + warning_messages(self, 'DIR_PATH_A_ERROR', override=True) + return False + + 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', override=True) + return False + return True def execute(self, context): @@ -1479,6 +1614,67 @@ class MATERIAL_OT_check_converter_path(Operator): return {'FINISHED'} +# Material selections pop-up + +class VIEW3D_UL_assign_material_popup_ui(UIList): + + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + self.use_filter_show = True + + col = layout.column(align=True) + col.alignment = "LEFT" + mat = bpy.data.materials.get(item.name, None) + if not mat: + col.label(text="{} - is not available".format(item.name), icon="ERROR") + else: + split = col.split(percentage=0.75, align=True) + row = split.row(align=True) + row.label(text=item.name, translate=False, icon="MATERIAL_DATA") + subrow = split.row(align=True) + subrow.alignment = "RIGHT" + subrow.label(text="", icon="PINNED" if item.mat_fake_user else "BLANK1") + subrow = split.row(align=True) + subrow.alignment = "RIGHT" + subrow.label(text="", icon="LINK_BLEND" if item.mat_lib else "BLANK1") + + +def draw_ui_list_popups(self, context, obj_data=False): + layout = self.layout + + if not self.is_not_undo: + layout.label(text=UNDO_MESSAGE, icon="INFO") + return + + matlen = len(context.scene.mat_specials_mats) + matdata = "in Object's Data" if obj_data else "in Data" + matgramma = "Material" if matlen == 1 else "Materials" + matcount = "No" if matlen < 1 else matlen + + box = layout.box() + col = box.column(align=True) + col.label(text="{} {} {}".format(matcount, matgramma, matdata), + icon="INFO") + sub_split = col.split(percentage=0.7, align=True) + sub_box_1 = sub_split.box() + sub_box_1.label("Name") + sub_split_2 = sub_split.split(percentage=0.5, align=True) + sub_box_2 = sub_split_2.box() + sub_box_2.label("Fake") + sub_box_3 = sub_split_2.box() + sub_box_3.label("Lib") + + col.template_list( + "VIEW3D_UL_assign_material_popup_ui", + 'mat_specials', + context.scene, + 'mat_specials_mats', + context.scene.mat_specials, + 'index_mat', + rows=10 + ) + return + + # Menu classes class VIEW3D_MT_assign_material(Menu): @@ -1488,26 +1684,45 @@ class VIEW3D_MT_assign_material(Menu): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - if c_data_has_materials(): - # no materials - for material_name in bpy.data.materials.keys(): - mats = layout.operator("view3d.assign_material", - text=material_name, - icon='MATERIAL_DATA') - mats.matname = material_name - mats.is_existing = True - 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) + mat_prop_name = context.scene.mat_specials.set_material_name - add_new = layout.operator("view3d.assign_material", - text="Add New", icon='ZOOMIN') + add_new = layout.operator( + "view3d.assign_material", + text="Add New", icon='ZOOMIN' + ) add_new.matname = mat_prop_name add_new.is_existing = False + if use_mat_menu_type() != 'POPUP': + use_separator(self, context) + + if c_data_has_materials(): + mat_entry = layout.column() + + if use_mat_menu_type() == 'POPUP': + matp = mat_entry.operator( + "view3d.assign_material", + icon='MATERIAL_DATA' + ) + matp.is_existing = True + elif use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column() + mat_entry = layout.column_flow(columns=get_col_size) + + if use_mat_menu_type() in {'COLUMNS', 'STANDARD'}: + for material_name in bpy.data.materials.keys(): + mats = mat_entry.operator( + "view3d.assign_material", + text=material_name, + icon='MATERIAL_DATA' + ) + mats.matname = material_name + mats.is_existing = True + class VIEW3D_MT_select_material(Menu): bl_label = "Select by Material" @@ -1515,28 +1730,58 @@ class VIEW3D_MT_select_material(Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' - ob = context.object + obj = context.active_object + has_users = False + col = layout.column() + + if not c_data_has_materials(): + layout.label(text="*No Materials in the Data*", icon="INFO") + return + elif not obj: + layout.label(text="*No Active Object*", icon="INFO") + return + elif obj.mode == "EDIT" and not c_obj_data_has_materials(obj): + layout.label(text="*No Materials in the Object's Data*", icon="INFO") + return + elif use_mat_menu_type() == 'POPUP': + col.operator( + "view3d.select_material_by_name", + text="Select by Material", + icon='HAND', + ) + return - if (not c_data_has_materials()): - layout.label(text="*No Materials in the data*", icon="INFO") - elif (not ob): - layout.label(text="*No Objects to select*", icon="INFO") + if obj.mode == 'OBJECT': + if use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column(is_edit=False) + col = layout.column_flow(columns=get_col_size) + # show all used materials in entire blend file + for material_name, material in bpy.data.materials.items(): + if material.users > 0: + has_users = True + col.operator( + "view3d.select_material_by_name", + text=material_name, + icon='MATERIAL_DATA', + ).matname = material_name + if not has_users: + layout.label(text="*No users for Materials in the data*", icon="INFO") + return + elif obj.mode == 'EDIT': + if use_mat_menu_type() == 'COLUMNS': + get_col_size = switch_to_column(is_edit=True) + col = layout.column_flow(columns=get_col_size) + # show only the materials on this object + mats = obj.material_slots.keys() + for m in mats: + if m not in "": # skip empty slots + col.operator( + "view3d.select_material_by_name", + text=m, + icon='MATERIAL_DATA' + ).matname = m else: - 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 + layout.label(text="*Works only in Object and Edit mode*", icon="INFO") class VIEW3D_MT_remove_material(Menu): @@ -1545,25 +1790,38 @@ class VIEW3D_MT_remove_material(Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' + obj = context.active_object - layout.operator("view3d.clean_material_slots", - text="Clean Material Slots", - icon='COLOR_BLUE') - use_separator(self, context) + if obj.mode in {'TEXTURE_PAINT'}: + layout.label( + text="Removing materials can lead to loss of painting data", + icon="INFO" + ) + 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') + layout.operator( + "view3d.clean_material_slots", + text="Clean Material Slots", + icon='COLOR_BLUE' + ) + use_separator(self, context) - 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: + if c_render_engine("Lux"): layout.label(text="Sorry, other Menu functions are", icon="INFO") layout.label(text="unvailable with Lux Renderer") + return + + 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' + ) class VIEW3D_MT_master_material(Menu): @@ -1577,8 +1835,13 @@ class VIEW3D_MT_master_material(Menu): 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') + if use_mat_menu_type() == 'POPUP': + VIEW3D_MT_assign_material.draw(self, context) + use_separator(self, context) + VIEW3D_MT_select_material.draw(self, context) + else: + 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") @@ -1668,8 +1931,15 @@ def menu_func(self, context): 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') + if use_mat_menu_type() == 'POPUP': + VIEW3D_MT_assign_material.draw(self, context) + use_separator(self, context) + VIEW3D_MT_select_material.draw(self, context) + else: + layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN') + layout.menu("VIEW3D_MT_select_material", icon='HAND') + use_separator(self, context) + layout.operator("view3d.replace_material", text='Replace Material', icon='ARROW_LEFTRIGHT') @@ -1744,11 +2014,15 @@ class MATERIAL_PT_scenemassive(Panel): def poll(cls, context): return (enable_converters() is True and converter_type('BI_CONV')) + def draw_header(self, context): + layout = self.layout + help_panel_header(layout, menu_type="HELP_MT_biconvert") + def draw(self, context): layout = self.layout sc = context.scene - row = layout.row() - box = row.box() + col = layout.column(align=True) + box = col.box() split = box.box().split(0.5) split.operator("ml.refresh", @@ -1760,14 +2034,9 @@ class MATERIAL_PT_scenemassive(Panel): icon='MATERIAL') ml_restore.switcher = False ml_restore.renderer = "BI" - - box = layout.box() - row = box.row() row.menu("scenemassive.opt", text="Advanced Options", icon='SCRIPTWIN') - row.menu("HELP_MT_biconvert", - text="Usage Information Guide", icon="INFO") - box = layout.box() + box = col.box() box.label("Save Directory") split = box.split(0.85) split.prop(sc.mat_specials, "conv_path", text="", icon="RENDER_RESULT") @@ -1786,28 +2055,32 @@ class MATERIAL_PT_xps_convert(Panel): def poll(cls, context): return (enable_converters() is True and converter_type('CYC_CONV')) + def draw_header(self, context): + layout = self.layout + help_panel_header(layout, menu_type="help.nodeconvert") + def draw(self, context): layout = self.layout - row = layout.row() - box = row.box() + col = layout.column(align=True) + box = col.box() - box.label(text="Multi Image Support (Imports)") + box.label(text="Multi Image Support (Imports)", icon="INFO") 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") + 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" + ) - box = layout.box() - row = box.row() - ml_restore = row.operator("ml.restore", text="To BI Nodes ON", + box = col.box() + ml_restore = box.operator("ml.restore", text="To BI Nodes ON", icon='MATERIAL') ml_restore.switcher = True ml_restore.renderer = "BI" - row.menu("help.nodeconvert", - text="Usage Information Guide", icon="INFO") - # Converters Help # @@ -1829,12 +2102,12 @@ class MATERIAL_MT_biconv_help(Menu): 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="Enable Node Wrangler add-on", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="Extract Alpha: the images have to have alpha channel") 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="During Baking, the script will check writing 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") @@ -1864,7 +2137,7 @@ class MATERIAL_MT_nodeconv_help(Menu): 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="Enable Node Wrangler add-on", icon="NODETREE") layout.label(text="If Unconnected or No Image Node Error:", icon="MOD_EXPLODE") use_separator(self, context) layout.label(text="For Specular Nodes, Image color influence has to be enabled") @@ -1872,7 +2145,7 @@ class MATERIAL_MT_nodeconv_help(Menu): 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") + 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") @@ -1883,7 +2156,7 @@ class MATERIAL_MT_nodeconv_help(Menu): # Make Report -class material_converter_report(Operator): +class MATERIAL_OT_converter_report(Operator): bl_idname = "mat_converter.reports" bl_label = "Material Converter Report" bl_description = "Report about done Material Conversions" @@ -1904,208 +2177,309 @@ class material_converter_report(Operator): return context.window_manager.invoke_props_dialog(self, width=500) def execute(self, context): + return {'FINISHED'} # Scene Properties +class material_specials_scene_mats(PropertyGroup): + name = StringProperty() + mat_lib = BoolProperty( + default=False + ) + mat_fake_user = BoolProperty( + default=False + ) + + 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', - ) + 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, - description=("Extract Alpha channel from non-procedural images \n" - "Don't use this option if the image doesn't have Alpha"), - ) + attr="EXTRACT_ALPHA", + default=False, + description="Extract Alpha channel from non-procedural images\n" + "Don't use this option if the image doesn't have Alpha" + ) SET_FAKE_USER = BoolProperty( - attr="SET_FAKE_USER", - default=False, - description="Set fake user on unused images, so they can be kept in the .blend", - ) + attr="SET_FAKE_USER", + default=False, + description="Set fake user on unused images, so they can be kept in the .blend" + ) EXTRACT_PTEX = BoolProperty( - attr="EXTRACT_PTEX", - default=False, - description="Extract procedural images and bake them to jpeg", - ) + attr="EXTRACT_PTEX", + default=False, + description="Extract procedural images and bake them to jpeg" + ) EXTRACT_OW = BoolProperty( - attr="Overwrite", - default=False, - description="Extract textures again instead of re-using priorly extracted textures", - ) + attr="Overwrite", + default=False, + description="Extract textures again instead of re-using previously extracted textures" + ) SCULPT_PAINT = BoolProperty( - attr="SCULPT_PAINT", - default=False, - description=("Conversion geared towards sculpting and painting.\n" - "Creates a diffuse, glossy mixed with layer weight. \n" - "Image nodes are not connected"), - ) + attr="SCULPT_PAINT", + default=False, + description="Conversion geared towards sculpting and painting.\n" + "Creates a diffuse, glossy mixed with layer weight.\n" + "Image nodes are not connected" + ) UV_UNWRAP = BoolProperty( - attr="UV_UNWRAP", - default=False, - description=("Use automatical Angle based UV Unwrap of the active Object"), - ) + attr="UV_UNWRAP", + default=False, + description="Use automatic Angle based UV Unwrap for the Active Object" + ) enable_report = BoolProperty( - attr="enable_report", - default=False, - description=("Enable Converter Report in the UI"), - ) + attr="enable_report", + default=False, + description="Enable Converter Report in the UI" + ) 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', - ) + 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' + ) set_material_name = StringProperty( - name="New Material name", - description="What Base name pattern to use for a new created Material\n" - "It is appended by an automatic numeric pattern depending\n" - "on the number of Scene's materials containing the Base", - default="Material_New", - maxlen=128, - ) + name="New Material name", + description="What Base name pattern to use for a new created Material\n" + "It is appended by an automatic numeric pattern depending\n" + "on the number of Scene's materials containing the Base", + default="Material_New", + maxlen=128 + ) use_tweak = BoolProperty( name="Tweak Settings", description="Open Preview Active Material after new Material creation", - default=False, - ) + default=False + ) + index_mat = IntProperty( + name="index", + options={"HIDDEN"} + ) -# Addon Preferences +# Add-on 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", - ) + name="Enable Warning messages", + default=False, + description="Show warning messages when an action is executed or failed" + ) show_remove_mat = BoolProperty( - name="Enable Remove all Materials", - default=False, - description="Enable Remove all Materials for all Selected Objects\n\n" - "Use with care - if you want to keep materials after\n" - "closing or reloading Blender, Set Fake User for them", - ) + name="Enable Remove all Materials", + default=False, + description="Enable Remove all Materials for all Selected Objects\n\n" + "Use with care - if you want to keep materials after\n" + "closing or 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", - ) + 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", - ) + name="Enable Auto Clean", + default=True, + description="Enable Automatic Removal of unused Material Slots\n" + "called together with the Assign Material menu option.\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", - ) + 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=False, - description="Enable Material Converters", - ) + name="Enable Converters", + default=False, + description="Enable Material Converters" + ) 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', - ) + 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", - items=(('LIST', "Classic", - "Display as a Classic List like in Blender Propreties.\n" - "Preview of Active Material is not available"), - ('PREVIEW', "Preview Display", - "Display as a preview of Thumbnails\n" - "It can have some performance issues with scenes containing a lot of materials\n" - "Preview of Active Material is available")), - default='PREVIEW', - ) + name="Preview Menu Type", + description="Set the the Preview menu type", + items=( + ('LIST', "Classic", + "Display as a Classic List like in Blender Properties.\n" + "Preview of Active Material is not available\n\n" + "Note: Choosing a different material from the list will replace the active one"), + ('PREVIEW', "Preview Display", + "Display as a preview of Thumbnails\n" + "It can have some performance issues with scenes containing a lot of materials\n" + "Preview of Active Material is available\n\n" + "Note: Choosing a different material from the list will replace the active one") + ), + default='PREVIEW' + ) set_experimental_type = EnumProperty( - name="Experimental Features", - description="Set the Type of converters enabled", - 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', - ) + name="Experimental Features", + description="Set the type of converters enabled", + 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', + ) + set_add_material_menu = EnumProperty( + name="Add Material Menu", + description="Set the type of Add Material menu", + items=( + ('STANDARD', "Standard Menu", + "Material entries in the menu are bellow each other"), + ('COLUMNS', "Column Menu", + "Material entries are placed in column blocks"), + ('POPUP', "Pop up Menu", + "Material entries are placed in a scrollable list inside a pop-up menu") + ), + default='POPUP' + ) def draw(self, context): layout = self.layout sc = context.scene - box = layout.box() + col_m = layout.column(align=True) + + box = col_m.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") - - box = layout.box() + split.operator( + "material.check_converter_path", + text="", icon="EXTERNAL_DATA" + ) + box = col_m.box() split = box.split(align=True) - col = split.column() + col = split.column(align=True) col.prop(self, "show_warnings") col.prop(self, "show_remove_mat") - - col = split.column() - col.alignment = 'RIGHT' col.prop(self, "set_cleanmatslots") col.prop(self, "show_separators") - boxie = box.box() - split = boxie.split(percentage=0.3) + col = split.column(align=True) + col.label("Apply / Select Material mode:") + col.prop(self, "set_add_material_menu", expand=True) + + box = col_m.box() + size_split = 0.3 if self.show_mat_preview else 1.0 + split = box.split(percentage=size_split, align=True) split.prop(self, "show_mat_preview") + if self.show_mat_preview: - rowsy = split.row(align=True) - rowsy.enabled = True if self.show_mat_preview else False - rowsy.prop(self, "set_preview_type", expand=True) - rowsa = boxie.row(align=True) - rowsa.enabled = True if self.set_preview_type in {'PREVIEW'} else False - rowsa.prop(self, "set_preview_size", expand=True) - - boxif = box.box() - split = boxif.split(percentage=0.3) + subsplit = split.split(percentage=0.7, align=True) + row = subsplit.row(align=True) + row.prop(self, "set_preview_type", expand=True) + + subrow = subsplit.row(align=True) + subrow.enabled = True if self.set_preview_type in {'PREVIEW'} else False + subrow.prop(self, "set_preview_size", text="") + + box = col_m.box() + size_split = 0.3 if self.show_converters else 1.0 + split = box.split(percentage=size_split, align=True) split.prop(self, "show_converters") + if self.show_converters: - rowe = split.row(align=True) - rowe.prop(self, "set_experimental_type", expand=True) + row = split.row(align=True) + row.prop(self, "set_experimental_type", expand=True) # utility functions: +def help_panel_header(layout, menu_type="VIEW3D_MT_master_material"): + layout.separator() + box = layout.box() + box.scale_y = 0.5 + box.menu(menu_type, text="", icon="INFO") + layout.separator() + + +def activate_mat_slot(actob, matname): + mats = actob.material_slots + for i, m in enumerate(mats): + if m.name == matname: + # make slot active + actob.active_material_index = i + break + + +def materials_lists_fill_names(context, refresh=False, is_object=False): + mats_list = context.scene.mat_specials_mats + if refresh: + for key in mats_list.keys(): + index = mats_list.find(key) + if index != -1: + mats_list.remove(index) + + obj = context.active_object + mat_collection = [] + if (is_object and obj): + mat_collection = [ + slot.material for slot in obj.material_slots if + slot.material + ] + else: + mat_collection = bpy.data.materials + + for mat in mat_collection: + if mat.name not in mats_list.keys() or refresh: + prop = mats_list.add() + prop.name = mat.name + prop.mat_lib = bool(mat.library) + prop.mat_fake_user = mat.use_fake_user + + +def switch_to_column(is_edit=False): + obj = bpy.context.active_object + collect = obj.material_slots if is_edit else bpy.data.materials + col_size = int(round(len(collect) / COLUMN_SPLIT)) + + return col_size if col_size > 0 else 1 + + +def remove_material_slot(): + if bpy.ops.object.material_slot_remove.poll(): + bpy.ops.object.material_slot_remove() + + def check_mat_name_unique(name_id="Material_new"): # check if the new name pattern is in materials' data name_list = [] @@ -2232,6 +2606,13 @@ def use_remove_mat_all(): return bool(show_rmv_mat) +def use_mat_menu_type(): + pref = return_preferences() + use_menu_mat = pref.set_add_material_menu + + return use_menu_mat + + def use_mat_preview(): pref = return_preferences() show_mat_prw = pref.show_mat_preview @@ -2286,8 +2667,12 @@ def register(): # Register Scene Properties bpy.types.Scene.mat_specials = PointerProperty( - type=material_specials_scene_props - ) + type=material_specials_scene_props + ) + bpy.types.Scene.mat_specials_mats = CollectionProperty( + name="Material name", + type=material_specials_scene_mats + ) kc = bpy.context.window_manager.keyconfigs.addon if kc: @@ -2313,6 +2698,7 @@ def unregister(): bpy.types.MATERIAL_MT_specials.remove(menu_func) del bpy.types.Scene.mat_specials + del bpy.types.Scene.mat_specials_mats bpy.utils.unregister_module(__name__) diff --git a/materials_utils/material_converter.py b/materials_utils/material_converter.py index dfde5c50..c682e612 100644 --- a/materials_utils/material_converter.py +++ b/materials_utils/material_converter.py @@ -4,11 +4,11 @@ import bpy from mathutils import Vector from bpy.types import Operator from .warning_messages_utils import ( - warning_messages, - c_is_cycles_addon_enabled, - c_data_has_materials, - collect_report, - ) + warning_messages, + c_is_cycles_addon_enabled, + c_data_has_materials, + collect_report, +) # ----------------------------------------------------------------------------- # Globals @@ -752,6 +752,7 @@ class material_convert_all(Operator): def execute(self, context): AutoNode(False, self) + return {'FINISHED'} @@ -766,13 +767,12 @@ class material_convert_selected(Operator): def poll(cls, context): return (bpy.data.filepath != "" and c_data_has_materials() and c_is_cycles_addon_enabled() and - bool(next((obj for obj in context.selected_objects if obj.type == 'MESH'), - None) - ) + bool(next((obj for obj in context.selected_objects if obj.type == 'MESH'), None)) ) def execute(self, context): AutoNode(True, self) + return {'FINISHED'} diff --git a/materials_utils/materials_cycles_converter.py b/materials_utils/materials_cycles_converter.py index c9dd994d..ddd4eb6e 100644 --- a/materials_utils/materials_cycles_converter.py +++ b/materials_utils/materials_cycles_converter.py @@ -6,18 +6,18 @@ import bpy from os import path as os_path from bpy.types import Operator from math import ( - log2, ceil, - ) + log2, ceil, +) from bpy.props import ( - BoolProperty, - EnumProperty, - ) + BoolProperty, + EnumProperty, +) from .warning_messages_utils import ( - warning_messages, - c_is_cycles_addon_enabled, - c_data_has_materials, - collect_report, - ) + warning_messages, + c_is_cycles_addon_enabled, + c_data_has_materials, + collect_report, +) # ----------------------------------------------------------------------------- # Globals @@ -130,7 +130,7 @@ def BakingText(tex, mode, tex_type=None): img = bpy.data.images.get("TMP_BAKING") img.file_format = ("JPEG" if not mode == "ALPHA" else "PNG") - # switch temporarly to 'IMAGE EDITOR', other approaches are not reliable + # switch temporarily to 'IMAGE EDITOR', other approaches are not reliable check_area = False store_area = bpy.context.area.type collect_report("INFO: Temporarly switching context to Image Editor") @@ -267,8 +267,7 @@ def AutoNode(active=False, operator=None): for n in TreeNodes.nodes: TreeNodes.nodes.remove(n) - # Starting point is diffuse BSDF and output material - # and a Color Ramp node + # Starting point is diffuse BSDF and output material and a Color Ramp node shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse') shader.location = 10, 10 shader_val = TreeNodes.nodes.new('ShaderNodeValToRGB') @@ -848,6 +847,21 @@ def create_mix_node(TreeNodes, links, nodes, loc, start, median_point, row, fram return mix_node +def unwrap_active_object(context): + enable_unwrap = context.scene.mat_specials.UV_UNWRAP + if enable_unwrap: + obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") + try: + # it's possible that the active object would fail UV Unwrap + bpy.ops.object.editmode_toggle() + bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) + bpy.ops.object.editmode_toggle() + collect_report("INFO: UV Unwrapping active object {}".format(obj_name)) + except: + collect_report("ERROR: UV Unwrapping failed for " + "active object {}".format(obj_name)) + + # ----------------------------------------------------------------------------- # Operator Classes @@ -866,10 +880,8 @@ class mllock(Operator): TreeNodes = cmat.node_tree for n in TreeNodes.nodes: if n.type == 'ShaderNodeOutputMaterial': - if n.label == 'Locked': - n.label = '' - else: - n.label = 'Locked' + n.label = "" if n.label == "Locked" else "Locked" + return {'FINISHED'} @@ -882,28 +894,17 @@ class mlrefresh(Operator): @classmethod def poll(cls, context): - return (bpy.data.filepath != ""and c_is_cycles_addon_enabled() and + return (bpy.data.filepath != "" and c_is_cycles_addon_enabled() and c_data_has_materials()) def execute(self, context): AutoNodeInitiate(False, self) if CHECK_AUTONODE is True: - enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP - if enable_unwrap: - obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") - try: - # it's possible to the active object would fail UV Unwrap - bpy.ops.object.editmode_toggle() - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) - bpy.ops.object.editmode_toggle() - collect_report("INFO: UV Unwrapping active object " - "{}".format(obj_name)) - except: - collect_report("ERROR: UV Unwrapping failed for " - "active object {}".format(obj_name)) + unwrap_active_object(context) collect_report("Conversion finished !", False, True) + return {'FINISHED'} @@ -922,20 +923,10 @@ class mlrefresh_active(Operator): def execute(self, context): AutoNodeInitiate(True, self) if CHECK_AUTONODE is True: - obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT") - enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP - if enable_unwrap: - try: - # you can already guess it, what could happen here - bpy.ops.object.editmode_toggle() - bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001) - bpy.ops.object.editmode_toggle() - collect_report("INFO: UV Unwrapping object {}".format(obj_name)) - except: - collect_report("ERROR: UV Unwrapping failed for " - "object {}".format(obj_name)) + unwrap_active_object(context) collect_report("Conversion finished !", False, True) + return {'FINISHED'} @@ -947,27 +938,28 @@ class mlrestore(Operator): bl_options = {'REGISTER', 'UNDO'} switcher = BoolProperty( - name="Use Nodes", - description="When restoring, switch Use Nodes On/Off", - default=True - ) + name="Use Nodes", + description="When restoring, switch Use Nodes On/Off", + default=True + ) renderer = EnumProperty( - name="Renderer", - description="Choose Cycles or Blender Internal", - items=(('CYCLES', "Cycles", "Switch to Cycles"), - ('BI', "Blender Internal", "Switch to Blender Internal")), - default='CYCLES', - ) + name="Renderer", + description="Choose Cycles or Blender Internal", + items=( + ('CYCLES', "Cycles", "Switch to Cycles"), + ('BI', "Blender Internal", "Switch to Blender Internal") + ), + default='CYCLES', + ) @classmethod def poll(cls, context): return c_is_cycles_addon_enabled() def execute(self, context): - if self.switcher: - AutoNodeSwitch(self.renderer, "ON", self) - else: - AutoNodeSwitch(self.renderer, "OFF", self) + switch = "ON" if self.switcher else "OFF" + AutoNodeSwitch(self.renderer, switch, self) + return {'FINISHED'} diff --git a/materials_utils/texture_rename.py b/materials_utils/texture_rename.py index c803295e..585a3a7d 100644 --- a/materials_utils/texture_rename.py +++ b/materials_utils/texture_rename.py @@ -3,31 +3,41 @@ import bpy from bpy.types import ( - Operator, - Panel, - ) -from bpy.props import StringProperty + Operator, + Panel, +) +from bpy.props import ( + BoolProperty, + StringProperty, +) from .warning_messages_utils import ( - warning_messages, - c_data_has_images, - ) + warning_messages, + c_data_has_images, +) class TEXTURE_OT_patern_rename(Operator): bl_idname = "texture.patern_rename" bl_label = "Texture Renamer" bl_description = ("Replace the Texture names pattern with the attached Image ones\n" - "Works on all Textures (Including Brushes) \n \n" - "The First field - the name pattern to replace \n" - "The Second - searches for existing names \n") + "Works on all Textures (Including Brushes)\n" + "The First field - the name pattern to replace\n" + "The Second - search for existing names") bl_options = {'REGISTER', 'UNDO'} def_name = "Texture" # default name is_not_undo = False # prevent drawing props on undo + named = StringProperty( - name="Search for name", - default=def_name - ) + name="Search for name", + description="Enter the name pattern or choose the one from the dropdown list below", + default=def_name + ) + replace_all = BoolProperty( + name="Replace all", + description="Replace all the Textures in the data with the names of the images attached", + default=False + ) @classmethod def poll(cls, context): @@ -35,28 +45,35 @@ class TEXTURE_OT_patern_rename(Operator): def draw(self, context): layout = self.layout - if self.is_not_undo is True: - box = layout.box() - box.prop(self, "named", text="Name pattern", icon="SYNTAX_ON") - layout.separator() + if not self.is_not_undo: + layout.label(text="*Only Undo is available*", icon="INFO") + return + + layout.prop(self, "replace_all") - box = layout.box() - box.prop_search(self, "named", bpy.data, "textures") - else: - layout.label(text="**Only Undo is available**", icon="INFO") + box = layout.box() + box.enabled = not self.replace_all + box.prop(self, "named", text="Name pattern", icon="SYNTAX_ON") + + box = layout.box() + box.enabled = not self.replace_all + box.prop_search(self, "named", bpy.data, "textures") def invoke(self, context, event): self.is_not_undo = True return context.window_manager.invoke_props_dialog(self) + def check(self, context): + return self.is_not_undo + def execute(self, context): errors = [] # collect texture names without images attached - tex_count = 0 # check if there is textures at all + tex_count = len(bpy.data.textures) for texture in bpy.data.textures: try: - if texture and self.named in texture.name and texture.type in {"IMAGE"}: - tex_count += 1 + is_allowed = self.named in texture.name if not self.replace_all else True + if texture and is_allowed and texture.type in {"IMAGE"}: textname = "" img = (bpy.data.textures[texture.name].image if bpy.data.textures[texture.name] else None) if not img: @@ -67,7 +84,7 @@ class TEXTURE_OT_patern_rename(Operator): else: break texture.name = textname - if texture.type != "IMAGE": # rename specific textures as clouds, environnement map,... + if texture.type != "IMAGE": # rename specific textures as clouds, environment map... texture.name = texture.type.lower() except: continue @@ -86,7 +103,6 @@ class TEXTURE_OT_patern_rename(Operator): class TEXTURE_PT_rename_panel(Panel): - # Creates a Panel in the scene context of the properties editor bl_label = "Texture Rename" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' @@ -98,13 +114,13 @@ class TEXTURE_PT_rename_panel(Panel): def register(): - bpy.utils.register_module(__name__) - pass + bpy.utils.register_class(TEXTURE_OT_patern_rename) + bpy.utils.register_class(TEXTURE_PT_rename_panel) def unregister(): - bpy.utils.unregister_module(__name__) - pass + bpy.utils.unregister_class(TEXTURE_PT_rename_panel) + bpy.utils.unregister_class(TEXTURE_OT_patern_rename) if __name__ == "__main__": diff --git a/materials_utils/warning_messages_utils.py b/materials_utils/warning_messages_utils.py index 2e5f3b69..00f4a719 100644 --- a/materials_utils/warning_messages_utils.py +++ b/materials_utils/warning_messages_utils.py @@ -21,7 +21,7 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, # a list of strings can be passed and concatenated in obj_name too # is_mat a switch to change to materials or textures for obj_name('MAT','TEX', 'FILE', None) # fake - optional string that can be passed - # MAX_COUNT - max members of an list to be displayed + # MAX_COUNT - max members of an list to be displayed in UI report # override - important messages that should be enabled, no matter the setting # pass the show_warnings bool to enable/disable them @@ -72,6 +72,8 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, "not cleaned"), 'C_OB_MIX_NO_MAT': "{}{}".format(obj_name, "No Materials or an Object type that " "can't have Materials (Clean Material Slots)"), + 'C_OB_MIX_SLOT_MAT': "{}{}".format(obj_name, "No Materials or only empty Slots are removed " + "(Clean Material Slots)"), 'R_OB_NO_MAT': "{}{}".format(obj_name, "No Materials. Nothing to remove"), 'R_OB_FAIL_MAT': "{}{}".format(obj_name, "Failed to remove materials - (Operator Error)"), 'R_NO_SL_MAT': "No Selection. Material slots are not removed", @@ -79,6 +81,7 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, 'R_ALL_NO_MAT': "Object(s) have no materials to remove", 'R_ACT_MAT': "{}{}".format(obj_name, "Removed active Material"), 'R_ACT_MAT_ALL': "{}{}".format(obj_name, "Removed all Material from the Object"), + 'SL_MAT_EDIT_BY_NAME': "{}{}{}".format("Geometry with the Material ", obj_name, "been selected"), 'SL_MAT_BY_NAME': "{}{}{}".format("Objects with the Material ", obj_name, "been selected"), 'OB_CANT_MAT': "{}{}".format(obj_name, "Object type that can't have Materials"), 'REP_MAT_NONE': "Replace Material: No materials replaced", @@ -123,17 +126,18 @@ def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, operator.report({'INFO'}, message[warn]) if obj_size_big is True: - print("\n** MATERIAL SPECIALS **: \n Full list for the Info message is: \n", - ", ".join(object_name), "\n") + print("\n[Materials Utils Specials]:\nFull list for the Info message is:\n\n", + " ".join(names + "," + "\n" * ((i + 1) % 10 == 0) for i, names in enumerate(object_name)), + "\n") - # restore settings if overriden + # restore settings if overridden if override: addon.preferences.show_warnings = get_warn def collect_report(collection="", is_start=False, is_final=False): # collection passes a string for appending to COLLECT_REPORT global - # is_final swithes to the final report with the operator in __init__ + # is_final switches to the final report with the operator in __init__ global COLLECT_REPORT scene = bpy.context.scene.mat_specials use_report = scene.enable_report @@ -164,20 +168,14 @@ def c_data_has_materials(): return (len(bpy.data.materials) > 0) +def c_obj_data_has_materials(obj): + # check for material presence in object's data + matlen = 0 + if obj: + matlen = len(obj.data.materials) + return (matlen > 0) + + def c_data_has_images(): # check for image presence in data return (len(bpy.data.images) > 0) - - -def register(): - bpy.utils.register_module(__name__) - pass - - -def unregister(): - bpy.utils.unregister_module(__name__) - pass - - -if __name__ == "__main__": - register() |