Move 'render auto tile size' addon to main repo.
[blender-addons-contrib.git] / scene_amaranth_toolset.py
index 55c52c108985ac8eee2328866e6f9638854a3280..4dcff3425e183754c55d695a024d239ce57850e6 100755 (executable)
@@ -18,9 +18,9 @@
 
 bl_info = {
     "name": "Amaranth Toolset",
-    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin",
-    "version": (0, 8, 1),
-    "blender": (2, 70),
+    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne",
+    "version": (0, 9, 5),
+    "blender": (2, 71),
     "location": "Everywhere!",
     "description": "A collection of tools and settings to improve productivity",
     "warning": "",
@@ -32,11 +32,26 @@ bl_info = {
 import bpy
 import bmesh
 from bpy.types import Operator, AddonPreferences, Panel, Menu
-from bpy.props import BoolProperty
+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
+cycles_exists = False
+
+
+def check_cycles_exists():
+    global cycles_exists
+    cycles_exists = ('cycles' in dir(bpy.types.Scene))
+    return cycles_exists
+
+
+check_cycles_exists()
+
+
 # Preferences
 class AmaranthToolsetPreferences(AddonPreferences):
     bl_idname = __name__
@@ -72,6 +87,24 @@ class AmaranthToolsetPreferences(AddonPreferences):
             default=True,
             )
 
+    frames_jump = IntProperty(
+                name="Frames",
+                description="Number of frames to jump forward/backward",
+                default=10,
+                min=1)
+
+    use_framerate = BoolProperty(
+        name="Framerate Jump",
+        description="Jump the amount of frames forward/backward that you have set as your framerate",
+        default=False,
+    )
+
+    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
@@ -94,6 +127,8 @@ class AmaranthToolsetPreferences(AddonPreferences):
         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.prop(self, "use_framerate")
 
         sub.separator()
 
@@ -109,7 +144,7 @@ class AmaranthToolsetPreferences(AddonPreferences):
             text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
 
         sub.separator()
-        sub.label(text="") # General
+        sub.label(text="") # General icon
         sub.label(
             text="Quickly save and reload the current file (no warning!). "
                  "File menu or Ctrl+Shift+W")
@@ -117,7 +152,9 @@ class AmaranthToolsetPreferences(AddonPreferences):
             text="SMPTE Timecode and frames left/ahead on Timeline's header")
         sub.label(
             text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
-
+        sub.label(text="Save the set of layers that should be activated for a final render")
+        sub.label(text="Jump the amount of frames forward/backward that you've set as your framerate")
+        
         sub.separator()
         sub.label(text="") # Nodes
         sub.label(
@@ -141,7 +178,7 @@ def init_properties():
         default=True,
         description="If disabled, display all available indices")
 
-    test_items = [
+    nodes_compo_types = [
         ("ALL", "All Types", "", 0),
         ("BLUR", "Blur", "", 1),
         ("BOKEHBLUR", "Bokeh Blur", "", 2),
@@ -150,47 +187,79 @@ def init_properties():
         ("R_LAYERS", "Render Layer", "", 5)
         ]
 
-    nodes_compo.types = bpy.props.EnumProperty(
-        items=test_items, name = "Types")
+    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
-    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),
-        ]
-
-    scene.amaranth_cycles_node_types = bpy.props.EnumProperty(
-        items=cycles_shader_node_types, name = "Shader")
-
-    scene.amaranth_debug_scene_list_lamps = BoolProperty(
+    if check_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="Lamps List",
-        description="Display a list of all the lamps")
+        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")
 
