Anim: treat untyped slot identifiers ("XXSlot") as wildcard

When assigning an Action to an ID, a slot can be automatically
assigned as well. This behaviour is now extended by making untyped
slot identifiers (like `XXSlot`) act as wildcards.

If the last-used slot identifier was 'untyped' (like `XXSlot`), and a
slot with the same name that is specific to the animated ID's type
exists, that slot will be chosen.

Similarly, if the last-used slot identifier was 'typed' (like
`OBSlot`), a slot `OBSlot` does NOT exist, but an untyped slot with
the same name exists (like `XXSlot`), that one will be chosen.

If there is any ambiguity in the matter, the more specific slot is
chosen. In other words, in this case:

- last_slot_identifier = `XXSlot`
- both `XXSlot` and `OBSlot` exist on the Action (where `OB`
    represents the ID type of `animated_id`).

the `OBSlot` should be chosen. This means that `XXSlot` NOT being
auto-assigned if there is an alternative. Since untyped slots are
bound on assignment, this design keeps the Action as-is, which means
that the `XXSlot` remains untyped and thus the user is free to assign
this to another ID type if desired.

Pull Request: https://projects.blender.org/blender/blender/pulls/133653
This commit is contained in:
Sybren A. Stüvel
2025-01-27 18:03:20 +01:00
parent e39b2ee816
commit 1a35335fcd
2 changed files with 78 additions and 0 deletions

View File

@@ -1351,10 +1351,49 @@ Slot *generic_slot_for_autoassign(const ID &animated_id,
/* Try the slot identifier, if it is set. */
if (!last_slot_identifier.is_empty()) {
/* If the last-used slot identifier was 'untyped', i.e. started with XX, see if something more
* specific to this ID type exists.
*
* If there is any choice in the matter, the more specific slot is chosen. In other words, in
* this case:
*
* - last_slot_identifier = `XXSlot`
* - both `XXSlot` and `OBSlot` exist on the Action (where `OB` represents the ID type of
* `animated_id`).
*
* the `OBSlot` should be chosen. This means that `XXSlot` NOT being auto-assigned if there is
* an alternative. Since untyped slots are bound on assignment, this design keeps the Action
* as-is, which means that the `XXSlot` remains untyped and thus the user is free to assign
* this to another ID type if desired. */
const bool last_used_identifier_is_typed = last_slot_identifier.substr(0, 2) !=
slot_untyped_prefix;
if (!last_used_identifier_is_typed) {
const std::string with_idtype_prefix = StringRef(animated_id.name, 2) +
last_slot_identifier.substr(2);
Slot *slot = action.slot_find_by_identifier(with_idtype_prefix);
if (slot && slot->is_suitable_for(animated_id)) {
return slot;
}
}
/* See if the actual last-used slot identifier can be matched. */
Slot *slot = action.slot_find_by_identifier(last_slot_identifier);
if (slot && slot->is_suitable_for(animated_id)) {
return slot;
}
/* If the last-used slot identifier was IDSomething, and XXSomething exists (where ID = the
* ID code of the animated ID), fall back to the XX. If slot `IDSomething` existed, the code
* above would have already returned it. */
if (last_used_identifier_is_typed) {
const std::string with_untyped_prefix = StringRef(slot_untyped_prefix) +
last_slot_identifier.substr(2);
Slot *slot = action.slot_find_by_identifier(with_untyped_prefix);
if (slot && slot->is_suitable_for(animated_id)) {
return slot;
}
}
}
/* Search for the ID name (which includes the ID type). */

View File

@@ -666,6 +666,45 @@ TEST_F(ActionLayersTest, generic_slot_for_autoassign)
generic_slot_for_autoassign(cube->id, *this->action, cube->adt->last_slot_identifier));
}
TEST_F(ActionLayersTest, generic_slot_for_autoassign_untyped_wildcarding)
{
/* Test the untyped slot "wildcard" behaviour, where OBSlot should be chosen when the last slot
* identifier was "XXSlot", and vice versa. */
/* ===
* Action has OBSlot, last-used slot is XXSlot. Should pick OBSlot. */
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
STRNCPY_UTF8(adt->last_slot_identifier, "XXSlot");
Slot &ob_slot = action->slot_add_for_id_type(ID_OB);
action->slot_identifier_define(ob_slot, "OBSlot");
EXPECT_EQ(&ob_slot,
generic_slot_for_autoassign(cube->id, *this->action, adt->last_slot_identifier));
/* ===
* Action has OBSlot and XXSlot, last-used slot is XXSlot. Should pick OBSlot. */
Slot &xx_slot = action->slot_add();
action->slot_identifier_define(xx_slot, "XXSlot");
ASSERT_FALSE(xx_slot.has_idtype());
ASSERT_STREQ("XXSlot", xx_slot.identifier);
ASSERT_STREQ("XXSlot", adt->last_slot_identifier);
EXPECT_EQ(&ob_slot,
generic_slot_for_autoassign(cube->id, *this->action, adt->last_slot_identifier));
/* ===
* Action has OBSlot and XXSlot, last-used slot is OBSlot. Should pick OBSlot. */
STRNCPY_UTF8(adt->last_slot_identifier, "OBSlot");
EXPECT_EQ(&ob_slot,
generic_slot_for_autoassign(cube->id, *this->action, adt->last_slot_identifier));
/* ===
* Action has XXSlot, last-used slot is OBSlot. Should pick XXSlot. */
action->slot_remove(ob_slot);
ASSERT_STREQ("OBSlot", adt->last_slot_identifier);
EXPECT_EQ(&xx_slot,
generic_slot_for_autoassign(cube->id, *this->action, adt->last_slot_identifier));
}
TEST_F(ActionLayersTest, active_slot)
{
{ /* Empty case, no slots exist yet. */