Amaranth: Fix for changes on File Browser
[blender-addons-contrib.git] / scene_amaranth_toolset.py
index e324b9e..094c81c 100755 (executable)
@@ -18,9 +18,9 @@
 
 bl_info = {
     "name": "Amaranth Toolset",
-    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin",
-    "version": (0, 8, 9),
-    "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": "",
@@ -33,12 +33,25 @@ import bpy
 import bmesh
 from bpy.types import Operator, AddonPreferences, Panel, Menu
 from bpy.props import (BoolProperty, EnumProperty,
-                       FloatProperty, IntProperty,
-                       StringProperty)
+                       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__
@@ -80,6 +93,18 @@ class AmaranthToolsetPreferences(AddonPreferences):
                 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
@@ -102,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()
 
@@ -117,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")
@@ -125,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(
@@ -166,50 +195,71 @@ def init_properties():
 
     # 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),
-        ("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_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")
 
-    scene.amaranth_cycles_list_sampling = BoolProperty(
-        default=False,
-        name="Samples Per:")
-
     bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
     bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector
-    
-    bpy.types.CyclesRenderSettings.use_samples_final = BoolProperty(
-        name="Use Final Render Samples",
-        description="Use current shader samples as final render samples",
-        default=False,)
+
+    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 = (
@@ -221,11 +271,15 @@ def clear_properties():
         "types",
         "toggle_mute",
         "amaranth_cycles_node_types",
-        "amaranth_debug_scene_list_lamps",
+        "amaranth_lighterscorner_list_meshlights",
         "amaranth_debug_scene_list_missing_images",
         "amarath_cycles_list_sampling",
         "normal_vector",
-        "use_samples_final"
+        "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
@@ -242,25 +296,27 @@ def amaranth_text_startup(context):
     global amth_text
 
     try:
-        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
-                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
-                break
+        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
 
-# Is Emission Material? For select and stats
+# FUNCTION: Check if material has Emission (for select and stats)
 def cycles_is_emission(context, ob):
 
     is_emission = False
@@ -268,12 +324,12 @@ def cycles_is_emission(context, ob):
     if ob.material_slots:
         for ma in ob.material_slots:
             if ma.material:
-                if ma.material.node_tree:
+                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':
+                                    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:
@@ -285,6 +341,14 @@ def cycles_is_emission(context, ob):
                                             is_emission = True
     return is_emission
 
+# FUNCTION: Check if object has keyframes for a specific frame
+def is_keyframe(ob, frame):
+    if ob is not None and ob.animation_data is not None and ob.animation_data.action is not None:
+        for fcu in ob.animation_data.action.fcurves:
+            if frame in (p.co.x for p in fcu.keyframe_points):
+                return True
+    return False
+
 # FEATURE: Refresh Scene!
 class AMTH_SCENE_OT_refresh(Operator):
     """Refresh the current scene"""
@@ -370,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
@@ -410,7 +477,8 @@ def button_directory_current_blend(self, context):
 # FEATURE: Libraries panel on file browser
 class AMTH_FILE_PT_libraries(Panel):
     bl_space_type = 'FILE_BROWSER'
-    bl_region_type = 'CHANNELS'
+    bl_region_type = 'TOOLS'
+    bl_category = "Bookmarks"
     bl_label = "Libraries"
 
     def draw(self, context):
@@ -479,6 +547,7 @@ class AMTH_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')
 
@@ -501,21 +570,27 @@ class AMTH_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 = overlay.location
+        ellipse.location += Vector((-715.0, -400))
+        ellipse.inputs[0].hide = True
+        ellipse.inputs[1].hide = True
 
-        ellipse.location = blur.location
-        ellipse.location += Vector((-300.0, 0))
+        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
@@ -526,7 +601,7 @@ class AMTH_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'
@@ -536,6 +611,57 @@ class AMTH_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 AMTH_NODE_MT_amaranth_templates(Menu):
     bl_idname = 'AMTH_NODE_MT_amaranth_templates'
@@ -545,19 +671,22 @@ class AMTH_NODE_MT_amaranth_templates(Menu):
 
     def draw(self, context):
         layout = self.layout
+        layout.operator(
+            AMTH_NODE_OT_AddTemplateVectorBlur.bl_idname,
+            text="Vector Blur",
+            icon='FORCE_HARMONIC')
         layout.operator(
             AMTH_NODE_OT_AddTemplateVignette.bl_idname,
             text="Vignette",
             icon='COLOR')
 
 def node_templates_pulldown(self, context):
-
     if context.space_data.tree_type == 'CompositorNodeTree':
         layout = self.layout
         row = layout.row(align=True)
         row.scale_x = 1.3
         row.menu("AMTH_NODE_MT_amaranth_templates",
-            icon="RADIO")
+            icon="NODETREE")
 # // FEATURE: Node Templates
 
 def node_stats(self,context):
@@ -896,6 +1025,8 @@ class AMTH_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"}
 
@@ -910,14 +1041,20 @@ class AMTH_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
 
@@ -947,8 +1084,7 @@ class AMTH_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
 
@@ -1082,14 +1218,15 @@ class AMTH_MESH_OT_make_symmetric(Operator):
 def render_cycles_scene_samples(self, context):
 
     layout = self.layout
-
     scenes = bpy.data.scenes
     scene = context.scene
-    cscene = scene.cycles
     render = scene.render
-    list_sampling = scene.amaranth_cycles_list_sampling
+    if cycles_exists:
+        cscene = scene.cycles
+        list_sampling = scene.amaranth_cycles_list_sampling
 
-    if cscene.progressive == 'BRANCHED_PATH':
+    # Set Render Samples
+    if cycles_exists and cscene.progressive == 'BRANCHED_PATH':
         layout.separator()
         split = layout.split()
         col = split.column()
@@ -1116,7 +1253,7 @@ def render_cycles_scene_samples(self, context):
             AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
             text="25%").percent=25
 
-    # List Lamps
+    # List Samples
     if (len(scene.render.layers) > 1) or \
         (len(bpy.data.scenes) > 1):
 
@@ -1149,9 +1286,8 @@ def render_cycles_scene_samples(self, context):
             col.separator()
 
             col.label(text="Scenes:", icon='SCENE_DATA')
-            row = col.row(align=True)
 
-            if cscene.progressive == 'PATH':
+            if cycles_exists and cscene.progressive == 'PATH':
                 for s in bpy.data.scenes:
                     if s != scene:
                         row = col.row(align=True)
@@ -1317,7 +1453,7 @@ class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
 
     @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
@@ -1406,7 +1542,11 @@ class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(Operator):
     """Clear the list below"""
     bl_idname = "scene.cycles_list_nodes_clear"
     bl_label = "Clear Materials List"
-    
+
+    @classmethod
+    def poll(cls, context):
+        return cycles_exists
+
     def execute(self, context):
         AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
         print("* Cleared Cycles Materials List")
@@ -1674,6 +1814,10 @@ class AMTH_SCENE_PT_scene_debug(Panel):
     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
@@ -1682,7 +1826,6 @@ class AMTH_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)
@@ -1691,127 +1834,6 @@ class AMTH_SCENE_PT_scene_debug(Panel):
         missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
         engine = scene.render.engine
 
