This is a replacement for the workflow that uses "Bake Curve" and "Unbake Curve" to quickly generate dense key data. Compared to the existing workflow it has the advantage of allowing the user more control over the key types, and distance between keys, as well as the frame range affected. Operator options * Range: the range that will be baked. Defaults to the scene range or preview range. * Step: Distance between keyframes. Can be used to bake on 2s or even bake to subframes. * Remove Existing Keys: Boolean option that if enabled also removes keys outside the specified baking range * Interpolation Type: Choose a interpolation mode used for new keys e.g. Constant or Bezier * Bake Modifiers: If enabled bakes the effect of the modifier stack to keys and deletes the modifier stack. If false, the code disables the modifiers before baking, so the resulting keys will behave as if the modifiers didn't exist The operator can be found in the Graph Editor under `Channel->Bake Channels` Part of: #111050 Pull Request: https://projects.blender.org/blender/blender/pulls/111263
530 lines
18 KiB
Python
530 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
|
|
|
|
DopesheetFilterPopoverBase.draw_generic_filters(context, layout)
|
|
layout.separator()
|
|
DopesheetFilterPopoverBase.draw_search_filters(context, layout)
|
|
layout.separator()
|
|
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
|
|
|
|
|
|
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.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()
|
|
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.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)
|