-    global materials
+    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 = (
@@ -198,7 +267,19 @@ def clear_properties():
         "simplify_status",
         "use_matching_indices",
         "use_simplify_nodes_vector",
-        "status"
+        "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
@@ -206,8 +287,70 @@ def clear_properties():
         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 SCENE_OT_refresh(Operator):
+class AMTH_SCENE_OT_refresh(Operator):
     """Refresh the current scene"""
     bl_idname = "scene.refresh"
     bl_label = "Refresh!"
@@ -230,7 +373,7 @@ def button_refresh(self, context):
     if preferences.use_scene_refresh:
         self.layout.separator()
         self.layout.operator(
-            SCENE_OT_refresh.bl_idname,
+            AMTH_SCENE_OT_refresh.bl_idname,
             text="Refresh!",
             icon='FILE_REFRESH')
 # // FEATURE: Refresh Scene!
@@ -245,7 +388,7 @@ def save_reload(self, context, path):
     else:
         bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
 
-class WM_OT_save_reload(Operator):
+class AMTH_WM_OT_save_reload(Operator):
     """Save and Reload the current blend file"""
     bl_idname = "wm.save_reload"
     bl_label = "Save & Reload"
@@ -263,7 +406,7 @@ def button_save_reload(self, context):
     if preferences.use_file_save_reload:
         self.layout.separator()
         self.layout.operator(
-            WM_OT_save_reload.bl_idname,
+            AMTH_WM_OT_save_reload.bl_idname,
             text="Save & Reload",
             icon='FILE_REFRESH')
 # // FEATURE: Save & Reload
@@ -291,6 +434,9 @@ def label_timeline_extra_info(self, context):
     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
@@ -310,7 +456,7 @@ def label_timeline_extra_info(self, context):
 # // FEATURE: Timeline Time + Frames Left
 
 # FEATURE: Directory Current Blend
-class FILE_OT_directory_current_blend(Operator):
+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"
@@ -323,13 +469,13 @@ def button_directory_current_blend(self, context):
 
     if bpy.data.filepath:
         self.layout.operator(
-            FILE_OT_directory_current_blend.bl_idname,
+            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 FILE_PT_libraries(Panel):
+class AMTH_FILE_PT_libraries(Panel):
     bl_space_type = 'FILE_BROWSER'
     bl_region_type = 'CHANNELS'
     bl_label = "Libraries"
@@ -341,7 +487,7 @@ class FILE_PT_libraries(Panel):
         libslist = []
 
         # Build the list of folders from libraries
-        import os
+        import os.path
 
         for lib in libs:
             directory_name = os.path.dirname(lib.filepath)
@@ -352,37 +498,37 @@ class FILE_PT_libraries(Panel):
         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 = box.row()
+                    row = col.row()
                     row.alignment = 'LEFT'
                     props = row.operator(
-                        FILE_OT_directory_go_to.bl_idname,
+                        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 FILE_OT_directory_go_to(Operator):
+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 NODE_OT_AddTemplateVignette(Operator):
+class AMTH_NODE_OT_AddTemplateVignette(Operator):
     bl_idname = "node.template_add_vignette"
     bl_label = "Add Vignette"
     bl_description = "Add a vignette effect"
@@ -400,6 +546,7 @@ class NODE_OT_AddTemplateVignette(Operator):
         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')
 
@@ -422,21 +569,27 @@ class NODE_OT_AddTemplateVignette(Operator):
         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 tree.nodes.active:
-            blur.location = tree.nodes.active.location
-            blur.location += Vector((330.0, -250.0))
+        if has_act:
+            overlay.location = tree.nodes.active.location
+            overlay.location += Vector((350.0, 0.0))
         else:
-            blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
+            overlay.location += Vector((space.cursor_location[0], space.cursor_location[1]))
 
-        ellipse.location = blur.location
-        ellipse.location += Vector((-300.0, 0))
+        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))
-
-        overlay.location = ramp.location
-        overlay.location += Vector((240.0, 275.0))
+        ramp.outputs['Alpha'].hide = True
 
         for node in {ellipse, blur, ramp, overlay}:
             node.select = True
@@ -447,7 +600,7 @@ class NODE_OT_AddTemplateVignette(Operator):
         frame = ellipse.parent
         frame.label = 'Vignette'
         frame.use_custom_color = True
-        frame.color = (0.783538, 0.0241576, 0.0802198)
+        frame.color = (0.1, 0.1, 0.1)
         
         overlay.parent = None
         overlay.label = 'Vignette Overlay'
@@ -457,9 +610,60 @@ class NODE_OT_AddTemplateVignette(Operator):
 
         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 NODE_MT_amaranth_templates(Menu):
-    bl_idname = 'NODE_MT_amaranth_templates'
+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"
@@ -467,18 +671,21 @@ class NODE_MT_amaranth_templates(Menu):
     def draw(self, context):
         layout = self.layout
         layout.operator(
-            NODE_OT_AddTemplateVignette.bl_idname,
+            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("NODE_MT_amaranth_templates",
-            icon="RADIO")
+        row.menu("AMTH_NODE_MT_amaranth_templates",
+            icon="NODETREE")
 # // FEATURE: Node Templates
 
 def node_stats(self,context):
@@ -497,7 +704,7 @@ def node_stats(self,context):
             row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
 
 # FEATURE: Simplify Compo Nodes
-class NODE_PT_simplify(Panel):
+class AMTH_NODE_PT_simplify(Panel):
     '''Simplify Compositor Panel'''
     bl_space_type = 'NODE_EDITOR'
     bl_region_type = 'UI'
@@ -517,7 +724,7 @@ class NODE_PT_simplify(Panel):
 
         if node_tree is not None:
             layout.prop(node_tree, 'types')
-            layout.operator(NODE_OT_toggle_mute.bl_idname,
+            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')
         
@@ -525,7 +732,7 @@ class NODE_PT_simplify(Panel):
                 layout.label(text="This will also toggle the Vector pass {}".format(
                                     "on" if node_tree.toggle_mute else "off"), icon="INFO")
 
-class NODE_OT_toggle_mute(Operator):
+class AMTH_NODE_OT_toggle_mute(Operator):
     """"""
     bl_idname = "node.toggle_mute"
     bl_label = "Toggle Mute"
@@ -583,7 +790,7 @@ class NODE_OT_toggle_mute(Operator):
         
 
 # FEATURE: OB/MA ID panel in Node Editor
-class NODE_PT_indices(Panel):
+class AMTH_NODE_PT_indices(Panel):
     '''Object / Material Indices Panel'''
     bl_space_type = 'NODE_EDITOR'
     bl_region_type = 'UI'
@@ -734,23 +941,16 @@ def stats_scene(self, context):
         cameras_selected = 0
         meshlights = 0
         meshlights_visible = 0
-    
+
         for ob in context.scene.objects:
-            if ob.material_slots:
-                for ma in ob.material_slots:
-                    if ma.material:
-                        if ma.material.node_tree:
-                            for no in ma.material.node_tree.nodes:
-                                if no.type == 'EMISSION':
-                                    for ou in no.outputs:
-                                        if ou.links:
-                                            meshlights = meshlights + 1
-                                            if ob in context.visible_objects:
-                                                meshlights_visible = meshlights_visible + 1
-                                            break
+            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 = cameras_selected + 1
+                    cameras_selected += 1
     
         meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
     
@@ -762,7 +962,7 @@ def stats_scene(self, context):
 # //FEATURE: Extra Info Stats
 
 # FEATURE: Camera Bounds as Render Border
-class VIEW3D_OT_render_border_camera(Operator):
+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"
@@ -788,7 +988,7 @@ def button_render_border_camera(self, context):
     if view3d.view_perspective == 'CAMERA':
         layout = self.layout
         layout.separator()
-        layout.operator(VIEW3D_OT_render_border_camera.bl_idname,
+        layout.operator(AMTH_VIEW3D_OT_render_border_camera.bl_idname,
                         text="Camera as Render Border", icon="FULLSCREEN_ENTER")
 
 # //FEATURE: Camera Bounds as Render Border
@@ -807,7 +1007,7 @@ def button_camera_passepartout(self, context):
             layout.prop(cam, "show_passepartout")
 
 # FEATURE: Show Only Render with Alt+Shift+Z
-class VIEW3D_OT_show_only_render(Operator):
+class AMTH_VIEW3D_OT_show_only_render(Operator):
     bl_idname = "view3d.show_only_render"
     bl_label = "Show Only Render"
 
@@ -824,10 +1024,12 @@ class VIEW3D_OT_show_only_render(Operator):
 # FEATURE: Display Active Image Node on Image Editor
 # Made by Sergey Sharybin, tweaks from Bassam Kurdali
 image_nodes = {"CompositorNodeImage",
+               "CompositorNodeViewer",
+               "CompositorNodeComposite",
                "ShaderNodeTexImage",
                "ShaderNodeTexEnvironment"}
 
-class NODE_OT_show_active_node_image(Operator):
+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"
@@ -838,19 +1040,25 @@ class NODE_OT_show_active_node_image(Operator):
         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:
+
+                if active_node.bl_idname in image_nodes:
                     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
+                                    if active_node.bl_idname == 'CompositorNodeViewer':
+                                        space.image = bpy.data.images['Viewer Node']
+                                    elif active_node.bl_idname == 'CompositorNodeComposite':
+                                        space.image = bpy.data.images['Render Result']
+                                    elif active_node.image:
+                                        space.image = active_node.image
                             break
-    
+
         return {'FINISHED'}
 # // FEATURE: Display Active Image Node on Image Editor
 
 # FEATURE: Select Meshlights
-class OBJECT_OT_select_meshlights(Operator):
+class AMTH_OBJECT_OT_select_meshlights(Operator):
     """Select light emitting meshes"""
     bl_idname = "object.select_meshlights"
     bl_label = "Select Meshlights"
@@ -865,14 +1073,9 @@ class OBJECT_OT_select_meshlights(Operator):
         bpy.ops.object.select_all(action='DESELECT')
 
         for ob in context.scene.objects:
-            if ob.material_slots:
-                for ma in ob.material_slots:
-                    if ma.material:
-                        if ma.material.node_tree:
-                            for no in ma.material.node_tree.nodes:
-                                if no.type == 'EMISSION':
-                                    ob.select = True
-                                    context.scene.objects.active = ob
+            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")
@@ -880,13 +1083,12 @@ class OBJECT_OT_select_meshlights(Operator):
         return {'FINISHED'}
 
 def button_select_meshlights(self, context):
-    
-    if context.scene.render.engine == 'CYCLES':
+    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 MESH_OT_find_asymmetric(Operator):
+class AMTH_MESH_OT_find_asymmetric(Operator):
     """
     Find asymmetric vertices
     """
@@ -937,7 +1139,7 @@ class MESH_OT_find_asymmetric(Operator):
 
         return {'FINISHED'}
 
-class MESH_OT_make_symmetric(Operator):
+class AMTH_MESH_OT_make_symmetric(Operator):
     """
     Make symmetric
     """
@@ -1011,58 +1213,107 @@ class MESH_OT_make_symmetric(Operator):
         return {'FINISHED'}
 # // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
 
-# FEATURE: Cycles Render Samples per Scene
+# FEATURE: Cycles Render Sampling Extra
 def render_cycles_scene_samples(self, context):
 
     layout = self.layout
-
     scenes = bpy.data.scenes
     scene = context.scene
-    cscene = scene.cycles
+    render = scene.render
+    if cycles_exists:
+        cscene = scene.cycles
+        list_sampling = scene.amaranth_cycles_list_sampling
 
-    if (len(bpy.data.scenes) > 1):
+    # Set Render Samples
+    if cycles_exists and cscene.progressive == 'BRANCHED_PATH':
         layout.separator()
+        split = layout.split()
+        col = split.column()
 
-        layout.label(text="Samples Per Scene:")
+        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')
 
-        if cscene.progressive == 'PATH':
-            for s in bpy.data.scenes:
-                if s != scene:
-                    if s.render.engine == 'CYCLES':
-                        cscene = s.cycles
-        
-                        split = layout.split()
-                        col = split.column()
-                        sub = col.column(align=True)
-        
-                        sub.label(text="%s" % s.name)
-        
-                        col = split.column()
-                        sub = col.column(align=True)
-                        sub.prop(cscene, "samples", text="Samples")
-                    else:
-                        layout.label(text="Scene: '%s' is not using Cycles" % s.name)
+        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:
-            for s in bpy.data.scenes:
-                if s != scene:
-                    if s.render.engine == 'CYCLES':
-                        cscene = s.cycles
-        
-                        split = layout.split()
-                        col = split.column()
-                        sub = col.column(align=True)
-        
-                        sub.label(text="%s" % s.name)
-        
-                        col = split.column()
-                        sub = col.column(align=True)
-                        sub.prop(cscene, "aa_samples", text="AA Samples")
-                    else:
-                        layout.label(text="Scene: '%s' is not using Cycles" % s.name)
-# // FEATURE: Cycles Render Samples per Scene
+            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 POSE_OT_paths_clear_all(Operator):
+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"
@@ -1080,7 +1331,7 @@ class POSE_OT_paths_clear_all(Operator):
             b.select = False
         return {'FINISHED'}
 
-class POSE_OT_paths_frame_match(Operator):
+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"
@@ -1133,7 +1384,7 @@ def pose_motion_paths_ui(self, context):
         col.label(text="Select Bones First", icon="ERROR")
 
     col = split.column(align=True)
-    col.operator(POSE_OT_paths_frame_match.bl_idname,
+    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"
@@ -1150,7 +1401,7 @@ def pose_motion_paths_ui(self, context):
         row.prop(avs.motion_path, "frame_after", text="After")
 
     layout.separator()
-    layout.operator(POSE_OT_paths_clear_all.bl_idname, icon="X")
+    layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
 # // FEATURE: Motion Paths Extras
 
 # FEATURE: Final Render Resolution Display
@@ -1193,103 +1444,379 @@ def node_shader_extra(self, context):
 # // FEATURE: Shader Nodes Extra Info
 
 # FEATURE: Scene Debug
-class SCENE_OT_cycles_shader_list_nodes(Operator):
+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 context.scene.render.engine == 'CYCLES'
+        return cycles_exists and context.scene.render.engine == 'CYCLES'
 
     def execute(self, context):
         node_type = context.scene.amaranth_cycles_node_types
-        count_ma = 0
         roughness = False
-        materials = []
-        global materials
+        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:
-                                if no.type in ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']:
+                                connected = True
+                                if no.type in shaders_roughness:
                                     roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
                                 else:
                                     roughness = False
                             else:
-                                print('Note: \nOutput from "%s" node' % node_type,
-                                      'in material "%s"' % ma.name,
-                                      'not connected',
-                                      '\n')
+                                connected = False
+                                print(print_unconnected)
 
-                            materials = list(set(materials))
-
-                            count_ma = len(materials)
-
-                            if ma.name not in materials:
-                                materials.append('%s%s [%s] %s%s' % (
+                            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 ''))
-
-        if count_ma == 0:
-            print("* No materials with nodes type %s found" % node_type)
-
+                                    ' - [%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 %s materials using %s was found \n" % (
-                    count_ma + 1, node_type))
+            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 materials:
-                print('%02d. %s' % (count + 1, materials[count]))
-                count = count + 1
+            for mat in self.__class__.materials:
+                print('%02d. %s' % (count+1, self.__class__.materials[count]))
+                count += 1
             print("\n")
 
-        materials = list(set(materials))
+        self.__class__.materials = sorted(list(set(self.__class__.materials)))
 
         return {'FINISHED'}
 
-class SCENE_OT_cycles_shader_list_nodes_clear(Operator):
+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):
-        materials[:] = []
+        AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
+        print("* Cleared Cycles Materials List")
         return {'FINISHED'}
 
-class SCENE_OT_amaranth_debug_lamp_select(Operator):
-    '''Select Lamp'''
-    bl_idname = "scene.amaranth_debug_lamp_select"
-    bl_label = "Select Lamp"
-    lamp = bpy.props.StringProperty()
+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.lamp:
-            lamp = bpy.data.objects[self.lamp]
+        if self.object:
+            object = bpy.data.objects[self.object]
 
             bpy.ops.object.select_all(action='DESELECT')
-            lamp.select = True
-            context.scene.objects.active = lamp
+            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'}    
+        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))
 
