Move two settings that were previously in the "View" menu of the Graph Editor into User Preferences. It has been mentioned in the meeting by Luciano Muñoz Sessarego that it would be good to move that to the preferences so you can set it once and then forget about it. The Settings moved are: Only Selected Curve Keyframes Use High Quality Display Pull Request: blender/blender#104532
486 lines
16 KiB
Python
486 lines
16 KiB
Python
# 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)
|
|
|
|
layout.prop(st, "auto_snap", 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(tool_settings, "proportional_edit_falloff", text="", icon_only=True)
|
|
|
|
|
|
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
|
|
|
|
DopesheetFilterPopoverBase.draw_generic_filters(context, layout)
|
|
layout.separator()
|
|
DopesheetFilterPopoverBase.draw_search_filters(context, layout)
|
|
layout.separator()
|
|
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
|
|
|
|
|
|
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.prop(st, "use_realtime_update")
|
|
layout.prop(st, "show_cursor")
|
|
layout.prop(st, "show_sliders")
|
|
layout.prop(st, "use_auto_merge_keyframes")
|
|
|
|
if st.mode != 'DRIVERS':
|
|
layout.separator()
|
|
layout.prop(st, "show_markers")
|
|
|
|
layout.prop(st, "show_extrapolation")
|
|
|
|
layout.prop(st, "show_handles")
|
|
layout.prop(st, "use_only_selected_keyframe_handles")
|
|
|
|
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("graph.previewrange_set")
|
|
|
|
layout.separator()
|
|
layout.operator("graph.view_all")
|
|
layout.operator("graph.view_selected")
|
|
layout.operator("graph.view_frame")
|
|
|
|
# Add this to show key-binding (reverse action in dope-sheet).
|
|
layout.separator()
|
|
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()
|
|
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
|
|
|
|
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")
|
|
layout.operator_menu_enum("graph.fmodifier_add", "type", text="Add F-Curve Modifier").only_active = False
|
|
|
|
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("anim.channels_view_selected")
|
|
|
|
|
|
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_menu_enum("graph.keyframe_insert", "type")
|
|
layout.operator_menu_enum("graph.fmodifier_add", "type")
|
|
layout.operator("graph.sound_bake")
|
|
|
|
layout.separator()
|
|
layout.operator("graph.frame_jump")
|
|
|
|
layout.separator()
|
|
layout.operator("graph.copy")
|
|
layout.operator("graph.paste")
|
|
layout.operator("graph.paste", text="Paste Flipped").flipped = True
|
|
layout.operator("graph.duplicate_move")
|
|
layout.operator("graph.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()
|
|
operator_context = layout.operator_context
|
|
|
|
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.
|
|
layout.operator_context = 'EXEC_REGION_WIN'
|
|
layout.operator("graph.decimate", text="Decimate (Allowed Change)").mode = 'ERROR'
|
|
layout.operator_context = operator_context
|
|
|
|
layout.menu("GRAPH_MT_slider", text="Slider Operators")
|
|
|
|
layout.operator("graph.clean").channels = False
|
|
layout.operator("graph.clean", text="Clean Channels").channels = True
|
|
layout.operator("graph.smooth")
|
|
layout.operator("graph.sample")
|
|
layout.operator("graph.bake")
|
|
layout.operator("graph.unbake")
|
|
|
|
layout.separator()
|
|
layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter")
|
|
|
|
|
|
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_slider(Menu):
|
|
bl_label = "Slider Operators"
|
|
|
|
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")
|
|
|
|
|
|
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 Context Menu"
|
|
|
|
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")
|
|
|
|
|
|
class GRAPH_MT_channel_context_menu(Menu):
|
|
bl_label = "F-Curve Channel Context Menu"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
st = context.space_data
|
|
|
|
layout.separator()
|
|
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")
|
|
layout.operator_menu_enum("graph.extrapolation_type", "type", text="Extrapolation Mode")
|
|
|
|
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_delete")
|
|
if st.mode == 'DRIVERS':
|
|
layout.operator("graph.driver_delete_invalid")
|
|
|
|
|
|
classes = (
|
|
GRAPH_HT_header,
|
|
GRAPH_MT_editor_menus,
|
|
GRAPH_MT_view,
|
|
GRAPH_MT_select,
|
|
GRAPH_MT_marker,
|
|
GRAPH_MT_channel,
|
|
GRAPH_MT_key,
|
|
GRAPH_MT_key_transform,
|
|
GRAPH_MT_key_snap,
|
|
GRAPH_MT_slider,
|
|
GRAPH_MT_delete,
|
|
GRAPH_MT_context_menu,
|
|
GRAPH_MT_channel_context_menu,
|
|
GRAPH_MT_pivot_pie,
|
|
GRAPH_MT_snap_pie,
|
|
GRAPH_MT_view_pie,
|
|
GRAPH_PT_filters,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|