Skip to content
Snippets Groups Projects
materials_cycles_converter.py 43.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • # gpl: author Silvio Falcinelli. Fixes by angavrilov and others.
    # special thanks to user blenderartists.org cmomoney
    # -*- coding: utf-8 -*-
    
    import bpy
    from os import path as os_path
    from bpy.types import Operator
    
    from math import (log2,
                      ceil,
                      )
    
    from bpy.props import (
                BoolProperty,
                EnumProperty,
                )
    from .warning_messages_utils import (
                warning_messages,
                c_is_cycles_addon_enabled,
                c_data_has_materials,
                collect_report,
                )
    
    # -----------------------------------------------------------------------------
    # Globals #
    
    # switch for operator's function called after AutoNodeInitiate
    CHECK_AUTONODE = False
    
    
    # set the node color for baked images (default greenish)
    
    NODE_COLOR = (0.32, 0.75, 0.32)
    
    # set the node color for the paint base images (default reddish)
    NODE_COLOR_PAINT = (0.6, 0.0, 0.0)
    
    # set the mix node color (default blueish)
    NODE_COLOR_MIX = (0.1, 0.7, 0.8)
    
    
    # color for sculpt/texture painting setting (default clay the last entry is Roughness)
    PAINT_SC_COLOR = (0.80, 0.75, 0.54, 0.9)
    CLAY_GLOSSY = (0.38, 0.032, 0.023, 1)
    
    
    # -----------------------------------------------------------------------------
    # Functions #
    
    
    def AutoNodeSwitch(renderer="CYCLES", switch="OFF", operator=None):
        mats = bpy.data.materials
        use_nodes = (True if switch in ("ON") else False)
        warn_message = ('BI_SW_NODES_ON' if switch in ("ON") else
                        'BI_SW_NODES_OFF')
        warn_message_2 = ('CYC_SW_NODES_ON' if switch in ("ON") else
                          'CYC_SW_NODES_OFF')
        for cmat in mats:
            cmat.use_nodes = use_nodes
        renders = ('CYCLES' if renderer and renderer == "CYCLES" else
                   'BLENDER_RENDER')
        bpy.context.scene.render.engine = renders
        if operator:
            warning_messages(operator, (warn_message_2 if renders in ('CYCLES') else
                                        warn_message))
    
    
    def SetFakeUserTex():
        images = bpy.data.images
        for image in images:
            has_user = getattr(image, "users", -1)
            image_name = getattr(image, "name", "NONAME")
    
            if has_user == 0:
                image.use_fake_user = True
                collect_report("INFO: Set fake user for unused image: " + image_name)
    
    
    def BakingText(tex, mode, tex_type=None):
        collect_report("INFO: start bake texture named: " + tex.name)
        saved_img_path = None
    
        bpy.ops.object.mode_set(mode='OBJECT')
        sc = bpy.context.scene
        tmat = ''
        img = ''
        Robj = bpy.context.active_object
        for n in bpy.data.materials:
            if n.name == 'TMP_BAKING':
                tmat = n
        if not tmat:
            tmat = bpy.data.materials.new('TMP_BAKING')
            tmat.name = "TMP_BAKING"
    
        bpy.ops.mesh.primitive_plane_add()
        tm = bpy.context.active_object
        tm.name = "TMP_BAKING"
        tm.data.name = "TMP_BAKING"
        bpy.ops.object.select_pattern(extend=False, pattern="TMP_BAKING",
                                      case_sensitive=False)
        sc.objects.active = tm
        bpy.context.scene.render.engine = 'BLENDER_RENDER'
        tm.data.materials.append(tmat)
        if len(tmat.texture_slots.items()) == 0:
            tmat.texture_slots.add()
        tmat.texture_slots[0].texture_coords = 'UV'
        tmat.texture_slots[0].use_map_alpha = True
        tmat.texture_slots[0].texture = tex.texture
        tmat.texture_slots[0].use_map_alpha = True
        tmat.texture_slots[0].use_map_color_diffuse = False
        tmat.use_transparency = True
        tmat.alpha = 0
        tmat.use_nodes = False
        tmat.diffuse_color = 1, 1, 1
        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.uv.unwrap()
    
        # clean up temporary baking images if any
        for n in bpy.data.images:
            if n.name == 'TMP_BAKING':
                n.user_clear()
                bpy.data.images.remove(n)
    
        if mode == "ALPHA" and tex.texture.type == 'IMAGE':
            sizeX = tex.texture.image.size[0]
            sizeY = tex.texture.image.size[1]
        else:
            bake_size = (int(sc.mat_specials.img_bake_size) if
                         sc.mat_specials.img_bake_size else 1024)
            sizeX = bake_size
            sizeY = bake_size
    
        bpy.ops.image.new(name="TMP_BAKING", width=sizeX, height=sizeY,
                          color=(0.0, 0.0, 0.0, 1.0), alpha=True, float=False)
    
        sc.render.engine = 'BLENDER_RENDER'
        img = bpy.data.images["TMP_BAKING"]
        img = bpy.data.images.get("TMP_BAKING")
        img.file_format = ("JPEG" if not mode == "ALPHA" else "PNG")
    
    
        # switch temporarly to 'IMAGE EDITOR'
        # other approaches are not reliable
        check_area = False
        store_area = bpy.context.area.type
        collect_report("INFO: Temporarly switching context to Image Editor")
    
            bpy.context.area.type = 'IMAGE_EDITOR'
            bpy.context.area.spaces[0].image = bpy.data.images["TMP_BAKING"]
            check_area = True
    
            collect_report("ERROR: Setting to Image Editor failed, Baking aborted")
            check_area = False
    
        if check_area:
            paths = bpy.path.abspath(sc.mat_specials.conv_path)
            tex_name = getattr(getattr(tex.texture, "image", None), "name", None)
            texture_name = (tex_name.rpartition(".")[0] if tex_name else tex.texture.name)
            new_tex_name = "baked"
            name_append = ("_BAKING" if mode == "ALPHA" and
                           tex.texture.type == 'IMAGE' else "_PTEXT")
            new_appendix = (".jpg" if not mode == "ALPHA" else ".png")
    
            if name_append in texture_name:
                new_tex_name = texture_name
            elif tex_type:
                new_tex_name = tex_type + name_append
            else:
                new_tex_name = texture_name + name_append
    
            img.filepath_raw = paths + new_tex_name + new_appendix
            saved_img_path = img.filepath_raw
    
            sc.render.bake_type = 'ALPHA'
            sc.render.use_bake_selected_to_active = True
            sc.render.use_bake_clear = True
    
            # try to bake if it fails give report
            try:
                bpy.ops.object.bake_image()
                img.save()
            except:
                # no return value, so the image loading is skipped
                saved_img_path = None
                collect_report("ERROR: Baking could not be completed. "
                               "Check System Console for info")
        if store_area:
            bpy.context.area.type = store_area
    
    
        bpy.ops.object.mode_set(mode='OBJECT')
        bpy.ops.object.delete()
        bpy.ops.object.select_pattern(extend=False, pattern=Robj.name, case_sensitive=False)
        sc.objects.active = Robj
        img.user_clear()
        bpy.data.images.remove(img)
    
        if tmat.users == 0:
            bpy.data.materials.remove(tmat)
    
        if saved_img_path:
    
            collect_report("------- Baking finished -------")
    
            return saved_img_path
    
    
    def AutoNodeInitiate(active=False, operator=None):
        # Checks with bpy.ops.material.check_converter_path
        # if it's possible to write in the output path
        # if it passes procedes with calling AutoNode
    
        # if CheckImagePath(operator):
        check_path = bpy.ops.material.check_converter_path()
    
        global CHECK_AUTONODE
    
        if 'FINISHED' in check_path:
            sc = bpy.context.scene
            CHECK_AUTONODE = True
    
            collect_report("_______________________", True, False)
    
            AutoNode(active, operator)
            if sc.mat_specials.SET_FAKE_USER:
                SetFakeUserTex()
        else:
            warning_messages(operator, 'DIR_PATH_CONVERT')
    
    
    def AutoNode(active=False, operator=None):
        global CHECK_AUTONODE
        sc = bpy.context.scene
        if active:
            # fix for empty slots by angavrilov
            mats = [slot.material for slot in bpy.context.active_object.material_slots if
                    slot.material]
        else:
            mats = bpy.data.materials
    
        # No Materials for the chosen action - abort
        if not mats:
            CHECK_AUTONODE = False
            if operator:
                if active:
                    act_obj = bpy.context.active_object
                    warning_messages(operator, 'CONV_NO_OBJ_MAT', act_obj.name)
                else:
                    warning_messages(operator, 'CONV_NO_SC_MAT')
            return
    
        for cmat in mats:
            # check for empty material (it will fall through the first check)
            test_empty = getattr(cmat, "name", None)
            if test_empty is None:
                collect_report("An empty material was hit, skipping")
                continue
    
            cmat.use_nodes = True
            TreeNodes = cmat.node_tree
            links = TreeNodes.links
    
            # Don't alter nodes of locked materials
            locked = False
            for n in TreeNodes.nodes:
                if n.type == 'ShaderNodeOutputMaterial':
                    if n.label == 'Locked':
                        locked = True
                        break
    
            if not locked:
                # Convert this material from non-nodes to Cycles nodes
                shader = ''
                shtsl = ''
                Add_Emission = ''
                Add_Translucent = ''
                Mix_Alpha = ''
                sT = False
    
                # check if some link creation failed
                link_fail = False
    
    
                for n in TreeNodes.nodes:
                    TreeNodes.nodes.remove(n)
    
                # Starting point is diffuse BSDF and output material
    
                # and a Color Ramp node
    
                shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse')
    
                shader.location = 10, 10
                shader_val = TreeNodes.nodes.new('ShaderNodeValToRGB')
                shader_val.location = 0, -200
    
                shout = TreeNodes.nodes.new('ShaderNodeOutputMaterial')
    
                shout.location = 200, 10
    
                try:
                    links.new(shader.outputs[0], shout.inputs[0])
    
                    links.new(shader.inputs[0], shader_val.outputs[0])
    
                except:
                    link_fail = True
    
                # Create other shader types only sculpt/texture paint mode is False
                sculpt_paint = sc.mat_specials.SCULPT_PAINT
                if sculpt_paint is False:
    
                    cmat_is_transp = cmat.use_transparency and cmat.alpha < 1
    
                    if not cmat.raytrace_mirror.use and not cmat_is_transp:
                        if not shader.type == 'ShaderNodeBsdfDiffuse':
                            collect_report("INFO: Make DIFFUSE shader node for: " + cmat.name)
                            TreeNodes.nodes.remove(shader)
                            shader = TreeNodes.nodes.new('ShaderNodeBsdfDiffuse')
    
                            shader.location = 10, 10
    
                            try:
                                links.new(shader.outputs[0], shout.inputs[0])
                            except:
                                link_fail = True
    
                    if cmat.raytrace_mirror.use and cmat.raytrace_mirror.reflect_factor > 0.001 and cmat_is_transp:
                        if not shader.type == 'ShaderNodeBsdfGlass':
                            collect_report("INFO: Make GLASS shader node for: " + cmat.name)
                            TreeNodes.nodes.remove(shader)
                            shader = TreeNodes.nodes.new('ShaderNodeBsdfGlass')
    
                            shader.location = 0, 100
    
                            try:
                                links.new(shader.outputs[0], shout.inputs[0])
                            except:
                                link_fail = True
    
                    if cmat.raytrace_mirror.use and not cmat_is_transp and cmat.raytrace_mirror.reflect_factor > 0.001:
                        if not shader.type == 'ShaderNodeBsdfGlossy':
                            collect_report("INFO: Make MIRROR shader node for: " + cmat.name)
                            TreeNodes.nodes.remove(shader)
                            shader = TreeNodes.nodes.new('ShaderNodeBsdfGlossy')
    
                            shader.location = 0, 10
    
                            try:
                                links.new(shader.outputs[0], shout.inputs[0])
                            except:
                                link_fail = True
    
                    if cmat.emit > 0.001:
                        if (not shader.type == 'ShaderNodeEmission' and not
                           cmat.raytrace_mirror.reflect_factor > 0.001 and not cmat_is_transp):
    
                            collect_report("INFO: Mix EMISSION shader node for: " + cmat.name)
                            TreeNodes.nodes.remove(shader)
                            shader = TreeNodes.nodes.new('ShaderNodeEmission')
    
                            shader.location = 0, 200
    
                            try:
                                links.new(shader.outputs[0], shout.inputs[0])
                            except:
                                link_fail = True
                        else:
                            if not Add_Emission:
                                collect_report("INFO: Add EMISSION shader node for: " + cmat.name)
    
                                shout.location = 600, 100
    
                                Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader')
    
                                Add_Emission.location = 370, 100
    
    
                                shem = TreeNodes.nodes.new('ShaderNodeEmission')
    
                                shem.location = 0, 200
    
                                try:
                                    links.new(Add_Emission.outputs[0], shout.inputs[0])
                                    links.new(shem.outputs[0], Add_Emission.inputs[1])
                                    links.new(shader.outputs[0], Add_Emission.inputs[0])
                                except:
                                    link_fail = True
    
                                shem.inputs['Color'].default_value = (cmat.diffuse_color.r,
                                                                      cmat.diffuse_color.g,
                                                                      cmat.diffuse_color.b, 1)
                                shem.inputs['Strength'].default_value = cmat.emit
    
                    if cmat.translucency > 0.001:
                        collect_report("INFO: Add BSDF_TRANSLUCENT shader node for: " + cmat.name)
                        shout.location = 770, 330
                        Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader')
                        Add_Translucent.location = 580, 490
    
                        shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent')
                        shtsl.location = 400, 350
                        try:
                            links.new(Add_Translucent.outputs[0], shout.inputs[0])
                            links.new(shtsl.outputs[0], Add_Translucent.inputs[1])
    
                            if Add_Emission:
                                links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0])
                            else:
                                links.new(shader.outputs[0], Add_Translucent.inputs[0])
                        except:
                            link_fail = True
    
                        shtsl.inputs['Color'].default_value = (cmat.translucency,
                                                               cmat.translucency,
                                                               cmat.translucency, 1)
                if sculpt_paint is False:
                    shader.inputs['Color'].default_value = (cmat.diffuse_color.r,
                                                            cmat.diffuse_color.g,
                                                            cmat.diffuse_color.b, 1)
                else:
                    # Create Clay Material (Diffuse, Glossy, Layer Weight)
                    shader.inputs['Color'].default_value = PAINT_SC_COLOR
                    shader.inputs['Roughness'].default_value = 0.9
    
                    # remove Color Ramp and links from the default shader and reroute
                    try:
    
                        shout.location = 400, 0
    
                        for link in links:
                            links.remove(link)
    
                        clay_frame = TreeNodes.nodes.new('NodeFrame')
                        clay_frame.name = 'Clay Material'
                        clay_frame.label = 'Clay Material'
    
                        sh_glossy = TreeNodes.nodes.new('ShaderNodeBsdfGlossy')
    
                        sh_glossy.location = 0, 200
    
                        sh_glossy.inputs['Color'].default_value = CLAY_GLOSSY
                        sh_mix = TreeNodes.nodes.new('ShaderNodeMixShader')
    
                        sh_mix.location = 200, 0
    
                        sh_weight = TreeNodes.nodes.new('ShaderNodeLayerWeight')
    
                        sh_weight.location = 0, 350
    
                        links.new(sh_mix.outputs[0], shout.inputs[0])
                        links.new(sh_weight.outputs[1], sh_mix.inputs[0])
                        links.new(shader.outputs[0], sh_mix.inputs[1])
                        links.new(sh_glossy.outputs[0], sh_mix.inputs[2])
                        # set frame as parent to everything
                        for clay_node in (shader, sh_glossy, sh_mix, sh_weight):
                            clay_node.parent = clay_frame
                    except:
                        collect_report("ERROR: Failure to create Clay Material")
    
                if not sculpt_paint:
                    if shader.type == 'ShaderNodeBsdfDiffuse':
                        shader.inputs['Roughness'].default_value = cmat.specular_intensity
    
                    if shader.type == 'ShaderNodeBsdfGlossy':
                        shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor
    
                    if shader.type == 'ShaderNodeBsdfGlass':
                        shader.inputs['Roughness'].default_value = 1 - cmat.raytrace_mirror.gloss_factor
                        shader.inputs['IOR'].default_value = cmat.raytrace_transparency.ior
    
                    if shader.type == 'ShaderNodeEmission':
                        shader.inputs['Strength'].default_value = cmat.emit
    
                # texture presence check
                is_textures = False
    
    
                for tex in cmat.texture_slots:
                    if tex:
    
                        if not is_textures:
                            is_textures = True
    
                if is_textures:
                    # collect the texture nodes created
    
                    # for spreading a bit the texture nodes
    
                    tex_node_collect = []
    
    
                    sM = True
    
                    for tex in cmat.texture_slots:
                        sT = False
                        tex_use = getattr(tex, "use", None)
    
                        baked_path = None
    
                            tex_node_loc = -200, 450
    
                            ma_alpha = getattr(tex, "use_map_alpha", None)
                            sM = (False if ma_alpha else True)
    
    
                            if tex.texture.type == 'IMAGE':
                                if sc.mat_specials.EXTRACT_ALPHA and tex.texture.use_alpha:
                                    if (not
                                       os_path.exists(bpy.path.abspath(tex.texture.image.filepath + "_BAKING.png")) or
                                       sc.mat_specials.EXTRACT_OW):
                                        baked_path = BakingText(tex, 'ALPHA')
    
                                            try:
                                                img = bpy.data.images.load(baked_path)
                                                collect_report("INFO: Loading Baked texture path:")
                                                collect_report(baked_path)
                                            except:
                                                collect_report("ERROR: Baked image could not be loaded")
                                else:
                                    has_image = getattr(tex.texture, "image", None)
                                    if has_image:
                                        img = has_image
    
                                if img:
                                    img_name = (img.name if hasattr(img, "name") else "NO NAME")
                                    shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
                                    shtext.location = tex_node_loc
    
                                    shtext.hide = True
                                    shtext.width_hidden = 150
    
                                    shtext.image = img
                                    shtext.name = img_name
                                    shtext.label = "Image " + img_name
                                    if baked_path:
                                        shtext.use_custom_color = True
                                        shtext.color = NODE_COLOR
                                    collect_report("INFO: Creating Image Node for image: " + img_name)
                                    tex_node_collect.append(shtext)
                                    sT = True
                                else:
                                    collect_report("ERROR: A problem occured with loading an image for {} "
                                                   "(possibly missing)".format(tex.texture.name))
    
                            else:
                                if sc.mat_specials.EXTRACT_PTEX or (sc.mat_specials.EXTRACT_ALPHA and ma_alpha):
                                    if (not os_path.exists(bpy.path.abspath(tex.texture.name + "_PTEXT.jpg")) or
                                       sc.mat_specials.EXTRACT_OW):
                                        tex_type = tex.texture.type.lower()
                                        collect_report("Attempting to Extract Procedural Texture type: " + tex_type)
                                        baked_path = BakingText(tex, 'PTEX', tex_type)
    
                                    if baked_path:
                                        try:
                                            img = bpy.data.images.load(baked_path)
                                            collect_report("Loading Baked texture path:")
                                            collect_report(baked_path)
                                            img_name = (img.name if hasattr(img, "name") else "NO NAME")
                                            shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
                                            shtext.location = tex_node_loc
    
                                            shtext.hide = True
                                            shtext.width_hidden = 150
    
                                            shtext.image = img
                                            shtext.name = img_name
                                            shtext.label = "Baked Image " + img_name
                                            shtext.use_custom_color = True
                                            shtext.color = NODE_COLOR
                                            collect_report("Creating Image Node for baked image: " + img_name)
    
                                            tex_node_collect.append(shtext)
    
                                            sT = True
                                        except:
                                            collect_report("ERROR: Failure to load baked image: " + img_name)
                                    else:
                                        collect_report("ERROR: Failure during baking, no images loaded")
    
    
                        if sculpt_paint is False:
                            if (cmat_is_transp and cmat.raytrace_transparency.ior == 1 and
                               not cmat.raytrace_mirror.use and sM):
    
                                if not shader.type == 'ShaderNodeBsdfTransparent':
                                    collect_report("INFO: Make TRANSPARENT shader node for: " + cmat.name)
                                    TreeNodes.nodes.remove(shader)
                                    shader = TreeNodes.nodes.new('ShaderNodeBsdfTransparent')
                                    shader.location = 0, 470
                                    try:
                                        links.new(shader.outputs[0], shout.inputs[0])
                                    except:
                                        link_fail = True
    
                                    shader.inputs['Color'].default_value = (cmat.diffuse_color.r,
                                                                            cmat.diffuse_color.g,
                                                                            cmat.diffuse_color.b, 1)
    
                        if sT and sculpt_paint is False:
    
                            if tex.use_map_color_diffuse:
    
                                    links.new(shtext.outputs[0], shader.inputs[0])
    
                            if tex.use_map_emit:
                                if not Add_Emission:
                                    collect_report("INFO: Mix EMISSION + Texture shader node for: " + cmat.name)
                                    intensity = 0.5 + (tex.emit_factor / 2)
    
                                    shout.location = 550, 330
                                    Add_Emission = TreeNodes.nodes.new('ShaderNodeAddShader')
                                    Add_Emission.name = "Add_Emission"
                                    Add_Emission.location = 370, 490
    
                                    shem = TreeNodes.nodes.new('ShaderNodeEmission')
                                    shem.location = 180, 380
    
    
                                    try:
                                        links.new(Add_Emission.outputs[0], shout.inputs[0])
                                        links.new(shem.outputs[0], Add_Emission.inputs[1])
                                        links.new(shader.outputs[0], Add_Emission.inputs[0])
                                        links.new(shtext.outputs[0], shem.inputs[0])
                                    except:
                                        link_fail = True
    
    
                                    shem.inputs['Color'].default_value = (cmat.diffuse_color.r,
                                                                          cmat.diffuse_color.g,
                                                                          cmat.diffuse_color.b, 1)
                                    shem.inputs['Strength'].default_value = intensity * 2
    
                            if tex.use_map_mirror:
    
                                    links.new(shtext.outputs[0], shader.inputs[0])
    
                                except:
                                    link_fail = True
    
    
                            if tex.use_map_translucency:
                                if not Add_Translucent:
                                    collect_report("INFO: Add Translucency + Texture shader node for: " + cmat.name)
    
                                    intensity = 0.5 + (tex.emit_factor / 2)
                                    shout.location = 550, 330
                                    Add_Translucent = TreeNodes.nodes.new('ShaderNodeAddShader')
                                    Add_Translucent.name = "Add_Translucent"
                                    Add_Translucent.location = 370, 290
    
                                    shtsl = TreeNodes.nodes.new('ShaderNodeBsdfTranslucent')
                                    shtsl.location = 180, 240
    
                                    try:
                                        links.new(shtsl.outputs[0], Add_Translucent.inputs[1])
    
                                        if Add_Emission:
                                            links.new(Add_Translucent.outputs[0], shout.inputs[0])
                                            links.new(Add_Emission.outputs[0], Add_Translucent.inputs[0])
                                            pass
                                        else:
                                            links.new(Add_Translucent.outputs[0], shout.inputs[0])
                                            links.new(shader.outputs[0], Add_Translucent.inputs[0])
    
                                        links.new(shtext.outputs[0], shtsl.inputs[0])
                                    except:
                                        link_fail = True
    
    
                            if tex.use_map_alpha:
                                if not Mix_Alpha:
                                    collect_report("INFO: Mix Alpha + Texture shader node for: " + cmat.name)
    
                                    shout.location = 750, 330
                                    Mix_Alpha = TreeNodes.nodes.new('ShaderNodeMixShader')
                                    Mix_Alpha.name = "Add_Alpha"
                                    Mix_Alpha.location = 570, 290
                                    sMask = TreeNodes.nodes.new('ShaderNodeBsdfTransparent')
                                    sMask.location = 250, 180
                                    tMask, imask = None, None
    
                                    # search if the texture node already exists, if not create
                                    nodes = getattr(cmat.node_tree, "nodes", None)
                                    img_name = getattr(img, "name", "NO NAME")
                                    for node in nodes:
                                        if type(node) == bpy.types.ShaderNodeTexImage:
                                            node_name = getattr(node, "name")
                                            if img_name in node_name:
                                                tMask = node
                                                collect_report("INFO: Using existing Texture Node for Mask: " + node_name)
                                                break
    
                                    if tMask is None:
                                        tMask = TreeNodes.nodes.new('ShaderNodeTexImage')
                                        tMask.location = tex_node_loc
    
                                        tex_node_collect.append(tMask)
    
    
                                        try:
                                            file_path = getattr(img, "filepath", None)
                                            if file_path:
                                                imask = bpy.data.images.load(file_path)
                                            else:
                                                imask = bpy.data.images.get(img_name)
                                            collect_report("INFO: Attempting to load image for Mask: " + img_name)
                                        except:
                                            collect_report("ERROR: Failure to load image for Mask: " + img_name)
    
                                        if imask:
                                            tMask.image = imask
    
                                    if tMask:
    
                                        try:
                                            links.new(Mix_Alpha.inputs[0], tMask.outputs[1])
                                            links.new(shout.inputs[0], Mix_Alpha.outputs[0])
                                            links.new(sMask.outputs[0], Mix_Alpha.inputs[1])
    
                                            if not Add_Translucent:
                                                if Add_Emission:
                                                    links.new(Mix_Alpha.inputs[2], Add_Emission.outputs[0])
                                                else:
                                                    links.new(Mix_Alpha.inputs[2], shader.outputs[0])
    
                                                links.new(Mix_Alpha.inputs[2], Add_Translucent.outputs[0])
                                        except:
                                            link_fail = True
    
                                    else:
                                        collect_report("ERROR: Mix Alpha could not be created "
                                                       "(mask image could not be loaded)")
    
                            if tex.use_map_normal:
                                t = TreeNodes.nodes.new('ShaderNodeRGBToBW')
                                t.location = -0, 300
    
                                try:
                                    links.new(t.outputs[0], shout.inputs[2])
                                    links.new(shtext.outputs[1], t.inputs[0])
                                except:
                                    link_fail = True
    
                    if sculpt_paint:
                        try:
                            # create a new image for texture painting and make it active
                            img_size = (int(sc.mat_specials.img_bake_size) if
                                        sc.mat_specials.img_bake_size else 1024)
                            paint_mat_name = getattr(cmat, "name", "NO NAME")
                            paint_img_name = "Paint Base Image {}".format(paint_mat_name)
                            bpy.ops.image.new(name=paint_img_name, width=img_size, height=img_size,
                                              color=(1.0, 1.0, 1.0, 1.0), alpha=True, float=False)
    
                            img = bpy.data.images.get(paint_img_name)
                            img_name = (img.name if hasattr(img, "name") else "NO NAME")
                            shtext = TreeNodes.nodes.new('ShaderNodeTexImage')
    
                            shtext.hide = True
                            shtext.width_hidden = 150
    
                            shtext.location = tex_node_loc
                            shtext.image = img
                            shtext.name = img_name
                            shtext.label = "Paint: " + img_name
                            shtext.use_custom_color = True
                            shtext.color = NODE_COLOR_PAINT
                            shtext.select = True
                            collect_report("INFO: Creating Image Node for Painting: " + img_name)
                            collect_report("WARNING: Don't forget to save it on Disk")
                            tex_node_collect.append(shtext)
                        except:
                            collect_report("ERROR: Failed to create image and node for Texture Painting")
    
    
                    # spread the texture nodes, create node frames if necessary
    
                    # create texture coordinate and mapping too
    
                    row_node = -1
                    tex_node_collect_size = len(tex_node_collect)
                    median_point = ((tex_node_collect_size / 2) * 100)
                    check_frame = bool(tex_node_collect_size > 1)
    
                    node_frame, tex_map = None, None
                    node_f_coord, node_f_mix = None, None
    
                    tex_map_collection, tex_map_coord = [], None
    
                    tree_size, tree_tex_start = 0, 0
    
    
                    if check_frame:
                        node_frame = TreeNodes.nodes.new('NodeFrame')
                        node_frame.name = 'Converter Textures'
                        node_frame.label = 'Converter Textures'
    
                        node_f_coord = TreeNodes.nodes.new('NodeFrame')
                        node_f_coord.name = "Coordinates"
                        node_f_coord.label = "Coordinates"
    
    
                        node_f_mix = TreeNodes.nodes.new('NodeFrame')
                        node_f_mix.name = "Mix"
                        node_f_mix.label = "Mix"
    
    
                    if tex_node_collect:
                        tex_map_coord = TreeNodes.nodes.new('ShaderNodeTexCoord')
                        tex_map_coord.location = -900, 575
    
    
                        # precalculate the depth of the inverted tree
                        tree_size = int(ceil(log2(tex_node_collect_size)))
                        # offset the start of the mix nodes by the depth of the tree
                        tree_tex_start = ((tree_size + 1) * 150)
    
    
                    for node_tex in tex_node_collect:
    
                        row_node += 1
                        col_node_start = (median_point - (-(row_node * 50) + median_point))
                        tex_node_row = tree_tex_start + 300
                        mix_node_row = tree_tex_start + 620
                        tex_node_loc = (-(tex_node_row), col_node_start)
    
    
                        try:
                            node_tex.location = tex_node_loc
                            if check_frame:
                                node_tex.parent = node_frame
    
                            else:
                                node_tex.hide = False
    
    
                            tex_node_name = getattr(node_tex, "name", "NO NAME")
                            tex_map_name = "Mapping: {}".format(tex_node_name)
                            tex_map = TreeNodes.nodes.new('ShaderNodeMapping')
    
                            tex_map.location = (-(mix_node_row), col_node_start)
    
                            tex_map.width = 240
                            tex_map.hide = True
                            tex_map.width_hidden = 150
                            tex_map.name = tex_map_name
                            tex_map.label = tex_map_name
                            tex_map_collection.append(tex_map)
                            links.new(tex_map.outputs[0], node_tex.inputs[0])
                        except:
                            link_fail = True
                            continue
    
                    if tex_map_collection:
    
                        tex_mix_start = len(tex_map_collection) / 2
                        row_map_start = -(tree_tex_start + 850)
    
    
                        if tex_map_coord:
    
                            tex_map_coord.location = (row_map_start,
                                                      (median_point - (tex_mix_start * 50)))
    
    
                        for maps in tex_map_collection:
                            try:
                                if node_f_coord:
                                    maps.parent = node_f_coord
    
    
                                links.new(maps.inputs[0], tex_map_coord.outputs['UV'])
                            except:
                                link_fail = True
                                continue
    
    
                    # create mix nodes to connect texture nodes to the shader input
                    # sculpt mode doesn't need them
                    if check_frame and not sculpt_paint:
                        mix_node_pairs = loop_node_from_list(TreeNodes, links, tex_node_collect,
                                                             0, tree_tex_start, median_point, node_f_mix)
    
                        for n in range(1, tree_size):
                            mix_node_pairs = loop_node_from_list(TreeNodes, links, mix_node_pairs,
                                                                 n, tree_tex_start, median_point, node_f_mix)
                        try:
                            for node in mix_node_pairs:
                                links.new(node.outputs[0], shader.inputs[0])
                        except:
                            link_fail = True
    
                        mix_node_pairs = []
    
    
                    tex_node_collect, tex_map_collection = [], []
    
                    if link_fail:
                        collect_report("ERROR: Some of the node links failed to connect")
    
    
                else:
                    collect_report("No textures in the Scene, no Image Nodes to add")
    
        bpy.context.scene.render.engine = 'CYCLES'
    
    
    
    def loop_node_from_list(TreeNodes, links, node_list, loc, start, median_point, frame):
        row = 1
        mix_nodes = []
        node_list_size = len(node_list)
        tuplify = [tuple(node_list[s:s + 2]) for s in range(0, node_list_size, 2)]
        for nodes in tuplify:
            row += 1
            create_mix = create_mix_node(TreeNodes, links, nodes, loc, start,
                                         median_point, row, frame)
            if create_mix:
                mix_nodes.append(create_mix)
        return mix_nodes
    
    
    def create_mix_node(TreeNodes, links, nodes, loc, start, median_point, row, frame):
        mix_node = TreeNodes.nodes.new('ShaderNodeMixRGB')
        mix_node.name = "MIX level: " + str(loc)
        mix_node.label = "MIX level: " + str(loc)
        mix_node.use_custom_color = True
        mix_node.color = NODE_COLOR_MIX
        mix_node.hide = True
        mix_node.width_hidden = 75
    
        if frame:
            mix_node.parent = frame
        mix_node.location = -(start - loc * 175), ((median_point / 4) + (row * 50))
    
        try:
            if len(nodes) > 1:
                links.new(nodes[0].outputs[0], mix_node.inputs["Color2"])
                links.new(nodes[1].outputs[0], mix_node.inputs["Color1"])
            elif len(nodes) == 1:
                links.new(nodes[0].outputs[0], mix_node.inputs["Color1"])
        except:
            collect_report("ERROR: Link failed for mix node {}".format(mix_node.label))
        return mix_node
    
    
    
    # -----------------------------------------------------------------------------
    # Operator Classes #
    
    class mllock(Operator):
        bl_idname = "ml.lock"
        bl_label = "Lock"
        bl_description = "Lock/unlock this material against modification by conversions"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            return (c_is_cycles_addon_enabled() and c_data_has_materials())
    
        def execute(self, context):
            cmat = bpy.context.selected_objects[0].active_material
            TreeNodes = cmat.node_tree
            for n in TreeNodes.nodes:
                if n.type == 'ShaderNodeOutputMaterial':
                    if n.label == 'Locked':
                        n.label = ''
                    else:
                        n.label = 'Locked'
            return {'FINISHED'}
    
    
    class mlrefresh(Operator):
        bl_idname = "ml.refresh"
        bl_label = "Convert All Materials"
        bl_description = "Convert All Materials in the scene from non-nodes to Cycles"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            return (c_is_cycles_addon_enabled() and c_data_has_materials())
    
        def execute(self, context):
            AutoNodeInitiate(False, self)
    
            if CHECK_AUTONODE is True:
    
                enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP
                if enable_unwrap:
                    obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT")
                    try:
                        # it's possible to the active object would fail UV Unwrap
                        bpy.ops.object.editmode_toggle()
                        bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
                        bpy.ops.object.editmode_toggle()
                        collect_report("INFO: UV Unwrapping active object "
                                       "{}".format(obj_name))
                    except:
                        collect_report("ERROR: UV Unwrapping failed for "
                                       "active object {}".format(obj_name))
    
            collect_report("Conversion finished !", False, True)
    
            return {'FINISHED'}
    
    
    class mlrefresh_active(Operator):
        bl_idname = "ml.refresh_active"
        bl_label = "Convert All Materials From Active Object"
        bl_description = "Convert all Active Object's Materials from non-nodes to Cycles"
        bl_options = {'REGISTER', 'UNDO'}
    
        @classmethod
        def poll(cls, context):
            return (c_is_cycles_addon_enabled() and c_data_has_materials() and
                    context.active_object is not None)
    
        def execute(self, context):
            AutoNodeInitiate(True, self)
            if CHECK_AUTONODE is True:
    
                obj_name = getattr(context.active_object, "name", "UNNAMED OBJECT")
                enable_unwrap = bpy.context.scene.mat_specials.UV_UNWRAP
                if enable_unwrap:
                    try:
                        # you can already guess it, what could happen here
                        bpy.ops.object.editmode_toggle()
                        bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.001)
                        bpy.ops.object.editmode_toggle()
                        collect_report("INFO: UV Unwrapping object {}".format(obj_name))
                    except:
                        collect_report("ERROR: UV Unwrapping failed for "
                                       "object {}".format(obj_name))
    
            collect_report("Conversion finished !", False, True)
    
            return {'FINISHED'}
    
    
    class mlrestore(Operator):
        bl_idname = "ml.restore"
        bl_label = "Switch Between Renderers"
        bl_description = ("Switch between Renderers \n"
                          "(Doesn't create new nor converts existing materials)")
        bl_options = {'REGISTER', 'UNDO'}
    
        switcher = BoolProperty(
                name="Use Nodes",
                description="When restoring, switch Use Nodes On/Off",
                default=True
                )
        renderer = EnumProperty(
                name="Renderer",
                description="Choose Cycles or Blender Internal",
                items=(('CYCLES', "Cycles", "Switch to Cycles"),
                       ('BI', "Blender Internal", "Switch to Blender Internal")),
                default='CYCLES',
                )
    
        @classmethod
        def poll(cls, context):
            return c_is_cycles_addon_enabled()
    
        def execute(self, context):
            if self.switcher:
                AutoNodeSwitch(self.renderer, "ON", self)
            else:
                AutoNodeSwitch(self.renderer, "OFF", self)
            return {'FINISHED'}
    
    
    def register():
        bpy.utils.register_module(__name__)
        pass
    
    
    def unregister():
        bpy.utils.unregister_module(__name__)
        pass
    
    if __name__ == "__main__":
        register()