-class SCENE_PT_scene_debug(Panel):
+            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
@@ -1298,149 +1825,20 @@ class SCENE_PT_scene_debug(Panel):
         images = bpy.data.images
         lamps = bpy.data.lamps
         images_missing = []
-        list_lamps = scene.amaranth_debug_scene_list_lamps
         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
 
-        if engine == 'CYCLES':
-
-            layout.label(text="List Cycles Materials:")
-
-            split = layout.split(percentage=0.5)
-            split.prop(scene, 'amaranth_cycles_node_types')
-
-            row = split.row(align=True)
-            row.operator(SCENE_OT_cycles_shader_list_nodes.bl_idname,
-                            icon="SORTALPHA")
-            row.operator(SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
-                            icon="X", text="")
-
-            try:
-                materials
-            except NameError:
-                layout.label(text="No materials to show", icon="INFO")
-            else:
-                count = 0
-                layout.label(text="%s %s found" % (len(materials),
-                    'material' if len(materials) < 2 else 'materials'), icon="INFO")
-                for mat in materials:
-                    count = count + 1
-                    layout.label(text='%s' % (materials[count-1]), icon="MATERIAL")
-
-        # List Lamps
+        # List Missing Images
         box = layout.box()
         row = box.row(align=True)
         split = row.split()
         col = split.column()
