# ##### 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 #####

bl_info = {
    "name": "Amaranth Toolset",
    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne",
    "version": (0, 9, 4),
    "blender": (2, 70),
    "location": "Everywhere!",
    "description": "A collection of tools and settings to improve productivity",
    "warning": "",
    "wiki_url": "http://pablovazquez.org/amaranth",
    "tracker_url": "",
    "category": "Scene"}


import bpy
import bmesh
from bpy.types import Operator, AddonPreferences, Panel, Menu
from bpy.props import (BoolProperty, EnumProperty,
                       FloatProperty, FloatVectorProperty,
                       IntProperty, StringProperty)
from mathutils import Vector
from bpy.app.handlers import persistent
from bl_operators.presets import AddPresetBase

# Addon wide, we need to know if cycles is available
global cycles_exists
cycles_exists = 'cycles' in dir(bpy.types.Scene)

# Preferences
class AmaranthToolsetPreferences(AddonPreferences):
    bl_idname = __name__
    use_frame_current = BoolProperty(
            name="Current Frame Slider",
            description="Set the current frame from the Specials menu in the 3D View",
            default=True,
            )
    use_file_save_reload = BoolProperty(
            name="Save & Reload File",
            description="File menu > Save & Reload, or Ctrl + Shift + W",
            default=True,
            )

    use_scene_refresh = BoolProperty(
            name="Refresh Scene",
            description="Specials Menu [W], or hit F5",
            default=True,
            )
    use_timeline_extra_info = BoolProperty(
            name="Timeline Extra Info",
            description="Timeline Header",
            default=True,
            )
    use_image_node_display = BoolProperty(
            name="Active Image Node in Editor",
            description="Display active node image in image editor",
            default=True,
            )
    use_scene_stats = BoolProperty(
            name="Extra Scene Statistics",
            description="Display extra scene statistics in Info editor's header",
            default=True,
            )

    frames_jump = IntProperty(
                name="Frames",
                description="Number of frames to jump forward/backward",
                default=10,
                min=1)

    use_layers_for_render = BoolProperty(
            name="Current Layers for Render",
            description="Save the layers that should be enabled for render",
            default=True,
            )


    def draw(self, context):
        layout = self.layout

        layout.label(
            text="Here you can enable or disable specific tools, "
                 "in case they interfere with others or are just plain annoying")

        split = layout.split(percentage=0.25)

        col = split.column()
        sub = col.column(align=True)
        sub.label(text="3D View", icon="VIEW3D")
        sub.prop(self, "use_frame_current")
        sub.prop(self, "use_scene_refresh")

        sub.separator()

        sub.label(text="General", icon="SCENE_DATA")
        sub.prop(self, "use_file_save_reload")
        sub.prop(self, "use_timeline_extra_info")
        sub.prop(self, "use_scene_stats")
        sub.prop(self, "use_layers_for_render")

        sub.separator()

        sub.label(text="Nodes Editor", icon="NODETREE")
        sub.prop(self, "use_image_node_display")

        col = split.column()
        sub = col.column(align=True)
        sub.label(text="")
        sub.label(
            text="Set the current frame from the Specials menu in the 3D View [W]")
        sub.label(
            text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")

        sub.separator()
        sub.label(text="") # General
        sub.label(
            text="Quickly save and reload the current file (no warning!). "
                 "File menu or Ctrl+Shift+W")
        sub.label(
            text="SMPTE Timecode and frames left/ahead on Timeline's header")
        sub.label(
            text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")

        sub.separator()
        sub.label(text="") # Nodes
        sub.label(
            text="When selecting an Image node, display it on the Image editor "
                 "(if any)")

# Properties
def init_properties():

    scene = bpy.types.Scene
    node = bpy.types.Node
    nodes_compo = bpy.types.CompositorNodeTree

    scene.use_unsimplify_render = BoolProperty(
        default=False,
        name="Unsimplify Render",
        description="Disable Simplify during render")
    scene.simplify_status = BoolProperty(default=False)

    node.use_matching_indices = BoolProperty(
        default=True,
        description="If disabled, display all available indices")

    nodes_compo_types = [
        ("ALL", "All Types", "", 0),
        ("BLUR", "Blur", "", 1),
        ("BOKEHBLUR", "Bokeh Blur", "", 2),
        ("VECBLUR", "Vector Blur", "", 3),
        ("DEFOCUS", "Defocus", "", 4),
        ("R_LAYERS", "Render Layer", "", 5)
        ]

    nodes_compo.types = EnumProperty(
        items=nodes_compo_types, name = "Types")

    nodes_compo.toggle_mute = BoolProperty(default=False)
    node.status = BoolProperty(default=False)

    # Scene Debug
    # Cycles Node Types
    if cycles_exists:
        cycles_shader_node_types = [
            ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
            ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
            ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
            ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
            ("BSDF_GLASS", "Glass BSDF", "", 4),
            ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
            ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
            ("BSDF_VELVET", "Velvet BSDF", "", 7),
            ("BSDF_TOON", "Toon BSDF", "", 8),
            ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
            ("EMISSION", "Emission", "", 10),
            ("BSDF_HAIR", "Hair BSDF", "", 11),
            ("BACKGROUND", "Background", "", 12),
            ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
            ("HOLDOUT", "Holdout", "", 14),
            ("VOLUME_ABSORPTION", "Volume Absorption", "", 15),
            ("VOLUME_SCATTER", "Volume Scatter", "", 16)
            ]

        scene.amaranth_cycles_node_types = EnumProperty(
            items=cycles_shader_node_types, name = "Shader")

        scene.amaranth_cycles_list_sampling = BoolProperty(
            default=False,
            name="Samples Per:")

        bpy.types.CyclesRenderSettings.use_samples_final = BoolProperty(
            name="Use Final Render Samples",
            description="Use current shader samples as final render samples",
            default=False)

    scene.amaranth_lighterscorner_list_meshlights = BoolProperty(
        default=False,
        name="List Meshlights",
        description="Include light emitting meshes on the list")

    scene.amaranth_debug_scene_list_missing_images = BoolProperty(
        default=False,
        name="List Missing Images",
        description="Display a list of all the missing images")

    bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
    bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector

    bpy.types.Object.is_keyframe = is_keyframe

    scene.amth_wire_toggle_scene_all = BoolProperty(
        default=False,
        name="All Scenes",
        description="Toggle wire on objects in all scenes")
    scene.amth_wire_toggle_is_selected = BoolProperty(
        default=False,
        name="Only Selected",
        description="Only toggle wire on selected objects")
    scene.amth_wire_toggle_edges_all = BoolProperty(
        default=True,
        name="All Edges",
        description="Draw all edges")
    scene.amth_wire_toggle_optimal = BoolProperty(
        default=False,
        name="Optimal Display",
        description="Skip drawing/rendering of interior subdivided edges "
                    "on meshes with Subdivision Surface modifier")

def clear_properties():
    props = (
        "use_unsimplify_render",
        "simplify_status",
        "use_matching_indices",
        "use_simplify_nodes_vector",
        "status",
        "types",
        "toggle_mute",
        "amaranth_cycles_node_types",
        "amaranth_lighterscorner_list_meshlights",
        "amaranth_debug_scene_list_missing_images",
        "amarath_cycles_list_sampling",
        "normal_vector",
        "use_samples_final",
        'amth_wire_toggle_is_selected',
        'amth_wire_toggle_scene_all',
        "amth_wire_toggle_edges_all",
        "amth_wire_toggle_optimal"
    )
    
    wm = bpy.context.window_manager
    for p in props:
        if p in wm:
            del wm[p]

# Some settings are bound to be saved on a startup py file
def amaranth_text_startup(context):

    amth_text_name = "AmaranthStartup.py"
    amth_text_exists = False

    global amth_text

    try:
        if bpy.data.texts:
            for tx in bpy.data.texts:
                if tx.name == amth_text_name:
                    amth_text_exists = True
                    amth_text = bpy.data.texts[amth_text_name]
                    break
                else:
                    amth_text_exists = False

        if not amth_text_exists:
            bpy.ops.text.new()
            amth_text = bpy.data.texts[-1]
            amth_text.name = amth_text_name
            amth_text.write("# Amaranth Startup Script\nimport bpy\n\n")
            amth_text.use_module = True

        return amth_text_exists
    except AttributeError:
        return None

# FUNCTION: Check if material has Emission (for select and stats)
def cycles_is_emission(context, ob):

    is_emission = False

    if ob.material_slots:
        for ma in ob.material_slots:
            if ma.material:
                if ma.material.node_tree and ma.material.node_tree.nodes:
                    for no in ma.material.node_tree.nodes:
                        if no.type in {'EMISSION', 'GROUP'}:
                            for ou in no.outputs:
                                if ou.links:
                                    if no.type == 'GROUP' and no.node_tree and no.node_tree.nodes:
                                        for gno in no.node_tree.nodes:
                                            if gno.type == 'EMISSION':
                                                for gou in gno.outputs:
                                                    if ou.links and gou.links:
                                                        is_emission = True

                                    elif no.type == 'EMISSION':
                                        if ou.links:
                                            is_emission = True
    return is_emission

# FUNCTION: Check if object has keyframes for a specific frame
def is_keyframe(ob, frame):
    if ob is not None and ob.animation_data is not None and ob.animation_data.action is not None:
        for fcu in ob.animation_data.action.fcurves:
            if frame in (p.co.x for p in fcu.keyframe_points):
                return True
    return False

# FEATURE: Refresh Scene!
class AMTH_SCENE_OT_refresh(Operator):
    """Refresh the current scene"""
    bl_idname = "scene.refresh"
    bl_label = "Refresh!"
    
    def execute(self, context):
        preferences = context.user_preferences.addons[__name__].preferences
        scene = context.scene

        if preferences.use_scene_refresh:    
            # Changing the frame is usually the best way to go
            scene.frame_current = scene.frame_current
            self.report({"INFO"}, "Scene Refreshed!")
            
        return {'FINISHED'}

def button_refresh(self, context):

    preferences = context.user_preferences.addons[__name__].preferences

    if preferences.use_scene_refresh:
        self.layout.separator()
        self.layout.operator(
            AMTH_SCENE_OT_refresh.bl_idname,
            text="Refresh!",
            icon='FILE_REFRESH')
# // FEATURE: Refresh Scene!

# FEATURE: Save & Reload
def save_reload(self, context, path):

    if path:
        bpy.ops.wm.save_mainfile()
        self.report({'INFO'}, "Saved & Reloaded")
        bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
    else:
        bpy.ops.wm.save_as_mainfile("INVOKE_AREA")

class AMTH_WM_OT_save_reload(Operator):
    """Save and Reload the current blend file"""
    bl_idname = "wm.save_reload"
    bl_label = "Save & Reload"

    def execute(self, context):

        path = bpy.data.filepath
        save_reload(self, context, path)
        return {'FINISHED'}

def button_save_reload(self, context):

    preferences = context.user_preferences.addons[__name__].preferences

    if preferences.use_file_save_reload:
        self.layout.separator()
        self.layout.operator(
            AMTH_WM_OT_save_reload.bl_idname,
            text="Save & Reload",
            icon='FILE_REFRESH')
# // FEATURE: Save & Reload

# FEATURE: Current Frame
def button_frame_current(self, context):

    preferences = context.user_preferences.addons[__name__].preferences
    scene = context.scene

    if preferences.use_frame_current:
        self.layout.separator()
        self.layout.prop(
            scene, "frame_current",
            text="Set Current Frame")
# // FEATURE: Current Frame

# FEATURE: Timeline Time + Frames Left
def label_timeline_extra_info(self, context):

    preferences = context.user_preferences.addons[__name__].preferences
    layout = self.layout
    scene = context.scene

    if preferences.use_timeline_extra_info:
        row = layout.row(align=True)

        row.operator(AMTH_SCREEN_OT_keyframe_jump_inbetween.bl_idname, icon="PREV_KEYFRAME", text="").backwards = True
        row.operator(AMTH_SCREEN_OT_keyframe_jump_inbetween.bl_idname, icon="NEXT_KEYFRAME", text="").backwards = False

        # Check for preview range
        frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
        frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
        
        row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
                        bpy.utils.smpte_from_frame(frame_end - frame_start)))

        if (scene.frame_current > frame_end):
            row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
        elif (scene.frame_current == frame_start):
            row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
        elif (scene.frame_current == frame_end):
            row.label(text="%s End Frame" % scene.frame_current)
        else:
            row.label(text="%s Frames Left" % (frame_end - scene.frame_current))

