#### Motivation
The View pie menu is a convenient way to access operators such as `Frame Selected` and `Frame All` which are usually mapped to `PERIOD` or `HOME` keys on the right side of most keyboard, making it hard hard to reach with the left hand.
The motivation for this patch comes from working with a 75% keyboard (no numpad). Most laptops face a similar problem.
#### Implementation
The View pie menu has been added to the following editors and sub-modes where applicable:
* Node Editor
* Video Sequencer
* Dopesheet
* Graph
* NLA
* Image
* Clip
* Outliner
More options could definitely be added to this menu for convenience, as long as it maintains the common options in the same place (Frame Selected on the left, Frame All on the right).
For positioning I went with the following layout:
{F11791186, size=full}
I've added `Zoom 1:1`to the Image Editor and the VSE Preview since there is no way to reset the zoom on keyboards without numpad (unless Emulate Numpad is turned on).
The Outliner uses `Show Active` and `Show Hierarchy` which are the closest ones to the equivalent in other editors. Should `Show Active` be renamed to `Frame Selected`?
The shortcut assigned is the same as the 3D Viewport (`ACCENT_GRAVE`).
#### Screenshots
Node Editor
{F11778387, size=full}
Dopesheet
{F11778400, size=full}
Graph
{F11778403, size=full}
Image Editor (Paint and View)
{F11791113, size=full}
Image Editor (Mask)
{F11791114, size=full}
UV Editor
{F11791119, size=full}
Clip Editor (Tracking)
{F11791137, size=full}
Clip Editor (Mask)
{F11791140, size=full}
Clip Editor (Graph)
{F11791151, size=full}
View operators are not yet implemented in Clip Editor Dopesheet mode (left a note about this in the menu poll).
Reviewed By: #user_interface, campbellbarton
Differential Revision: https://developer.blender.org/D13169
807 lines
27 KiB
Python
807 lines
27 KiB
Python
# ##### 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 #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
import bpy
|
|
from bpy.types import (
|
|
Header,
|
|
Menu,
|
|
Panel,
|
|
)
|
|
|
|
from bl_ui.properties_grease_pencil_common import (
|
|
GreasePencilLayerMasksPanel,
|
|
GreasePencilLayerTransformPanel,
|
|
GreasePencilLayerAdjustmentsPanel,
|
|
GreasePencilLayerRelationsPanel,
|
|
GreasePencilLayerDisplayPanel,
|
|
)
|
|
|
|
#######################################
|
|
# DopeSheet Filtering - Header Buttons
|
|
|
|
# used for DopeSheet, NLA, and Graph Editors
|
|
|
|
|
|
def dopesheet_filter(layout, context):
|
|
dopesheet = context.space_data.dopesheet
|
|
is_nla = context.area.type == 'NLA_EDITOR'
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(dopesheet, "show_only_selected", text="")
|
|
row.prop(dopesheet, "show_hidden", text="")
|
|
|
|
if is_nla:
|
|
row.prop(dopesheet, "show_missing_nla", text="")
|
|
else: # graph and dopesheet editors - F-Curves and drivers only
|
|
row.prop(dopesheet, "show_only_errors", text="")
|
|
|
|
#######################################
|
|
# Dopesheet Filtering Popovers
|
|
|
|
# Generic Layout - Used as base for filtering popovers used in all animation editors
|
|
# Used for DopeSheet, NLA, and Graph Editors
|
|
|
|
|
|
class DopesheetFilterPopoverBase:
|
|
bl_region_type = 'HEADER'
|
|
bl_label = "Filters"
|
|
|
|
# Generic = Affects all datatypes
|
|
# XXX: Perhaps we want these to stay in the header instead, for easy/fast access
|
|
@classmethod
|
|
def draw_generic_filters(cls, context, layout):
|
|
dopesheet = context.space_data.dopesheet
|
|
is_nla = context.area.type == 'NLA_EDITOR'
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(dopesheet, "show_only_selected", icon='NONE')
|
|
col.prop(dopesheet, "show_hidden", icon='NONE')
|
|
|
|
if is_nla:
|
|
col.prop(dopesheet, "show_missing_nla", icon='NONE')
|
|
else: # graph and dopesheet editors - F-Curves and drivers only
|
|
col.prop(dopesheet, "show_only_errors", icon='NONE')
|
|
|
|
# Name/Membership Filters
|
|
# XXX: Perhaps these should just stay in the headers (exclusively)?
|
|
@classmethod
|
|
def draw_search_filters(cls, context, layout, generic_filters_only=False):
|
|
dopesheet = context.space_data.dopesheet
|
|
is_nla = context.area.type == 'NLA_EDITOR'
|
|
|
|
col = layout.column(align=True)
|
|
if not is_nla:
|
|
row = col.row(align=True)
|
|
row.prop(dopesheet, "filter_fcurve_name", text="")
|
|
else:
|
|
row = col.row(align=True)
|
|
row.prop(dopesheet, "filter_text", text="")
|
|
|
|
if (not generic_filters_only) and bpy.data.collections:
|
|
col = layout.column(align=True)
|
|
col.prop(dopesheet, "filter_collection", text="")
|
|
|
|
# Standard = Present in all panels
|
|
@classmethod
|
|
def draw_standard_filters(cls, context, layout):
|
|
dopesheet = context.space_data.dopesheet
|
|
|
|
# datablock filters
|
|
layout.label(text="Filter by Type:")
|
|
flow = layout.grid_flow(row_major=True, columns=2, even_rows=False, align=False)
|
|
|
|
flow.prop(dopesheet, "show_scenes", text="Scenes")
|
|
flow.prop(dopesheet, "show_nodes", text="Node Trees")
|
|
|
|
# object types
|
|
if bpy.data.armatures:
|
|
flow.prop(dopesheet, "show_armatures", text="Armatures")
|
|
if bpy.data.cameras:
|
|
flow.prop(dopesheet, "show_cameras", text="Cameras")
|
|
if bpy.data.grease_pencils:
|
|
flow.prop(dopesheet, "show_gpencil", text="Grease Pencil Objects")
|
|
if bpy.data.lights:
|
|
flow.prop(dopesheet, "show_lights", text="Lights")
|
|
if bpy.data.meshes:
|
|
flow.prop(dopesheet, "show_meshes", text="Meshes")
|
|
if bpy.data.curves:
|
|
flow.prop(dopesheet, "show_curves", text="Curves")
|
|
if bpy.data.lattices:
|
|
flow.prop(dopesheet, "show_lattices", text="Lattices")
|
|
if bpy.data.metaballs:
|
|
flow.prop(dopesheet, "show_metaballs", text="Metaballs")
|
|
if hasattr(bpy.data, "hairs") and bpy.data.hairs:
|
|
flow.prop(dopesheet, "show_hairs", text="Hairs")
|
|
if hasattr(bpy.data, "pointclouds") and bpy.data.pointclouds:
|
|
flow.prop(dopesheet, "show_pointclouds", text="Point Clouds")
|
|
if bpy.data.volumes:
|
|
flow.prop(dopesheet, "show_volumes", text="Volumes")
|
|
|
|
# data types
|
|
flow.prop(dopesheet, "show_worlds", text="Worlds")
|
|
if bpy.data.particles:
|
|
flow.prop(dopesheet, "show_particles", text="Particles")
|
|
if bpy.data.linestyles:
|
|
flow.prop(dopesheet, "show_linestyles", text="Line Styles")
|
|
if bpy.data.speakers:
|
|
flow.prop(dopesheet, "show_speakers", text="Speakers")
|
|
if bpy.data.materials:
|
|
flow.prop(dopesheet, "show_materials", text="Materials")
|
|
if bpy.data.textures:
|
|
flow.prop(dopesheet, "show_textures", text="Textures")
|
|
if bpy.data.shape_keys:
|
|
flow.prop(dopesheet, "show_shapekeys", text="Shape Keys")
|
|
if bpy.data.cache_files:
|
|
flow.prop(dopesheet, "show_cache_files", text="Cache Files")
|
|
if bpy.data.movieclips:
|
|
flow.prop(dopesheet, "show_movieclips", text="Movie Clips")
|
|
|
|
layout.separator()
|
|
|
|
# Object Data Filters
|
|
|
|
# TODO: Add per-channel/axis convenience toggles?
|
|
split = layout.split()
|
|
|
|
col = split.column()
|
|
col.prop(dopesheet, "show_transforms", text="Transforms")
|
|
|
|
col = split.column()
|
|
col.prop(dopesheet, "show_modifiers", text="Modifiers")
|
|
|
|
layout.separator()
|
|
|
|
# performance-related options (users will mostly have these enabled)
|
|
col = layout.column(align=True)
|
|
col.label(text="Options:")
|
|
col.prop(dopesheet, "use_datablock_sort", icon='NONE')
|
|
|
|
|
|
# Popover for Dopesheet Editor(s) - Dopesheet, Action, Shapekey, GPencil, Mask, etc.
|
|
class DOPESHEET_PT_filters(DopesheetFilterPopoverBase, Panel):
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
bl_region_type = 'HEADER'
|
|
bl_label = "Filters"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
dopesheet = context.space_data.dopesheet
|
|
ds_mode = context.space_data.mode
|
|
|
|
layout.prop(dopesheet, "show_summary", text="Summary")
|
|
|
|
DopesheetFilterPopoverBase.draw_generic_filters(context, layout)
|
|
|
|
if ds_mode in {'DOPESHEET', 'ACTION', 'GPENCIL'}:
|
|
layout.separator()
|
|
generic_filters_only = ds_mode != 'DOPESHEET'
|
|
DopesheetFilterPopoverBase.draw_search_filters(context, layout,
|
|
generic_filters_only=generic_filters_only)
|
|
|
|
if ds_mode == 'DOPESHEET':
|
|
layout.separator()
|
|
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
|
|
|
|
|
|
#######################################
|
|
# DopeSheet Editor - General/Standard UI
|
|
|
|
class DOPESHEET_HT_header(Header):
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
st = context.space_data
|
|
|
|
layout.template_header()
|
|
|
|
if st.mode == 'TIMELINE':
|
|
from bl_ui.space_time import (
|
|
TIME_MT_editor_menus,
|
|
TIME_HT_editor_buttons,
|
|
)
|
|
TIME_MT_editor_menus.draw_collapsible(context, layout)
|
|
TIME_HT_editor_buttons.draw_header(context, layout)
|
|
else:
|
|
layout.prop(st, "ui_mode", text="")
|
|
|
|
DOPESHEET_MT_editor_menus.draw_collapsible(context, layout)
|
|
DOPESHEET_HT_editor_buttons.draw_header(context, layout)
|
|
|
|
|
|
# Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.)
|
|
class DOPESHEET_HT_editor_buttons:
|
|
|
|
@staticmethod
|
|
def draw_header(context, layout):
|
|
st = context.space_data
|
|
tool_settings = context.tool_settings
|
|
|
|
if st.mode in {'ACTION', 'SHAPEKEY'}:
|
|
# TODO: These buttons need some tidying up -
|
|
# Probably by using a popover, and bypassing the template_id() here
|
|
row = layout.row(align=True)
|
|
row.operator("action.layer_prev", text="", icon='TRIA_DOWN')
|
|
row.operator("action.layer_next", text="", icon='TRIA_UP')
|
|
|
|
row = layout.row(align=True)
|
|
row.operator("action.push_down", text="Push Down", icon='NLA_PUSHDOWN')
|
|
row.operator("action.stash", text="Stash", icon='FREEZE')
|
|
|
|
layout.separator_spacer()
|
|
|
|
layout.template_ID(st, "action", new="action.new", unlink="action.unlink")
|
|
|
|
# Layer management
|
|
if st.mode == 'GPENCIL':
|
|
ob = context.active_object
|
|
selected = st.dopesheet.show_only_selected
|
|
enable_but = selected and ob is not None and ob.type == 'GPENCIL'
|
|
|
|
row = layout.row(align=True)
|
|
row.enabled = enable_but
|
|
row.operator("gpencil.layer_add", icon='ADD', text="")
|
|
row.operator("gpencil.layer_remove", icon='REMOVE', text="")
|
|
row.menu("GPENCIL_MT_layer_context_menu", icon='DOWNARROW_HLT', text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.enabled = enable_but
|
|
row.operator("gpencil.layer_move", icon='TRIA_UP', text="").type = 'UP'
|
|
row.operator("gpencil.layer_move", icon='TRIA_DOWN', text="").type = 'DOWN'
|
|
|
|
row = layout.row(align=True)
|
|
row.enabled = enable_but
|
|
row.operator("gpencil.layer_isolate", icon='RESTRICT_VIEW_ON', text="").affect_visibility = True
|
|
row.operator("gpencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False
|
|
|
|
layout.separator_spacer()
|
|
|
|
if st.mode == 'DOPESHEET':
|
|
dopesheet_filter(layout, context)
|
|
elif st.mode == 'ACTION':
|
|
dopesheet_filter(layout, context)
|
|
elif st.mode == 'GPENCIL':
|
|
row = layout.row(align=True)
|
|
row.prop(st.dopesheet, "show_only_selected", text="")
|
|
row.prop(st.dopesheet, "show_hidden", text="")
|
|
|
|
layout.popover(
|
|
panel="DOPESHEET_PT_filters",
|
|
text="",
|
|
icon='FILTER',
|
|
)
|
|
|
|
# Grease Pencil mode doesn't need snapping, as it's frame-aligned only
|
|
if st.mode != 'GPENCIL':
|
|
layout.prop(st, "auto_snap", text="")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(tool_settings, "use_proportional_action", text="", icon_only=True)
|
|
sub = row.row(align=True)
|
|
sub.active = tool_settings.use_proportional_action
|
|
sub.prop(tool_settings, "proportional_edit_falloff", text="", icon_only=True)
|
|
|
|
|
|
class DOPESHEET_MT_editor_menus(Menu):
|
|
bl_idname = "DOPESHEET_MT_editor_menus"
|
|
bl_label = ""
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
st = context.space_data
|
|
|
|
layout.menu("DOPESHEET_MT_view")
|
|
layout.menu("DOPESHEET_MT_select")
|
|
if st.show_markers:
|
|
layout.menu("DOPESHEET_MT_marker")
|
|
|
|
if st.mode == 'DOPESHEET' or (st.mode == 'ACTION' and st.action is not None):
|
|
layout.menu("DOPESHEET_MT_channel")
|
|
elif st.mode == 'GPENCIL':
|
|
layout.menu("DOPESHEET_MT_gpencil_channel")
|
|
|
|
if st.mode != 'GPENCIL':
|
|
layout.menu("DOPESHEET_MT_key")
|
|
else:
|
|
layout.menu("DOPESHEET_MT_gpencil_key")
|
|
|
|
|
|
class DOPESHEET_MT_view(Menu):
|
|
bl_label = "View"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
st = context.space_data
|
|
|
|
layout.prop(st, "show_region_ui")
|
|
|
|
layout.separator()
|
|
|
|
layout.prop(st.dopesheet, "use_multi_word_filter", text="Multi-Word Match Search")
|
|
|
|
layout.separator()
|
|
|
|
layout.prop(st, "use_realtime_update")
|
|
|
|
# Sliders are always shown in the Shape Key Editor regardless of this setting.
|
|
col = layout.column()
|
|
col.active = context.space_data.mode != 'SHAPEKEY'
|
|
col.prop(st, "show_sliders")
|
|
|
|
layout.prop(st, "show_interpolation")
|
|
layout.prop(st, "show_extremes")
|
|
layout.prop(st, "use_auto_merge_keyframes")
|
|
|
|
layout.separator()
|
|
layout.prop(st, "show_markers")
|
|
|
|
layout.separator()
|
|
layout.prop(st, "show_seconds")
|
|
layout.prop(st, "show_locked_time")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.previewrange_set")
|
|
layout.operator("anim.previewrange_clear")
|
|
layout.operator("action.previewrange_set")
|
|
|
|
layout.separator()
|
|
layout.operator("action.view_all")
|
|
layout.operator("action.view_selected")
|
|
layout.operator("action.view_frame")
|
|
|
|
# Add this to show key-binding (reverse action in dope-sheet).
|
|
layout.separator()
|
|
props = layout.operator("wm.context_set_enum", text="Toggle Graph Editor", icon='GRAPH')
|
|
props.data_path = "area.type"
|
|
props.value = 'GRAPH_EDITOR'
|
|
|
|
layout.separator()
|
|
layout.menu("INFO_MT_area")
|
|
|
|
|
|
class DOPESHEET_MT_view_pie(Menu):
|
|
bl_label = "View"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
pie = layout.menu_pie()
|
|
pie.operator("action.view_all")
|
|
pie.operator("action.view_selected", icon='ZOOM_SELECTED')
|
|
pie.operator("action.view_frame")
|
|
|
|
|
|
class DOPESHEET_MT_select(Menu):
|
|
bl_label = "Select"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.operator("action.select_all", text="All").action = 'SELECT'
|
|
layout.operator("action.select_all", text="None").action = 'DESELECT'
|
|
layout.operator("action.select_all", text="Invert").action = 'INVERT'
|
|
|
|
layout.separator()
|
|
layout.operator("action.select_box").axis_range = False
|
|
layout.operator("action.select_box", text="Box Select (Axis Range)").axis_range = True
|
|
|
|
layout.operator("action.select_circle")
|
|
layout.operator_menu_enum("action.select_lasso", "mode")
|
|
|
|
layout.separator()
|
|
layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS'
|
|
layout.operator("action.select_column", text="Column on Current Frame").mode = 'CFRA'
|
|
|
|
layout.operator("action.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN'
|
|
layout.operator("action.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN'
|
|
|
|
layout.separator()
|
|
props = layout.operator("action.select_leftright", text="Before Current Frame")
|
|
props.extend = False
|
|
props.mode = 'LEFT'
|
|
props = layout.operator("action.select_leftright", text="After Current Frame")
|
|
props.extend = False
|
|
props.mode = 'RIGHT'
|
|
|
|
# FIXME: grease pencil mode isn't supported for these yet, so skip for that mode only
|
|
if context.space_data.mode != 'GPENCIL':
|
|
layout.separator()
|
|
layout.operator("action.select_more")
|
|
layout.operator("action.select_less")
|
|
|
|
layout.separator()
|
|
layout.operator("action.select_linked")
|
|
|
|
|
|
class DOPESHEET_MT_marker(Menu):
|
|
bl_label = "Marker"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
from bl_ui.space_time import marker_menu_generic
|
|
marker_menu_generic(layout, context)
|
|
|
|
st = context.space_data
|
|
|
|
if st.mode in {'ACTION', 'SHAPEKEY'} and st.action:
|
|
layout.separator()
|
|
layout.prop(st, "show_pose_markers")
|
|
|
|
if st.show_pose_markers is False:
|
|
layout.operator("action.markers_make_local")
|
|
|
|
layout.prop(st, "use_marker_sync")
|
|
|
|
#######################################
|
|
# Keyframe Editing
|
|
|
|
|
|
class DOPESHEET_MT_channel(Menu):
|
|
bl_label = "Channel"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator_context = 'INVOKE_REGION_CHANNELS'
|
|
|
|
layout.operator("anim.channels_delete")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_group")
|
|
layout.operator("anim.channels_ungroup")
|
|
|
|
layout.separator()
|
|
layout.operator_menu_enum("anim.channels_setting_toggle", "type")
|
|
layout.operator_menu_enum("anim.channels_setting_enable", "type")
|
|
layout.operator_menu_enum("anim.channels_setting_disable", "type")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_editable_toggle")
|
|
layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_expand")
|
|
layout.operator("anim.channels_collapse")
|
|
|
|
layout.separator()
|
|
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_fcurves_enable")
|
|
|
|
|
|
class DOPESHEET_MT_key(Menu):
|
|
bl_label = "Key"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.menu("DOPESHEET_MT_key_transform", text="Transform")
|
|
|
|
layout.operator_menu_enum("action.snap", "type", text="Snap")
|
|
layout.operator_menu_enum("action.mirror", "type", text="Mirror")
|
|
|
|
layout.separator()
|
|
layout.operator("action.keyframe_insert")
|
|
|
|
layout.separator()
|
|
layout.operator("action.frame_jump")
|
|
|
|
layout.separator()
|
|
layout.operator("action.copy")
|
|
layout.operator("action.paste")
|
|
layout.operator("action.paste", text="Paste Flipped").flipped = True
|
|
layout.operator("action.duplicate_move")
|
|
layout.operator("action.delete")
|
|
|
|
layout.separator()
|
|
layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
|
|
layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
|
|
layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
|
|
layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode")
|
|
|
|
layout.separator()
|
|
layout.operator("action.clean").channels = False
|
|
layout.operator("action.clean", text="Clean Channels").channels = True
|
|
layout.operator("action.sample")
|
|
|
|
|
|
class DOPESHEET_MT_key_transform(Menu):
|
|
bl_label = "Transform"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("transform.transform", text="Move").mode = 'TIME_TRANSLATE'
|
|
layout.operator("transform.transform", text="Extend").mode = 'TIME_EXTEND'
|
|
layout.operator("transform.transform", text="Slide").mode = 'TIME_SLIDE'
|
|
layout.operator("transform.transform", text="Scale").mode = 'TIME_SCALE'
|
|
|
|
|
|
#######################################
|
|
# Grease Pencil Editing
|
|
|
|
class DOPESHEET_MT_gpencil_channel(Menu):
|
|
bl_label = "Channel"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator_context = 'INVOKE_REGION_CHANNELS'
|
|
|
|
layout.operator("anim.channels_delete")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_setting_toggle")
|
|
layout.operator("anim.channels_setting_enable")
|
|
layout.operator("anim.channels_setting_disable")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_editable_toggle")
|
|
|
|
# XXX: to be enabled when these are ready for use!
|
|
# layout.separator()
|
|
# layout.operator("anim.channels_expand")
|
|
# layout.operator("anim.channels_collapse")
|
|
|
|
layout.separator()
|
|
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
|
|
|
|
|
class DOPESHEET_MT_gpencil_key(Menu):
|
|
bl_label = "Key"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.menu("DOPESHEET_MT_key_transform", text="Transform")
|
|
layout.operator_menu_enum("action.snap", "type", text="Snap")
|
|
layout.operator_menu_enum("action.mirror", "type", text="Mirror")
|
|
|
|
layout.separator()
|
|
layout.operator("action.keyframe_insert")
|
|
|
|
layout.separator()
|
|
layout.operator("action.delete")
|
|
layout.operator("gpencil.interpolate_reverse")
|
|
|
|
layout.separator()
|
|
layout.operator("action.keyframe_type", text="Keyframe Type")
|
|
|
|
|
|
class DOPESHEET_MT_delete(Menu):
|
|
bl_label = "Delete"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("action.delete")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("action.clean").channels = False
|
|
layout.operator("action.clean", text="Clean Channels").channels = True
|
|
|
|
|
|
class DOPESHEET_MT_context_menu(Menu):
|
|
bl_label = "Dope Sheet Context Menu"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
st = context.space_data
|
|
|
|
layout.operator_context = 'INVOKE_DEFAULT'
|
|
|
|
layout.operator("action.copy", text="Copy", icon='COPYDOWN')
|
|
layout.operator("action.paste", text="Paste", icon='PASTEDOWN')
|
|
layout.operator("action.paste", text="Paste Flipped", icon='PASTEFLIPDOWN').flipped = True
|
|
|
|
layout.separator()
|
|
|
|
layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
|
|
|
|
if st.mode != 'GPENCIL':
|
|
layout.operator_menu_enum("action.handle_type", "type", text="Handle Type")
|
|
layout.operator_menu_enum("action.interpolation_type", "type", text="Interpolation Mode")
|
|
layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("action.keyframe_insert").type = 'SEL'
|
|
layout.operator("action.duplicate_move")
|
|
|
|
if st.mode == 'GPENCIL':
|
|
layout.separator()
|
|
|
|
layout.operator_context = 'EXEC_REGION_WIN'
|
|
layout.operator("action.delete")
|
|
|
|
if st.mode == 'GPENCIL':
|
|
layout.operator("gpencil.interpolate_reverse")
|
|
layout.operator("gpencil.frame_clean_duplicate", text="Delete Duplicate Frames")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator_menu_enum("action.mirror", "type", text="Mirror")
|
|
layout.operator_menu_enum("action.snap", "type", text="Snap")
|
|
|
|
|
|
class DOPESHEET_MT_channel_context_menu(Menu):
|
|
bl_label = "Dope Sheet Channel Context Menu"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
# This menu is used from the graph editor too.
|
|
is_graph_editor = context.area.type == 'GRAPH_EDITOR'
|
|
|
|
layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
|
|
layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
|
|
layout.separator()
|
|
layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
|
|
layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_group")
|
|
layout.operator("anim.channels_ungroup")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_editable_toggle")
|
|
|
|
if is_graph_editor:
|
|
operator = "graph.extrapolation_type"
|
|
else:
|
|
operator = "action.extrapolation_type"
|
|
layout.operator_menu_enum(operator, "type", text="Extrapolation Mode")
|
|
|
|
layout.separator()
|
|
layout.operator("anim.channels_expand")
|
|
layout.operator("anim.channels_collapse")
|
|
|
|
layout.separator()
|
|
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("anim.channels_delete")
|
|
|
|
|
|
class DOPESHEET_MT_snap_pie(Menu):
|
|
bl_label = "Snap"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
pie = layout.menu_pie()
|
|
|
|
pie.operator("action.snap", text="Selection to Current Frame").type = 'CFRA'
|
|
pie.operator("action.snap", text="Selection to Nearest Frame").type = 'NEAREST_FRAME'
|
|
pie.operator("action.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND'
|
|
pie.operator("action.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER'
|
|
|
|
|
|
class LayersDopeSheetPanel:
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
bl_region_type = 'UI'
|
|
bl_category = "View"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
st = context.space_data
|
|
ob = context.object
|
|
if st.mode != 'GPENCIL' or ob is None or ob.type != 'GPENCIL':
|
|
return False
|
|
|
|
gpd = ob.data
|
|
gpl = gpd.layers.active
|
|
if gpl:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_mode(LayersDopeSheetPanel, Panel):
|
|
# bl_space_type = 'DOPESHEET_EDITOR'
|
|
# bl_region_type = 'UI'
|
|
# bl_category = "View"
|
|
bl_label = "Layer"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
ob = context.object
|
|
gpd = ob.data
|
|
gpl = gpd.layers.active
|
|
if gpl:
|
|
row = layout.row(align=True)
|
|
row.prop(gpl, "blend_mode", text="Blend")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(gpl, "opacity", text="Opacity", slider=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(gpl, "use_lights")
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_layer_masks(LayersDopeSheetPanel, GreasePencilLayerMasksPanel, Panel):
|
|
bl_label = "Masks"
|
|
bl_parent_id = 'DOPESHEET_PT_gpencil_mode'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_layer_transform(LayersDopeSheetPanel, GreasePencilLayerTransformPanel, Panel):
|
|
bl_label = "Transform"
|
|
bl_parent_id = 'DOPESHEET_PT_gpencil_mode'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_layer_adjustments(LayersDopeSheetPanel, GreasePencilLayerAdjustmentsPanel, Panel):
|
|
bl_label = "Adjustments"
|
|
bl_parent_id = 'DOPESHEET_PT_gpencil_mode'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_layer_relations(LayersDopeSheetPanel, GreasePencilLayerRelationsPanel, Panel):
|
|
bl_label = "Relations"
|
|
bl_parent_id = 'DOPESHEET_PT_gpencil_mode'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DOPESHEET_PT_gpencil_layer_display(LayersDopeSheetPanel, GreasePencilLayerDisplayPanel, Panel):
|
|
bl_label = "Display"
|
|
bl_parent_id = 'DOPESHEET_PT_gpencil_mode'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
classes = (
|
|
DOPESHEET_HT_header,
|
|
DOPESHEET_MT_editor_menus,
|
|
DOPESHEET_MT_view,
|
|
DOPESHEET_MT_select,
|
|
DOPESHEET_MT_marker,
|
|
DOPESHEET_MT_channel,
|
|
DOPESHEET_MT_key,
|
|
DOPESHEET_MT_key_transform,
|
|
DOPESHEET_MT_gpencil_channel,
|
|
DOPESHEET_MT_gpencil_key,
|
|
DOPESHEET_MT_delete,
|
|
DOPESHEET_MT_context_menu,
|
|
DOPESHEET_MT_channel_context_menu,
|
|
DOPESHEET_MT_snap_pie,
|
|
DOPESHEET_MT_view_pie,
|
|
DOPESHEET_PT_filters,
|
|
DOPESHEET_PT_gpencil_mode,
|
|
DOPESHEET_PT_gpencil_layer_masks,
|
|
DOPESHEET_PT_gpencil_layer_transform,
|
|
DOPESHEET_PT_gpencil_layer_adjustments,
|
|
DOPESHEET_PT_gpencil_layer_relations,
|
|
DOPESHEET_PT_gpencil_layer_display,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|