-        # List Lamps
-        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.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]"),
-                                    icon="LAMP_%s" % ob.data.type,
-                                    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 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")
-                            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)
-
-                        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
         box = layout.box()
         row = box.row(align=True)
@@ -1878,7 +1900,7 @@ class AMTH_SCENE_PT_scene_debug(Panel):
             row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
 
         # List Cycles Materials by Shader
-        if engine == 'CYCLES':
+        if cycles_exists and engine == 'CYCLES':
             box = layout.box()
             split = box.split()
             col = split.column(align=True)
@@ -1975,11 +1997,8 @@ class AMTH_SCENE_PT_scene_debug(Panel):
 
                     row = col.row()
                     row.alignment = 'LEFT'
-                    row.operator(AMTH_SCENE_OT_amaranth_object_select.bl_idname,
-                                text='%s' % (
-                                    missing_material_slots_obs[count-1]),
-                                icon="OBJECT_DATA",
-                                emboss=False).object = missing_material_slots_obs[count-1]
+                    row.label(text='%s' % missing_material_slots_obs[count-1],
+                                icon="OBJECT_DATA")
 
                 if missing_material_slots_lib:
                     col.separator()
@@ -2128,7 +2147,6 @@ class AMTH_OBJECT_OT_id_dupligroup(Operator):
         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
