Welcome to mirror list, hosted at ThFree Co, Russian Federation.

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