Anim: New/Un-assign Slot operators
All checks were successful
buildbot/vdev-code-daily-lint Build done.
buildbot/vdev-code-daily-darwin-x86_64 Build done.
buildbot/vdev-code-daily-darwin-arm64 Build done.
buildbot/vdev-code-daily-windows-amd64 Build done.
buildbot/vdev-code-daily-linux-x86_64 Build done.
buildbot/vdev-code-daily-coordinator Build done.

Add two new operators, `anim.slot_new_for_id` and
`anim.slot_unassign_from_id`. These are used in the Action editor and
the Animation panels in the Properties editor, to respectively create a
new Action Slot for an ID and to unassign whatever slot is currently
assigned to that ID.

The latter operator also replaces the C++ operator
`anim.slot_unassign_object`, which was specifically made for the
Dopesheet header. The Python ones are generic enough to be used there
too.

Pull Request: #126943
This commit is contained in:
Sybren A. Stüvel 2024-09-02 14:10:49 +02:00
parent e143c0baee
commit a6d7e12e22
4 changed files with 93 additions and 62 deletions

View File

@ -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,
)

View File

@ -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'

View File

@ -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",
)

View File

@ -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);
}