Files
test2/scripts/startup/bl_ui/space_graph.py
Nika Kutsniashvili 5a0b50e0a6 Anim: Reorganize items in 'View' menus in animation and video editors
View menu in animation and video editors are too
different from each other and unorganized.
Same operators appear in different places in different editors.
That makes navigating them harder, because for example
you expect framing operators at the bottom because they're
at the bottom in graph editor, but they're at the top in Sequencer
and 3D Viewport. It's important that ordering of operators
match as well as possible, so that users can expect certain
operators in certain places and dont spend time searching
for buttons in messy menus.

I tried to match ordering to 3D viewport menu
as much as possible, and also to use separators right,
so that grouping of items is logical and can be shareable between editors.

This grouping also looks better and makes navigation easier,
because it splits huge pile of toggles at the top of the menus
in smaller chunks and puts operators between them,
so they're esier to separate them in one glance.

Split off from #116492

Pull Request: https://projects.blender.org/blender/blender/pulls/117162
2024-01-23 12:11:22 +01:00

537 lines
18 KiB
Python

# SPDX-FileCopyrightText: 2009-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
from bpy.types import Header, Menu, Panel
from bl_ui.space_dopesheet import (
DopesheetFilterPopoverBase,
dopesheet_filter,
)
class GRAPH_HT_header(Header):
bl_space_type = 'GRAPH_EDITOR'
def draw(self, context):
layout = self.layout
tool_settings = context.tool_settings
st = context.space_data
layout.template_header()
# Now a exposed as a sub-space type
# layout.prop(st, "mode", text="")
GRAPH_MT_editor_menus.draw_collapsible(context, layout)
row = layout.row(align=True)
row.prop(st, "use_normalization", icon='NORMALIZE_FCURVES', text="Normalize", toggle=True)
sub = row.row(align=True)
sub.active = st.use_normalization
sub.prop(st, "use_auto_normalization", icon='FILE_REFRESH', text="", toggle=True)
layout.separator_spacer()
dopesheet_filter(layout, context)
row = layout.row(align=True)
if st.has_ghost_curves:
row.operator("graph.ghost_curves_clear", text="", icon='X')
else:
row.operator("graph.ghost_curves_create", text="", icon='FCURVE_SNAPSHOT')
layout.popover(
panel="GRAPH_PT_filters",
text="",
icon='FILTER',
)
layout.prop(st, "pivot_point", icon_only=True)
row = layout.row(align=True)
row.prop(tool_settings, "use_snap_anim", text="")
sub = row.row(align=True)
sub.popover(
panel="GRAPH_PT_snapping",
text="",
)
row = layout.row(align=True)
row.prop(tool_settings, "use_proportional_fcurve", text="", icon_only=True)
sub = row.row(align=True)
sub.active = tool_settings.use_proportional_fcurve
sub.prop_with_popover(
tool_settings,
"proportional_edit_falloff",
text="",
icon_only=True,
panel="GRAPH_PT_proportional_edit",
)
class GRAPH_PT_proportional_edit(Panel):
bl_space_type = 'GRAPH_EDITOR'
bl_region_type = 'HEADER'
bl_label = "Proportional Editing"
bl_ui_units_x = 8
def draw(self, context):
layout = self.layout
tool_settings = context.tool_settings
col = layout.column()
col.active = tool_settings.use_proportional_fcurve
col.prop(tool_settings, "proportional_edit_falloff", expand=True)
col.prop(tool_settings, "proportional_size")
class GRAPH_PT_filters(DopesheetFilterPopoverBase, Panel):
bl_space_type = 'GRAPH_EDITOR'
bl_region_type = 'HEADER'
bl_label = "Filters"
def draw(self, context):
layout = self.layout
st = context.space_data
DopesheetFilterPopoverBase.draw_generic_filters(context, layout)
layout.separator()
DopesheetFilterPopoverBase.draw_search_filters(context, layout)
layout.separator()
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
if st.mode == 'DRIVERS':
layout.separator()
col = layout.column(align=True)
col.label(text="Drivers:")
col.prop(st.dopesheet, "show_driver_fallback_as_error")
class GRAPH_PT_snapping(Panel):
bl_space_type = 'GRAPH_EDITOR'
bl_region_type = 'HEADER'
bl_label = "Snapping"
def draw(self, context):
layout = self.layout
col = layout.column()
col.label(text="Snap To")
tool_settings = context.tool_settings
col.prop(tool_settings, "snap_anim_element", expand=True)
if tool_settings.snap_anim_element != 'MARKER':
col.prop(tool_settings, "use_snap_time_absolute")
class GRAPH_MT_editor_menus(Menu):
bl_idname = "GRAPH_MT_editor_menus"
bl_label = ""
def draw(self, context):
st = context.space_data
layout = self.layout
layout.menu("GRAPH_MT_view")
layout.menu("GRAPH_MT_select")
if st.mode != 'DRIVERS' and st.show_markers:
layout.menu("GRAPH_MT_marker")
layout.menu("GRAPH_MT_channel")
layout.menu("GRAPH_MT_key")
class GRAPH_MT_view(Menu):
bl_label = "View"
def draw(self, context):
layout = self.layout
st = context.space_data
layout.prop(st, "show_region_ui")
layout.prop(st, "show_region_hud")
layout.separator()
layout.operator("graph.view_selected")
layout.operator("graph.view_all")
layout.operator("graph.view_frame")
layout.separator()
layout.prop(st, "use_realtime_update")
layout.prop(st, "show_sliders")
layout.prop(st, "use_auto_merge_keyframes")
layout.separator()
if st.mode != 'DRIVERS':
layout.prop(st, "show_markers")
layout.prop(st, "show_cursor")
layout.prop(st, "show_seconds")
layout.prop(st, "show_locked_time")
layout.separator()
layout.prop(st, "show_extrapolation")
layout.prop(st, "show_handles")
layout.prop(st, "use_only_selected_keyframe_handles")
layout.separator()
layout.operator("anim.previewrange_set")
layout.operator("anim.previewrange_clear")
layout.operator("graph.previewrange_set")
layout.separator()
# Add this to show key-binding (reverse action in dope-sheet).
props = layout.operator("wm.context_set_enum", text="Toggle Dope Sheet")
props.data_path = "area.type"
props.value = 'DOPESHEET_EDITOR'
layout.separator()
layout.menu("INFO_MT_area")
class GRAPH_MT_select(Menu):
bl_label = "Select"
def draw(self, _context):
layout = self.layout
layout.operator("graph.select_all", text="All").action = 'SELECT'
layout.operator("graph.select_all", text="None").action = 'DESELECT'
layout.operator("graph.select_all", text="Invert").action = 'INVERT'
layout.separator()
layout.operator("graph.select_box")
props = layout.operator("graph.select_box", text="Box Select (Axis Range)")
props.axis_range = True
props = layout.operator("graph.select_box", text="Box Select (Include Handles)")
props.include_handles = True
layout.operator("graph.select_circle")
layout.operator_menu_enum("graph.select_lasso", "mode")
layout.separator()
layout.operator("graph.select_column", text="Columns on Selected Keys").mode = 'KEYS'
layout.operator("graph.select_column", text="Column on Current Frame").mode = 'CFRA'
layout.operator("graph.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN'
layout.operator("graph.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN'
layout.separator()
props = layout.operator("graph.select_leftright", text="Before Current Frame")
props.extend = False
props.mode = 'LEFT'
props = layout.operator("graph.select_leftright", text="After Current Frame")
props.extend = False
props.mode = 'RIGHT'
layout.separator()
props = layout.operator("graph.select_key_handles", text="Select Handles")
props.left_handle_action = 'SELECT'
props.right_handle_action = 'SELECT'
props.key_action = 'KEEP'
props = layout.operator("graph.select_key_handles", text="Select Key")
props.left_handle_action = 'DESELECT'
props.right_handle_action = 'DESELECT'
props.key_action = 'SELECT'
layout.separator()
layout.operator("graph.select_more")
layout.operator("graph.select_less")
layout.separator()
layout.operator("graph.select_linked")
class GRAPH_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)
# TODO: pose markers for action edit mode only?
class GRAPH_MT_channel(Menu):
bl_label = "Channel"
def draw(self, context):
layout = self.layout
operator_context = layout.operator_context
layout.operator_context = 'INVOKE_REGION_CHANNELS'
layout.operator("anim.channels_delete")
if context.space_data.mode == 'DRIVERS':
layout.operator("graph.driver_delete_invalid")
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("graph.extrapolation_type", "type", text="Extrapolation Mode")
# To get it to display the hotkey.
layout.operator_context = operator_context
layout.operator_menu_enum("graph.fmodifier_add", "type").only_active = False
layout.operator_context = 'INVOKE_REGION_CHANNELS'
layout.separator()
layout.operator("graph.hide", text="Hide Selected Curves").unselected = False
layout.operator("graph.hide", text="Hide Unselected Curves").unselected = True
layout.operator("graph.reveal")
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")
layout.separator()
layout.operator("graph.keys_to_samples")
layout.operator("graph.samples_to_keys")
layout.operator("graph.sound_to_samples")
layout.operator("anim.channels_bake")
layout.separator()
layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter")
layout.separator()
layout.operator("anim.channels_view_selected")
class GRAPH_MT_key_density(Menu):
bl_label = "Density"
def draw(self, _context):
from bl_ui_utils.layout import operator_context
layout = self.layout
layout.operator("graph.decimate", text="Decimate (Ratio)").mode = 'RATIO'
# Using the modal operation doesn't make sense for this variant
# as we do not have a modal mode for it, so just execute it.
with operator_context(layout, 'EXEC_REGION_WIN'):
layout.operator("graph.decimate", text="Decimate (Allowed Change)").mode = 'ERROR'
layout.operator("graph.bake_keys")
layout.separator()
layout.operator("graph.clean").channels = False
class GRAPH_MT_key_blending(Menu):
bl_label = "Blend"
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
layout.operator("graph.breakdown", text="Breakdown")
layout.operator("graph.blend_to_neighbor", text="Blend to Neighbor")
layout.operator("graph.blend_to_default", text="Blend to Default Value")
layout.operator("graph.ease", text="Ease")
layout.operator("graph.blend_offset", text="Blend Offset")
layout.operator("graph.blend_to_ease", text="Blend to Ease")
layout.operator("graph.match_slope", text="Match Slope")
layout.operator("graph.push_pull", text="Push Pull")
layout.operator("graph.shear", text="Shear Keys")
layout.operator("graph.scale_average", text="Scale Average")
layout.operator("graph.scale_from_neighbor", text="Scale from Neighbor")
layout.operator("graph.time_offset", text="Time Offset")
class GRAPH_MT_key_smoothing(Menu):
bl_label = "Smooth"
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
layout.operator("graph.gaussian_smooth", text="Smooth (Gaussian)")
layout.operator("graph.smooth", text="Smooth (Legacy)")
layout.operator("graph.butterworth_smooth")
class GRAPH_MT_key(Menu):
bl_label = "Key"
def draw(self, _context):
layout = self.layout
layout.menu("GRAPH_MT_key_transform", text="Transform")
layout.menu("GRAPH_MT_key_snap", text="Snap")
layout.operator_menu_enum("graph.mirror", "type", text="Mirror")
layout.separator()
layout.operator("graph.frame_jump", text="Jump to Selected")
layout.separator()
layout.operator_menu_enum("graph.keyframe_insert", "type", text="Insert")
layout.operator("graph.copy", text="Copy")
layout.operator("graph.paste", text="Paste")
layout.operator("graph.paste", text="Paste Flipped").flipped = True
layout.operator("graph.duplicate_move")
layout.operator("graph.delete", text="Delete")
layout.separator()
layout.operator_menu_enum("graph.handle_type", "type", text="Handle Type")
layout.operator_menu_enum("graph.interpolation_type", "type", text="Interpolation Mode")
layout.operator_menu_enum("graph.easing_type", "type", text="Easing Type")
layout.separator()
layout.menu("GRAPH_MT_key_density")
layout.menu("GRAPH_MT_key_blending")
layout.menu("GRAPH_MT_key_smoothing")
class GRAPH_MT_key_transform(Menu):
bl_label = "Transform"
def draw(self, _context):
layout = self.layout
layout.operator("transform.translate", text="Move")
layout.operator("transform.transform", text="Extend").mode = 'TIME_EXTEND'
layout.operator("transform.rotate", text="Rotate")
layout.operator("transform.resize", text="Scale")
class GRAPH_MT_key_snap(Menu):
bl_label = "Snap"
def draw(self, _context):
layout = self.layout
layout.operator("graph.snap", text="Selection to Current Frame").type = 'CFRA'
layout.operator("graph.snap", text="Selection to Cursor Value").type = 'VALUE'
layout.operator("graph.snap", text="Selection to Nearest Frame").type = 'NEAREST_FRAME'
layout.operator("graph.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND'
layout.operator("graph.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER'
layout.operator("graph.snap", text="Flatten Handles").type = 'HORIZONTAL'
layout.operator("graph.equalize_handles", text="Equalize Handles").side = 'BOTH'
layout.separator()
layout.operator("graph.frame_jump", text="Cursor to Selection")
layout.operator("graph.snap_cursor_value", text="Cursor Value to Selection")
class GRAPH_MT_view_pie(Menu):
bl_label = "View"
def draw(self, _context):
layout = self.layout
pie = layout.menu_pie()
pie.operator("graph.view_all")
pie.operator("graph.view_selected", icon='ZOOM_SELECTED')
pie.operator("graph.view_frame")
class GRAPH_MT_delete(Menu):
bl_label = "Delete"
def draw(self, _context):
layout = self.layout
layout.operator("graph.delete")
layout.separator()
layout.operator("graph.clean").channels = False
layout.operator("graph.clean", text="Clean Channels").channels = True
class GRAPH_MT_context_menu(Menu):
bl_label = "F-Curve"
def draw(self, _context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
layout.operator("graph.copy", text="Copy", icon='COPYDOWN')
layout.operator("graph.paste", text="Paste", icon='PASTEDOWN')
layout.operator("graph.paste", text="Paste Flipped", icon='PASTEFLIPDOWN').flipped = True
layout.separator()
layout.operator_menu_enum("graph.handle_type", "type", text="Handle Type")
layout.operator_menu_enum("graph.interpolation_type", "type", text="Interpolation Mode")
layout.operator_menu_enum("graph.easing_type", "type", text="Easing Type")
layout.separator()
layout.operator("graph.keyframe_insert").type = 'SEL'
layout.operator("graph.duplicate_move")
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("graph.delete")
layout.separator()
layout.operator_menu_enum("graph.mirror", "type", text="Mirror")
layout.operator_menu_enum("graph.snap", "type", text="Snap")
class GRAPH_MT_pivot_pie(Menu):
bl_label = "Pivot Point"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
pie.prop_enum(context.space_data, "pivot_point", value='BOUNDING_BOX_CENTER')
pie.prop_enum(context.space_data, "pivot_point", value='CURSOR')
pie.prop_enum(context.space_data, "pivot_point", value='INDIVIDUAL_ORIGINS')
class GRAPH_MT_snap_pie(Menu):
bl_label = "Snap"
def draw(self, _context):
layout = self.layout
pie = layout.menu_pie()
pie.operator("graph.snap", text="Selection to Current Frame").type = 'CFRA'
pie.operator("graph.snap", text="Selection to Cursor Value").type = 'VALUE'
pie.operator("graph.snap", text="Selection to Nearest Frame").type = 'NEAREST_FRAME'
pie.operator("graph.snap", text="Selection to Nearest Second").type = 'NEAREST_SECOND'
pie.operator("graph.snap", text="Selection to Nearest Marker").type = 'NEAREST_MARKER'
pie.operator("graph.snap", text="Flatten Handles").type = 'HORIZONTAL'
pie.operator("graph.frame_jump", text="Cursor to Selection")
pie.operator("graph.snap_cursor_value", text="Cursor Value to Selection")
classes = (
GRAPH_HT_header,
GRAPH_PT_proportional_edit,
GRAPH_MT_editor_menus,
GRAPH_MT_view,
GRAPH_MT_select,
GRAPH_MT_marker,
GRAPH_MT_channel,
GRAPH_MT_key,
GRAPH_MT_key_density,
GRAPH_MT_key_transform,
GRAPH_MT_key_snap,
GRAPH_MT_key_smoothing,
GRAPH_MT_key_blending,
GRAPH_MT_delete,
GRAPH_MT_context_menu,
GRAPH_MT_pivot_pie,
GRAPH_MT_snap_pie,
GRAPH_MT_view_pie,
GRAPH_PT_filters,
GRAPH_PT_snapping,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)