# // FEATURE: Timeline Time + Frames Left

# FEATURE: Directory Current Blend
class AMTH_FILE_OT_directory_current_blend(Operator):
    """Go to the directory of the currently open blend file"""
    bl_idname = "file.directory_current_blend"
    bl_label = "Current Blend's Folder"

    def execute(self, context):
        bpy.ops.file.select_bookmark(dir='//')
        return {'FINISHED'}

def button_directory_current_blend(self, context):

    if bpy.data.filepath:
        self.layout.operator(
            AMTH_FILE_OT_directory_current_blend.bl_idname,
            text="Current Blend's Folder",
            icon='APPEND_BLEND')
# // FEATURE: Directory Current Blend

# FEATURE: Libraries panel on file browser
class AMTH_FILE_PT_libraries(Panel):
    bl_space_type = 'FILE_BROWSER'
    bl_region_type = 'CHANNELS'
    bl_label = "Libraries"

    def draw(self, context):
        layout = self.layout

        libs = bpy.data.libraries
        libslist = []

        # Build the list of folders from libraries
        import os.path

        for lib in libs:
            directory_name = os.path.dirname(lib.filepath)
            libslist.append(directory_name)

        # Remove duplicates and sort by name
        libslist = set(libslist)
        libslist = sorted(libslist)

        # Draw the box with libs

        row = layout.row()
        box = row.box()

        if libslist:
            col = box.column()
            for filepath in libslist:
                if filepath != '//':
                    row = col.row()
                    row.alignment = 'LEFT'
                    props = row.operator(
                        AMTH_FILE_OT_directory_go_to.bl_idname,
                        text=filepath, icon="BOOKMARKS",
                        emboss=False)
                    props.filepath = filepath
        else:
            box.label(text='No libraries loaded')

class AMTH_FILE_OT_directory_go_to(Operator):
    """Go to this library's directory"""
    bl_idname = "file.directory_go_to"
    bl_label = "Go To"

    filepath = bpy.props.StringProperty(subtype="FILE_PATH")

    def execute(self, context):
        bpy.ops.file.select_bookmark(dir=self.filepath)
        return {'FINISHED'}

# FEATURE: Node Templates
class AMTH_NODE_OT_AddTemplateVignette(Operator):
    bl_idname = "node.template_add_vignette"
    bl_label = "Add Vignette"
    bl_description = "Add a vignette effect"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        space = context.space_data
        return space.type == 'NODE_EDITOR' \
                and space.node_tree is not None \
                and space.tree_type == 'CompositorNodeTree'

    # used as reference the setup scene script from master nazgul
    def _setupNodes(self, context):
        scene = context.scene
        space = context.space_data
        tree = scene.node_tree
        has_act = True if tree.nodes.active else False

        bpy.ops.node.select_all(action='DESELECT')

        ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
        ellipse.width = 0.8
        ellipse.height = 0.4
        blur = tree.nodes.new(type='CompositorNodeBlur')
        blur.use_relative = True
        blur.factor_x = 30
        blur.factor_y = 50
        ramp = tree.nodes.new(type='CompositorNodeValToRGB')
        ramp.color_ramp.interpolation = 'B_SPLINE'
        ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)

        overlay = tree.nodes.new(type='CompositorNodeMixRGB')
        overlay.blend_type = 'OVERLAY'
        overlay.inputs[0].default_value = 0.8
        overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)

        tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
        tree.links.new(blur.outputs["Image"],ramp.inputs[0])
        tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
        if has_act:
            tree.links.new(tree.nodes.active.outputs[0],overlay.inputs[1])

        if has_act:
            overlay.location = tree.nodes.active.location
            overlay.location += Vector((350.0, 0.0))
        else:
            overlay.location += Vector((space.cursor_location[0], space.cursor_location[1]))

        ellipse.location = overlay.location
        ellipse.location += Vector((-715.0, -400))
        ellipse.inputs[0].hide = True
        ellipse.inputs[1].hide = True

        blur.location = ellipse.location
        blur.location += Vector((300.0, 0.0))
        blur.inputs['Size'].hide = True

        ramp.location = blur.location
        ramp.location += Vector((175.0, 0))
        ramp.outputs['Alpha'].hide = True

        for node in {ellipse, blur, ramp, overlay}:
            node.select = True
            node.show_preview = False

        bpy.ops.node.join()

        frame = ellipse.parent
        frame.label = 'Vignette'
        frame.use_custom_color = True
        frame.color = (0.1, 0.1, 0.1)
        
        overlay.parent = None
        overlay.label = 'Vignette Overlay'

    def execute(self, context):
        self._setupNodes(context)

        return {'FINISHED'}

class AMTH_NODE_OT_AddTemplateVectorBlur(Operator):
    bl_idname = "node.template_add_vectorblur"
    bl_label = "Add Vector Blur"
    bl_description = "Add a vector blur filter"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        space = context.space_data
        tree = context.scene.node_tree
        return space.type == 'NODE_EDITOR' \
                and space.node_tree is not None \
                and space.tree_type == 'CompositorNodeTree' \
                and tree \
                and tree.nodes.active \
                and tree.nodes.active.type == 'R_LAYERS'

    def _setupNodes(self, context):
        scene = context.scene
        space = context.space_data
        tree = scene.node_tree

        bpy.ops.node.select_all(action='DESELECT')

        act_node = tree.nodes.active
        rlayer = act_node.scene.render.layers[act_node.layer]

        if not rlayer.use_pass_vector:
            rlayer.use_pass_vector = True

        vblur = tree.nodes.new(type='CompositorNodeVecBlur')
        vblur.use_curved = True
        vblur.factor = 0.5

        tree.links.new(act_node.outputs["Image"],vblur.inputs["Image"])
        tree.links.new(act_node.outputs["Z"],vblur.inputs["Z"])
        tree.links.new(act_node.outputs["Speed"],vblur.inputs["Speed"])

        if tree.nodes.active:
            vblur.location = tree.nodes.active.location
            vblur.location += Vector((250.0, 0.0))
        else:
            vblur.location += Vector((space.cursor_location[0], space.cursor_location[1]))

        vblur.select = True

    def execute(self, context):
        self._setupNodes(context)

        return {'FINISHED'}

# Node Templates Menu
class AMTH_NODE_MT_amaranth_templates(Menu):
    bl_idname = 'AMTH_NODE_MT_amaranth_templates'
    bl_space_type = 'NODE_EDITOR'
    bl_label = "Templates"
    bl_description = "List of Amaranth Templates"

    def draw(self, context):
        layout = self.layout
        layout.operator(
            AMTH_NODE_OT_AddTemplateVectorBlur.bl_idname,
            text="Vector Blur",
            icon='FORCE_HARMONIC')
        layout.operator(
            AMTH_NODE_OT_AddTemplateVignette.bl_idname,
            text="Vignette",
            icon='COLOR')

def node_templates_pulldown(self, context):
    if context.space_data.tree_type == 'CompositorNodeTree':
        layout = self.layout
        row = layout.row(align=True)
        row.scale_x = 1.3
        row.menu("AMTH_NODE_MT_amaranth_templates",
            icon="NODETREE")
# // FEATURE: Node Templates

def node_stats(self,context):
    if context.scene.node_tree:
        tree_type = context.space_data.tree_type
        nodes = context.scene.node_tree.nodes
        nodes_total = len(nodes.keys())
        nodes_selected = 0
        for n in nodes:
            if n.select:
                nodes_selected = nodes_selected + 1

        if tree_type == 'CompositorNodeTree':
            layout = self.layout
            row = layout.row(align=True)
            row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))

# FEATURE: Simplify Compo Nodes
class AMTH_NODE_PT_simplify(Panel):
    '''Simplify Compositor Panel'''
    bl_space_type = 'NODE_EDITOR'
    bl_region_type = 'UI'
    bl_label = 'Simplify'
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        space = context.space_data
        return space.type == 'NODE_EDITOR' \
                and space.node_tree is not None \
                and space.tree_type == 'CompositorNodeTree'

    def draw(self, context):
        layout = self.layout
        node_tree = context.scene.node_tree

        if node_tree is not None:
            layout.prop(node_tree, 'types')
            layout.operator(AMTH_NODE_OT_toggle_mute.bl_idname,
                text="Turn On" if node_tree.toggle_mute else "Turn Off",
                icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
        
            if node_tree.types == 'VECBLUR':
                layout.label(text="This will also toggle the Vector pass {}".format(
                                    "on" if node_tree.toggle_mute else "off"), icon="INFO")

class AMTH_NODE_OT_toggle_mute(Operator):
    """"""
    bl_idname = "node.toggle_mute"
    bl_label = "Toggle Mute"

    def execute(self, context):
        scene = context.scene
        node_tree = scene.node_tree
        node_type = node_tree.types
        rlayers = scene.render
        
        if not 'amaranth_pass_vector' in scene.keys():
            scene['amaranth_pass_vector'] = []
        
        #can't extend() the list, so make a dummy one
        pass_vector = scene['amaranth_pass_vector']

        if not pass_vector:
            pass_vector = []

        if node_tree.toggle_mute:
            for node in node_tree.nodes:
                if node_type == 'ALL':
                    node.mute = node.status
                if node.type == node_type:
                    node.mute = node.status
                if node_type == 'VECBLUR':
                    for layer in rlayers.layers:
                        if layer.name in pass_vector:
                            layer.use_pass_vector = True
                            pass_vector.remove(layer.name)

                node_tree.toggle_mute = False

        else:
            for node in node_tree.nodes:
                if node_type == 'ALL':
                    node.mute = True
                if node.type == node_type:
                    node.status = node.mute
                    node.mute = True
                if node_type == 'VECBLUR':
                    for layer in rlayers.layers:
                        if layer.use_pass_vector:
                            pass_vector.append(layer.name)
                            layer.use_pass_vector = False
                            pass

                node_tree.toggle_mute = True

        # Write back to the custom prop
        pass_vector = sorted(set(pass_vector))
        scene['amaranth_pass_vector'] = pass_vector

        return {'FINISHED'}
        

# FEATURE: OB/MA ID panel in Node Editor
class AMTH_NODE_PT_indices(Panel):
    '''Object / Material Indices Panel'''
    bl_space_type = 'NODE_EDITOR'
    bl_region_type = 'UI'
    bl_label = 'Object / Material Indices'
    bl_options = {'DEFAULT_CLOSED'}

    @classmethod
    def poll(cls, context):
        node = context.active_node
        return node and node.type == 'ID_MASK'

    def draw(self, context):
        layout = self.layout

        objects = bpy.data.objects
        materials = bpy.data.materials
        node = context.active_node

        show_ob_id = False
        show_ma_id = False
        matching_ids = False

        if context.active_object:
            ob_act = context.active_object
        else:
            ob_act = False

        for ob in objects:
            if ob and ob.pass_index > 0:
                show_ob_id = True
        for ma in materials:
            if ma and ma.pass_index > 0:
                show_ma_id = True
        row = layout.row(align=True)  
        row.prop(node, 'index', text="Mask Index")
        row.prop(node, 'use_matching_indices', text="Only Matching IDs")
        
        layout.separator()

        if not show_ob_id and not show_ma_id:
            layout.label(text="No objects or materials indices so far.", icon="INFO")

        if show_ob_id:
            split = layout.split()
            col = split.column()
            col.label(text="Object Name")
            split.label(text="ID Number")
            row = layout.row()
            for ob in objects:
                icon = "OUTLINER_DATA_" + ob.type
                if ob.library:
                    icon = "LIBRARY_DATA_DIRECT"
                elif ob.is_library_indirect:
                    icon = "LIBRARY_DATA_INDIRECT"

                if ob and node.use_matching_indices \
                      and ob.pass_index == node.index \
                      and ob.pass_index != 0:
                    matching_ids = True
                    row.label(
                      text="[{}]".format(ob.name)
                          if ob_act and ob.name == ob_act.name else ob.name,
                      icon=icon)
                    row.label(text="%s" % ob.pass_index)
                    row = layout.row()

                elif ob and not node.use_matching_indices \
                        and ob.pass_index > 0:

                    matching_ids = True
                    row.label(
                      text="[{}]".format(ob.name)
                          if ob_act and ob.name == ob_act.name else ob.name,
                      icon=icon)
                    row.label(text="%s" % ob.pass_index)
                    row = layout.row()

            if node.use_matching_indices and not matching_ids:
                row.label(text="No objects with ID %s" % node.index, icon="INFO")

            layout.separator()

        if show_ma_id:
            split = layout.split()
            col = split.column()
            col.label(text="Material Name")
            split.label(text="ID Number")
            row = layout.row()

            for ma in materials:
                icon = "BLANK1"
                if ma.use_nodes:
                    icon = "NODETREE"
                elif ma.library:
                    icon = "LIBRARY_DATA_DIRECT"
                    if ma.is_library_indirect:
                        icon = "LIBRARY_DATA_INDIRECT"

                if ma and node.use_matching_indices \
                      and ma.pass_index == node.index \
                      and ma.pass_index != 0:
                    matching_ids = True
                    row.label(text="%s" % ma.name, icon=icon)
                    row.label(text="%s" % ma.pass_index)
                    row = layout.row()

                elif ma and not node.use_matching_indices \
                        and ma.pass_index > 0:

                    matching_ids = True
                    row.label(text="%s" % ma.name, icon=icon)
                    row.label(text="%s" % ma.pass_index)
                    row = layout.row()

            if node.use_matching_indices and not matching_ids:
                row.label(text="No materials with ID %s" % node.index, icon="INFO")


# // FEATURE: OB/MA ID panel in Node Editor

# FEATURE: Unsimplify on render
@persistent
def unsimplify_render_pre(scene):
    render = scene.render
    scene.simplify_status = render.use_simplify

    if scene.use_unsimplify_render:
        render.use_simplify = False

@persistent
def unsimplify_render_post(scene):
    render = scene.render
    render.use_simplify = scene.simplify_status

def unsimplify_ui(self,context):
    scene = bpy.context.scene
    self.layout.prop(scene, 'use_unsimplify_render')
# //FEATURE: Unsimplify on render

# FEATURE: Extra Info Stats
def stats_scene(self, context):

    preferences = context.user_preferences.addons[__name__].preferences

    if preferences.use_scene_stats:
        scenes_count = str(len(bpy.data.scenes))
        cameras_count = str(len(bpy.data.cameras))
        cameras_selected = 0
        meshlights = 0
        meshlights_visible = 0

        for ob in context.scene.objects:
            if cycles_is_emission(context, ob):
                meshlights += 1
                if ob in context.visible_objects:
                    meshlights_visible += 1

            if ob in context.selected_objects:
                if ob.type == 'CAMERA':
                    cameras_selected += 1
    
        meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
    
        row = self.layout.row(align=True)
        row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
                   scenes_count, cameras_selected, cameras_count,
                   meshlights_string if context.scene.render.engine == 'CYCLES' else ''))

