From b2a06888c797a8a6cb93913b4f2e16c997cda391 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Tue, 21 Jan 2025 15:12:59 +0100 Subject: [PATCH] Anim: add Action+Slot selector for Masks This adds an Animation panel to the Mask tab of the n-panel of the Movie Clip and Image editors. Masks can be animated (for example, the opacity of a Mask Layer), but there was no way to manage the Action and Slot that held those F-Curves. To keep things DRY, this PR also moves the code for drawing Action+Slot selectors from the `PropertiesAnimationMixin` class to a utility function, which is now called from both that class and the Mask UI code. Pull Request: https://projects.blender.org/blender/blender/pulls/133153 --- scripts/startup/bl_ui/anim.py | 27 +++++++++++++++++++ .../startup/bl_ui/properties_mask_common.py | 26 ++++++++++++++++++ scripts/startup/bl_ui/space_clip.py | 8 ++++++ scripts/startup/bl_ui/space_image.py | 8 ++++++ scripts/startup/bl_ui/space_properties.py | 17 ++---------- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/scripts/startup/bl_ui/anim.py b/scripts/startup/bl_ui/anim.py index b91f66e871f..be5a40d8a0c 100644 --- a/scripts/startup/bl_ui/anim.py +++ b/scripts/startup/bl_ui/anim.py @@ -5,6 +5,33 @@ from bpy.types import Menu +def draw_action_and_slot_selector_for_id(layout, animated_id): + """ + Draw the action and slot selector for an ID, using the given layout. + + The ID must be an animatable ID. + + Note that the slot selector is only drawn when the ID has an assigned + Action. + """ + + layout.template_action(animated_id, new="action.new", unlink="action.unlink") + + adt = animated_id.animation_data + if not adt or not adt.action: + return + + # Only show the slot selector when a layered Action is assigned. + if adt.action.is_action_layered: + layout.context_pointer_set("animated_id", animated_id) + layout.template_search( + adt, "action_slot", + adt, "action_suitable_slots", + new="anim.slot_new_for_id", + unlink="anim.slot_unassign_from_id", + ) + + class ANIM_MT_keyframe_insert_pie(Menu): bl_label = "Keyframe Insert Pie" diff --git a/scripts/startup/bl_ui/properties_mask_common.py b/scripts/startup/bl_ui/properties_mask_common.py index 7ab0952baa0..a84024e9d8e 100644 --- a/scripts/startup/bl_ui/properties_mask_common.py +++ b/scripts/startup/bl_ui/properties_mask_common.py @@ -7,6 +7,7 @@ from bpy.types import Menu, UIList from bpy.app.translations import contexts as i18n_contexts +from . import anim # Use by both image & clip context menus. @@ -222,6 +223,31 @@ class MASK_PT_point: ) +class MASK_PT_animation: + # subclasses must define... + # ~ bl_space_type = 'CLIP_EDITOR' + # ~ bl_region_type = 'UI' + bl_label = "Animation" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + space_data = context.space_data + return space_data.mask and space_data.mode == 'MASK' + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + # poll() ensures this is not None. + sc = context.space_data + mask = sc.mask + + col = layout.column(align=True) + anim.draw_action_and_slot_selector_for_id(col, mask) + + class MASK_PT_display: # subclasses must define... # ~ bl_space_type = 'CLIP_EDITOR' diff --git a/scripts/startup/bl_ui/space_clip.py b/scripts/startup/bl_ui/space_clip.py index a42f657b11a..7b452659a14 100644 --- a/scripts/startup/bl_ui/space_clip.py +++ b/scripts/startup/bl_ui/space_clip.py @@ -1191,6 +1191,7 @@ from bl_ui.properties_mask_common import ( MASK_PT_layers, MASK_PT_spline, MASK_PT_point, + MASK_PT_animation, MASK_PT_display, MASK_PT_transforms, MASK_PT_tools, @@ -1215,6 +1216,12 @@ class CLIP_PT_active_mask_point(MASK_PT_point, Panel): bl_category = "Mask" +class CLIP_PT_mask_animation(MASK_PT_animation, Panel): + bl_space_type = 'CLIP_EDITOR' + bl_region_type = 'UI' + bl_category = "Mask" + + class CLIP_PT_mask(MASK_PT_mask, Panel): bl_space_type = 'CLIP_EDITOR' bl_region_type = 'UI' @@ -1996,6 +2003,7 @@ classes = ( CLIP_PT_mask_display, CLIP_PT_active_mask_spline, CLIP_PT_active_mask_point, + CLIP_PT_mask_animation, CLIP_PT_tools_mask_transforms, CLIP_PT_tools_mask_tools, CLIP_PT_tools_scenesetup, diff --git a/scripts/startup/bl_ui/space_image.py b/scripts/startup/bl_ui/space_image.py index 43542507f42..a39c003473a 100644 --- a/scripts/startup/bl_ui/space_image.py +++ b/scripts/startup/bl_ui/space_image.py @@ -987,6 +987,7 @@ from bl_ui.properties_mask_common import ( MASK_PT_layers, MASK_PT_spline, MASK_PT_point, + MASK_PT_animation, MASK_PT_display, ) @@ -1015,6 +1016,12 @@ class IMAGE_PT_active_mask_point(MASK_PT_point, Panel): bl_category = "Mask" +class IMAGE_PT_mask_animation(MASK_PT_animation, Panel): + bl_space_type = 'IMAGE_EDITOR' + bl_region_type = 'UI' + bl_category = "Mask" + + class IMAGE_PT_mask_display(MASK_PT_display, Panel): bl_space_type = 'IMAGE_EDITOR' bl_region_type = 'HEADER' @@ -1771,6 +1778,7 @@ classes = ( IMAGE_PT_mask_display, IMAGE_PT_active_mask_spline, IMAGE_PT_active_mask_point, + IMAGE_PT_mask_animation, IMAGE_PT_snapping, IMAGE_PT_proportional_edit, IMAGE_PT_image_properties, diff --git a/scripts/startup/bl_ui/space_properties.py b/scripts/startup/bl_ui/space_properties.py index 01de5bde72c..9213edfa8bb 100644 --- a/scripts/startup/bl_ui/space_properties.py +++ b/scripts/startup/bl_ui/space_properties.py @@ -4,6 +4,7 @@ from bpy.types import Header, Panel from rna_prop_ui import PropertyPanel +from . import anim class PROPERTIES_HT_header(Header): @@ -122,21 +123,7 @@ class PropertiesAnimationMixin: layout.label(text="No animatable data-block, please report as bug", icon='ERROR') return - layout.template_action(animated_id, new="action.new", unlink="action.unlink") - - adt = animated_id.animation_data - if not adt or not adt.action: - return - - # Only show the slot selector when a layered Action is assigned. - if adt.action.is_action_layered: - layout.context_pointer_set("animated_id", animated_id) - layout.template_search( - adt, "action_slot", - adt, "action_suitable_slots", - new="anim.slot_new_for_id", - unlink="anim.slot_unassign_from_id", - ) + anim.draw_action_and_slot_selector_for_id(layout, animated_id) classes = (