-        
-        if lamps:
-            row = col.row(align=True)
-            row.alignment = 'LEFT'
-            row.prop(scene, 'amaranth_debug_scene_list_lamps',
-                        icon="%s" % 'TRIA_DOWN' if list_lamps else 'TRIA_RIGHT',
-                        emboss=False)
-
-            if objects and list_lamps:
-                row = box.row(align=True)
-                split = row.split(percentage=0.42)
-                col = split.column()
-                col.label(text="Name")
-
-                split = split.split(percentage=0.1)
-                col = split.column()
-                col.label(text="", icon="BLANK1")
-                if engine in ['CYCLES', 'BLENDER_RENDER']:
-                    if engine == 'BLENDER_RENDER':
-                        split = split.split(percentage=0.7)
-                    else:
-                        split = split.split(percentage=0.35)
-                    col = split.column()
-                    col.label(text="Samples")
-
-                if engine == 'CYCLES':
-                    split = split.split(percentage=0.35)
-                    col = split.column()
-                    col.label(text="Size")
-
-                split = split.split(percentage=0.8)
-                col = split.column()
-                col.label(text="Visibility")
 
-                for ob in objects:
-                    if ob and ob.type == 'LAMP':
-                        lamp = ob.data
-                        clamp = ob.data.cycles
-
-                        row = box.row(align=True)
-                        split = row.split(percentage=0.5)
-                        col = split.column()
-                        row = col.row()
-                        row.alignment = 'LEFT'
-                        row.active = ob.name in context.scene.objects
-                        row.operator("scene.amaranth_debug_lamp_select",
-                                    text='%s %s%s' % (
-                                        " [L] " if ob.library else "",
-                                        ob.name,
-                                        "" if ob.name in context.scene.objects else " [Not in Scene]"),
-                                    icon="LAMP_%s" % ob.data.type,
-                                    emboss=False).lamp = ob.name
-
-                        if engine == 'CYCLES':
-                            split = split.split(percentage=0.35)
-                            col = split.column()
-                            if scene.cycles.progressive == 'BRANCHED_PATH':
-                                col.prop(clamp, "samples", text="")
-                            if scene.cycles.progressive == 'PATH':
-                               col.label(text="N/A")
-                           
-                        if engine == 'BLENDER_RENDER':
-                            split = split.split(percentage=0.7)
-                            col = split.column()
-                            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")
-
-                        if engine == 'CYCLES':
-                            split = split.split(percentage=0.4)
-                            col = split.column()    
-                            if lamp.type in ['POINT','SUN', 'SPOT']:
-                                col.label(text="%.2f" % lamp.shadow_soft_size)
-                            elif lamp.type == 'HEMI':
-                                col.label(text="N/A")
-                            else:
-                                col.label(text="%.2f" % lamp.size)
-
-                        split = split.split(percentage=0.8)
-                        col = split.column()
-                        row = col.row(align=True)
-                        row.prop(ob, "hide", text="", emboss=False)
-                        row.prop(ob, "hide_render", text="", emboss=False)
-
-                        split = split.split(percentage=0.3)
-                        col = split.column()
-                        col.label(text="", icon="%s" % "TRIA_LEFT" if ob == ob_act else "BLANK1")
-
-        else:
-            row = col.row(align=True)
-            row.alignment = 'LEFT'
-            row.label(text="Lamps List", icon="RIGHTARROW_THIN")
-
-            split = split.split()
-            col = split.column()
-
-            col.label(text="No Lamps", icon="LAMP_DATA")
-
-        # List Missing Images
         if images:
             import os.path
 
