diff options
Diffstat (limited to 'materials_utils')
-rw-r--r-- | materials_utils/__init__.py | 2741 | ||||
-rw-r--r-- | materials_utils/material_converter.py | 793 | ||||
-rw-r--r-- | materials_utils/materials_cycles_converter.py | 977 | ||||
-rw-r--r-- | materials_utils/texture_rename.py | 127 | ||||
-rw-r--r-- | materials_utils/warning_messages_utils.py | 181 |
5 files changed, 0 insertions, 4819 deletions
diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py deleted file mode 100644 index 70ff4fc9..00000000 --- a/materials_utils/__init__.py +++ /dev/null @@ -1,2741 +0,0 @@ -# ##### 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, 6), - "blender": (2, 79, 0), - "location": "Materials Properties Specials > Shift Q", - "description": "Materials Utils and Convertors", - "warning": "", - "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 - importlib.reload(texture_rename) - importlib.reload(warning_messages_utils) -else: - 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 ( - BoolProperty, - CollectionProperty, - EnumProperty, - IntProperty, - StringProperty, - PointerProperty, -) -from bpy.types import ( - AddonPreferences, - Menu, - Operator, - Panel, - PropertyGroup, - UIList, -) -from .warning_messages_utils import ( - warning_messages, - c_data_has_materials, - c_obj_data_has_materials, -) - -# Globals -UNDO_MESSAGE = "*Only Undo is available*" -COLUMN_SPLIT = 20 - - -# 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 - # referenced more than once - - # Indicate which objects were affected - if update_selection: - ob.select_set(True) - match = True - - if update_selection and not match: - ob.select_set(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 edit mode - 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_set(True) - # the active object may not have the mat! - # set it to one that does! - scn.objects.active = ob - break - else: - ob.select_set(False) - # deselect non-meshes - else: - ob.select_set(False) - else: - # it's edit mode, 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 - # uv layer 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 uv layer - 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: - remove_material_slot() - - # 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_set(True) - - objs = bpy.context.selected_editable_objects - # collect all object names for warning_messages - message_a = [] - # Flags if there are non MESH objects selected - mixed_obj, mixed_obj_slot = False, False - - for ob in objs: - 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_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: - # 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 - - # 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'}): - # 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 - - # the material is not attached to the object - if not found: - actob.data.materials.append(target) - - # is selected ? - selected = bool(actob.select) - # select active object - actob.select_set(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 the active object so we can restore it later - actob = bpy.context.active_object - - # is active object selected ? - selected = bool(actob.select_get) - actob.select_set(True) - - # 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 object mode 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.select_get = 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.view_layer.objects.active = actob - - # restore selection state - actob.select_set = 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): - # edit mode 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 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): - # 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 stack exchange (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 and context related settings" - bl_options = {'REGISTER', 'UNDO'} - - is_not_undo = False # prevent drawing props on undo - - @classmethod - def poll(cls, context): - 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 - 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 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.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(text=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(text="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" - "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 - 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): - 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 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" - bl_label = "New Material Settings" - bl_description = ("Set the Base name of the new Material\n" - "and tweaking after the new Material creation") - bl_options = {'REGISTER'} - - def invoke(self, context, event): - return context.window_manager.invoke_props_dialog(self) - - def draw(self, context): - layout = self.layout - scene = context.scene.mat_context_menu - - box = layout.box() - box.label(text="Base name:") - box.prop(scene, "set_material_name", text="", icon="SYNTAX_ON") - layout.separator() - layout.prop(scene, "use_tweak") - - def execute(self, context): - - return {'FINISHED'} - - -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): - 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 - scene = context.scene.mat_context_menu - 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 - - 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() - 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') - except: - self.report({'INFO'}, "Preview Active Material could not be opened") - - 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 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" - "Can't be used in Edit Mode") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - # materials can't be removed in Edit mode - return (c_data_has_materials() and - context.active_object is not None and - not context.active_object.mode == 'EDIT') - - def execute(self, context): - 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" - "Can't be used in Edit Mode") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - # materials can't be removed in Edit mode - return (c_data_has_materials() and - context.active_object is not None and - not context.active_object.mode == 'EDIT') - - def execute(self, context): - 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" - 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 - def poll(cls, context): - # materials can't be removed in Edit mode - return (c_data_has_materials() and - context.active_object is not None and - not context.active_object.mode == 'EDIT') - - def invoke(self, context, event): - return context.window_manager.invoke_confirm(self, event) - - def execute(self, context): - 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" - 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, - ) - is_not_undo = False - is_edit = False - - @classmethod - def poll(cls, context): - obj = context.active_object - return (c_data_has_materials() and - 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): - if use_mat_menu_type() == 'POPUP': - mats_col = context.scene.mat_specials_mats - scene = context.scene.mat_context_menu - 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) - 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'} - - -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="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, - ) - - @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="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): - 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\n" - "Geometry node 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 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 - 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 exception - 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 exception - 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("\n[Materials Utils Specials]\nLink to base names\nError:" - "Base material %r not found\n" % 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 = "Check if the given path is writable (has OS writing privileges)" - bl_options = {'REGISTER', 'INTERNAL'} - - def check_valid_path(self, context): - sc = context.scene - paths = bpy.path.abspath(sc.mat_context_menu.conv_path) - - if bpy.data.filepath == "": - warning_messages(self, "DIR_PATH_EMPTY", override=True) - return False - - 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): - if not self.check_valid_path(context): - return {'CANCELLED'} - - warning_messages(self, 'DIR_PATH_W_OK', override=True) - - 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(factor=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(factor=0.7, align=True) - sub_box_1 = sub_split.box() - sub_box_1.label(text="Name") - sub_split_2 = sub_split.split(factor=0.5, align=True) - sub_box_2 = sub_split_2.box() - sub_box_2.label(text="Fake") - sub_box_3 = sub_split_2.box() - sub_box_3.label(text="Lib") - - col.template_list( - "VIEW3D_UL_assign_material_popup_ui", - 'mat_context_menu', - context.scene, - 'mat_specials_mats', - context.scene.mat_context_menu, - 'index_mat', - rows=10 - ) - return - - -# 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 (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_context_menu.set_material_name - add_new = layout.operator( - "view3d.assign_material", - text="Add New", icon='ZOOM_IN' - ) - 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" - - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' - 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 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: - layout.label(text="*Works only in Object and Edit mode*", icon="INFO") - - -class VIEW3D_MT_remove_material(Menu): - bl_label = "Clean Slots" - - def draw(self, context): - layout = self.layout - layout.operator_context = 'INVOKE_REGION_WIN' - - if context.mode in {'PAINT_TEXTURE'}: - layout.label( - text="Removing materials can lead to loss of painting data", - icon="INFO" - ) - use_separator(self, context) - - layout.operator( - "view3d.clean_material_slots", - text="Clean Material Slots", - icon='COLOR_BLUE' - ) - use_separator(self, context) - - if c_render_engine("Lux"): - layout.label(text="Sorry, other Menu functions are", icon="INFO") - layout.label(text="unavailable 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): - 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) - - 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") - 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 - - layout.operator("view3d.set_new_material_name", icon="SETTINGS") - - 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) - 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') - 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 - scene = context.scene.mat_context_menu - - layout.prop(scene, "EXTRACT_ALPHA", - text="Extract Alpha Textures (slow)") - use_separator(self, context) - layout.prop(scene, "EXTRACT_PTEX", - text="Extract Procedural Textures (slow)") - use_separator(self, context) - layout.prop(scene, "EXTRACT_OW", text="Re-extract Textures") - use_separator(self, context) - layout.prop(scene, "SET_FAKE_USER", text="Set Fake User on unused images") - use_separator(self, context) - layout.prop(scene, "SCULPT_PAINT", text="Sculpt/Texture paint mode") - use_separator(self, context) - layout.prop(scene, "UV_UNWRAP", text="Set Auto UV Unwrap (Active Object)") - use_separator(self, context) - layout.prop(scene, "enable_report", text="Enable Report in the UI") - use_separator(self, context) - - layout.label(text="Set the Bake Resolution") - res = str(scene.img_bake_size) - layout.label(text="Current Setting is : %s" % (res + "x" + res), icon='INFO') - use_separator(self, context) - layout.prop(scene, "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_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 - col = layout.column(align=True) - box = col.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') - row = box.row() - ml_restore = row.operator("ml.restore", text="To BI Nodes Off", - icon='MATERIAL') - ml_restore.switcher = False - ml_restore.renderer = "BI" - row.menu("scenemassive.opt", text="Advanced Options", icon='SCRIPTWIN') - - box = col.box() - box.label(text="Save Directory") - split = box.split(0.85) - split.prop(sc.mat_context_menu, "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_header(self, context): - layout = self.layout - help_panel_header(layout, menu_type="help.nodeconvert") - - def draw(self, context): - layout = self.layout - col = layout.column(align=True) - box = col.box() - - 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" - ) - - box = col.box() - ml_restore = box.operator("ml.restore", text="To BI Nodes ON", - icon='MATERIAL') - ml_restore.switcher = True - ml_restore.renderer = "BI" - - -# Converters Help # - -class MATERIAL_MT_biconv_help(Menu): - bl_idname = "HELP_MT_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="If possible, avoid multiple conversions in a row") - layout.label(text="Save Your Work Often", icon="ERROR") - use_separator(self, context) - layout.label(text="Try to link them manually using Mix Color nodes") - layout.label(text="Only the last Image in the stack gets linked to Shader") - layout.label(text="Current limitation:", icon="MOD_EXPLODE") - 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 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 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") - 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="If possible, avoid multiple conversions in a row") - 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 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") - 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_OT_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 - layout.label(text="Information:", icon='INFO') - - if self.message and type(self.message) is str: - list_string = self.message.split("*") - for line in range(len(list_string)): - layout.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_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' - ) - 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" - ) - 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" - ) - EXTRACT_PTEX: BoolProperty( - 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 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" - ) - UV_UNWRAP: BoolProperty( - 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" - ) - 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' - ) - 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 - ) - use_tweak: BoolProperty( - name="Tweak Settings", - description="Open Preview Active Material after new Material creation", - default=False - ) - index_mat: IntProperty( - name="index", - options={"HIDDEN"} - ) - - -# 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 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" - ) - 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" - "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=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' - ) - 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 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', - ) - 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 below 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 - - col_m = layout.column(align=True) - - box = col_m.box() - box.label(text="Save Directory") - split = box.split(factor=0.85) - split.prop(sc.mat_context_menu, "conv_path", text="", icon="RENDER_RESULT") - split.operator( - "material.check_converter_path", - text="", icon="EXTERNAL_DATA" - ) - box = col_m.box() - split = box.split(align=True) - - col = split.column(align=True) - col.prop(self, "show_warnings") - col.prop(self, "show_remove_mat") - col.prop(self, "set_cleanmatslots") - col.prop(self, "show_separators") - - col = split.column(align=True) - col.label(text="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(factor=size_split, align=True) - split.prop(self, "show_mat_preview") - - if self.show_mat_preview: - subsplit = split.split(factor=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(factor=size_split, align=True) - split.prop(self, "show_converters") - - if self.show_converters: - 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 = [] - suffix = 1 - try: - if c_data_has_materials(): - name_list = [mat.name for mat in bpy.data.materials if name_id in mat.name] - new_name = "{}_{}".format(name_id, len(name_list) + suffix) - if new_name in name_list: - # KISS failed - numbering is not sequential - # try harvesting numbers in material names, find the rightmost ones - test_num = [] - from re import findall - for words in name_list: - test_num.append(findall("\d+", words)) - - suffix += max([int(l[-1]) for l in test_num]) - new_name = "{}_{}".format(name_id, suffix) - return new_name - except Exception as e: - print("\n[Materials Utils Specials]\nfunction: check_mat_name_unique\nError: %s \n" % e) - pass - return name_id - - -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', 'GPENCIL'] - 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.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_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 - - 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) - -# ----------------------------------------------------- -# Registration -# ------------------------------------------------------ -classes = ( - VIEW3D_OT_show_mat_preview, - VIEW3D_OT_copy_material_to_selected, - VIEW3D_OT_texface_to_material, - VIEW3D_OT_set_new_material_name, - VIEW3D_OT_assign_material, - VIEW3D_OT_clean_material_slots, - VIEW3D_OT_material_to_texface, - VIEW3D_OT_material_remove_slot, - VIEW3D_OT_material_remove_object, - VIEW3D_OT_material_remove_all, - VIEW3D_OT_select_material_by_name, - VIEW3D_OT_replace_material, - VIEW3D_OT_fake_user_set, - MATERIAL_OT_set_transparent_back_side, - MATERIAL_OT_move_slot_top, - MATERIAL_OT_move_slot_bottom, - MATERIAL_OT_link_to_base_names, - MATERIAL_OT_check_converter_path, - VIEW3D_UL_assign_material_popup_ui, - VIEW3D_MT_assign_material, - VIEW3D_MT_select_material, - VIEW3D_MT_remove_material, - VIEW3D_MT_master_material, - VIEW3D_MT_mat_special, - MATERIAL_MT_scenemassive_opt, - MATERIAL_PT_scenemassive, - MATERIAL_PT_xps_convert, - MATERIAL_MT_biconv_help, - MATERIAL_MT_nodeconv_help, - MATERIAL_OT_converter_report, - material_specials_scene_mats, - material_specials_scene_props, - VIEW3D_MT_material_utils_pref -) - -def register(): - for cls in classes: - bpy.utils.register_class(cls) - - warning_messages_utils.MAT_SPEC_NAME = __name__ - - # Register Scene Properties - bpy.types.Scene.mat_context_menu= PointerProperty( - 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: - 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_context_menu.prepend(menu_move) - bpy.types.MATERIAL_MT_context_menu.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_context_menu.remove(menu_move) - bpy.types.MATERIAL_MT_context_menu.remove(menu_func) - - del bpy.types.Scene.mat_context_menu - del bpy.types.Scene.mat_specials_mats - - for cls in classes: - bpy.utils.unregister_class(cls) - - -if __name__ == "__main__": - register() diff --git a/materials_utils/material_converter.py b/materials_utils/material_converter.py deleted file mode 100644 index 3a208e80..00000000 --- a/materials_utils/material_converter.py +++ /dev/null @@ -1,793 +0,0 @@ -# -*- coding: utf-8 -*- - -import bpy -import math -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, -) - -# ----------------------------------------------------------------------------- -# Globals - -nodesDictionary = None - -NODE_FRAME = 'NodeFrame' -BI_MATERIAL_NODE = 'ShaderNodeMaterial' -BI_OUTPUT_NODE = 'ShaderNodeOutput' -TEXTURE_IMAGE_NODE = 'ShaderNodeTexImage' -OUTPUT_NODE = 'ShaderNodeOutputMaterial' -RGB_MIX_NODE = 'ShaderNodeMixRGB' -MAPPING_NODE = 'ShaderNodeMapping' -NORMAL_MAP_NODE = 'ShaderNodeNormalMap' -SHADER_MIX_NODE = 'ShaderNodeMixShader' -SHADER_ADD_NODE = 'ShaderNodeAddShader' -COORD_NODE = 'ShaderNodeTexCoord' -RGB_TO_BW_NODE = 'ShaderNodeRGBToBW' -BSDF_DIFFUSE_NODE = 'ShaderNodeBsdfDiffuse' -BSDF_EMISSION_NODE = 'ShaderNodeEmission' -BSDF_TRANSPARENT_NODE = 'ShaderNodeBsdfTransparent' -BSDF_GLOSSY_NODE = 'ShaderNodeBsdfGlossy' -BSDF_GLASS_NODE = 'ShaderNodeBsdfGlass' - -textureNodeSizeX = 150 -textureNodeSizeY = 350 - - -# ----------------------------------------------------------------------------- -# Functions - -def makeTextureNodeDict(cmat): - global nodesDictionary - nodesDictionary = {} - textures = {textureSlot.texture for textureSlot in cmat.texture_slots if textureSlot} - - for tex in textures: - texNode = None - if tex.type == 'IMAGE': - texNode = makeNodeUsingImage1(cmat, tex) - if texNode: - nodesDictionary[tex] = texNode - return nodesDictionary - - -def getTexNodeDic(texture): - return nodesDictionary.get(texture) - - -def clearNodes(TreeNodes): - TreeNodes.nodes.clear() - - -def clearCycleMaterial(cmat): - TreeNodes = cmat.node_tree - clearNodes(TreeNodes) - - -def copyMapping(textureSlot, textureMapping): - textureMapping.scale.x = textureSlot.scale.x - textureMapping.scale.y = textureSlot.scale.y - textureMapping.scale.z = textureSlot.scale.z - - -def addRGBMixNode(TreeNodes, textureSlot, mixRgbNode, prevTexNode, newTexNode, nodeType, textureIdx): - try: - links = TreeNodes.links - mixRgbNode.name = '{} Mix {:d}'.format(nodeType, textureIdx) - mixRgbNode.blend_type = textureSlot.blend_type - mixRgbNode.inputs['Fac'].default_value = textureSlot.diffuse_color_factor - links.new(prevTexNode.outputs['Color'], mixRgbNode.inputs['Color2']) - links.new(newTexNode.outputs['Color'], mixRgbNode.inputs['Color1']) - except: - collect_report("ERROR: Failure to find link with a Mix node") - - -def makeBiNodes(cmat): - # Create Blender Internal Material Nodes - TreeNodes = cmat.node_tree - links = TreeNodes.links - - BIFrame = TreeNodes.nodes.new(NODE_FRAME) - BIFrame.name = 'BI Frame' - BIFrame.label = 'BI Material' - - biShaderNodeMaterial = TreeNodes.nodes.new(BI_MATERIAL_NODE) - biShaderNodeMaterial.parent = BIFrame - biShaderNodeMaterial.name = 'BI Material' - biShaderNodeMaterial.material = cmat - biShaderNodeMaterial.location = 0, 600 - - biShaderNodeOutput = TreeNodes.nodes.new(BI_OUTPUT_NODE) - biShaderNodeOutput.parent = BIFrame - biShaderNodeOutput.name = 'BI Output' - biShaderNodeOutput.location = 200, 600 - try: - links.new(biShaderNodeMaterial.outputs['Color'], biShaderNodeOutput.inputs['Color']) - links.new(biShaderNodeMaterial.outputs['Alpha'], biShaderNodeOutput.inputs['Alpha']) - except: - collect_report("ERROR: Failure to find links with the BI Shader Material") - - -def placeNode(node, posX, posY, deltaX, deltaY, countX, countY): - nodeX = posX - (deltaX * countX) - nodeY = posY - (deltaY * countY) - node.location = nodeX, nodeY - - -def makeImageTextureNode(TreeNodes, img): - texNode = TreeNodes.nodes.new(TEXTURE_IMAGE_NODE) - texNode.image = img - return texNode - - -def makeNodeUsingImage1(cmat, texture): - TreeNodes = cmat.node_tree - img = texture.image - texNode = makeImageTextureNode(TreeNodes, img) - return texNode - - -def makeMainShader(TreeNodes): - mainShader = TreeNodes.nodes.new(BSDF_DIFFUSE_NODE) - mainShader.name = 'Diffuse BSDF' - mainShader.location = 0, 0 - return mainShader - - -def makeEmissionShader(TreeNodes): - mainShader = TreeNodes.nodes.new(BSDF_EMISSION_NODE) - mainShader.name = 'Emmission' - mainShader.location = 0, 0 - return mainShader - - -def makeMaterialOutput(TreeNodes): - shout = TreeNodes.nodes.new(OUTPUT_NODE) - shout.location = 200, 0 - return shout - - -def replaceNode(oldNode, newNode): - newNode.location = oldNode.location - try: - for link in oldNode.outputs['BSDF'].links: - link.new(newNode.outputs['BSDF'], link.to_socket) - for link in oldNode.inputs['Color'].links: - link.new(newNode.inputs['Color'], link.from_socket) - for link in oldNode.inputs['Normal'].links: - link.new(newNode.inputs['Normal'], link.from_socket) - except: - collect_report("ERROR: Failure to replace node") - - -def BIToCycleTexCoord(links, textureSlot, texCoordNode, textureMappingNode): - # Texture Coordinates - linkOutput = None - if textureSlot.texture_coords in {'TANGENT', 'STRESS', 'STRAND'}: - linkOutput = None - elif textureSlot.texture_coords == 'REFLECTION': - linkOutput = 'Reflection' - elif textureSlot.texture_coords == 'NORMAL': - linkOutput = 'Normal' - elif textureSlot.texture_coords == 'WINDOW': - linkOutput = 'Window' - elif textureSlot.texture_coords == 'UV': - linkOutput = 'UV' - elif textureSlot.texture_coords == 'ORCO': - linkOutput = 'Generated' - elif textureSlot.texture_coords == 'OBJECT': - linkOutput = 'Object' - elif textureSlot.texture_coords == 'GLOBAL': - linkOutput = 'Camera' - - if linkOutput: - links.new(texCoordNode.outputs[linkOutput], textureMappingNode.inputs['Vector']) - - -def createDiffuseNodes(cmat, texCoordNode, mainShader, materialOutput): - TreeNodes = cmat.node_tree - links = TreeNodes.links - texCount = len([node for node in TreeNodes.nodes if node.type == 'MAPPING']) - currPosY = -textureNodeSizeY * texCount - - textureSlots = [textureSlot for textureSlot in cmat.texture_slots if - (textureSlot and textureSlot.use_map_color_diffuse)] - - texCount = len(textureSlots) - texNode = None - latestNode = None - groupName = 'Diffuse' - - if any(textureSlots): - diffuseFrame = TreeNodes.nodes.new(NODE_FRAME) - diffuseFrame.name = '{} Frame'.format(groupName) - diffuseFrame.label = '{}'.format(groupName) - - for textureIdx, textureSlot in enumerate(textureSlots): - texNode = getTexNodeDic(textureSlot.texture) - if texNode: - tex_node_name = getattr(texNode.image, "name", "") - collect_report("INFO: Generating {} Nodes for: ".format(groupName) + tex_node_name) - texNode.parent = diffuseFrame - placeNode(texNode, -500 - ((texCount - 1) * 200), - currPosY, textureNodeSizeX, textureNodeSizeY, 0, textureIdx) - - # Add mapping node - textureMapping = TreeNodes.nodes.new(MAPPING_NODE) - textureMapping.parent = diffuseFrame - renameNode(textureMapping, '{} Mapping'.format(groupName), texCount, textureIdx) - textureMapping.location = texNode.location + Vector((-400, 0)) - copyMapping(textureSlot, textureMapping) - - # Texture Coordinates - BIToCycleTexCoord(links, textureSlot, texCoordNode, textureMapping) - - # Place the texture node - renameNode(texNode, '{} Texture'.format(groupName), texCount, textureIdx) - links.new(textureMapping.outputs['Vector'], texNode.inputs['Vector']) - - # Add multiply node - colorMult = TreeNodes.nodes.new(RGB_MIX_NODE) - colorMult.parent = diffuseFrame - renameNode(colorMult, 'Color Mult', texCount, textureIdx) - colorMult.blend_type = 'MIX' - colorMult.inputs['Fac'].default_value = 1 - colorMult.inputs['Color1'].default_value = (1, 1, 1, 1) - - colorMult.location = texNode.location + Vector((200, 0)) - links.new(texNode.outputs['Color'], colorMult.inputs['Color2']) - - texNode = colorMult - if textureSlot.use and textureIdx == 0: - latestNode = texNode - - if textureSlot.use and textureIdx > 0: - try: - # Create a node to mix multiple texture nodes - mixRgbNode = TreeNodes.nodes.new(RGB_MIX_NODE) - mixRgbNode.parent = diffuseFrame - addRGBMixNode(TreeNodes, textureSlot, mixRgbNode, texNode, latestNode, - '{}'.format(groupName), textureIdx) - mixRgbNode.location = Vector( - (max(texNode.location.x, latestNode.location.x), - (texNode.location.y + latestNode.location.y) / 2)) + Vector((200, 0) - ) - latestNode = mixRgbNode - except: - continue - - if latestNode: - links.new(latestNode.outputs['Color'], mainShader.inputs['Color']) - - # Y Position next texture node - currPosY = currPosY - (textureNodeSizeY * (texCount)) - - # BI Material to Cycles - Alpha Transparency - textureSlots = [textureSlot for textureSlot in cmat.texture_slots if - (textureSlot and textureSlot.use_map_alpha)] - texCount = len(textureSlots) - texNode = None - latestNode = None - for textureIdx, textureSlot in enumerate(textureSlots): - texNode = getTexNodeDic(textureSlot.texture) - if texNode: - tex_node_name = getattr(texNode.image, "name", "") - collect_report("INFO: Generating Transparency Nodes for: " + tex_node_name) - if textureSlot.use and textureIdx == 0: - latestNode = texNode - - if textureSlot.use and textureIdx > 0: - try: - # Create a node to mix multiple texture nodes - mixAlphaNode = TreeNodes.nodes.new(RGB_MIX_NODE) - mixAlphaNode.name = 'Alpha Mix {:d}'.format(textureIdx) - mixAlphaNode.blend_type = textureSlot.blend_type - mixAlphaNode.inputs['Fac'].default_value = textureSlot.diffuse_color_factor - placeNode(mixAlphaNode, -200 - ((texCount - textureIdx - 1) * 200), 400 - 240, - textureNodeSizeX, textureNodeSizeY, 0, 0) - links.new(texNode.outputs['Alpha'], mixAlphaNode.inputs['Color2']) - links.new(latestNode.outputs['Alpha'], mixAlphaNode.inputs['Color1']) - latestNode = mixAlphaNode - except: - continue - if latestNode: - alphaMixShader = TreeNodes.nodes.get('Alpha Mix Shader') - if alphaMixShader: - if latestNode.type == 'TEX_IMAGE': - outputLink = 'Alpha' - else: - outputLink = 'Color' - links.new(latestNode.outputs[outputLink], alphaMixShader.inputs['Fac']) - - -def createNormalNodes(cmat, texCoordNode, mainShader, materialOutput): - TreeNodes = cmat.node_tree - links = TreeNodes.links - texCount = len([node for node in TreeNodes.nodes if node.type == 'MAPPING']) - currPosY = -textureNodeSizeY * texCount - - textureSlots = [textureSlot for textureSlot in cmat.texture_slots if - (textureSlot and textureSlot.use_map_normal)] - texCount = len(textureSlots) - texNode = None - latestNode = None - groupName = 'Normal' - if any(textureSlots): - normalFrame = TreeNodes.nodes.new(NODE_FRAME) - normalFrame.name = '{} Frame'.format(groupName) - normalFrame.label = '{}'.format(groupName) - - for textureIdx, textureSlot in enumerate(textureSlots): - texNode = getTexNodeDic(textureSlot.texture) - if texNode: - tex_node_name = getattr(texNode.image, "name", "") - collect_report("INFO: Generating Normal Nodes for: " + tex_node_name) - texNode.parent = normalFrame - placeNode(texNode, -500 - ((texCount) * 200), currPosY, - textureNodeSizeX, textureNodeSizeY, 0, textureIdx) - - # Add mapping node - normalMapping = TreeNodes.nodes.new(MAPPING_NODE) - normalMapping.parent = normalFrame - renameNode(normalMapping, '{} Mapping'.format(groupName), texCount, textureIdx) - normalMapping.location = texNode.location + Vector((-400, 0)) - copyMapping(textureSlot, normalMapping) - - # Texture Coordinates - BIToCycleTexCoord(links, textureSlot, texCoordNode, normalMapping) - - # Place the texture node - renameNode(texNode, '{} Texture'.format(groupName), texCount, textureIdx) - if texNode.image.image: - texNode.image.colorspace_settings.is_data = True - links.new(normalMapping.outputs['Vector'], texNode.inputs['Vector']) - - # Add multiply node - normalMult = TreeNodes.nodes.new(RGB_MIX_NODE) - normalMult.parent = normalFrame - renameNode(normalMult, 'Normal Mult', texCount, textureIdx) - normalMult.blend_type = 'MIX' - normalMult.inputs['Fac'].default_value = 1 - normalMult.inputs['Color1'].default_value = (.5, .5, 1, 1) - - normalMult.location = texNode.location + Vector((200, 0)) - links.new(texNode.outputs['Color'], normalMult.inputs['Color2']) - - texNode = normalMult - if textureSlot.use and textureIdx == 0: - latestNode = texNode - - if textureSlot.use and textureIdx > 0: - try: - # Create a node to mix multiple texture nodes - mixRgbNode = TreeNodes.nodes.new(RGB_MIX_NODE) - mixRgbNode.parent = normalFrame - addRGBMixNode(TreeNodes, textureSlot, mixRgbNode, texNode, latestNode, - '{}'.format(groupName), textureIdx) - mixRgbNode.location = Vector( - (max(texNode.location.x, latestNode.location.x), - (texNode.location.y + latestNode.location.y) / 2)) + Vector((200, 0) - ) - latestNode = mixRgbNode - except: - continue - - if latestNode: - normalMapNode = TreeNodes.nodes.new(NORMAL_MAP_NODE) - normalMapNode.parent = normalFrame - normalMapNode.location = latestNode.location + Vector((200, 0)) - links.new(latestNode.outputs['Color'], normalMapNode.inputs['Color']) - links.new(normalMapNode.outputs['Normal'], mainShader.inputs['Normal']) - - -def createSpecularNodes(cmat, texCoordNode, mainShader, mainDiffuse, materialOutput): - TreeNodes = cmat.node_tree - links = TreeNodes.links - texCount = len([node for node in TreeNodes.nodes if node.type == 'MAPPING']) - currPosY = -textureNodeSizeY * texCount - - textureSlots = [textureSlot for textureSlot in cmat.texture_slots if - (textureSlot and textureSlot.use_map_color_spec)] - texCount = len(textureSlots) - texNode = None - latestNode = None - groupName = 'Specular' - if any(textureSlots): - specularFrame = TreeNodes.nodes.new(NODE_FRAME) - specularFrame.name = '{} Frame'.format(groupName) - specularFrame.label = '{}'.format(groupName) - - for textureIdx, textureSlot in enumerate(textureSlots): - texNode = getTexNodeDic(textureSlot.texture) - if texNode: - tex_node_name = getattr(texNode.image, "name", "") - collect_report("INFO: Generating {} Nodes for: ".format(groupName) + tex_node_name) - texNode.parent = specularFrame - placeNode(texNode, -500 - ((texCount) * 200), - currPosY, textureNodeSizeX, textureNodeSizeY, 0, textureIdx) - - # Add mapping node - specularMapping = TreeNodes.nodes.new(MAPPING_NODE) - specularMapping.parent = specularFrame - renameNode(specularMapping, '{} Mapping'.format(groupName), texCount, textureIdx) - specularMapping.location = texNode.location + Vector((-400, 0)) - copyMapping(textureSlot, specularMapping) - - # Texture Coordinates - BIToCycleTexCoord(links, textureSlot, texCoordNode, specularMapping) - - # Place the texture node - renameNode(texNode, '{} Texture'.format(groupName), texCount, textureIdx) - links.new(specularMapping.outputs['Vector'], texNode.inputs['Vector']) - - # Add multiply node - specularMult = TreeNodes.nodes.new(RGB_MIX_NODE) - specularMult.parent = specularFrame - renameNode(specularMult, 'Specular Mult', texCount, textureIdx) - specularMult.blend_type = 'MULTIPLY' - specularMult.inputs['Fac'].default_value = 1 - specularMult.inputs['Color1'].default_value = (1, 1, 1, 1) - - specularMult.location = texNode.location + Vector((200, 0)) - links.new(texNode.outputs['Color'], specularMult.inputs['Color2']) - - texNode = specularMult - if textureSlot.use and textureIdx == 0: - latestNode = texNode - - if textureSlot.use and textureIdx > 0: - try: - # Create a node to mix multiple texture nodes - mixRgbNode = TreeNodes.nodes.new(RGB_MIX_NODE) - mixRgbNode.parent = specularFrame - addRGBMixNode(TreeNodes, textureSlot, mixRgbNode, texNode, latestNode, - '{}'.format(groupName), textureIdx) - mixRgbNode.location = Vector( - (max(texNode.location.x, latestNode.location.x), - (texNode.location.y + latestNode.location.y) / 2)) + Vector((200, 0) - ) - latestNode = mixRgbNode - except: - continue - - if latestNode: - try: - glossShader = TreeNodes.nodes.new(BSDF_GLOSSY_NODE) - RGBToBW = TreeNodes.nodes.new(RGB_TO_BW_NODE) - RGBToBW.location = Vector((0, latestNode.location.y)) + Vector((0, 0)) - glossShader.location = Vector((0, latestNode.location.y)) + Vector((0, -80)) - - links.new(latestNode.outputs['Color'], glossShader.inputs['Color']) - links.new(latestNode.outputs['Color'], RGBToBW.inputs['Color']) - - outputNode = TreeNodes.nodes.get('Material Output') - spec_mixer_1 = TreeNodes.nodes.new(SHADER_MIX_NODE) - spec_mixer_1.location = outputNode.location - spec_mixer_2 = TreeNodes.nodes.new(SHADER_MIX_NODE) - spec_mixer_2.inputs['Fac'].default_value = .4 - spec_mixer_2.location = outputNode.location + Vector((180, 0)) - links.new(spec_mixer_1.outputs['Shader'], spec_mixer_2.inputs[2]) - links.new(spec_mixer_2.outputs['Shader'], outputNode.inputs['Surface']) - links.new(RGBToBW.outputs['Val'], spec_mixer_1.inputs['Fac']) - - links.new(glossShader.outputs['BSDF'], spec_mixer_1.inputs[2]) - - outputNode.location += Vector((360, 0)) - normalMapNode = TreeNodes.nodes.get('Normal Map') - links.new(normalMapNode.outputs['Normal'], glossShader.inputs['Normal']) - - if mainDiffuse.type == 'BSDF_DIFFUSE': - outputLink = 'BSDF' - else: - outputLink = 'Shader' - - links.new(mainDiffuse.outputs[outputLink], spec_mixer_1.inputs[1]) - links.new(mainDiffuse.outputs[outputLink], spec_mixer_2.inputs[1]) - except: - return - - -def createEmissionNodes(cmat, texCoordNode, mainShader, materialOutput): - TreeNodes = cmat.node_tree - links = TreeNodes.links - texCount = len([node for node in TreeNodes.nodes if node.type == 'MAPPING']) - currPosY = -textureNodeSizeY * texCount - - textureSlots = [textureSlot for textureSlot in cmat.texture_slots if - (textureSlot and textureSlot.use_map_emit)] - texCount = len(textureSlots) - texNode = None - latestNode = None - groupName = 'Emission' - if any(textureSlots): - emissionFrame = TreeNodes.nodes.new(NODE_FRAME) - emissionFrame.name = '{} Frame'.format(groupName) - emissionFrame.label = '{}'.format(groupName) - - for textureIdx, textureSlot in enumerate(textureSlots): - texNode = getTexNodeDic(textureSlot.texture) - if texNode: - tex_node_name = getattr(texNode.image, "name", "") - collect_report("INFO: Generating {} Nodes for: ".format(groupName) + tex_node_name) - texNode.parent = emissionFrame - placeNode(texNode, -500 - ((texCount) * 200), currPosY, - textureNodeSizeX, textureNodeSizeY, 0, textureIdx) - - # Add mapping node - emissionMapping = TreeNodes.nodes.new(MAPPING_NODE) - emissionMapping.parent = emissionFrame - renameNode(emissionMapping, '{} Mapping'.format(groupName), texCount, textureIdx) - emissionMapping.location = texNode.location + Vector((-400, 0)) - copyMapping(textureSlot, emissionMapping) - - # Texture Coordinates - BIToCycleTexCoord(links, textureSlot, texCoordNode, emissionMapping) - - # Place the texture node - renameNode(texNode, '{} Texture'.format(groupName), texCount, textureIdx) - if texNode.image.image: - texNode.image.colorspace_settings.is_data = True - links.new(emissionMapping.outputs['Vector'], texNode.inputs['Vector']) - - # Add multiply node - emissionMult = TreeNodes.nodes.new(RGB_MIX_NODE) - emissionMult.parent = emissionFrame - renameNode(emissionMult, 'Emission Mult', texCount, textureIdx) - emissionMult.blend_type = 'MIX' - emissionMult.inputs['Fac'].default_value = 1 - emissionMult.inputs['Color1'].default_value = (0, 0, 0, 1) - - emissionMult.location = texNode.location + Vector((200, 0)) - links.new(texNode.outputs['Color'], emissionMult.inputs['Color2']) - - texNode = emissionMult - if textureSlot.use and textureIdx == 0: - latestNode = texNode - - if textureSlot.use and textureIdx > 0: - try: - # Create a node to mix multiple texture nodes - mixRgbNode = TreeNodes.nodes.new(RGB_MIX_NODE) - mixRgbNode.parent = emissionFrame - addRGBMixNode(TreeNodes, textureSlot, mixRgbNode, texNode, latestNode, - '{}'.format(groupName), textureIdx) - mixRgbNode.location = Vector( - (max(texNode.location.x, latestNode.location.x), - (texNode.location.y + latestNode.location.y) / 2)) + Vector((200, 0) - ) - latestNode = mixRgbNode - except: - continue - - if latestNode: - try: - emissionNode = TreeNodes.nodes.new(BSDF_EMISSION_NODE) - emissionNode.inputs['Strength'].default_value = 1 - addShaderNode = TreeNodes.nodes.new(SHADER_ADD_NODE) - addShaderNode.location = materialOutput.location + Vector((0, -100)) - xPos = mainShader.location.x - yPos = latestNode.location.y - - emissionNode.location = Vector((xPos, yPos)) - materialOutput.location += Vector((400, 0)) - - node = materialOutput.inputs[0].links[0].from_node - node.location += Vector((400, 0)) - - links.new(latestNode.outputs['Color'], emissionNode.inputs['Color']) - links.new(emissionNode.outputs['Emission'], addShaderNode.inputs[1]) - links.new(mainShader.outputs['BSDF'], addShaderNode.inputs[0]) - links.new(addShaderNode.outputs['Shader'], node.inputs[2]) - except: - return - - -def renameNode(node, baseName, nodesCount, nodeIndex): - if nodesCount == 1: - node.name = baseName - else: - node.name = '{} {:d}'.format(baseName, nodeIndex + 1) - - -def hasAlphaTex(cmat): - tex_is_transp = False - for textureSlot in cmat.texture_slots: - if textureSlot: - if textureSlot.use: - if textureSlot.use_map_alpha: - tex_is_transp = tex_is_transp or True - return tex_is_transp - - -def AutoNode(active=False, operator=None): - collect_report("________________________________________", True, False) - collect_report("START CYCLES CONVERSION") - - if active: - materials = [mat for obj in bpy.context.selected_objects if - obj.type == 'MESH' for mat in obj.data.materials] - else: - materials = bpy.data.materials - - # No Materials for the chosen action - abort - if not materials: - if operator: - if active: - warning_messages(operator, 'CONV_NO_SEL_MAT', override=True) - else: - warning_messages(operator, 'CONV_NO_SC_MAT', override=True) - return - - for cmat in materials: - # check for empty material (it will fall through the first check) - test_empty = getattr(cmat, "name", None) - if test_empty is None: - collect_report("INFO: An empty material was hit, skipping") - continue - else: - cmat.use_nodes = True - clearCycleMaterial(cmat) - makeBiNodes(cmat) - makeCyclesFromBI(cmat) - - collect_report("Conversion finished !", False, True) - - bpy.context.scene.render.engine = 'CYCLES' - - -def makeCyclesFromBI(cmat): - mat_name = getattr(cmat, "name", "NO NAME") - collect_report("Converting Material: " + mat_name) - - global nodesDictionary - TreeNodes = cmat.node_tree - links = TreeNodes.links - - # Convert this material from non-nodes to Cycles nodes - mainShader = None - mainDiffuse = None - Mix_Alpha = None - - tex_is_transp = hasAlphaTex(cmat) - - cmat_use_transp = cmat.use_transparency and cmat.alpha < 1 - cmat_trans_method = cmat.transparency_method - cmat_ior = cmat.raytrace_transparency.ior - cmat_transp_z = cmat_use_transp and cmat_trans_method == 'Z_TRANSPARENCY' - cmat_transp_ray = cmat_use_transp and cmat_trans_method == 'RAYTRACE' and cmat_ior == 1 - cmat_mirror = cmat.raytrace_mirror.use - cmat_mirror_fac = cmat.raytrace_mirror.reflect_factor - - # Material Shaders - # Diffuse nodes - # -------------------------------------- - - # Make Diffuse and Output nodes - mainShader = makeMainShader(TreeNodes) - mainShader.inputs['Roughness'].default_value = math.sqrt(max(cmat.specular_intensity, 0.0)) - mainDiffuse = mainShader - materialOutput = makeMaterialOutput(TreeNodes) - links.new(mainShader.outputs['BSDF'], materialOutput.inputs['Surface']) - - texCoordNode = TreeNodes.nodes.new(COORD_NODE) - texCoordNode.name = 'Texture Coordinate' - - # Material Transparent - if not cmat_mirror and cmat_use_transp and tex_is_transp and (cmat_transp_z or cmat_transp_ray): - collect_report("INFO: Make TRANSPARENT material nodes: " + cmat.name) - Mix_Alpha = TreeNodes.nodes.new(SHADER_MIX_NODE) - Mix_Alpha.name = 'Alpha Mix Shader' - Mix_Alpha.location = materialOutput.location - materialOutput.location += Vector((180, 0)) - Mix_Alpha.inputs['Fac'].default_value = cmat.alpha - transparentShader = TreeNodes.nodes.new(BSDF_TRANSPARENT_NODE) - transparentShader.location = mainShader.location - mainShader.location += Vector((0, -100)) - links.new(transparentShader.outputs['BSDF'], Mix_Alpha.inputs[1]) - links.new(mainShader.outputs['BSDF'], Mix_Alpha.inputs[2]) - links.new(Mix_Alpha.outputs['Shader'], materialOutput.inputs['Surface']) - mainDiffuse = Mix_Alpha - - if cmat_mirror and cmat_mirror_fac > 0.001: - if cmat_use_transp: - # Material Glass - collect_report("INFO: Make GLASS shader node: " + cmat.name) - newShader = TreeNodes.nodes.new(BSDF_GLASS_NODE) - shader = newShader - replaceNode(shader, newShader) - TreeNodes.nodes.remove(shader) - else: - # Material Mirror - collect_report("INFO: Make MIRROR shader node: " + cmat.name) - newShader = TreeNodes.nodes.new(BSDF_GLOSSY_NODE) - shader = newShader - replaceNode(shader, newShader) - TreeNodes.nodes.remove(shader) - - nodesDictionary = makeTextureNodeDict(cmat) - - # -------------------------------------- - # Texture nodes - - # BI Material to Cycles - Diffuse Textures - createDiffuseNodes(cmat, texCoordNode, mainShader, materialOutput) - - # BI Material to Cycles - Normal map - createNormalNodes(cmat, texCoordNode, mainShader, materialOutput) - - # BI Material to Cycles - Specular map - createSpecularNodes(cmat, texCoordNode, mainShader, mainDiffuse, materialOutput) - - # BI Material to Cycles - Emission map - createEmissionNodes(cmat, texCoordNode, mainShader, materialOutput) - - # Texture coordinates - # list all nodes connected to outputs - mappingNodes = [link.to_node for output in texCoordNode.outputs for link in output.links] - mappingNodesCount = len(mappingNodes) - - if mappingNodes: - xList = [node.location.x for node in mappingNodes] - yList = [node.location.y for node in mappingNodes] - minPosX = min(xList) - 400 - avgPosY = sum(yList) / mappingNodesCount - texCoordNode.location = Vector((minPosX, avgPosY)) - - -# ----------------------------------------------------------------------------- -# Operator Classes - -class material_convert_all(Operator): - bl_idname = "xps_tools.convert_to_cycles_all" - bl_label = "Convert All Materials" - bl_description = ("Convert All Materials to BI and Cycles Nodes\n" - "Needs saving the .blend file first") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return (bpy.data.filepath != "" and c_is_cycles_addon_enabled() and - c_data_has_materials()) - - def execute(self, context): - AutoNode(False, self) - - return {'FINISHED'} - - -class material_convert_selected(Operator): - bl_idname = "xps_tools.convert_to_cycles_selected" - bl_label = "Convert All Materials From Selected Objects" - bl_description = ("Convert All Materials on Selected Objects to BI and Cycles Nodes\n" - "Needs saving the .blend file first") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - 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)) - ) - - def execute(self, context): - AutoNode(True, self) - - return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - pass - - -def unregister(): - bpy.utils.unregister_module(__name__) - pass - - -if __name__ == "__main__": - register() diff --git a/materials_utils/materials_cycles_converter.py b/materials_utils/materials_cycles_converter.py deleted file mode 100644 index 573e4309..00000000 --- a/materials_utils/materials_cycles_converter.py +++ /dev/null @@ -1,977 +0,0 @@ -# gpl: author Silvio Falcinelli. Fixes by angavrilov and others. -# special thanks to user blenderartists.org cmomoney -# -*- coding: utf-8 -*- - -import bpy -from os import path as os_path -from bpy.types import Operator -from math import ( - log2, ceil, sqrt, -) -from bpy.props import ( - BoolProperty, - EnumProperty, -) -from .warning_messages_utils import ( - warning_messages, - c_is_cycles_addon_enabled, - c_data_has_materials, - collect_report, -) - -# ----------------------------------------------------------------------------- -# Globals - -# switch for operator's function called after AutoNodeInitiate -CHECK_AUTONODE = False - -# set the node color for baked images (default greenish) -NODE_COLOR = (0.32, 0.75, 0.32) -# set the node color for the paint base images (default reddish) -NODE_COLOR_PAINT = (0.6, 0.0, 0.0) -# set the mix node color (default blueish) -NODE_COLOR_MIX = (0.1, 0.7, 0.8) - -# color for sculpt/texture painting setting (default clay the last entry is Roughness) -PAINT_SC_COLOR = (0.80, 0.75, 0.54, 0.9) -CLAY_GLOSSY = (0.38, 0.032, 0.023, 1) - -# ----------------------------------------------------------------------------- -# Functions - - -def AutoNodeSwitch(renderer="CYCLES", switch="OFF", operator=None): - mats = bpy.data.materials - use_nodes = (True if switch in ("ON") else False) - warn_message = ('BI_SW_NODES_ON' if switch in ("ON") else - 'BI_SW_NODES_OFF') - warn_message_2 = ('CYC_SW_NODES_ON' if switch in ("ON") else - 'CYC_SW_NODES_OFF') - for cmat in mats: - cmat.use_nodes = use_nodes - renders = ('CYCLES' if renderer and renderer == "CYCLES" else - 'BLENDER_RENDER') - bpy.context.scene.render.engine = renders - if operator: - warning_messages(operator, (warn_message_2 if renders in ('CYCLES') else - warn_message)) - - -def SetFakeUserTex(): - images = bpy.data.images - for image in images: - has_user = getattr(image, "users", -1) - image_name = getattr(image, "name", "NONAME") - - if has_user == 0: - image.use_fake_user = True - collect_report("INFO: Set fake user for unused image: " + image_name) - - -def BakingText(tex, mode, tex_type=None): - collect_report("INFO: start bake texture named: " + tex.name) - saved_img_path = None - - bpy.ops.object.mode_set(mode='OBJECT') - sc = bpy.context.scene - tmat = '' - img = '' - Robj = bpy.context.active_object - for n in bpy.data.materials: - if n.name == 'TMP_BAKING': - tmat = n - if not tmat: - tmat = bpy.data.materials.new('TMP_BAKING') - tmat.name = "TMP_BAKING" - - bpy.ops.mesh.primitive_plane_add() - tm = bpy.context.active_object - tm.name = "TMP_BAKING" - tm.data.name = "TMP_BAKING" - bpy.ops.object.select_pattern(extend=False, pattern="TMP_BAKING", - case_sensitive=False) - sc.objects.active = tm - bpy.context.scene.render.engine = 'BLENDER_RENDER' - tm.data.materials.append(tmat) - if len(tmat.texture_slots.items()) == 0: - tmat.texture_slots.add() - tmat.texture_slots[0].texture_coords = 'UV' - tmat.texture_slots[0].use_map_alpha = True - tmat.texture_slots[0].texture = tex.texture - tmat.texture_slots[0].use_map_alpha = True - tmat.texture_slots[0].use_map_color_diffuse = False - tmat.use_transparency = True - tmat.alpha = 0 - tmat.use_nodes = False - tmat.diffuse_color = 1, 1, 1 - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.uv.unwrap() - - # clean up temporary baking images if any - for n in bpy.data.images: - if n.name == 'TMP_BAKING': - n.user_clear() - bpy.data.images.remove(n) - - if mode == "ALPHA" and tex.texture.type == 'IMAGE': - sizeX = tex.texture.image.size[0] - sizeY = tex.texture.image.size[1] - else: - bake_size = (int(sc.mat_context_menu.img_bake_size) if - sc.mat_context_menu.img_bake_size else 1024) - sizeX = bake_size - sizeY = bake_size - - bpy.ops.image.new(name="TMP_BAKING", width=sizeX, height=sizeY, - color=(0.0, 0.0, 0.0, 1.0), alpha=True, float=False) - - sc.render.engine = 'BLENDER_RENDER' - img = bpy.data.images["TMP_BAKING"] - img = bpy.data.images.get("TMP_BAKING") - img.file_format = ("JPEG" if not mode == "ALPHA" else "PNG") - - # switch temporarily to 'IMAGE EDITOR', other approaches are not reliable - check_area = False - store_area = bpy.context.area.type - collect_report("INFO: Temporarily switching context to Image Editor") - try: - bpy.context.area.type = 'IMAGE_EDITOR' - bpy.context.area.spaces[0].image = bpy.data.images["TMP_BAKING"] - check_area = True - except: - collect_report("ERROR: Setting to Image Editor failed, Baking aborted") - check_area = False - - if check_area: - paths = bpy.path.abspath(sc.mat_context_menu.conv_path) - tex_name = getattr(getattr(tex.texture, "image", None), "name", None) - texture_name = (tex_name.rpartition(".")[0] if tex_name else tex.texture.name) - new_tex_name = "baked" - name_append = ("_BAKING" if mode == "ALPHA" and - tex.texture.type == 'IMAGE' else "_PTEXT") - new_appendix = (".jpg" if not mode == "ALPHA" else ".png") - - if name_append in texture_name: - new_tex_name = texture_name - elif tex_type: - new_tex_name = tex_type + name_append - else: - new_tex_name = texture_name + name_append - - img.filepath_raw = paths + new_tex_name + new_appendix - saved_img_path = img.filepath_raw - - sc.render.bake_type = 'ALPHA' - sc.render.use_bake_selected_to_active = True - sc.render.use_bake_clear = True - - # try to bake if it fails give report - try: - bpy.ops.object.bake_image() - img.save() - except: - # no return value, so the image loading is skipped - saved_img_path = None - collect_report("ERROR: Baking could not be completed. " - "Check System Console for info") - if store_area: - bpy.context.area.type = store_area - - bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.delete() - bpy.ops.object.select_pattern(extend=False, pattern=Robj.name, case_sensitive=False) - sc.objects.active = Robj - img.user_clear() - bpy.data.images.remove(img) - - if tmat.users == 0: - bpy.data.materials.remove(tmat) - - if saved_img_path: - collect_report("------- Baking finished -------") - return saved_img_path - - -def AutoNodeInitiate(active=False, operator=None): - # Checks with bpy.ops.material.check_converter_path - # if it's possible to write in the output path - # if it passes proceeds with calling AutoNode - - # if CheckImagePath(operator): - check_path = bpy.ops.material.check_converter_path() - - global CHECK_AUTONODE - - if 'FINISHED' in check_path: - sc = bpy.context.scene - CHECK_AUTONODE = True - collect_report("_______________________", True, False) - AutoNode(active, operator) - if sc.mat_context_menu.SET_FAKE_USER: - SetFakeUserTex() - else: - warning_messages(operator, 'DIR_PATH_CONVERT', override=True) - - -def AutoNode(active=False, operator=None): - global CHECK_AUTONODE - sc = bpy.context.scene - if active: - # fix for empty slots by angavrilov - mats = [slot.material for slot in bpy.context.active_object.material_slots if - slot.material] - else: - mats = bpy.data.materials - - # No Materials for the chosen action - abort - if not mats: - CHECK_AUTONODE = False - if operator: - if active: - act_obj = bpy.context.active_object - warning_messages(operator, 'CONV_NO_OBJ_MAT', act_obj.name) - else: - warning_messages(operator, 'CONV_NO_SC_MAT') - return - - for cmat in mats: - # check for empty material (it will fall through the first check) - test_empty = getattr(cmat, "name", None) - if test_empty is None: - collect_report("An empty material was hit, skipping") - continue - - cmat.use_nodes = True - TreeNodes = cmat.node_tree - links = TreeNodes.links - - # Don't alter nodes of locked materials - locked = False - for n in TreeNodes.nodes: - if n.type == 'ShaderNodeOutputMaterial': - if n.label == 'Locked': - locked = True - break - - if not locked: - # Convert this material from non-nodes to Cycles nodes - shader = '' - shtsl = '' - Add_Emission = '' - Add_Translucent = '' - Mix_Alpha = '' - sT = False - # check if some link creation failed - link_fail = False - - for n in TreeNodes.nodes: - TreeNodes.nodes.remove(n) - - # 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') - shader_val.location = 0, -200 - shout = TreeNodes.nodes.new('ShaderNodeOutputMaterial') - shout.location = 200, 10 - try: - links.new(shader.outputs[0], shout.inputs[0]) - links.new(shader.inputs[0], shader_val.outputs[0]) - except: - link_fail = True - - # Create other shader types only sculpt/texture paint mode is False - sculpt_paint = sc.mat_context_menu.SCULPT_PAINT - if sculpt_paint is False: - - cmat_is_transp = cmat.use_transparency and cmat.alpha < 1 - - if not cmat.raytrace_mirror.use and not cmat_is_transp: - if not shader.type == 'ShaderNodeBsdfDiffuse': - collect_report("INFO: Make DIFFUSE shader node for: " + cmat.name) - TreeNodes.nodes.remove(shader) - shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse') - shader.location = 10, 10 - try: - links.new(shader.outputs[0], shout.inputs[0]) - except: - link_fail = True - - if cmat.raytrace_mirror.use and cmat.raytrace_mirror.reflect_factor > 0.001 and cmat_is_transp: - if not shader.type == 'ShaderNodeBsdfGlass': - collect_report("INFO: Make GLASS shader node for: " + cmat.name) - TreeNodes.nodes.remove(shader) - shader = TreeNodes.nodes.new('ShaderNodeBsdfGlass') - shader.location = 0, 100 - try: - links.new(shader.outputs[0], shout.inputs[0]) - except: - link_fail = True - - if cmat.raytrace_mirror.use and not cmat_is_transp and cmat.raytrace_mirror.reflect_factor > 0.001: - if not shader.type == 'ShaderNodeBsdfGlossy': - collect_report("INFO: Make MIRROR shader node for: " + cmat.name) - TreeNodes.nodes.remove(shader) - shader = TreeNodes.nodes.new('ShaderNodeBsdfGlossy') - shader.location = 0, 10 - try: - links.new(shader.outputs[0], shout.inputs[0]) - except: - link_fail = True - - if cmat.emit > 0.001: - if (not shader.type == 'ShaderNodeEmission' and not - cmat.raytrace_mirror.reflect_factor > 0.001 and not cmat_is_transp): - - collect_report("INFO: Mix EMISSION shader node for: " + cmat.name) - TreeNodes.nodes.remove(shader) - shader = TreeNodes.nodes.new('ShaderNodeEmission') - shader.location = 0, 200 - try: - links.new(shader.outputs[0], shout.inputs[0]) - except: - link_fail = True - else: - if not Add_Emission: - collect_report("INFO: Add EMISSION shader node for: " + cmat.name) - shout.location = 600, 100 - Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader') - Add_Emission.location = 370, 100 - - shem = TreeNodes.nodes.new('ShaderNodeEmission') - shem.location = 0, 200 - try: - links.new(Add_Emission.outputs[0], shout.inputs[0]) - links.new(shem.outputs[0], Add_Emission.inputs[1]) - links.new(shader.outputs[0], Add_Emission.inputs[0]) - except: - link_fail = True - - shem.inputs['Color'].default_value = (cmat.diffuse_color.r, - cmat.diffuse_color.g, - cmat.diffuse_color.b, 1) - shem.inputs['Strength'].default_value = cmat.emit - - if cmat.translucency > 0.001: - collect_report("INFO: Add BSDF_TRANSLUCENT shader node for: " + cmat.name) - shout.location = 770, 330 - Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader') - Add_Translucent.location = 580, 490 - - shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent') - shtsl.location = 400, 350 - try: - links.new(Add_Translucent.outputs[0], shout.inputs[0]) - links.new(shtsl.outputs[0], Add_Translucent.inputs[1]) - - if Add_Emission: - links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0]) - else: - links.new(shader.outputs[0], Add_Translucent.inputs[0]) - except: - link_fail = True - - shtsl.inputs['Color'].default_value = (cmat.translucency, - cmat.translucency, - cmat.translucency, 1) - if sculpt_paint is False: - shader.inputs['Color'].default_value = (cmat.diffuse_color.r, - cmat.diffuse_color.g, - cmat.diffuse_color.b, 1) - else: - # Create Clay Material (Diffuse, Glossy, Layer Weight) - shader.inputs['Color'].default_value = PAINT_SC_COLOR - shader.inputs['Roughness'].default_value = 0.9486 - - # remove Color Ramp and links from the default shader and reroute - try: - shout.location = 400, 0 - for link in links: - links.remove(link) - - clay_frame = TreeNodes.nodes.new('NodeFrame') - clay_frame.name = 'Clay Material' - clay_frame.label = 'Clay Material' - - sh_glossy = TreeNodes.nodes.new('ShaderNodeBsdfGlossy') - sh_glossy.location = 0, 200 - sh_glossy.inputs['Color'].default_value = CLAY_GLOSSY - sh_mix = TreeNodes.nodes.new('ShaderNodeMixShader') - sh_mix.location = 200, 0 - sh_weight = TreeNodes.nodes.new('ShaderNodeLayerWeight') - sh_weight.location = 0, 350 - links.new(sh_mix.outputs[0], shout.inputs[0]) - links.new(sh_weight.outputs[1], sh_mix.inputs[0]) - links.new(shader.outputs[0], sh_mix.inputs[1]) - links.new(sh_glossy.outputs[0], sh_mix.inputs[2]) - # set frame as parent to everything - for clay_node in (shader, sh_glossy, sh_mix, sh_weight): - clay_node.parent = clay_frame - except: - collect_report("ERROR: Failure to create Clay Material") - - if not sculpt_paint: - if shader.type == 'ShaderNodeBsdfDiffuse': - shader.inputs['Roughness'].default_value = cmat.specular_intensity - - if shader.type == 'ShaderNodeBsdfGlossy': - shader.inputs['Roughness'].default_value = sqrt(max(1 - cmat.raytrace_mirror.gloss_factor, 0.0)) - - if shader.type == 'ShaderNodeBsdfGlass': - shader.inputs['Roughness'].default_value = sqrt(max(1 - cmat.raytrace_mirror.gloss_factor, 0.0)) - shader.inputs['IOR'].default_value = cmat.raytrace_transparency.ior - - if shader.type == 'ShaderNodeEmission': - shader.inputs['Strength'].default_value = cmat.emit - - # texture presence check - is_textures = False - - for tex in cmat.texture_slots: - if tex: - if not is_textures: - is_textures = True - break - - if is_textures: - # collect the texture nodes created - # for spreading a bit the texture nodes - tex_node_collect = [] - - sM = True - - for tex in cmat.texture_slots: - sT = False - tex_use = getattr(tex, "use", None) - baked_path = None - if tex_use: - tex_node_loc = -200, 450 - ma_alpha = getattr(tex, "use_map_alpha", None) - sM = (False if ma_alpha else True) - img = None - - if tex.texture.type == 'IMAGE': - if sc.mat_context_menu.EXTRACT_ALPHA and tex.texture.use_alpha: - if (not - os_path.exists(bpy.path.abspath(tex.texture.image.filepath + "_BAKING.png")) or - sc.mat_context_menu.EXTRACT_OW): - baked_path = BakingText(tex, 'ALPHA') - - if baked_path: - try: - img = bpy.data.images.load(baked_path) - collect_report("INFO: Loading Baked texture path:") - collect_report(baked_path) - except: - collect_report("ERROR: Baked image could not be loaded") - else: - has_image = getattr(tex.texture, "image", None) - if has_image: - img = has_image - - if img: - img_name = (img.name if hasattr(img, "name") else "NO NAME") - shtext = TreeNodes.nodes.new('ShaderNodeTexImage') - shtext.location = tex_node_loc - shtext.hide = True - shtext.width_hidden = 150 - shtext.image = img - shtext.name = img_name - shtext.label = "Image " + img_name - if baked_path: - shtext.use_custom_color = True - shtext.color = NODE_COLOR - collect_report("INFO: Creating Image Node for image: " + img_name) - tex_node_collect.append(shtext) - sT = True - else: - collect_report("ERROR: A problem occurred with loading an image for {} " - "(possibly missing)".format(tex.texture.name)) - else: - if sc.mat_context_menu.EXTRACT_PTEX or (sc.mat_context_menu.EXTRACT_ALPHA and ma_alpha): - if (not os_path.exists(bpy.path.abspath(tex.texture.name + "_PTEXT.jpg")) or - sc.mat_context_menu.EXTRACT_OW): - tex_type = tex.texture.type.lower() - collect_report("Attempting to Extract Procedural Texture type: " + tex_type) - baked_path = BakingText(tex, 'PTEX', tex_type) - - if baked_path: - try: - img = bpy.data.images.load(baked_path) - collect_report("Loading Baked texture path:") - collect_report(baked_path) - img_name = (img.name if hasattr(img, "name") else "NO NAME") - shtext = TreeNodes.nodes.new('ShaderNodeTexImage') - shtext.location = tex_node_loc - shtext.hide = True - shtext.width_hidden = 150 - shtext.image = img - shtext.name = img_name - shtext.label = "Baked Image " + img_name - shtext.use_custom_color = True - shtext.color = NODE_COLOR - collect_report("Creating Image Node for baked image: " + img_name) - tex_node_collect.append(shtext) - sT = True - except: - collect_report("ERROR: Failure to load baked image: " + img_name) - else: - collect_report("ERROR: Failure during baking, no images loaded") - - if sculpt_paint is False: - if (cmat_is_transp and cmat.raytrace_transparency.ior == 1 and - not cmat.raytrace_mirror.use and sM): - - if not shader.type == 'ShaderNodeBsdfTransparent': - collect_report("INFO: Make TRANSPARENT shader node for: " + cmat.name) - TreeNodes.nodes.remove(shader) - shader = TreeNodes.nodes.new('ShaderNodeBsdfTransparent') - shader.location = 0, 470 - try: - links.new(shader.outputs[0], shout.inputs[0]) - except: - link_fail = True - - shader.inputs['Color'].default_value = (cmat.diffuse_color.r, - cmat.diffuse_color.g, - cmat.diffuse_color.b, 1) - - if sT and sculpt_paint is False: - if tex.use_map_color_diffuse: - try: - links.new(shtext.outputs[0], shader.inputs[0]) - except: - pass - if tex.use_map_emit: - if not Add_Emission: - collect_report("INFO: Mix EMISSION + Texture shader node for: " + cmat.name) - intensity = 0.5 + (tex.emit_factor / 2) - - shout.location = 550, 330 - Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader') - Add_Emission.name = "Add_Emission" - Add_Emission.location = 370, 490 - - shem = TreeNodes.nodes.new('ShaderNodeEmission') - shem.location = 180, 380 - - try: - links.new(Add_Emission.outputs[0], shout.inputs[0]) - links.new(shem.outputs[0], Add_Emission.inputs[1]) - links.new(shader.outputs[0], Add_Emission.inputs[0]) - links.new(shtext.outputs[0], shem.inputs[0]) - except: - link_fail = True - - shem.inputs['Color'].default_value = (cmat.diffuse_color.r, - cmat.diffuse_color.g, - cmat.diffuse_color.b, 1) - shem.inputs['Strength'].default_value = intensity * 2 - - if tex.use_map_mirror: - try: - links.new(shtext.outputs[0], shader.inputs[0]) - except: - link_fail = True - - if tex.use_map_translucency: - if not Add_Translucent: - collect_report("INFO: Add Translucency + Texture shader node for: " + cmat.name) - - intensity = 0.5 + (tex.emit_factor / 2) - shout.location = 550, 330 - Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader') - Add_Translucent.name = "Add_Translucent" - Add_Translucent.location = 370, 290 - - shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent') - shtsl.location = 180, 240 - try: - links.new(shtsl.outputs[0], Add_Translucent.inputs[1]) - - if Add_Emission: - links.new(Add_Translucent.outputs[0], shout.inputs[0]) - links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0]) - pass - else: - links.new(Add_Translucent.outputs[0], shout.inputs[0]) - links.new(shader.outputs[0], Add_Translucent.inputs[0]) - - links.new(shtext.outputs[0], shtsl.inputs[0]) - except: - link_fail = True - - if tex.use_map_alpha: - if not Mix_Alpha: - collect_report("INFO: Mix Alpha + Texture shader node for: " + cmat.name) - - shout.location = 750, 330 - Mix_Alpha = TreeNodes.nodes.new('ShaderNodeMixShader') - Mix_Alpha.name = "Add_Alpha" - Mix_Alpha.location = 570, 290 - sMask = TreeNodes.nodes.new('ShaderNodeBsdfTransparent') - sMask.location = 250, 180 - tMask, imask = None, None - - # search if the texture node already exists, if not create - nodes = getattr(cmat.node_tree, "nodes", None) - img_name = getattr(img, "name", "NO NAME") - for node in nodes: - if type(node) == bpy.types.ShaderNodeTexImage: - node_name = getattr(node, "name") - if img_name in node_name: - tMask = node - collect_report("INFO: Using existing Texture Node for Mask: " + node_name) - break - - if tMask is None: - tMask = TreeNodes.nodes.new('ShaderNodeTexImage') - tMask.location = tex_node_loc - tex_node_collect.append(tMask) - - try: - file_path = getattr(img, "filepath", None) - if file_path: - imask = bpy.data.images.load(file_path) - else: - imask = bpy.data.images.get(img_name) - collect_report("INFO: Attempting to load image for Mask: " + img_name) - except: - collect_report("ERROR: Failure to load image for Mask: " + img_name) - - if imask: - tMask.image = imask - - if tMask: - try: - links.new(Mix_Alpha.inputs[0], tMask.outputs[1]) - links.new(shout.inputs[0], Mix_Alpha.outputs[0]) - links.new(sMask.outputs[0], Mix_Alpha.inputs[1]) - - if not Add_Translucent: - if Add_Emission: - links.new(Mix_Alpha.inputs[2], Add_Emission.outputs[0]) - else: - links.new(Mix_Alpha.inputs[2], shader.outputs[0]) - else: - links.new(Mix_Alpha.inputs[2], Add_Translucent.outputs[0]) - except: - link_fail = True - else: - collect_report("ERROR: Mix Alpha could not be created " - "(mask image could not be loaded)") - - if tex.use_map_normal: - t = TreeNodes.nodes.new('ShaderNodeRGBToBW') - t.location = -0, 300 - try: - links.new(t.outputs[0], shout.inputs[2]) - links.new(shtext.outputs[1], t.inputs[0]) - except: - link_fail = True - - if sculpt_paint: - try: - # create a new image for texture painting and make it active - img_size = (int(sc.mat_context_menu.img_bake_size) if - sc.mat_context_menu.img_bake_size else 1024) - paint_mat_name = getattr(cmat, "name", "NO NAME") - paint_img_name = "Paint Base Image {}".format(paint_mat_name) - bpy.ops.image.new(name=paint_img_name, width=img_size, height=img_size, - color=(1.0, 1.0, 1.0, 1.0), alpha=True, float=False) - - img = bpy.data.images.get(paint_img_name) - img_name = (img.name if hasattr(img, "name") else "NO NAME") - shtext = TreeNodes.nodes.new('ShaderNodeTexImage') - shtext.hide = True - shtext.width_hidden = 150 - shtext.location = tex_node_loc - shtext.image = img - shtext.name = img_name - shtext.label = "Paint: " + img_name - shtext.use_custom_color = True - shtext.color = NODE_COLOR_PAINT - shtext.select = True - collect_report("INFO: Creating Image Node for Painting: " + img_name) - collect_report("WARNING: Don't forget to save it on Disk") - tex_node_collect.append(shtext) - except: - collect_report("ERROR: Failed to create image and node for Texture Painting") - - # spread the texture nodes, create node frames if necessary - # create texture coordinate and mapping too - row_node = -1 - tex_node_collect_size = len(tex_node_collect) - median_point = ((tex_node_collect_size / 2) * 100) - check_frame = bool(tex_node_collect_size > 1) - - node_frame, tex_map = None, None - node_f_coord, node_f_mix = None, None - tex_map_collection, tex_map_coord = [], None - tree_size, tree_tex_start = 0, 0 - - if check_frame: - node_frame = TreeNodes.nodes.new('NodeFrame') - node_frame.name = 'Converter Textures' - node_frame.label = 'Converter Textures' - - node_f_coord = TreeNodes.nodes.new('NodeFrame') - node_f_coord.name = "Coordinates" - node_f_coord.label = "Coordinates" - - node_f_mix = TreeNodes.nodes.new('NodeFrame') - node_f_mix.name = "Mix" - node_f_mix.label = "Mix" - - if tex_node_collect: - tex_map_coord = TreeNodes.nodes.new('ShaderNodeTexCoord') - tex_map_coord.location = -900, 575 - - # precalculate the depth of the inverted tree - tree_size = int(ceil(log2(tex_node_collect_size))) - # offset the start of the mix nodes by the depth of the tree - tree_tex_start = ((tree_size + 1) * 150) - - for node_tex in tex_node_collect: - row_node += 1 - col_node_start = (median_point - (-(row_node * 50) + median_point)) - tex_node_row = tree_tex_start + 300 - mix_node_row = tree_tex_start + 620 - tex_node_loc = (-(tex_node_row), col_node_start) - - try: - node_tex.location = tex_node_loc - if check_frame: - node_tex.parent = node_frame - else: - node_tex.hide = False - - tex_node_name = getattr(node_tex, "name", "NO NAME") - tex_map_name = "Mapping: {}".format(tex_node_name) - tex_map = TreeNodes.nodes.new('ShaderNodeMapping') - tex_map.location = (-(mix_node_row), col_node_start) - tex_map.width = 240 - tex_map.hide = True - tex_map.width_hidden = 150 - tex_map.name = tex_map_name - tex_map.label = tex_map_name - tex_map_collection.append(tex_map) - links.new(tex_map.outputs[0], node_tex.inputs[0]) - except: - link_fail = True - continue - - if tex_map_collection: - tex_mix_start = len(tex_map_collection) / 2 - row_map_start = -(tree_tex_start + 850) - - if tex_map_coord: - tex_map_coord.location = (row_map_start, - (median_point - (tex_mix_start * 50))) - - for maps in tex_map_collection: - try: - if node_f_coord: - maps.parent = node_f_coord - else: - maps.hide = False - - links.new(maps.inputs[0], tex_map_coord.outputs['UV']) - except: - link_fail = True - continue - - # create mix nodes to connect texture nodes to the shader input - # sculpt mode doesn't need them - if check_frame and not sculpt_paint: - mix_node_pairs = loop_node_from_list(TreeNodes, links, tex_node_collect, - 0, tree_tex_start, median_point, node_f_mix) - - for n in range(1, tree_size): - mix_node_pairs = loop_node_from_list(TreeNodes, links, mix_node_pairs, - n, tree_tex_start, median_point, node_f_mix) - try: - for node in mix_node_pairs: - links.new(node.outputs[0], shader.inputs[0]) - except: - link_fail = True - - mix_node_pairs = [] - - tex_node_collect, tex_map_collection = [], [] - - if link_fail: - collect_report("ERROR: Some of the node links failed to connect") - - else: - collect_report("No textures in the Scene, no Image Nodes to add") - - bpy.context.scene.render.engine = 'CYCLES' - - -def loop_node_from_list(TreeNodes, links, node_list, loc, start, median_point, frame): - row = 1 - mix_nodes = [] - node_list_size = len(node_list) - tuplify = [tuple(node_list[s:s + 2]) for s in range(0, node_list_size, 2)] - for nodes in tuplify: - row += 1 - create_mix = create_mix_node(TreeNodes, links, nodes, loc, start, - median_point, row, frame) - if create_mix: - mix_nodes.append(create_mix) - return mix_nodes - - -def create_mix_node(TreeNodes, links, nodes, loc, start, median_point, row, frame): - mix_node = TreeNodes.nodes.new('ShaderNodeMixRGB') - mix_node.name = "MIX level: " + str(loc) - mix_node.label = "MIX level: " + str(loc) - mix_node.use_custom_color = True - mix_node.color = NODE_COLOR_MIX - mix_node.hide = True - mix_node.width_hidden = 75 - - if frame: - mix_node.parent = frame - mix_node.location = -(start - loc * 175), ((median_point / 4) + (row * 50)) - - try: - if len(nodes) > 1: - links.new(nodes[0].outputs[0], mix_node.inputs["Color2"]) - links.new(nodes[1].outputs[0], mix_node.inputs["Color1"]) - elif len(nodes) == 1: - links.new(nodes[0].outputs[0], mix_node.inputs["Color1"]) - except: - collect_report("ERROR: Link failed for mix node {}".format(mix_node.label)) - return mix_node - - -def unwrap_active_object(context): - enable_unwrap = context.scene.mat_context_menu.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 - -class mllock(Operator): - bl_idname = "ml.lock" - bl_label = "Lock" - bl_description = "Lock/unlock this material against modification by conversions" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return (c_is_cycles_addon_enabled() and c_data_has_materials()) - - def execute(self, context): - cmat = bpy.context.selected_objects[0].active_material - TreeNodes = cmat.node_tree - for n in TreeNodes.nodes: - if n.type == 'ShaderNodeOutputMaterial': - n.label = "" if n.label == "Locked" else "Locked" - - return {'FINISHED'} - - -class mlrefresh(Operator): - bl_idname = "ml.refresh" - bl_label = "Convert All Materials" - bl_description = ("Convert All Materials in the scene from non-nodes to Cycles\n" - "Needs saving the .blend file first") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - 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: - unwrap_active_object(context) - - collect_report("Conversion finished !", False, True) - - return {'FINISHED'} - - -class mlrefresh_active(Operator): - bl_idname = "ml.refresh_active" - bl_label = "Convert All Materials From Active Object" - bl_description = ("Convert all Active Object's Materials from non-nodes to Cycles\n" - "Needs saving the .blend file first") - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return (bpy.data.filepath != "" and c_is_cycles_addon_enabled() and - c_data_has_materials() and context.active_object is not None) - - def execute(self, context): - AutoNodeInitiate(True, self) - if CHECK_AUTONODE is True: - unwrap_active_object(context) - - collect_report("Conversion finished !", False, True) - - return {'FINISHED'} - - -class mlrestore(Operator): - bl_idname = "ml.restore" - bl_label = "Switch Between Renderers" - bl_description = ("Switch between Renderers \n" - "(Doesn't create new nor converts existing materials)") - bl_options = {'REGISTER', 'UNDO'} - - switcher: BoolProperty( - 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', - ) - - @classmethod - def poll(cls, context): - return c_is_cycles_addon_enabled() - - def execute(self, context): - switch = "ON" if self.switcher else "OFF" - AutoNodeSwitch(self.renderer, switch, self) - - return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - pass - - -def unregister(): - bpy.utils.unregister_module(__name__) - pass - - -if __name__ == "__main__": - register() diff --git a/materials_utils/texture_rename.py b/materials_utils/texture_rename.py deleted file mode 100644 index 7ca26c4a..00000000 --- a/materials_utils/texture_rename.py +++ /dev/null @@ -1,127 +0,0 @@ -# gpl: author Yadoob -# -*- coding: utf-8 -*- - -import bpy -from bpy.types import ( - Operator, - Panel, -) -from bpy.props import ( - BoolProperty, - StringProperty, -) -from .warning_messages_utils import ( - 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" - "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", - 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): - return c_data_has_images() - - def draw(self, context): - layout = self.layout - 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.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 = len(bpy.data.textures) - - for texture in bpy.data.textures: - try: - 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: - errors.append(str(texture.name)) - for word in img.name: - if word != ".": - textname = textname + word - else: - break - texture.name = textname - if texture.type != "IMAGE": # rename specific textures as clouds, environment map... - texture.name = texture.type.lower() - except: - continue - - if tex_count == 0: - warning_messages(self, 'NO_TEX_RENAME') - elif errors: - warning_messages(self, 'TEX_RENAME_F', errors, 'TEX') - - # reset name to default - self.named = self.def_name - - self.is_not_undo = False - - return {'FINISHED'} - - -class TEXTURE_PT_rename_panel(Panel): - bl_label = "Texture Rename" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "texture" - - def draw(self, context): - layout = self.layout - layout.operator("texture.patern_rename") - - -def register(): - bpy.utils.register_class(TEXTURE_OT_patern_rename) - bpy.utils.register_class(TEXTURE_PT_rename_panel) - - -def unregister(): - bpy.utils.unregister_class(TEXTURE_PT_rename_panel) - bpy.utils.unregister_class(TEXTURE_OT_patern_rename) - - -if __name__ == "__main__": - register() diff --git a/materials_utils/warning_messages_utils.py b/materials_utils/warning_messages_utils.py deleted file mode 100644 index 1ce12d9d..00000000 --- a/materials_utils/warning_messages_utils.py +++ /dev/null @@ -1,181 +0,0 @@ -# gpl: author lijenstina -# -*- coding: utf-8 -*- - -import bpy - -# Globals # - -# change the name for the properties settings -MAT_SPEC_NAME = "materials_context_menu" - -# collect messages for the report operator -COLLECT_REPORT = [] - - -# Functions - -def warning_messages(operator=None, warn='DEFAULT', object_name="", is_mat=None, - fake="", override=False): - # Enter warning messages to the message dictionary - # warn - if nothing passed falls back to DEFAULT - # 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 in UI report - # override - important messages that should be enabled, no matter the setting - - # pass the show_warnings bool to enable/disable them - addon = bpy.context.preferences.addons[MAT_SPEC_NAME] - get_warn = addon.preferences.show_warnings if addon else False - show_warn = get_warn if override is False else override - - if show_warn and operator: - obj_name = "" - MAX_COUNT = 6 - gramma_s, gramma_p = " - has ", " - have " - - if is_mat: - if is_mat in ('MAT'): - gramma_s, gramma_p = " - Material has ", " - Materials have " - elif is_mat in ('TEX'): - gramma_s, gramma_p = " - Texture has ", " - Textures have " - elif is_mat in ('FILE'): - gramma_s, gramma_p = " - File ", " - Files " - - # print the whole list in the console if abbreviated - obj_size_big = False - - if object_name: - if type(object_name) is list: - obj_name = ", ".join(object_name) - obj_size = len(object_name) - - # compare string list size - if (1 < obj_size <= MAX_COUNT): - obj_name = "{}{}".format(obj_name, gramma_p) - elif (obj_size > MAX_COUNT): - abbrevation = ("(Multiple)" if is_mat else "(Multiple Objects)") - obj_size_big = True - obj_name = "{}{}".format(abbrevation, gramma_p) - elif (obj_size == 1): - obj_name = "{}{}".format(obj_name, gramma_s) - else: - obj_name = "{}{}".format(object_name, gramma_s) - - message = { - 'DEFAULT': "No editable selected objects, could not finish", - 'PLACEHOLDER': "{}{}".format(warn, " - Message key is not present in the warning_message_utils"), - 'RMV_EDIT': "{}{}".format(obj_name, "Unable to remove material slot in edit mode)"), - 'A_OB_MIX_NO_MAT': "{}{}".format(obj_name, "No Material applied. Object type can't have materials"), - 'A_MAT_NAME_EDIT': "{}{}".format(obj_name, " been applied to selection"), - 'C_OB_NO_MAT': "{}{}".format(obj_name, "No Materials. Unused material slots are " - "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", - 'R_ALL_SL_MAT': "All materials removed from selected objects", - '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", - 'FAKE_SET_ON': "{}{}{}".format(obj_name, "set Fake user ", fake), - 'FAKE_SET_OFF': "{}{}{}".format(obj_name, "disabled Fake user ", fake), - 'FAKE_NO_MAT': "Fake User Settings: Object(s) with no Materials or no changes needed", - 'CPY_MAT_MIX_OB': "Copy Materials to others: Some of the Object types can't have Materials", - 'CPY_MAT_ONE_OB': "Copy Materials to others: Only one object selected", - 'CPY_MAT_FAIL': "Copy Materials to others: (Operator Error)", - 'CPY_MAT_DONE': "Materials are copied from active to selected objects", - 'TEX_MAT_NO_SL': "Texface to Material: No Selected Objects", - 'TEX_MAT_NO_CRT': "{}{}".format(obj_name, "not met the conditions for the tool (UVs, Active Images)"), - 'MAT_TEX_NO_SL': "Material to Texface: No Selected Objects", - 'MAT_TEX_NO_MESH': "{}{}".format(obj_name, "not met the conditions for the tool (Mesh)"), - 'MAT_TEX_NO_MAT': "{}{}".format(obj_name, "not met the conditions for the tool (Material)"), - 'BI_SW_NODES_OFF': "Switching to Blender Render, Use Nodes disabled", - 'BI_SW_NODES_ON': "Switching to Blender Render, Use Nodes enabled", - 'CYC_SW_NODES_ON': "Switching back to Cycles, Use Nodes enabled", - 'CYC_SW_NODES_OFF': "Switching back to Cycles, Use Nodes disabled", - 'TEX_RENAME_F': "{}{}".format(obj_name, "no Images assigned, skipping"), - 'NO_TEX_RENAME': "No Textures in Data, nothing to rename", - 'DIR_PATH_W_ERROR': "ERROR: Directory without writing privileges", - 'DIR_PATH_N_ERROR': "ERROR: Directory not existing", - 'DIR_PATH_A_ERROR': "ERROR: Directory not accessible", - 'DIR_PATH_W_OK': "Directory has writing privileges", - 'DIR_PATH_CONVERT': "Conversion Cancelled. Problem with chosen Directory, check System Console", - 'DIR_PATH_EMPTY': "File Path is empty. Please save the .blend file first", - 'MAT_LINK_ERROR': "{}{}".format(obj_name, "not be renamed or set as Base(s)"), - 'MAT_LINK_NO_NAME': "No Base name given, No changes applied", - 'MOVE_SLOT_UP': "{}{}".format(obj_name, "been moved on top of the stack"), - 'MOVE_SLOT_DOWN': "{}{}".format(obj_name, "been moved to the bottom of the stack"), - 'MAT_TRNSP_BACK': "{}{}".format(obj_name, "been set with Alpha connected to Front/Back Geometry node"), - 'E_MAT_TRNSP_BACK': "Transparent back (BI): Failure to set the action", - 'CONV_NO_OBJ_MAT': "{}{}".format(obj_name, "has no Materials. Nothing to convert"), - 'CONV_NO_SC_MAT': "No Materials in the Scene. Nothing to convert", - 'CONV_NO_SEL_MAT': "No Materials on Selected Objects. Nothing to convert", - } - - # doh! did we passed an non existing dict key - warn = (warn if warn in message else 'PLACEHOLDER') - - operator.report({'INFO'}, message[warn]) - - if obj_size_big is True: - 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 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 switches to the final report with the operator in __init__ - global COLLECT_REPORT - scene = bpy.context.scene.mat_context_menu - use_report = scene.enable_report - - if is_start: - # there was a crash somewhere before the is_final call - COLLECT_REPORT = [] - - if collection and type(collection) is str: - if use_report: - COLLECT_REPORT.append(collection) - print(collection) - - if is_final and use_report: - # final operator pass uses * as delimiter for splitting into new lines - messages = "*".join(COLLECT_REPORT) - bpy.ops.mat_converter.reports('INVOKE_DEFAULT', message=messages) - COLLECT_REPORT = [] - - -def c_is_cycles_addon_enabled(): - # checks if Cycles is enabled thanks to ideasman42 - return ('cycles' in bpy.context.preferences.addons.keys()) - - -def c_data_has_materials(): - # check for material presence in data - 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) |