# //FEATURE: Extra Info Stats

# FEATURE: Camera Bounds as Render Border
class AMTH_VIEW3D_OT_render_border_camera(Operator):
    """Set camera bounds as render border"""
    bl_idname = "view3d.render_border_camera"
    bl_label = "Camera as Render Border"

    @classmethod
    def poll(cls, context):
        return context.space_data.region_3d.view_perspective == 'CAMERA'

    def execute(self, context):
        render = context.scene.render
        render.use_border = True
        render.border_min_x = 0
        render.border_min_y = 0
        render.border_max_x = 1
        render.border_max_y = 1

        return {'FINISHED'}

def button_render_border_camera(self, context):

    view3d = context.space_data.region_3d
    
    if view3d.view_perspective == 'CAMERA':
        layout = self.layout
        layout.separator()
        layout.operator(AMTH_VIEW3D_OT_render_border_camera.bl_idname,
                        text="Camera as Render Border", icon="FULLSCREEN_ENTER")

# //FEATURE: Camera Bounds as Render Border

# FEATURE: Passepartout options on W menu
def button_camera_passepartout(self, context):

    view3d = context.space_data.region_3d
    cam = context.scene.camera.data
    
    if view3d.view_perspective == 'CAMERA':
        layout = self.layout
        if cam.show_passepartout:
            layout.prop(cam, "passepartout_alpha", text="Passepartout")
        else:
            layout.prop(cam, "show_passepartout")

# FEATURE: Show Only Render with Alt+Shift+Z
class AMTH_VIEW3D_OT_show_only_render(Operator):
    bl_idname = "view3d.show_only_render"
    bl_label = "Show Only Render"

    def execute(self, context):
        space = bpy.context.space_data
        
        if space.show_only_render:
            space.show_only_render = False
        else:
            space.show_only_render = True
        return {'FINISHED'}


# FEATURE: Display Active Image Node on Image Editor
# Made by Sergey Sharybin, tweaks from Bassam Kurdali
image_nodes = {"CompositorNodeImage",
               "ShaderNodeTexImage",
               "ShaderNodeTexEnvironment"}

class AMTH_NODE_OT_show_active_node_image(Operator):
    """Show active image node image in the image editor"""
    bl_idname = "node.show_active_node_image"
    bl_label = "Show Active Node Node"
    bl_options = {'UNDO'}

    def execute(self, context):
        preferences = context.user_preferences.addons[__name__].preferences
        if preferences.use_image_node_display:
            if context.active_node:
                active_node = context.active_node
                if active_node.bl_idname in image_nodes and active_node.image:
                    for area in context.screen.areas:
                        if area.type == "IMAGE_EDITOR":
                            for space in area.spaces:
                                if space.type == "IMAGE_EDITOR":
                                    space.image = active_node.image
                            break
    
        return {'FINISHED'}
# // FEATURE: Display Active Image Node on Image Editor

# FEATURE: Select Meshlights
class AMTH_OBJECT_OT_select_meshlights(Operator):
    """Select light emitting meshes"""
    bl_idname = "object.select_meshlights"
    bl_label = "Select Meshlights"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.scene.render.engine == 'CYCLES'

    def execute(self, context):
        # Deselect everything first
        bpy.ops.object.select_all(action='DESELECT')

        for ob in context.scene.objects:
            if cycles_is_emission(context, ob):
                ob.select = True
                context.scene.objects.active = ob

        if not context.selected_objects and not context.scene.objects.active:
            self.report({'INFO'}, "No meshlights to select")

        return {'FINISHED'}

def button_select_meshlights(self, context):
    if cycles_exists and context.scene.render.engine == 'CYCLES':
        self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
# // FEATURE: Select Meshlights

# FEATURE: Mesh Symmetry Tools by Sergey Sharybin
class AMTH_MESH_OT_find_asymmetric(Operator):
    """
    Find asymmetric vertices
    """

    bl_idname = "mesh.find_asymmetric"
    bl_label = "Find Asymmetric"
    bl_options = {'UNDO', 'REGISTER'}

    @classmethod
    def poll(cls, context):
        object = context.object
        if object:
            return object.mode == 'EDIT' and object.type == 'MESH'
        return False

    def execute(self, context):
        threshold = 1e-6

        object = context.object
        bm = bmesh.from_edit_mesh(object.data)

        # Deselect all the vertices
        for v in bm.verts:
            v.select = False

        for v1 in bm.verts:
            if abs(v1.co[0]) < threshold:
                continue

            mirror_found = False
            for v2 in bm.verts:
                if v1 == v2:
                    continue
                if v1.co[0] * v2.co[0] > 0.0:
                    continue

                mirror_coord = Vector(v2.co)
                mirror_coord[0] *= -1
                if (mirror_coord - v1.co).length_squared < threshold:
                    mirror_found = True
                    break
            if not mirror_found:
                v1.select = True

        bm.select_flush_mode()

        bmesh.update_edit_mesh(object.data)

        return {'FINISHED'}

class AMTH_MESH_OT_make_symmetric(Operator):
    """
    Make symmetric
    """

    bl_idname = "mesh.make_symmetric"
    bl_label = "Make Symmetric"
    bl_options = {'UNDO', 'REGISTER'}

    @classmethod
    def poll(cls, context):
        object = context.object
        if object:
            return object.mode == 'EDIT' and object.type == 'MESH'
        return False

    def execute(self, context):
        threshold = 1e-6

        object = context.object
        bm = bmesh.from_edit_mesh(object.data)

        for v1 in bm.verts:
            if v1.co[0] < threshold:
                continue
            if not v1.select:
                continue

            closest_vert = None
            closest_distance = -1
            for v2 in bm.verts:
                if v1 == v2:
                    continue
                if v2.co[0] > threshold:
                    continue
                if not v2.select:
                    continue

                mirror_coord = Vector(v2.co)
                mirror_coord[0] *= -1
                distance = (mirror_coord - v1.co).length_squared
                if closest_vert is None or distance < closest_distance:
                    closest_distance = distance
                    closest_vert = v2

            if closest_vert:
                closest_vert.select = False
                closest_vert.co = Vector(v1.co)
                closest_vert.co[0] *= -1
            v1.select = False

        for v1 in bm.verts:
            if v1.select:
                closest_vert = None
                closest_distance = -1
                for v2 in bm.verts:
                    if v1 != v2:
                        mirror_coord = Vector(v2.co)
                        mirror_coord[0] *= -1
                        distance = (mirror_coord - v1.co).length_squared
                        if closest_vert is None or distance < closest_distance:
                            closest_distance = distance
                            closest_vert = v2
                if closest_vert:
                    v1.select = False
                    v1.co = Vector(closest_vert.co)
                    v1.co[0] *= -1

        bm.select_flush_mode()
        bmesh.update_edit_mesh(object.data)

        return {'FINISHED'}
# // FEATURE: Mesh Symmetry Tools by Sergey Sharybin

# FEATURE: Cycles Render Sampling Extra
def render_cycles_scene_samples(self, context):

    layout = self.layout
    scenes = bpy.data.scenes
    scene = context.scene
    render = scene.render
    if cycles_exists:
        cscene = scene.cycles
        list_sampling = scene.amaranth_cycles_list_sampling

    # Set Render Samples
    if cycles_exists and cscene.progressive == 'BRANCHED_PATH':
        layout.separator()
        split = layout.split()
        col = split.column()

        col.operator(
            AMTH_RENDER_OT_cycles_samples_percentage_set.bl_idname,
            text="%s" % 'Set as Render Samples' if cscene.use_samples_final else 'Set New Render Samples',
            icon="%s" % 'PINNED' if cscene.use_samples_final else 'UNPINNED')

        col = split.column()
        row = col.row(align=True)
        row.enabled = True if scene.get('amth_cycles_samples_final') else False

        row.operator(
            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
            text="100%").percent=100
        row.operator(
            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
            text="75%").percent=75
        row.operator(
            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
            text="50%").percent=50
        row.operator(
            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
            text="25%").percent=25

    # List Samples
    if (len(scene.render.layers) > 1) or \
        (len(bpy.data.scenes) > 1):

        box = layout.box()
        row = box.row(align=True)
        col = row.column(align=True)

        row = col.row(align=True)
        row.alignment = 'LEFT'
        row.prop(scene, 'amaranth_cycles_list_sampling',
                    icon="%s" % 'TRIA_DOWN' if list_sampling else 'TRIA_RIGHT',
                    emboss=False)

    if list_sampling:
        if len(scene.render.layers) == 1 and \
            render.layers[0].samples == 0:
            pass
        else:
            col.separator()
            col.label(text="RenderLayers:", icon='RENDERLAYERS')

            for rl in scene.render.layers:
                row = col.row(align=True)
                row.label(rl.name, icon='BLANK1')
                row.prop(rl, "samples", text="%s" %
                    "Samples" if rl.samples > 0 else "Automatic (%s)" % (
                        cscene.aa_samples if cscene.progressive == 'BRANCHED_PATH' else cscene.samples))

        if (len(bpy.data.scenes) > 1):
            col.separator()

            col.label(text="Scenes:", icon='SCENE_DATA')

            if cycles_exists and cscene.progressive == 'PATH':
                for s in bpy.data.scenes:
                    if s != scene:
                        row = col.row(align=True)
                        if s.render.engine == 'CYCLES':
                            cscene = s.cycles

                            row.label(s.name)
                            row.prop(cscene, "samples", icon='BLANK1')
                        else:
                            row.label(text="Scene: '%s' is not using Cycles" % s.name)
            else:
                for s in bpy.data.scenes:
                    if s != scene:
                        row = col.row(align=True)
                        if s.render.engine == 'CYCLES':
                            cscene = s.cycles

                            row.label(s.name, icon='BLANK1')
                            row.prop(cscene, "aa_samples",
                                text="AA Samples")
                        else:
                            row.label(text="Scene: '%s' is not using Cycles" % s.name)

