diff --git a/scripts/startup/bl_operators/anim.py b/scripts/startup/bl_operators/anim.py index 9aaff27101d..3297cadf148 100644 --- a/scripts/startup/bl_operators/anim.py +++ b/scripts/startup/bl_operators/anim.py @@ -668,6 +668,69 @@ class ARMATURE_OT_collection_remove_unused(Operator): ) +class ANIM_OT_slot_new_for_id(Operator): + """Create a new Action Slot for an ID. + + Note that _which_ ID should get this slot must be set in the 'animated_id' context pointer, using: + + >>> layout.context_pointer_set("animated_id", animated_id) + """ + bl_idname = "anim.slot_new_for_id" + bl_label = "New Slot" + bl_description = "Create a new action slot for this data-block, to hold its animation" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + animated_id = getattr(context, 'animated_id', None) + if not animated_id: + return False + if not animated_id.animation_data or not animated_id.animation_data.action: + cls.poll_message_set("An action slot can only be created when an action was assigned") + return False + if not animated_id.animation_data.action.is_action_layered: + cls.poll_message_set("Action slots are only supported by layered Actions. Upgrade this Action first") + return False + return True + + def execute(self, context): + animated_id = getattr(context, 'animated_id', None) + + action = animated_id.animation_data.action + slot = action.slots.new(for_id=animated_id) + animated_id.animation_data.action_slot = slot + return {'FINISHED'} + + +class ANIM_OT_slot_unassign_from_id(Operator): + """Un-assign the assigned Action Slot from an ID. + + Note that _which_ ID should get this slot unassigned must be set in the + 'animated_id' context pointer, using: + + >>> layout.context_pointer_set("animated_id", animated_id) + """ + bl_idname = "anim.slot_unassign_from_id" + bl_label = "Unassign Slot" + bl_description = "Un-assign the action slot, effectively making this data-block non-animated" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + animated_id = getattr(context, 'animated_id', None) + if not animated_id: + return False + if not animated_id.animation_data or not animated_id.animation_data.action_slot: + cls.poll_message_set("This data-block has no Action slot assigned") + return False + return True + + def execute(self, context): + animated_id = getattr(context, 'animated_id', None) + animated_id.animation_data.action_slot = None + return {'FINISHED'} + + classes = ( ANIM_OT_keying_set_export, NLA_OT_bake, @@ -677,4 +740,6 @@ classes = ( ARMATURE_OT_collection_show_all, ARMATURE_OT_collection_unsolo_all, ARMATURE_OT_collection_remove_unused, + ANIM_OT_slot_new_for_id, + ANIM_OT_slot_unassign_from_id, ) diff --git a/scripts/startup/bl_ui/space_dopesheet.py b/scripts/startup/bl_ui/space_dopesheet.py index d7619ff8a2d..934a5b240a0 100644 --- a/scripts/startup/bl_ui/space_dopesheet.py +++ b/scripts/startup/bl_ui/space_dopesheet.py @@ -225,8 +225,8 @@ class DOPESHEET_HT_header(Header): # Header for "normal" dopesheet editor modes (e.g. Dope Sheet, Action, Shape Keys, etc.) class DOPESHEET_HT_editor_buttons: - @staticmethod - def draw_header(context, layout): + @classmethod + def draw_header(cls, context, layout): st = context.space_data tool_settings = context.tool_settings @@ -243,19 +243,7 @@ class DOPESHEET_HT_editor_buttons: if context.object: layout.separator_spacer() - layout.template_action(context.object, new="action.new", unlink="action.unlink") - - # Show slot selector. - if context.preferences.experimental.use_animation_baklava: - # context.space_data.action comes from the active object. - adt = context.object and context.object.animation_data - if adt and st.action and st.action.is_action_layered: - layout.template_search( - adt, "action_slot", - adt, "action_slots", - new="anim.slot_new_for_object", - unlink="anim.slot_unassign_object", - ) + cls._draw_action_selector(context, layout) # Layer management if st.mode == 'GPENCIL': @@ -318,6 +306,27 @@ class DOPESHEET_HT_editor_buttons: panel="DOPESHEET_PT_proportional_edit", ) + @staticmethod + def _draw_action_selector(context, layout): + layout.template_action(context.object, new="action.new", unlink="action.unlink") + + if not context.preferences.experimental.use_animation_baklava: + return + + adt = context.object and context.object.animation_data + 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. + layout.context_pointer_set("animated_id", context.object) + layout.template_search( + adt, "action_slot", + adt, "action_slots", + new="anim.slot_new_for_id", + unlink="anim.slot_unassign_from_id", + ) + class DOPESHEET_PT_snapping(Panel): bl_space_type = 'DOPESHEET_EDITOR' diff --git a/scripts/startup/bl_ui/space_properties.py b/scripts/startup/bl_ui/space_properties.py index e6dca79bcd9..47ed112fcb3 100644 --- a/scripts/startup/bl_ui/space_properties.py +++ b/scripts/startup/bl_ui/space_properties.py @@ -125,11 +125,12 @@ class PropertiesAnimationMixin: # 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_slots", - new="", # TODO: add this operator. - unlink="", # TODO: add this operator. + new="anim.slot_new_for_id", + unlink="anim.slot_unassign_from_id", ) diff --git a/source/blender/editors/animation/anim_ops.cc b/source/blender/editors/animation/anim_ops.cc index 182f1b845f0..dc0a20a93b7 100644 --- a/source/blender/editors/animation/anim_ops.cc +++ b/source/blender/editors/animation/anim_ops.cc @@ -715,7 +715,7 @@ static void ANIM_OT_scene_range_frame(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Slots +/** \name Conversion * \{ */ static bool slot_new_for_object_poll(bContext *C) @@ -770,49 +770,6 @@ static void ANIM_OT_slot_new_for_object(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -static bool slot_unassign_object_poll(bContext *C) -{ - Object *object = CTX_data_active_object(C); - if (!object) { - return false; - } - - AnimData *adt = BKE_animdata_from_id(&object->id); - if (!adt) { - return false; - } - - return adt->slot_handle != blender::animrig::Slot::unassigned; -} - -static int slot_unassign_object_exec(bContext *C, wmOperator * /*op*/) -{ - using namespace blender; - - Object *object = CTX_data_active_object(C); - animrig::unassign_slot(object->id); - - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr); - return OPERATOR_FINISHED; -} - -static void ANIM_OT_slot_unassign_object(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Unassign Slot"; - ot->idname = "ANIM_OT_slot_unassign_object"; - ot->description = - "Clear the assigned action slot, effectively making this data-block non-animated"; - - /* api callbacks */ - ot->exec = slot_unassign_object_exec; - ot->poll = slot_unassign_object_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - static int convert_action_exec(bContext *C, wmOperator * /*op*/) { using namespace blender; @@ -926,7 +883,6 @@ void ED_operatortypes_anim() WM_operatortype_append(ANIM_OT_keying_set_active_set); WM_operatortype_append(ANIM_OT_slot_new_for_object); - WM_operatortype_append(ANIM_OT_slot_unassign_object); WM_operatortype_append(ANIM_OT_convert_legacy_action); }