# SPDX-FileCopyrightText: 2009-2023 Blender Authors # # SPDX-License-Identifier: GPL-2.0-or-later from bpy.types import Header, Menu, Panel from bpy.app.translations import ( pgettext_iface as iface_, contexts as i18n_contexts, ) from bl_ui.space_dopesheet import ( DopesheetFilterPopoverBase, dopesheet_filter, ) from bl_ui.space_time import playback_controls def drivers_editor_footer(layout, context): act_fcurve = context.active_editable_fcurve if not act_fcurve: return act_driver = act_fcurve.driver if not act_driver: return layout.separator_spacer() layout.label( text=iface_("Driver: {:s} ({:s})").format( act_fcurve.id_data.name, act_fcurve.data_path), translate=False) if act_driver.variables: layout.separator(type='LINE') layout.label(text=iface_("Variables: {:d}").format(len(act_driver.variables)), translate=False) if act_driver.type == 'SCRIPTED' and act_driver.expression: layout.separator(type='LINE') layout.label(text=iface_("Expression: {:s}").format(act_driver.expression), translate=False) 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) if context.space_data.mode == 'DRIVERS': row.prop(tool_settings, "use_snap_driver", text="") sub = row.row(align=True) sub.popover( panel="GRAPH_PT_driver_snapping", text="", ) else: 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_HT_playback_controls(Header): bl_space_type = 'GRAPH_EDITOR' bl_region_type = 'FOOTER' def draw(self, context): layout = self.layout is_drivers_editor = context.space_data.mode == 'DRIVERS' if is_drivers_editor: drivers_editor_footer(layout, context) else: playback_controls(layout, context) 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_PT_driver_snapping(Panel): bl_space_type = 'GRAPH_EDITOR' bl_region_type = 'HEADER' bl_label = "Snapping" def draw(self, context): layout = self.layout col = layout.column() tool_settings = context.tool_settings col.prop(tool_settings, "use_snap_driver_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.prop(st, "show_region_channels") layout.prop(st, "show_region_footer", text="Playback Controls") layout.separator() layout.operator("graph.view_selected") layout.operator("graph.view_all") if context.scene.use_preview_range: layout.operator("anim.scene_range_frame", text="Frame Preview Range") else: layout.operator("anim.scene_range_frame", text="Frame Scene Range") 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.prop(st, "use_auto_lock_translation_axis") 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", text="Box Select (Include Handles)") props = layout.operator("graph.select_box", text="Box Select (Axis Range)") props.axis_range = True props = layout.operator("graph.select_box") props.include_handles = False layout.operator("graph.select_circle") layout.operator_menu_enum("graph.select_lasso", "mode") layout.separator() layout.operator("graph.select_more", text="More") layout.operator("graph.select_less", text="Less") layout.separator() layout.operator("graph.select_linked") 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' 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" bl_translation_context = i18n_contexts.operator_default 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" bl_translation_context = i18n_contexts.operator_default 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") if context.scene.use_preview_range: pie.operator("anim.scene_range_frame", text="Frame Preview Range") else: pie.operator("anim.scene_range_frame", text="Frame Scene Range") 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_HT_playback_controls, 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, GRAPH_PT_driver_snapping, ) if __name__ == "__main__": # only for live edit. from bpy.utils import register_class for cls in classes: register_class(cls)