Skip to content
Snippets Groups Projects
scene_amaranth_toolset.py 40.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • # ##### 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",
    
    Pablo Vazquez's avatar
    Pablo Vazquez committed
        "version": (0, 7, 3),
    
        "location": "Scene Properties > Amaranth Toolset Panel",
        "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
    
    from bpy.props import BoolProperty
    from mathutils import Vector
    from bpy.app.handlers import persistent
    
    # 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,
                )
    
    
        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.separator()
    
    
            sub.label(text="Nodes Editor", icon="NODETREE")
    
            sub.prop(self, "use_image_node_display")
    
            col = split.column()
            sub = col.column(align=True)
            sub.label(text="")
            sub.label(
                text="Set the current frame from the Specials menu in the 3D View [W]")
            sub.label(
                text="Refresh the current Scene. Hotkey: F5 or in Specials menu [W]")
    
            sub.separator()
            sub.label(text="") # General
            sub.label(
                text="Quickly save and reload the current file (no warning!). "
                     "File menu or Ctrl+Shift+W")
            sub.label(
                text="SMPTE Timecode and frames left/ahead on Timeline's header")
            sub.label(
                text="Display extra statistics for Scenes, Cameras, and Meshlights (Cycles)")
    
            sub.separator()
            sub.label(text="") # Nodes
            sub.label(
                text="When selecting an Image node, display it on the Image editor "
                     "(if any)")
    
    # Properties
    def init_properties():
    
        scene = bpy.types.Scene
        node = bpy.types.Node
        nodes_compo = bpy.types.CompositorNodeTree
    
        scene.use_unsimplify_render = bpy.props.BoolProperty(
            default=False,
            name="Unsimplify Render",
            description="Disable Simplify during render")
        scene.simplify_status = bpy.props.BoolProperty(default=False)
    
        node.use_matching_indices = bpy.props.BoolProperty(
            default=True,
            description="If disabled, display all available indices")
    
        test_items = [
            ("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 = bpy.props.EnumProperty(
            items=test_items, name = "Types")
    
        nodes_compo.toggle_mute = bpy.props.BoolProperty(default=False)
        node.status = bpy.props.BoolProperty(default=False)
    
    
    def clear_properties():
        props = (
            "use_unsimplify_render",
            "simplify_status",
            "use_matching_indices",
            "use_simplify_nodes_vector",
            "status"
        )
    
        wm = bpy.context.window_manager
        for p in props:
            if p in wm:
                del wm[p]
    
    # FEATURE: Refresh Scene!
    class 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
    
    
                # 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(
                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 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(
                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)
    
            # 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
    class 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(
                FILE_OT_directory_current_blend.bl_idname,
                text="Current Blend's Folder",
                icon='APPEND_BLEND')
    # // FEATURE: Directory Current Blend
    
    
    # FEATURE: Libraries panel on file browser
    class FILE_PT_libraries(Panel):
        bl_space_type = 'FILE_BROWSER'
        bl_region_type = 'CHANNELS'
        bl_label = "Libraries"
    
        def draw(self, context):
            layout = self.layout
    
            libs = bpy.data.libraries
            libslist = []
    
            # Build the list of folders from libraries
            import os
    
            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:
                for filepath in libslist:
                    if filepath != '//':
                        split = box.split(percentage=0.85)
                        col = split.column()
                        sub = col.column(align=True)
                        sub.label(text=filepath)
    
                        col = split.column()
                        sub = col.column(align=True)
                        props = sub.operator(
                            FILE_OT_directory_go_to.bl_idname,
                            text="", icon="BOOKMARKS")
                        props.filepath = filepath
            else:
                box.label(text='No libraries loaded')
    
    class FILE_OT_directory_go_to(Operator):
        """Go to this library's directory"""
        bl_idname = "file.directory_go_to"
        bl_label = "Go To"
    
        filepath = bpy.props.StringProperty(subtype="FILE_PATH")
    
        def execute(self, context):
    
            bpy.ops.file.select_bookmark(dir=self.filepath)
            return {'FINISHED'}
    
    # FEATURE: Node Templates
    class NODE_OT_AddTemplateVignette(Operator):
        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
    
            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 tree.nodes.active:
                blur.location = tree.nodes.active.location
                blur.location += Vector((330.0, -250.0))
            else:
                blur.location += Vector((space.cursor_location[0], space.cursor_location[1]))
    
            ellipse.location = blur.location
            ellipse.location += Vector((-300.0, 0))
    
            ramp.location = blur.location
            ramp.location += Vector((175.0, 0))
    
            overlay.location = ramp.location
            overlay.location += Vector((240.0, 275.0))
    
            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.783538, 0.0241576, 0.0802198)
    
            overlay.parent = None
            overlay.label = 'Vignette Overlay'
    
        def execute(self, context):
            self._setupNodes(context)
    
            return {'FINISHED'}
    
    # Node Templates Menu
    class NODE_MT_amaranth_templates(bpy.types.Menu):
        bl_idname = '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(
                NODE_OT_AddTemplateVignette.bl_idname,
                text="Vignette",
                icon='COLOR')
    
    def node_templates_pulldown(self, context):
    
    Pablo Vazquez's avatar
    Pablo Vazquez committed
    
        if context.space_data.tree_type == 'CompositorNodeTree':
            layout = self.layout
            row = layout.row(align=True)
            row.scale_x = 1.3
            row.menu("NODE_MT_amaranth_templates",
                icon="RADIO")
    
    # // 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 NODE_PT_simplify(bpy.types.Panel):
        '''Simplify Compositor Panel'''
        bl_space_type = 'NODE_EDITOR'
        bl_region_type = 'UI'
        bl_label = 'Simplify'
    
    Pablo Vazquez's avatar
    Pablo Vazquez committed
        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(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 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 NODE_PT_indices(bpy.types.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.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 ob.material_slots:
                    for ma in ob.material_slots:
                        if ma.material:
                            if ma.material.node_tree:
                                for no in ma.material.node_tree.nodes:
                                    if no.type == 'EMISSION':
                                        meshlights = meshlights + 1
                                        if ob in context.visible_objects:
                                            meshlights_visible = meshlights_visible + 1
                                        break
                if ob in context.selected_objects:
                    if ob.type == 'CAMERA':
                        cameras_selected = 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 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(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 VIEW3D_OT_show_only_render(Operator):
        bl_idname = "view3d.show_only_render"
        bl_label = "Show Only Render"
    
        def execute(self, context):
            space = bpy.context.space_data
    
            if space.show_only_render:
                space.show_only_render = False
            else:
                space.show_only_render = True
            return {'FINISHED'}
    
    
    # FEATURE: Display Active Image Node on Image Editor
    # Made by Sergey Sharybin, tweaks from Bassam Kurdali
    image_nodes = {"CompositorNodeImage",
                   "ShaderNodeTexImage",
                   "ShaderNodeTexEnvironment"}
    
    class NODE_OT_show_active_node_image(Operator):
        """Show active image node image in the image editor"""
        bl_idname = "node.show_active_node_image"
        bl_label = "Show Active Node Node"
        bl_options = {'UNDO'}
    
        def execute(self, context):
            preferences = context.user_preferences.addons[__name__].preferences
            if preferences.use_image_node_display:
                if context.active_node:
                    active_node = context.active_node
                    if active_node.bl_idname in image_nodes and active_node.image:
                        for area in context.screen.areas:
                            if area.type == "IMAGE_EDITOR":
                                for space in area.spaces:
                                    if space.type == "IMAGE_EDITOR":
                                        space.image = active_node.image
                                break
    
            return {'FINISHED'}
    # // FEATURE: Display Active Image Node on Image Editor
    
    # FEATURE: Select Meshlights
    class 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 ob.material_slots:
                    for ma in ob.material_slots:
                        if ma.material:
                            if ma.material.node_tree:
                                for no in ma.material.node_tree.nodes:
                                    if no.type == 'EMISSION':
                                        ob.select = True
                                        context.scene.objects.active = ob
    
            if 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 context.scene.render.engine == 'CYCLES':
            self.layout.operator('object.select_meshlights', icon="LAMP_SUN")
    # // FEATURE: Select Meshlights
    
    # FEATURE: Cycles Viewport Extra Settings
    def material_cycles_settings_extra(self, context):
    
        layout = self.layout
        col = layout.column()
        row = col.row(align=True)
    
        obj = context.object
        mat = context.material
        if obj.type == 'MESH':
            row.prop(obj, "show_transparent", text="Viewport Alpha")
            row.active = obj.show_transparent
            row.prop(mat, "alpha", text="Alpha")
    
    # // FEATURE: Cycles Viewport Extra Settings
    
    # FEATURE: Particles Material indicator
    def particles_material_info(self, context):
    
        layout = self.layout
    
        ob = context.object
        psys = context.particle_system
    
    
    Pablo Vazquez's avatar
    Pablo Vazquez committed
        mats = len(ob.material_slots)
    
    
        if ob.material_slots:
            if psys.settings.material <= len(ob.material_slots) \
            and ob.material_slots[psys.settings.material-1].name == "":
                layout.label(text="No material on this slot", icon="MATSPHERE")
            else:
                layout.label(
                    text="%s" % ob.material_slots[psys.settings.material-1].name \
                        if psys.settings.material <= mats \
                        else "No material with this index{}".format( \
                            ". Using %s" % ob.material_slots[mats-1].name \
                            if ob.material_slots[mats-1].name != "" else ""),
                    icon="MATERIAL_DATA")
    
    # // FEATURE: Particles Material indicator
    
    Pablo Vazquez's avatar
    Pablo Vazquez committed
    # FEATURE: Mesh Symmetry Tools by Sergey Sharybin
    class 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 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