@@ -2237,10 +2255,22 @@ class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
 
     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
@@ -2277,6 +2307,9 @@ class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
             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):
@@ -2286,7 +2319,6 @@ def ui_material_remove_unassigned(self, context):
         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)'''
@@ -2355,10 +2387,14 @@ class AMTH_SCREEN_OT_frame_jump(Operator):
         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 + preferences.frames_jump
+            scene.frame_current = scene.frame_current + framedelta
         else:
-            scene.frame_current = scene.frame_current - preferences.frames_jump
+            scene.frame_current = scene.frame_current - framedelta
 
         return{'FINISHED'}
 
@@ -2371,9 +2407,613 @@ def ui_userpreferences_edit(self, context):
                text="Frames to Jump")
 
 # // FEATURE: Jump forward/backward every N frames
+# FEATURE: Set Layers to Render
+class AMTH_SCENE_OT_layers_render_save(Operator):
+    '''Save the current scene layers as those that should be enabled for final renders'''
+    bl_idname = "scene.amaranth_layers_render_save"
+    bl_label = "Save as Layers for Render"
+
+    def execute(self, context):
+        which = []
+        n = -1
+
+        for l in context.scene.layers:
+            n += 1
+            if l:
+                which.append(n)
+
+        context.scene['amth_layers_for_render'] = which
+        self.report({'INFO'}, "Layers for Render Saved")
+
+        return{'FINISHED'}
+
+class AMTH_SCENE_OT_layers_render_view(Operator):
+    '''Enable the scene layers that should be active for final renders'''
+    bl_idname = "scene.amaranth_layers_render_view"
+    bl_label = "View Layers for Render"
+
+    def execute(self, context):
+        scene = context.scene
+        layers_render = scene['amth_layers_for_render']
+
+        for window in bpy.context.window_manager.windows:
+            screen = window.screen
+
+            for area in screen.areas:
+                if area.type == 'VIEW_3D':
+                    override = {'window': window, 'screen': screen, 'scene': scene, 
+                                'area': area, 'region': area.regions[4],
+                                'blend_data': context.blend_data}
+
+                    if layers_render:
+                        bpy.ops.view3d.layers(override, nr=layers_render[0]+1, extend=False, toggle=False)
+
+                        for n in layers_render:
+                            context.scene.layers[n] = True
+                    else:
+                        bpy.ops.view3d.layers(override, nr=1, extend=False, toggle=False)
+                        self.report({'INFO'}, "No layers set for render")
+
+                    break
+
+        return{'FINISHED'}
+
+class AMTH_SCENE_OT_layers_render_set_individual(Operator):
+    '''Whether this layer should be enabled or not for final renders'''
+    bl_idname = "scene.amaranth_layers_render_set_individual"
+    bl_label = "Set This Layer for Render"
+
+    toggle = BoolProperty()
+    number = IntProperty()
+
+    def execute(self, context):
+        toggle = self.toggle
+        number = self.number
+
+        new_layers = []
+
+        for la in context.scene['amth_layers_for_render']:
+            new_layers.append(la)
+
+        if len(context.scene['amth_layers_for_render']) and number in new_layers:
+            new_layers.remove(number)
+        else:
+            new_layers.append(number)
+
+        # Remove Duplicates
+        new_layers = list(set(new_layers))
+        context.scene['amth_layers_for_render'] = new_layers
+
+        bpy.ops.scene.amaranth_layers_render_view()
+
+        return{'FINISHED'}
+
+class AMTH_SCENE_OT_layers_render_clear(Operator):
+    '''Clear layers for render'''
+    bl_idname = "scene.amaranth_layers_render_clear"
+    bl_label = "Clear Layers for Render"
+
+    def execute(self, context):
+
+        if context.scene.get('amth_layers_for_render'):
+            context.scene['amth_layers_for_render'] = []
+
+        return{'FINISHED'}
+
+def ui_layers_for_render(self, context):
+
+    preferences = context.user_preferences.addons[__name__].preferences
+
+    if preferences.use_layers_for_render:
+        lfr_available = context.scene.get('amth_layers_for_render')
+        if lfr_available:
+            lfr = context.scene['amth_layers_for_render']
+
+        layout = self.layout
+        layout.label("Layers for Rendering:")
+        split = layout.split()
+        col = split.column(align=True)
+        row = col.row(align=True)
+        row.operator(
+            AMTH_SCENE_OT_layers_render_save.bl_idname,
+            text="Replace Layers" if lfr_available else "Save Current Layers for Render",
+            icon="FILE_REFRESH" if lfr_available else 'LAYER_USED')
+
+        if lfr_available:
+            row.operator(
+                AMTH_SCENE_OT_layers_render_clear.bl_idname,
+                icon='X', text="")
+            col = col.column(align=True)
+            col.enabled = True if lfr_available else False
+            col.operator(
+                AMTH_SCENE_OT_layers_render_view.bl_idname,
+                icon="RESTRICT_VIEW_OFF")
+
+            split = split.split()
+            col = split.column(align=True)
+            row = col.row(align=True)
+
+            for n in range(0,5):
+                row.operator(
+                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
+                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
+            row = col.row(align=True)
+            for n in range(10,15):
+                row.operator(
+                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
+                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
+
+            split = split.split()
+            col = split.column(align=True)
+            row = col.row(align=True)
+
+            for n in range(5,10):
+                row.operator(
+                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
+                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
+            row = col.row(align=True)
+            for n in range(15,20):
+                row.operator(
+                    AMTH_SCENE_OT_layers_render_set_individual.bl_idname, text="",
+                    icon='LAYER_ACTIVE' if n in lfr else 'BLANK1').number = n
+
+def ui_layers_for_render_header(self, context):
+
+    preferences = context.user_preferences.addons[__name__].preferences
+
+    if preferences.use_layers_for_render:
+        if context.scene.get('amth_layers_for_render'):
+            self.layout.operator(
+                AMTH_SCENE_OT_layers_render_view.bl_idname,
+                text="", icon="IMGDISPLAY")
+
+# // FEATURE: Set Layers to Render
+# FEATURE: Lighters Corner
+class AMTH_LightersCorner(bpy.types.Panel):
+    """The Lighters Panel"""
+    bl_label = "Lighter's Corner"
+    bl_idname = "AMTH_SCENE_PT_lighters_corner"
+    bl_space_type = 'PROPERTIES'
+    bl_region_type = 'WINDOW'
+    bl_context = "scene"
+
+    @classmethod
+    def poll(cls, context):
+        any_lamps = False
+        for ob in bpy.data.objects:
+            if ob.type == 'LAMP' or cycles_is_emission(context, ob):
+                any_lamps = True
+            else:
+                pass
+        return any_lamps
+
+    def draw_header(self, context):
+        layout = self.layout
+        layout.label(text="", icon="LAMP_SUN")
+
+    def draw(self, context):
+        layout = self.layout
+        scene = context.scene
+        objects =  bpy.data.objects
+        ob_act = context.active_object
+        lamps = bpy.data.lamps
+        list_meshlights = scene.amaranth_lighterscorner_list_meshlights
+        engine = scene.render.engine
+
+        if cycles_exists:
+            layout.prop(scene, "amaranth_lighterscorner_list_meshlights")
+
+        box = layout.box()
+        if lamps:
+            if objects:
+                row = box.row(align=True)
+                split = row.split(percentage=0.45)
+                col = split.column()
+
+                col.label(text="Name")
+
+                if engine in ['CYCLES', 'BLENDER_RENDER']:
+                    if engine == 'BLENDER_RENDER':
+                        split = split.split(percentage=0.7)
+                    else:
+                        split = split.split(percentage=0.27)
+                    col = split.column()
+                    col.label(text="Samples")
+
+                if cycles_exists and engine == 'CYCLES':
+                    split = split.split(percentage=0.2)
+                    col = split.column()
+                    col.label(text="Size")
+
+                split = split.split(percentage=1.0)
+                col = split.column()
+                col.label(text="%sRender Visibility" % 
+                    'Rays /' if cycles_exists else '')
+
+                for ob in objects:
+                    is_lamp = ob.type == 'LAMP'
+                    is_emission = True if cycles_is_emission(context, ob) and list_meshlights else False
+
+                    if ob and is_lamp or is_emission:
+                        lamp = ob.data
+                        if cycles_exists:
+                            clamp = ob.data.cycles
+                            visibility = ob.cycles_visibility
+
+                        row = box.row(align=True)
+                        split = row.split(percentage=1.0)
+                        col = split.column()
+                        row = col.row(align=True)
+                        col.active = ob == ob_act
+                        row.label(icon="%s" % ('LAMP_%s' % ob.data.type if is_lamp else 'MESH_GRID'))
+                        split = row.split(percentage=.45)
+                        col = split.column()
+                        row = col.row(align=True)
+                        row.alignment = 'LEFT'
+                        row.active = True
+                        row.operator(AMTH_SCENE_OT_amaranth_object_select.bl_idname,
+                                    text='%s %s%s' % (
+                                        " [L] " if ob.library else "",
+                                        ob.name,
+                                        "" if ob.name in context.scene.objects else " [Not in Scene]"),
+                                    emboss=False).object = ob.name
+                        if ob.library:
+                            row = col.row(align=True)
+                            row.alignment = "LEFT"
+                            row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
+                                         text=ob.library.filepath,
+                                         icon="LINK_BLEND",
+                                         emboss=False).filepath=ob.library.filepath
+
+                        if cycles_exists and engine == 'CYCLES':
+                            split = split.split(percentage=0.25)
+                            col = split.column()
+                            if is_lamp:
+                                if scene.cycles.progressive == 'BRANCHED_PATH':
+                                    col.prop(clamp, "samples", text="")
+                                if scene.cycles.progressive == 'PATH':
+                                   col.label(text="N/A")
+                            else:
+                              col.label(text="N/A")
+
+                        if engine == 'BLENDER_RENDER':
+                            split = split.split(percentage=0.7)
+                            col = split.column()
+                            if is_lamp:
+                                if lamp.type == 'HEMI':
+                                    col.label(text="Not Available")
+                                elif lamp.type == 'AREA' and lamp.shadow_method == 'RAY_SHADOW':
+                                    row = col.row(align=True)
+                                    row.prop(lamp, "shadow_ray_samples_x", text="X")
+                                    if lamp.shape == 'RECTANGLE':
+                                        row.prop(lamp, "shadow_ray_samples_y", text="Y")
+                                elif lamp.shadow_method == 'RAY_SHADOW':
+                                    col.prop(lamp, "shadow_ray_samples", text="Ray Samples")
+                                elif lamp.shadow_method == 'BUFFER_SHADOW':
+                                    col.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
+                                else:
+                                    col.label(text="No Shadow")
+                            else:
+                              col.label(text="N/A")
+
+                        if cycles_exists and engine == 'CYCLES':
+                            split = split.split(percentage=0.2)
+                            col = split.column()
+                            if is_lamp:
+                                if lamp.type in ['POINT','SUN', 'SPOT']:
+                                    col.label(text="%.2f" % lamp.shadow_soft_size)
+                                elif lamp.type == 'HEMI':
+                                    col.label(text="N/A")
+                                elif lamp.type == 'AREA' and lamp.shape == 'RECTANGLE':
+                                    col.label(text="%.2fx%.2f" % (lamp.size, lamp.size_y))
+                                else:
+                                    col.label(text="%.2f" % lamp.size)
+                            else:
+                              col.label(text="N/A")
+
+                        split = split.split(percentage=1.0)
+                        col = split.column()
+                        row = col.row(align=True)
+                        if cycles_exists:
+                            row.prop(visibility, "camera", text="")
+                            row.prop(visibility, "diffuse", text="")
+                            row.prop(visibility, "glossy", text="")
+                            row.prop(visibility, "shadow", text="")
+                            row.separator()
+                        row.prop(ob, "hide", text="", emboss=False)
+                        row.prop(ob, "hide_render", text="", emboss=False)
+        else:
+            box.label(text="No Lamps", icon="LAMP_DATA")
+
+# FEATURE: Jump to frame in-between next and previous keyframe
+class AMTH_SCREEN_OT_keyframe_jump_inbetween(Operator):
+    '''Jump to half in-between keyframes'''
+    bl_idname = "screen.amth_keyframe_jump_inbetween"
+    bl_label = "Jump to Keyframe In-between"
+
+    backwards = BoolProperty()
+
+    def execute(self, context):
+        back = self.backwards
+
+        scene = context.scene
+        ob = bpy.context.object
+        frame_start = scene.frame_start
+        frame_end = scene.frame_end
+
+        if not context.scene.get('amth_keyframes_jump'):
+            context.scene['amth_keyframes_jump'] = []
+
+        keyframes_list = context.scene['amth_keyframes_jump']
+
+        for f in range(frame_start, frame_end):
+            if ob.is_keyframe(f):
+                 keyframes_list = list(keyframes_list)
+                 keyframes_list.append(f)
+
+        if keyframes_list:
+            i = 0
+            keyframes_list_half = []
+
+            for item in keyframes_list:
+                try:
+                    keyframes_list_half.append(int((keyframes_list[i] + keyframes_list[i+1]) / 2))
+                    i += 1
+                except:
+                    pass
+
+            if len(keyframes_list_half) > 1:
+                if back:
+                    if scene.frame_current == keyframes_list_half[::-1][-1] or \
+                        scene.frame_current < keyframes_list_half[::-1][-1]:
+                        self.report({'INFO'}, "No keyframes behind")
+                    else:
+                        for i in keyframes_list_half[::-1]:
+                            if scene.frame_current > i:
+                                scene.frame_current = i
+                                break
+                else:
+                    if scene.frame_current == keyframes_list_half[-1] or \
+                        scene.frame_current > keyframes_list_half[-1]:
+                        self.report({'INFO'}, "No keyframes ahead")
+                    else:
+                        for i in keyframes_list_half:
+                            if scene.frame_current < i:
+                                scene.frame_current = i
+                                break
+            else:
+                self.report({'INFO'}, "Object has only 1 keyframe")
+        else:
+            self.report({'INFO'}, "Object has no keyframes")
+
+        return{'FINISHED'}
+# // FEATURE: Jump to frame in-between next and previous keyframe
+# FEATURE: Toggle Wire Display
+class AMTH_OBJECT_OT_wire_toggle(Operator):
+    '''Turn on/off wire display on mesh objects'''
+    bl_idname = "object.amth_wire_toggle"
+    bl_label = "Display Wireframe"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    clear = BoolProperty(
+        default=False, name="Clear Wireframe",
+        description="Clear Wireframe Display")
+
+    def execute(self, context):
+
+        scene = context.scene
+        is_all_scenes = scene.amth_wire_toggle_scene_all
+        is_selected = scene.amth_wire_toggle_is_selected
+        is_all_edges = scene.amth_wire_toggle_edges_all
+        is_optimal = scene.amth_wire_toggle_optimal
+        clear = self.clear
+
+        if is_all_scenes:
+            which = bpy.data.objects
+        elif is_selected:
+            if not context.selected_objects:
+                self.report({'INFO'}, "No selected objects")
+            which = context.selected_objects
+        else:
+            which = scene.objects
+
+        if which:
+            for ob in which:
+                if ob and ob.type in {
+                    'MESH', 'EMPTY', 'CURVE',
+                    'META','SURFACE','FONT'}:
+
+                    ob.show_wire = False if clear else True
+                    ob.show_all_edges = is_all_edges
+
+                    for mo in ob.modifiers:
+                        if mo and mo.type == 'SUBSURF':
+                            mo.show_only_control_edges = is_optimal
+
+        return{'FINISHED'}
+
+def ui_object_wire_toggle(self, context):
+
+    scene = context.scene
+
+    self.layout.separator()
+    col = self.layout.column(align=True)
+    row = col.row(align=True)
+    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
+        icon='MOD_WIREFRAME').clear = False
+    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
+        icon='X', text="").clear = True
+    col.separator()
+    row = col.row(align=True)
+    row.prop(scene, "amth_wire_toggle_edges_all")
+    row.prop(scene, "amth_wire_toggle_optimal")
+    row = col.row(align=True)
+    sub = row.row(align=True)
+    sub.active = not scene.amth_wire_toggle_scene_all
+    sub.prop(scene, "amth_wire_toggle_is_selected")
+    sub = row.row(align=True)
+    sub.active = not scene.amth_wire_toggle_is_selected
+    sub.prop(scene, "amth_wire_toggle_scene_all")
+
+# //FEATURE: Toggle Wire Display
+# FEATURE: Add Meshlight
+class AMTH_OBJECT_OT_meshlight_add(bpy.types.Operator):
+    """Add a light emitting mesh"""
+    bl_idname = "object.meshlight_add"
+    bl_label = "Add Meshlight"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    single_sided = BoolProperty(
+            name="Single Sided",
+            default=True,
+            description="Only emit light on one side",
+            )
+
+    visible = BoolProperty(
+            name="Visible on Camera",
+            default=False,
+            description="Show the meshlight on Cycles preview",
+            )
+
+    size = FloatProperty(
+            name="Size",
+            description="Meshlight size",
+            min=0.01, max=100.0,
+            default=1.0,
+            )
+
+    strength = FloatProperty(
+            name="Strength",
+            min=0.01, max=100000.0,
+            default=1.5,
+            step=0.25,
+            )
+
+    temperature = FloatProperty(
+            name="Temperature",
+            min=800, max=12000.0,
+            default=5500.0,
+            step=800.0,
+            )
+
+    rotation = FloatVectorProperty(
+            name="Rotation",
+            subtype='EULER',
+            )
+
+    def execute(self, context):
+        scene = context.scene
+        exists = False
+        number = 1
+
+        for obs in bpy.data.objects:
+            if obs.name.startswith("light_meshlight"):
+                number +=1
+
+        meshlight_name = 'light_meshlight_%.2d' % number
+
+        bpy.ops.mesh.primitive_grid_add(
+            x_subdivisions=4, y_subdivisions=4,
+            rotation=self.rotation, radius=self.size)
+
+        bpy.context.object.name = meshlight_name
+        meshlight = scene.objects[meshlight_name]
+        meshlight.show_wire = True
+        meshlight.show_all_edges = True
+
+        material = bpy.data.materials.get(meshlight_name)
+
+        if not material:
+            material = bpy.data.materials.new(meshlight_name)
+
+        bpy.ops.object.material_slot_add()
+        meshlight.active_material = material
+
+        material.use_nodes = True
+        material.diffuse_color = (1, 0.5, 0)
+        nodes = material.node_tree.nodes
+        links = material.node_tree.links
+
+        # clear default nodes to start nice fresh
+        for no in nodes:
+            nodes.remove(no)
+
+        if self.single_sided:
+            geometry = nodes.new(type="ShaderNodeNewGeometry")
+
+            transparency = nodes.new(type="ShaderNodeBsdfTransparent")
+            transparency.inputs[0].default_value = (1,1,1,1)
+            transparency.location = geometry.location
+            transparency.location += Vector((0.0, -55.0))
+
+            emission = nodes.new(type="ShaderNodeEmission")
+            emission.inputs['Strength'].default_value = self.strength
+            emission.location = transparency.location
+            emission.location += Vector((0.0, -80.0))
+            emission.inputs[0].show_expanded = True # so it shows slider on properties editor
+
+            blackbody = nodes.new(type="ShaderNodeBlackbody")
+            blackbody.inputs['Temperature'].default_value = self.temperature
+            blackbody.location = emission.location
+            blackbody.location += Vector((-180.0, 0.0))
+
+            mix = nodes.new(type="ShaderNodeMixShader")
+            mix.location = geometry.location
+            mix.location += Vector((180.0, 0.0))
+            mix.inputs[2].show_expanded = True
+
+            output = nodes.new(type="ShaderNodeOutputMaterial")
+            output.inputs[1].hide = True
+            output.inputs[2].hide = True
+            output.location = mix.location
+            output.location += Vector((180.0, 0.0))
+
+            # Make links
+            links.new(geometry.outputs['Backfacing'], mix.inputs['Fac'])
+            links.new(transparency.outputs['BSDF'], mix.inputs[1])
+            links.new(emission.outputs['Emission'], mix.inputs[2])
+            links.new(blackbody.outputs['Color'], emission.inputs['Color'])
+            links.new(mix.outputs['Shader'], output.inputs['Surface'])
+
+            for sockets in geometry.outputs:
+                sockets.hide = True
+        else:
+            emission = nodes.new(type="ShaderNodeEmission")
+            emission.inputs['Strength'].default_value = self.strength
+            emission.inputs[0].show_expanded = True
+
+            blackbody = nodes.new(type="ShaderNodeBlackbody")
+            blackbody.inputs['Temperature'].default_value = self.temperature
+            blackbody.location = emission.location
+            blackbody.location += Vector((-180.0, 0.0))
+
+            output = nodes.new(type="ShaderNodeOutputMaterial")
+            output.inputs[1].hide = True
+            output.inputs[2].hide = True
+            output.location = emission.location
+            output.location += Vector((180.0, 0.0))
+
+            links.new(blackbody.outputs['Color'], emission.inputs['Color'])
+            links.new(emission.outputs['Emission'], output.inputs['Surface'])
+
+        material.cycles.sample_as_light = True
+        meshlight.cycles_visibility.shadow = False
+        meshlight.cycles_visibility.camera = self.visible
+
+        return {'FINISHED'}
+
+def ui_menu_lamps_add(self, context):
+    if cycles_exists and context.scene.render.engine == 'CYCLES':
+        self.layout.separator()
+        self.layout.operator(
+            AMTH_OBJECT_OT_meshlight_add.bl_idname,
+            icon="LAMP_AREA", text="Meshlight")
+
+# //FEATURE: Add Meshlight: Single Sided
 
 classes = (AMTH_SCENE_MT_color_management_presets,
            AMTH_AddPresetColorManagement,
+           AMTH_LightersCorner,
            AMTH_SCENE_PT_scene_debug,
            AMTH_SCENE_OT_refresh,
            AMTH_SCENE_OT_cycles_shader_list_nodes,
@@ -2383,10 +3023,15 @@ classes = (AMTH_SCENE_MT_color_management_presets,
            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,
@@ -2400,12 +3045,15 @@ classes = (AMTH_SCENE_MT_color_management_presets,
            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_frame_jump,
+           AMTH_SCREEN_OT_keyframe_jump_inbetween)
 
 addon_keymaps = []
 
@@ -2421,27 +3069,28 @@ 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)
 
@@ -2460,6 +3109,12 @@ def register():
 
     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)
 
@@ -2467,8 +3122,7 @@ def register():
     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', 'RELEASE')
-        km.keymap_items.new("node.show_active_node_image", 'SELECTMOUSE', 'RELEASE')
+        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')
@@ -2484,6 +3138,12 @@ def register():
         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)
 
@@ -2515,13 +3175,13 @@ 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)
 
@@ -2530,12 +3190,13 @@ def unregister():
     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)
 
@@ -2554,6 +3215,12 @@ def unregister():
 
     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)
 
@@ -2565,3 +3232,4 @@ def unregister():
 
 if __name__ == "__main__":
     register()
+