Amaranth Addon
authorPablo Vazquez <venomgfx@gmail.com>
Fri, 27 Feb 2015 16:53:54 +0000 (17:53 +0100)
committerPablo Vazquez <venomgfx@gmail.com>
Fri, 27 Feb 2015 16:53:54 +0000 (17:53 +0100)
Upgrade to 1.0, includes the refactor by Cesar Saez (thanks!), and a bunch of new features.

Documentation is being updated here:
http://pablovazquez.org/amaranth/

Full changelog:
https://github.com/venomgfx/amaranth/commits/master

47 files changed:
amaranth/__init__.py [new file with mode: 0644]
amaranth/animation/__init__.py [new file with mode: 0644]
amaranth/animation/frame_current.py [new file with mode: 0644]
amaranth/animation/jump_frames.py [new file with mode: 0644]
amaranth/animation/motion_paths.py [new file with mode: 0644]
amaranth/animation/timeline_extra_info.py [new file with mode: 0644]
amaranth/misc/__init__.py [new file with mode: 0644]
amaranth/misc/color_management.py [new file with mode: 0644]
amaranth/misc/dopesheet_grapheditor.py [new file with mode: 0644]
amaranth/misc/dupli_group_id.py [new file with mode: 0644]
amaranth/misc/sequencer_extra_info.py [new file with mode: 0644]
amaranth/misc/toggle_wire.py [new file with mode: 0644]
amaranth/modeling/__init__.py [new file with mode: 0644]
amaranth/modeling/symmetry_tools.py [new file with mode: 0644]
amaranth/node_editor/__init__.py [new file with mode: 0644]
amaranth/node_editor/display_image.py [new file with mode: 0644]
amaranth/node_editor/id_panel.py [new file with mode: 0644]
amaranth/node_editor/node_shader_extra.py [new file with mode: 0644]
amaranth/node_editor/node_stats.py [new file with mode: 0644]
amaranth/node_editor/normal_node.py [new file with mode: 0644]
amaranth/node_editor/simplify_nodes.py [new file with mode: 0644]
amaranth/node_editor/switch_material.py [new file with mode: 0644]
amaranth/node_editor/templates/__init__.py [new file with mode: 0644]
amaranth/node_editor/templates/vectorblur.py [new file with mode: 0644]
amaranth/node_editor/templates/vignette.py [new file with mode: 0644]
amaranth/prefs.py [new file with mode: 0644]
amaranth/render/__init__.py [new file with mode: 0644]
amaranth/render/border_camera.py [new file with mode: 0644]
amaranth/render/final_resolution.py [new file with mode: 0644]
amaranth/render/meshlight_add.py [new file with mode: 0644]
amaranth/render/meshlight_select.py [new file with mode: 0644]
amaranth/render/only_render.py [new file with mode: 0644]
amaranth/render/passepartout.py [new file with mode: 0644]
amaranth/render/remember_layers.py [new file with mode: 0644]
amaranth/render/render_output_z.py [new file with mode: 0644]
amaranth/render/samples_scene.py [new file with mode: 0644]
amaranth/render/unsimplify.py [new file with mode: 0644]
amaranth/scene/__init__.py [new file with mode: 0644]
amaranth/scene/current_blend.py [new file with mode: 0644]
amaranth/scene/debug.py [new file with mode: 0755]
amaranth/scene/goto_library.py [new file with mode: 0644]
amaranth/scene/material_remove_unassigned.py [new file with mode: 0644]
amaranth/scene/refresh.py [new file with mode: 0644]
amaranth/scene/save_reload.py [new file with mode: 0644]
amaranth/scene/stats.py [new file with mode: 0644]
amaranth/utils.py [new file with mode: 0644]
scene_amaranth_toolset.py [deleted file]