@@ -1451,12 +1849,8 @@ class SCENE_PT_scene_debug(Panel):
                             '[L] ' if im.library else '',
                             im.name, im.users,
                             ' [F]' if im.use_fake_user else ''),
-                            im.filepath if im.filepath else 'No Filepath'])
-
-            box = layout.box()
-            row = box.row(align=True)
-            split = row.split()
-            col = split.column()
+                            im.filepath if im.filepath else 'No Filepath',
+                            im.library.filepath if im.library else ''])
 
             if images_missing:
                 row = col.row(align=True)
@@ -1478,7 +1872,14 @@ class SCENE_PT_scene_debug(Panel):
                     for mis in images_missing:
                         col.label(text=mis[0],
                          icon="IMAGE_DATA")
-                        col.label(text=mis[1], icon="BLANK1")
+                        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)
@@ -1492,23 +1893,160 @@ class SCENE_PT_scene_debug(Panel):
                              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")
 
-# // FEATURE: Scene Debug
+        # 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")
 
-# FEATURE: Color Management Presets
-class 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
+            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()
 
-class 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 = "SCENE_MT_color_management_presets"
+        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"
@@ -1531,43 +2069,992 @@ def ui_color_management_presets(self, context):
     layout = self.layout
 
     row = layout.row(align=True)
