2023-08-16 00:20:26 +10:00
|
|
|
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
2023-06-15 13:09:04 +10:00
|
|
|
#
|
2022-02-11 09:07:11 +11:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
2009-12-07 21:51:44 +00:00
|
|
|
|
|
|
|
|
import bpy
|
2018-09-27 13:27:53 +10:00
|
|
|
from bpy.types import (
|
|
|
|
|
Header,
|
|
|
|
|
Menu,
|
|
|
|
|
Panel,
|
|
|
|
|
)
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2024-04-29 15:14:31 +02:00
|
|
|
from bl_ui.properties_data_grease_pencil import (
|
|
|
|
|
GreasePencil_LayerMaskPanel,
|
|
|
|
|
GreasePencil_LayerTransformPanel,
|
2024-11-15 11:59:25 +01:00
|
|
|
GreasePencil_LayerRelationsPanel,
|
2024-11-15 12:10:28 +01:00
|
|
|
GreasePencil_LayerAdjustmentsPanel,
|
2024-12-09 14:11:29 +01:00
|
|
|
GreasePencil_LayerDisplayPanel,
|
2024-04-29 15:14:31 +02:00
|
|
|
)
|
2025-06-19 15:54:53 +02:00
|
|
|
from bl_ui.space_time import playback_controls
|
2025-09-09 11:38:19 +02:00
|
|
|
from bl_ui.properties_data_mesh import draw_shape_key_properties
|
2024-04-29 15:14:31 +02:00
|
|
|
|
2022-06-23 11:11:53 +02:00
|
|
|
from rna_prop_ui import PropertyPanel
|
|
|
|
|
|
2011-01-10 22:10:28 +00:00
|
|
|
#######################################
|
2018-06-26 21:12:25 +12:00
|
|
|
# DopeSheet Filtering - Header Buttons
|
2011-01-01 07:20:34 +00:00
|
|
|
|
2010-09-13 00:53:47 +00:00
|
|
|
# used for DopeSheet, NLA, and Graph Editors
|
2018-10-01 08:42:58 +10:00
|
|
|
|
|
|
|
|
|
2019-05-07 15:09:14 +02:00
|
|
|
def dopesheet_filter(layout, context):
|
2010-08-27 01:23:53 +00:00
|
|
|
dopesheet = context.space_data.dopesheet
|
|
|
|
|
is_nla = context.area.type == 'NLA_EDITOR'
|
2024-07-04 14:44:19 +02:00
|
|
|
is_action_editor = not is_nla and context.space_data.mode == 'ACTION'
|
2010-08-27 01:23:53 +00:00
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
Anim: Remove 'Slotted Actions' experimental flag
This commit takes the 'Slotted Actions' out of the experimental phase.
As a result:
- All newly created Actions will be slotted Actions.
- Legacy Actions loaded from disk will be versioned to slotted Actions.
- The new Python API for slots, layers, strips, and channel bags is
available.
- The legacy Python API for accessing F-Curves and Action Groups is
still available, and will operate on the F-Curves/Groups for the first
slot only.
- Creating an Action by keying (via the UI, operators, or the
`rna_struct.keyframe_insert` function) will try and share Actions
between related data-blocks. See !126655 for more info about this.
- Assigning an Action to a data-block will auto-assign a suitable Action
Slot. The logic for this is described below. However, There are cases
where this does _not_ automatically assign a slot, and thus the Action
will effectively _not_ animate the data-block. Effort has been spent
to make Action selection work both reliably for Blender users as well
as keep the behaviour the same for Python scripts. Where these two
goals did not converge, reliability and understandability for users
was prioritised.
Auto-selection of the Action Slot upon assigning the Action works as
follows. The first rule to find a slot wins.
1. The data-block remembers the slot name that was last assigned. If the
newly assigned Action has a slot with that name, it is chosen.
2. If the Action has a slot with the same name as the data-block, it is
chosen.
3. If the Action has only one slot, and it has never been assigned to
anything, it is chosen.
4. If the Action is assigned to an NLA strip or an Action constraint,
and the Action has a single slot, and that slot has a suitable ID
type, it is chosen.
This last step is what I was referring to with "Where these two goals
did not converge, reliability and understandability for users was
prioritised." For regular Action assignments (like via the Action
selectors in the Properties editor) this rule doesn't apply, even though
with legacy Actions the final state ("it is animated by this Action")
differs from the final state with slotted Actions ("it has no slot so is
not animated"). This is done to support the following workflow:
- Create an Action by animating Cube.
- In order to animate Suzanne with that same Action, assign the Action
to Suzanne.
- Start keying Suzanne. This auto-creates and auto-assigns a new slot
for Suzanne.
If rule 4. above would apply in this case, the 2nd step would
automatically select the Cube slot for Suzanne as well, which would
immediately overwrite Suzanne's properties with the Cube animation.
Technically, this commit:
- removes the `WITH_ANIM_BAKLAVA` build flag,
- removes the `use_animation_baklava` experimental flag in preferences,
- updates the code to properly deal with the fact that empty Actions are
now always considered slotted/layered Actions (instead of that relying
on the user preference).
Note that 'slotted Actions' and 'layered Actions' are the exact same
thing, just focusing on different aspects (slot & layers) of the new
data model.
The "Baklava phase 1" assumptions are still asserted. This means that:
- an Action can have zero or one layer,
- that layer can have zero or one strip,
- that strip must be of type 'keyframe' and be infinite with zero
offset.
The code to handle legacy Actions is NOT removed in this commit. It will
be removed later. For now it's likely better to keep it around as
reference to the old behaviour in order to aid in some inevitable
bugfixing.
Ref: #120406
2024-10-15 16:29:53 +02:00
|
|
|
if is_action_editor:
|
2024-10-14 19:21:36 +02:00
|
|
|
row.prop(dopesheet, "show_only_slot_of_active_object", text="")
|
2010-08-27 01:23:53 +00:00
|
|
|
row.prop(dopesheet, "show_only_selected", text="")
|
|
|
|
|
row.prop(dopesheet, "show_hidden", text="")
|
2011-02-04 09:27:25 +00:00
|
|
|
|
Animation Editors - Small Visual Tweaks for Usability
== Datablock filters in the headers are now hidden by default ==
This has been done because users were generally not frequently
toggling these, so quick access vs screen-estate cost wasn't really
worth it to have these always showing and taking up space on the
header.
Usage notes:
- To show these again, click on the "Filter more..." toggle.
- The "Filter more..." button DOES NOT affect whether those filters
apply.
Design notes:
- I tried many other button/icon combinations, but those were either
too space-hogging, vague, or had wrong button order.
- I also tried putting a box around these, but there was too much
padding.
- The ordering of the filters has also been modified a bit so that the
group/fcurve-name filters occur earlier in the list, given that
they're used more frequently
== Graph Editor - Use Fancy Drawing ==
Renamed this option to "Use High Quality Drawing" as suggested by
Matt. "Fancy" isn't really descriptive enough.
== Icons for Mode Dropdowns ==
The mode dropdowns in the DopeSheet and Graph Editors now have icons.
- These were important enough (compared to the auto-snap mode) that
some visual decoration was perhaps warranted.
- It makes it easier to see at a glance what mode the view is in
Icon choices:
- In some cases, the icons seem like quite a natural fit IMO (i.e.
outliner<->dopesheet, key<->shapekey editor, grease pencil, fcurve
editor)
- Action Editor uses an "object" icon to indicate that this is object-
level only for now (though I hope to find a way to address this
soon/later). This will be kept like this until then.
- There isn't any icon for drivers, so after trying a few
alternatives, I settled on area-link icon, since it ties together two
entities using some link.
2011-06-29 13:00:19 +00:00
|
|
|
if is_nla:
|
2011-08-22 02:14:39 +00:00
|
|
|
row.prop(dopesheet, "show_missing_nla", text="")
|
2013-03-11 02:19:58 +00:00
|
|
|
else: # graph and dopesheet editors - F-Curves and drivers only
|
2012-10-10 08:46:07 +00:00
|
|
|
row.prop(dopesheet, "show_only_errors", text="")
|
|
|
|
|
|
2018-06-26 21:12:25 +12:00
|
|
|
#######################################
|
2023-09-05 10:49:20 +10:00
|
|
|
# Dope-sheet Filtering Popovers
|
2018-06-26 21:12:25 +12:00
|
|
|
|
|
|
|
|
# Generic Layout - Used as base for filtering popovers used in all animation editors
|
|
|
|
|
# Used for DopeSheet, NLA, and Graph Editors
|
2018-06-26 22:56:39 +02:00
|
|
|
|
|
|
|
|
|
2018-06-26 21:12:25 +12:00
|
|
|
class DopesheetFilterPopoverBase:
|
|
|
|
|
bl_region_type = 'HEADER'
|
|
|
|
|
bl_label = "Filters"
|
|
|
|
|
|
2023-09-03 21:35:03 +10:00
|
|
|
# Generic = Affects all data-types.
|
2018-06-26 21:12:25 +12:00
|
|
|
# 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')
|
2023-09-05 10:49:20 +10:00
|
|
|
else: # Graph and dope-sheet editors - F-Curves and drivers only.
|
2018-06-26 21:12:25 +12:00
|
|
|
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'
|
|
|
|
|
|
2018-06-26 21:41:08 +12:00
|
|
|
col = layout.column(align=True)
|
2018-06-26 21:12:25 +12:00
|
|
|
if not is_nla:
|
2018-06-26 21:41:08 +12:00
|
|
|
row = col.row(align=True)
|
2018-06-26 21:35:31 +12:00
|
|
|
row.prop(dopesheet, "filter_fcurve_name", text="")
|
2018-06-26 21:12:25 +12:00
|
|
|
else:
|
2018-06-26 21:41:08 +12:00
|
|
|
row = col.row(align=True)
|
2018-06-26 21:35:31 +12:00
|
|
|
row.prop(dopesheet, "filter_text", text="")
|
2018-06-26 21:12:25 +12:00
|
|
|
|
2019-10-30 05:40:36 +11:00
|
|
|
if (not generic_filters_only) and bpy.data.collections:
|
2018-06-26 21:41:08 +12:00
|
|
|
col = layout.column(align=True)
|
|
|
|
|
col.prop(dopesheet, "filter_collection", text="")
|
|
|
|
|
|
2018-06-26 21:12:25 +12:00
|
|
|
# Standard = Present in all panels
|
|
|
|
|
@classmethod
|
|
|
|
|
def draw_standard_filters(cls, context, layout):
|
|
|
|
|
dopesheet = context.space_data.dopesheet
|
|
|
|
|
|
|
|
|
|
# datablock filters
|
2018-10-28 17:51:40 +01:00
|
|
|
layout.label(text="Filter by Type:")
|
2018-07-01 09:23:51 +02:00
|
|
|
flow = layout.grid_flow(row_major=True, columns=2, even_rows=False, align=False)
|
2018-06-26 21:12:25 +12:00
|
|
|
|
|
|
|
|
flow.prop(dopesheet, "show_scenes", text="Scenes")
|
|
|
|
|
flow.prop(dopesheet, "show_nodes", text="Node Trees")
|
|
|
|
|
|
2018-10-28 17:51:40 +01:00
|
|
|
# object types
|
2018-06-26 21:12:25 +12:00
|
|
|
if bpy.data.armatures:
|
|
|
|
|
flow.prop(dopesheet, "show_armatures", text="Armatures")
|
|
|
|
|
if bpy.data.cameras:
|
|
|
|
|
flow.prop(dopesheet, "show_cameras", text="Cameras")
|
2025-09-29 12:32:08 +02:00
|
|
|
if bpy.data.grease_pencils:
|
2018-06-26 21:12:25 +12:00
|
|
|
flow.prop(dopesheet, "show_gpencil", text="Grease Pencil Objects")
|
2018-06-27 14:41:53 +02:00
|
|
|
if bpy.data.lights:
|
|
|
|
|
flow.prop(dopesheet, "show_lights", text="Lights")
|
2018-06-26 21:12:25 +12:00
|
|
|
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")
|
2018-10-28 17:51:40 +01:00
|
|
|
if bpy.data.metaballs:
|
|
|
|
|
flow.prop(dopesheet, "show_metaballs", text="Metaballs")
|
Curves: Rename "Hair" types, variables, and functions to "Curves"
Based on discussions from T95355 and T94193, the plan is to use
the name "Curves" to describe the data-block container for multiple
curves. Eventually this will replace the existing "Curve" data-block.
However, it will be a while before the curve data-block can be replaced
so in order to distinguish the two curve types in the UI, "Hair Curves"
will be used, but eventually changed back to "Curves".
This patch renames "hair-related" files, functions, types, and variable
names to this convention. A deep rename is preferred to keep code
consistent and to avoid any "hair" terminology from leaking, since the
new data-block is meant for all curve types, not just hair use cases.
The downside of this naming is that the difference between "Curve"
and "Curves" has become important. That was considered during
design discussons and deemed acceptable, especially given the
non-permanent nature of the somewhat common conflict.
Some points of interest:
- All DNA compatibility is lost, just like rBf59767ff9729.
- I renamed `ID_HA` to `ID_CV` so there is no complete mismatch.
- `hair_curves` is used where necessary to distinguish from the
existing "curves" plural.
- I didn't rename any of the cycles/rendering code function names,
since that is also used by the old hair particle system.
Differential Revision: https://developer.blender.org/D14007
2022-02-07 11:55:54 -06:00
|
|
|
if hasattr(bpy.data, "hair_curves") and bpy.data.hair_curves:
|
|
|
|
|
flow.prop(dopesheet, "show_hair_curves", text="Hair Curves")
|
2020-03-17 14:41:48 +01:00
|
|
|
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")
|
2025-07-04 15:01:15 +02:00
|
|
|
if bpy.data.lightprobes:
|
|
|
|
|
flow.prop(dopesheet, "show_lightprobes", text="Light Probes")
|
2018-10-28 17:51:40 +01:00
|
|
|
|
|
|
|
|
# data types
|
|
|
|
|
flow.prop(dopesheet, "show_worlds", text="Worlds")
|
|
|
|
|
if bpy.data.particles:
|
|
|
|
|
flow.prop(dopesheet, "show_particles", text="Particles")
|
2018-06-26 21:12:25 +12:00
|
|
|
if bpy.data.linestyles:
|
|
|
|
|
flow.prop(dopesheet, "show_linestyles", text="Line Styles")
|
|
|
|
|
if bpy.data.speakers:
|
|
|
|
|
flow.prop(dopesheet, "show_speakers", text="Speakers")
|
2018-10-28 17:51:40 +01:00
|
|
|
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")
|
2019-01-31 15:42:59 +13:00
|
|
|
if bpy.data.cache_files:
|
|
|
|
|
flow.prop(dopesheet, "show_cache_files", text="Cache Files")
|
2019-07-08 17:27:57 +02:00
|
|
|
if bpy.data.movieclips:
|
|
|
|
|
flow.prop(dopesheet, "show_movieclips", text="Movie Clips")
|
2018-06-26 21:12:25 +12:00
|
|
|
|
|
|
|
|
layout.separator()
|
2018-11-04 10:08:55 +11:00
|
|
|
|
2018-10-28 17:51:40 +01:00
|
|
|
# Object Data Filters
|
|
|
|
|
|
|
|
|
|
# TODO: Add per-channel/axis convenience toggles?
|
|
|
|
|
split = layout.split()
|
|
|
|
|
|
|
|
|
|
col = split.column()
|
|
|
|
|
col.prop(dopesheet, "show_transforms", text="Transforms")
|
2018-11-04 10:08:55 +11:00
|
|
|
|
2018-10-28 17:51:40 +01:00
|
|
|
col = split.column()
|
|
|
|
|
col.prop(dopesheet, "show_modifiers", text="Modifiers")
|
2018-11-04 10:08:55 +11:00
|
|
|
|
2018-10-28 17:51:40 +01:00
|
|
|
layout.separator()
|
2018-06-26 21:12:25 +12:00
|
|
|
|
|
|
|
|
# performance-related options (users will mostly have these enabled)
|
|
|
|
|
col = layout.column(align=True)
|
2018-08-28 12:34:51 +10:00
|
|
|
col.label(text="Options:")
|
2018-06-26 21:12:25 +12:00
|
|
|
col.prop(dopesheet, "use_datablock_sort", icon='NONE')
|
|
|
|
|
|
|
|
|
|
|
2023-09-03 21:35:03 +10:00
|
|
|
# Popover for Dope-sheet Editor(s) - Dope-sheet, Action, Shape-key, GPencil, Mask, etc.
|
2018-06-26 21:12:25 +12:00
|
|
|
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'
|
2024-03-15 10:04:03 +11:00
|
|
|
DopesheetFilterPopoverBase.draw_search_filters(context, layout, generic_filters_only=generic_filters_only)
|
2018-06-26 21:12:25 +12:00
|
|
|
|
|
|
|
|
if ds_mode == 'DOPESHEET':
|
|
|
|
|
layout.separator()
|
|
|
|
|
DopesheetFilterPopoverBase.draw_standard_filters(context, layout)
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2016-08-01 11:54:02 +10:00
|
|
|
|
2011-01-10 22:10:28 +00:00
|
|
|
#######################################
|
|
|
|
|
# DopeSheet Editor - General/Standard UI
|
2010-08-27 01:23:53 +00:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_HT_header(Header):
|
2009-12-08 07:11:43 +00:00
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
st = context.space_data
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2019-04-21 04:49:19 +10:00
|
|
|
layout.template_header()
|
2025-10-02 09:44:53 +02:00
|
|
|
layout.prop(st, "ui_mode", text="")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2025-10-02 09:44:53 +02:00
|
|
|
DOPESHEET_MT_editor_menus.draw_collapsible(context, layout)
|
|
|
|
|
DOPESHEET_HT_editor_buttons.draw_header(context, layout)
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2018-04-19 14:41:20 +02:00
|
|
|
|
|
|
|
|
# Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.)
|
2020-09-17 14:38:45 +10:00
|
|
|
class DOPESHEET_HT_editor_buttons:
|
2018-04-19 14:41:20 +02:00
|
|
|
|
2024-09-02 14:10:49 +02:00
|
|
|
@classmethod
|
|
|
|
|
def draw_header(cls, context, layout):
|
2018-04-19 14:41:20 +02:00
|
|
|
st = context.space_data
|
2018-12-17 17:26:47 +11:00
|
|
|
tool_settings = context.tool_settings
|
2015-04-03 02:10:20 +13:00
|
|
|
|
2025-01-14 17:15:45 +01:00
|
|
|
if st.mode in {'ACTION', 'SHAPEKEY'} and context.object:
|
|
|
|
|
layout.separator_spacer()
|
|
|
|
|
cls._draw_action_selector(context, layout)
|
2024-07-25 17:20:27 +02:00
|
|
|
|
2019-12-04 14:13:21 +01:00
|
|
|
# Layer management
|
|
|
|
|
if st.mode == 'GPENCIL':
|
2020-03-11 16:10:10 +01:00
|
|
|
ob = context.active_object
|
2019-12-04 14:13:21 +01:00
|
|
|
|
2024-06-17 13:50:41 +02:00
|
|
|
enable_but = ob is not None and ob.type == 'GREASEPENCIL'
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.enabled = enable_but
|
|
|
|
|
row.operator("grease_pencil.layer_add", icon='ADD', text="")
|
|
|
|
|
row.operator("grease_pencil.layer_remove", icon='REMOVE', text="")
|
|
|
|
|
row.menu("GREASE_PENCIL_MT_grease_pencil_add_layer_extra", icon='DOWNARROW_HLT', text="")
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.enabled = enable_but
|
|
|
|
|
row.operator("anim.channels_move", icon='TRIA_UP', text="").direction = 'UP'
|
|
|
|
|
row.operator("anim.channels_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.enabled = enable_but
|
|
|
|
|
row.operator("grease_pencil.layer_isolate", icon='RESTRICT_VIEW_ON', text="").affect_visibility = True
|
|
|
|
|
row.operator("grease_pencil.layer_isolate", icon='LOCKED', text="").affect_visibility = False
|
2019-12-04 14:13:21 +01:00
|
|
|
|
2018-06-28 00:49:04 +02:00
|
|
|
layout.separator_spacer()
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
if st.mode == 'DOPESHEET':
|
2010-08-27 01:23:53 +00:00
|
|
|
dopesheet_filter(layout, context)
|
2011-02-01 22:21:43 +00:00
|
|
|
elif st.mode == 'ACTION':
|
2019-05-07 15:09:14 +02:00
|
|
|
dopesheet_filter(layout, context)
|
2015-12-13 21:03:13 +13:00
|
|
|
elif st.mode == 'GPENCIL':
|
|
|
|
|
row = layout.row(align=True)
|
2020-03-11 16:10:10 +01:00
|
|
|
row.prop(st.dopesheet, "show_only_selected", text="")
|
|
|
|
|
row.prop(st.dopesheet, "show_hidden", text="")
|
2010-08-27 01:23:53 +00:00
|
|
|
|
2018-06-28 00:49:04 +02:00
|
|
|
layout.popover(
|
2018-07-13 19:13:44 +02:00
|
|
|
panel="DOPESHEET_PT_filters",
|
2018-06-28 00:49:04 +02:00
|
|
|
text="",
|
|
|
|
|
icon='FILTER',
|
|
|
|
|
)
|
|
|
|
|
|
2018-07-02 15:53:46 +02:00
|
|
|
# Grease Pencil mode doesn't need snapping, as it's frame-aligned only
|
|
|
|
|
if st.mode != 'GPENCIL':
|
2023-09-05 10:06:55 +02:00
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(tool_settings, "use_snap_anim", text="")
|
|
|
|
|
sub = row.row(align=True)
|
|
|
|
|
sub.popover(
|
|
|
|
|
panel="DOPESHEET_PT_snapping",
|
2023-09-26 20:28:57 +02:00
|
|
|
text="",
|
2023-09-05 10:06:55 +02:00
|
|
|
)
|
2018-07-02 15:53:46 +02:00
|
|
|
|
2015-04-07 16:45:29 +02:00
|
|
|
row = layout.row(align=True)
|
2018-12-17 17:26:47 +11:00
|
|
|
row.prop(tool_settings, "use_proportional_action", text="", icon_only=True)
|
2018-06-11 16:57:35 +02:00
|
|
|
sub = row.row(align=True)
|
2018-12-17 17:26:47 +11:00
|
|
|
sub.active = tool_settings.use_proportional_action
|
2023-05-04 16:39:22 +02:00
|
|
|
sub.prop_with_popover(
|
|
|
|
|
tool_settings,
|
|
|
|
|
"proportional_edit_falloff",
|
|
|
|
|
text="",
|
|
|
|
|
icon_only=True,
|
|
|
|
|
panel="DOPESHEET_PT_proportional_edit",
|
|
|
|
|
)
|
2025-10-07 17:34:47 +02:00
|
|
|
overlays = st.overlays
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(overlays, "show_overlays", text="", icon='OVERLAY')
|
|
|
|
|
sub = row.row(align=True)
|
|
|
|
|
sub.popover(panel="DOPESHEET_PT_overlay", text="")
|
|
|
|
|
sub.active = overlays.show_overlays
|
2023-05-04 16:39:22 +02:00
|
|
|
|
2024-09-05 12:02:23 +02:00
|
|
|
@classmethod
|
|
|
|
|
def _draw_action_selector(cls, context, layout):
|
|
|
|
|
animated_id = cls._get_animated_id(context)
|
|
|
|
|
if not animated_id:
|
|
|
|
|
return
|
|
|
|
|
|
2024-09-13 15:54:24 +02:00
|
|
|
row = layout.row()
|
|
|
|
|
if animated_id.animation_data and animated_id.animation_data.use_tweak_mode:
|
|
|
|
|
row.enabled = False
|
|
|
|
|
|
|
|
|
|
row.template_action(animated_id, new="action.new", unlink="action.unlink")
|
2024-09-02 14:10:49 +02:00
|
|
|
|
2024-09-05 12:02:23 +02:00
|
|
|
adt = animated_id and animated_id.animation_data
|
2024-09-02 14:10:49 +02:00
|
|
|
if not adt or not adt.action or not adt.action.is_action_layered:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Store the animated ID in the context, so that the new/unlink operators
|
|
|
|
|
# have access to it.
|
2024-09-13 15:54:24 +02:00
|
|
|
row.context_pointer_set("animated_id", animated_id)
|
|
|
|
|
row.template_search(
|
2024-09-02 14:10:49 +02:00
|
|
|
adt, "action_slot",
|
2024-11-26 11:40:05 +01:00
|
|
|
adt, "action_suitable_slots",
|
2024-09-02 14:10:49 +02:00
|
|
|
new="anim.slot_new_for_id",
|
|
|
|
|
unlink="anim.slot_unassign_from_id",
|
|
|
|
|
)
|
|
|
|
|
|
2024-09-05 12:02:23 +02:00
|
|
|
@staticmethod
|
|
|
|
|
def _get_animated_id(context):
|
|
|
|
|
st = context.space_data
|
|
|
|
|
match st.mode:
|
|
|
|
|
case 'ACTION':
|
|
|
|
|
return context.object
|
|
|
|
|
case 'SHAPEKEY':
|
2024-10-01 10:20:14 +10:00
|
|
|
return getattr(context.object.data, "shape_keys", None)
|
2024-09-05 12:02:23 +02:00
|
|
|
case _:
|
2024-10-01 10:13:40 +10:00
|
|
|
print("Dope Sheet mode '{:s}' not expected to have an Action selector".format(st.mode))
|
2024-09-05 12:02:23 +02:00
|
|
|
return context.object
|
|
|
|
|
|
2023-05-04 16:39:22 +02:00
|
|
|
|
2025-06-19 15:54:53 +02:00
|
|
|
class DOPESHEET_HT_playback_controls(Header):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
|
bl_region_type = 'FOOTER'
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
|
|
|
|
|
|
|
|
|
playback_controls(layout, context)
|
|
|
|
|
|
|
|
|
|
|
2023-09-05 10:06:55 +02:00
|
|
|
class DOPESHEET_PT_snapping(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_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)
|
2023-10-05 13:53:20 +11:00
|
|
|
if tool_settings.snap_anim_element != 'MARKER':
|
2023-09-05 10:06:55 +02:00
|
|
|
col.prop(tool_settings, "use_snap_time_absolute")
|
|
|
|
|
|
|
|
|
|
|
2023-05-04 16:39:22 +02:00
|
|
|
class DOPESHEET_PT_proportional_edit(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_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_action
|
|
|
|
|
|
|
|
|
|
col.prop(tool_settings, "proportional_edit_falloff", expand=True)
|
|
|
|
|
col.prop(tool_settings, "proportional_size")
|
2015-04-07 16:45:29 +02:00
|
|
|
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2014-01-27 18:38:53 +11:00
|
|
|
class DOPESHEET_MT_editor_menus(Menu):
|
|
|
|
|
bl_idname = "DOPESHEET_MT_editor_menus"
|
|
|
|
|
bl_label = ""
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
2018-12-20 12:02:21 +11:00
|
|
|
layout = self.layout
|
2014-01-27 18:38:53 +11:00
|
|
|
st = context.space_data
|
2025-09-08 18:12:18 +02:00
|
|
|
active_action = context.active_action
|
2014-01-27 18:38:53 +11:00
|
|
|
|
|
|
|
|
layout.menu("DOPESHEET_MT_view")
|
|
|
|
|
layout.menu("DOPESHEET_MT_select")
|
2019-11-30 17:03:22 +11:00
|
|
|
if st.show_markers:
|
|
|
|
|
layout.menu("DOPESHEET_MT_marker")
|
2014-01-27 18:38:53 +11:00
|
|
|
|
2025-09-08 18:12:18 +02:00
|
|
|
if st.mode == 'DOPESHEET' or (st.mode == 'ACTION' and active_action is not None):
|
2014-01-27 18:38:53 +11:00
|
|
|
layout.menu("DOPESHEET_MT_channel")
|
|
|
|
|
elif st.mode == 'GPENCIL':
|
|
|
|
|
layout.menu("DOPESHEET_MT_gpencil_channel")
|
|
|
|
|
|
2024-10-04 13:05:09 +02:00
|
|
|
layout.menu("DOPESHEET_MT_key")
|
2014-01-27 18:38:53 +11:00
|
|
|
|
2025-09-08 18:12:18 +02:00
|
|
|
if st.mode in {'ACTION', 'SHAPEKEY'} and active_action is not None:
|
Anim: Remove 'Slotted Actions' experimental flag
This commit takes the 'Slotted Actions' out of the experimental phase.
As a result:
- All newly created Actions will be slotted Actions.
- Legacy Actions loaded from disk will be versioned to slotted Actions.
- The new Python API for slots, layers, strips, and channel bags is
available.
- The legacy Python API for accessing F-Curves and Action Groups is
still available, and will operate on the F-Curves/Groups for the first
slot only.
- Creating an Action by keying (via the UI, operators, or the
`rna_struct.keyframe_insert` function) will try and share Actions
between related data-blocks. See !126655 for more info about this.
- Assigning an Action to a data-block will auto-assign a suitable Action
Slot. The logic for this is described below. However, There are cases
where this does _not_ automatically assign a slot, and thus the Action
will effectively _not_ animate the data-block. Effort has been spent
to make Action selection work both reliably for Blender users as well
as keep the behaviour the same for Python scripts. Where these two
goals did not converge, reliability and understandability for users
was prioritised.
Auto-selection of the Action Slot upon assigning the Action works as
follows. The first rule to find a slot wins.
1. The data-block remembers the slot name that was last assigned. If the
newly assigned Action has a slot with that name, it is chosen.
2. If the Action has a slot with the same name as the data-block, it is
chosen.
3. If the Action has only one slot, and it has never been assigned to
anything, it is chosen.
4. If the Action is assigned to an NLA strip or an Action constraint,
and the Action has a single slot, and that slot has a suitable ID
type, it is chosen.
This last step is what I was referring to with "Where these two goals
did not converge, reliability and understandability for users was
prioritised." For regular Action assignments (like via the Action
selectors in the Properties editor) this rule doesn't apply, even though
with legacy Actions the final state ("it is animated by this Action")
differs from the final state with slotted Actions ("it has no slot so is
not animated"). This is done to support the following workflow:
- Create an Action by animating Cube.
- In order to animate Suzanne with that same Action, assign the Action
to Suzanne.
- Start keying Suzanne. This auto-creates and auto-assigns a new slot
for Suzanne.
If rule 4. above would apply in this case, the 2nd step would
automatically select the Cube slot for Suzanne as well, which would
immediately overwrite Suzanne's properties with the Cube animation.
Technically, this commit:
- removes the `WITH_ANIM_BAKLAVA` build flag,
- removes the `use_animation_baklava` experimental flag in preferences,
- updates the code to properly deal with the fact that empty Actions are
now always considered slotted/layered Actions (instead of that relying
on the user preference).
Note that 'slotted Actions' and 'layered Actions' are the exact same
thing, just focusing on different aspects (slot & layers) of the new
data model.
The "Baklava phase 1" assumptions are still asserted. This means that:
- an Action can have zero or one layer,
- that layer can have zero or one strip,
- that strip must be of type 'keyframe' and be infinite with zero
offset.
The code to handle legacy Actions is NOT removed in this commit. It will
be removed later. For now it's likely better to keep it around as
reference to the old behaviour in order to aid in some inevitable
bugfixing.
Ref: #120406
2024-10-15 16:29:53 +02:00
|
|
|
layout.menu("DOPESHEET_MT_action")
|
2024-09-27 14:34:24 +02:00
|
|
|
|
2014-01-27 18:38:53 +11:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_view(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "View"
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
st = context.space_data
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2019-04-18 12:16:03 +02:00
|
|
|
layout.prop(st, "show_region_ui")
|
2022-09-20 14:36:20 +02:00
|
|
|
layout.prop(st, "show_region_hud")
|
2024-02-05 12:13:31 +01:00
|
|
|
layout.prop(st, "show_region_channels")
|
2025-09-25 12:16:32 +02:00
|
|
|
layout.prop(st, "show_region_footer", text="Playback Controls")
|
2024-01-23 12:11:22 +01:00
|
|
|
layout.separator()
|
2019-04-18 12:16:03 +02:00
|
|
|
|
2024-01-23 12:11:22 +01:00
|
|
|
layout.operator("action.view_selected")
|
|
|
|
|
layout.operator("action.view_all")
|
2024-05-30 17:19:10 +02:00
|
|
|
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")
|
2024-01-23 12:11:22 +01:00
|
|
|
layout.operator("action.view_frame")
|
2016-05-08 14:38:30 +12:00
|
|
|
layout.separator()
|
|
|
|
|
|
2020-10-24 11:42:17 -07:00
|
|
|
layout.prop(st.dopesheet, "use_multi_word_filter", text="Multi-Word Match Search")
|
2018-06-28 00:49:04 +02:00
|
|
|
layout.separator()
|
|
|
|
|
|
2010-08-17 07:49:53 +00:00
|
|
|
layout.prop(st, "use_realtime_update")
|
2020-10-07 12:22:22 +02:00
|
|
|
|
|
|
|
|
# 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")
|
|
|
|
|
|
Dope Sheet: new option to display keyframe interpolation mode and extremes.
With the new automatic handle algorithm, it is possible to do a lot
of the animation via keyframes without touching the curves. It is
however necessary to change the keyframe interpolation and handle
types in certain cases. Currently the dopesheet/action editor
allows changing the types, but does not show them in any way.
To fix, add a new menu option to display this information. For handle
type, it is represented using the shape of the key icons: diamond for
Free, clipped diamond for Aligned, square for Vector, circle for Auto
Clamp, and cirle with dot for Automatic.
Non-bezier interpolation is a property of intervals between keys,
so it is marked by drawing lines, similar to holds. In this initial
version, only the fact of non-bezier interpolation is displayed,
without distinguishing types. For summaries, the line is drawn at
half alpha if not all curves in the group are non-bezier.
In addition, it is sometimes helpful to know the general direction
of change of the curve, and which keys are extremes. This commit
also adds an option to highlight extremes, based on comparing the
keyed values with adjacent keys. Half-intensity display is used
for overshot bezier extremes, or non-uniform summaries.
Reviewers: brecht, aligorith, billreynish
Differential Revision: https://developer.blender.org/D3788
2018-10-19 18:55:19 +03:00
|
|
|
layout.prop(st, "show_interpolation")
|
|
|
|
|
layout.prop(st, "show_extremes")
|
2010-08-17 07:49:53 +00:00
|
|
|
layout.prop(st, "use_auto_merge_keyframes")
|
2019-11-30 17:03:22 +11:00
|
|
|
layout.separator()
|
|
|
|
|
|
2024-01-23 12:11:22 +01:00
|
|
|
layout.prop(st, "show_markers")
|
2012-01-14 14:17:12 +00:00
|
|
|
layout.prop(st, "show_seconds")
|
2014-05-01 14:49:47 +10:00
|
|
|
layout.prop(st, "show_locked_time")
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2024-01-23 12:11:22 +01:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.operator("anim.previewrange_set")
|
|
|
|
|
layout.operator("anim.previewrange_clear")
|
|
|
|
|
layout.operator("action.previewrange_set")
|
|
|
|
|
layout.separator()
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2018-11-29 19:07:00 +11:00
|
|
|
# Add this to show key-binding (reverse action in dope-sheet).
|
2019-03-04 22:06:23 +11:00
|
|
|
props = layout.operator("wm.context_set_enum", text="Toggle Graph Editor", icon='GRAPH')
|
2018-11-29 19:07:00 +11:00
|
|
|
props.data_path = "area.type"
|
|
|
|
|
props.value = 'GRAPH_EDITOR'
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2024-01-23 12:11:22 +01:00
|
|
|
|
2025-06-27 12:23:48 +02:00
|
|
|
layout.menu("DOPESHEET_MT_cache")
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
2018-05-24 18:35:19 +02:00
|
|
|
layout.menu("INFO_MT_area")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
|
|
|
|
|
UI: Introduce View pie in more editors
#### 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
2021-11-10 02:17:24 +01:00
|
|
|
class DOPESHEET_MT_view_pie(Menu):
|
|
|
|
|
bl_label = "View"
|
|
|
|
|
|
2024-05-30 17:19:10 +02:00
|
|
|
def draw(self, context):
|
UI: Introduce View pie in more editors
#### 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
2021-11-10 02:17:24 +01:00
|
|
|
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")
|
2024-05-30 17:19:10 +02:00
|
|
|
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")
|
UI: Introduce View pie in more editors
#### 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
2021-11-10 02:17:24 +01:00
|
|
|
|
|
|
|
|
|
2025-06-27 12:23:48 +02:00
|
|
|
class DOPESHEET_MT_cache(Menu):
|
|
|
|
|
bl_label = "Cache"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
|
|
|
|
|
|
|
|
|
st = context.space_data
|
|
|
|
|
|
|
|
|
|
layout.prop(st, "show_cache")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
|
|
|
|
col = layout.column()
|
|
|
|
|
col.enabled = st.show_cache
|
|
|
|
|
col.prop(st, "cache_softbody")
|
|
|
|
|
col.prop(st, "cache_particles")
|
|
|
|
|
col.prop(st, "cache_cloth")
|
|
|
|
|
col.prop(st, "cache_simulation_nodes")
|
|
|
|
|
col.prop(st, "cache_smoke")
|
|
|
|
|
col.prop(st, "cache_dynamicpaint")
|
|
|
|
|
col.prop(st, "cache_rigidbody")
|
|
|
|
|
|
|
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_select(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Select"
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2018-07-03 15:44:56 +02:00
|
|
|
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'
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2018-10-05 10:27:04 +10:00
|
|
|
layout.operator("action.select_box").axis_range = False
|
2019-08-06 16:10:48 +10:00
|
|
|
layout.operator("action.select_box", text="Box Select (Axis Range)").axis_range = True
|
2016-06-23 23:16:14 +12:00
|
|
|
layout.operator("action.select_circle")
|
2021-09-17 12:09:30 +10:00
|
|
|
layout.operator_menu_enum("action.select_lasso", "mode")
|
2016-06-23 23:16:14 +12:00
|
|
|
|
2024-05-13 16:27:23 +02:00
|
|
|
# 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", text="More")
|
|
|
|
|
layout.operator("action.select_less", text="Less")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("action.select_linked")
|
|
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2011-09-21 15:18:38 +00:00
|
|
|
layout.operator("action.select_column", text="Columns on Selected Keys").mode = 'KEYS'
|
|
|
|
|
layout.operator("action.select_column", text="Column on Current Frame").mode = 'CFRA'
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2011-09-21 15:18:38 +00:00
|
|
|
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'
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-02-14 02:30:33 +00:00
|
|
|
layout.separator()
|
2015-01-29 17:34:05 +01:00
|
|
|
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'
|
2010-04-05 11:47:55 +00:00
|
|
|
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_marker(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Marker"
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-01-06 04:47:57 +00:00
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2019-06-11 16:08:32 +10:00
|
|
|
from bl_ui.space_time import marker_menu_generic
|
2019-03-13 11:52:54 +11:00
|
|
|
marker_menu_generic(layout, context)
|
2011-01-06 04:47:57 +00:00
|
|
|
|
2011-11-03 12:47:39 +00:00
|
|
|
st = context.space_data
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2025-09-08 18:12:18 +02:00
|
|
|
if st.mode in {'ACTION', 'SHAPEKEY'} and context.active_action:
|
2011-01-09 23:16:05 +00:00
|
|
|
layout.separator()
|
|
|
|
|
layout.prop(st, "show_pose_markers")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2011-02-26 06:28:24 +00:00
|
|
|
if st.show_pose_markers is False:
|
|
|
|
|
layout.operator("action.markers_make_local")
|
|
|
|
|
|
2018-11-05 15:11:18 +01:00
|
|
|
layout.prop(st, "use_marker_sync")
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-01-10 22:10:28 +00:00
|
|
|
#######################################
|
|
|
|
|
# Keyframe Editing
|
|
|
|
|
|
2018-12-20 13:01:40 +11:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_channel(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Channel"
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2019-04-19 07:32:24 +02:00
|
|
|
def draw(self, _context):
|
2009-12-08 07:11:43 +00:00
|
|
|
layout = self.layout
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2010-02-07 23:39:44 +00:00
|
|
|
layout.operator_context = 'INVOKE_REGION_CHANNELS'
|
|
|
|
|
|
2010-09-13 00:53:47 +00:00
|
|
|
layout.operator("anim.channels_delete")
|
2023-10-06 13:51:04 +02:00
|
|
|
layout.operator("action.clean", text="Clean Channels").channels = True
|
2010-09-13 00:53:47 +00:00
|
|
|
|
2013-02-22 01:49:51 +00:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_group")
|
|
|
|
|
layout.operator("anim.channels_ungroup")
|
|
|
|
|
|
2010-09-13 00:53:47 +00:00
|
|
|
layout.separator()
|
2012-11-20 02:03:20 +00:00
|
|
|
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")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_editable_toggle")
|
2011-09-21 15:18:38 +00:00
|
|
|
layout.operator_menu_enum("action.extrapolation_type", "type", text="Extrapolation Mode")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_expand")
|
|
|
|
|
layout.operator("anim.channels_collapse")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2010-11-07 12:09:15 +00:00
|
|
|
layout.separator()
|
2011-09-21 15:18:38 +00:00
|
|
|
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2010-11-11 11:56:50 +00:00
|
|
|
layout.separator()
|
2010-11-11 21:49:40 +00:00
|
|
|
layout.operator("anim.channels_fcurves_enable")
|
2010-11-11 11:56:50 +00:00
|
|
|
|
2024-07-04 17:12:04 +02:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_bake")
|
|
|
|
|
|
2023-02-20 11:51:16 +01:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_view_selected")
|
|
|
|
|
|
2010-11-11 11:56:50 +00:00
|
|
|
|
2024-09-27 14:34:24 +02:00
|
|
|
class DOPESHEET_MT_action(Menu):
|
|
|
|
|
bl_label = "Action"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
|
|
|
|
layout.operator("anim.merge_animation")
|
2024-09-27 16:39:29 +02:00
|
|
|
layout.operator("anim.separate_slots")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.slot_channels_move_to_new_action")
|
2024-09-27 14:34:24 +02:00
|
|
|
|
2025-01-14 17:15:45 +01:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("action.push_down", text="Push Down Action", icon='NLA_PUSHDOWN')
|
|
|
|
|
layout.operator("action.stash", text="Stash Action", icon='FREEZE')
|
|
|
|
|
|
2024-09-27 14:34:24 +02:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_key(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Key"
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2025-02-25 14:04:25 +11:00
|
|
|
def draw(self, context):
|
2009-12-08 07:11:43 +00:00
|
|
|
layout = self.layout
|
2025-02-25 14:04:25 +11:00
|
|
|
ob = context.active_object
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2011-09-21 15:18:38 +00:00
|
|
|
layout.menu("DOPESHEET_MT_key_transform", text="Transform")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2011-09-21 15:18:38 +00:00
|
|
|
layout.operator_menu_enum("action.snap", "type", text="Snap")
|
|
|
|
|
layout.operator_menu_enum("action.mirror", "type", text="Mirror")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2009-12-31 23:56:45 +00:00
|
|
|
layout.operator("action.keyframe_insert")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2012-10-15 03:52:27 +00:00
|
|
|
layout.separator()
|
2013-01-15 23:15:32 +00:00
|
|
|
layout.operator("action.frame_jump")
|
|
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2018-10-08 19:10:10 +02:00
|
|
|
layout.operator("action.copy")
|
|
|
|
|
layout.operator("action.paste")
|
|
|
|
|
layout.operator("action.paste", text="Paste Flipped").flipped = True
|
2011-07-13 12:02:39 +00:00
|
|
|
layout.operator("action.duplicate_move")
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.operator("action.delete")
|
2025-02-07 13:36:38 +01:00
|
|
|
if ob and ob.type == 'GREASEPENCIL':
|
|
|
|
|
layout.operator("grease_pencil.delete_breakdown")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2011-09-21 15:18:38 +00:00
|
|
|
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")
|
2020-03-10 14:02:16 +01:00
|
|
|
layout.operator_menu_enum("action.easing_type", "type", text="Easing Mode")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2009-12-08 07:11:43 +00:00
|
|
|
layout.separator()
|
2015-10-13 12:58:04 +02:00
|
|
|
layout.operator("action.clean").channels = False
|
2023-09-12 09:42:01 +02:00
|
|
|
layout.operator("action.bake_keys")
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2022-04-29 14:53:14 +02:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("graph.euler_filter", text="Discontinuity (Euler) Filter")
|
|
|
|
|
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_key_transform(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Transform"
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2019-04-19 07:32:24 +02:00
|
|
|
def draw(self, _context):
|
2009-12-08 07:11:43 +00:00
|
|
|
layout = self.layout
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2018-09-06 12:13:01 +02:00
|
|
|
layout.operator("transform.transform", text="Move").mode = 'TIME_TRANSLATE'
|
2011-09-21 15:18:38 +00:00
|
|
|
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'
|
2009-12-07 21:51:44 +00:00
|
|
|
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2021-05-03 00:03:00 +03:00
|
|
|
class DopesheetActionPanelBase:
|
|
|
|
|
bl_region_type = 'UI'
|
|
|
|
|
bl_label = "Action"
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2022-01-07 14:39:01 +11:00
|
|
|
def draw_generic_panel(cls, _context, layout, action):
|
2024-07-01 22:35:58 +02:00
|
|
|
layout.label(text=action.name, icon='ACTION', translate=False)
|
2021-05-03 00:03:00 +03:00
|
|
|
|
|
|
|
|
layout.prop(action, "use_frame_range")
|
|
|
|
|
|
|
|
|
|
col = layout.column()
|
|
|
|
|
col.active = action.use_frame_range
|
|
|
|
|
|
|
|
|
|
row = col.row(align=True)
|
|
|
|
|
row.prop(action, "frame_start", text="Start")
|
|
|
|
|
row.prop(action, "frame_end", text="End")
|
|
|
|
|
|
2021-07-27 10:39:52 +03:00
|
|
|
col.prop(action, "use_cyclic")
|
|
|
|
|
|
2021-05-03 00:03:00 +03:00
|
|
|
|
2022-06-23 11:11:53 +02:00
|
|
|
class DOPESHEET_PT_custom_props_action(PropertyPanel, Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
2022-06-24 17:08:54 +03:00
|
|
|
bl_category = "Action"
|
2022-06-23 11:11:53 +02:00
|
|
|
bl_region_type = 'UI'
|
2023-01-06 13:52:57 +11:00
|
|
|
bl_context = "data"
|
2022-06-24 17:08:54 +03:00
|
|
|
_context_path = "active_action"
|
2022-06-23 11:11:53 +02:00
|
|
|
_property_type = bpy.types.Action
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
2022-06-24 17:08:54 +03:00
|
|
|
return bool(context.active_action)
|
2022-06-23 11:11:53 +02:00
|
|
|
|
|
|
|
|
|
2021-05-03 00:03:00 +03:00
|
|
|
class DOPESHEET_PT_action(DopesheetActionPanelBase, Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
2022-06-24 17:08:54 +03:00
|
|
|
bl_category = "Action"
|
2021-05-03 00:03:00 +03:00
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
2022-07-10 12:28:44 +03:00
|
|
|
return bool(context.active_action)
|
2021-05-03 00:03:00 +03:00
|
|
|
|
|
|
|
|
def draw(self, context):
|
2022-07-10 12:28:44 +03:00
|
|
|
action = context.active_action
|
2021-05-03 00:03:00 +03:00
|
|
|
self.draw_generic_panel(context, self.layout, action)
|
|
|
|
|
|
|
|
|
|
|
2024-07-12 11:59:04 +02:00
|
|
|
class DOPESHEET_PT_action_slot(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
|
bl_region_type = 'UI'
|
|
|
|
|
bl_category = "Action"
|
|
|
|
|
bl_label = "Slot"
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
action = context.active_action
|
|
|
|
|
return bool(action and action.slots.active)
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
|
|
|
|
layout.use_property_split = True
|
|
|
|
|
layout.use_property_decorate = False
|
|
|
|
|
|
|
|
|
|
action = context.active_action
|
|
|
|
|
slot = action.slots.active
|
|
|
|
|
|
2024-09-26 15:33:44 +02:00
|
|
|
layout.prop(slot, "name_display", text="Name")
|
|
|
|
|
|
|
|
|
|
# Draw the ID type of the slot.
|
|
|
|
|
try:
|
2025-01-20 15:24:08 +01:00
|
|
|
enum_items = slot.bl_rna.properties['target_id_type'].enum_items
|
|
|
|
|
idtype_label = enum_items[slot.target_id_type].name
|
2024-09-26 15:33:44 +02:00
|
|
|
except (KeyError, IndexError, AttributeError) as ex:
|
|
|
|
|
idtype_label = str(ex)
|
|
|
|
|
|
|
|
|
|
split = layout.split(factor=0.4)
|
|
|
|
|
split.alignment = 'RIGHT'
|
|
|
|
|
split.label(text="Type")
|
|
|
|
|
split.alignment = 'LEFT'
|
|
|
|
|
|
2025-01-20 15:24:08 +01:00
|
|
|
split.label(text=idtype_label, icon_value=slot.target_id_type_icon)
|
2024-07-12 11:59:04 +02:00
|
|
|
|
|
|
|
|
|
2011-01-10 22:10:28 +00:00
|
|
|
#######################################
|
|
|
|
|
# Grease Pencil Editing
|
|
|
|
|
|
2011-08-12 06:57:00 +00:00
|
|
|
class DOPESHEET_MT_gpencil_channel(Menu):
|
2011-09-15 13:20:18 +00:00
|
|
|
bl_label = "Channel"
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2019-04-19 07:32:24 +02:00
|
|
|
def draw(self, _context):
|
2011-01-10 22:10:28 +00:00
|
|
|
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")
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2011-01-10 22:10:28 +00:00
|
|
|
# XXX: to be enabled when these are ready for use!
|
2018-06-05 16:32:11 +02:00
|
|
|
# layout.separator()
|
|
|
|
|
# layout.operator("anim.channels_expand")
|
|
|
|
|
# layout.operator("anim.channels_collapse")
|
2011-01-10 22:10:28 +00:00
|
|
|
|
2020-01-04 22:02:03 +01:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator_menu_enum("anim.channels_move", "direction", text="Move...")
|
2011-01-10 22:10:28 +00:00
|
|
|
|
2023-02-20 11:51:16 +01:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_view_selected")
|
|
|
|
|
|
2011-01-13 23:00:51 +00:00
|
|
|
|
2015-04-10 11:52:54 +02:00
|
|
|
class DOPESHEET_MT_delete(Menu):
|
|
|
|
|
bl_label = "Delete"
|
|
|
|
|
|
2019-04-19 07:32:24 +02:00
|
|
|
def draw(self, _context):
|
2015-04-10 11:52:54 +02:00
|
|
|
layout = self.layout
|
|
|
|
|
|
|
|
|
|
layout.operator("action.delete")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
2015-10-13 13:58:43 +02:00
|
|
|
layout.operator("action.clean").channels = False
|
2015-05-20 15:30:33 +02:00
|
|
|
layout.operator("action.clean", text="Clean Channels").channels = True
|
|
|
|
|
|
2015-04-10 11:52:54 +02:00
|
|
|
|
2019-03-12 10:59:57 +11:00
|
|
|
class DOPESHEET_MT_context_menu(Menu):
|
2023-09-25 16:25:55 +02:00
|
|
|
bl_label = "Dope Sheet"
|
2018-06-05 09:12:19 +02:00
|
|
|
|
2021-06-15 10:49:46 +10:00
|
|
|
def draw(self, context):
|
2018-06-05 09:12:19 +02:00
|
|
|
layout = self.layout
|
2021-06-15 10:49:46 +10:00
|
|
|
st = context.space_data
|
2018-06-05 09:12:19 +02:00
|
|
|
|
2019-05-24 12:01:15 +02:00
|
|
|
layout.operator_context = 'INVOKE_DEFAULT'
|
|
|
|
|
|
2019-05-07 20:52:41 +02:00
|
|
|
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
|
2018-06-05 09:12:19 +02:00
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
2019-06-17 18:54:08 +02:00
|
|
|
layout.operator_menu_enum("action.keyframe_type", "type", text="Keyframe Type")
|
2021-03-22 14:22:37 +11:00
|
|
|
|
2021-03-16 17:26:20 +01:00
|
|
|
if st.mode != 'GPENCIL':
|
2021-07-06 12:05:27 +10:00
|
|
|
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")
|
2018-06-05 09:12:19 +02:00
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
|
|
|
|
layout.operator("action.keyframe_insert").type = 'SEL'
|
|
|
|
|
layout.operator("action.duplicate_move")
|
2021-03-16 17:26:20 +01:00
|
|
|
|
|
|
|
|
if st.mode == 'GPENCIL':
|
|
|
|
|
layout.separator()
|
2025-02-07 13:36:38 +01:00
|
|
|
layout.operator("grease_pencil.delete_breakdown")
|
2021-03-16 17:26:20 +01:00
|
|
|
|
2019-05-24 12:01:15 +02:00
|
|
|
layout.operator_context = 'EXEC_REGION_WIN'
|
2018-06-05 09:12:19 +02:00
|
|
|
layout.operator("action.delete")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
|
|
|
|
|
layout.operator_menu_enum("action.mirror", "type", text="Mirror")
|
|
|
|
|
layout.operator_menu_enum("action.snap", "type", text="Snap")
|
|
|
|
|
|
2018-06-05 16:35:20 +02:00
|
|
|
|
2019-03-12 10:59:57 +11:00
|
|
|
class DOPESHEET_MT_channel_context_menu(Menu):
|
2023-12-14 09:09:30 +01:00
|
|
|
bl_label = "Channel"
|
2018-06-05 09:12:19 +02:00
|
|
|
|
2020-03-10 14:18:19 +01:00
|
|
|
def draw(self, context):
|
2018-06-05 09:12:19 +02:00
|
|
|
layout = self.layout
|
|
|
|
|
|
2021-03-22 20:12:08 +11:00
|
|
|
# This menu is used from the graph editor too.
|
|
|
|
|
is_graph_editor = context.area.type == 'GRAPH_EDITOR'
|
|
|
|
|
|
2023-08-23 09:40:27 +02:00
|
|
|
layout.operator_context = 'INVOKE_REGION_CHANNELS'
|
|
|
|
|
|
2023-02-20 11:51:16 +01:00
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_view_selected")
|
|
|
|
|
|
2018-06-05 16:35:20 +02:00
|
|
|
layout.operator("anim.channels_setting_enable", text="Mute Channels").type = 'MUTE'
|
|
|
|
|
layout.operator("anim.channels_setting_disable", text="Unmute Channels").type = 'MUTE'
|
2018-06-05 09:12:19 +02:00
|
|
|
layout.separator()
|
2018-06-05 16:35:20 +02:00
|
|
|
layout.operator("anim.channels_setting_enable", text="Protect Channels").type = 'PROTECT'
|
|
|
|
|
layout.operator("anim.channels_setting_disable", text="Unprotect Channels").type = 'PROTECT'
|
2018-06-05 09:12:19 +02:00
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_group")
|
|
|
|
|
layout.operator("anim.channels_ungroup")
|
|
|
|
|
|
|
|
|
|
layout.separator()
|
|
|
|
|
layout.operator("anim.channels_editable_toggle")
|
2020-03-10 14:18:19 +01:00
|
|
|
|
2021-03-22 20:12:08 +11:00
|
|
|
if is_graph_editor:
|
2020-03-10 14:18:19 +01:00
|
|
|
operator = "graph.extrapolation_type"
|
|
|
|
|
else:
|
|
|
|
|
operator = "action.extrapolation_type"
|
|
|
|
|
layout.operator_menu_enum(operator, "type", text="Extrapolation Mode")
|
2018-06-05 09:12:19 +02:00
|
|
|
|
2023-02-03 13:06:15 +01:00
|
|
|
if is_graph_editor:
|
|
|
|
|
layout.operator_menu_enum("graph.fmodifier_add", "type", text="Add F-Curve Modifier").only_active = False
|
2023-09-27 09:29:44 +02:00
|
|
|
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")
|
2023-02-03 13:06:15 +01:00
|
|
|
|
2018-06-05 09:12:19 +02:00
|
|
|
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")
|
|
|
|
|
|
2023-09-27 09:29:44 +02:00
|
|
|
if is_graph_editor and context.space_data.mode == 'DRIVERS':
|
|
|
|
|
layout.operator("graph.driver_delete_invalid")
|
|
|
|
|
|
2018-06-05 09:12:19 +02:00
|
|
|
|
2018-09-26 17:32:11 +02:00
|
|
|
class DOPESHEET_MT_snap_pie(Menu):
|
|
|
|
|
bl_label = "Snap"
|
|
|
|
|
|
2019-04-19 07:32:24 +02:00
|
|
|
def draw(self, _context):
|
2018-09-26 17:32:11 +02:00
|
|
|
layout = self.layout
|
|
|
|
|
pie = layout.menu_pie()
|
|
|
|
|
|
2020-10-28 14:06:26 -04:00
|
|
|
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'
|
2018-09-26 17:32:11 +02:00
|
|
|
|
2019-12-16 14:29:03 +11:00
|
|
|
|
2024-04-29 15:14:31 +02:00
|
|
|
class GreasePencilLayersDopeSheetPanel:
|
|
|
|
|
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 != 'GREASEPENCIL':
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
grease_pencil = ob.data
|
2024-07-23 11:47:56 +02:00
|
|
|
active_layer = grease_pencil.layers.active
|
2024-04-29 15:14:31 +02:00
|
|
|
if active_layer:
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOPESHEET_PT_grease_pencil_mode(GreasePencilLayersDopeSheetPanel, Panel):
|
|
|
|
|
bl_label = "Layer"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
layout = self.layout
|
|
|
|
|
layout.use_property_split = True
|
|
|
|
|
layout.use_property_decorate = False
|
|
|
|
|
|
|
|
|
|
ob = context.object
|
|
|
|
|
grease_pencil = ob.data
|
2024-07-23 11:47:56 +02:00
|
|
|
active_layer = grease_pencil.layers.active
|
2024-04-29 15:14:31 +02:00
|
|
|
|
|
|
|
|
if active_layer:
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(active_layer, "blend_mode", text="Blend")
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(active_layer, "opacity", text="Opacity", slider=True)
|
|
|
|
|
|
|
|
|
|
row = layout.row(align=True)
|
|
|
|
|
row.prop(active_layer, "use_lights", text="Lights")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOPESHEET_PT_grease_pencil_layer_masks(GreasePencilLayersDopeSheetPanel, GreasePencil_LayerMaskPanel, Panel):
|
|
|
|
|
bl_label = "Masks"
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
|
|
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOPESHEET_PT_grease_pencil_layer_transform(
|
|
|
|
|
GreasePencilLayersDopeSheetPanel,
|
|
|
|
|
GreasePencil_LayerTransformPanel,
|
2025-01-14 12:46:40 +11:00
|
|
|
Panel,
|
|
|
|
|
):
|
2024-04-29 15:14:31 +02:00
|
|
|
bl_label = "Transform"
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
|
|
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOPESHEET_PT_grease_pencil_layer_relations(
|
|
|
|
|
GreasePencilLayersDopeSheetPanel,
|
2024-11-15 11:59:25 +01:00
|
|
|
GreasePencil_LayerRelationsPanel,
|
2025-01-14 12:46:40 +11:00
|
|
|
Panel,
|
|
|
|
|
):
|
2024-04-29 15:14:31 +02:00
|
|
|
bl_label = "Relations"
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
|
|
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
|
|
|
|
|
2024-11-15 12:10:28 +01:00
|
|
|
class DOPESHEET_PT_grease_pencil_layer_adjustments(
|
|
|
|
|
GreasePencilLayersDopeSheetPanel,
|
|
|
|
|
GreasePencil_LayerAdjustmentsPanel,
|
2025-01-14 12:46:40 +11:00
|
|
|
Panel,
|
|
|
|
|
):
|
2024-11-15 12:10:28 +01:00
|
|
|
bl_label = "Adjustments"
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
|
|
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
|
|
|
|
|
2024-12-09 14:11:29 +01:00
|
|
|
class DOPESHEET_PT_grease_pencil_layer_display(
|
|
|
|
|
GreasePencilLayersDopeSheetPanel,
|
|
|
|
|
GreasePencil_LayerDisplayPanel,
|
2025-01-14 12:46:40 +11:00
|
|
|
Panel,
|
|
|
|
|
):
|
2024-12-09 14:11:29 +01:00
|
|
|
bl_label = "Display"
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_grease_pencil_mode"
|
|
|
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
|
|
|
|
|
2025-09-09 11:38:19 +02:00
|
|
|
class DOPESHEET_PT_ShapeKey(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
|
bl_region_type = 'UI'
|
|
|
|
|
bl_category = "Shape Key"
|
|
|
|
|
bl_label = "Shape Key"
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poll(cls, context):
|
|
|
|
|
st = context.space_data
|
|
|
|
|
if st.mode != 'SHAPEKEY':
|
|
|
|
|
return False
|
|
|
|
|
|
2025-09-10 11:31:23 +10:00
|
|
|
ob = context.object
|
|
|
|
|
if ob is None or ob.active_shape_key is None:
|
2025-09-09 11:38:19 +02:00
|
|
|
return False
|
|
|
|
|
|
2025-09-10 11:31:23 +10:00
|
|
|
if not ob.data.shape_keys.use_relative:
|
2025-09-09 11:38:19 +02:00
|
|
|
return False
|
|
|
|
|
|
2025-09-10 11:31:23 +10:00
|
|
|
return ob.active_shape_key_index > 0
|
2025-09-09 11:38:19 +02:00
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
draw_shape_key_properties(context, self.layout)
|
|
|
|
|
|
|
|
|
|
|
2025-10-07 17:34:47 +02:00
|
|
|
class DOPESHEET_PT_overlay(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
|
bl_region_type = 'HEADER'
|
|
|
|
|
bl_label = "Overlays"
|
|
|
|
|
bl_ui_units_x = 13
|
|
|
|
|
|
|
|
|
|
def draw(self, _context):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOPESHEET_PT_dopesheet_overlay(Panel):
|
|
|
|
|
bl_space_type = 'DOPESHEET_EDITOR'
|
|
|
|
|
bl_region_type = 'HEADER'
|
|
|
|
|
bl_parent_id = "DOPESHEET_PT_overlay"
|
|
|
|
|
bl_label = "Dope Sheet Overlays"
|
|
|
|
|
|
|
|
|
|
def draw(self, context):
|
|
|
|
|
st = context.space_data
|
|
|
|
|
overlay_settings = st.overlays
|
|
|
|
|
layout = self.layout
|
|
|
|
|
|
|
|
|
|
layout.active = overlay_settings.show_overlays
|
|
|
|
|
row = layout.row()
|
|
|
|
|
row.active = context.workspace.use_scene_time_sync
|
|
|
|
|
row.prop(overlay_settings, "show_scene_strip_range")
|
|
|
|
|
|
|
|
|
|
|
2017-03-18 20:03:24 +11:00
|
|
|
classes = (
|
|
|
|
|
DOPESHEET_HT_header,
|
2025-06-19 15:54:53 +02:00
|
|
|
DOPESHEET_HT_playback_controls,
|
2023-05-04 16:39:22 +02:00
|
|
|
DOPESHEET_PT_proportional_edit,
|
2017-03-18 20:03:24 +11:00
|
|
|
DOPESHEET_MT_editor_menus,
|
2017-03-20 02:34:32 +11:00
|
|
|
DOPESHEET_MT_view,
|
2025-06-27 12:23:48 +02:00
|
|
|
DOPESHEET_MT_cache,
|
2017-03-20 02:34:32 +11:00
|
|
|
DOPESHEET_MT_select,
|
|
|
|
|
DOPESHEET_MT_marker,
|
|
|
|
|
DOPESHEET_MT_channel,
|
2024-09-27 14:34:24 +02:00
|
|
|
DOPESHEET_MT_action,
|
2017-03-18 20:03:24 +11:00
|
|
|
DOPESHEET_MT_key,
|
|
|
|
|
DOPESHEET_MT_key_transform,
|
2017-03-20 02:34:32 +11:00
|
|
|
DOPESHEET_MT_gpencil_channel,
|
|
|
|
|
DOPESHEET_MT_delete,
|
2019-03-12 10:59:57 +11:00
|
|
|
DOPESHEET_MT_context_menu,
|
|
|
|
|
DOPESHEET_MT_channel_context_menu,
|
2018-09-26 17:32:11 +02:00
|
|
|
DOPESHEET_MT_snap_pie,
|
UI: Introduce View pie in more editors
#### 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
2021-11-10 02:17:24 +01:00
|
|
|
DOPESHEET_MT_view_pie,
|
2018-06-26 21:12:25 +12:00
|
|
|
DOPESHEET_PT_filters,
|
2021-05-03 00:03:00 +03:00
|
|
|
DOPESHEET_PT_action,
|
2024-07-12 11:59:04 +02:00
|
|
|
DOPESHEET_PT_action_slot,
|
2022-06-23 11:11:53 +02:00
|
|
|
DOPESHEET_PT_custom_props_action,
|
2024-04-29 15:14:31 +02:00
|
|
|
DOPESHEET_PT_snapping,
|
|
|
|
|
DOPESHEET_PT_grease_pencil_mode,
|
|
|
|
|
DOPESHEET_PT_grease_pencil_layer_masks,
|
|
|
|
|
DOPESHEET_PT_grease_pencil_layer_transform,
|
2024-11-15 12:10:28 +01:00
|
|
|
DOPESHEET_PT_grease_pencil_layer_adjustments,
|
2024-04-29 15:14:31 +02:00
|
|
|
DOPESHEET_PT_grease_pencil_layer_relations,
|
2024-12-09 14:11:29 +01:00
|
|
|
DOPESHEET_PT_grease_pencil_layer_display,
|
2025-09-09 11:38:19 +02:00
|
|
|
DOPESHEET_PT_ShapeKey,
|
2025-10-07 17:34:47 +02:00
|
|
|
|
|
|
|
|
DOPESHEET_PT_overlay,
|
|
|
|
|
DOPESHEET_PT_dopesheet_overlay,
|
2017-03-18 20:03:24 +11:00
|
|
|
)
|
|
|
|
|
|
2011-04-04 10:13:04 +00:00
|
|
|
if __name__ == "__main__": # only for live edit.
|
2017-03-18 20:03:24 +11:00
|
|
|
from bpy.utils import register_class
|
|
|
|
|
for cls in classes:
|
|
|
|
|
register_class(cls)
|