Anim: when creating action slot by keying, name it after last-used slot

When a new Action slot is created by keying a property, it is now
named after the last-assigned slot. This is in support of the
following scenario:

- Action `A` is assigned, with slot `Legacy Slot`.
- The slot is renamed to `Main Light`, because that's what being
  animated by it.
- Animator wants to try an alternative animation, and unassigns the
  Action.
- Animator starts keying the light, which creates Action `B` and a
  slot.
- This slot is now also named `Main Light`, independently of the
  actual name of the light being animated.
- Animator can switch between actions `A` and `B`, and because the
  slots have the same name, the auto-assignment Just Works™.

Pull Request: https://projects.blender.org/blender/blender/pulls/131600
This commit is contained in:
Sybren A. Stüvel
2024-12-09 12:57:06 +01:00
parent b68be2aedd
commit e909a17554
3 changed files with 38 additions and 3 deletions

View File

@@ -210,7 +210,11 @@ class Action : public ::bAction {
Slot &slot_add_for_id_type(ID_Type idtype);
/**
* Create a new slot, named after the given ID, and limited to the ID's type.
* Create a new slot suitable for the ID's type.
*
* The slot will be named after `animated_id.adt.last_slot_identifier`, defaulting to the ID's
* name when that is not set. This is done so that toggling Actions works transparently, when
* toggling between `this` and the Action last assigned to the ID.
*
* Note that this assigns neither this Action nor the new Slot to the ID. This function
* merely initializes the Slot itself to suitable values to start animating this ID.

View File

@@ -529,13 +529,28 @@ Slot &Action::slot_add_for_id_type(const ID_Type idtype)
Slot &Action::slot_add_for_id(const ID &animated_id)
{
Slot &slot = this->slot_add();
slot.idtype = GS(animated_id.name);
this->slot_identifier_define(slot, animated_id.name);
/* Determine the identifier for this slot, prioritising transparent
* auto-selection when toggling between Actions. That's why the last-used slot
* identifier is used here, and the ID name only as fallback. */
const AnimData *adt = BKE_animdata_from_id(&animated_id);
const StringRefNull last_slot_identifier = adt ? adt->last_slot_identifier : "";
StringRefNull slot_identifier = last_slot_identifier;
if (slot_identifier.is_empty()) {
slot_identifier = animated_id.name;
}
this->slot_identifier_define(slot, slot_identifier);
/* No need to call anim.slot_identifier_propagate() as nothing will be using
* this brand new Slot yet. */
/* The last-used slot might have had a different ID type through some quirk (changes to linked
* data, for example). So better ensure that the identifier prefix is correct on this new slot,
* instead of relying for 100% on the old one. */
slot.identifier_ensure_prefix();
return slot;
}

View File

@@ -302,6 +302,22 @@ TEST_F(ActionLayersTest, add_slot)
EXPECT_STREQ(cube->id.name, slot.identifier);
EXPECT_EQ(ID_OB, slot.idtype);
}
{ /* Creating a Slot for a specific ID that already had a slot assigned before should name it
* after that previous slot. This should also ensure that the first two characters are actually
* correct for the ID type. */
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
STRNCPY_UTF8(adt->last_slot_identifier, "$$Kübuš 😹");
Slot &slot = action->slot_add_for_id(cube->id);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 3, action->last_slot_handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 3, slot.handle);
EXPECT_STREQ("Kübuš 😹", slot.identifier + 2)
<< "The last-assigned slot name should be reused";
EXPECT_STREQ("OBKübuš 😹", slot.identifier)
<< "The ID type encoded in the slot identifier should be correct";
EXPECT_EQ(ID_OB, slot.idtype);
}
}
TEST_F(ActionLayersTest, add_slot__reset_idroot)