-    row.menu("SCENE_MT_color_management_presets", text=bpy.types.SCENE_MT_color_management_presets.bl_label)
+    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
 
-classes = (SCENE_MT_color_management_presets,
-           AddPresetColorManagement,
-           SCENE_PT_scene_debug,
-           SCENE_OT_refresh,
-           SCENE_OT_cycles_shader_list_nodes,
-           SCENE_OT_cycles_shader_list_nodes_clear,
-           SCENE_OT_amaranth_debug_lamp_select,
-           WM_OT_save_reload,
-           MESH_OT_find_asymmetric,
-           MESH_OT_make_symmetric,
-           NODE_OT_AddTemplateVignette,
-           NODE_MT_amaranth_templates,
-           FILE_OT_directory_current_blend,
-           FILE_OT_directory_go_to,
-           NODE_PT_indices,
-           NODE_PT_simplify,
-           NODE_OT_toggle_mute,
-           NODE_OT_show_active_node_image,
-           VIEW3D_OT_render_border_camera,
-           VIEW3D_OT_show_only_render,
-           OBJECT_OT_select_meshlights,
-           POSE_OT_paths_clear_all,
-           POSE_OT_paths_frame_match,
-           FILE_PT_libraries)
+def ui_sequencer_extra_info(self, context):
 