# // FEATURE: Cycles Render Sampling Extra

# FEATURE: Motion Paths Extras
class AMTH_POSE_OT_paths_clear_all(Operator):
    """Clear motion paths from all bones"""
    bl_idname = "pose.paths_clear_all"
    bl_label = "Clear All Motion Paths"
    bl_options = {'UNDO'}

    @classmethod
    def poll(cls, context):
        return context.mode == 'POSE'

    def execute(self, context):
        #silly but works
        for b in context.object.data.bones:
            b.select = True
            bpy.ops.pose.paths_clear()
            b.select = False
        return {'FINISHED'}

class AMTH_POSE_OT_paths_frame_match(Operator):
    """Match Start/End frame of scene to motion path range"""
    bl_idname = "pose.paths_frame_match"
    bl_label = "Match Frame Range"
    bl_options = {'UNDO'}

    def execute(self, context):
        avs = context.object.pose.animation_visualization
        scene = context.scene

        if avs.motion_path.type == 'RANGE':
            if scene.use_preview_range:
                avs.motion_path.frame_start = scene.frame_preview_start
                avs.motion_path.frame_end = scene.frame_preview_end
            else:
                avs.motion_path.frame_start = scene.frame_start
                avs.motion_path.frame_end = scene.frame_end

        else:
            if scene.use_preview_range:
                avs.motion_path.frame_before = scene.frame_preview_start
                avs.motion_path.frame_after = scene.frame_preview_end
            else:
                avs.motion_path.frame_before = scene.frame_start
                avs.motion_path.frame_after = scene.frame_end

        return {'FINISHED'}

def pose_motion_paths_ui(self, context):

    layout = self.layout
    scene = context.scene
    avs = context.object.pose.animation_visualization
    if context.active_pose_bone:
        mpath = context.active_pose_bone.motion_path
    layout.separator()    
    layout.label(text="Motion Paths Extras:")

    split = layout.split()

    col = split.column(align=True)

    if context.selected_pose_bones:
        if mpath:
            sub = col.row(align=True)
            sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
            sub.operator("pose.paths_clear", text="", icon='X')
        else:
            col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
    else:
        col.label(text="Select Bones First", icon="ERROR")

    col = split.column(align=True)
    col.operator(AMTH_POSE_OT_paths_frame_match.bl_idname,
        text="{}".format( "Set Preview Frame Range"
                if scene.use_preview_range else "Set Frame Range"),
        icon="{}".format("PREVIEW_RANGE"
                if scene.use_preview_range else "TIME"))

    col = layout.column()
    row = col.row(align=True)

    if avs.motion_path.type == 'RANGE':
        row.prop(avs.motion_path, "frame_start", text="Start")
        row.prop(avs.motion_path, "frame_end", text="End")
    else:
        row.prop(avs.motion_path, "frame_before", text="Before")
        row.prop(avs.motion_path, "frame_after", text="After")

    layout.separator()
    layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
# // FEATURE: Motion Paths Extras

# FEATURE: Final Render Resolution Display
def render_final_resolution_ui(self, context):

    rd = context.scene.render
    layout = self.layout

    final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
    final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100

    if rd.use_border:
       final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
       final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
       layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
             str(final_res_x)[:-2], str(final_res_y)[:-2],
             str(final_res_x_border), str(final_res_y_border)))
    else:
        layout.label(text="Final Resolution: {} x {}".format(
             str(final_res_x)[:-2], str(final_res_y)[:-2]))
# // FEATURE: Final Render Resolution Display

# FEATURE: Shader Nodes Extra Info
def node_shader_extra(self, context):

    if context.space_data.tree_type == 'ShaderNodeTree':
        ob = context.active_object
        snode = context.space_data
        layout = self.layout

        if ob and snode.shader_type != 'WORLD':
            if ob.type == 'LAMP':
                layout.label(text="%s" % ob.name,
                             icon="LAMP_%s" % ob.data.type)        
            else:
                layout.label(text="%s" % ob.name,
                             icon="OUTLINER_DATA_%s" % ob.type)
             

# // FEATURE: Shader Nodes Extra Info

# FEATURE: Scene Debug
class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
    """List Cycles materials containing a specific shader"""
    bl_idname = "scene.cycles_list_nodes"
    bl_label = "List Materials"
    materials = []

    @classmethod
    def poll(cls, context):
        return cycles_exists and context.scene.render.engine == 'CYCLES'

    def execute(self, context):
        node_type = context.scene.amaranth_cycles_node_types
        roughness = False
        self.__class__.materials = []
        shaders_roughness = ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']

        print("\n=== Cycles Shader Type: %s === \n" % node_type)

        for ma in bpy.data.materials:
            if ma.node_tree:
                nodes = ma.node_tree.nodes
                
                print_unconnected = ('Note: \nOutput from "%s" node' % node_type,
                                        'in material "%s"' % ma.name, 'not connected\n')

                for no in nodes:
                    if no.type == node_type:
                        for ou in no.outputs:
                            if ou.links:
                                connected = True
                                if no.type in shaders_roughness:
                                    roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
                                else:
                                    roughness = False
                            else:
                                connected = False
                                print(print_unconnected)

                            if ma.name not in self.__class__.materials:
                                self.__class__.materials.append('%s%s [%s] %s%s%s' % (
                                    '[L] ' if ma.library else '',
                                    ma.name, ma.users,
                                    '[F]' if ma.use_fake_user else '',
                                    ' - [%s]' % roughness if roughness else '',
                                    ' * Output not connected' if not connected else ''))

                    elif no.type == 'GROUP':
                        if no.node_tree:
                            for nog in no.node_tree.nodes:
                                if nog.type == node_type:
                                    for ou in nog.outputs:
                                        if ou.links:
                                            connected = True
                                            if nog.type in shaders_roughness:
                                                roughness = 'R: %.4f' % nog.inputs['Roughness'].default_value
                                            else:
                                                roughness = False
                                        else:
                                            connected = False
                                            print(print_unconnected)

                                        if ma.name not in self.__class__.materials:
                                            self.__class__.materials.append('%s%s%s [%s] %s%s%s' % (
                                                '[L] ' if ma.library else '',
                                                'Node Group:  %s%s  ->  ' % (
                                                    '[L] ' if no.node_tree.library else '',
                                                    no.node_tree.name),
                                                ma.name, ma.users,
                                                '[F]' if ma.use_fake_user else '',
                                                ' - [%s]' % roughness if roughness else '',
                                                ' * Output not connected' if not connected else ''))

                    self.__class__.materials = sorted(list(set(self.__class__.materials)))

        if len(self.__class__.materials) == 0:
            self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
        else:
            print("* A total of %d %s using %s was found \n" % (
                    len(self.__class__.materials),
                    "material" if len(self.__class__.materials) == 1 else "materials",
                    node_type))

            count = 0

            for mat in self.__class__.materials:
                print('%02d. %s' % (count+1, self.__class__.materials[count]))
                count += 1
            print("\n")

        self.__class__.materials = sorted(list(set(self.__class__.materials)))

        return {'FINISHED'}

class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(Operator):
    """Clear the list below"""
    bl_idname = "scene.cycles_list_nodes_clear"
    bl_label = "Clear Materials List"

    @classmethod
    def poll(cls, context):
        return cycles_exists

    def execute(self, context):
        AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
        print("* Cleared Cycles Materials List")
        return {'FINISHED'}

class AMTH_SCENE_OT_amaranth_object_select(Operator):
    '''Select object'''
    bl_idname = "scene.amaranth_object_select"
    bl_label = "Select Object"
    object = bpy.props.StringProperty()
 
    def execute(self, context):
        if self.object:
            object = bpy.data.objects[self.object]

            bpy.ops.object.select_all(action='DESELECT')
            object.select = True
            context.scene.objects.active = object

        return{'FINISHED'}

