# ##### 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, 5),
    "blender": (2, 77, 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(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 (
    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)
    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.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.view_layer.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):
    # 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(percentage=0.75, align=True)
            row = split.row(align=True)
            row.label(text=item.name, translate=False, icon="MATERIAL_DATA")
            subrow = split.row(align=True)
            subrow.alignment = "RIGHT"
            subrow.label(text="", icon="PINNED" if item.mat_fake_user else "BLANK1")
            subrow = split.row(align=True)
            subrow.alignment = "RIGHT"
            subrow.label(text="", icon="LINK_BLEND" if item.mat_lib else "BLANK1")


def draw_ui_list_popups(self, context, obj_data=False):
    layout = self.layout

    if not self.is_not_undo:
        layout.label(text=UNDO_MESSAGE, icon="INFO")
        return

    matlen = len(context.scene.mat_specials_mats)
    matdata = "in Object's Data" if obj_data else "in Data"
    matgramma = "Material" if matlen == 1 else "Materials"
    matcount = "No" if matlen < 1 else matlen

    box = layout.box()
    col = box.column(align=True)
    col.label(text="{} {} {}".format(matcount, matgramma, matdata),
              icon="INFO")
    sub_split = col.split(percentage=0.7, align=True)
    sub_box_1 = sub_split.box()
    sub_box_1.label(text="Name")
    sub_split_2 = sub_split.split(percentage=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='ZOOMIN'
                        )
        add_new.matname = mat_prop_name
        add_new.is_existing = False

        if use_mat_menu_type() != 'POPUP':
            use_separator(self, context)

        if c_data_has_materials():
            mat_entry = layout.column()

            if use_mat_menu_type() == 'POPUP':
                matp = mat_entry.operator(
                            "view3d.assign_material",
                            icon='MATERIAL_DATA'
                            )
                matp.is_existing = True
            elif use_mat_menu_type() == 'COLUMNS':
                get_col_size = switch_to_column()
                mat_entry = layout.column_flow(columns=get_col_size)

            if use_mat_menu_type() in {'COLUMNS', 'STANDARD'}:
                for material_name in bpy.data.materials.keys():
                    mats = mat_entry.operator(
                                "view3d.assign_material",
                                text=material_name,
                                icon='MATERIAL_DATA'
                                )
                    mats.matname = material_name
                    mats.is_existing = True


class VIEW3D_MT_select_material(Menu):
    bl_label = "Select by Material"

    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(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(percentage=size_split, align=True)
        split.prop(self, "show_mat_preview")

        if self.show_mat_preview:
            subsplit = split.split(percentage=0.7, align=True)
            row = subsplit.row(align=True)
            row.prop(self, "set_preview_type", expand=True)

            subrow = subsplit.row(align=True)
            subrow.enabled = True if self.set_preview_type in {'PREVIEW'} else False
            subrow.prop(self, "set_preview_size", text="")

        box = col_m.box()
        size_split = 0.3 if self.show_converters else 1.0
        split = box.split(percentage=size_split, align=True)
        split.prop(self, "show_converters")

        if self.show_converters:
            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)


def register():
    bpy.utils.register_module(__name__)

    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

    bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
    register()
