Skip to content
Snippets Groups Projects
scene_amaranth_toolset.py 116 KiB
Newer Older
# ##### 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",
Pablo Vazquez's avatar
Pablo Vazquez committed
    "author": "Pablo Vazquez, Bassam Kurdali, Sergey Sharybin, Lukas Tönne",
Pablo Vazquez's avatar
Pablo Vazquez committed
    "version": (0, 9, 4),
    "blender": (2, 70),
    "location": "Everywhere!",
    "description": "A collection of tools and settings to improve productivity",
    "warning": "",
    "wiki_url": "http://pablovazquez.org/amaranth",
    "tracker_url": "",
    "category": "Scene"}


import bpy
Pablo Vazquez's avatar
Pablo Vazquez committed
import bmesh
from bpy.types import Operator, AddonPreferences, Panel, Menu
Pablo Vazquez's avatar
Pablo Vazquez committed
from bpy.props import (BoolProperty, EnumProperty,
Pablo Vazquez's avatar
Pablo Vazquez committed
                       FloatProperty, FloatVectorProperty,
                       IntProperty, StringProperty)
from mathutils import Vector
from bpy.app.handlers import persistent
from bl_operators.presets import AddPresetBase
Pablo Vazquez's avatar
Pablo Vazquez committed
# Addon wide, we need to know if cycles is available
global cycles_exists
cycles_exists = 'cycles' in dir(bpy.types.Scene)

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

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

Pablo Vazquez's avatar
Pablo Vazquez committed
    frames_jump = IntProperty(
                name="Frames",
                description="Number of frames to jump forward/backward",
                default=10,
                min=1)

Pablo Vazquez's avatar
Pablo Vazquez committed
    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")
Pablo Vazquez's avatar
Pablo Vazquez committed
        sub.prop(self, "use_layers_for_render")
        sub.label(text="Nodes Editor", icon="NODETREE")
        sub.prop(self, "use_image_node_display")

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

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

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

# Properties
def init_properties():

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

    scene.use_unsimplify_render = BoolProperty(
        default=False,
        name="Unsimplify Render",
        description="Disable Simplify during render")
    scene.simplify_status = BoolProperty(default=False)
    node.use_matching_indices = BoolProperty(
        default=True,
        description="If disabled, display all available indices")

Pablo Vazquez's avatar
Pablo Vazquez committed
    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)
        ]

Pablo Vazquez's avatar
Pablo Vazquez committed
    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
Pablo Vazquez's avatar
Pablo Vazquez committed
    if cycles_exists:
        cycles_shader_node_types = [
            ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
            ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
            ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
            ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
            ("BSDF_GLASS", "Glass BSDF", "", 4),
            ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
            ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
            ("BSDF_VELVET", "Velvet BSDF", "", 7),
            ("BSDF_TOON", "Toon BSDF", "", 8),
            ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
            ("EMISSION", "Emission", "", 10),
            ("BSDF_HAIR", "Hair BSDF", "", 11),
            ("BACKGROUND", "Background", "", 12),
            ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
            ("HOLDOUT", "Holdout", "", 14),
            ("VOLUME_ABSORPTION", "Volume Absorption", "", 15),
            ("VOLUME_SCATTER", "Volume Scatter", "", 16)
            ]

Pablo Vazquez's avatar
Pablo Vazquez committed
        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")

Pablo Vazquez's avatar
Pablo Vazquez committed
    bpy.types.ShaderNodeNormal.normal_vector = prop_normal_vector
    bpy.types.CompositorNodeNormal.normal_vector = prop_normal_vector
Pablo Vazquez's avatar
Pablo Vazquez committed

    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",
Pablo Vazquez's avatar
Pablo Vazquez committed
        "status",
        "types",
        "toggle_mute",
        "amaranth_cycles_node_types",
        "amaranth_lighterscorner_list_meshlights",
Pablo Vazquez's avatar
Pablo Vazquez committed
        "amaranth_debug_scene_list_missing_images",
        "amarath_cycles_list_sampling",
        "normal_vector",
Pablo Vazquez's avatar
Pablo Vazquez committed
        "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]

Pablo Vazquez's avatar
Pablo Vazquez committed
# 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:
Pablo Vazquez's avatar
Pablo Vazquez committed
        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
Pablo Vazquez's avatar
Pablo Vazquez committed

        return amth_text_exists
    except AttributeError:
        return None

Pablo Vazquez's avatar
Pablo Vazquez committed
# FUNCTION: Check if material has Emission (for select and stats)
Pablo Vazquez's avatar
Pablo Vazquez committed
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:
Pablo Vazquez's avatar
Pablo Vazquez committed
                    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:
Pablo Vazquez's avatar
Pablo Vazquez committed
                                        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

Pablo Vazquez's avatar
Pablo Vazquez committed
# 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

Pablo Vazquez's avatar
Pablo Vazquez committed
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(
Pablo Vazquez's avatar
Pablo Vazquez committed
            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")

Pablo Vazquez's avatar
Pablo Vazquez committed
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(
Pablo Vazquez's avatar
Pablo Vazquez committed
            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)

Pablo Vazquez's avatar
Pablo Vazquez committed
        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):
Pablo Vazquez's avatar
Pablo Vazquez committed
            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
Pablo Vazquez's avatar
Pablo Vazquez committed
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(
Pablo Vazquez's avatar
Pablo Vazquez committed
            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
Pablo Vazquez's avatar
Pablo Vazquez committed
class AMTH_FILE_PT_libraries(Panel):
    bl_space_type = 'FILE_BROWSER'
    bl_region_type = 'CHANNELS'
    bl_label = "Libraries"

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

        libs = bpy.data.libraries
        libslist = []

        # Build the list of folders from libraries
Pablo Vazquez's avatar
Pablo Vazquez committed
        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)

        # Dra
Loading
Loading full blame...