class AMTH_SCENE_OT_list_missing_node_links(Operator):
    '''Print a list of missing node links'''
    bl_idname = "scene.list_missing_node_links"
    bl_label = "List Missing Node Links"

    count_groups = 0
    count_images = 0
    count_image_node_unlinked = 0

    def execute(self, context):
        missing_groups = []
        missing_images = []
        image_nodes_unlinked = []
        libraries = []
        self.__class__.count_groups = 0
        self.__class__.count_images = 0
        self.__class__.count_image_node_unlinked = 0

        for ma in bpy.data.materials:
            if ma.node_tree:
                for no in ma.node_tree.nodes:
                    if no.type == 'GROUP':
                        if not no.node_tree:
                            self.__class__.count_groups += 1

                            users_ngroup = []

                            for ob in bpy.data.objects:
                                if ob.material_slots and ma.name in ob.material_slots:
                                    users_ngroup.append("%s%s%s" % (
                                        "[L] " if ob.library else "",
                                        "[F] " if ob.use_fake_user else "",
                                        ob.name))

                            missing_groups.append("MA: %s%s%s [%s]%s%s%s\n" % (
                                "[L] " if ma.library else "",
                                "[F] " if ma.use_fake_user else "",
                                ma.name, ma.users,
                                " *** No users *** " if ma.users == 0 else "",
                                "\nLI: %s" % 
                                ma.library.filepath if ma.library else "",
                                "\nOB: %s" % ',  '.join(users_ngroup) if users_ngroup else ""))

                            if ma.library:
                                libraries.append(ma.library.filepath)
                    if no.type == 'TEX_IMAGE':

                        outputs_empty = not no.outputs['Color'].is_linked and not no.outputs['Alpha'].is_linked

                        if no.image:
                            import os.path
                            image_path_exists = os.path.exists(
                                                    bpy.path.abspath(
                                                        no.image.filepath, library=no.image.library))

                        if outputs_empty or not \
                           no.image or not \
                           image_path_exists:

                            users_images = []

                            for ob in bpy.data.objects:
                                if ob.material_slots and ma.name in ob.material_slots:
                                    users_images.append("%s%s%s" % (
                                        "[L] " if ob.library else "",
                                        "[F] " if ob.use_fake_user else "",
                                        ob.name))

                            if outputs_empty:
                                self.__class__.count_image_node_unlinked += 1

                                image_nodes_unlinked.append("%s%s%s%s%s [%s]%s%s%s%s%s\n" % (
                                    "NO: %s" % no.name,
                                    "\nMA: ",
                                    "[L] " if ma.library else "",
                                    "[F] " if ma.use_fake_user else "",
                                    ma.name, ma.users,
                                    " *** No users *** " if ma.users == 0 else "",
                                    "\nLI: %s" % 
                                    ma.library.filepath if ma.library else "",
                                    "\nIM: %s" % no.image.name if no.image else "",
                                    "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
                                    "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
                            

                            if not no.image or not image_path_exists:
                                self.__class__.count_images += 1

                                missing_images.append("MA: %s%s%s [%s]%s%s%s%s%s\n" % (
                                    "[L] " if ma.library else "",
                                    "[F] " if ma.use_fake_user else "",
                                    ma.name, ma.users,
                                    " *** No users *** " if ma.users == 0 else "",
                                    "\nLI: %s" % 
                                    ma.library.filepath if ma.library else "",
                                    "\nIM: %s" % no.image.name if no.image else "",
                                    "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
                                    "\nOB: %s" % ',  '.join(users_images) if users_images else ""))

                                if ma.library:
                                    libraries.append(ma.library.filepath)

        # Remove duplicates and sort
        missing_groups = sorted(list(set(missing_groups)))
        missing_images = sorted(list(set(missing_images)))
        image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
        libraries = sorted(list(set(libraries)))

        print("\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
            ("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
            "node" if self.__class__.count_images == 1 else "nodes",
            "no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
            "group" if self.__class__.count_groups == 1 else "groups",
            "no" if self.__class__.count_image_node_unlinked == 0 else str(self.__class__.count_image_node_unlinked),
            "node" if self.__class__.count_groups == 1 else "nodes"))

        # List Missing Node Groups
        if missing_groups:
            print("\n* Missing Node Group Links\n")
            for mig in missing_groups:
                print(mig)

        # List Missing Image Nodes
        if missing_images:
            print("\n* Missing Image Nodes Link\n")

            for mii in missing_images:
                print(mii)

        # List Image Nodes with its outputs unlinked
        if image_nodes_unlinked:
            print("\n* Image Nodes Unlinked\n")

            for nou in image_nodes_unlinked:
                print(nou)

        if missing_groups or \
           missing_images or \
           image_nodes_unlinked:
            if libraries:
                print("\nThat's bad, run check on %s:" % (
                    "this library" if len(libraries) == 1 else "these libraries"))
                for li in libraries:
                    print(li)
        else:
            self.report({"INFO"}, "Yay! No missing node links")            

        print("\n")

        if missing_groups and missing_images:
            self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
                (self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
                self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))

        return{'FINISHED'}

class AMTH_SCENE_OT_list_missing_material_slots(Operator):
    '''List objects with empty material slots'''
    bl_idname = "scene.list_missing_material_slots"
    bl_label = "List Empty Material Slots"

    objects = []
    libraries = []

    def execute(self, context):
        self.__class__.objects = []
        self.__class__.libraries = []

        for ob in bpy.data.objects:
            for ma in ob.material_slots:
                if not ma.material:
                    self.__class__.objects.append('%s%s' % (
                        '[L] ' if ob.library else '',
                        ob.name))
                    if ob.library:
                        self.__class__.libraries.append(ob.library.filepath)

        self.__class__.objects = sorted(list(set(self.__class__.objects)))
        self.__class__.libraries = sorted(list(set(self.__class__.libraries)))

        if len(self.__class__.objects) == 0:
            self.report({"INFO"}, "No objects with empty material slots found")
        else:
            print("\n* A total of %d %s with empty material slots was found \n" % (
                    len(self.__class__.objects),
                    "object" if len(self.__class__.objects) == 1 else "objects"))

            count = 0
            count_lib = 0

            for obs in self.__class__.objects:
                print('%02d. %s' % (
                    count+1, self.__class__.objects[count]))
                count += 1

            if self.__class__.libraries:
                print("\n\n* Check %s:\n" % 
                    ("this library" if len(self.__class__.libraries) == 1
                        else "these libraries"))

                for libs in self.__class__.libraries:
                    print('%02d. %s' % (
                        count_lib+1, self.__class__.libraries[count_lib]))
                    count_lib += 1
            print("\n")

        return{'FINISHED'}

class AMTH_SCENE_OT_list_missing_material_slots_clear(Operator):
    """Clear the list below"""
    bl_idname = "scene.list_missing_material_slots_clear"
    bl_label = "Clear Empty Material Slots List"
    
    def execute(self, context):
        AMTH_SCENE_OT_list_missing_material_slots.objects[:] = []
        print("* Cleared Empty Material Slots List")
        return {'FINISHED'}

class AMTH_SCENE_OT_blender_instance_open(Operator):
    '''Open in a new Blender instance'''
    bl_idname = "scene.blender_instance_open"
    bl_label = "Open Blender Instance"
    filepath = bpy.props.StringProperty()

    def execute(self, context):
        if self.filepath:
            import os.path
            filepath = os.path.normpath(bpy.path.abspath(self.filepath))

            import subprocess
            try:
                subprocess.Popen([bpy.app.binary_path, filepath])
            except:
                print("Error on the new Blender instance")
                import traceback
                traceback.print_exc()

        return{'FINISHED'}

class AMTH_SCENE_PT_scene_debug(Panel):
    '''Scene Debug'''
    bl_label = 'Scene Debug'
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "scene"

    def draw_header(self, context):
        layout = self.layout
        layout.label(text="", icon="RADIO")

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        objects =  bpy.data.objects
        ob_act = context.active_object
        images = bpy.data.images
        lamps = bpy.data.lamps
        images_missing = []
        list_missing_images = scene.amaranth_debug_scene_list_missing_images
        materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
        materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
        missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
        missing_material_slots_count = len(AMTH_SCENE_OT_list_missing_material_slots.objects)
        missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
        engine = scene.render.engine

        # List Missing Images
        box = layout.box()
        row = box.row(align=True)
        split = row.split()
        col = split.column()

        if images:
            import os.path

            for im in images:
                if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']: 
                    if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
                        images_missing.append(["%s%s [%s]%s" % (
                            '[L] ' if im.library else '',
                            im.name, im.users,
                            ' [F]' if im.use_fake_user else ''),
                            im.filepath if im.filepath else 'No Filepath',
                            im.library.filepath if im.library else ''])

            if images_missing:
                row = col.row(align=True)
                row.alignment = 'LEFT'
                row.prop(scene, 'amaranth_debug_scene_list_missing_images',
                            icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
                            emboss=False)

                split = split.split()
                col = split.column()

                col.label(text="%s missing %s" % (
                             str(len(images_missing)),
                             'image' if len(images_missing) == 1 else 'images'),
                             icon="ERROR")

                if list_missing_images:
                    col = box.column(align=True)
                    for mis in images_missing:
                        col.label(text=mis[0],
                         icon="IMAGE_DATA")
                        col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
                        if mis[2]:
                            row = col.row(align=True)
                            row.alignment = "LEFT"
                            row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
                                         text=mis[2],
                                         icon="LINK_BLEND",
                                         emboss=False).filepath=mis[2]
                        col.separator()
            else:
                row = col.row(align=True)
                row.alignment = 'LEFT'
                row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")

                split = split.split()
                col = split.column()

                col.label(text="%s %s loading correctly" % (
                             str(len(images)),
                             'image' if len(images) == 1 else 'images'),
                             icon="IMAGE_DATA")
        else:
            row = col.row(align=True)
            row.alignment = 'LEFT'
            row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")

        # List Cycles Materials by Shader
        if cycles_exists and engine == 'CYCLES':
            box = layout.box()
            split = box.split()
            col = split.column(align=True)
            col.prop(scene, 'amaranth_cycles_node_types',
                icon="MATERIAL")

            row = split.row(align=True)
            row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
                            icon="SORTSIZE",
                            text="List Materials Using Shader")
            if materials_count != 0: 
                row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
                                icon="X", text="")
            col.separator()

            try:
                materials
            except NameError:
                pass
            else:
                if materials_count != 0: 
                    col = box.column(align=True)
                    count = 0
                    col.label(text="%s %s found" % (materials_count,
                        'material' if materials_count == 1 else 'materials'), icon="INFO")
                    for mat in materials:
                        count += 1
                        col.label(text='%s' % (materials[count-1]), icon="MATERIAL")

        # List Missing Node Trees
        box = layout.box()
        row = box.row(align=True)
        split = row.split()
        col = split.column(align=True)

        split = col.split()
        split.label(text="Node Links")
        split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
                        icon="NODETREE")

        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
            AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
            AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
            col.label(text="Warning! Check Console", icon="ERROR")

        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
            col.label(text="%s" % ("%s node %s missing link" % (
                     str(AMTH_SCENE_OT_list_missing_node_links.count_groups),
                     "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
                     icon="NODETREE")
        if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
            col.label(text="%s" % ("%s image %s missing link" % (
                     str(AMTH_SCENE_OT_list_missing_node_links.count_images),
                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
                     icon="IMAGE_DATA")

        if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
            col.label(text="%s" % ("%s image %s with no output conected" % (
                     str(AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
                     icon="NODE")

        # List Empty Materials Slots
        box = layout.box()
        split = box.split()
        col = split.column(align=True)
        col.label(text="Material Slots")

        row = split.row(align=True)
        row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
                        icon="MATERIAL",
                        text="List Empty Materials Slots")
        if missing_material_slots_count != 0: 
            row.operator(AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
                            icon="X", text="")
        col.separator()

        try:
            missing_material_slots_obs
        except NameError:
            pass
        else:
            if missing_material_slots_count != 0: 
                col = box.column(align=True)
                count = 0
                count_lib = 0
                col.label(text="%s %s with empty material slots found" % (
                    missing_material_slots_count,
                    'object' if missing_material_slots_count == 1 else 'objects'),
                    icon="INFO")

                for obs in missing_material_slots_obs:
                    count += 1

                    row = col.row()
                    row.alignment = 'LEFT'
                    row.label(text='%s' % missing_material_slots_obs[count-1],
                                icon="OBJECT_DATA")

                if missing_material_slots_lib:
                    col.separator()
                    col.label("Check %s:" % (
                        "this library" if
                            len(missing_material_slots_lib) == 1
                                else "these libraries"))
                    
                    for libs in missing_material_slots_lib:
                        count_lib += 1
                        row = col.row(align=True)
                        row.alignment = "LEFT"
                        row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
                                     text=missing_material_slots_lib[count_lib-1],
                                     icon="LINK_BLEND",
                                     emboss=False).filepath=missing_material_slots_lib[count_lib-1]

# // FEATURE: Scene Debug
# FEATURE: Dupli  Group Path
def ui_dupli_group_library_path(self, context):

    ob = context.object

    row = self.layout.row()
    row.alignment = 'LEFT'

    if ob and ob.dupli_group and ob.dupli_group.library:
        lib = ob.dupli_group.library.filepath

        row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
            text="Library: %s" % lib,
            emboss=False,
            icon="LINK_BLEND").filepath=lib

# // FEATURE: Dupli  Group Path
# FEATURE: Color Management Presets
class AMTH_SCENE_MT_color_management_presets(Menu):
    """List of Color Management presets"""
    bl_label = "Color Management Presets"
    preset_subdir = "color"
    preset_operator = "script.execute_preset"
    draw = Menu.draw_preset


class AMTH_AddPresetColorManagement(AddPresetBase, Operator):
    """Add or remove a Color Management preset"""
    bl_idname = "scene.color_management_preset_add"
    bl_label = "Add Color Management Preset"
    preset_menu = "AMTH_SCENE_MT_color_management_presets"

    preset_defines = [
        "scene = bpy.context.scene"
    ]

    preset_values = [
        "scene.view_settings.view_transform",
        "scene.display_settings.display_device",
        "scene.view_settings.exposure",
        "scene.view_settings.gamma",
        "scene.view_settings.look",
        "scene.view_settings.use_curve_mapping",
        "scene.sequencer_colorspace_settings.name",
    ]

    preset_subdir = "color"

def ui_color_management_presets(self, context):
    
    layout = self.layout

    row = layout.row(align=True)
    row.menu("AMTH_SCENE_MT_color_management_presets", text=bpy.types.AMTH_SCENE_MT_color_management_presets.bl_label)
    row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
    row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
    layout.separator()
# // FEATURE: Color Management Presets

# FEATURE: Sequencer Extra Info
def act_strip(context):
    try:
        return context.scene.sequence_editor.active_strip
    except AttributeError:
        return None

def ui_sequencer_extra_info(self, context):

    layout = self.layout
    strip = act_strip(context)

    if strip:
        seq_type = strip.type

        if seq_type and seq_type == 'IMAGE':
            elem = strip.strip_elem_from_frame(context.scene.frame_current)
            if elem:
                layout.label(text="%s %s" % (
                    elem.filename,
                    "[%s]" % (context.scene.frame_current - strip.frame_start)))
# // FEATURE: Sequencer Extra Info

# FEATURE: Normal Node Values, by Lukas Tönne
def normal_vector_get(self):
    return self.outputs['Normal'].default_value

def normal_vector_set(self, values):
    # default_value allows un-normalized values,
    # do this here to prevent awkward results
    values = Vector(values).normalized()
    self.outputs['Normal'].default_value = values

prop_normal_vector = bpy.props.FloatVectorProperty(
                        name="Normal", size=3, subtype='XYZ',
                        min=-1.0, max=1.0, soft_min=-1.0, soft_max=1.0,
                        get=normal_vector_get, set=normal_vector_set
                        )

def act_node(context):
    try:
        return context.active_node
    except AttributeError:
        return None

def ui_node_normal_values(self, context):

    node = act_node(context)

    if act_node:
        if node and node.type == 'NORMAL':
            self.layout.prop(node, "normal_vector", text="")

# // FEATURE: Normal Node Values, by Lukas Tönne

# FEATURE: Object ID for objects inside DupliGroups
class AMTH_OBJECT_OT_id_dupligroup(Operator):
    '''Set the Object ID for objects in the dupli group'''
    bl_idname = "object.amaranth_object_id_duplis"
    bl_label = "Apply Object ID to Duplis"

    clear = False

    @classmethod
    def poll(cls, context):
        return context.active_object.dupli_group

    def execute(self, context):
        self.__class__.clear = False
        ob = context.active_object
        amth_text_exists = amaranth_text_startup(context)
        script_exists = False
        script_intro = "# OB ID: %s" % ob.name
        obdata = "bpy.data.objects['%s']" % ob.name
        script = "%s" % (
            "\nif %(obdata)s and %(obdata)s.dupli_group and %(obdata)s.pass_index != 0: %(obname)s \n"
            "    for dob in %(obdata)s.dupli_group.objects: %(obname)s \n"
            "        dob.pass_index = %(obdata)s.pass_index %(obname)s \n" %
                {'obdata' : obdata, 'obname' : script_intro})

        for txt in bpy.data.texts:
            if txt.name == amth_text.name:
                for li in txt.lines:
                    if script_intro == li.body:
                        script_exists = True
                        continue

        if not script_exists:
            amth_text.write("\n")
            amth_text.write(script_intro)
            amth_text.write(script)

        if ob and ob.dupli_group:
            if ob.pass_index != 0:
                for dob in ob.dupli_group.objects:
                    dob.pass_index = ob.pass_index

        self.report({'INFO'},
            "%s ID: %s to all objects in this Dupli Group" % (
                "Applied" if not script_exists else "Updated",
                ob.pass_index))

        return{'FINISHED'}

class AMTH_OBJECT_OT_id_dupligroup_clear(Operator):
    '''Clear the Object ID from objects in dupli group'''
    bl_idname = "object.amaranth_object_id_duplis_clear"
    bl_label = "Clear Object ID from Duplis"

    @classmethod
    def poll(cls, context):
        return context.active_object.dupli_group

    def execute(self, context):
        context.active_object.pass_index = 0
        AMTH_OBJECT_OT_id_dupligroup.clear = True
        amth_text_exists = amaranth_text_startup(context)
        match_first = "# OB ID: %s" % context.active_object.name

        if amth_text_exists:
            for txt in bpy.data.texts:
                if txt.name == amth_text.name:
                    for li in txt.lines:
                        if match_first in li.body:
                            li.body = ''
                            continue

        self.report({'INFO'}, "Object IDs back to normal")
        return{'FINISHED'}

def ui_object_id_duplis(self, context):

    if context.active_object.dupli_group:
        split = self.layout.split()
        row = split.row(align=True)
        row.enabled = context.active_object.pass_index != 0
        row.operator(
            AMTH_OBJECT_OT_id_dupligroup.bl_idname)
        row.operator(
            AMTH_OBJECT_OT_id_dupligroup_clear.bl_idname,
            icon="X", text="")
        split.separator()

        if AMTH_OBJECT_OT_id_dupligroup.clear:
            self.layout.label(text="Next time you save/reload this file, "
                                        "object IDs will be back to normal",
                              icon="INFO")

# // FEATURE: Object ID for objects inside DupliGroups
# UI: Warning about Z not connected when using EXR
def ui_render_output_z(self, context):

    scene = bpy.context.scene
    image = scene.render.image_settings
    if scene.render.use_compositing and \
        image.file_format == 'OPEN_EXR' and \
        image.use_zbuffer:
        if scene.node_tree and scene.node_tree.nodes:
            for no in scene.node_tree.nodes:
                if no.type == 'COMPOSITE':
                    if not no.inputs['Z'].is_linked:
                        self.layout.label(
                            text="The Z output in node \"%s\" is not connected" % 
                                no.name, icon="ERROR")

# // UI: Warning about Z not connected

# FEATURE: Delete Materials not assigned to any verts
class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
    '''Remove materials not assigned to any vertex'''
    bl_idname = "object.amaranth_object_material_remove_unassigned"
    bl_label = "Remove Unassigned Materials"

    @classmethod
    def poll(cls, context):
        return context.active_object.material_slots

    def execute(self, context):

        scene = context.scene
        act_ob = context.active_object
        count = len(act_ob.material_slots)
        materials_removed = []
        act_ob.active_material_index = 0
        is_visible = True

        if act_ob not in context.visible_objects:
            is_visible = False
            n = -1
            for lay in act_ob.layers:
                n += 1
                if lay:
                    break

            scene.layers[n] = True

        for slot in act_ob.material_slots:
            count -= 1

            bpy.ops.object.mode_set(mode='EDIT')
            bpy.ops.mesh.select_all(action='DESELECT')
            act_ob.active_material_index = count
            bpy.ops.object.material_slot_select()
            
            if act_ob.data.total_vert_sel == 0 or \
                (len(act_ob.material_slots) == 1 and not \
                    act_ob.material_slots[0].material):
                materials_removed.append(
                    "%s" % act_ob.active_material.name if act_ob.active_material else "Empty")
                bpy.ops.object.mode_set(mode='OBJECT')
                bpy.ops.object.material_slot_remove()
            else:
                pass

        bpy.ops.object.mode_set(mode='EDIT')
        bpy.ops.mesh.select_all(action='DESELECT')
        bpy.ops.object.mode_set(mode='OBJECT')

        if materials_removed:
            print("\n* Removed %s Unassigned Materials \n" % len(materials_removed))

            count_mr = 0

            for mr in materials_removed:
                count_mr += 1
                print("%0.2d. %s" % (count_mr, materials_removed[count_mr - 1]))

            print("\n")
            self.report({'INFO'}, "Removed %s Unassigned Materials" %
                len(materials_removed))

        if not is_visible:
            scene.layers[n] = False

        return{'FINISHED'}

def ui_material_remove_unassigned(self, context):

    self.layout.operator(
        AMTH_OBJECT_OT_material_remove_unassigned.bl_idname,
        icon="X")

# // FEATURE: Delete Materials not assigned to any verts
# FEATURE: Cycles Samples Percentage
class AMTH_RENDER_OT_cycles_samples_percentage_set(Operator):
    '''Save the current number of samples per shader as final (gets saved in .blend)'''
    bl_idname = "scene.amaranth_cycles_samples_percentage_set"
    bl_label = "Set as Render Samples"

    def execute(self, context):
        cycles = context.scene.cycles
        cycles.use_samples_final = True

        context.scene['amth_cycles_samples_final'] = [
            cycles.diffuse_samples,
            cycles.glossy_samples,
            cycles.transmission_samples,
            cycles.ao_samples,
            cycles.mesh_light_samples,
            cycles.subsurface_samples,
            cycles.volume_samples]

        self.report({'INFO'}, "Render Samples Saved")

        return{'FINISHED'}


class AMTH_RENDER_OT_cycles_samples_percentage(Operator):
    '''Set a percentage of the final render samples'''
    bl_idname = "scene.amaranth_cycles_samples_percentage"
    bl_label = "Set Render Samples Percentage"

    percent = IntProperty(
                name="Percentage",
                description="Percentage to divide render samples by",
                subtype='PERCENTAGE',
                default=0)

    def execute(self, context):
        percent = self.percent
        cycles = context.scene.cycles
        cycles_samples_final = context.scene['amth_cycles_samples_final']

        cycles.use_samples_final = False

        if percent == 100:
            cycles.use_samples_final = True

        cycles.diffuse_samples = int((cycles_samples_final[0] / 100) * percent)
        cycles.glossy_samples = int((cycles_samples_final[1] / 100) * percent)
        cycles.transmission_samples = int((cycles_samples_final[2] / 100) * percent)
        cycles.ao_samples = int((cycles_samples_final[3] / 100) * percent)
        cycles.mesh_light_samples = int((cycles_samples_final[4] / 100) * percent)
        cycles.subsurface_samples = int((cycles_samples_final[5] / 100) * percent)
        cycles.volume_samples = int((cycles_samples_final[6] / 100) * percent)

        return{'FINISHED'}

# //FEATURE: Cycles Samples Percentage
# FEATURE: Jump forward/backward every N frames
class AMTH_SCREEN_OT_frame_jump(Operator):
    '''Jump a number of frames forward/backwards'''
    bl_idname = "screen.amaranth_frame_jump"
    bl_label = "Jump Frames"

    forward = BoolProperty(default=True)

    def execute(self, context):
        scene = context.scene
        preferences = context.user_preferences.addons[__name__].preferences

        if self.forward:
            scene.frame_current = scene.frame_current + preferences.frames_jump
        else:
            scene.frame_current = scene.frame_current - preferences.frames_jump

        return{'FINISHED'}

def ui_userpreferences_edit(self, context):
    preferences = context.user_preferences.addons[__name__].preferences

    col = self.layout.column()
    split = col.split(percentage=0.21)
    split.prop(preferences, "frames_jump",
               text="Frames to Jump")

# // FEATURE: Jump forward/backward every N frames
# FEATURE: Set Layers to Render
class AMTH_SCENE_OT_layers_render_save(Operator):
    '''Save the current scene layers as those that should be enabled for final renders'''
    bl_idname = "scene.amaranth_layers_render_save"
    bl_label = "Save as Layers for Render"

    def execute(self, context):
        which = []
        n = -1

        for l in context.scene.layers:
            n += 1
            if l:
                which.append(n)

        context.scene['amth_layers_for_render'] = which
        self.report({'INFO'}, "Layers for Render Saved")

        return{'FINISHED'}

class AMTH_SCENE_OT_layers_render_view(Operator):
    '''Enable the scene layers that should be active for final renders'''
    bl_idname = "scene.amaranth_layers_render_view"
    bl_label = "View Layers for Render"

    def execute(self, context):
        scene = context.scene
        layers_render = scene['amth_layers_for_render']

        for window in bpy.context.window_manager.windows:
            screen = window.screen

            for area in screen.areas:
                if area.type == 'VIEW_3D':
                    override = {'window': window, 'screen': screen, 'scene': scene, 
                                'area': area, 'region': area.regions[4],
                                'blend_data': context.blend_data}

                    if layers_render:
                        bpy.ops.view3d.layers(override, nr=layers_render[0]+1, extend=False, toggle=False)

                        for n in layers_render:
                            context.scene.layers[n] = True
                    else:
                        bpy.ops.view3d.layers(override, nr=1, extend=False, toggle=False)
                        self.report({'INFO'}, "No layers set for render")

                    break

        return{'FINISHED'}

class AMTH_SCENE_OT_layers_render_set_individual(Operator):
    '''Whether this layer should be enabled or not for final renders'''
    bl_idname = "scene.amaranth_layers_render_set_individual"
    bl_label = "Set This Layer for Render"

    toggle = BoolProperty()
    number = IntProperty()

    def execute(self, context):
        toggle = self.toggle
        number = self.number

        new_layers = []

        for la in context.scene['amth_layers_for_render']:
            new_layers.append(la)

        if len(context.scene['amth_layers_for_render']) and number in new_layers:
            new_layers.remove(number)
        else:
            new_layers.append(number)

        # Remove Duplicates
        new_layers = list(set(new_layers))
        context.scene['amth_layers_for_render'] = new_layers

        bpy.ops.scene.amaranth_layers_render_view()

        return{'FINISHED'}

class AMTH_SCENE_OT_layers_render_clear(Operator):
    '''Clear layers for render'''
    bl_idname = "scene.amaranth_layers_render_clear"
    bl_label = "Clear Layers for Render"

    def execute(self, context):

        if context.scene.get('amth_layers_for_render'):
            context.scene['amth_layers_for_render'] = []

        return{'FINISHED'}

def ui_layers_for_render(self, context):

    preferences = context.user_preferences.addons[__name__].preferences

    if preferences.use_layers_for_render:
        lfr_available = context.scene.get('amth_layers_for_render')
        if lfr_available:
            lfr = context.scene['amth_layers_for_render']

        layout = self.layout
        layout.label("Layers for Rendering:")
        split = layout.split()
        col = split.column(align=True)
        row = col.row(align=True)
        row.operator(
            AMTH_SCENE_OT_layers_render_save.bl_idname,
            text="Replace Layers" if lfr_available else "Save Current Layers for Render",
            icon="FILE_REFRESH" if lfr_available else 'LAYER_USED')

        if lfr_available:
            row.operator(
                AMTH_SCENE_OT_layers_render_clear.bl_idname,
                icon='X', text="")
            col = col.column(align=True)
            col.enabled = True if lfr_available else False
            col.operator(
                AMTH_SCENE_OT_layers_render_view.bl_idname,
                icon="RESTRICT_VIEW_OFF")

            split = split.split()
            col = split.column(align=True)
            row = col.row(align=True)

            for n in range(0,5):
                row.operator(
                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
            row = col.row(align=True)
            for n in range(10,15):
                row.operator(
                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n

            split = split.split()
            col = split.column(align=True)
            row = col.row(align=True)

            for n in range(5,10):
                row.operator(
                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
            row = col.row(align=True)
            for n in range(15,20):
                row.operator(
                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n

def ui_layers_for_render_header(self, context):

    preferences = context.user_preferences.addons[__name__].preferences

    if preferences.use_layers_for_render:
        if context.scene.get('amth_layers_for_render'):
            self.layout.operator(
                AMTH_SCENE_OT_layers_render_view.bl_idname,
                text="", icon="IMGDISPLAY")

# // FEATURE: Set Layers to Render
# FEATURE: Lighters Corner
class AMTH_LightersCorner(bpy.types.Panel):
    """The Lighters Panel"""
    bl_label = "Lighter's Corner"
    bl_idname = "AMTH_SCENE_PT_lighters_corner"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    @classmethod
    def poll(cls, context):
        any_lamps = False
        for ob in bpy.data.objects:
            if ob.type == 'LAMP' or cycles_is_emission(context, ob):
                any_lamps = True
            else:
                pass
        return any_lamps

    def draw_header(self, context):
        layout = self.layout
        layout.label(text="", icon="LAMP_SUN")

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        objects =  bpy.data.objects
        ob_act = context.active_object
        lamps = bpy.data.lamps
        list_meshlights = scene.amaranth_lighterscorner_list_meshlights
        engine = scene.render.engine

        if cycles_exists:
            layout.prop(scene, "amaranth_lighterscorner_list_meshlights")

        box = layout.box()
        if lamps:
            if objects:
                row = box.row(align=True)
                split = row.split(percentage=0.45)
                col = split.column()

                col.label(text="Name")

                if engine in ['CYCLES', 'BLENDER_RENDER']:
                    if engine == 'BLENDER_RENDER':
                        split = split.split(percentage=0.7)
                    else:
                        split = split.split(percentage=0.27)
                    col = split.column()
                    col.label(text="Samples")

                if cycles_exists and engine == 'CYCLES':
                    split = split.split(percentage=0.2)
                    col = split.column()
                    col.label(text="Size")

                split = split.split(percentage=1.0)
                col = split.column()
                col.label(text="%sRender Visibility" % 
                    'Rays /' if cycles_exists else '')

                for ob in objects:
                    is_lamp = ob.type == 'LAMP'
                    is_emission = True if cycles_is_emission(context, ob) and list_meshlights else False

                    if ob and is_lamp or is_emission:
                        lamp = ob.data
                        if cycles_exists:
                            clamp = ob.data.cycles
                            visibility = ob.cycles_visibility

                        row = box.row(align=True)
                        split = row.split(percentage=1.0)
                        col = split.column()
                        row = col.row(align=True)
                        col.active = ob == ob_act
                        row.label(icon="%s" % ('LAMP_%s' % ob.data.type if is_lamp else 'MESH_GRID'))
                        split = row.split(percentage=.45)
                        col = split.column()
                        row = col.row(align=True)
                        row.alignment = 'LEFT'
                        row.active = True
                        row.operator(AMTH_SCENE_OT_amaranth_object_select.bl_idname,
                                    text='%s %s%s' % (
                                        " [L] " if ob.library else "",
                                        ob.name,
                                        "" if ob.name in context.scene.objects else " [Not in Scene]"),
                                    emboss=False).object = ob.name
                        if ob.library:
                            row = col.row(align=True)
                            row.alignment = "LEFT"
                            row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
                                         text=ob.library.filepath,
                                         icon="LINK_BLEND",
                                         emboss=False).filepath=ob.library.filepath

                        if cycles_exists and engine == 'CYCLES':
                            split = split.split(percentage=0.25)
                            col = split.column()
                            if is_lamp:
                                if scene.cycles.progressive == 'BRANCHED_PATH':
                                    col.prop(clamp, "samples", text="")
                                if scene.cycles.progressive == 'PATH':
                                   col.label(text="N/A")
                            else:
                              col.label(text="N/A")

                        if engine == 'BLENDER_RENDER':
                            split = split.split(percentage=0.7)
                            col = split.column()
                            if is_lamp:
                                if lamp.type == 'HEMI':
                                    col.label(text="Not Available")
                                elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
                                    row = col.row(align=True)
                                    row.prop(lamp, "shadow_ray_samples_x", text="X")
                                    if lamp.shape == 'RECTANGLE':
                                        row.prop(lamp, "shadow_ray_samples_y", text="Y")
                                elif lamp.shadow_method == 'RAY_SHADOW':
                                    col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
                                elif lamp.shadow_method == 'BUFFER_SHADOW':
                                    col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
                                else:
                                    col.label(text="No Shadow")
                            else:
                              col.label(text="N/A")

                        if cycles_exists and engine == 'CYCLES':
                            split = split.split(percentage=0.2)
                            col = split.column()
                            if is_lamp:
                                if lamp.type in ['POINT','SUN', 'SPOT']:
                                    col.label(text="%.2f" % lamp.shadow_soft_size)
                                elif lamp.type == 'HEMI':
                                    col.label(text="N/A")
                                elif lamp.type == 'AREA' and lamp.shape == 'RECTANGLE':
                                    col.label(text="%.2fx%.2f" % (lamp.size, lamp.size_y))
                                else:
                                    col.label(text="%.2f" % lamp.size)
                            else:
                              col.label(text="N/A")

                        split = split.split(percentage=1.0)
                        col = split.column()
                        row = col.row(align=True)
                        if cycles_exists:
                            row.prop(visibility, "camera", text="")
                            row.prop(visibility, "diffuse", text="")
                            row.prop(visibility, "glossy", text="")
                            row.prop(visibility, "shadow", text="")
                            row.separator()
                        row.prop(ob, "hide", text="", emboss=False)
                        row.prop(ob, "hide_render", text="", emboss=False)
        else:
            box.label(text="No Lamps", icon="LAMP_DATA")

# FEATURE: Jump to frame in-between next and previous keyframe
class AMTH_SCREEN_OT_keyframe_jump_inbetween(Operator):
    '''Jump to half in-between keyframes'''
    bl_idname = "screen.amth_keyframe_jump_inbetween"
    bl_label = "Jump to Keyframe In-between"

    backwards = BoolProperty()

    def execute(self, context):
        back = self.backwards

        scene = context.scene
        ob = bpy.context.object
        frame_start = scene.frame_start
        frame_end = scene.frame_end

        if not context.scene.get('amth_keyframes_jump'):
            context.scene['amth_keyframes_jump'] = []

        keyframes_list = context.scene['amth_keyframes_jump']

        for f in range(frame_start, frame_end):
            if ob.is_keyframe(f):
                 keyframes_list = list(keyframes_list)
                 keyframes_list.append(f)

        if keyframes_list:
            i = 0
            keyframes_list_half = []

            for item in keyframes_list:
                try:
                    keyframes_list_half.append(int((keyframes_list[i] + keyframes_list[i+1]) / 2))
                    i += 1
                except:
                    pass

            if len(keyframes_list_half) > 1:
                if back:
                    if scene.frame_current == keyframes_list_half[::-1][-1] or \
                        scene.frame_current < keyframes_list_half[::-1][-1]:
                        self.report({'INFO'}, "No keyframes behind")
                    else:
                        for i in keyframes_list_half[::-1]:
                            if scene.frame_current > i:
                                scene.frame_current = i
                                break
                else:
                    if scene.frame_current == keyframes_list_half[-1] or \
                        scene.frame_current > keyframes_list_half[-1]:
                        self.report({'INFO'}, "No keyframes ahead")
                    else:
                        for i in keyframes_list_half:
                            if scene.frame_current < i:
                                scene.frame_current = i
                                break
            else:
                self.report({'INFO'}, "Object has only 1 keyframe")
        else:
            self.report({'INFO'}, "Object has no keyframes")

        return{'FINISHED'}
# // FEATURE: Jump to frame in-between next and previous keyframe
# FEATURE: Toggle Wire Display
class AMTH_OBJECT_OT_wire_toggle(Operator):
    '''Turn on/off wire display on mesh objects'''
    bl_idname = "object.amth_wire_toggle"
    bl_label = "Display Wireframe"
    bl_options = {'REGISTER', 'UNDO'}

    clear = BoolProperty(
        default=False, name="Clear Wireframe",
        description="Clear Wireframe Display")

    def execute(self, context):

        scene = context.scene
        is_all_scenes = scene.amth_wire_toggle_scene_all
        is_selected = scene.amth_wire_toggle_is_selected
        is_all_edges = scene.amth_wire_toggle_edges_all
        is_optimal = scene.amth_wire_toggle_optimal
        clear = self.clear

        if is_all_scenes:
            which = bpy.data.objects
        elif is_selected:
            if not context.selected_objects:
                self.report({'INFO'}, "No selected objects")
            which = context.selected_objects
        else:
            which = scene.objects

        if which:
            for ob in which:
                if ob and ob.type in {
                    'MESH', 'EMPTY', 'CURVE',
                    'META','SURFACE','FONT'}:

                    ob.show_wire = False if clear else True
                    ob.show_all_edges = is_all_edges

                    for mo in ob.modifiers:
                        if mo and mo.type == 'SUBSURF':
                            mo.show_only_control_edges = is_optimal

        return{'FINISHED'}

def ui_object_wire_toggle(self, context):

    scene = context.scene

    self.layout.separator()
    col = self.layout.column(align=True)
    row = col.row(align=True)
    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
        icon='MOD_WIREFRAME').clear = False
    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
        icon='X', text="").clear = True
    col.separator()
    row = col.row(align=True)
    row.prop(scene, "amth_wire_toggle_edges_all")
    row.prop(scene, "amth_wire_toggle_optimal")
    row = col.row(align=True)
    sub = row.row(align=True)
    sub.active = not scene.amth_wire_toggle_scene_all
    sub.prop(scene, "amth_wire_toggle_is_selected")
    sub = row.row(align=True)
    sub.active = not scene.amth_wire_toggle_is_selected
    sub.prop(scene, "amth_wire_toggle_scene_all")

# //FEATURE: Toggle Wire Display
# FEATURE: Add Meshlight
class AMTH_OBJECT_OT_meshlight_add(bpy.types.Operator):
    """Add a light emitting mesh"""
    bl_idname = "object.meshlight_add"
    bl_label = "Add Meshlight"
    bl_options = {'REGISTER', 'UNDO'}

    single_sided = BoolProperty(
            name="Single Sided",
            default=True,
            description="Only emit light on one side",
            )

    visible = BoolProperty(
            name="Visible on Camera",
            default=False,
            description="Show the meshlight on Cycles preview",
            )

    size = FloatProperty(
            name="Size",
            description="Meshlight size",
            min=0.01, max=100.0,
            default=1.0,
            )

    strength = FloatProperty(
            name="Strength",
            min=0.01, max=100000.0,
            default=1.5,
            step=0.25,
            )

    temperature = FloatProperty(
            name="Temperature",
            min=800, max=12000.0,
            default=5500.0,
            step=800.0,
            )

    rotation = FloatVectorProperty(
            name="Rotation",
            subtype='EULER',
            )

    def execute(self, context):
        scene = context.scene
        exists = False
        number = 1

        for obs in bpy.data.objects:
            if obs.name.startswith("light_meshlight"):
                number +=1

        meshlight_name = 'light_meshlight_%.2d' % number

        bpy.ops.mesh.primitive_grid_add(
            x_subdivisions=4, y_subdivisions=4,
            rotation=self.rotation, radius=self.size)

        bpy.context.object.name = meshlight_name
        meshlight = scene.objects[meshlight_name]
        meshlight.show_wire = True
        meshlight.show_all_edges = True

        material = bpy.data.materials.get(meshlight_name)

        if not material:
            material = bpy.data.materials.new(meshlight_name)

        bpy.ops.object.material_slot_add()
        meshlight.active_material = material

        material.use_nodes = True
        material.diffuse_color = (1, 0.5, 0)
        nodes = material.node_tree.nodes
        links = material.node_tree.links

        # clear default nodes to start nice fresh
        for no in nodes:
            nodes.remove(no)

        if self.single_sided:
            geometry = nodes.new(type="ShaderNodeNewGeometry")

            transparency = nodes.new(type="ShaderNodeBsdfTransparent")
            transparency.inputs[0].default_value = (1,1,1,1)
            transparency.location = geometry.location
            transparency.location += Vector((0.0, -55.0))

            emission = nodes.new(type="ShaderNodeEmission")
            emission.inputs['Strength'].default_value = self.strength
            emission.location = transparency.location
            emission.location += Vector((0.0, -80.0))
            emission.inputs[0].show_expanded = True # so it shows slider on properties editor

            blackbody = nodes.new(type="ShaderNodeBlackbody")
            blackbody.inputs['Temperature'].default_value = self.temperature
            blackbody.location = emission.location
            blackbody.location += Vector((-180.0, 0.0))

            mix = nodes.new(type="ShaderNodeMixShader")
            mix.location = geometry.location
            mix.location += Vector((180.0, 0.0))
            mix.inputs[2].show_expanded = True

            output = nodes.new(type="ShaderNodeOutputMaterial")
            output.inputs[1].hide = True
            output.inputs[2].hide = True
            output.location = mix.location
            output.location += Vector((180.0, 0.0))

            # Make links
            links.new(geometry.outputs['Backfacing'], mix.inputs['Fac'])
            links.new(transparency.outputs['BSDF'], mix.inputs[1])
            links.new(emission.outputs['Emission'], mix.inputs[2])
            links.new(blackbody.outputs['Color'], emission.inputs['Color'])
            links.new(mix.outputs['Shader'], output.inputs['Surface'])

            for sockets in geometry.outputs:
                sockets.hide = True
        else:
            emission = nodes.new(type="ShaderNodeEmission")
            emission.inputs['Strength'].default_value = self.strength
            emission.inputs[0].show_expanded = True

            blackbody = nodes.new(type="ShaderNodeBlackbody")
            blackbody.inputs['Temperature'].default_value = self.temperature
            blackbody.location = emission.location
            blackbody.location += Vector((-180.0, 0.0))

            output = nodes.new(type="ShaderNodeOutputMaterial")
            output.inputs[1].hide = True
            output.inputs[2].hide = True
            output.location = emission.location
            output.location += Vector((180.0, 0.0))

            links.new(blackbody.outputs['Color'], emission.inputs['Color'])
            links.new(emission.outputs['Emission'], output.inputs['Surface'])

        material.cycles.sample_as_light = True
        meshlight.cycles_visibility.shadow = False
        meshlight.cycles_visibility.camera = self.visible

        return {'FINISHED'}

def ui_menu_lamps_add(self, context):
    if cycles_exists and context.scene.render.engine == 'CYCLES':
        self.layout.separator()
        self.layout.operator(
            AMTH_OBJECT_OT_meshlight_add.bl_idname,
            icon="LAMP_AREA", text="Meshlight")

# //FEATURE: Add Meshlight: Single Sided

classes = (AMTH_SCENE_MT_color_management_presets,
           AMTH_AddPresetColorManagement,
           AMTH_LightersCorner,
           AMTH_SCENE_PT_scene_debug,
           AMTH_SCENE_OT_refresh,
           AMTH_SCENE_OT_cycles_shader_list_nodes,
           AMTH_SCENE_OT_cycles_shader_list_nodes_clear,
           AMTH_SCENE_OT_amaranth_object_select,
           AMTH_SCENE_OT_list_missing_node_links,
           AMTH_SCENE_OT_list_missing_material_slots,
           AMTH_SCENE_OT_list_missing_material_slots_clear,
           AMTH_SCENE_OT_blender_instance_open,
           AMTH_SCENE_OT_layers_render_save,
           AMTH_SCENE_OT_layers_render_view,
           AMTH_SCENE_OT_layers_render_set_individual,
           AMTH_SCENE_OT_layers_render_clear,
           AMTH_WM_OT_save_reload,
           AMTH_MESH_OT_find_asymmetric,
           AMTH_MESH_OT_make_symmetric,
           AMTH_NODE_OT_AddTemplateVignette,
           AMTH_NODE_OT_AddTemplateVectorBlur,
           AMTH_NODE_MT_amaranth_templates,
           AMTH_FILE_OT_directory_current_blend,
           AMTH_FILE_OT_directory_go_to,
           AMTH_NODE_PT_indices,
           AMTH_NODE_PT_simplify,
           AMTH_NODE_OT_toggle_mute,
           AMTH_NODE_OT_show_active_node_image,
           AMTH_VIEW3D_OT_render_border_camera,
           AMTH_VIEW3D_OT_show_only_render,
           AMTH_OBJECT_OT_select_meshlights,
           AMTH_OBJECT_OT_id_dupligroup,
           AMTH_OBJECT_OT_id_dupligroup_clear,
           AMTH_OBJECT_OT_material_remove_unassigned,
           AMTH_OBJECT_OT_wire_toggle,
           AMTH_OBJECT_OT_meshlight_add,
           AMTH_POSE_OT_paths_clear_all,
           AMTH_POSE_OT_paths_frame_match,
           AMTH_RENDER_OT_cycles_samples_percentage,
           AMTH_RENDER_OT_cycles_samples_percentage_set,
           AMTH_FILE_PT_libraries,
           AMTH_SCREEN_OT_frame_jump,
           AMTH_SCREEN_OT_keyframe_jump_inbetween)

addon_keymaps = []

def register():

    bpy.utils.register_class(AmaranthToolsetPreferences)

    # UI: Register the panel
    init_properties()
    for c in classes:
        bpy.utils.register_class(c)

    bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
    bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
    bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
    bpy.types.VIEW3D_MT_object_specials.append(button_frame_current)
    bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
    bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
    bpy.types.VIEW3D_HT_header.append(ui_layers_for_render_header)

    bpy.types.INFO_MT_file.append(button_save_reload)
    bpy.types.INFO_HT_header.append(stats_scene)

    bpy.types.TIME_HT_header.append(label_timeline_extra_info)

    bpy.types.NODE_HT_header.append(node_templates_pulldown)
    bpy.types.NODE_HT_header.append(node_stats)
    bpy.types.NODE_HT_header.append(node_shader_extra)
    bpy.types.NODE_PT_active_node_properties.append(ui_node_normal_values)

    if cycles_exists:
        bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
        bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)

    bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)

    bpy.types.SCENE_PT_simplify.append(unsimplify_ui)

    bpy.types.DATA_PT_display.append(pose_motion_paths_ui)

    bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
    bpy.types.RENDER_PT_output.append(ui_render_output_z)

    bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)

    bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)

    bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)

    bpy.types.OBJECT_PT_relations.append(ui_object_id_duplis)

    bpy.types.MATERIAL_MT_specials.append(ui_material_remove_unassigned)

    bpy.types.USERPREF_PT_edit.append(ui_userpreferences_edit)

    bpy.types.RENDERLAYER_PT_layers.append(ui_layers_for_render)

    bpy.types.VIEW3D_PT_view3d_display.append(ui_object_wire_toggle)

    bpy.types.INFO_MT_mesh_add.append(ui_menu_lamps_add)

    bpy.app.handlers.render_pre.append(unsimplify_render_pre)
    bpy.app.handlers.render_post.append(unsimplify_render_post)

    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
        km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'DOUBLE_CLICK')

        km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
        kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS')
        kmi.properties.name = "AMTH_NODE_MT_amaranth_templates"

        km = kc.keymaps.new(name='Window')
        kmi = km.keymap_items.new('scene.refresh', 'F5', 'PRESS', shift=False, ctrl=False)
        kmi = km.keymap_items.new('wm.save_reload', 'W', 'PRESS', shift=True, ctrl=True)

        km = kc.keymaps.new(name='Frames')
        kmi = km.keymap_items.new('screen.amaranth_frame_jump', 'UP_ARROW', 'PRESS', shift=True)
        kmi.properties.forward = True
        kmi = km.keymap_items.new('screen.amaranth_frame_jump', 'DOWN_ARROW', 'PRESS', shift=True)
        kmi.properties.forward = False

        km = kc.keymaps.new(name='Frames')
        kmi = km.keymap_items.new('screen.amth_keyframe_jump_inbetween', 'UP_ARROW', 'PRESS', shift=True, ctrl=True)
        kmi.properties.backwards = False
        kmi = km.keymap_items.new('screen.amth_keyframe_jump_inbetween', 'DOWN_ARROW', 'PRESS', shift=True, ctrl=True)
        kmi.properties.backwards = True

        km = kc.keymaps.new(name='3D View', space_type='VIEW_3D')
        kmi = km.keymap_items.new('view3d.show_only_render', 'Z', 'PRESS', shift=True, alt=True)

        km = kc.keymaps.new(name='Graph Editor', space_type='GRAPH_EDITOR')
        kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
        kmi.properties.data_path = 'area.type'
        kmi.properties.value = 'DOPESHEET_EDITOR'

        km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
        kmi = km.keymap_items.new('wm.context_set_enum', 'TAB', 'PRESS', ctrl=True)
        kmi.properties.data_path = 'area.type'
        kmi.properties.value = 'GRAPH_EDITOR'

        km = kc.keymaps.new(name='Dopesheet', space_type='DOPESHEET_EDITOR')
        kmi = km.keymap_items.new('wm.context_toggle_enum', 'TAB', 'PRESS', shift=True)
        kmi.properties.data_path = 'space_data.mode'
        kmi.properties.value_1 = 'ACTION'
        kmi.properties.value_2 = 'DOPESHEET'

        addon_keymaps.append((km, kmi))