diff --git a/amaranth/__init__.py b/amaranth/__init__.py
new file mode 100644 (file)
index 0000000..d8ebf4a
--- /dev/null
@@ -0,0 +1,123 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Amaranth
+
+Using Blender every day, you get to change little things on it to speedup
+your workflow. The problem is when you have to switch computers with
+somebody else's Blender, it sucks.
+That's the main reason behind Amaranth. I ported all sort of little changes
+I find useful into this addon.
+
+What is it about? Anything, whatever I think it can speedup workflow,
+I'll try to add it. Enjoy <3
+"""
+
+import sys
+
+# import amaranth's modules
+
+# NOTE: avoid local imports whenever possible!
+# Thanks to Christopher Crouzet for let me know about this.
+# http://stackoverflow.com/questions/13392038/python-making-a-class-variable-static-even-when-a-module-is-imported-in-differe
+
+from amaranth import prefs
+
+from amaranth.modeling import symmetry_tools
+
+from amaranth.scene import (
+    refresh,
+    save_reload,
+    current_blend,
+    stats,
+    goto_library,
+    debug,
+    material_remove_unassigned,
+    )
+
+from amaranth.node_editor import (
+    id_panel,
+    display_image,
+    templates,
+    simplify_nodes,
+    node_stats,
+    normal_node,
+    switch_material,
+    node_shader_extra,
+    )
+
+from amaranth.render import (
+    border_camera,
+    meshlight_add,
+    meshlight_select,
+    passepartout,
+    only_render,
+    unsimplify,
+    final_resolution,
+    samples_scene,
+    remember_layers,
+    render_output_z,
+    )
+
+from amaranth.animation import (
+    timeline_extra_info,
+    frame_current,
+    motion_paths,
+    jump_frames,
+    )
+
+from amaranth.misc import (
+    dopesheet_grapheditor,
+    color_management,
+    dupli_group_id,
+    toggle_wire,
+    sequencer_extra_info,
+    )
+
+
+# register the addon + modules found in globals()
+bl_info = {
+    "name": "Amaranth Toolset",
+    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne, Cesar Saez",
+    "version": (1, 0, 0),
+    "blender": (2, 73),
+    "location": "Everywhere!",
+    "description": "A collection of tools and settings to improve productivity",
+    "warning": "",
+    "wiki_url": "http://pablovazquez.org/amaranth",
+    "tracker_url": "",
+    "category": "Scene",
+}
+
+
+def _call_globals(attr_name):
+    for m in globals().values():
+        if hasattr(m, attr_name):
+            getattr(m, attr_name)()
+
+
+def _flush_modules(pkg_name):
+    pkg_name = pkg_name.lower()
+    for k in tuple(sys.modules.keys()):
+        if k.lower().startswith(pkg_name):
+            del sys.modules[k]
+
+
+def register():
+    _call_globals("register")
+
+
+def unregister():
+    _call_globals("unregister")
+    _flush_modules("amaranth")  # reload amaranth
diff --git a/amaranth/animation/__init__.py b/amaranth/animation/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/animation/frame_current.py b/amaranth/animation/frame_current.py
new file mode 100644 (file)
index 0000000..538931c
--- /dev/null
@@ -0,0 +1,42 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Current Frame Slider
+
+Currently the only way to change the current frame is to have a Timeline
+editor open, but sometimes you don't have one, or you're fullscreen.
+This option adds the Current Frame slider to the Specials menu. Find it
+hitting the W menu in Object mode, you can slide or click in the middle
+of the button to set the frame manually.
+"""
+
+import bpy
+
+
+def button_frame_current(self, context):
+    preferences = context.user_preferences.addons["amaranth"].preferences
+    scene = context.scene
+    if preferences.use_frame_current:
+        self.layout.separator()
+        self.layout.prop(scene, "frame_current", text="Set Current Frame")
+
+
+def register():
+    bpy.types.VIEW3D_MT_object_specials.append(button_frame_current)
+    bpy.types.VIEW3D_MT_pose_specials.append(button_frame_current)
+
+
+def unregister():
+    bpy.types.VIEW3D_MT_object_specials.remove(button_frame_current)
+    bpy.types.VIEW3D_MT_pose_specials.remove(button_frame_current)
diff --git a/amaranth/animation/jump_frames.py b/amaranth/animation/jump_frames.py
new file mode 100644 (file)
index 0000000..5f4ca0c
--- /dev/null
@@ -0,0 +1,196 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Jump X Frames on Shift Up/Down
+
+When you hit Shift Up/Down, you'll jump 10 frames forward/backwards.
+Sometimes is nice to tweak that value.
+
+In the User Preferences, Editing tab, you'll find a "Frames to Jump"
+slider where you can adjust how many frames you'd like to move
+forwards/backwards.
+
+Make sure you save your user settings if you want to use this value from
+now on.
+
+Find it on the User Preferences, Editing.
+"""
+
+import bpy
+
+KEYMAPS = list()
+
+
+# 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
+
+# monkey path is_keyframe function
+bpy.types.Object.is_keyframe = is_keyframe
+
+
+# FEATURE: Jump to frame in-between next and previous keyframe
+class AMTH_SCREEN_OT_keyframe_jump_inbetween(bpy.types.Operator):
+
+    """Jump to half in-between keyframes"""
+    bl_idname = "screen.amth_keyframe_jump_inbetween"
+    bl_label = "Jump to Keyframe In-between"
+    backwards = bpy.props.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"] = list()
+
+        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:
+            keyframes_list_half = []
+
+            for i, item in enumerate(keyframes_list):
+                try:
+                    next_item = keyframes_list[i + 1]
+                    keyframes_list_half.append(int((item + next_item) / 2))
+                except:
+                    pass
+
+            if len(keyframes_list_half) > 1:
+                if back:
+                    v = (scene.frame_current == keyframes_list_half[::-1][-1],
+                         scene.frame_current < keyframes_list_half[::-1][-1])
+                    if any(v):
+                        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:
+                    v = (scene.frame_current == keyframes_list_half[-1],
+                         scene.frame_current > keyframes_list_half[-1])
+                    if any(v):
+                        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 forward/backward every N frames
+class AMTH_SCREEN_OT_frame_jump(bpy.types.Operator):
+
+    """Jump a number of frames forward/backwards"""
+    bl_idname = "screen.amaranth_frame_jump"
+    bl_label = "Jump Frames"
+
+    forward = bpy.props.BoolProperty(default=True)
+
+    def execute(self, context):
+        scene = context.scene
+        preferences = context.user_preferences.addons["amaranth"].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["amaranth"].preferences
+
+    col = self.layout.column()
+    split = col.split(percentage=0.21)
+    split.prop(preferences, "frames_jump",
+               text="Frames to Jump")
+
+
+def label(self, context):
+
+    preferences = context.user_preferences.addons["amaranth"].preferences
+    layout = self.layout
+
+    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
+
+
+def register():
+    bpy.utils.register_class(AMTH_SCREEN_OT_frame_jump)
+    bpy.utils.register_class(AMTH_SCREEN_OT_keyframe_jump_inbetween)
+    bpy.types.USERPREF_PT_edit.append(ui_userpreferences_edit)
+    bpy.types.USERPREF_PT_edit.append(label)
+
+    # register keyboard shortcuts
+    wm = bpy.context.window_manager
+    kc = wm.keyconfigs.addon
+
+    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
+    KEYMAPS.append((km, kmi))
+
+    kmi = km.keymap_items.new('screen.amth_keyframe_jump_inbetween', 'DOWN_ARROW', 'PRESS', shift=True, ctrl=True)
+    kmi.properties.backwards = True
+    KEYMAPS.append((km, kmi))
+
+    kmi = km.keymap_items.new(
+        "screen.amaranth_frame_jump", "UP_ARROW", "PRESS", shift=True)
+    kmi.properties.forward = True
+    KEYMAPS.append((km, kmi))
+
+    kmi = km.keymap_items.new(
+        "screen.amaranth_frame_jump", "DOWN_ARROW", "PRESS", shift=True)
+    kmi.properties.forward = False
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_SCREEN_OT_frame_jump)
+    bpy.utils.unregister_class(AMTH_SCREEN_OT_keyframe_jump_inbetween)
+    bpy.types.USERPREF_PT_edit.remove(ui_userpreferences_edit)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/animation/motion_paths.py b/amaranth/animation/motion_paths.py
new file mode 100644 (file)
index 0000000..7988b45
--- /dev/null
@@ -0,0 +1,144 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Bone Motion Paths:
+
+Match Frame Range + Clear All Paths
+
+* Clear All Paths:
+Silly operator to loop through all bones and clear their paths, useful
+when having hidden bones (othrewise you have to go through each one of
+them and clear manually)
+
+*Match Current Frame Range:
+Set the current frame range as motion path range.
+
+Both requests by Hjalti from Project Pampa
+Thanks to Bassam Kurdali for helping finding out the weirdness behind
+Motion Paths bpy.
+
+Developed during Caminandes Open Movie Project
+"""
+
+import bpy
+
+
+class AMTH_POSE_OT_paths_clear_all(bpy.types.Operator):
+
+    """Clear motion paths from all bones"""
+    bl_idname = "pose.paths_clear_all"
+    bl_label = "Clear All Motion Paths"
+    bl_options = {"UNDO"}
+
+    @classmethod
+    def poll(cls, context):
+        return context.mode == "POSE"
+
+    def execute(self, context):
+        # silly but works
+        for b in context.object.data.bones:
+            b.select = True
+            bpy.ops.pose.paths_clear()
+            b.select = False
+        return {"FINISHED"}
+
+
+class AMTH_POSE_OT_paths_frame_match(bpy.types.Operator):
+
+    """Match Start/End frame of scene to motion path range"""
+    bl_idname = "pose.paths_frame_match"
+    bl_label = "Match Frame Range"
+    bl_options = {"UNDO"}
+
+    def execute(self, context):
+        avs = context.object.pose.animation_visualization
+        scene = context.scene
+
+        if avs.motion_path.type == "RANGE":
+            if scene.use_preview_range:
+                avs.motion_path.frame_start = scene.frame_preview_start
+                avs.motion_path.frame_end = scene.frame_preview_end
+            else:
+                avs.motion_path.frame_start = scene.frame_start
+                avs.motion_path.frame_end = scene.frame_end
+
+        else:
+            if scene.use_preview_range:
+                avs.motion_path.frame_before = scene.frame_preview_start
+                avs.motion_path.frame_after = scene.frame_preview_end
+            else:
+                avs.motion_path.frame_before = scene.frame_start
+                avs.motion_path.frame_after = scene.frame_end
+
+        return {"FINISHED"}
+
+
+def pose_motion_paths_ui(self, context):
+
+    layout = self.layout
+    scene = context.scene
+    avs = context.object.pose.animation_visualization
+    if context.active_pose_bone:
+        mpath = context.active_pose_bone.motion_path
+    layout.separator()
+    layout.label(text="Motion Paths Extras:")
+
+    split = layout.split()
+
+    col = split.column(align=True)
+
+    if context.selected_pose_bones:
+        if mpath:
+            sub = col.row(align=True)
+            sub.operator(
+                "pose.paths_update", text="Update Path", icon="BONE_DATA")
+            sub.operator("pose.paths_clear", text="", icon="X")
+        else:
+            col.operator(
+                "pose.paths_calculate",
+                text="Calculate Path",
+                icon="BONE_DATA")
+    else:
+        col.label(text="Select Bones First", icon="ERROR")
+
+    col = split.column(align=True)
+    col.operator(
+        AMTH_POSE_OT_paths_frame_match.bl_idname,
+        text="Set Preview Frame Range" if scene.use_preview_range else "Set Frame Range",
+        icon="PREVIEW_RANGE" if scene.use_preview_range else "TIME")
+
+    col = layout.column()
+    row = col.row(align=True)
+
+    if avs.motion_path.type == "RANGE":
+        row.prop(avs.motion_path, "frame_start", text="Start")
+        row.prop(avs.motion_path, "frame_end", text="End")
+    else:
+        row.prop(avs.motion_path, "frame_before", text="Before")
+        row.prop(avs.motion_path, "frame_after", text="After")
+
+    layout.separator()
+    layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
+
+
+def register():
+    bpy.utils.register_class(AMTH_POSE_OT_paths_clear_all)
+    bpy.utils.register_class(AMTH_POSE_OT_paths_frame_match)
+    bpy.types.DATA_PT_display.append(pose_motion_paths_ui)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_POSE_OT_paths_clear_all)
+    bpy.utils.unregister_class(AMTH_POSE_OT_paths_frame_match)
+    bpy.types.DATA_PT_display.remove(pose_motion_paths_ui)
diff --git a/amaranth/animation/timeline_extra_info.py b/amaranth/animation/timeline_extra_info.py
new file mode 100644 (file)
index 0000000..c1a6449
--- /dev/null
@@ -0,0 +1,65 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Timeline Extra Info
+
+Display amount of frames left until Frame End, very handy especially when
+rendering an animation or OpenGL preview.
+Display current/end time on SMPTE. Find it on the Timeline header.
+"""
+
+import bpy
+
+
+def label_timeline_extra_info(self, context):
+
+    preferences = context.user_preferences.addons["amaranth"].preferences
+    layout = self.layout
+    scene = context.scene
+
+    if preferences.use_timeline_extra_info:
+        row = layout.row(align=True)
+
+        # Check for preview range
+        frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
+        frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
+
+        row.label(
+            text="%s / %s" %
+            (bpy.utils.smpte_from_frame(
+                scene.frame_current -
+                frame_start),
+                bpy.utils.smpte_from_frame(
+                    frame_end -
+                    frame_start)))
+
+        if (scene.frame_current > frame_end):
+            row.label(text="%s Frames Ahead" %
+                      ((frame_end - scene.frame_current) * -1))
+        elif (scene.frame_current == frame_start):
+            row.label(text="Start Frame (%s left)" %
+                      (frame_end - scene.frame_current))
+        elif (scene.frame_current == frame_end):
+            row.label(text="%s End Frame" % scene.frame_current)
+        else:
+            row.label(text="%s Frames Left" %
+                      (frame_end - scene.frame_current))
+
+
+def register():
+    bpy.types.TIME_HT_header.append(label_timeline_extra_info)
+
+
+def unregister():
+    bpy.types.TIME_HT_header.remove(label_timeline_extra_info)
diff --git a/amaranth/misc/__init__.py b/amaranth/misc/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/misc/color_management.py b/amaranth/misc/color_management.py
new file mode 100644 (file)
index 0000000..a8335ab
--- /dev/null
@@ -0,0 +1,84 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Color Management Presets
+
+Save your Color Management options as presets, for easy re-use.
+
+It will pretty much every option in the Color Management panel, such as
+the look, color settings, and so on. Except the curve points (have to
+figure out how to do that nicely), good news is that in Blender 2.69+ you
+can now copy/paste curves.
+"""
+
+import bpy
+from bl_operators.presets import AddPresetBase
+
+
+class AMTH_SCENE_MT_color_management_presets(bpy.types.Menu):
+
+    """List of Color Management presets"""
+    bl_label = "Color Management Presets"
+    preset_subdir = "color"
+    preset_operator = "script.execute_preset"
+    draw = bpy.types.Menu.draw_preset
+
+
+class AMTH_AddPresetColorManagement(AddPresetBase, bpy.types.Operator):
+
+    """Add or remove a Color Management preset"""
+    bl_idname = "scene.color_management_preset_add"
+    bl_label = "Add Color Management Preset"
+    preset_menu = "AMTH_SCENE_MT_color_management_presets"
+
+    preset_defines = [
+        "scene = bpy.context.scene",
+    ]
+
+    preset_values = [
+        "scene.view_settings.view_transform",
+        "scene.display_settings.display_device",
+        "scene.view_settings.exposure",
+        "scene.view_settings.gamma",
+        "scene.view_settings.look",
+        "scene.view_settings.use_curve_mapping",
+        "scene.sequencer_colorspace_settings.name",
+    ]
+
+    preset_subdir = "color"
+
+
+def ui_color_management_presets(self, context):
+
+    layout = self.layout
+
+    row = layout.row(align=True)
+    row.menu("AMTH_SCENE_MT_color_management_presets",
+             text=bpy.types.AMTH_SCENE_MT_color_management_presets.bl_label)
+    row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
+    row.operator("scene.color_management_preset_add",
+                 text="", icon="ZOOMOUT").remove_active = True
+    layout.separator()
+
+
+def register():
+    bpy.utils.register_class(AMTH_AddPresetColorManagement)
+    bpy.utils.register_class(AMTH_SCENE_MT_color_management_presets)
+    bpy.types.SCENE_PT_color_management.prepend(ui_color_management_presets)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_AddPresetColorManagement)
+    bpy.utils.unregister_class(AMTH_SCENE_MT_color_management_presets)
+    bpy.types.SCENE_PT_color_management.remove(ui_color_management_presets)
diff --git a/amaranth/misc/dopesheet_grapheditor.py b/amaranth/misc/dopesheet_grapheditor.py
new file mode 100644 (file)
index 0000000..c48a30a
--- /dev/null
@@ -0,0 +1,55 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Shortcut: Ctrl+Tab to switch between Dopesheet/Graph Editor
+
+Hit Ctrl + Tab to switch between Dopesheet and the Graph Editor.
+Developed during Caminandes Open Movie Project
+"""
+
+
+import bpy
+
+KEYMAPS = list()
+
+
+def register():
+    wm = bpy.context.window_manager
+    kc = wm.keyconfigs.addon
+
+    km = kc.keymaps.new(name="Graph Editor", space_type="GRAPH_EDITOR")
+    kmi = km.keymap_items.new("wm.context_set_enum", "TAB", "PRESS", ctrl=True)
+    kmi.properties.data_path = "area.type"
+    kmi.properties.value = "DOPESHEET_EDITOR"
+    KEYMAPS.append((km, kmi))
+
+    km = kc.keymaps.new(name="Dopesheet", space_type="DOPESHEET_EDITOR")
+    kmi = km.keymap_items.new("wm.context_set_enum", "TAB", "PRESS", ctrl=True)
+    kmi.properties.data_path = "area.type"
+    kmi.properties.value = "GRAPH_EDITOR"
+    KEYMAPS.append((km, kmi))
+
+    km = kc.keymaps.new(name="Dopesheet", space_type="DOPESHEET_EDITOR")
+    kmi = km.keymap_items.new("wm.context_toggle_enum",
+                              "TAB", "PRESS", shift=True)
+    kmi.properties.data_path = "space_data.mode"
+    kmi.properties.value_1 = "ACTION"
+    kmi.properties.value_2 = "DOPESHEET"
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/misc/dupli_group_id.py b/amaranth/misc/dupli_group_id.py
new file mode 100644 (file)
index 0000000..bbbe786
--- /dev/null
@@ -0,0 +1,197 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Object ID for Dupli Groups
+Say you have a linked character or asset, you can now set an Object ID for the
+entire instance (the objects in the group), and use it with the Object Index
+pass later in compositing. Something that I always wanted and it wasn't
+possible!
+
+In order for the Object ID to be loaded afterwards on computers without
+Amaranth installed, it will automatically create a text file (called
+AmaranthStartup.py) and save it inside the .blend, this will autorun on
+startup and set the OB IDs. Remember to have auto-run python scripts on your
+startup preferences.
+
+Set a Pass Index and press "Apply Object ID to Duplis" on the Relations panel,
+Object Properties.
+"""
+
+
+import bpy
+from amaranth.scene.debug import AMTH_SCENE_OT_blender_instance_open
+
+
+# Some settings are bound to be saved on a startup py file
+# TODO: refactor this, amth_text should not be declared as a global variable,
+#       otherwise becomes confusing when you call it in the classes below.
+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
+
+
+# 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: Object ID for objects inside DupliGroups
+class AMTH_OBJECT_OT_id_dupligroup(bpy.types.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
+        amaranth_text_startup(context)
+        script_exists = False
+        script_intro = "# OB ID: %s" % ob.name
+        obdata = 'bpy.data.objects[" % s"]' % ob.name
+        # TODO: cleanup script var using format or template strings
+        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(bpy.types.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
+
+
+def register():
+    bpy.utils.register_class(AMTH_OBJECT_OT_id_dupligroup)
+    bpy.utils.register_class(AMTH_OBJECT_OT_id_dupligroup_clear)
+    bpy.types.OBJECT_PT_duplication.append(ui_dupli_group_library_path)
+    bpy.types.OBJECT_PT_relations.append(ui_object_id_duplis)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_id_dupligroup)
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_id_dupligroup_clear)
+    bpy.types.OBJECT_PT_duplication.remove(ui_dupli_group_library_path)
+    bpy.types.OBJECT_PT_relations.remove(ui_object_id_duplis)
diff --git a/amaranth/misc/sequencer_extra_info.py b/amaranth/misc/sequencer_extra_info.py
new file mode 100644 (file)
index 0000000..2d5d6b7
--- /dev/null
@@ -0,0 +1,67 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Sequencer: Display Image File Name
+
+When seeking through an image sequence, display the active strips' file name
+for the current frame, and it's [playhead].
+
+Find it on the VSE header.
+"""
+import bpy
+
+
+# FEATURE: Sequencer Extra Info
+def act_strip(context):
+    try:
+        return context.scene.sequence_editor.active_strip
+    except AttributeError:
+        return None
+
+
+def ui_sequencer_extra_info(self, context):
+    layout = self.layout
+    strip = act_strip(context)
+    if strip:
+        seq_type = strip.type
+        if seq_type and seq_type == 'IMAGE':
+            elem = strip.strip_elem_from_frame(context.scene.frame_current)
+            if elem:
+                layout.label(
+                    text="%s %s" %
+                    (elem.filename, "[%s]" %
+                     (context.scene.frame_current - strip.frame_start)))
+
+# // FEATURE: Sequencer Extra Info
+
+
+def register():
+    bpy.types.SEQUENCER_HT_header.append(ui_sequencer_extra_info)
+
+
+def unregister():
+    bpy.types.SEQUENCER_HT_header.remove(ui_sequencer_extra_info)
diff --git a/amaranth/misc/toggle_wire.py b/amaranth/misc/toggle_wire.py
new file mode 100644 (file)
index 0000000..9102f3b
--- /dev/null
@@ -0,0 +1,134 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import bpy
+
+
+# FEATURE: Toggle Wire Display
+class AMTH_OBJECT_OT_wire_toggle(bpy.types.Operator):
+
+    """Turn on/off wire display on mesh objects"""
+    bl_idname = "object.amth_wire_toggle"
+    bl_label = "Display Wireframe"
+    bl_options = {"REGISTER", "UNDO"}
+
+    clear = bpy.props.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)
+    col.label(text="Wireframes:")
+    row = col.row(align=True)
+    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
+                 icon="MOD_WIREFRAME", text="Display").clear = False
+    row.operator(AMTH_OBJECT_OT_wire_toggle.bl_idname,
+                 icon="X", text="Clear").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")
+
+
+def init_properties():
+    scene = bpy.types.Scene
+    scene.amth_wire_toggle_scene_all = bpy.props.BoolProperty(
+        default=False,
+        name="All Scenes",
+        description="Toggle wire on objects in all scenes")
+    scene.amth_wire_toggle_is_selected = bpy.props.BoolProperty(
+        default=False,
+        name="Only Selected Objects",
+        description="Only toggle wire on selected objects")
+    scene.amth_wire_toggle_edges_all = bpy.props.BoolProperty(
+        default=True,
+        name="Draw All Edges",
+        description="Draw all the edges even on coplanar faces")
+    scene.amth_wire_toggle_optimal = bpy.props.BoolProperty(
+        default=False,
+        name="Subsurf Optimal Display",
+        description="Skip drawing/rendering of interior subdivided edges "
+                    "on meshes with Subdivision Surface modifier")
+
+
+def clear_properties():
+    props = (
+        'amth_wire_toggle_is_selected',
+        'amth_wire_toggle_scene_all',
+        "amth_wire_toggle_edges_all",
+        "amth_wire_toggle_optimal"
+    )
+    wm = bpy.context.window_manager
+    for p in props:
+        if p in wm:
+            del wm[p]
+
+# //FEATURE: Toggle Wire Display
+
+
+def register():
+    init_properties()
+    bpy.utils.register_class(AMTH_OBJECT_OT_wire_toggle)
+    bpy.types.VIEW3D_PT_view3d_display.append(ui_object_wire_toggle)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_wire_toggle)
+    bpy.types.VIEW3D_PT_view3d_display.remove(ui_object_wire_toggle)
+    clear_properties()
diff --git a/amaranth/modeling/__init__.py b/amaranth/modeling/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/modeling/symmetry_tools.py b/amaranth/modeling/symmetry_tools.py
new file mode 100644 (file)
index 0000000..be300ca
--- /dev/null
@@ -0,0 +1,176 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Symmetry Tools: Find Asymmetric + Make Symmetric (by Sergey Sharybin)
+
+Our character wasn’t completely symmetric in some parts where it was
+supposed to, this could be by moving vertices by mistake or just reasons.
+To fix this in a fast way, Sergey coded this two super useful tools:
+
+* Find Asymmetric:
+Selects vertices that don’t have the same position on the opposite side.
+
+* Make Symmetric:
+Move selected vertices to match the position of those on the other side.
+
+This tools may not apply on every single model out there, but I tried it
+in many different characters and it worked. So probably better use it on
+those models that were already symmetric at some point, modeled with a
+mirror modifier or so.
+Search (spacebar) for "Find Asymmetric", and "Make Symmetric""Settings".
+
+> Developed during Caminandes Open Movie Project
+"""
+
+import bpy
+import bmesh
+from mathutils import Vector
+
+
+class AMTH_MESH_OT_find_asymmetric(bpy.types.Operator):
+
+    """
+    Find asymmetric vertices
+    """
+
+    bl_idname = "mesh.find_asymmetric"
+    bl_label = "Find Asymmetric"
+    bl_options = {"UNDO", "REGISTER"}
+
+    @classmethod
+    def poll(cls, context):
+        object = context.object
+        if object:
+            return object.mode == "EDIT" and object.type == "MESH"
+        return False
+
+    def execute(self, context):
+        threshold = 1e-6
+
+        object = context.object
+        bm = bmesh.from_edit_mesh(object.data)
+
+        # Deselect all the vertices
+        for v in bm.verts:
+            v.select = False
+
+        for v1 in bm.verts:
+            if abs(v1.co[0]) < threshold:
+                continue
+
+            mirror_found = False
+            for v2 in bm.verts:
+                if v1 == v2:
+                    continue
+                if v1.co[0] * v2.co[0] > 0.0:
+                    continue
+
+                mirror_coord = Vector(v2.co)
+                mirror_coord[0] *= -1
+                if (mirror_coord - v1.co).length_squared < threshold:
+                    mirror_found = True
+                    break
+            if not mirror_found:
+                v1.select = True
+
+        bm.select_flush_mode()
+
+        bmesh.update_edit_mesh(object.data)
+
+        return {"FINISHED"}
+
+
+class AMTH_MESH_OT_make_symmetric(bpy.types.Operator):
+
+    """
+    Make symmetric
+    """
+
+    bl_idname = "mesh.make_symmetric"
+    bl_label = "Make Symmetric"
+    bl_options = {"UNDO", "REGISTER"}
+
+    @classmethod
+    def poll(cls, context):
+        object = context.object
+        if object:
+            return object.mode == "EDIT" and object.type == "MESH"
+        return False
+
+    def execute(self, context):
+        threshold = 1e-6
+
+        object = context.object
+        bm = bmesh.from_edit_mesh(object.data)
+
+        for v1 in bm.verts:
+            if v1.co[0] < threshold:
+                continue
+            if not v1.select:
+                continue
+
+            closest_vert = None
+            closest_distance = -1
+            for v2 in bm.verts:
+                if v1 == v2:
+                    continue
+                if v2.co[0] > threshold:
+                    continue
+                if not v2.select:
+                    continue
+
+                mirror_coord = Vector(v2.co)
+                mirror_coord[0] *= -1
+                distance = (mirror_coord - v1.co).length_squared
+                if closest_vert is None or distance < closest_distance:
+                    closest_distance = distance
+                    closest_vert = v2
+
+            if closest_vert:
+                closest_vert.select = False
+                closest_vert.co = Vector(v1.co)
+                closest_vert.co[0] *= -1
+            v1.select = False
+
+        for v1 in bm.verts:
+            if v1.select:
+                closest_vert = None
+                closest_distance = -1
+                for v2 in bm.verts:
+                    if v1 != v2:
+                        mirror_coord = Vector(v2.co)
+                        mirror_coord[0] *= -1
+                        distance = (mirror_coord - v1.co).length_squared
+                        if closest_vert is None or distance < closest_distance:
+                            closest_distance = distance
+                            closest_vert = v2
+                if closest_vert:
+                    v1.select = False
+                    v1.co = Vector(closest_vert.co)
+                    v1.co[0] *= -1
+
+        bm.select_flush_mode()
+        bmesh.update_edit_mesh(object.data)
+
+        return {"FINISHED"}
+
+
+def register():
+    bpy.utils.register_class(AMTH_MESH_OT_find_asymmetric)
+    bpy.utils.register_class(AMTH_MESH_OT_make_symmetric)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_MESH_OT_find_asymmetric)
+    bpy.utils.unregister_class(AMTH_MESH_OT_make_symmetric)
diff --git a/amaranth/node_editor/__init__.py b/amaranth/node_editor/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/node_editor/display_image.py b/amaranth/node_editor/display_image.py
new file mode 100644 (file)
index 0000000..1a84466
--- /dev/null
@@ -0,0 +1,81 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Display Active Image Node on Image Editor
+
+When selecting an Image node, it will show it on the Image editor (if
+there is any available). If you don't like this behavior, you can
+disable it from the Amaranth Toolset panel on the Scene properties.
+Coded by the awesome Sergey Sharybin. This feature only works on Blender
+2.68 and newer. Select an Image Node in the Compositor or Cycles nodes
+editor, there must be at least one image editor available.
+"""
+
+import bpy
+
+
+KEYMAPS = list()
+
+image_nodes = ("CompositorNodeImage",
+               "CompositorNodeViewer",
+               "CompositorNodeComposite",
+               "ShaderNodeTexImage",
+               "ShaderNodeTexEnvironment")
+
+
+class AMTH_NODE_OT_show_active_node_image(bpy.types.Operator):
+
+    """Show active image node image in the image editor"""
+    bl_idname = "node.show_active_node_image"
+    bl_label = "Show Active Node Node"
+    bl_options = {"UNDO"}
+
+    def execute(self, context):
+        preferences = context.user_preferences.addons["amaranth"].preferences
+        if preferences.use_image_node_display:
+            if context.active_node:
+                active_node = context.active_node
+
+                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":
+                                    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"}
+
+
+def register():
+    bpy.utils.register_class(AMTH_NODE_OT_show_active_node_image)
+    kc = bpy.context.window_manager.keyconfigs.addon
+    km = kc.keymaps.new(name="Node Editor", space_type="NODE_EDITOR")
+    kmi = km.keymap_items.new("node.show_active_node_image",
+                              "ACTIONMOUSE", "DOUBLE_CLICK")
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_NODE_OT_show_active_node_image)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/node_editor/id_panel.py b/amaranth/node_editor/id_panel.py
new file mode 100644 (file)
index 0000000..09f031d
--- /dev/null
@@ -0,0 +1,154 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+"""
+Object / Material Indices Panel
+
+When working with ID Masks in the Nodes Editor, is hard to follow track
+of which objects/materials have which ID.
+This adds a panel on the sidebar when an ID Mask node is selected.
+The active object is highlighted between [square brackets] On the Nodes
+Editor's sidebar, when an ID Mask node is selected.
+"""
+
+import bpy
+
+
+class AMTH_NODE_PT_indices(bpy.types.Panel):
+    bl_space_type = "NODE_EDITOR"
+    bl_region_type = "UI"
+    bl_label = "Object / Material Indices"
+    bl_options = {"DEFAULT_CLOSED"}
+
+    @classmethod
+    def poll(cls, context):
+        node = context.active_node
+        return node and node.type == "ID_MASK"
+
+    def draw(self, context):
+        layout = self.layout
+
+        objects = bpy.data.objects
+        materials = bpy.data.materials
+        node = context.active_node
+
+        show_ob_id = False
+        show_ma_id = False
+        matching_ids = False
+
+        if context.active_object:
+            ob_act = context.active_object
+        else:
+            ob_act = False
+
+        for ob in objects:
+            if ob and ob.pass_index > 0:
+                show_ob_id = True
+        for ma in materials:
+            if ma and ma.pass_index > 0:
+                show_ma_id = True
+        row = layout.row(align=True)
+        row.prop(node, "index", text="Mask Index")
+        row.prop(node, "use_matching_indices", text="Only Matching IDs")
+
+        layout.separator()
+
+        if not show_ob_id and not show_ma_id:
+            layout.label(
+                text="No objects or materials indices so far.", icon="INFO")
+
+        if show_ob_id:
+            split = layout.split()
+            col = split.column()
+            col.label(text="Object Name")
+            split.label(text="ID Number")
+            row = layout.row()
+            for ob in objects:
+                icon = "OUTLINER_DATA_" + ob.type
+                if ob.library:
+                    icon = "LIBRARY_DATA_DIRECT"
+                elif ob.is_library_indirect:
+                    icon = "LIBRARY_DATA_INDIRECT"
+
+                if ob and node.use_matching_indices \
+                    and ob.pass_index == node.index \
+                    and ob.pass_index != 0:
+                    matching_ids = True
+                    row.label(
+                        text="[{}]".format(ob.name)
+                        if ob_act and ob.name == ob_act.name else ob.name,
+                        icon=icon)
+                    row.label(text="%s" % ob.pass_index)
+                    row = layout.row()
+
+                elif ob and not node.use_matching_indices \
+                        and ob.pass_index > 0:
+
+                    matching_ids = True
+                    row.label(
+                        text="[{}]".format(ob.name)
+                        if ob_act and ob.name == ob_act.name else ob.name,
+                        icon=icon)
+                    row.label(text="%s" % ob.pass_index)
+                    row = layout.row()
+
+            if node.use_matching_indices and not matching_ids:
+                row.label(text="No objects with ID %s" %
+                          node.index, icon="INFO")
+
+            layout.separator()
+
+        if show_ma_id:
+            split = layout.split()
+            col = split.column()
+            col.label(text="Material Name")
+            split.label(text="ID Number")
+            row = layout.row()
+
+            for ma in materials:
+                icon = "BLANK1"
+                if ma.use_nodes:
+                    icon = "NODETREE"
+                elif ma.library:
+                    icon = "LIBRARY_DATA_DIRECT"
+                    if ma.is_library_indirect:
+                        icon = "LIBRARY_DATA_INDIRECT"
+
+                if ma and node.use_matching_indices \
+                    and ma.pass_index == node.index \
+                    and ma.pass_index != 0:
+                    matching_ids = True
+                    row.label(text="%s" % ma.name, icon=icon)
+                    row.label(text="%s" % ma.pass_index)
+                    row = layout.row()
+
+                elif ma and not node.use_matching_indices \
+                        and ma.pass_index > 0:
+
+                    matching_ids = True
+                    row.label(text="%s" % ma.name, icon=icon)
+                    row.label(text="%s" % ma.pass_index)
+                    row = layout.row()
+
+            if node.use_matching_indices and not matching_ids:
+                row.label(text="No materials with ID %s" %
+                          node.index, icon="INFO")
+
+
+def register():
+    bpy.utils.register_class(AMTH_NODE_PT_indices)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_NODE_PT_indices)
diff --git a/amaranth/node_editor/node_shader_extra.py b/amaranth/node_editor/node_shader_extra.py
new file mode 100644 (file)
index 0000000..7fbaf22
--- /dev/null
@@ -0,0 +1,40 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+import bpy
+
+
+# FEATURE: Shader Nodes Extra Info
+def node_shader_extra(self, context):
+    if context.space_data.tree_type == 'ShaderNodeTree':
+        ob = context.active_object
+        snode = context.space_data
+        layout = self.layout
+
+        if ob and snode.shader_type == 'OBJECT':
+            if ob.type == 'LAMP':
+                layout.label(text="%s" % ob.name,
+                             icon="LAMP_%s" % ob.data.type)
+            else:
+                layout.label(text="%s" % ob.name,
+                             icon="OUTLINER_DATA_%s" % ob.type)
+
+# // FEATURE: Shader Nodes Extra Info
+
+
+def register():
+    bpy.types.NODE_HT_header.append(node_shader_extra)
+
+
+def unregister():
+    bpy.types.NODE_HT_header.remove(node_shader_extra)
diff --git a/amaranth/node_editor/node_stats.py b/amaranth/node_editor/node_stats.py
new file mode 100644 (file)
index 0000000..796e10d
--- /dev/null
@@ -0,0 +1,45 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Nodes Stats
+
+Display the number of selected and total nodes on the compositor. On the
+Compositing Nodes Editor.
+"""
+
+import bpy
+
+
+def node_stats(self, context):
+    if context.scene.node_tree:
+        tree_type = context.space_data.tree_type
+        nodes = context.scene.node_tree.nodes
+        nodes_total = len(nodes.keys())
+        nodes_selected = 0
+        for n in nodes:
+            if n.select:
+                nodes_selected = nodes_selected + 1
+
+        if tree_type == 'CompositorNodeTree':
+            layout = self.layout
+            row = layout.row(align=True)
+            row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
+
+
+def register():
+    bpy.types.NODE_HT_header.append(node_stats)
+
+
+def unregister():
+    bpy.types.NODE_HT_header.remove(node_stats)
diff --git a/amaranth/node_editor/normal_node.py b/amaranth/node_editor/normal_node.py
new file mode 100644 (file)
index 0000000..fe77856
--- /dev/null
@@ -0,0 +1,83 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Nodes: XYZ Sliders for Normal Node
+
+Tweak the Normal node more accurately by using these sliders. Not the most
+intuitive way to tweak, but it helps.
+
+ProTip: Hit Shift+Drag for moving in very small steps.
+
+Coded by Lukas Töenne. Thanks!
+Find it on the Properties panel, when selecting a Normal node.
+"""
+
+
+import bpy
+from mathutils import Vector
+
+
+# FEATURE: Normal Node Values, by Lukas Tönne
+def init():
+    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
+        )
+    bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
+    bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector
+
+
+def clear():
+    del bpy.types.ShaderNodeNormal.normal_vector
+    del bpy.types.CompositorNodeNormal.normal_vector
+
+
+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
+
+
+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
+
+
+def register():
+    init()
+    bpy.types.NODE_PT_active_node_properties.append(ui_node_normal_values)
+
+
+def unregister():
+    bpy.types.NODE_PT_active_node_properties.remove(ui_node_normal_values)
+    clear()
diff --git a/amaranth/node_editor/simplify_nodes.py b/amaranth/node_editor/simplify_nodes.py
new file mode 100644 (file)
index 0000000..71aec24
--- /dev/null
@@ -0,0 +1,147 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Nodes Simplify Panel [WIP Feature]
+
+Disable/Enable certain nodes at a time. Useful to quickly "simplify"
+compositing.
+This feature is a work in progress, the main issue now is when switching
+many different kinds one after the other.
+
+On the Nodes Editor Properties N panel.
+"""
+
+import bpy
+
+
+def init():
+    nodes_compo_types = (
+        ("ALL", "All Types", "", 0),
+        ("BLUR", "Blur", "", 1),
+        ("BOKEHBLUR", "Bokeh Blur", "", 2),
+        ("VECBLUR", "Vector Blur", "", 3),
+        ("DEFOCUS", "Defocus", "", 4),
+        ("R_LAYERS", "Render Layer", "", 5),
+    )
+    node = bpy.types.Node
+    nodes_compo = bpy.types.CompositorNodeTree
+    nodes_compo.types = bpy.props.EnumProperty(
+        items=nodes_compo_types, name="Types")
+    nodes_compo.toggle_mute = bpy.props.BoolProperty(default=False)
+    node.status = bpy.props.BoolProperty(default=False)
+
+
+def clear():
+    wm = bpy.context.window_manager
+    for p in ("types", "toggle_mute", "status"):
+        if wm.get(p):
+            del wm[p]
+
+
+class AMTH_NODE_PT_simplify(bpy.types.Panel):
+
+    bl_space_type = "NODE_EDITOR"
+    bl_region_type = "UI"
+    bl_label = "Simplify"
+    bl_options = {"DEFAULT_CLOSED"}
+
+    @classmethod
+    def poll(cls, context):
+        space = context.space_data
+        return space.type == "NODE_EDITOR" \
+            and space.node_tree is not None \
+            and space.tree_type == "CompositorNodeTree"
+
+    def draw(self, context):
+        layout = self.layout
+        node_tree = context.scene.node_tree
+
+        if node_tree is not None:
+            layout.prop(node_tree, "types")
+            layout.operator(AMTH_NODE_OT_toggle_mute.bl_idname,
+                            text="Turn On" if node_tree.toggle_mute else "Turn Off",
+                            icon="RESTRICT_VIEW_OFF" if node_tree.toggle_mute else "RESTRICT_VIEW_ON")
+
+            if node_tree.types == "VECBLUR":
+                layout.label(text="This will also toggle the Vector pass {}".format(
+                    "on" if node_tree.toggle_mute else "off"), icon="INFO")
+
+
+class AMTH_NODE_OT_toggle_mute(bpy.types.Operator):
+
+    bl_idname = "node.toggle_mute"
+    bl_label = "Toggle Mute"
+
+    def execute(self, context):
+        scene = context.scene
+        node_tree = scene.node_tree
+        node_type = node_tree.types
+        rlayers = scene.render
+
+        if "amaranth_pass_vector" not in scene.keys():
+            scene["amaranth_pass_vector"] = []
+
+        # can"t extend() the list, so make a dummy one
+        pass_vector = scene["amaranth_pass_vector"]
+
+        if not pass_vector:
+            pass_vector = []
+
+        if node_tree.toggle_mute:
+            for node in node_tree.nodes:
+                if node_type == "ALL":
+                    node.mute = node.status
+                if node.type == node_type:
+                    node.mute = node.status
+                if node_type == "VECBLUR":
+                    for layer in rlayers.layers:
+                        if layer.name in pass_vector:
+                            layer.use_pass_vector = True
+                            pass_vector.remove(layer.name)
+
+                node_tree.toggle_mute = False
+
+        else:
+            for node in node_tree.nodes:
+                if node_type == "ALL":
+                    node.mute = True
+                if node.type == node_type:
+                    node.status = node.mute
+                    node.mute = True
+                if node_type == "VECBLUR":
+                    for layer in rlayers.layers:
+                        if layer.use_pass_vector:
+                            pass_vector.append(layer.name)
+                            layer.use_pass_vector = False
+                            pass
+
+                node_tree.toggle_mute = True
+
+        # Write back to the custom prop
+        pass_vector = sorted(set(pass_vector))
+        scene["amaranth_pass_vector"] = pass_vector
+
+        return {"FINISHED"}
+
+
+def register():
+    init()
+    bpy.utils.register_class(AMTH_NODE_PT_simplify)
+    bpy.utils.register_class(AMTH_NODE_OT_toggle_mute)
+
+
+def unregister():
+    clear()
+    bpy.utils.unregister_class(AMTH_NODE_PT_simplify)
+    bpy.utils.unregister_class(AMTH_NODE_OT_toggle_mute)
diff --git a/amaranth/node_editor/switch_material.py b/amaranth/node_editor/switch_material.py
new file mode 100644 (file)
index 0000000..bd5d6bc
--- /dev/null
@@ -0,0 +1,68 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Material Selector
+
+Quickly switch materials in the active mesh without going to the Properties editor
+
+Based on 'Afeitadora's work on Elysiun
+http://www.elysiun.com/forum/showthread.php?290097-Dynamic-Object-Dropdown-List&p=2361851#post2361851
+
+"""
+
+import bpy
+
+def ui_node_editor_material_select(self, context):
+
+    act_ob = context.active_object
+
+    if act_ob and context.active_object.type in {'MESH', 'CURVE', 'SURFACE', 'META'} and \
+        context.space_data.tree_type == 'ShaderNodeTree' and \
+        context.space_data.shader_type == 'OBJECT':
+
+        if act_ob.active_material:
+            mat_name = act_ob.active_material.name
+        else:
+            mat_name = "No Material"
+
+        self.layout.operator_menu_enum("material.menu_select",
+                                       "material_select",
+                                        text=mat_name,
+                                       icon="MATERIAL")
+
+class AMNodeEditorMaterialSelect(bpy.types.Operator):
+    bl_idname = "material.menu_select"
+    bl_label = "Select Material"
+    bl_description = "Switch to another material in this mesh"
+
+    def avail_materials(self,context):
+        items = [(str(i),x.name,x.name, "MATERIAL", i) for i,x in enumerate(bpy.context.active_object.material_slots)]
+        return items
+    material_select = bpy.props.EnumProperty(items = avail_materials, name = "Available Materials")
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object
+    
+    def execute(self,context):
+        bpy.context.active_object.active_material_index = int(self.material_select)
+        return {'FINISHED'}
+
+def register():
+    bpy.utils.register_class(AMNodeEditorMaterialSelect)
+    bpy.types.NODE_HT_header.append(ui_node_editor_material_select)
+
+def unregister():
+    bpy.utils.unregister_class(AMNodeEditorMaterialSelect)
+    bpy.types.NODE_HT_header.remove(ui_node_editor_material_select)
diff --git a/amaranth/node_editor/templates/__init__.py b/amaranth/node_editor/templates/__init__.py
new file mode 100644 (file)
index 0000000..90edac4
--- /dev/null
@@ -0,0 +1,81 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Node Templates - Vignette, Vector Blur
+
+Add a set of nodes with one click, in this version I added a "Vignette"
+as first example.
+
+There is no official way to make a vignette, this is just my approach at
+it. Templates: On the Compositor's header, "Template" pulldown. Or hit W.
+Vignette: Adjust the size and position of the vignette with the Ellipse
+Mask's X/Y and width, height values.
+"""
+
+import bpy
+from amaranth.node_editor.templates.vectorblur import AMTH_NODE_OT_AddTemplateVectorBlur
+from amaranth.node_editor.templates.vignette import AMTH_NODE_OT_AddTemplateVignette
+
+
+KEYMAPS = list()
+
+
+# Node Templates Menu
+class AMTH_NODE_MT_amaranth_templates(bpy.types.Menu):
+    bl_idname = 'AMTH_NODE_MT_amaranth_templates'
+    bl_space_type = 'NODE_EDITOR'
+    bl_label = "Templates"
+    bl_description = "List of Amaranth Templates"
+
+    def draw(self, context):
+        layout = self.layout
+        layout.operator(
+            AMTH_NODE_OT_AddTemplateVectorBlur.bl_idname,
+            text="Vector Blur",
+            icon='FORCE_HARMONIC')
+        layout.operator(
+            AMTH_NODE_OT_AddTemplateVignette.bl_idname,
+            text="Vignette",
+            icon='COLOR')
+
+
+def node_templates_pulldown(self, context):
+    if context.space_data.tree_type == 'CompositorNodeTree':
+        layout = self.layout
+        row = layout.row(align=True)
+        row.scale_x = 1.3
+        row.menu("AMTH_NODE_MT_amaranth_templates",
+                 icon="NODETREE")
+
+
+def register():
+    bpy.utils.register_class(AMTH_NODE_MT_amaranth_templates)
+    bpy.utils.register_class(AMTH_NODE_OT_AddTemplateVignette)
+    bpy.utils.register_class(AMTH_NODE_OT_AddTemplateVectorBlur)
+    bpy.types.NODE_HT_header.append(node_templates_pulldown)
+    kc = bpy.context.window_manager.keyconfigs.addon
+    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"
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_NODE_MT_amaranth_templates)
+    bpy.utils.unregister_class(AMTH_NODE_OT_AddTemplateVignette)
+    bpy.utils.unregister_class(AMTH_NODE_OT_AddTemplateVectorBlur)
+    bpy.types.NODE_HT_header.remove(node_templates_pulldown)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/node_editor/templates/vectorblur.py b/amaranth/node_editor/templates/vectorblur.py
new file mode 100644 (file)
index 0000000..2cb9b45
--- /dev/null
@@ -0,0 +1,69 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import bpy
+from mathutils import Vector
+
+
+class AMTH_NODE_OT_AddTemplateVectorBlur(bpy.types.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"}
diff --git a/amaranth/node_editor/templates/vignette.py b/amaranth/node_editor/templates/vignette.py
new file mode 100644 (file)
index 0000000..5e8ce0a
--- /dev/null
@@ -0,0 +1,100 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import bpy
+from mathutils import Vector
+
+
+class AMTH_NODE_OT_AddTemplateVignette(bpy.types.Operator):
+    bl_idname = "node.template_add_vignette"
+    bl_label = "Add Vignette"
+    bl_description = "Add a vignette effect"
+    bl_options = {"REGISTER", "UNDO"}
+
+    @classmethod
+    def poll(cls, context):
+        space = context.space_data
+        return space.type == "NODE_EDITOR" \
+            and space.node_tree is not None \
+            and space.tree_type == "CompositorNodeTree"
+
+    # used as reference the setup scene script from master nazgul
+    def _setupNodes(self, context):
+        scene = context.scene
+        space = context.space_data
+        tree = scene.node_tree
+        has_act = True if tree.nodes.active else False
+
+        bpy.ops.node.select_all(action="DESELECT")
+
+        ellipse = tree.nodes.new(type="CompositorNodeEllipseMask")
+        ellipse.width = 0.8
+        ellipse.height = 0.4
+        blur = tree.nodes.new(type="CompositorNodeBlur")
+        blur.use_relative = True
+        blur.factor_x = 30
+        blur.factor_y = 50
+        ramp = tree.nodes.new(type="CompositorNodeValToRGB")
+        ramp.color_ramp.interpolation = "B_SPLINE"
+        ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
+
+        overlay = tree.nodes.new(type="CompositorNodeMixRGB")
+        overlay.blend_type = "OVERLAY"
+        overlay.inputs[0].default_value = 0.8
+        overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
+
+        tree.links.new(ellipse.outputs["Mask"], blur.inputs["Image"])
+        tree.links.new(blur.outputs["Image"], ramp.inputs[0])
+        tree.links.new(ramp.outputs["Image"], overlay.inputs[2])
+        if has_act:
+            tree.links.new(tree.nodes.active.outputs[0], overlay.inputs[1])
+
+        if has_act:
+            overlay.location = tree.nodes.active.location
+            overlay.location += Vector((350.0, 0.0))
+        else:
+            overlay.location += Vector(
+                (space.cursor_location[0], space.cursor_location[1]))
+
+        ellipse.location = overlay.location
+        ellipse.location += Vector((-715.0, -400))
+        ellipse.inputs[0].hide = True
+        ellipse.inputs[1].hide = True
+
+        blur.location = ellipse.location
+        blur.location += Vector((300.0, 0.0))
+        blur.inputs["Size"].hide = True
+
+        ramp.location = blur.location
+        ramp.location += Vector((175.0, 0))
+        ramp.outputs["Alpha"].hide = True
+
+        for node in (ellipse, blur, ramp, overlay):
+            node.select = True
+            node.show_preview = False
+
+        bpy.ops.node.join()
+
+        frame = ellipse.parent
+        frame.label = "Vignette"
+        frame.use_custom_color = True
+        frame.color = (0.1, 0.1, 0.1)
+
+        overlay.parent = None
+        overlay.label = "Vignette Overlay"
+
+    def execute(self, context):
+        self._setupNodes(context)
+
+        return {"FINISHED"}
diff --git a/amaranth/prefs.py b/amaranth/prefs.py
new file mode 100644 (file)
index 0000000..f023463
--- /dev/null
@@ -0,0 +1,133 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import bpy
+
+
+class AmaranthToolsetPreferences(bpy.types.AddonPreferences):
+    bl_idname = "amaranth"
+    use_frame_current = bpy.props.BoolProperty(
+        name="Current Frame Slider",
+        description="Set the current frame from the Specials menu in the 3D View",
+        default=True,
+    )
+    use_file_save_reload = bpy.props.BoolProperty(
+        name="Save & Reload File",
+        description="File menu > Save & Reload, or Ctrl + Shift + W",
+        default=True,
+    )
+
+    use_scene_refresh = bpy.props.BoolProperty(
+        name="Refresh Scene",
+        description="Specials Menu [W], or hit F5",
+        default=True,
+    )
+    use_timeline_extra_info = bpy.props.BoolProperty(
+        name="Timeline Extra Info",
+        description="Timeline Header",
+        default=True,
+    )
+    use_image_node_display = bpy.props.BoolProperty(
+        name="Active Image Node in Editor",
+        description="Display active node image in image editor",
+        default=True,
+    )
+    use_scene_stats = bpy.props.BoolProperty(
+        name="Extra Scene Statistics",
+        description="Display extra scene statistics in Info header (may be slow in heavy scenes)",
+        default=False,
+    )
+
+    frames_jump = bpy.props.IntProperty(
+        name="Frames",
+        description="Number of frames to jump forward/backward",
+        default=10,
+        min=1)
+
+    use_framerate = bpy.props.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 = bpy.props.BoolProperty(
+        name="Current Layers for Render",
+        description="Save the layers that should be enabled for render",
+        default=True,
+    )
+
+    def draw(self, context):
+        layout = self.layout
+
+        layout.label(
+            text="Here you can enable or disable specific tools, "
+                 "in case they interfere with others or are just plain annoying")
+
+        split = layout.split(percentage=0.25)
+
+        col = split.column()
+        sub = col.column(align=True)
+        sub.label(text="3D View", icon="VIEW3D")
+        sub.prop(self, "use_frame_current")
+        sub.prop(self, "use_scene_refresh")
+
+        sub.separator()
+
+        sub.label(text="General", icon="SCENE_DATA")
+        sub.prop(self, "use_file_save_reload")
+        sub.prop(self, "use_timeline_extra_info")
+        sub.prop(self, "use_scene_stats")
+        sub.prop(self, "use_layers_for_render")
+        sub.prop(self, "use_framerate")
+
+        sub.separator()
+
+        sub.label(text="Nodes Editor", icon="NODETREE")
+        sub.prop(self, "use_image_node_display")
+
+        col = split.column()
+        sub = col.column(align=True)
+        sub.label(text="")
+        sub.label(
+            text="Set the current frame from the Specials menu in the 3D View [W]")
+        sub.label(
+            text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
+
+        sub.separator()
+        sub.label(text="")  # General icon
+        sub.label(
+            text="Quickly save and reload the current file (no warning!). "
+                 "File menu or Ctrl+Shift+W")
+        sub.label(
+            text="SMPTE Timecode and frames left/ahead on Timeline's header")
+        sub.label(
+            text="Display extra stats for Scenes, Cameras, Meshlights (Cycles). Can be slow in heavy scenes")
+        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(
+            text="When double-clicking an Image node, display it on the Image editor "
+                 "(if any)")
+
+
+def register():
+    bpy.utils.register_class(AmaranthToolsetPreferences)
+
+
+def unregister():
+    bpy.utils.unregister_class(AmaranthToolsetPreferences)
diff --git a/amaranth/render/__init__.py b/amaranth/render/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/render/border_camera.py b/amaranth/render/border_camera.py
new file mode 100644 (file)
index 0000000..5f5a35d
--- /dev/null
@@ -0,0 +1,64 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Set Camera Bounds as Render Border
+
+When in camera view, we can now set the border-render to be the same size
+of the camera, so we don't render outside the view. Makes faster render
+preview. Under Specials menu W, when in Camera view.
+"""
+
+import bpy
+
+
+class AMTH_VIEW3D_OT_render_border_camera(bpy.types.Operator):
+
+    """Set camera bounds as render border"""
+    bl_idname = "view3d.render_border_camera"
+    bl_label = "Camera as Render Border"
+
+    @classmethod
+    def poll(cls, context):
+        return context.space_data.region_3d.view_perspective == "CAMERA"
+
+    def execute(self, context):
+        render = context.scene.render
+        render.use_border = True
+        render.border_min_x = 0
+        render.border_min_y = 0
+        render.border_max_x = 1
+        render.border_max_y = 1
+
+        return {"FINISHED"}
+
+
+def button_render_border_camera(self, context):
+    view3d = context.space_data.region_3d
+
+    if view3d.view_perspective == "CAMERA":
+        layout = self.layout
+        layout.separator()
+        layout.operator(AMTH_VIEW3D_OT_render_border_camera.bl_idname,
+                        text="Camera as Render Border",
+                        icon="FULLSCREEN_ENTER")
+
+
+def register():
+    bpy.utils.register_class(AMTH_VIEW3D_OT_render_border_camera)
+    bpy.types.VIEW3D_MT_object_specials.append(button_render_border_camera)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_VIEW3D_OT_render_border_camera)
+    bpy.types.VIEW3D_MT_object_specials.remove(button_render_border_camera)
diff --git a/amaranth/render/final_resolution.py b/amaranth/render/final_resolution.py
new file mode 100644 (file)
index 0000000..deb81a6
--- /dev/null
@@ -0,0 +1,53 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+UI: Final Resolution
+
+Always wondered how big the render was going to be when rendering at a
+certain %?
+This feature displays a "Final Resolution" label with the size in pixels
+of your render, it also displays the size for border renders.
+
+On the 'Dimensions' panel, Render properties.
+"""
+import bpy
+
+
+def render_final_resolution_ui(self, context):
+
+    rd = context.scene.render
+    layout = self.layout
+
+    final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
+    final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
+
+    if rd.use_border:
+        final_res_x_border = round(
+            (final_res_x * (rd.border_max_x - rd.border_min_x)))
+        final_res_y_border = round(
+            (final_res_y * (rd.border_max_y - rd.border_min_y)))
+        layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
+                     str(final_res_x)[:-2], str(final_res_y)[:-2],
+                     str(final_res_x_border), str(final_res_y_border)))
+    else:
+        layout.label(text="Final Resolution: {} x {}".format(
+                     str(final_res_x)[:-2], str(final_res_y)[:-2]))
+
+
+def register():
+    bpy.types.RENDER_PT_dimensions.append(render_final_resolution_ui)
+
+
+def unregister():
+    bpy.types.RENDER_PT_dimensions.remove(render_final_resolution_ui)
diff --git a/amaranth/render/meshlight_add.py b/amaranth/render/meshlight_add.py
new file mode 100644 (file)
index 0000000..a96db23
--- /dev/null
@@ -0,0 +1,194 @@
+import bpy
+from mathutils import Vector
+from amaranth.utils import cycles_exists
+
+
+# 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 = bpy.props.BoolProperty(
+        name="Single Sided",
+        default=True,
+        description="Only emit light on one side",
+        )
+
+    is_constant = bpy.props.BoolProperty(
+        name="Constant Falloff",
+        default=False,
+        description="Energy is constant (i.e. the Sun), "
+                    "independent of how close to the source you are",
+        )
+
+    visible = bpy.props.BoolProperty(
+        name="Visible on Camera",
+        default=False,
+        description="Whether to show the meshlight source on Camera",
+        )
+
+    size = bpy.props.FloatProperty(
+        name="Size",
+        description="Meshlight size. Lower is sharper shadows, higher is softer",
+        min=0.01, max=100.0,
+        default=1.0,
+        )
+
+    strength = bpy.props.FloatProperty(
+        name="Strength",
+        min=0.01, max=100000.0,
+        default=1.5,
+        step=0.25,
+        )
+
+    temperature = bpy.props.FloatProperty(
+        name="Temperature",
+        min=800, max=12000.0,
+        default=5500.0,
+        step=800.0,
+        description="Temperature in Kelvin. Lower is warmer, higher is colder",
+        )
+
+    rotation = bpy.props.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))
+
+            blackbody = nodes.new(type="ShaderNodeBlackbody")
+            blackbody.inputs['Temperature'].default_value = self.temperature
+            blackbody.location = emission.location
+            blackbody.location += Vector((-180.0, 0.0))
+            blackbody.label = 'Temperature'
+
+            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[0])
+            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
+
+            blackbody = nodes.new(type="ShaderNodeBlackbody")
+            blackbody.inputs['Temperature'].default_value = self.temperature
+            blackbody.location = emission.location
+            blackbody.location += Vector((-180.0, 0.0))
+            blackbody.label = 'Temperature'
+
+            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'])
+
+        if self.is_constant:
+            falloff = nodes.new(type="ShaderNodeLightFalloff")
+            falloff.inputs['Strength'].default_value = self.strength
+            falloff.location = emission.location
+            falloff.location += Vector((-180.0, -80.0))
+
+            links.new(falloff.outputs['Constant'], emission.inputs['Strength'])
+
+            for sockets in falloff.outputs:
+                sockets.hide = True
+
+        # so it shows slider on properties editor
+        for sockets in emission.inputs:
+            sockets.show_expanded = True
+
+        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
+
+
+def register():
+    bpy.utils.register_class(AMTH_OBJECT_OT_meshlight_add)
+    bpy.types.INFO_MT_mesh_add.append(ui_menu_lamps_add)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_meshlight_add)
+    bpy.types.INFO_MT_mesh_add.remove(ui_menu_lamps_add)
diff --git a/amaranth/render/meshlight_select.py b/amaranth/render/meshlight_select.py
new file mode 100644 (file)
index 0000000..c315507
--- /dev/null
@@ -0,0 +1,63 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Select Meshlights
+
+Select all the meshes that emit light. On the header of the 3D View, top
+of the select menu.
+"""
+
+import bpy
+from amaranth import utils
+
+
+class AMTH_OBJECT_OT_select_meshlights(bpy.types.Operator):
+
+    """Select light emitting meshes"""
+    bl_idname = "object.select_meshlights"
+    bl_label = "Select Meshlights"
+    bl_options = {"UNDO"}
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene.render.engine == "CYCLES"
+
+    def execute(self, context):
+        # Deselect everything first
+        bpy.ops.object.select_all(action="DESELECT")
+
+        for ob in context.scene.objects:
+            if utils.cycles_is_emission(context, ob):
+                ob.select = True
+                context.scene.objects.active = ob
+
+        if not context.selected_objects and not context.scene.objects.active:
+            self.report({"INFO"}, "No meshlights to select")
+
+        return {"FINISHED"}
+
+
+def button_select_meshlights(self, context):
+    if utils.cycles_exists() and utils.cycles_active(context):
+        self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
+
+
+def register():
+    bpy.utils.register_class(AMTH_OBJECT_OT_select_meshlights)
+    bpy.types.VIEW3D_MT_select_object.append(button_select_meshlights)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_select_meshlights)
+    bpy.types.VIEW3D_MT_select_object.remove(button_select_meshlights)
diff --git a/amaranth/render/only_render.py b/amaranth/render/only_render.py
new file mode 100644 (file)
index 0000000..664051d
--- /dev/null
@@ -0,0 +1,55 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+3D View Shading Shortcuts
+
+Two new shortcuts have been added to the 3D View.
+Shift+Z Rendered Preview (now in Blender 2.70)
+Alt+Shift+Z Only Render
+"""
+
+import bpy
+
+
+KEYMAPS = list()
+
+
+class AMTH_VIEW3D_OT_show_only_render(bpy.types.Operator):
+    bl_idname = "view3d.show_only_render"
+    bl_label = "Show Only Render"
+
+    def execute(self, context):
+        space = bpy.context.space_data
+
+        if space.show_only_render:
+            space.show_only_render = False
+        else:
+            space.show_only_render = True
+        return {"FINISHED"}
+
+
+def register():
+    bpy.utils.register_class(AMTH_VIEW3D_OT_show_only_render)
+    kc = bpy.context.window_manager.keyconfigs.addon
+    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)
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_VIEW3D_OT_show_only_render)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/render/passepartout.py b/amaranth/render/passepartout.py
new file mode 100644 (file)
index 0000000..525cfe0
--- /dev/null
@@ -0,0 +1,43 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Passepartout on Specials menu
+
+The passepartout value of local cameras is now available on the Specials
+menu for easy access.
+Under Specials menu W, when in Camera view.
+"""
+
+import bpy
+
+
+def button_camera_passepartout(self, context):
+
+    view3d = context.space_data.region_3d
+    cam = context.scene.camera.data
+
+    if view3d.view_perspective == "CAMERA":
+        layout = self.layout
+        if cam.show_passepartout:
+            layout.prop(cam, "passepartout_alpha", text="Passepartout")
+        else:
+            layout.prop(cam, "show_passepartout")
+
+
+def register():
+    bpy.types.VIEW3D_MT_object_specials.append(button_camera_passepartout)
+
+
+def unregister():
+    bpy.types.VIEW3D_MT_object_specials.remove(button_camera_passepartout)
diff --git a/amaranth/render/remember_layers.py b/amaranth/render/remember_layers.py
new file mode 100644 (file)
index 0000000..97e878b
--- /dev/null
@@ -0,0 +1,237 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Remember Layers for Render
+If you're doing lighting/rendering tasks, you'll probably have a bunch of
+layers that you want/need to be enabled for final renders.
+
+When tweaking lighting, or if somebody else has to open the file to check
+something, you/they are likely to tweak the layers visibility and forget
+which ones were needed for the render to look good.
+
+In the Render Layers properties, you'll now find a "Save Current Layers for
+Render" button, this will save the currently visible scene layers as those
+that should be enabled for render. You can adjust this further by clicking
+on the slots on the right.
+
+Now all you need to do before saving your file for rendering is press the
+"View Layers for Render". Find it on the Render Layers properties.
+"""
+
+import bpy
+
+
+# FEATURE: Set Layers to Render
+class AMTH_SCENE_OT_layers_render_save(bpy.types.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(bpy.types.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(bpy.types.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 = bpy.props.BoolProperty()
+    number = bpy.props.IntProperty()
+
+    def execute(self, context):
+        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(bpy.types.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["amaranth"].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["amaranth"].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
+
+
+def register():
+    bpy.utils.register_class(AMTH_SCENE_OT_layers_render_clear)
+    bpy.utils.register_class(AMTH_SCENE_OT_layers_render_save)
+    bpy.utils.register_class(AMTH_SCENE_OT_layers_render_set_individual)
+    bpy.utils.register_class(AMTH_SCENE_OT_layers_render_view)
+    bpy.types.VIEW3D_HT_header.append(ui_layers_for_render_header)
+    bpy.types.RENDERLAYER_PT_layers.append(ui_layers_for_render)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_SCENE_OT_layers_render_clear)
+    bpy.utils.unregister_class(AMTH_SCENE_OT_layers_render_save)
+    bpy.utils.unregister_class(AMTH_SCENE_OT_layers_render_set_individual)
+    bpy.utils.unregister_class(AMTH_SCENE_OT_layers_render_view)
+    bpy.types.VIEW3D_HT_header.remove(ui_layers_for_render_header)
+    bpy.types.RENDERLAYER_PT_layers.remove(ui_layers_for_render)
diff --git a/amaranth/render/render_output_z.py b/amaranth/render/render_output_z.py
new file mode 100644 (file)
index 0000000..bbde91f
--- /dev/null
@@ -0,0 +1,53 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+EXR Render: Warn when Z not connected
+Display a little warning label when exporting EXR, with Z Buffer enabled, but
+forgot to plug the Z input in the Compositor.
+
+Might be a bit too specific, but found it nice to remember to plug the Z input
+if we explicitely specify for Z Buffers to be saved (because it's disabled by
+default).
+
+Find it on the Output panel, Render properties.
+"""
+import bpy
+
+
+# // 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
+
+
+def register():
+    bpy.types.RENDER_PT_output.append(ui_render_output_z)
+
+
+def unregister():
+    bpy.types.RENDER_PT_output.remove(ui_render_output_z)
diff --git a/amaranth/render/samples_scene.py b/amaranth/render/samples_scene.py
new file mode 100644 (file)
index 0000000..00105cd
--- /dev/null
@@ -0,0 +1,226 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Cycles: Samples per Scene
+
+When working in production, it's often more convenient to do lighting and
+compositing in different scenes (so you can later append the comp scene
+    to bring together nodes, settings, lamps, RenderLayers).
+
+This would lead to work with more than one scene. When doing render tests
+you want to know at a glance how many samples the other scenes have,
+without manually switching. This is the idea behind the feature.
+
+Find it on the Sampling panel, on Render properties.
+Developed during Caminandes Open Movie Project
+"""
+
+import bpy
+from amaranth import utils
+
+
+class AMTH_RENDER_OT_cycles_samples_percentage_set(bpy.types.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(bpy.types.Operator):
+
+    """Set a percentage of the final render samples"""
+    bl_idname = "scene.amaranth_cycles_samples_percentage"
+    bl_label = "Set Render Samples Percentage"
+
+    percent = bpy.props.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"}
+
+
+def render_cycles_scene_samples(self, context):
+
+    layout = self.layout
+    scene = context.scene
+    render = scene.render
+    if utils.cycles_exists():
+        cscene = scene.cycles
+        list_sampling = scene.amaranth_cycles_list_sampling
+
+    # Set Render Samples
+    if utils.cycles_exists() and cscene.progressive == "BRANCHED_PATH":
+        layout.separator()
+        split = layout.split()
+        col = split.column()
+
+        col.operator(
+            AMTH_RENDER_OT_cycles_samples_percentage_set.bl_idname,
+            text="%s" %
+            "Set as Render Samples" if cscene.use_samples_final else "Set New Render Samples",
+            icon="%s" %
+            "PINNED" if cscene.use_samples_final else "UNPINNED")
+
+        col = split.column()
+        row = col.row(align=True)
+        row.enabled = True if scene.get("amth_cycles_samples_final") else False
+
+        row.operator(
+            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
+            text="100%").percent = 100
+        row.operator(
+            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
+            text="75%").percent = 75
+        row.operator(
+            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
+            text="50%").percent = 50
+        row.operator(
+            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
+            text="25%").percent = 25
+
+    # List Samples
+    if (len(scene.render.layers) > 1) or (len(bpy.data.scenes) > 1):
+
+        box = layout.box()
+        row = box.row(align=True)
+        col = row.column(align=True)
+
+        row = col.row(align=True)
+        row.alignment = "LEFT"
+        row.prop(scene, "amaranth_cycles_list_sampling",
+                 icon="%s" % "TRIA_DOWN" if list_sampling else "TRIA_RIGHT",
+                 emboss=False)
+
+    if list_sampling:
+        if len(scene.render.layers) == 1 and render.layers[0].samples == 0:
+            pass
+        else:
+            col.separator()
+            col.label(text="RenderLayers:", icon="RENDERLAYERS")
+
+            for rl in scene.render.layers:
+                row = col.row(align=True)
+                row.label(rl.name, icon="BLANK1")
+                row.prop(
+                    rl, "samples", text="%s" %
+                    "Samples" if rl.samples > 0 else "Automatic (%s)" %
+                    (cscene.aa_samples if cscene.progressive == "BRANCHED_PATH" else cscene.samples))
+
+        if (len(bpy.data.scenes) > 1):
+            col.separator()
+
+            col.label(text="Scenes:", icon="SCENE_DATA")
+
+            if utils.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)
+
+
+def init():
+    scene = bpy.types.Scene
+    if utils.cycles_exists():
+        scene.amaranth_cycles_list_sampling = bpy.props.BoolProperty(
+            default=False,
+            name="Samples Per:")
+
+        bpy.types.CyclesRenderSettings.use_samples_final = bpy.props.BoolProperty(
+            name="Use Final Render Samples",
+            description="Use current shader samples as final render samples",
+            default=False)
+
+
+def clear():
+    wm = bpy.context.window_manager
+    for p in ("amarath_cycles_list_sampling", "use_samples_final"):
+        if p in wm:
+            del wm[p]
+
+
+def register():
+    init()
+    bpy.utils.register_class(AMTH_RENDER_OT_cycles_samples_percentage)
+    bpy.utils.register_class(AMTH_RENDER_OT_cycles_samples_percentage_set)
+    if utils.cycles_exists():
+        bpy.types.CyclesRender_PT_sampling.append(render_cycles_scene_samples)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_RENDER_OT_cycles_samples_percentage)
+    bpy.utils.unregister_class(AMTH_RENDER_OT_cycles_samples_percentage_set)
+    if utils.cycles_exists():
+        bpy.types.CyclesRender_PT_sampling.remove(render_cycles_scene_samples)
+    clear()
diff --git a/amaranth/render/unsimplify.py b/amaranth/render/unsimplify.py
new file mode 100644 (file)
index 0000000..90b66c0
--- /dev/null
@@ -0,0 +1,76 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Unsimplify Render
+
+Handy option when you want to simplify the 3D View but unsimplify during
+render. Find it on the Simplify panel under Scene properties.
+"""
+
+import bpy
+from amaranth import utils
+
+
+def init():
+    scene = bpy.types.Scene
+    scene.use_unsimplify_render = bpy.props.BoolProperty(
+        default=False,
+        name="Unsimplify Render",
+        description="Disable Simplify during render")
+    scene.simplify_status = bpy.props.BoolProperty(default=False)
+
+
+def clear():
+    wm = bpy.context.window_manager
+    for p in ("use_unsimplify_render", "simplify_status"):
+        if wm.get(p):
+            del wm[p]
+
+
+@bpy.app.handlers.persistent
+def unsimplify_render_pre(scene):
+    render = scene.render
+    scene.simplify_status = render.use_simplify
+
+    if scene.use_unsimplify_render:
+        render.use_simplify = False
+
+
+@bpy.app.handlers.persistent
+def unsimplify_render_post(scene):
+    render = scene.render
+    render.use_simplify = scene.simplify_status
+
+
+def unsimplify_ui(self, context):
+    scene = bpy.context.scene
+    self.layout.prop(scene, 'use_unsimplify_render')
+
+
+def register():
+    init()
+    bpy.app.handlers.render_pre.append(unsimplify_render_pre)
+    bpy.app.handlers.render_post.append(unsimplify_render_post)
+    bpy.types.SCENE_PT_simplify.append(unsimplify_ui)
+    if utils.cycles_exists():
+        bpy.types.CyclesScene_PT_simplify.append(unsimplify_ui)
+
+
+def unregister():
+    clear()
+    bpy.app.handlers.render_pre.remove(unsimplify_render_pre)
+    bpy.app.handlers.render_post.remove(unsimplify_render_post)
+    bpy.types.SCENE_PT_simplify.remove(unsimplify_ui)
+    if utils.cycles_exists():
+        bpy.types.CyclesScene_PT_simplify.remove(unsimplify_ui)
diff --git a/amaranth/scene/__init__.py b/amaranth/scene/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/amaranth/scene/current_blend.py b/amaranth/scene/current_blend.py
new file mode 100644 (file)
index 0000000..6b984d1
--- /dev/null
@@ -0,0 +1,51 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+File Browser > Go to Current Blend's Folder
+
+For when you're lost browsing files and want to go back to the currently
+open blend's directory. Look for it on the File Browser's header, only
+shows up if the file is saved.
+"""
+
+import bpy
+
+
+class AMTH_FILE_OT_directory_current_blend(bpy.types.Operator):
+
+    """Go to the directory of the currently open blend file"""
+    bl_idname = "file.directory_current_blend"
+    bl_label = "Current Blend's Folder"
+
+    def execute(self, context):
+        bpy.ops.file.select_bookmark(dir="//")
+        return {"FINISHED"}
+
+
+def button_directory_current_blend(self, context):
+    if bpy.data.filepath:
+        self.layout.operator(
+            AMTH_FILE_OT_directory_current_blend.bl_idname,
+            text="Current Blend's Folder",
+            icon="APPEND_BLEND")
+
+
+def register():
+    bpy.utils.register_class(AMTH_FILE_OT_directory_current_blend)
+    bpy.types.FILEBROWSER_HT_header.append(button_directory_current_blend)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_FILE_OT_directory_current_blend)
+    bpy.types.FILEBROWSER_HT_header.remove(button_directory_current_blend)
diff --git a/amaranth/scene/debug.py b/amaranth/scene/debug.py
new file mode 100755 (executable)
index 0000000..c023f39
--- /dev/null
@@ -0,0 +1,1349 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Scene Debug Panel
+
+This is something I've been wanting to have for a while, a way to know
+certain info about your scene. A way to "debug" it, especially when
+working in production with other teams, this came in very handy.
+
+Being mostly a lighting guy myself, I needed two main features to start with:
+
+* List Cycles Material using X shader
+Where X is any shader type you want. It will display (and print on console)
+a list of all the materials containing the shader you specified above.
+Good for finding out if there's any Meshlight (Emission) material hidden,
+or if there are many glossy shaders making things noisy.
+A current limitation is that it doesn't look inside node groups (yet,
+working on it!). It works since 0.8.8!
+
+* Lamps List
+This is a collapsable list of Lamps in the scene(s).
+It allows you to quickly see how many lamps you have, select them by
+clicking on their name, see their type (icon), samples number (if using
+Branched Path Tracing), size, and change their visibility.
+The active lamp is indicated by a triangle on the right.
+
+Under the "Scene Debug" panel in Scene properties.
+"""
+
+# TODO: module cleanup! maybe break it up in a package
+#     dicts instead of if, elif,else all over the place.
+#     helper functions instead of everything on the execute method.
+#     str.format() + dicts instead of inline % op all over the place.
+#     remove/manage debug print calls.
+#     self.__class__.attr? use type(self).attr or self.attr instead.
+#     avoid duplicate code/patterns through helper functions.
+
+import os
+import bpy
+from amaranth import utils
+
+
+def init():
+    scene = bpy.types.Scene
+
+    scene.amaranth_debug_scene_list_missing_images = bpy.props.BoolProperty(
+        default=False,
+        name="List Missing Images",
+        description="Display a list of all the missing images")
+
+    scene.amaranth_lighterscorner_list_meshlights = bpy.props.BoolProperty(
+        default=False,
+        name="List Meshlights",
+        description="Include light emitting meshes on the list")
+
+    amth_datablock_types = (
+        ("IMAGE_DATA", "Image", "Image Datablocks", 0),
+        ("MATERIAL", "Material", "Material Datablocks", 1),
+        ("GROUP_VCOL", "Vertex Colors", "Vertex Color Layers", 2),
+    )
+    scene.amth_datablock_types = bpy.props.EnumProperty(
+        items=amth_datablock_types,
+        name="Type",
+        description="Datablock Type")
+
+    if utils.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),
+            ("MIX_SHADER", "Mix Shader", "", 17),
+            ("ADD_SHADER", "Add Shader", "", 18),
+        )
+        scene.amaranth_cycles_node_types = bpy.props.EnumProperty(
+            items=cycles_shader_node_types, name="Shader")
+
+
+def clear():
+    props = (
+        "amaranth_debug_scene_list_missing_images",
+        "amaranth_cycles_node_types",
+        "amaranth_lighterscorner_list_meshlights",
+    )
+    wm = bpy.context.window_manager
+    for p in props:
+        if wm.get(p):
+            del wm[p]
+
+
+class AMTH_SCENE_OT_cycles_shader_list_nodes(bpy.types.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 utils.cycles_exists() and utils.cycles_active(context)
+
+    def execute(self, context):
+        node_type = context.scene.amaranth_cycles_node_types
+        roughness = False
+        self.__class__.materials = []
+        shaders_roughness = ("BSDF_GLOSSY", "BSDF_DIFFUSE", "BSDF_GLASS")
+
+        print("\n=== Cycles Shader Type: %s === \n" % node_type)
+
+        for ma in bpy.data.materials:
+            if ma.node_tree:
+                nodes = ma.node_tree.nodes
+
+                print_unconnected = (
+                    "Note: \nOutput from \"%s\" node" % node_type,
+                    "in material \"%s\"" % ma.name, "not connected\n")
+
+                for no in nodes:
+                    if no.type == node_type:
+                        for ou in no.outputs:
+                            if ou.links:
+                                connected = True
+                                if no.type in shaders_roughness:
+                                    roughness = "R: %.4f" % no.inputs[
+                                        "Roughness"].default_value
+                                else:
+                                    roughness = False
+                            else:
+                                connected = False
+                                print(print_unconnected)
+
+                            if ma.name not in self.__class__.materials:
+                                self.__class__.materials.append(
+                                    "%s%s [%s] %s%s%s" %
+                                    ("[L] " if ma.library else "",
+                                     ma.name,
+                                     ma.users,
+                                     "[F]" if ma.use_fake_user else "",
+                                     " - [%s]" %
+                                     roughness if roughness else "",
+                                     " * Output not connected" if not connected else ""))
+
+                    elif no.type == "GROUP":
+                        if no.node_tree:
+                            for nog in no.node_tree.nodes:
+                                if nog.type == node_type:
+                                    for ou in nog.outputs:
+                                        if ou.links:
+                                            connected = True
+                                            if nog.type in shaders_roughness:
+                                                roughness = "R: %.4f" % nog.inputs[
+                                                    "Roughness"].default_value
+                                            else:
+                                                roughness = False
+                                        else:
+                                            connected = False
+                                            print(print_unconnected)
+
+                                        if ma.name not in self.__class__.materials:
+                                            self.__class__.materials.append(
+                                                '%s%s%s [%s] %s%s%s' %
+                                                ("[L] " if ma.library else "",
+                                                 "Node Group:  %s%s  ->  " %
+                                                 ("[L] " if no.node_tree.library else "",
+                                                  no.node_tree.name),
+                                                    ma.name,
+                                                    ma.users,
+                                                    "[F]" if ma.use_fake_user else "",
+                                                    " - [%s]" %
+                                                    roughness if roughness else "",
+                                                    " * Output not connected" if not connected else ""))
+
+                    self.__class__.materials = sorted(
+                        list(set(self.__class__.materials)))
+
+        if len(self.__class__.materials) == 0:
+            self.report({"INFO"},
+                        "No materials with nodes type %s found" % node_type)
+        else:
+            print("* A total of %d %s using %s was found \n" % (
+                len(self.__class__.materials),
+                "material" if len(
+                    self.__class__.materials) == 1 else "materials",
+                node_type))
+
+            count = 0
+
+            for mat in self.__class__.materials:
+                print('%02d. %s' %
+                      (count + 1, self.__class__.materials[count]))
+                count += 1
+            print("\n")
+
+        self.__class__.materials = sorted(list(set(self.__class__.materials)))
+
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(bpy.types.Operator):
+
+    """Clear the list below"""
+    bl_idname = "scene.cycles_list_nodes_clear"
+    bl_label = "Clear Materials List"
+
+    @classmethod
+    def poll(cls, context):
+        return utils.cycles_exists()
+
+    def execute(self, context):
+        AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
+        print("* Cleared Cycles Materials List")
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_amaranth_object_select(bpy.types.Operator):
+
+    """Select object"""
+    bl_idname = "scene.amaranth_object_select"
+    bl_label = "Select Object"
+    object = bpy.props.StringProperty()
+
+    def execute(self, context):
+        if self.object:
+            object = bpy.data.objects[self.object]
+
+            bpy.ops.object.select_all(action="DESELECT")
+            object.select = True
+            context.scene.objects.active = object
+
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_list_missing_node_links(bpy.types.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:
+                            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(bpy.types.Operator):
+
+    """List objects with empty material slots"""
+    bl_idname = "scene.list_missing_material_slots"
+    bl_label = "List Empty Material Slots"
+
+    objects = []
+    libraries = []
+
+    def execute(self, context):
+        self.__class__.objects = []
+        self.__class__.libraries = []
+
+        for ob in bpy.data.objects:
+            for ma in ob.material_slots:
+                if not ma.material:
+                    self.__class__.objects.append('%s%s' % (
+                        '[L] ' if ob.library else '',
+                        ob.name))
+                    if ob.library:
+                        self.__class__.libraries.append(ob.library.filepath)
+
+        self.__class__.objects = sorted(list(set(self.__class__.objects)))
+        self.__class__.libraries = sorted(list(set(self.__class__.libraries)))
+
+        if len(self.__class__.objects) == 0:
+            self.report({"INFO"},
+                        "No objects with empty material slots found")
+        else:
+            print(
+                "\n* A total of %d %s with empty material slots was found \n" %
+                (len(
+                    self.__class__.objects), "object" if len(
+                    self.__class__.objects) == 1 else "objects"))
+
+            count = 0
+            count_lib = 0
+
+            for obs in self.__class__.objects:
+                print('%02d. %s' % (
+                    count + 1, self.__class__.objects[count]))
+                count += 1
+
+            if self.__class__.libraries:
+                print("\n\n* Check %s:\n" %
+                     ("this library" if len(self.__class__.libraries) == 1
+                      else "these libraries"))
+
+                for libs in self.__class__.libraries:
+                    print('%02d. %s' % (
+                        count_lib + 1, self.__class__.libraries[count_lib]))
+                    count_lib += 1
+            print("\n")
+
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_list_missing_material_slots_clear(bpy.types.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_list_users_for_x_type(bpy.types.Operator):
+    bl_idname = "scene.amth_list_users_for_x_type"
+    bl_label = "Select"
+    bl_description = "Select Datablock Name"
+
+    def avail(self,context):
+        datablock_type = bpy.context.scene.amth_datablock_types
+
+        if datablock_type == 'IMAGE_DATA':
+            where = []
+            for im in bpy.data.images:
+                if im.name not in {'Render Result', 'Viewer Node'}:
+                    where.append(im)
+
+        elif datablock_type == 'MATERIAL':
+            where = bpy.data.materials
+
+        elif datablock_type == 'GROUP_VCOL':
+            where = []
+            for ob in bpy.data.objects:
+                if ob.type == 'MESH':
+                    for v in ob.data.vertex_colors:
+                        if v and v not in where:
+                            where.append(v)
+            where = list(set(where))
+
+        items = [(str(i),x.name,x.name, datablock_type, i) for i,x in enumerate(where)]
+        items = sorted(list(set(items)))
+        return items
+
+    list_type_select = bpy.props.EnumProperty(items = avail, name = "Available")
+
+    @classmethod
+    def poll(cls, context):
+        return bpy.context.scene.amth_datablock_types
+    
+    def execute(self,context):
+        datablock_type = bpy.context.scene.amth_datablock_types
+
+        if datablock_type == 'IMAGE_DATA':
+            where = []
+            for im in bpy.data.images:
+                if im.name not in {'Render Result', 'Viewer Node'}:
+                    where.append(im)
+
+        elif datablock_type == 'MATERIAL':
+            where = bpy.data.materials
+
+        elif datablock_type == 'GROUP_VCOL':
+            where = []
+            for ob in bpy.data.objects:
+                if ob.type == 'MESH':
+                    for v in ob.data.vertex_colors:
+                        if v and v not in where:
+                            where.append(v)
+            where = list(set(where))
+
+        bpy.context.scene.amth_list_users_for_x_name = where[int(self.list_type_select)].name
+        return {'FINISHED'}
+
+
+class AMTH_SCENE_OT_list_users_for_x(bpy.types.Operator):
+
+    """List users for a particular datablock"""
+    bl_idname = "scene.amth_list_users_for_x"
+    bl_label = "List Users for Datablock"
+
+    name = bpy.props.StringProperty()
+    users = {}
+
+    def execute(self, context):
+
+        datablock_type = context.scene.amth_datablock_types
+        d = bpy.data
+
+        if self.name:
+            x = self.name
+        else:
+            x = context.scene.amth_list_users_for_x_name
+
+        dtype = context.scene.amth_datablock_types
+
+        self.__class__.users = {
+            'OBJECT_DATA' : [], # Store Objects with Material
+            'MATERIAL' : [], # Materials (Node tree)
+            'LAMP' : [], # Lamps
+            'WORLD' : [], # World
+            'TEXTURE' : [], # Textures (Psys, Brushes)
+            'MODIFIER' : [], # Modifiers
+            'MESH_DATA' : [], # Vertex Colors
+            'VIEW3D' : [], # Background Images
+            'NODETREE' : [], # Compositor
+        }
+
+        # IMAGE TYPE
+        if dtype == 'IMAGE_DATA':
+            # Check Materials
+            for ma in d.materials:
+                # Cycles
+                if utils.cycles_exists():
+                    if ma and ma.node_tree and ma.node_tree.nodes:
+                        materials = []
+
+                        for nd in ma.node_tree.nodes:
+                            if nd and nd.type in {'TEX_IMAGE','TEX_ENVIRONMENT'}:
+                                materials.append(nd)
+                            if nd and nd.type == 'GROUP':
+                                if nd.node_tree and nd.node_tree.nodes:
+                                    for ng in nd.node_tree.nodes:
+                                        if ng.type in {'TEX_IMAGE','TEX_ENVIRONMENT'}:
+                                            materials.append(ng)
+
+                            for no in materials:
+                                if no.image and no.image.name == x:
+                                    objects = []
+
+                                    for ob in d.objects:
+                                        if ma.name in ob.material_slots:
+                                            objects.append(ob.name)
+
+                                    links = False
+
+                                    for o in no.outputs:
+                                        if o.links:
+                                            links = True
+
+                                    name = '"{0}" {1}{2}'.format(
+                                            ma.name,
+                                            'in object: {0}'.format(objects) if objects else ' (unassigned)',
+                                            '' if links else ' (unconnected)')
+
+                                    if name not in self.__class__.users['MATERIAL']:
+                                        self.__class__.users['MATERIAL'].append(name)
+
+            # Check Lamps
+            for la in d.lamps:
+                # Cycles
+                if utils.cycles_exists():
+                    if la and la.node_tree and la.node_tree.nodes:
+                        for no in la.node_tree.nodes:
+                            if no and \
+                               no.type in {'TEX_IMAGE','TEX_ENVIRONMENT'} and \
+                               no.image and no.image.name == x:
+                                    if la.name not in self.__class__.users['LAMP']:
+                                        self.__class__.users['LAMP'].append(la.name)
+
+            # Check World
+            for wo in d.worlds:
+                # Cycles
+                if utils.cycles_exists():
+                    if wo and wo.node_tree and wo.node_tree.nodes:
+                        for no in wo.node_tree.nodes:
+                            if no and \
+                               no.type in {'TEX_IMAGE','TEX_ENVIRONMENT'} and \
+                               no.image and no.image.name == x:
+                                if wo.name not in self.__class__.users['WORLD']:
+                                    self.__class__.users['WORLD'].append(wo.name)
+
+            # Check Textures
+            for te in d.textures:
+                if te and te.type =='IMAGE' and te.image:
+                    name = te.image.name
+
+                    if name == x and \
+                       name not in self.__class__.users['TEXTURE'] :
+                        self.__class__.users['TEXTURE'].append(te.name)
+
+            # Check Modifiers in Objects
+            for ob in d.objects:
+                for mo in ob.modifiers:
+                    if mo.type in {'UV_PROJECT'}:
+                        image = mo.image
+
+                        if mo and image and image.name == x:
+                            name = '"{0}" modifier in {1}'.format(mo.name, ob.name)
+                            if name not in self.__class__.users['MODIFIER']:
+                                self.__class__.users['MODIFIER'].append(name)
+
+            # Check Background Images in Viewports
+            for scr in d.screens:
+                for ar in scr.areas:
+                    if ar.type == 'VIEW_3D':
+                        for bg in ar.spaces.active.background_images:
+                            image = bg.image
+
+                            if bg and image and image.name == x:
+                                name = 'Background for 3D Viewport in Screen "{0}"'\
+                                        .format(scr.name)
+                                if name not in self.__class__.users['VIEW3D']:
+                                    self.__class__.users['VIEW3D'].append(name)
+
+            # Check the Compositor
+            for sce in d.scenes:
+                if sce.node_tree and sce.node_tree.nodes:
+                    nodes = []
+
+                    for nd in sce.node_tree.nodes:
+                        if nd.type == 'IMAGE':
+                            nodes.append(nd)
+                        elif nd.type == 'GROUP':
+                            if nd.node_tree and nd.node_tree.nodes:
+                                for ng in nd.node_tree.nodes:
+                                    if ng.type == 'IMAGE':
+                                        nodes.append(ng)
+
+                        for no in nodes:
+                            if no.image and no.image.name == x:
+
+                                links = False
+
+                                for o in no.outputs:
+                                    if o.links:
+                                        links = True
+
+                                name = 'Node {0} in Compositor (Scene "{1}"){2}'.format(
+                                        no.name,
+                                        sce.name,
+                                        '' if links else ' (unconnected)')
+
+                                if name not in self.__class__.users['NODETREE']:
+                                    self.__class__.users['NODETREE'].append(name)
+
+        # MATERIAL TYPE
+        if dtype == 'MATERIAL':
+            # Check Materials
+            for ob in d.objects:
+                for ma in ob.material_slots:
+                    if ma.name == x:
+                        if ma not in self.__class__.users['OBJECT_DATA']:
+                            self.__class__.users['OBJECT_DATA'].append(ob)
+
+        # VERTEX COLOR TYPE
+        elif dtype == 'GROUP_VCOL':
+            # Check VCOL in Meshes
+            for ob in bpy.data.objects:
+                if ob.type == 'MESH':
+                    for v in ob.data.vertex_colors:
+                        if v.name == x:
+                            name = '{0}'.format(ob.name)
+
+                            if name not in self.__class__.users['MESH_DATA']:
+                                self.__class__.users['MESH_DATA'].append(name)
+
+            # Check VCOL in Materials
+            for ma in d.materials:
+                # Cycles
+                if utils.cycles_exists():
+                    if ma and ma.node_tree and ma.node_tree.nodes:
+                        for no in ma.node_tree.nodes:
+                            if no and no.type in {'ATTRIBUTE'}:
+                                if no.attribute_name == x:
+                                    objects = []
+
+                                    for ob in d.objects:
+                                        if ma.name in ob.material_slots:
+                                            objects.append(ob.name)
+
+                                    if objects:
+                                        name = '{0} in object: {1}'.format(ma.name, objects)
+                                    else:
+                                        name = '{0} (unassigned)'.format(ma.name)
+
+                                    if name not in self.__class__.users['MATERIAL']:
+                                        self.__class__.users['MATERIAL'].append(name)
+
+        # Print on console
+        empty = True
+
+        for t in self.__class__.users:
+            if self.__class__.users[t]:
+                empty = False
+                print('\n== {0} {1} use {2} "{3}" ==\n'.format(
+                        len(self.__class__.users[t]),
+                        t,
+                        dtype,
+                        x))
+                for p in self.__class__.users[t]:
+                    print(' {0}'.format(p))
+        if empty:
+            print('\n== No users for {0} ==\n'.format(x))
+
+        #print('Type: {0}'.format(context.scene.amth_datablock_types))
+        #print('X: {0}'.format(x))
+
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_list_users_for_x_clear(bpy.types.Operator):
+
+    """Clear the list below"""
+    bl_idname = "scene.amth_list_users_for_x_clear"
+    bl_label = "Clear Users Lists for X"
+
+    def execute(self, context):
+        AMTH_SCENE_OT_list_users_for_x.users = {}
+        print("* Cleared Users List for Datablock")
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_OT_blender_instance_open(bpy.types.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:
+            filepath = os.path.normpath(bpy.path.abspath(self.filepath))
+
+            import subprocess
+            try:
+                subprocess.Popen([bpy.app.binary_path, filepath])
+            except:
+                print("Error on the new Blender instance")
+                import traceback
+                traceback.print_exc()
+
+        return {"FINISHED"}
+
+
+class AMTH_SCENE_PT_scene_debug(bpy.types.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
+        images = bpy.data.images
+        images_missing = []
+        list_missing_images = scene.amaranth_debug_scene_list_missing_images
+        materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
+        materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
+        missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
+        missing_material_slots_count = len(
+            AMTH_SCENE_OT_list_missing_material_slots.objects)
+        missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
+        engine = scene.render.engine
+
+        # List Missing Images
+        box = layout.box()
+        row = box.row(align=True)
+        split = row.split()
+        col = split.column()
+
+        if images:
+            import os.path
+
+            for im in images:
+                if im.type not in ("UV_TEST", "RENDER_RESULT", "COMPOSITING"):
+                    if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
+                        images_missing.append(["%s%s [%s]%s" % (
+                            "[L] " if im.library else "",
+                            im.name, im.users,
+                            " [F]" if im.use_fake_user else ""),
+                            im.filepath if im.filepath else "No Filepath",
+                            im.library.filepath if im.library else ""])
+
+            if images_missing:
+                row = col.row(align=True)
+                row.alignment = "LEFT"
+                row.prop(
+                    scene,
+                    "amaranth_debug_scene_list_missing_images",
+                    icon="%s" %
+                    "TRIA_DOWN" if list_missing_images else "TRIA_RIGHT",
+                    emboss=False)
+
+                split = split.split()
+                col = split.column()
+
+                col.label(text="%s missing %s" % (
+                          str(len(images_missing)),
+                          'image' if len(images_missing) == 1 else "images"),
+                          icon="ERROR")
+
+                if list_missing_images:
+                    col = box.column(align=True)
+                    for mis in images_missing:
+                        row = col.row(align=True)
+                        row.alignment = "LEFT"
+                        row.label(
+                            text=mis[0],
+                            icon="IMAGE_DATA")
+                        # XXX TODO // make clicking on image work (needs new op to set x)
+                        # row.operator(
+                        #     AMTH_SCENE_OT_list_users_for_x.bl_idname,
+                        #     text=mis[0],
+                        #     icon="IMAGE_DATA",
+                        #     emboss=False).name = mis[0][:-4]
+
+                        row = col.row(align=True)
+                        row.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
+                        if mis[2]:
+                            row = col.row(align=True)
+                            row.alignment = "LEFT"
+                            row.operator(
+                                AMTH_SCENE_OT_blender_instance_open.bl_idname,
+                                text=mis[2],
+                                icon="LINK_BLEND",
+                                emboss=False).filepath = mis[2]
+                        col.separator()
+            else:
+                row = col.row(align=True)
+                row.alignment = "LEFT"
+                row.label(
+                    text="Great! No missing images", icon="RIGHTARROW_THIN")
+
+                split = split.split()
+                col = split.column()
+
+                col.label(text="%s %s loading correctly" % (
+                          str(len(images)),
+                          "image" if len(images) == 1 else "images"),
+                          icon="IMAGE_DATA")
+        else:
+            row = col.row(align=True)
+            row.alignment = "LEFT"
+            row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
+
+        # List Cycles Materials by Shader
+        if utils.cycles_exists() and engine == "CYCLES":
+            box = layout.box()
+            split = box.split()
+            col = split.column(align=True)
+            col.prop(scene, "amaranth_cycles_node_types",
+                     icon="MATERIAL")
+
+            row = split.row(align=True)
+            row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
+                         icon="SORTSIZE",
+                         text="List Materials Using Shader")
+            if materials_count != 0:
+                row.operator(
+                    AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
+                    icon="X", text="")
+            col.separator()
+
+            try:
+                materials
+            except NameError:
+                pass
+            else:
+                if materials_count != 0:
+                    col = box.column(align=True)
+                    count = 0
+                    col.label(
+                        text="%s %s found" %
+                        (materials_count,
+                         "material" if materials_count == 1 else "materials"),
+                        icon="INFO")
+                    for mat in materials:
+                        count += 1
+                        col.label(
+                            text="%s" %
+                            (materials[
+                                count -
+                                1]),
+                            icon="MATERIAL")
+
+        # List Missing Node Trees
+        box = layout.box()
+        row = box.row(align=True)
+        split = row.split()
+        col = split.column(align=True)
+
+        split = col.split()
+        split.label(text="Node Links")
+        split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
+                       icon="NODETREE")
+
+        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
+                AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
+                AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
+            col.label(text="Warning! Check Console", icon="ERROR")
+
+        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
+            col.label(
+                text="%s" %
+                ("%s node %s missing link" %
+                 (str(
+                     AMTH_SCENE_OT_list_missing_node_links.count_groups),
+                     "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
+                icon="NODETREE")
+        if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
+            col.label(
+                text="%s" %
+                ("%s image %s missing link" %
+                 (str(
+                     AMTH_SCENE_OT_list_missing_node_links.count_images),
+                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
+                icon="IMAGE_DATA")
+
+        if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
+            col.label(
+                text="%s" %
+                ("%s image %s with no output conected" %
+                 (str(
+                     AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
+                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
+                icon="NODE")
+
+        # List Empty Materials Slots
+        box = layout.box()
+        split = box.split()
+        col = split.column(align=True)
+        col.label(text="Material Slots")
+
+        row = split.row(align=True)
+        row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
+                     icon="MATERIAL",
+                     text="List Empty Materials Slots")
+        if missing_material_slots_count != 0:
+            row.operator(
+                AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
+                icon="X", text="")
+        col.separator()
+
+        try:
+            missing_material_slots_obs
+        except NameError:
+            pass
+        else:
+            if missing_material_slots_count != 0:
+                col = box.column(align=True)
+                count = 0
+                count_lib = 0
+                col.label(
+                    text="%s %s with empty material slots found" %
+                    (missing_material_slots_count,
+                     "object" if missing_material_slots_count == 1 else "objects"),
+                    icon="INFO")
+
+                for obs in missing_material_slots_obs:
+                    count += 1
+
+                    row = col.row()
+                    row.alignment = "LEFT"
+                    row.label(
+                        text="%s" % missing_material_slots_obs[count - 1],
+                        icon="OBJECT_DATA")
+
+                if missing_material_slots_lib:
+                    col.separator()
+                    col.label("Check %s:" % (
+                        "this library" if
+                        len(missing_material_slots_lib) == 1
+                        else "these libraries"))
+
+                    for libs in missing_material_slots_lib:
+                        count_lib += 1
+                        row = col.row(align=True)
+                        row.alignment = "LEFT"
+                        row.operator(
+                            AMTH_SCENE_OT_blender_instance_open.bl_idname,
+                            text=missing_material_slots_lib[
+                                count_lib - 1],
+                            icon="LINK_BLEND",
+                            emboss=False).filepath = missing_material_slots_lib[
+                            count_lib - 1]
+
+
+        # List Users for Datablock
+        list_users = AMTH_SCENE_OT_list_users_for_x.users
+
+        box = layout.box()
+        row = box.row(align=True)
+        row.label(text="List Users for Datablock")
+
+        col = box.column(align=True)
+        split = col.split()
+        row = split.row(align=True)
+        row.prop(scene, "amth_datablock_types",
+                    icon=scene.amth_datablock_types,
+                    text="")
+
+        row.operator_menu_enum("scene.amth_list_users_for_x_type",
+                                       "list_type_select",
+                                       text=scene.amth_list_users_for_x_name)
+
+        row = split.row(align=True)
+        row.enabled = True if scene.amth_list_users_for_x_name else False
+        row.operator(AMTH_SCENE_OT_list_users_for_x.bl_idname).name = scene.amth_list_users_for_x_name
+        if list_users:
+            row.operator(
+                AMTH_SCENE_OT_list_users_for_x_clear.bl_idname,
+                icon="X", text="")
+
+        try:
+            list_users
+        except NameError:
+            pass
+        else:
+            if list_users:
+                empty = True
+                col = box.column(align=True)
+                for t in list_users:
+                    if list_users[t]:
+                        empty = False
+                        for ma in list_users[t]:
+                            row = col.row(align=True)
+                            row.alignment = "LEFT"
+                            if t == 'OBJECT_DATA':
+                                row.operator(
+                                    AMTH_SCENE_OT_amaranth_object_select.bl_idname,
+                                    text="%s %s%s" %
+                                    (" [L] " if ma.library else "",
+                                     ma.name,
+                                     "" if ma.name in context.scene.objects else " [Not in Scene]"),
+                                    icon=t,
+                                    emboss=False).object = ma.name
+                            else:
+                                row.label(text=ma,
+                                          icon=t)
+                if empty:
+                    row = col.row(align=True)
+                    row.alignment = "LEFT"
+                    row.label(text="No users for '{0}'".format(
+                              scene.amth_list_users_for_x_name),
+                              icon='INFO')
+
+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 utils.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 utils.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 utils.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 utils.cycles_exists() else "")
+
+                for ob in objects:
+                    is_lamp = ob.type == "LAMP"
+                    is_emission = True if utils.cycles_is_emission(
+                        context, ob) and list_meshlights else False
+
+                    if ob and is_lamp or is_emission:
+                        lamp = ob.data
+                        if utils.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 utils.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 utils.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 utils.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")
+
+
+classes = {
+    AMTH_SCENE_PT_scene_debug,
+    AMTH_SCENE_OT_blender_instance_open,
+    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_cycles_shader_list_nodes,
+    AMTH_SCENE_OT_cycles_shader_list_nodes_clear,
+    AMTH_SCENE_OT_list_users_for_x,
+    AMTH_SCENE_OT_list_users_for_x_type,
+    AMTH_SCENE_OT_list_users_for_x_clear,
+    AMTH_LightersCorner
+}
+
+def register():
+    init()
+
+    for cls in classes:
+        bpy.utils.register_class(cls)
+
+    from bpy.types import Scene
+
+    bpy.types.Scene.amth_list_users_for_x_name = bpy.props.StringProperty(
+                                                    default="",
+                                                    name="Name",
+                                                    description="Which datablock type to look for")
+
+def unregister():
+    clear()
+
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+    from bpy.types import Scene
+
+    del Scene.amth_list_users_for_x_name
diff --git a/amaranth/scene/goto_library.py b/amaranth/scene/goto_library.py
new file mode 100644 (file)
index 0000000..8a18a9a
--- /dev/null
@@ -0,0 +1,90 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+File Browser: Libraries Bookmark
+
+The "Libraries" panel on the File Browser displays the path to all the
+libraries linked to that .blend. So you can quickly go to the folders
+related to the file.
+
+Click on any path to go to that directory.
+Developed during Caminandes Open Movie Project
+"""
+
+import bpy
+
+
+class AMTH_FILE_PT_libraries(bpy.types.Panel):
+    bl_space_type = "FILE_BROWSER"
+    bl_region_type = "TOOLS"
+    bl_category = "Bookmarks"
+    bl_label = "Libraries"
+
+    def draw(self, context):
+        layout = self.layout
+
+        libs = bpy.data.libraries
+        libslist = []
+
+        # Build the list of folders from libraries
+        import os.path
+
+        for lib in libs:
+            directory_name = os.path.dirname(lib.filepath)
+            libslist.append(directory_name)
+
+        # Remove duplicates and sort by name
+        libslist = set(libslist)
+        libslist = sorted(libslist)
+
+        # Draw the box with libs
+        row = layout.row()
+        box = row.box()
+
+        if libslist:
+            col = box.column()
+            for filepath in libslist:
+                if filepath != "//":
+                    row = col.row()
+                    row.alignment = "LEFT"
+                    props = row.operator(
+                        AMTH_FILE_OT_directory_go_to.bl_idname,
+                        text=filepath, icon="BOOKMARKS",
+                        emboss=False)
+                    props.filepath = filepath
+        else:
+            box.label(text="No libraries loaded")
+
+
+class AMTH_FILE_OT_directory_go_to(bpy.types.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"}
+
+
+def register():
+    bpy.utils.register_class(AMTH_FILE_PT_libraries)
+    bpy.utils.register_class(AMTH_FILE_OT_directory_go_to)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_FILE_PT_libraries)
+    bpy.utils.unregister_class(AMTH_FILE_OT_directory_go_to)
diff --git a/amaranth/scene/material_remove_unassigned.py b/amaranth/scene/material_remove_unassigned.py
new file mode 100644 (file)
index 0000000..8b9e208
--- /dev/null
@@ -0,0 +1,108 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+import bpy
+
+
+# FEATURE: Delete Materials not assigned to any verts
+class AMTH_OBJECT_OT_material_remove_unassigned(bpy.types.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
+
+
+def register():
+    bpy.utils.register_class(AMTH_OBJECT_OT_material_remove_unassigned)
+    bpy.types.MATERIAL_MT_specials.append(ui_material_remove_unassigned)
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_OBJECT_OT_material_remove_unassigned)
+    bpy.types.MATERIAL_MT_specials.remove(ui_material_remove_unassigned)
diff --git a/amaranth/scene/refresh.py b/amaranth/scene/refresh.py
new file mode 100644 (file)
index 0000000..707d732
--- /dev/null
@@ -0,0 +1,71 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Refresh Scene
+
+Refresh the current scene, useful when working with libraries or drivers.
+Could also add an option to refresh the VSE maybe? Usage: Hit F5 or find
+it on the Specials menu W.
+"""
+
+import bpy
+
+
+KEYMAPS = list()
+
+
+class AMTH_SCENE_OT_refresh(bpy.types.Operator):
+
+    """Refresh the current scene"""
+    bl_idname = "scene.refresh"
+    bl_label = "Refresh!"
+
+    def execute(self, context):
+        preferences = context.user_preferences.addons["amaranth"].preferences
+        scene = context.scene
+
+        if preferences.use_scene_refresh:
+            # Changing the frame is usually the best way to go
+            scene.frame_current = scene.frame_current
+            self.report({"INFO"}, "Scene Refreshed!")
+
+        return {"FINISHED"}
+
+
+def button_refresh(self, context):
+    preferences = context.user_preferences.addons["amaranth"].preferences
+
+    if preferences.use_scene_refresh:
+        self.layout.separator()
+        self.layout.operator(AMTH_SCENE_OT_refresh.bl_idname,
+                             text="Refresh!",
+                             icon="FILE_REFRESH")
+
+
+def register():
+    bpy.utils.register_class(AMTH_SCENE_OT_refresh)
+    bpy.types.VIEW3D_MT_object_specials.append(button_refresh)
+    kc = bpy.context.window_manager.keyconfigs.addon
+    km = kc.keymaps.new(name="Window")
+    kmi = km.keymap_items.new("scene.refresh", "F5", "PRESS",
+                              alt=True)
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_SCENE_OT_refresh)
+    bpy.types.VIEW3D_MT_object_specials.remove(button_refresh)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/scene/save_reload.py b/amaranth/scene/save_reload.py
new file mode 100644 (file)
index 0000000..e27285e
--- /dev/null
@@ -0,0 +1,76 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Save & Reload File
+
+When working with linked libraries, very often you need to save and load
+again to see the changes.
+This does it in one go, without asking, so be careful :)
+Usage: Hit Ctrl + Shift + W or find it at the bottom of the File menu.
+"""
+
+import bpy
+
+
+KEYMAPS = list()
+
+
+class AMTH_WM_OT_save_reload(bpy.types.Operator):
+
+    """Save and Reload the current blend file"""
+    bl_idname = "wm.save_reload"
+    bl_label = "Save & Reload"
+
+    def save_reload(self, context, path):
+        if not path:
+            bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
+            return
+        bpy.ops.wm.save_mainfile()
+        self.report({"INFO"}, "Saved & Reloaded")
+        bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
+
+    def execute(self, context):
+        path = bpy.data.filepath
+        self.save_reload(context, path)
+        return {"FINISHED"}
+
+
+def button_save_reload(self, context):
+    preferences = context.user_preferences.addons["amaranth"].preferences
+
+    if preferences.use_file_save_reload:
+        self.layout.separator()
+        self.layout.operator(
+            AMTH_WM_OT_save_reload.bl_idname,
+            text="Save & Reload",
+            icon="FILE_REFRESH")
+
+
+def register():
+    bpy.utils.register_class(AMTH_WM_OT_save_reload)
+    bpy.types.INFO_MT_file.append(button_save_reload)
+    wm = bpy.context.window_manager
+    kc = wm.keyconfigs.addon
+    km = kc.keymaps.new(name="Window")
+    kmi = km.keymap_items.new("wm.save_reload", "W", "PRESS",
+                              shift=True, ctrl=True)
+    KEYMAPS.append((km, kmi))
+
+
+def unregister():
+    bpy.utils.unregister_class(AMTH_WM_OT_save_reload)
+    bpy.types.INFO_MT_file.remove(button_save_reload)
+    for km, kmi in KEYMAPS:
+        km.keymap_items.remove(kmi)
+    KEYMAPS.clear()
diff --git a/amaranth/scene/stats.py b/amaranth/scene/stats.py
new file mode 100644 (file)
index 0000000..0f6f519
--- /dev/null
@@ -0,0 +1,61 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""
+Scene, Cameras, and Meshlights Count
+
+Increase the stats by displaying the number of scenes, cameras, and light
+emitting meshes.
+On the Info header.
+"""
+
+import bpy
+from amaranth import utils
+
+
+def stats_scene(self, context):
+
+    preferences = context.user_preferences.addons["amaranth"].preferences
+
+    if preferences.use_scene_stats:
+        scenes_count = str(len(bpy.data.scenes))
+        cameras_count = str(len(bpy.data.cameras))
+        cameras_selected = 0
+        meshlights = 0
+        meshlights_visible = 0
+
+        for ob in context.scene.objects:
+            if utils.cycles_is_emission(context, ob):
+                meshlights += 1
+                if ob in context.visible_objects:
+                    meshlights_visible += 1
+
+            if ob in context.selected_objects:
+                if ob.type == 'CAMERA':
+                    cameras_selected += 1
+
+        meshlights_string = '| Meshlights:{}/{}'.format(
+            meshlights_visible, meshlights)
+
+        row = self.layout.row(align=True)
+        row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
+                  scenes_count, cameras_selected, cameras_count,
+                  meshlights_string if utils.cycles_active(context) else ''))
+
+
+def register():
+    bpy.types.INFO_HT_header.append(stats_scene)
+
+
+def unregister():
+    bpy.types.INFO_HT_header.remove(stats_scene)
diff --git a/amaranth/utils.py b/amaranth/utils.py
new file mode 100644 (file)
index 0000000..f636787
--- /dev/null
@@ -0,0 +1,64 @@
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import bpy
+
+
+# FUNCTION: Checks if cycles is available
+def cycles_exists():
+    return hasattr(bpy.types.Scene, "cycles")
+
+
+# FUNCTION: Checks if cycles is the active renderer
+def cycles_active(context):
+    return context.scene.render.engine == "CYCLES"
+
+
+# FUNCTION: Check if material has Emission (for select and stats)
+def cycles_is_emission(context, ob):
+    is_emission = False
+
+    if not ob.material_slots:
+        return is_emission
+
+    for ma in ob.material_slots:
+        if not ma.material:
+            continue
+        if ma.material.node_tree and ma.material.node_tree.nodes:
+            for no in ma.material.node_tree.nodes:
+                if not no.type in ("EMISSION", "GROUP"):
+                    continue
+                for ou in no.outputs:
+                    if not ou.links:
+                        continue
+                    if no.type == "GROUP" and no.node_tree and no.node_tree.nodes:
+                        for gno in no.node_tree.nodes:
+                            if gno.type != "EMISSION":
+                                continue
+                            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
diff --git a/scene_amaranth_toolset.py b/scene_amaranth_toolset.py
deleted file mode 100755 (executable)
index 094c81c..0000000
+++ /dev/null
@@ -1,3235 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-bl_info = {
-    "name": "Amaranth Toolset",
-    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne",
-    "version": (0, 9, 5),
-    "blender": (2, 71),
-    "location": "Everywhere!",
-    "description": "A collection of tools and settings to improve productivity",
-    "warning": "",
-    "wiki_url": "http://pablovazquez.org/amaranth",
-    "tracker_url": "",
-    "category": "Scene"}
-
-
-import bpy
-import bmesh
-from bpy.types import Operator, AddonPreferences, Panel, Menu
-from bpy.props import (BoolProperty, EnumProperty,
-                       FloatProperty, FloatVectorProperty,
-                       IntProperty, StringProperty)
-from mathutils import Vector
-from bpy.app.handlers import persistent
-from bl_operators.presets import AddPresetBase
-
-# Addon wide, we need to know if cycles is available
-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__
-    use_frame_current = BoolProperty(
-            name="Current Frame Slider",
-            description="Set the current frame from the Specials menu in the 3D View",
-            default=True,
-            )
-    use_file_save_reload = BoolProperty(
-            name="Save & Reload File",
-            description="File menu > Save & Reload, or Ctrl + Shift + W",
-            default=True,
-            )
-
-    use_scene_refresh = BoolProperty(
-            name="Refresh Scene",
-            description="Specials Menu [W], or hit F5",
-            default=True,
-            )
-    use_timeline_extra_info = BoolProperty(
-            name="Timeline Extra Info",
-            description="Timeline Header",
-            default=True,
-            )
-    use_image_node_display = BoolProperty(
-            name="Active Image Node in Editor",
-            description="Display active node image in image editor",
-            default=True,
-            )
-    use_scene_stats = BoolProperty(
-            name="Extra Scene Statistics",
-            description="Display extra scene statistics in Info editor's header",
-            default=True,
-            )
-
-    frames_jump = IntProperty(
-                name="Frames",
-                description="Number of frames to jump forward/backward",
-                default=10,
-                min=1)
-
-    use_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
-
-        layout.label(
-            text="Here you can enable or disable specific tools, "
-                 "in case they interfere with others or are just plain annoying")
-
-        split = layout.split(percentage=0.25)
-
-        col = split.column()
-        sub = col.column(align=True)
-        sub.label(text="3D View", icon="VIEW3D")
-        sub.prop(self, "use_frame_current")
-        sub.prop(self, "use_scene_refresh")
-
-        sub.separator()
-
-        sub.label(text="General", icon="SCENE_DATA")
-        sub.prop(self, "use_file_save_reload")
-        sub.prop(self, "use_timeline_extra_info")
-        sub.prop(self, "use_scene_stats")
-        sub.prop(self, "use_layers_for_render")
-        sub.prop(self, "use_framerate")
-
-        sub.separator()
-
-        sub.label(text="Nodes Editor", icon="NODETREE")
-        sub.prop(self, "use_image_node_display")
-
-        col = split.column()
-        sub = col.column(align=True)
-        sub.label(text="")
-        sub.label(
-            text="Set the current frame from the Specials menu in the 3D View [W]")
-        sub.label(
-            text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
-
-        sub.separator()
-        sub.label(text="") # General icon
-        sub.label(
-            text="Quickly save and reload the current file (no warning!). "
-                 "File menu or Ctrl+Shift+W")
-        sub.label(
-            text="SMPTE Timecode and frames left/ahead on Timeline's header")
-        sub.label(
-            text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
-        sub.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(
-            text="When selecting an Image node, display it on the Image editor "
-                 "(if any)")
-
-# Properties
-def init_properties():
-
-    scene = bpy.types.Scene
-    node = bpy.types.Node
-    nodes_compo = bpy.types.CompositorNodeTree
-
-    scene.use_unsimplify_render = BoolProperty(
-        default=False,
-        name="Unsimplify Render",
-        description="Disable Simplify during render")
-    scene.simplify_status = BoolProperty(default=False)
-
-    node.use_matching_indices = BoolProperty(
-        default=True,
-        description="If disabled, display all available indices")
-
-    nodes_compo_types = [
-        ("ALL", "All Types", "", 0),
-        ("BLUR", "Blur", "", 1),
-        ("BOKEHBLUR", "Bokeh Blur", "", 2),
-        ("VECBLUR", "Vector Blur", "", 3),
-        ("DEFOCUS", "Defocus", "", 4),
-        ("R_LAYERS", "Render Layer", "", 5)
-        ]
-
-    nodes_compo.types = EnumProperty(
-        items=nodes_compo_types, name = "Types")
-
-    nodes_compo.toggle_mute = BoolProperty(default=False)
-    node.status = BoolProperty(default=False)
-
-    # Scene Debug
-    # Cycles Node Types
-    if 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="List Meshlights",
-        description="Include light emitting meshes on the list")
-
-    scene.amaranth_debug_scene_list_missing_images = BoolProperty(
-        default=False,
-        name="List Missing Images",
-        description="Display a list of all the missing images")
-
-    bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
-    bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector
-
-    bpy.types.Object.is_keyframe = is_keyframe
-
-    scene.amth_wire_toggle_scene_all = BoolProperty(
-        default=False,
-        name="All Scenes",
-        description="Toggle wire on objects in all scenes")
-    scene.amth_wire_toggle_is_selected = BoolProperty(
-        default=False,
-        name="Only Selected",
-        description="Only toggle wire on selected objects")
-    scene.amth_wire_toggle_edges_all = BoolProperty(
-        default=True,
-        name="All Edges",
-        description="Draw all edges")
-    scene.amth_wire_toggle_optimal = BoolProperty(
-        default=False,
-        name="Optimal Display",
-        description="Skip drawing/rendering of interior subdivided edges "
-                    "on meshes with Subdivision Surface modifier")
-
-def clear_properties():
-    props = (
-        "use_unsimplify_render",
-        "simplify_status",
-        "use_matching_indices",
-        "use_simplify_nodes_vector",
-        "status",
-        "types",
-        "toggle_mute",
-        "amaranth_cycles_node_types",
-        "amaranth_lighterscorner_list_meshlights",
-        "amaranth_debug_scene_list_missing_images",
-        "amarath_cycles_list_sampling",
-        "normal_vector",
-        "use_samples_final",
-        'amth_wire_toggle_is_selected',
-        'amth_wire_toggle_scene_all',
-        "amth_wire_toggle_edges_all",
-        "amth_wire_toggle_optimal"
-    )
-    
-    wm = bpy.context.window_manager
-    for p in props:
-        if p in wm:
-            del wm[p]
-
-# Some settings are bound to be saved on a startup py file
-def amaranth_text_startup(context):
-
-    amth_text_name = "AmaranthStartup.py"
-    amth_text_exists = False
-
-    global amth_text
-
-    try:
-        if bpy.data.texts:
-            for tx in bpy.data.texts:
-                if tx.name == amth_text_name:
-                    amth_text_exists = True
-                    amth_text = bpy.data.texts[amth_text_name]
-                    break
-                else:
-                    amth_text_exists = False
-
-        if not amth_text_exists:
-            bpy.ops.text.new()
-            amth_text = bpy.data.texts[-1]
-            amth_text.name = amth_text_name
-            amth_text.write("# Amaranth Startup Script\nimport bpy\n\n")
-            amth_text.use_module = True
-
-        return amth_text_exists
-    except AttributeError:
-        return None
-
-# FUNCTION: Check if material has Emission (for select and stats)
-def cycles_is_emission(context, ob):
-
-    is_emission = False
-
-    if ob.material_slots:
-        for ma in ob.material_slots:
-            if ma.material:
-                if ma.material.node_tree and ma.material.node_tree.nodes:
-                    for no in ma.material.node_tree.nodes:
-                        if no.type in {'EMISSION', 'GROUP'}:
-                            for ou in no.outputs:
-                                if ou.links:
-                                    if no.type == 'GROUP' and no.node_tree and no.node_tree.nodes:
-                                        for gno in no.node_tree.nodes:
-                                            if gno.type == 'EMISSION':
-                                                for gou in gno.outputs:
-                                                    if ou.links and gou.links:
-                                                        is_emission = True
-
-                                    elif no.type == 'EMISSION':
-                                        if ou.links:
-                                            is_emission = True
-    return is_emission
-
-# FUNCTION: Check if object has keyframes for a specific frame
-def is_keyframe(ob, frame):
-    if ob is not None and ob.animation_data is not None and ob.animation_data.action is not None:
-        for fcu in ob.animation_data.action.fcurves:
-            if frame in (p.co.x for p in fcu.keyframe_points):
-                return True
-    return False
-
-# FEATURE: Refresh Scene!
-class AMTH_SCENE_OT_refresh(Operator):
-    """Refresh the current scene"""
-    bl_idname = "scene.refresh"
-    bl_label = "Refresh!"
-    
-    def execute(self, context):
-        preferences = context.user_preferences.addons[__name__].preferences
-        scene = context.scene
-
-        if preferences.use_scene_refresh:    
-            # Changing the frame is usually the best way to go
-            scene.frame_current = scene.frame_current
-            self.report({"INFO"}, "Scene Refreshed!")
-            
-        return {'FINISHED'}
-
-def button_refresh(self, context):
-
-    preferences = context.user_preferences.addons[__name__].preferences
-
-    if preferences.use_scene_refresh:
-        self.layout.separator()
-        self.layout.operator(
-            AMTH_SCENE_OT_refresh.bl_idname,
-            text="Refresh!",
-            icon='FILE_REFRESH')
-# // FEATURE: Refresh Scene!
-
-# FEATURE: Save & Reload
-def save_reload(self, context, path):
-
-    if path:
-        bpy.ops.wm.save_mainfile()
-        self.report({'INFO'}, "Saved & Reloaded")
-        bpy.ops.wm.open_mainfile("EXEC_DEFAULT", filepath=path)
-    else:
-        bpy.ops.wm.save_as_mainfile("INVOKE_AREA")
-
-class AMTH_WM_OT_save_reload(Operator):
-    """Save and Reload the current blend file"""
-    bl_idname = "wm.save_reload"
-    bl_label = "Save & Reload"
-
-    def execute(self, context):
-
-        path = bpy.data.filepath
-        save_reload(self, context, path)
-        return {'FINISHED'}
-
-def button_save_reload(self, context):
-
-    preferences = context.user_preferences.addons[__name__].preferences
-
-    if preferences.use_file_save_reload:
-        self.layout.separator()
-        self.layout.operator(
-            AMTH_WM_OT_save_reload.bl_idname,
-            text="Save & Reload",
-            icon='FILE_REFRESH')
-# // FEATURE: Save & Reload
-
-# FEATURE: Current Frame
-def button_frame_current(self, context):
-
-    preferences = context.user_preferences.addons[__name__].preferences
-    scene = context.scene
-
-    if preferences.use_frame_current:
-        self.layout.separator()
-        self.layout.prop(
-            scene, "frame_current",
-            text="Set Current Frame")
-# // FEATURE: Current Frame
-
-# FEATURE: Timeline Time + Frames Left
-def label_timeline_extra_info(self, context):
-
-    preferences = context.user_preferences.addons[__name__].preferences
-    layout = self.layout
-    scene = context.scene
-
-    if preferences.use_timeline_extra_info:
-        row = layout.row(align=True)
-
-        row.operator(AMTH_SCREEN_OT_keyframe_jump_inbetween.bl_idname, icon="PREV_KEYFRAME", text="").backwards = True
-        row.operator(AMTH_SCREEN_OT_keyframe_jump_inbetween.bl_idname, icon="NEXT_KEYFRAME", text="").backwards = False
-
-        # Check for preview range
-        frame_start = scene.frame_preview_start if scene.use_preview_range else scene.frame_start
-        frame_end = scene.frame_preview_end if scene.use_preview_range else scene.frame_end
-        
-        row.label(text="%s / %s" % (bpy.utils.smpte_from_frame(scene.frame_current - frame_start),
-                        bpy.utils.smpte_from_frame(frame_end - frame_start)))
-
-        if (scene.frame_current > frame_end):
-            row.label(text="%s Frames Ahead" % ((frame_end - scene.frame_current) * -1))
-        elif (scene.frame_current == frame_start):
-            row.label(text="Start Frame (%s left)" % (frame_end - scene.frame_current))
-        elif (scene.frame_current == frame_end):
-            row.label(text="%s End Frame" % scene.frame_current)
-        else:
-            row.label(text="%s Frames Left" % (frame_end - scene.frame_current))
-
-# // FEATURE: Timeline Time + Frames Left
-
-# FEATURE: Directory Current Blend
-class AMTH_FILE_OT_directory_current_blend(Operator):
-    """Go to the directory of the currently open blend file"""
-    bl_idname = "file.directory_current_blend"
-    bl_label = "Current Blend's Folder"
-
-    def execute(self, context):
-        bpy.ops.file.select_bookmark(dir='//')
-        return {'FINISHED'}
-
-def button_directory_current_blend(self, context):
-
-    if bpy.data.filepath:
-        self.layout.operator(
-            AMTH_FILE_OT_directory_current_blend.bl_idname,
-            text="Current Blend's Folder",
-            icon='APPEND_BLEND')
-# // FEATURE: Directory Current Blend
-
-# FEATURE: Libraries panel on file browser
-class AMTH_FILE_PT_libraries(Panel):
-    bl_space_type = 'FILE_BROWSER'
-    bl_region_type = 'TOOLS'
-    bl_category = "Bookmarks"
-    bl_label = "Libraries"
-
-    def draw(self, context):
-        layout = self.layout
-
-        libs = bpy.data.libraries
-        libslist = []
-
-        # Build the list of folders from libraries
-        import os.path
-
-        for lib in libs:
-            directory_name = os.path.dirname(lib.filepath)
-            libslist.append(directory_name)
-
-        # Remove duplicates and sort by name
-        libslist = set(libslist)
-        libslist = sorted(libslist)
-
-        # Draw the box with libs
-
-        row = layout.row()
-        box = row.box()
-
-        if libslist:
-            col = box.column()
-            for filepath in libslist:
-                if filepath != '//':
-                    row = col.row()
-                    row.alignment = 'LEFT'
-                    props = row.operator(
-                        AMTH_FILE_OT_directory_go_to.bl_idname,
-                        text=filepath, icon="BOOKMARKS",
-                        emboss=False)
-                    props.filepath = filepath
-        else:
-            box.label(text='No libraries loaded')
-
-class AMTH_FILE_OT_directory_go_to(Operator):
-    """Go to this library's directory"""
-    bl_idname = "file.directory_go_to"
-    bl_label = "Go To"
-
-    filepath = bpy.props.StringProperty(subtype="FILE_PATH")
-
-    def execute(self, context):
-        bpy.ops.file.select_bookmark(dir=self.filepath)
-        return {'FINISHED'}
-
-# FEATURE: Node Templates
-class AMTH_NODE_OT_AddTemplateVignette(Operator):
-    bl_idname = "node.template_add_vignette"
-    bl_label = "Add Vignette"
-    bl_description = "Add a vignette effect"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    @classmethod
-    def poll(cls, context):
-        space = context.space_data
-        return space.type == 'NODE_EDITOR' \
-                and space.node_tree is not None \
-                and space.tree_type == 'CompositorNodeTree'
-
-    # used as reference the setup scene script from master nazgul
-    def _setupNodes(self, context):
-        scene = context.scene
-        space = context.space_data
-        tree = scene.node_tree
-        has_act = True if tree.nodes.active else False
-
-        bpy.ops.node.select_all(action='DESELECT')
-
-        ellipse = tree.nodes.new(type='CompositorNodeEllipseMask')
-        ellipse.width = 0.8
-        ellipse.height = 0.4
-        blur = tree.nodes.new(type='CompositorNodeBlur')
-        blur.use_relative = True
-        blur.factor_x = 30
-        blur.factor_y = 50
-        ramp = tree.nodes.new(type='CompositorNodeValToRGB')
-        ramp.color_ramp.interpolation = 'B_SPLINE'
-        ramp.color_ramp.elements[1].color = (0.6, 0.6, 0.6, 1)
-
-        overlay = tree.nodes.new(type='CompositorNodeMixRGB')
-        overlay.blend_type = 'OVERLAY'
-        overlay.inputs[0].default_value = 0.8
-        overlay.inputs[1].default_value = (0.5, 0.5, 0.5, 1)
-
-        tree.links.new(ellipse.outputs["Mask"],blur.inputs["Image"])
-        tree.links.new(blur.outputs["Image"],ramp.inputs[0])
-        tree.links.new(ramp.outputs["Image"],overlay.inputs[2])
-        if has_act:
-            tree.links.new(tree.nodes.active.outputs[0],overlay.inputs[1])
-
-        if has_act:
-            overlay.location = tree.nodes.active.location
-            overlay.location += Vector((350.0, 0.0))
-        else:
-            overlay.location += Vector((space.cursor_location[0], space.cursor_location[1]))
-
-        ellipse.location = overlay.location
-        ellipse.location += Vector((-715.0, -400))
-        ellipse.inputs[0].hide = True
-        ellipse.inputs[1].hide = True
-
-        blur.location = ellipse.location
-        blur.location += Vector((300.0, 0.0))
-        blur.inputs['Size'].hide = True
-
-        ramp.location = blur.location
-        ramp.location += Vector((175.0, 0))
-        ramp.outputs['Alpha'].hide = True
-
-        for node in {ellipse, blur, ramp, overlay}:
-            node.select = True
-            node.show_preview = False
-
-        bpy.ops.node.join()
-
-        frame = ellipse.parent
-        frame.label = 'Vignette'
-        frame.use_custom_color = True
-        frame.color = (0.1, 0.1, 0.1)
-        
-        overlay.parent = None
-        overlay.label = 'Vignette Overlay'
-
-    def execute(self, context):
-        self._setupNodes(context)
-
-        return {'FINISHED'}
-
-class AMTH_NODE_OT_AddTemplateVectorBlur(Operator):
-    bl_idname = "node.template_add_vectorblur"
-    bl_label = "Add Vector Blur"
-    bl_description = "Add a vector blur filter"
-    bl_options = {'REGISTER', 'UNDO'}
-
-    @classmethod
-    def poll(cls, context):
-        space = context.space_data
-        tree = context.scene.node_tree
-        return space.type == 'NODE_EDITOR' \
-                and space.node_tree is not None \
-                and space.tree_type == 'CompositorNodeTree' \
-                and tree \
-                and tree.nodes.active \
-                and tree.nodes.active.type == 'R_LAYERS'
-
-    def _setupNodes(self, context):
-        scene = context.scene
-        space = context.space_data
-        tree = scene.node_tree
-
-        bpy.ops.node.select_all(action='DESELECT')
-
-        act_node = tree.nodes.active
-        rlayer = act_node.scene.render.layers[act_node.layer]
-
-        if not rlayer.use_pass_vector:
-            rlayer.use_pass_vector = True
-
-        vblur = tree.nodes.new(type='CompositorNodeVecBlur')
-        vblur.use_curved = True
-        vblur.factor = 0.5
-
-        tree.links.new(act_node.outputs["Image"],vblur.inputs["Image"])
-        tree.links.new(act_node.outputs["Z"],vblur.inputs["Z"])
-        tree.links.new(act_node.outputs["Speed"],vblur.inputs["Speed"])
-
-        if tree.nodes.active:
-            vblur.location = tree.nodes.active.location
-            vblur.location += Vector((250.0, 0.0))
-        else:
-            vblur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
-
-        vblur.select = True
-
-    def execute(self, context):
-        self._setupNodes(context)
-
-        return {'FINISHED'}
-
-# Node Templates Menu
-class AMTH_NODE_MT_amaranth_templates(Menu):
-    bl_idname = 'AMTH_NODE_MT_amaranth_templates'
-    bl_space_type = 'NODE_EDITOR'
-    bl_label = "Templates"
-    bl_description = "List of Amaranth Templates"
-
-    def draw(self, context):
-        layout = self.layout
-        layout.operator(
-            AMTH_NODE_OT_AddTemplateVectorBlur.bl_idname,
-            text="Vector Blur",
-            icon='FORCE_HARMONIC')
-        layout.operator(
-            AMTH_NODE_OT_AddTemplateVignette.bl_idname,
-            text="Vignette",
-            icon='COLOR')
-
-def node_templates_pulldown(self, context):
-    if context.space_data.tree_type == 'CompositorNodeTree':
-        layout = self.layout
-        row = layout.row(align=True)
-        row.scale_x = 1.3
-        row.menu("AMTH_NODE_MT_amaranth_templates",
-            icon="NODETREE")
-# // FEATURE: Node Templates
-
-def node_stats(self,context):
-    if context.scene.node_tree:
-        tree_type = context.space_data.tree_type
-        nodes = context.scene.node_tree.nodes
-        nodes_total = len(nodes.keys())
-        nodes_selected = 0
-        for n in nodes:
-            if n.select:
-                nodes_selected = nodes_selected + 1
-
-        if tree_type == 'CompositorNodeTree':
-            layout = self.layout
-            row = layout.row(align=True)
-            row.label(text="Nodes: %s/%s" % (nodes_selected, str(nodes_total)))
-
-# FEATURE: Simplify Compo Nodes
-class AMTH_NODE_PT_simplify(Panel):
-    '''Simplify Compositor Panel'''
-    bl_space_type = 'NODE_EDITOR'
-    bl_region_type = 'UI'
-    bl_label = 'Simplify'
-    bl_options = {'DEFAULT_CLOSED'}
-
-    @classmethod
-    def poll(cls, context):
-        space = context.space_data
-        return space.type == 'NODE_EDITOR' \
-                and space.node_tree is not None \
-                and space.tree_type == 'CompositorNodeTree'
-
-    def draw(self, context):
-        layout = self.layout
-        node_tree = context.scene.node_tree
-
-        if node_tree is not None:
-            layout.prop(node_tree, 'types')
-            layout.operator(AMTH_NODE_OT_toggle_mute.bl_idname,
-                text="Turn On" if node_tree.toggle_mute else "Turn Off",
-                icon='RESTRICT_VIEW_OFF' if node_tree.toggle_mute else 'RESTRICT_VIEW_ON')
-        
-            if node_tree.types == 'VECBLUR':
-                layout.label(text="This will also toggle the Vector pass {}".format(
-                                    "on" if node_tree.toggle_mute else "off"), icon="INFO")
-
-class AMTH_NODE_OT_toggle_mute(Operator):
-    """"""
-    bl_idname = "node.toggle_mute"
-    bl_label = "Toggle Mute"
-
-    def execute(self, context):
-        scene = context.scene
-        node_tree = scene.node_tree
-        node_type = node_tree.types
-        rlayers = scene.render
-        
-        if not 'amaranth_pass_vector' in scene.keys():
-            scene['amaranth_pass_vector'] = []
-        
-        #can't extend() the list, so make a dummy one
-        pass_vector = scene['amaranth_pass_vector']
-
-        if not pass_vector:
-            pass_vector = []
-
-        if node_tree.toggle_mute:
-            for node in node_tree.nodes:
-                if node_type == 'ALL':
-                    node.mute = node.status
-                if node.type == node_type:
-                    node.mute = node.status
-                if node_type == 'VECBLUR':
-                    for layer in rlayers.layers:
-                        if layer.name in pass_vector:
-                            layer.use_pass_vector = True
-                            pass_vector.remove(layer.name)
-
-                node_tree.toggle_mute = False
-
-        else:
-            for node in node_tree.nodes:
-                if node_type == 'ALL':
-                    node.mute = True
-                if node.type == node_type:
-                    node.status = node.mute
-                    node.mute = True
-                if node_type == 'VECBLUR':
-                    for layer in rlayers.layers:
-                        if layer.use_pass_vector:
-                            pass_vector.append(layer.name)
-                            layer.use_pass_vector = False
-                            pass
-
-                node_tree.toggle_mute = True
-
-        # Write back to the custom prop
-        pass_vector = sorted(set(pass_vector))
-        scene['amaranth_pass_vector'] = pass_vector
-
-        return {'FINISHED'}
-        
-
-# FEATURE: OB/MA ID panel in Node Editor
-class AMTH_NODE_PT_indices(Panel):
-    '''Object / Material Indices Panel'''
-    bl_space_type = 'NODE_EDITOR'
-    bl_region_type = 'UI'
-    bl_label = 'Object / Material Indices'
-    bl_options = {'DEFAULT_CLOSED'}
-
-    @classmethod
-    def poll(cls, context):
-        node = context.active_node
-        return node and node.type == 'ID_MASK'
-
-    def draw(self, context):
-        layout = self.layout
-
-        objects = bpy.data.objects
-        materials = bpy.data.materials
-        node = context.active_node
-
-        show_ob_id = False
-        show_ma_id = False
-        matching_ids = False
-
-        if context.active_object:
-            ob_act = context.active_object
-        else:
-            ob_act = False
-
-        for ob in objects:
-            if ob and ob.pass_index > 0:
-                show_ob_id = True
-        for ma in materials:
-            if ma and ma.pass_index > 0:
-                show_ma_id = True
-        row = layout.row(align=True)  
-        row.prop(node, 'index', text="Mask Index")
-        row.prop(node, 'use_matching_indices', text="Only Matching IDs")
-        
-        layout.separator()
-
-        if not show_ob_id and not show_ma_id:
-            layout.label(text="No objects or materials indices so far.", icon="INFO")
-
-        if show_ob_id:
-            split = layout.split()
-            col = split.column()
-            col.label(text="Object Name")
-            split.label(text="ID Number")
-            row = layout.row()
-            for ob in objects:
-                icon = "OUTLINER_DATA_" + ob.type
-                if ob.library:
-                    icon = "LIBRARY_DATA_DIRECT"
-                elif ob.is_library_indirect:
-                    icon = "LIBRARY_DATA_INDIRECT"
-
-                if ob and node.use_matching_indices \
-                      and ob.pass_index == node.index \
-                      and ob.pass_index != 0:
-                    matching_ids = True
-                    row.label(
-                      text="[{}]".format(ob.name)
-                          if ob_act and ob.name == ob_act.name else ob.name,
-                      icon=icon)
-                    row.label(text="%s" % ob.pass_index)
-                    row = layout.row()
-
-                elif ob and not node.use_matching_indices \
-                        and ob.pass_index > 0:
-
-                    matching_ids = True
-                    row.label(
-                      text="[{}]".format(ob.name)
-                          if ob_act and ob.name == ob_act.name else ob.name,
-                      icon=icon)
-                    row.label(text="%s" % ob.pass_index)
-                    row = layout.row()
-
-            if node.use_matching_indices and not matching_ids:
-                row.label(text="No objects with ID %s" % node.index, icon="INFO")
-
-            layout.separator()
-
-        if show_ma_id:
-            split = layout.split()
-            col = split.column()
-            col.label(text="Material Name")
-            split.label(text="ID Number")
-            row = layout.row()
-
-            for ma in materials:
-                icon = "BLANK1"
-                if ma.use_nodes:
-                    icon = "NODETREE"
-                elif ma.library:
-                    icon = "LIBRARY_DATA_DIRECT"
-                    if ma.is_library_indirect:
-                        icon = "LIBRARY_DATA_INDIRECT"
-
-                if ma and node.use_matching_indices \
-                      and ma.pass_index == node.index \
-                      and ma.pass_index != 0:
-                    matching_ids = True
-                    row.label(text="%s" % ma.name, icon=icon)
-                    row.label(text="%s" % ma.pass_index)
-                    row = layout.row()
-
-                elif ma and not node.use_matching_indices \
-                        and ma.pass_index > 0:
-
-                    matching_ids = True
-                    row.label(text="%s" % ma.name, icon=icon)
-                    row.label(text="%s" % ma.pass_index)
-                    row = layout.row()
-
-            if node.use_matching_indices and not matching_ids:
-                row.label(text="No materials with ID %s" % node.index, icon="INFO")
-
-
-# // FEATURE: OB/MA ID panel in Node Editor
-
-# FEATURE: Unsimplify on render
-@persistent
-def unsimplify_render_pre(scene):
-    render = scene.render
-    scene.simplify_status = render.use_simplify
-
-    if scene.use_unsimplify_render:
-        render.use_simplify = False
-
-@persistent
-def unsimplify_render_post(scene):
-    render = scene.render
-    render.use_simplify = scene.simplify_status
-
-def unsimplify_ui(self,context):
-    scene = bpy.context.scene
-    self.layout.prop(scene, 'use_unsimplify_render')
-# //FEATURE: Unsimplify on render
-
-# FEATURE: Extra Info Stats
-def stats_scene(self, context):
-
-    preferences = context.user_preferences.addons[__name__].preferences
-
-    if preferences.use_scene_stats:
-        scenes_count = str(len(bpy.data.scenes))
-        cameras_count = str(len(bpy.data.cameras))
-        cameras_selected = 0
-        meshlights = 0
-        meshlights_visible = 0
-
-        for ob in context.scene.objects:
-            if cycles_is_emission(context, ob):
-                meshlights += 1
-                if ob in context.visible_objects:
-                    meshlights_visible += 1
-
-            if ob in context.selected_objects:
-                if ob.type == 'CAMERA':
-                    cameras_selected += 1
-    
-        meshlights_string = '| Meshlights:{}/{}'.format(meshlights_visible, meshlights)
-    
-        row = self.layout.row(align=True)
-        row.label(text="Scenes:{} | Cameras:{}/{} {}".format(
-                   scenes_count, cameras_selected, cameras_count,
-                   meshlights_string if context.scene.render.engine == 'CYCLES' else ''))
-
-# //FEATURE: Extra Info Stats
-
-# FEATURE: Camera Bounds as Render Border
-class AMTH_VIEW3D_OT_render_border_camera(Operator):
-    """Set camera bounds as render border"""
-    bl_idname = "view3d.render_border_camera"
-    bl_label = "Camera as Render Border"
-
-    @classmethod
-    def poll(cls, context):
-        return context.space_data.region_3d.view_perspective == 'CAMERA'
-
-    def execute(self, context):
-        render = context.scene.render
-        render.use_border = True
-        render.border_min_x = 0
-        render.border_min_y = 0
-        render.border_max_x = 1
-        render.border_max_y = 1
-
-        return {'FINISHED'}
-
-def button_render_border_camera(self, context):
-
-    view3d = context.space_data.region_3d
-    
-    if view3d.view_perspective == 'CAMERA':
-        layout = self.layout
-        layout.separator()
-        layout.operator(AMTH_VIEW3D_OT_render_border_camera.bl_idname,
-                        text="Camera as Render Border", icon="FULLSCREEN_ENTER")
-
-# //FEATURE: Camera Bounds as Render Border
-
-# FEATURE: Passepartout options on W menu
-def button_camera_passepartout(self, context):
-
-    view3d = context.space_data.region_3d
-    cam = context.scene.camera.data
-    
-    if view3d.view_perspective == 'CAMERA':
-        layout = self.layout
-        if cam.show_passepartout:
-            layout.prop(cam, "passepartout_alpha", text="Passepartout")
-        else:
-            layout.prop(cam, "show_passepartout")
-
-# FEATURE: Show Only Render with Alt+Shift+Z
-class AMTH_VIEW3D_OT_show_only_render(Operator):
-    bl_idname = "view3d.show_only_render"
-    bl_label = "Show Only Render"
-
-    def execute(self, context):
-        space = bpy.context.space_data
-        
-        if space.show_only_render:
-            space.show_only_render = False
-        else:
-            space.show_only_render = True
-        return {'FINISHED'}
-
-
-# FEATURE: Display Active Image Node on Image Editor
-# Made by Sergey Sharybin, tweaks from Bassam Kurdali
-image_nodes = {"CompositorNodeImage",
-               "CompositorNodeViewer",
-               "CompositorNodeComposite",
-               "ShaderNodeTexImage",
-               "ShaderNodeTexEnvironment"}
-
-class AMTH_NODE_OT_show_active_node_image(Operator):
-    """Show active image node image in the image editor"""
-    bl_idname = "node.show_active_node_image"
-    bl_label = "Show Active Node Node"
-    bl_options = {'UNDO'}
-
-    def execute(self, context):
-        preferences = context.user_preferences.addons[__name__].preferences
-        if preferences.use_image_node_display:
-            if context.active_node:
-                active_node = context.active_node
-
-                if active_node.bl_idname in image_nodes:
-                    for area in context.screen.areas:
-                        if area.type == "IMAGE_EDITOR":
-                            for space in area.spaces:
-                                if space.type == "IMAGE_EDITOR":
-                                    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 AMTH_OBJECT_OT_select_meshlights(Operator):
-    """Select light emitting meshes"""
-    bl_idname = "object.select_meshlights"
-    bl_label = "Select Meshlights"
-    bl_options = {'UNDO'}
-
-    @classmethod
-    def poll(cls, context):
-        return context.scene.render.engine == 'CYCLES'
-
-    def execute(self, context):
-        # Deselect everything first
-        bpy.ops.object.select_all(action='DESELECT')
-
-        for ob in context.scene.objects:
-            if cycles_is_emission(context, ob):
-                ob.select = True
-                context.scene.objects.active = ob
-
-        if not context.selected_objects and not context.scene.objects.active:
-            self.report({'INFO'}, "No meshlights to select")
-
-        return {'FINISHED'}
-
-def button_select_meshlights(self, context):
-    if cycles_exists and context.scene.render.engine == 'CYCLES':
-        self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
-# // FEATURE: Select Meshlights
-
-# FEATURE: Mesh Symmetry Tools by Sergey Sharybin
-class AMTH_MESH_OT_find_asymmetric(Operator):
-    """
-    Find asymmetric vertices
-    """
-
-    bl_idname = "mesh.find_asymmetric"
-    bl_label = "Find Asymmetric"
-    bl_options = {'UNDO', 'REGISTER'}
-
-    @classmethod
-    def poll(cls, context):
-        object = context.object
-        if object:
-            return object.mode == 'EDIT' and object.type == 'MESH'
-        return False
-
-    def execute(self, context):
-        threshold = 1e-6
-
-        object = context.object
-        bm = bmesh.from_edit_mesh(object.data)
-
-        # Deselect all the vertices
-        for v in bm.verts:
-            v.select = False
-
-        for v1 in bm.verts:
-            if abs(v1.co[0]) < threshold:
-                continue
-
-            mirror_found = False
-            for v2 in bm.verts:
-                if v1 == v2:
-                    continue
-                if v1.co[0] * v2.co[0] > 0.0:
-                    continue
-
-                mirror_coord = Vector(v2.co)
-                mirror_coord[0] *= -1
-                if (mirror_coord - v1.co).length_squared < threshold:
-                    mirror_found = True
-                    break
-            if not mirror_found:
-                v1.select = True
-
-        bm.select_flush_mode()
-
-        bmesh.update_edit_mesh(object.data)
-
-        return {'FINISHED'}
-
-class AMTH_MESH_OT_make_symmetric(Operator):
-    """
-    Make symmetric
-    """
-
-    bl_idname = "mesh.make_symmetric"
-    bl_label = "Make Symmetric"
-    bl_options = {'UNDO', 'REGISTER'}
-
-    @classmethod
-    def poll(cls, context):
-        object = context.object
-        if object:
-            return object.mode == 'EDIT' and object.type == 'MESH'
-        return False
-
-    def execute(self, context):
-        threshold = 1e-6
-
-        object = context.object
-        bm = bmesh.from_edit_mesh(object.data)
-
-        for v1 in bm.verts:
-            if v1.co[0] < threshold:
-                continue
-            if not v1.select:
-                continue
-
-            closest_vert = None
-            closest_distance = -1
-            for v2 in bm.verts:
-                if v1 == v2:
-                    continue
-                if v2.co[0] > threshold:
-                    continue
-                if not v2.select:
-                    continue
-
-                mirror_coord = Vector(v2.co)
-                mirror_coord[0] *= -1
-                distance = (mirror_coord - v1.co).length_squared
-                if closest_vert is None or distance < closest_distance:
-                    closest_distance = distance
-                    closest_vert = v2
-
-            if closest_vert:
-                closest_vert.select = False
-                closest_vert.co = Vector(v1.co)
-                closest_vert.co[0] *= -1
-            v1.select = False
-
-        for v1 in bm.verts:
-            if v1.select:
-                closest_vert = None
-                closest_distance = -1
-                for v2 in bm.verts:
-                    if v1 != v2:
-                        mirror_coord = Vector(v2.co)
-                        mirror_coord[0] *= -1
-                        distance = (mirror_coord - v1.co).length_squared
-                        if closest_vert is None or distance < closest_distance:
-                            closest_distance = distance
-                            closest_vert = v2
-                if closest_vert:
-                    v1.select = False
-                    v1.co = Vector(closest_vert.co)
-                    v1.co[0] *= -1
-
-        bm.select_flush_mode()
-        bmesh.update_edit_mesh(object.data)
-
-        return {'FINISHED'}
-# // FEATURE: Mesh Symmetry Tools by Sergey Sharybin
-
-# FEATURE: Cycles Render Sampling Extra
-def render_cycles_scene_samples(self, context):
-
-    layout = self.layout
-    scenes = bpy.data.scenes
-    scene = context.scene
-    render = scene.render
-    if cycles_exists:
-        cscene = scene.cycles
-        list_sampling = scene.amaranth_cycles_list_sampling
-
-    # Set Render Samples
-    if cycles_exists and cscene.progressive == 'BRANCHED_PATH':
-        layout.separator()
-        split = layout.split()
-        col = split.column()
-
-        col.operator(
-            AMTH_RENDER_OT_cycles_samples_percentage_set.bl_idname,
-            text="%s" % 'Set as Render Samples' if cscene.use_samples_final else 'Set New Render Samples',
-            icon="%s" % 'PINNED' if cscene.use_samples_final else 'UNPINNED')
-
-        col = split.column()
-        row = col.row(align=True)
-        row.enabled = True if scene.get('amth_cycles_samples_final') else False
-
-        row.operator(
-            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
-            text="100%").percent=100
-        row.operator(
-            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
-            text="75%").percent=75
-        row.operator(
-            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
-            text="50%").percent=50
-        row.operator(
-            AMTH_RENDER_OT_cycles_samples_percentage.bl_idname,
-            text="25%").percent=25
-
-    # List Samples
-    if (len(scene.render.layers) > 1) or \
-        (len(bpy.data.scenes) > 1):
-
-        box = layout.box()
-        row = box.row(align=True)
-        col = row.column(align=True)
-
-        row = col.row(align=True)
-        row.alignment = 'LEFT'
-        row.prop(scene, 'amaranth_cycles_list_sampling',
-                    icon="%s" % 'TRIA_DOWN' if list_sampling else 'TRIA_RIGHT',
-                    emboss=False)
-
-    if list_sampling:
-        if len(scene.render.layers) == 1 and \
-            render.layers[0].samples == 0:
-            pass
-        else:
-            col.separator()
-            col.label(text="RenderLayers:", icon='RENDERLAYERS')
-
-            for rl in scene.render.layers:
-                row = col.row(align=True)
-                row.label(rl.name, icon='BLANK1')
-                row.prop(rl, "samples", text="%s" %
-                    "Samples" if rl.samples > 0 else "Automatic (%s)" % (
-                        cscene.aa_samples if cscene.progressive == 'BRANCHED_PATH' else cscene.samples))
-
-        if (len(bpy.data.scenes) > 1):
-            col.separator()
-
-            col.label(text="Scenes:", icon='SCENE_DATA')
-
-            if cycles_exists and cscene.progressive == 'PATH':
-                for s in bpy.data.scenes:
-                    if s != scene:
-                        row = col.row(align=True)
-                        if s.render.engine == 'CYCLES':
-                            cscene = s.cycles
-
-                            row.label(s.name)
-                            row.prop(cscene, "samples", icon='BLANK1')
-                        else:
-                            row.label(text="Scene: '%s' is not using Cycles" % s.name)
-            else:
-                for s in bpy.data.scenes:
-                    if s != scene:
-                        row = col.row(align=True)
-                        if s.render.engine == 'CYCLES':
-                            cscene = s.cycles
-
-                            row.label(s.name, icon='BLANK1')
-                            row.prop(cscene, "aa_samples",
-                                text="AA Samples")
-                        else:
-                            row.label(text="Scene: '%s' is not using Cycles" % s.name)
-
-# // FEATURE: Cycles Render Sampling Extra
-
-# FEATURE: Motion Paths Extras
-class AMTH_POSE_OT_paths_clear_all(Operator):
-    """Clear motion paths from all bones"""
-    bl_idname = "pose.paths_clear_all"
-    bl_label = "Clear All Motion Paths"
-    bl_options = {'UNDO'}
-
-    @classmethod
-    def poll(cls, context):
-        return context.mode == 'POSE'
-
-    def execute(self, context):
-        #silly but works
-        for b in context.object.data.bones:
-            b.select = True
-            bpy.ops.pose.paths_clear()
-            b.select = False
-        return {'FINISHED'}
-
-class AMTH_POSE_OT_paths_frame_match(Operator):
-    """Match Start/End frame of scene to motion path range"""
-    bl_idname = "pose.paths_frame_match"
-    bl_label = "Match Frame Range"
-    bl_options = {'UNDO'}
-
-    def execute(self, context):
-        avs = context.object.pose.animation_visualization
-        scene = context.scene
-
-        if avs.motion_path.type == 'RANGE':
-            if scene.use_preview_range:
-                avs.motion_path.frame_start = scene.frame_preview_start
-                avs.motion_path.frame_end = scene.frame_preview_end
-            else:
-                avs.motion_path.frame_start = scene.frame_start
-                avs.motion_path.frame_end = scene.frame_end
-
-        else:
-            if scene.use_preview_range:
-                avs.motion_path.frame_before = scene.frame_preview_start
-                avs.motion_path.frame_after = scene.frame_preview_end
-            else:
-                avs.motion_path.frame_before = scene.frame_start
-                avs.motion_path.frame_after = scene.frame_end
-
-        return {'FINISHED'}
-
-def pose_motion_paths_ui(self, context):
-
-    layout = self.layout
-    scene = context.scene
-    avs = context.object.pose.animation_visualization
-    if context.active_pose_bone:
-        mpath = context.active_pose_bone.motion_path
-    layout.separator()    
-    layout.label(text="Motion Paths Extras:")
-
-    split = layout.split()
-
-    col = split.column(align=True)
-
-    if context.selected_pose_bones:
-        if mpath:
-            sub = col.row(align=True)
-            sub.operator("pose.paths_update", text="Update Path", icon='BONE_DATA')
-            sub.operator("pose.paths_clear", text="", icon='X')
-        else:
-            col.operator("pose.paths_calculate", text="Calculate Path", icon='BONE_DATA')
-    else:
-        col.label(text="Select Bones First", icon="ERROR")
-
-    col = split.column(align=True)
-    col.operator(AMTH_POSE_OT_paths_frame_match.bl_idname,
-        text="{}".format( "Set Preview Frame Range"
-                if scene.use_preview_range else "Set Frame Range"),
-        icon="{}".format("PREVIEW_RANGE"
-                if scene.use_preview_range else "TIME"))
-
-    col = layout.column()
-    row = col.row(align=True)
-
-    if avs.motion_path.type == 'RANGE':
-        row.prop(avs.motion_path, "frame_start", text="Start")
-        row.prop(avs.motion_path, "frame_end", text="End")
-    else:
-        row.prop(avs.motion_path, "frame_before", text="Before")
-        row.prop(avs.motion_path, "frame_after", text="After")
-
-    layout.separator()
-    layout.operator(AMTH_POSE_OT_paths_clear_all.bl_idname, icon="X")
-# // FEATURE: Motion Paths Extras
-
-# FEATURE: Final Render Resolution Display
-def render_final_resolution_ui(self, context):
-
-    rd = context.scene.render
-    layout = self.layout
-
-    final_res_x = (rd.resolution_x * rd.resolution_percentage) / 100
-    final_res_y = (rd.resolution_y * rd.resolution_percentage) / 100
-
-    if rd.use_border:
-       final_res_x_border = round((final_res_x * (rd.border_max_x - rd.border_min_x)))
-       final_res_y_border = round((final_res_y * (rd.border_max_y - rd.border_min_y)))
-       layout.label(text="Final Resolution: {} x {} [Border: {} x {}]".format(
-             str(final_res_x)[:-2], str(final_res_y)[:-2],
-             str(final_res_x_border), str(final_res_y_border)))
-    else:
-        layout.label(text="Final Resolution: {} x {}".format(
-             str(final_res_x)[:-2], str(final_res_y)[:-2]))
-# // FEATURE: Final Render Resolution Display
-
-# FEATURE: Shader Nodes Extra Info
-def node_shader_extra(self, context):
-
-    if context.space_data.tree_type == 'ShaderNodeTree':
-        ob = context.active_object
-        snode = context.space_data
-        layout = self.layout
-
-        if ob and snode.shader_type != 'WORLD':
-            if ob.type == 'LAMP':
-                layout.label(text="%s" % ob.name,
-                             icon="LAMP_%s" % ob.data.type)        
-            else:
-                layout.label(text="%s" % ob.name,
-                             icon="OUTLINER_DATA_%s" % ob.type)
-             
-
-# // FEATURE: Shader Nodes Extra Info
-
-# FEATURE: Scene Debug
-class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
-    """List Cycles materials containing a specific shader"""
-    bl_idname = "scene.cycles_list_nodes"
-    bl_label = "List Materials"
-    materials = []
-
-    @classmethod
-    def poll(cls, context):
-        return cycles_exists and context.scene.render.engine == 'CYCLES'
-
-    def execute(self, context):
-        node_type = context.scene.amaranth_cycles_node_types
-        roughness = False
-        self.__class__.materials = []
-        shaders_roughness = ['BSDF_GLOSSY','BSDF_DIFFUSE','BSDF_GLASS']
-
-        print("\n=== Cycles Shader Type: %s === \n" % node_type)
-
-        for ma in bpy.data.materials:
-            if ma.node_tree:
-                nodes = ma.node_tree.nodes
-                
-                print_unconnected = ('Note: \nOutput from "%s" node' % node_type,
-                                        'in material "%s"' % ma.name, 'not connected\n')
-
-                for no in nodes:
-                    if no.type == node_type:
-                        for ou in no.outputs:
-                            if ou.links:
-                                connected = True
-                                if no.type in shaders_roughness:
-                                    roughness = 'R: %.4f' % no.inputs['Roughness'].default_value
-                                else:
-                                    roughness = False
-                            else:
-                                connected = False
-                                print(print_unconnected)
-
-                            if ma.name not in self.__class__.materials:
-                                self.__class__.materials.append('%s%s [%s] %s%s%s' % (
-                                    '[L] ' if ma.library else '',
-                                    ma.name, ma.users,
-                                    '[F]' if ma.use_fake_user else '',
-                                    ' - [%s]' % roughness if roughness else '',
-                                    ' * Output not connected' if not connected else ''))
-
-                    elif no.type == 'GROUP':
-                        if no.node_tree:
-                            for nog in no.node_tree.nodes:
-                                if nog.type == node_type:
-                                    for ou in nog.outputs:
-                                        if ou.links:
-                                            connected = True
-                                            if nog.type in shaders_roughness:
-                                                roughness = 'R: %.4f' % nog.inputs['Roughness'].default_value
-                                            else:
-                                                roughness = False
-                                        else:
-                                            connected = False
-                                            print(print_unconnected)
-
-                                        if ma.name not in self.__class__.materials:
-                                            self.__class__.materials.append('%s%s%s [%s] %s%s%s' % (
-                                                '[L] ' if ma.library else '',
-                                                'Node Group:  %s%s  ->  ' % (
-                                                    '[L] ' if no.node_tree.library else '',
-                                                    no.node_tree.name),
-                                                ma.name, ma.users,
-                                                '[F]' if ma.use_fake_user else '',
-                                                ' - [%s]' % roughness if roughness else '',
-                                                ' * Output not connected' if not connected else ''))
-
-                    self.__class__.materials = sorted(list(set(self.__class__.materials)))
-
-        if len(self.__class__.materials) == 0:
-            self.report({"INFO"}, "No materials with nodes type %s found" % node_type)
-        else:
-            print("* A total of %d %s using %s was found \n" % (
-                    len(self.__class__.materials),
-                    "material" if len(self.__class__.materials) == 1 else "materials",
-                    node_type))
-
-            count = 0
-
-            for mat in self.__class__.materials:
-                print('%02d. %s' % (count+1, self.__class__.materials[count]))
-                count += 1
-            print("\n")
-
-        self.__class__.materials = sorted(list(set(self.__class__.materials)))
-
-        return {'FINISHED'}
-
-class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(Operator):
-    """Clear the list below"""
-    bl_idname = "scene.cycles_list_nodes_clear"
-    bl_label = "Clear Materials List"
-
-    @classmethod
-    def poll(cls, context):
-        return cycles_exists
-
-    def execute(self, context):
-        AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
-        print("* Cleared Cycles Materials List")
-        return {'FINISHED'}
-
-class AMTH_SCENE_OT_amaranth_object_select(Operator):
-    '''Select object'''
-    bl_idname = "scene.amaranth_object_select"
-    bl_label = "Select Object"
-    object = bpy.props.StringProperty()
-    def execute(self, context):
-        if self.object:
-            object = bpy.data.objects[self.object]
-
-            bpy.ops.object.select_all(action='DESELECT')
-            object.select = True
-            context.scene.objects.active = object
-
-        return{'FINISHED'}
-
-class AMTH_SCENE_OT_list_missing_node_links(Operator):
-    '''Print a list of missing node links'''
-    bl_idname = "scene.list_missing_node_links"
-    bl_label = "List Missing Node Links"
-
-    count_groups = 0
-    count_images = 0
-    count_image_node_unlinked = 0
-
-    def execute(self, context):
-        missing_groups = []
-        missing_images = []
-        image_nodes_unlinked = []
-        libraries = []
-        self.__class__.count_groups = 0
-        self.__class__.count_images = 0
-        self.__class__.count_image_node_unlinked = 0
-
-        for ma in bpy.data.materials:
-            if ma.node_tree:
-                for no in ma.node_tree.nodes:
-                    if no.type == 'GROUP':
-                        if not no.node_tree:
-                            self.__class__.count_groups += 1
-
-                            users_ngroup = []
-
-                            for ob in bpy.data.objects:
-                                if ob.material_slots and ma.name in ob.material_slots:
-                                    users_ngroup.append("%s%s%s" % (
-                                        "[L] " if ob.library else "",
-                                        "[F] " if ob.use_fake_user else "",
-                                        ob.name))
-
-                            missing_groups.append("MA: %s%s%s [%s]%s%s%s\n" % (
-                                "[L] " if ma.library else "",
-                                "[F] " if ma.use_fake_user else "",
-                                ma.name, ma.users,
-                                " *** No users *** " if ma.users == 0 else "",
-                                "\nLI: %s" % 
-                                ma.library.filepath if ma.library else "",
-                                "\nOB: %s" % ',  '.join(users_ngroup) if users_ngroup else ""))
-
-                            if ma.library:
-                                libraries.append(ma.library.filepath)
-                    if no.type == 'TEX_IMAGE':
-
-                        outputs_empty = not no.outputs['Color'].is_linked and not no.outputs['Alpha'].is_linked
-
-                        if no.image:
-                            import os.path
-                            image_path_exists = os.path.exists(
-                                                    bpy.path.abspath(
-                                                        no.image.filepath, library=no.image.library))
-
-                        if outputs_empty or not \
-                           no.image or not \
-                           image_path_exists:
-
-                            users_images = []
-
-                            for ob in bpy.data.objects:
-                                if ob.material_slots and ma.name in ob.material_slots:
-                                    users_images.append("%s%s%s" % (
-                                        "[L] " if ob.library else "",
-                                        "[F] " if ob.use_fake_user else "",
-                                        ob.name))
-
-                            if outputs_empty:
-                                self.__class__.count_image_node_unlinked += 1
-
-                                image_nodes_unlinked.append("%s%s%s%s%s [%s]%s%s%s%s%s\n" % (
-                                    "NO: %s" % no.name,
-                                    "\nMA: ",
-                                    "[L] " if ma.library else "",
-                                    "[F] " if ma.use_fake_user else "",
-                                    ma.name, ma.users,
-                                    " *** No users *** " if ma.users == 0 else "",
-                                    "\nLI: %s" % 
-                                    ma.library.filepath if ma.library else "",
-                                    "\nIM: %s" % no.image.name if no.image else "",
-                                    "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
-                                    "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
-                            
-
-                            if not no.image or not image_path_exists:
-                                self.__class__.count_images += 1
-
-                                missing_images.append("MA: %s%s%s [%s]%s%s%s%s%s\n" % (
-                                    "[L] " if ma.library else "",
-                                    "[F] " if ma.use_fake_user else "",
-                                    ma.name, ma.users,
-                                    " *** No users *** " if ma.users == 0 else "",
-                                    "\nLI: %s" % 
-                                    ma.library.filepath if ma.library else "",
-                                    "\nIM: %s" % no.image.name if no.image else "",
-                                    "\nLI: %s" % no.image.filepath if no.image and no.image.filepath else "",
-                                    "\nOB: %s" % ',  '.join(users_images) if users_images else ""))
-
-                                if ma.library:
-                                    libraries.append(ma.library.filepath)
-
-        # Remove duplicates and sort
-        missing_groups = sorted(list(set(missing_groups)))
-        missing_images = sorted(list(set(missing_images)))
-        image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
-        libraries = sorted(list(set(libraries)))
-
-        print("\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
-            ("No" if self.__class__.count_images == 0 else str(self.__class__.count_images),
-            "node" if self.__class__.count_images == 1 else "nodes",
-            "no" if self.__class__.count_groups == 0 else str(self.__class__.count_groups),
-            "group" if self.__class__.count_groups == 1 else "groups",
-            "no" if self.__class__.count_image_node_unlinked == 0 else str(self.__class__.count_image_node_unlinked),
-            "node" if self.__class__.count_groups == 1 else "nodes"))
-
-        # List Missing Node Groups
-        if missing_groups:
-            print("\n* Missing Node Group Links\n")
-            for mig in missing_groups:
-                print(mig)
-
-        # List Missing Image Nodes
-        if missing_images:
-            print("\n* Missing Image Nodes Link\n")
-
-            for mii in missing_images:
-                print(mii)
-
-        # List Image Nodes with its outputs unlinked
-        if image_nodes_unlinked:
-            print("\n* Image Nodes Unlinked\n")
-
-            for nou in image_nodes_unlinked:
-                print(nou)
-
-        if missing_groups or \
-           missing_images or \
-           image_nodes_unlinked:
-            if libraries:
-                print("\nThat's bad, run check on %s:" % (
-                    "this library" if len(libraries) == 1 else "these libraries"))
-                for li in libraries:
-                    print(li)
-        else:
-            self.report({"INFO"}, "Yay! No missing node links")            
-
-        print("\n")
-
-        if missing_groups and missing_images:
-            self.report({"WARNING"}, "%d missing image %s and %d missing node %s found" %
-                (self.__class__.count_images, "node" if self.__class__.count_images == 1 else "nodes",
-                self.__class__.count_groups, "group" if self.__class__.count_groups == 1 else "groups"))
-
-        return{'FINISHED'}
-
-class AMTH_SCENE_OT_list_missing_material_slots(Operator):
-    '''List objects with empty material slots'''
-    bl_idname = "scene.list_missing_material_slots"
-    bl_label = "List Empty Material Slots"
-
-    objects = []
-    libraries = []
-
-    def execute(self, context):
-        self.__class__.objects = []
-        self.__class__.libraries = []
-
-        for ob in bpy.data.objects:
-            for ma in ob.material_slots:
-                if not ma.material:
-                    self.__class__.objects.append('%s%s' % (
-                        '[L] ' if ob.library else '',
-                        ob.name))
-                    if ob.library:
-                        self.__class__.libraries.append(ob.library.filepath)
-
-        self.__class__.objects = sorted(list(set(self.__class__.objects)))
-        self.__class__.libraries = sorted(list(set(self.__class__.libraries)))
-
-        if len(self.__class__.objects) == 0:
-            self.report({"INFO"}, "No objects with empty material slots found")
-        else:
-            print("\n* A total of %d %s with empty material slots was found \n" % (
-                    len(self.__class__.objects),
-                    "object" if len(self.__class__.objects) == 1 else "objects"))
-
-            count = 0
-            count_lib = 0
-
-            for obs in self.__class__.objects:
-                print('%02d. %s' % (
-                    count+1, self.__class__.objects[count]))
-                count += 1
-
-            if self.__class__.libraries:
-                print("\n\n* Check %s:\n" % 
-                    ("this library" if len(self.__class__.libraries) == 1
-                        else "these libraries"))
-
-                for libs in self.__class__.libraries:
-                    print('%02d. %s' % (
-                        count_lib+1, self.__class__.libraries[count_lib]))
-                    count_lib += 1
-            print("\n")
-
-        return{'FINISHED'}
-
-class AMTH_SCENE_OT_list_missing_material_slots_clear(Operator):
-    """Clear the list below"""
-    bl_idname = "scene.list_missing_material_slots_clear"
-    bl_label = "Clear Empty Material Slots List"
-    
-    def execute(self, context):
-        AMTH_SCENE_OT_list_missing_material_slots.objects[:] = []
-        print("* Cleared Empty Material Slots List")
-        return {'FINISHED'}
-
-class AMTH_SCENE_OT_blender_instance_open(Operator):
-    '''Open in a new Blender instance'''
-    bl_idname = "scene.blender_instance_open"
-    bl_label = "Open Blender Instance"
-    filepath = bpy.props.StringProperty()
-
-    def execute(self, context):
-        if self.filepath:
-            import os.path
-            filepath = os.path.normpath(bpy.path.abspath(self.filepath))
-
-            import subprocess
-            try:
-                subprocess.Popen([bpy.app.binary_path, filepath])
-            except:
-                print("Error on the new Blender instance")
-                import traceback
-                traceback.print_exc()
-
-        return{'FINISHED'}
-
-class AMTH_SCENE_PT_scene_debug(Panel):
-    '''Scene Debug'''
-    bl_label = 'Scene Debug'
-    bl_space_type = "PROPERTIES"
-    bl_region_type = "WINDOW"
-    bl_context = "scene"
-
-    def draw_header(self, context):
-        layout = self.layout
-        layout.label(text="", icon="RADIO")
-
-    def draw(self, context):
-        layout = self.layout
-        scene = context.scene
-        objects =  bpy.data.objects
-        ob_act = context.active_object
-        images = bpy.data.images
-        lamps = bpy.data.lamps
-        images_missing = []
-        list_missing_images = scene.amaranth_debug_scene_list_missing_images
-        materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
-        materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
-        missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
-        missing_material_slots_count = len(AMTH_SCENE_OT_list_missing_material_slots.objects)
-        missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
-        engine = scene.render.engine
-
-        # List Missing Images
-        box = layout.box()
-        row = box.row(align=True)
-        split = row.split()
-        col = split.column()
-
-        if images:
-            import os.path
-
-            for im in images:
-                if im.type not in ['UV_TEST', 'RENDER_RESULT', 'COMPOSITING']: 
-                    if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
-                        images_missing.append(["%s%s [%s]%s" % (
-                            '[L] ' if im.library else '',
-                            im.name, im.users,
-                            ' [F]' if im.use_fake_user else ''),
-                            im.filepath if im.filepath else 'No Filepath',
-                            im.library.filepath if im.library else ''])
-
-            if images_missing:
-                row = col.row(align=True)
-                row.alignment = 'LEFT'
-                row.prop(scene, 'amaranth_debug_scene_list_missing_images',
-                            icon="%s" % 'TRIA_DOWN' if list_missing_images else 'TRIA_RIGHT',
-                            emboss=False)
-
-                split = split.split()
-                col = split.column()
-
-                col.label(text="%s missing %s" % (
-                             str(len(images_missing)),
-                             'image' if len(images_missing) == 1 else 'images'),
-                             icon="ERROR")
-
-                if list_missing_images:
-                    col = box.column(align=True)
-                    for mis in images_missing:
-                        col.label(text=mis[0],
-                         icon="IMAGE_DATA")
-                        col.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
-                        if mis[2]:
-                            row = col.row(align=True)
-                            row.alignment = "LEFT"
-                            row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
-                                         text=mis[2],
-                                         icon="LINK_BLEND",
-                                         emboss=False).filepath=mis[2]
-                        col.separator()
-            else:
-                row = col.row(align=True)
-                row.alignment = 'LEFT'
-                row.label(text="Great! No missing images", icon="RIGHTARROW_THIN")
-
-                split = split.split()
-                col = split.column()
-
-                col.label(text="%s %s loading correctly" % (
-                             str(len(images)),
-                             'image' if len(images) == 1 else 'images'),
-                             icon="IMAGE_DATA")
-        else:
-            row = col.row(align=True)
-            row.alignment = 'LEFT'
-            row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
-
-        # List Cycles Materials by Shader
-        if cycles_exists and engine == 'CYCLES':
-            box = layout.box()
-            split = box.split()
-            col = split.column(align=True)
-            col.prop(scene, 'amaranth_cycles_node_types',
-                icon="MATERIAL")
-
-            row = split.row(align=True)
-            row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
-                            icon="SORTSIZE",
-                            text="List Materials Using Shader")
-            if materials_count != 0: 
-                row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
-                                icon="X", text="")
-            col.separator()
-
-            try:
-                materials
-            except NameError:
-                pass
-            else:
-                if materials_count != 0: 
-                    col = box.column(align=True)
-                    count = 0
-                    col.label(text="%s %s found" % (materials_count,
-                        'material' if materials_count == 1 else 'materials'), icon="INFO")
-                    for mat in materials:
-                        count += 1
-                        col.label(text='%s' % (materials[count-1]), icon="MATERIAL")
-
-        # List Missing Node Trees
-        box = layout.box()
-        row = box.row(align=True)
-        split = row.split()
-        col = split.column(align=True)
-
-        split = col.split()
-        split.label(text="Node Links")
-        split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
-                        icon="NODETREE")
-
-        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
-            AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
-            AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
-            col.label(text="Warning! Check Console", icon="ERROR")
-
-        if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
-            col.label(text="%s" % ("%s node %s missing link" % (
-                     str(AMTH_SCENE_OT_list_missing_node_links.count_groups),
-                     "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
-                     icon="NODETREE")
-        if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
-            col.label(text="%s" % ("%s image %s missing link" % (
-                     str(AMTH_SCENE_OT_list_missing_node_links.count_images),
-                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
-                     icon="IMAGE_DATA")
-
-        if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
-            col.label(text="%s" % ("%s image %s with no output conected" % (
-                     str(AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
-                     "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
-                     icon="NODE")
-
-        # List Empty Materials Slots
-        box = layout.box()
-        split = box.split()
-        col = split.column(align=True)
-        col.label(text="Material Slots")
-
-        row = split.row(align=True)
-        row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
-                        icon="MATERIAL",
-                        text="List Empty Materials Slots")
-        if missing_material_slots_count != 0: 
-            row.operator(AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
-                            icon="X", text="")
-        col.separator()
-
-        try:
-            missing_material_slots_obs
-        except NameError:
-            pass
-        else:
-            if missing_material_slots_count != 0: 
-                col = box.column(align=True)
-                count = 0
-                count_lib = 0
-                col.label(text="%s %s with empty material slots found" % (
-                    missing_material_slots_count,
-                    'object' if missing_material_slots_count == 1 else 'objects'),
-                    icon="INFO")
-
-                for obs in missing_material_slots_obs:
-                    count += 1
-
-                    row = col.row()
-                    row.alignment = 'LEFT'
-                    row.label(text='%s' % missing_material_slots_obs[count-1],
-                                icon="OBJECT_DATA")
-
-                if missing_material_slots_lib:
-                    col.separator()
-                    col.label("Check %s:" % (
-                        "this library" if
-                            len(missing_material_slots_lib) == 1
-                                else "these libraries"))
-                    
-                    for libs in missing_material_slots_lib:
-                        count_lib += 1
-                        row = col.row(align=True)
-                        row.alignment = "LEFT"
-                        row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
-                                     text=missing_material_slots_lib[count_lib-1],
-                                     icon="LINK_BLEND",
-                                     emboss=False).filepath=missing_material_slots_lib[count_lib-1]
-
-# // FEATURE: Scene Debug
-# FEATURE: Dupli  Group Path
-def ui_dupli_group_library_path(self, context):
-
-    ob = context.object
-
-    row = self.layout.row()
-    row.alignment = 'LEFT'
-
-    if ob and ob.dupli_group and ob.dupli_group.library:
-        lib = ob.dupli_group.library.filepath
-
-        row.operator(AMTH_SCENE_OT_blender_instance_open.bl_idname,
-            text="Library: %s" % lib,
-            emboss=False,
-            icon="LINK_BLEND").filepath=lib
-
-# // FEATURE: Dupli  Group Path
-# FEATURE: Color Management Presets
-class AMTH_SCENE_MT_color_management_presets(Menu):
-    """List of Color Management presets"""
-    bl_label = "Color Management Presets"
-    preset_subdir = "color"
-    preset_operator = "script.execute_preset"
-    draw = Menu.draw_preset
-
-
-class AMTH_AddPresetColorManagement(AddPresetBase, Operator):
-    """Add or remove a Color Management preset"""
-    bl_idname = "scene.color_management_preset_add"
-    bl_label = "Add Color Management Preset"
-    preset_menu = "AMTH_SCENE_MT_color_management_presets"
-
-    preset_defines = [
-        "scene = bpy.context.scene"
-    ]
-
-    preset_values = [
-        "scene.view_settings.view_transform",
-        "scene.display_settings.display_device",
-        "scene.view_settings.exposure",
-        "scene.view_settings.gamma",
-        "scene.view_settings.look",
-        "scene.view_settings.use_curve_mapping",
-        "scene.sequencer_colorspace_settings.name",
-    ]
-
-    preset_subdir = "color"
-
-def ui_color_management_presets(self, context):
-    
-    layout = self.layout
-
-    row = layout.row(align=True)
-    row.menu("AMTH_SCENE_MT_color_management_presets", text=bpy.types.AMTH_SCENE_MT_color_management_presets.bl_label)
-    row.operator("scene.color_management_preset_add", text="", icon="ZOOMIN")
-    row.operator("scene.color_management_preset_add", text="", icon="ZOOMOUT").remove_active = True
-    layout.separator()
-# // FEATURE: Color Management Presets
-
-# FEATURE: Sequencer Extra Info
-def act_strip(context):
-    try:
-        return context.scene.sequence_editor.active_strip
-    except AttributeError:
-        return None
-
-def ui_sequencer_extra_info(self, context):
-
-    layout = self.layout
-    strip = act_strip(context)
-
-    if strip:
-        seq_type = strip.type
-
-        if seq_type and seq_type == 'IMAGE':
-            elem = strip.strip_elem_from_frame(context.scene.frame_current)
-            if elem:
-                layout.label(text="%s %s" % (
-                    elem.filename,
-                    "[%s]" % (context.scene.frame_current - strip.frame_start)))
-# // FEATURE: Sequencer Extra Info
-
-# FEATURE: Normal Node Values, by Lukas Tönne
-def normal_vector_get(self):
-    return self.outputs['Normal'].default_value
-
-def normal_vector_set(self, values):
-    # default_value allows un-normalized values,
-    # do this here to prevent awkward results
-    values = Vector(values).normalized()
-    self.outputs['Normal'].default_value = values
-
-prop_normal_vector = bpy.props.FloatVectorProperty(
-                        name="Normal", size=3, subtype='XYZ',
-                        min=-1.0, max=1.0, soft_min=-1.0, soft_max=1.0,
-                        get=normal_vector_get, set=normal_vector_set
-                        )
-
-def act_node(context):
-    try:
-        return context.active_node
-    except AttributeError:
-        return None
-
-def ui_node_normal_values(self, context):
-
-    node = act_node(context)
-
-    if act_node:
-        if node and node.type == 'NORMAL':
-            self.layout.prop(node, "normal_vector", text="")
-
-# // FEATURE: Normal Node Values, by Lukas Tönne
-
-# FEATURE: Object ID for objects inside DupliGroups
-class AMTH_OBJECT_OT_id_dupligroup(Operator):
-    '''Set the Object ID for objects in the dupli group'''
-    bl_idname = "object.amaranth_object_id_duplis"
-    bl_label = "Apply Object ID to Duplis"
-
-    clear = False
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object.dupli_group
-
-    def execute(self, context):
-        self.__class__.clear = False
-        ob = context.active_object
-        amth_text_exists = amaranth_text_startup(context)
-        script_exists = False
-        script_intro = "# OB ID: %s" % ob.name
-        obdata = "bpy.data.objects['%s']" % ob.name
-        script = "%s" % (
-            "\nif %(obdata)s and %(obdata)s.dupli_group and %(obdata)s.pass_index != 0: %(obname)s \n"
-            "    for dob in %(obdata)s.dupli_group.objects: %(obname)s \n"
-            "        dob.pass_index = %(obdata)s.pass_index %(obname)s \n" %
-                {'obdata' : obdata, 'obname' : script_intro})
-
-        for txt in bpy.data.texts:
-            if txt.name == amth_text.name:
-                for li in txt.lines:
-                    if script_intro == li.body:
-                        script_exists = True
-                        continue
-
-        if not script_exists:
-            amth_text.write("\n")
-            amth_text.write(script_intro)
-            amth_text.write(script)
-
-        if ob and ob.dupli_group:
-            if ob.pass_index != 0:
-                for dob in ob.dupli_group.objects:
-                    dob.pass_index = ob.pass_index
-
-        self.report({'INFO'},
-            "%s ID: %s to all objects in this Dupli Group" % (
-                "Applied" if not script_exists else "Updated",
-                ob.pass_index))
-
-        return{'FINISHED'}
-
-class AMTH_OBJECT_OT_id_dupligroup_clear(Operator):
-    '''Clear the Object ID from objects in dupli group'''
-    bl_idname = "object.amaranth_object_id_duplis_clear"
-    bl_label = "Clear Object ID from Duplis"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object.dupli_group
-
-    def execute(self, context):
-        context.active_object.pass_index = 0
-        AMTH_OBJECT_OT_id_dupligroup.clear = True
-        amth_text_exists = amaranth_text_startup(context)
-        match_first = "# OB ID: %s" % context.active_object.name
-
-        if amth_text_exists:
-            for txt in bpy.data.texts:
-                if txt.name == amth_text.name:
-                    for li in txt.lines:
-                        if match_first in li.body:
-                            li.body = ''
-                            continue
-
-        self.report({'INFO'}, "Object IDs back to normal")
-        return{'FINISHED'}
-
-def ui_object_id_duplis(self, context):
-
-    if context.active_object.dupli_group:
-        split = self.layout.split()
-        row = split.row(align=True)
-        row.enabled = context.active_object.pass_index != 0
-        row.operator(
-            AMTH_OBJECT_OT_id_dupligroup.bl_idname)
-        row.operator(
-            AMTH_OBJECT_OT_id_dupligroup_clear.bl_idname,
-            icon="X", text="")
-        split.separator()
-
-        if AMTH_OBJECT_OT_id_dupligroup.clear:
-            self.layout.label(text="Next time you save/reload this file, "
-                                        "object IDs will be back to normal",
-                              icon="INFO")
-
-# // FEATURE: Object ID for objects inside DupliGroups
-# UI: Warning about Z not connected when using EXR
-def ui_render_output_z(self, context):
-
-    scene = bpy.context.scene
-    image = scene.render.image_settings
-    if scene.render.use_compositing and \
-        image.file_format == 'OPEN_EXR' and \
-        image.use_zbuffer:
-        if scene.node_tree and scene.node_tree.nodes:
-            for no in scene.node_tree.nodes:
-                if no.type == 'COMPOSITE':
-                    if not no.inputs['Z'].is_linked:
-                        self.layout.label(
-                            text="The Z output in node \"%s\" is not connected" % 
-                                no.name, icon="ERROR")
-
-# // UI: Warning about Z not connected
-
-# FEATURE: Delete Materials not assigned to any verts
-class AMTH_OBJECT_OT_material_remove_unassigned(Operator):
-    '''Remove materials not assigned to any vertex'''
-    bl_idname = "object.amaranth_object_material_remove_unassigned"
-    bl_label = "Remove Unassigned Materials"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object.material_slots
-
-    def execute(self, context):
-
-        scene = context.scene
-        act_ob = context.active_object
-        count = len(act_ob.material_slots)
-        materials_removed = []
-        act_ob.active_material_index = 0
-        is_visible = True
-
-        if act_ob not in context.visible_objects:
-            is_visible = False
-            n = -1
-            for lay in act_ob.layers:
-                n += 1
-                if lay:
-                    break
-
-            scene.layers[n] = True
-
-        for slot in act_ob.material_slots:
-            count -= 1
-
-            bpy.ops.object.mode_set(mode='EDIT')
-            bpy.ops.mesh.select_all(action='DESELECT')
-            act_ob.active_material_index = count
-            bpy.ops.object.material_slot_select()
-            
-            if act_ob.data.total_vert_sel == 0 or \
-                (len(act_ob.material_slots) == 1 and not \
-                    act_ob.material_slots[0].material):
-                materials_removed.append(
-                    "%s" % act_ob.active_material.name if act_ob.active_material else "Empty")
-                bpy.ops.object.mode_set(mode='OBJECT')
-                bpy.ops.object.material_slot_remove()
-            else:
-                pass
-
-        bpy.ops.object.mode_set(mode='EDIT')
-        bpy.ops.mesh.select_all(action='DESELECT')
-        bpy.ops.object.mode_set(mode='OBJECT')
-
-        if materials_removed:
-            print("\n* Removed %s Unassigned Materials \n" % len(materials_removed))
-
-            count_mr = 0
-
-            for mr in materials_removed:
-                count_mr += 1
-                print("%0.2d. %s" % (count_mr, materials_removed[count_mr - 1]))
-
-            print("\n")
-            self.report({'INFO'}, "Removed %s Unassigned Materials" %
-                len(materials_removed))
-
-        if not is_visible:
-            scene.layers[n] = False
-
-        return{'FINISHED'}
-
-def ui_material_remove_unassigned(self, context):
-
-    self.layout.operator(
-        AMTH_OBJECT_OT_material_remove_unassigned.bl_idname,
-        icon="X")
-
-# // FEATURE: Delete Materials not assigned to any verts
-# FEATURE: Cycles Samples Percentage
-class AMTH_RENDER_OT_cycles_samples_percentage_set(Operator):
-    '''Save the current number of samples per shader as final (gets saved in .blend)'''
-    bl_idname = "scene.amaranth_cycles_samples_percentage_set"
-    bl_label = "Set as Render Samples"
-
-    def execute(self, context):
-        cycles = context.scene.cycles
-        cycles.use_samples_final = True
-
-        context.scene['amth_cycles_samples_final'] = [
-            cycles.diffuse_samples,
-            cycles.glossy_samples,
-            cycles.transmission_samples,
-            cycles.ao_samples,
-            cycles.mesh_light_samples,
-            cycles.subsurface_samples,
-            cycles.volume_samples]
-
-        self.report({'INFO'}, "Render Samples Saved")
-
-        return{'FINISHED'}
-
-
-class AMTH_RENDER_OT_cycles_samples_percentage(Operator):
-    '''Set a percentage of the final render samples'''
-    bl_idname = "scene.amaranth_cycles_samples_percentage"
-    bl_label = "Set Render Samples Percentage"
-
-    percent = IntProperty(
-                name="Percentage",
-                description="Percentage to divide render samples by",
-                subtype='PERCENTAGE',
-                default=0)
-
-    def execute(self, context):
-        percent = self.percent
-        cycles = context.scene.cycles
-        cycles_samples_final = context.scene['amth_cycles_samples_final']
-
-        cycles.use_samples_final = False
-
-        if percent == 100:
-            cycles.use_samples_final = True
-
-        cycles.diffuse_samples = int((cycles_samples_final[0] / 100) * percent)
-        cycles.glossy_samples = int((cycles_samples_final[1] / 100) * percent)
-        cycles.transmission_samples = int((cycles_samples_final[2] / 100) * percent)
-        cycles.ao_samples = int((cycles_samples_final[3] / 100) * percent)
-        cycles.mesh_light_samples = int((cycles_samples_final[4] / 100) * percent)
-        cycles.subsurface_samples = int((cycles_samples_final[5] / 100) * percent)
-        cycles.volume_samples = int((cycles_samples_final[6] / 100) * percent)
-
-        return{'FINISHED'}
-
-# //FEATURE: Cycles Samples Percentage
-# FEATURE: Jump forward/backward every N frames
-class AMTH_SCREEN_OT_frame_jump(Operator):
-    '''Jump a number of frames forward/backwards'''
-    bl_idname = "screen.amaranth_frame_jump"
-    bl_label = "Jump Frames"
-
-    forward = BoolProperty(default=True)
-
-    def execute(self, context):
-        scene = context.scene
-        preferences = context.user_preferences.addons[__name__].preferences
-
-        if 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")
-
-          &n