-addon_keymaps = []
+    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 preferences.use_framerate:
+            framedelta = scene.render.fps
+        else:
+            framedelta = preferences.frames_jump
+        if self.forward:
+            scene.frame_current = scene.frame_current + framedelta
+        else:
+            scene.frame_current = scene.frame_current - framedelta
+
+        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
 
-kmi_defs = (
-    ('wm.call_menu', 'W', False, False, False, (('name', NODE_MT_amaranth_templates.bl_idname),)),
-)
+        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():
 
@@ -1581,43 +3068,81 @@ def register():
     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.VIEW3D_MT_object_specials.append(button_frame_current) # Current Frame
-    bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
-    bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
-
-    bpy.types.TIME_HT_header.append(label_timeline_extra_info) # Timeline Extra Info
+    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)
 
-    bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
+    if check_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.CyclesScene_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)
 
@@ -1637,21 +3162,8 @@ def register():
         kmi.properties.value_1 = 'ACTION'
         kmi.properties.value_2 = 'DOPESHEET'
 
-        km = kc.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
-        km.keymap_items.new("node.show_active_node_image", 'ACTIONMOUSE', 'RELEASE')
-        km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
-
         addon_keymaps.append((km, kmi))
 
-        # copypasted from the awesome node efficiency tools, future hotkeys proof!
-        km = kc.keymaps.new(name='Node Editor', space_type="NODE_EDITOR")
-        for (identifier, key, CTRL, SHIFT, ALT, props) in kmi_defs:
-            kmi = km.keymap_items.new(identifier, key, 'PRESS', ctrl=CTRL, shift=SHIFT, alt=ALT)
-            if props:
-                for prop, value in props:
-                    setattr(kmi.properties, prop, value)
-            addon_keymaps.append((km, kmi))
-
 def unregister():
 
     bpy.utils.unregister_class(AmaranthToolsetPreferences)
@@ -1662,33 +3174,52 @@ def unregister():
     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.INFO_MT_file.remove(button_save_reload)
-    bpy.types.INFO_HT_header.remove(stats_scene)
-
     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)
 
-    bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
+    if check_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.CyclesScene_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)
 
@@ -1700,3 +3231,4 @@ def unregister():
 
 if __name__ == "__main__":
     register()
+