def unregister():

    bpy.utils.unregister_class(AmaranthToolsetPreferences)

    for c in classes:
        bpy.utils.unregister_class(c)

    bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
    bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
    bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
    bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
    bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
    bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
    bpy.types.VIEW3D_HT_header.remove(ui_layers_for_render_header)

    bpy.types.INFO_MT_file.remove(button_save_reload)
    bpy.types.INFO_HT_header.remove(stats_scene)

    bpy.types.TIME_HT_header.remove(label_timeline_extra_info)

    bpy.types.NODE_HT_header.remove(node_templates_pulldown)
    bpy.types.NODE_HT_header.remove(node_stats)
    bpy.types.NODE_HT_header.remove(node_shader_extra)
    bpy.types.NODE_PT_active_node_properties.remove(ui_node_normal_values)

    if cycles_exists:
        bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
        bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)

    bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)

    bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)

    bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)

    bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
    bpy.types.RENDER_PT_output.remove(ui_render_output_z)

    bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)

    bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)

    bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)

    bpy.types.OBJECT_PT_relations.remove(ui_object_id_duplis)

    bpy.types.MATERIAL_MT_specials.remove(ui_material_remove_unassigned)

    bpy.types.USERPREF_PT_edit.remove(ui_userpreferences_edit)

    bpy.types.RENDERLAYER_PT_layers.remove(ui_layers_for_render)

    bpy.types.VIEW3D_PT_view3d_display.remove(ui_object_wire_toggle)

    bpy.types.INFO_MT_mesh_add.remove(ui_menu_lamps_add)

    bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
    bpy.app.handlers.render_post.remove(unsimplify_render_post)

    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()
    
    clear_properties()

if __name__ == "__main__":
    register()