Anim: make action/slot assignment code more generic

In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.

The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.

This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.

Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.

New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.

This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.

The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).

Pull Request: https://projects.blender.org/blender/blender/pulls/127712
This commit is contained in:
Sybren A. Stüvel
2024-09-16 17:23:56 +02:00
committed by Gitea
parent 22cf74d23b
commit 12fe0dd0cf
18 changed files with 401 additions and 368 deletions

View File

@@ -203,6 +203,10 @@ class Action : public ::bAction {
*
* After this call, the reference is no longer valid as the slot will have been freed.
*
* Note that this does NOT unassign this slot from all its users. When the Action is linked into
* another file, that other file cannot be updated, and so missing slots are something that has
* to be handled anyway. Also any new slot on this Action will NOT reuse this slot's handle.
*
* \return true when the layer was found & removed, false if it wasn't found.
*/
bool slot_remove(Slot &slot_to_remove);
@@ -228,30 +232,6 @@ class Action : public ::bAction {
*/
Slot *slot_active_get();
/**
* Assign this Action + Slot to the ID.
*
* If another Action is already assigned to this ID, the caller should unassign the ID from its
* existing Action first, or use the top-level function `assign_action(action, ID)` to do things
* all in one go.
*
* \param slot: The slot this ID should be animated by, may be nullptr if it is to be
* assigned later. In that case, the ID will not actually receive any animation.
* \param animated_id: The ID that should be animated by this Action.
*
* \return whether the assignment was successful.
*/
bool assign_id(Slot *slot, ID &animated_id);
/**
* Unassign this Action from the animated ID.
*
* \param animated_id: ID that is animated by this Action. Calling this
* function when this ID is _not_ animated by this Action is not allowed,
* and considered a bug.
*/
void unassign_id(ID &animated_id);
/**
* Find the slot that best matches the animated ID.
*
@@ -336,6 +316,19 @@ class Action : public ::bAction {
*/
float2 get_frame_range_of_keys(bool include_modifiers) const ATTR_WARN_UNUSED_RESULT;
/**
* Set the slot's ID type to that of the animated ID, ensure the name
* prefix is set accordingly, and that the name is unique within the
* Action.
*
* This is a low-level function, and shouldn't be called directly outside of
* the generic slot-assignment functions.
*
* \note This assumes that the slot has no ID type set yet. If it does, it
* is considered a bug to call this function.
*/
void slot_setup_for_id(Slot &slot, const ID &animated_id);
protected:
/** Return the layer's index, or -1 if not found in this Action. */
int64_t find_layer_index(const Layer &layer) const;
@@ -355,16 +348,6 @@ class Action : public ::bAction {
* \see Action::slot_name_propagate
*/
void slot_name_ensure_prefix(Slot &slot);
/**
* Set the slot's ID type to that of the animated ID, ensure the name
* prefix is set accordingly, and that the name is unique within the
* Action.
*
* \note This assumes that the slot has no ID type set yet. If it does, it
* is considered a bug to call this function.
*/
void slot_setup_for_id(Slot &slot, const ID &animated_id);
};
static_assert(sizeof(Action) == sizeof(::bAction),
"DNA struct and its C++ wrapper must have the same size");
@@ -582,6 +565,10 @@ class Slot : public ::ActionSlot {
*/
constexpr static int name_length_min = 3;
constexpr static int name_length_max = MAX_ID_NAME;
static_assert(sizeof(AnimData::slot_name) == name_length_max);
static_assert(sizeof(NlaStrip::action_slot_name) == name_length_max);
/**
* Return the name prefix for the Slot's type.
*
@@ -1075,7 +1062,16 @@ static_assert(sizeof(ChannelGroup) == sizeof(::bActionGroup),
* be animated). If the above fall-through case of "no slot found" is reached, this function
* will still return `true` as the Action was successfully assigned.
*/
bool assign_action(Action &action, ID &animated_id);
bool assign_action(Action *action, ID &animated_id);
/**
* Un-assign the Action assigned to this ID.
*
* Same as calling `assign_action(nullptr, animated_id)`.
*
* \see assign_action
*/
void unassign_action(ID &animated_id);
/**
* Assign the Action, ensuring that a Slot is also assigned.
@@ -1095,31 +1091,60 @@ bool assign_action(Action &action, ID &animated_id);
*/
Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id);
/**
* Generic function to build Action-assignment logic.
*
* This is a low-level function, intended as a building block for higher-level Action assignment
* functions.
*
* This function always succeeds, and thus it doesn't have any return value.
*/
void generic_assign_action(ID &animated_id,
Action *action_to_assign,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_name);
enum class ActionSlotAssignmentResult : int8_t {
OK = 0,
SlotNotFromAction = 1, /* Slot does not belong to the assigned Action. */
SlotNotSuitable = 2, /* Slot is not suitable for the given ID type.*/
MissingAction = 3, /* No Action assigned yet, so cannot assign slot. */
};
/**
* Generic function to build Slot-assignment logic.
*
* This is a low-level function, intended as a building block for higher-level slot assignment
* functions.
*/
ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign,
ID &animated_id,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_name) ATTR_WARN_UNUSED_RESULT;
/**
* Return whether the given Action can be assigned to the ID.
*
* This always returns `true` for layered Actions. For legacy Actions it
* returns `true` if the Action's `idroot` matches the ID.
*/
bool is_action_assignable_to(const bAction *dna_action, ID_Type id_code);
bool is_action_assignable_to(const bAction *dna_action, ID_Type id_code) ATTR_WARN_UNUSED_RESULT;
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id);
/**
* Ensure that this ID is no longer animated.
* Utility function that assigns both an Action and a slot of that Action.
*
* Returns the result of the underlying assign_action_slot() call.
*
* \see assign_action
* \see assign_action_slot
*/
void unassign_action(ID &animated_id);
/**
* Clear the Action slot of this ID.
*
* `adt.slot_handle_name` is updated to reflect the current name of the
* slot, before un-assigning. This is to ensure that the stored name reflects
* the actual slot that was used, making re-slot trivial.
*
* \param animated_id: the animated ID.
*
* \note this does not clear the Action pointer, just the slot handle.
*/
void unassign_slot(ID &animated_id);
ActionSlotAssignmentResult assign_action_and_slot(Action *action,
Slot *slot_to_assign,
ID &animated_id);
/**
* Return the Action of this ID, or nullptr if it has none.

View File

@@ -38,4 +38,23 @@ void action_foreach_fcurve(Action &action,
slot_handle_t handle,
FunctionRef<void(FCurve &fcurve)> callback);
/**
* Call the given callback for each Action + Slot that this ID uses.
*
* The following cases are visited:
* - Direct Action+Slot assignment.
* - NLA strips.
* - Action Constraints, both on Object and Pose Bone level.
*
* \param callback The function to call for each Action+Slot used. Even when there is no slot
* assigned, this function will be called (but then with slot_handle = Slot::unassigned). The
* callback should return `true` to continue the foreach loop, or return `false` to stop it.
*
* \returns Whether the foreach loop came to a natural end. So returns `false` when the callback
* returned `false`, and `true` otherwise.
*/
bool foreach_action_slot_use(
const ID &animated_id,
FunctionRef<bool(const Action &action, slot_handle_t slot_handle)> callback);
} // namespace blender::animrig

View File

@@ -15,12 +15,6 @@ struct NlaStrip;
namespace blender::animrig::nla {
enum class ActionSlotAssignmentResult : int8_t {
OK = 0,
SlotNotFromAction = 1, /* Slot does not belong to the assigned Action. */
SlotNotSuitable = 2, /* Slot is not suitable for the given ID type.*/
};
/**
* Assign the Action to this NLA strip.
*
@@ -50,9 +44,4 @@ ActionSlotAssignmentResult assign_action_slot_handle(NlaStrip &strip,
slot_handle_t slot_handle,
ID &animated_id);
/**
* Returns whether any NLA strip of this AnimData references the given Action and Slot.
*/
bool is_nla_referencing_slot(const AnimData &adt, const Action &action, slot_handle_t slot_handle);
} // namespace blender::animrig::nla

View File

@@ -20,6 +20,7 @@
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
@@ -42,6 +43,7 @@
#include "DEG_depsgraph_build.hh"
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "ANIM_nla.hh"
@@ -516,22 +518,8 @@ bool Action::slot_remove(Slot &slot_to_remove)
layer->slot_data_remove(slot_to_remove.handle);
}
/* Un-assign this slot from its users. Only do this if the list of users is valid, */
for (ID *user : slot_to_remove.runtime_users()) {
/* Sanity check: make sure the slot is still assigned, before un-assigning anything. */
std::optional<std::pair<Action *, Slot *>> action_and_slot = get_action_slot_pair(*user);
BLI_assert_msg(action_and_slot, "Slot user has no Action assigned");
BLI_assert_msg(action_and_slot->first == this, "Slot user has other Action assigned");
BLI_assert_msg(action_and_slot->second == &slot_to_remove,
"Slot user has other Slot assigned");
if (!action_and_slot || action_and_slot->first != this ||
action_and_slot->second != &slot_to_remove)
{
continue;
}
this->assign_id(nullptr, *user);
}
/* Don't bother un-assigning this slot from its users. The slot handle will
* not be reused by a new slot anyway. */
/* Remove the actual slot. */
dna::array::remove_index(
@@ -607,71 +595,6 @@ Layer *Action::get_layer_for_keyframing()
return this->layer(0);
}
bool Action::assign_id(Slot *slot, ID &animated_id)
{
BLI_assert_msg(!slot || this->slots().as_span().contains(slot),
"Slot should be owned by this Action");
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
if (!adt) {
return false;
}
if (adt->action && adt->action != this) {
/* The caller should unassign the ID from its existing animation first, or
* use the top-level function `assign_action(anim, ID)`. */
return false;
}
/* Check that the new Slot is suitable, before changing `adt`. */
if (slot && !slot->is_suitable_for(animated_id)) {
return false;
}
/* Unassign any previously-assigned Slot. */
Slot *slot_to_unassign = this->slot_for_handle(adt->slot_handle);
if (slot_to_unassign) {
/* There could still be NLA strips on this ID, referring to the same slot, so we cannot just
* remove this ID from the slot users. */
if (!nla::is_nla_referencing_slot(*adt, *this, slot_to_unassign->handle)) {
slot_to_unassign->users_remove(animated_id);
}
/* Before unassigning, make sure that the stored Slot name is up to date. The slot name
* might have changed in a way that wasn't copied into the ADT yet (for example when the
* Action is linked from another file), so better copy the name to be sure that it can be
* transparently reassigned later.
*
* TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure
* this name is always correct. */
STRNCPY_UTF8(adt->slot_name, slot_to_unassign->name);
}
/* Assign the Action itself. */
if (!adt->action) {
/* Due to the precondition check above, we know that adt->action is either 'this' (in which
* case the user count is already correct) or `nullptr` (in which case this is a new
* reference, and the user count should be increased). */
id_us_plus(&this->id);
adt->action = this;
}
/* Assign the Slot. */
if (slot) {
this->slot_setup_for_id(*slot, animated_id);
adt->slot_handle = slot->handle;
slot->users_add(animated_id);
/* Always make sure the ID's slot name matches the assigned slot. */
STRNCPY_UTF8(adt->slot_name, slot->name);
}
else {
adt->slot_handle = Slot::unassigned;
}
return true;
}
void Action::slot_name_ensure_prefix(Slot &slot)
{
slot.name_ensure_prefix();
@@ -689,20 +612,6 @@ void Action::slot_setup_for_id(Slot &slot, const ID &animated_id)
this->slot_name_ensure_prefix(slot);
}
void Action::unassign_id(ID &animated_id)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
BLI_assert_msg(adt, "ID is not animated at all");
BLI_assert_msg(adt->action == this, "ID is not assigned to this Animation");
/* Unassign the Slot first. */
this->assign_id(nullptr, animated_id);
/* Unassign the Action itself. */
id_us_min(&this->id);
adt->action = nullptr;
}
bool Action::has_keyframes(const slot_handle_t action_slot_handle) const
{
if (this->is_action_legacy()) {
@@ -1152,12 +1061,20 @@ void Strip::slot_data_remove(const slot_handle_t slot_handle)
/* ----- Functions ----------- */
bool assign_action(Action &action, ID &animated_id)
bool assign_action(Action *action, ID &animated_id)
{
unassign_action(animated_id);
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
if (!adt) {
return false;
}
Slot *slot = action.find_suitable_slot_for(animated_id);
return action.assign_id(slot, animated_id);
generic_assign_action(animated_id, action, adt->action, adt->slot_handle, adt->slot_name);
return true;
}
void unassign_action(ID &animated_id)
{
assign_action(nullptr, animated_id);
}
Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id)
@@ -1193,13 +1110,125 @@ Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id)
slot = &action.slot_add_for_id(animated_id);
}
if (!action.assign_id(slot, animated_id)) {
assign_action(&action, animated_id);
if (assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK) {
return nullptr;
}
return slot;
}
static bool is_id_using_action_slot(const ID &animated_id,
const Action &action,
const slot_handle_t slot_handle)
{
auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool {
const bool is_used = (&used_action == &action && used_slot_handle == slot_handle);
return !is_used; /* Stop searching when we found a use of this Action+Slot. */
};
const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use);
return !looped_until_end;
}
void generic_assign_action(ID &animated_id,
Action *action_to_assign,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_name)
{
BLI_assert(slot_name);
/* Un-assign any previously-assigned Action first. */
if (action_ptr_ref) {
/* Un-assign the slot. This will always succeed, so no need to check the result. */
if (slot_handle_ref != Slot::unassigned) {
const ActionSlotAssignmentResult result = generic_assign_action_slot(
nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_name);
BLI_assert(result == ActionSlotAssignmentResult::OK);
UNUSED_VARS_NDEBUG(result);
}
/* Un-assign the Action itself. */
id_us_min(&action_ptr_ref->id);
action_ptr_ref = nullptr;
}
if (!action_to_assign) {
/* Un-assigning was the point, so the work is done. */
return;
}
/* Assign the new Action. */
action_ptr_ref = action_to_assign;
id_us_plus(&action_ptr_ref->id);
/* Assign the slot. Since the slot is guaranteed to be usable (or nullptr) and from the assigned
* Action, this call is guaranteed to succeed. */
Slot *slot = action_to_assign->find_suitable_slot_for(animated_id);
const ActionSlotAssignmentResult result = generic_assign_action_slot(
slot, animated_id, action_ptr_ref, slot_handle_ref, slot_name);
BLI_assert(result == ActionSlotAssignmentResult::OK);
UNUSED_VARS_NDEBUG(result);
}
ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign,
ID &animated_id,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_name)
{
BLI_assert(slot_name);
if (!action_ptr_ref) {
/* No action assigned yet, so no way to assign a slot. */
return ActionSlotAssignmentResult::MissingAction;
}
Action &action = action_ptr_ref->wrap();
/* Check that the slot can actually be assigned. */
if (slot_to_assign) {
if (!action.slots().as_span().contains(slot_to_assign)) {
return ActionSlotAssignmentResult::SlotNotFromAction;
}
if (!slot_to_assign->is_suitable_for(animated_id)) {
return ActionSlotAssignmentResult::SlotNotSuitable;
}
}
Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref);
/* If there was a previously-assigned slot, unassign it first. */
slot_handle_ref = Slot::unassigned;
if (slot_to_unassign) {
/* Make sure that the stored Slot name is up to date. The slot name might have
* changed in a way that wasn't copied into the ADT yet (for example when the
* Action is linked from another file), so better copy the name to be sure
* that it can be transparently reassigned later.
*
* TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure
* this name is always correct. */
BLI_strncpy_utf8(slot_name, slot_to_unassign->name, Slot::name_length_max);
/* If this was the last use of this slot, remove this ID from its users. */
if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) {
slot_to_unassign->users_remove(animated_id);
}
}
if (!slot_to_assign) {
return ActionSlotAssignmentResult::OK;
}
action.slot_setup_for_id(*slot_to_assign, animated_id);
slot_handle_ref = slot_to_assign->handle;
BLI_strncpy_utf8(slot_name, slot_to_assign->name, Slot::name_length_max);
slot_to_assign->users_add(animated_id);
return ActionSlotAssignmentResult::OK;
}
bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
{
if (!dna_action) {
@@ -1224,33 +1253,25 @@ bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
return true;
}
void unassign_action(ID &animated_id)
{
Action *action = get_action(animated_id);
if (!action) {
return;
}
action->unassign_id(animated_id);
}
void unassign_slot(ID &animated_id)
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
BLI_assert_msg(adt, "Cannot unassign an Action Slot from a non-animated ID.");
if (!adt) {
return;
return ActionSlotAssignmentResult::MissingAction;
}
if (!adt->action) {
/* Nothing assigned. */
BLI_assert_msg(adt->slot_handle == Slot::unassigned,
"Slot handle should be 'unassigned' when no Action is assigned");
return;
}
return generic_assign_action_slot(
slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->slot_name);
}
/* Assign the 'nullptr' slot, effectively unassigning it. */
Action &action = adt->action->wrap();
action.assign_id(nullptr, animated_id);
ActionSlotAssignmentResult assign_action_and_slot(Action *action,
Slot *slot_to_assign,
ID &animated_id)
{
if (!assign_action(action, animated_id)) {
return ActionSlotAssignmentResult::MissingAction;
}
return assign_action_slot(slot_to_assign, animated_id);
}
/* TODO: rename to get_action(). */

View File

@@ -8,8 +8,12 @@
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "BLI_assert.h"
#include "BKE_anim_data.hh"
#include "BKE_nla.hh"
namespace blender::animrig {
void action_foreach_fcurve(Action &action,
@@ -42,4 +46,35 @@ void action_foreach_fcurve(Action &action,
}
}
bool foreach_action_slot_use(
const ID &animated_id,
FunctionRef<bool(const Action &action, slot_handle_t slot_handle)> callback)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
if (adt) {
if (adt->action) {
/* Direct assignment. */
if (!callback(adt->action->wrap(), adt->slot_handle)) {
return false;
}
}
/* NLA strips. */
const bool looped_until_last_strip = bke::nla::foreach_strip_adt(*adt, [&](NlaStrip *strip) {
if (strip->act) {
if (!callback(strip->act->wrap(), strip->action_slot_handle)) {
return false;
}
}
return true;
});
if (!looped_until_last_strip) {
return false;
}
}
return true;
}
} // namespace blender::animrig

View File

@@ -236,8 +236,10 @@ TEST_F(ActionLayersTest, add_slot_multiple)
{
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action->slot_add();
EXPECT_TRUE(action->assign_id(&slot_cube, cube->id));
EXPECT_TRUE(action->assign_id(&slot_suzanne, suzanne->id));
assign_action(action, cube->id);
EXPECT_EQ(assign_action_slot(&slot_cube, cube->id), ActionSlotAssignmentResult::OK);
assign_action(action, suzanne->id);
EXPECT_EQ(assign_action_slot(&slot_suzanne, suzanne->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(2, action->last_slot_handle);
EXPECT_EQ(1, slot_cube.handle);
@@ -318,14 +320,17 @@ TEST_F(ActionLayersTest, slot_remove)
EXPECT_NE(strip.channelbag_for_slot(slot3.handle), nullptr);
}
{ /* Removing an in-use slot should un-assign it from its users. */
{ /* Removing an in-use slot doesn't un-assign it from its users.
* This is not that important, but it covers the current behaviour. */
Slot &slot = action->slot_add_for_id(cube->id);
action->assign_id(&slot, cube->id);
ASSERT_EQ(assign_action_and_slot(action, &slot, cube->id), ActionSlotAssignmentResult::OK);
ASSERT_TRUE(slot.runtime_users().contains(&cube->id));
ASSERT_EQ(cube->adt->slot_handle, slot.handle);
const slot_handle_t removed_slot_handle = slot.handle;
ASSERT_TRUE(action->slot_remove(slot));
EXPECT_EQ(cube->adt->slot_handle, Slot::unassigned);
EXPECT_EQ(cube->adt->slot_handle, removed_slot_handle);
}
{ /* Creating a slot after removing one should not reuse its handle. */
@@ -347,7 +352,8 @@ TEST_F(ActionLayersTest, action_assign_id)
Slot &slot_cube = action->slot_add();
ASSERT_NE(nullptr, slot_cube.runtime);
ASSERT_STREQ(slot_cube.name, "XXSlot");
ASSERT_TRUE(action->assign_id(&slot_cube, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(slot_cube.handle, cube->adt->slot_handle);
EXPECT_STREQ(slot_cube.name, "OBSlot");
EXPECT_STREQ(slot_cube.name, cube->adt->slot_name)
@@ -357,7 +363,8 @@ TEST_F(ActionLayersTest, action_assign_id)
<< "Expecting Cube to be registered as animated by its slot.";
/* Assign another ID to the same Slot. */
ASSERT_TRUE(action->assign_id(&slot_cube, suzanne->id));
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, suzanne->id),
ActionSlotAssignmentResult::OK);
EXPECT_STREQ(slot_cube.name, "OBSlot");
EXPECT_STREQ(slot_cube.name, cube->adt->slot_name)
<< "The slot name should be copied to the adt";
@@ -368,16 +375,21 @@ TEST_F(ActionLayersTest, action_assign_id)
{ /* Assign Cube to another action+slot without unassigning first. */
Action *another_anim = static_cast<Action *>(BKE_id_new(bmain, ID_AC, "ACOtherAnim"));
Slot &another_slot = another_anim->slot_add();
ASSERT_FALSE(another_anim->assign_id(&another_slot, cube->id))
<< "Assigning Action (with this function) when already assigned should fail.";
EXPECT_TRUE(slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Cube to still be registered as animated by its slot.";
ASSERT_EQ(assign_action_and_slot(another_anim, &another_slot, cube->id),
ActionSlotAssignmentResult::OK);
EXPECT_FALSE(slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Cube to no longer be registered as user of its old slot.";
EXPECT_TRUE(another_slot.users(*bmain).contains(&cube->id))
<< "Expecting Cube to be registered as user of its new slot.";
}
{ /* Assign Cube to another slot of the same Action, this should work. */
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id),
ActionSlotAssignmentResult::OK);
const int user_count_pre = action->id.us;
Slot &slot_cube_2 = action->slot_add();
ASSERT_TRUE(action->assign_id(&slot_cube_2, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &slot_cube_2, cube->id),
ActionSlotAssignmentResult::OK);
ASSERT_EQ(action->id.us, user_count_pre)
<< "Assigning to a different slot of the same Action should _not_ change the user "
"count of that Action";
@@ -389,7 +401,7 @@ TEST_F(ActionLayersTest, action_assign_id)
{ /* Unassign the Action. */
const int user_count_pre = action->id.us;
action->unassign_id(cube->id);
unassign_action(cube->id);
ASSERT_EQ(action->id.us, user_count_pre - 1)
<< "Unassigning an Action should lower its user count";
@@ -403,7 +415,8 @@ TEST_F(ActionLayersTest, action_assign_id)
/* Assign Cube to another 'virgin' slot. This should not cause a name
* collision between the Slots. */
Slot &another_slot_cube = action->slot_add();
ASSERT_TRUE(action->assign_id(&another_slot_cube, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &another_slot_cube, cube->id),
ActionSlotAssignmentResult::OK);
EXPECT_EQ(another_slot_cube.handle, cube->adt->slot_handle);
EXPECT_STREQ("OBSlot.002", another_slot_cube.name) << "The slot should be uniquely named";
EXPECT_STREQ("OBSlot.002", cube->adt->slot_name) << "The slot name should be copied to the adt";
@@ -412,7 +425,8 @@ TEST_F(ActionLayersTest, action_assign_id)
/* Create an ID of another type. This should not be assignable to this slot. */
ID *mesh = static_cast<ID *>(BKE_id_new_nomain(ID_ME, "Mesh"));
EXPECT_FALSE(action->assign_id(&slot_cube, *mesh))
ASSERT_TRUE(assign_action(action, *mesh));
EXPECT_EQ(assign_action_slot(&slot_cube, *mesh), ActionSlotAssignmentResult::SlotNotSuitable)
<< "Mesh should not be animatable by an Object slot";
EXPECT_FALSE(another_slot_cube.users(*bmain).contains(mesh))
<< "Expecting Mesh to not be registered as animated by the 'slot_cube' slot.";
@@ -422,7 +436,7 @@ TEST_F(ActionLayersTest, action_assign_id)
TEST_F(ActionLayersTest, rename_slot)
{
Slot &slot_cube = action->slot_add();
ASSERT_TRUE(action->assign_id(&slot_cube, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(slot_cube.handle, cube->adt->slot_handle);
EXPECT_STREQ("OBSlot", slot_cube.name);
EXPECT_STREQ(slot_cube.name, cube->adt->slot_name)
@@ -442,7 +456,7 @@ TEST_F(ActionLayersTest, rename_slot)
* unassign. This should still result in the correct slot name being stored
* on the ADT. */
action->slot_name_define(slot_cube, "Even Newer Name");
action->unassign_id(cube->id);
unassign_action(cube->id);
EXPECT_STREQ("Even Newer Name", cube->adt->slot_name);
}
@@ -647,7 +661,7 @@ TEST_F(ActionLayersTest, strip)
TEST_F(ActionLayersTest, KeyframeStrip__keyframe_insert)
{
Slot &slot = action->slot_add();
EXPECT_TRUE(action->assign_id(&slot, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &slot, cube->id), ActionSlotAssignmentResult::OK);
Layer &layer = action->layer_add("Kübus layer");
Strip &strip = layer.strip_add(Strip::Type::Keyframe);
@@ -730,7 +744,7 @@ TEST_F(ActionLayersTest, is_action_assignable_to)
TEST_F(ActionLayersTest, action_slot_get_id_for_keying__empty_action)
{
action->assign_id(nullptr, cube->id);
assign_action(action, cube->id);
/* Double-check that the action is considered empty for the test. */
EXPECT_TRUE(action->is_empty());
@@ -747,7 +761,7 @@ TEST_F(ActionLayersTest, action_slot_get_id_for_keying__legacy_action)
FCurve *fcurve = action_fcurve_ensure(bmain, action, nullptr, nullptr, {"location", 0});
EXPECT_FALSE(fcurve == nullptr);
action->assign_id(nullptr, cube->id);
assign_action(action, cube->id);
/* Double-check that the action is considered legacy for the test. */
EXPECT_TRUE(action->is_action_legacy());
@@ -771,14 +785,14 @@ TEST_F(ActionLayersTest, action_slot_get_id_for_keying__layered_action)
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, slot.handle, &cube->id));
/* A slot with precisely one user should always return that user. */
action->assign_id(&slot, cube->id);
ASSERT_EQ(assign_action_and_slot(action, &slot, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(&cube->id, action_slot_get_id_for_keying(*bmain, *action, slot.handle, nullptr));
EXPECT_EQ(&cube->id, action_slot_get_id_for_keying(*bmain, *action, slot.handle, &cube->id));
EXPECT_EQ(&cube->id, action_slot_get_id_for_keying(*bmain, *action, slot.handle, &suzanne->id));
/* A slot with more than one user should return the passed `primary_id` if it
* is among its users, and nullptr otherwise. */
action->assign_id(&slot, suzanne->id);
ASSERT_EQ(assign_action_and_slot(action, &slot, suzanne->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(&cube->id, action_slot_get_id_for_keying(*bmain, *action, slot.handle, &cube->id));
EXPECT_EQ(&suzanne->id,
action_slot_get_id_for_keying(*bmain, *action, slot.handle, &suzanne->id));

View File

@@ -65,7 +65,8 @@ class AnimationEvaluationTest : public testing::Test {
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "Küüübus");
slot = &action->slot_add();
action->assign_id(slot, cube->id);
ASSERT_EQ(assign_action_and_slot(action, slot, cube->id), ActionSlotAssignmentResult::OK);
layer = &action->layer_add("Kübus layer");
/* Make it easier to predict test values. */

View File

@@ -650,7 +650,8 @@ TEST_F(KeyframingTest, insert_keyframes__layered_action__multiple_ids)
ASSERT_NE(nullptr, channel_bag_1);
/* Assign the action to the second object, with no slot. */
action.assign_id(nullptr, armature_object->id);
assign_action(&action, armature_object->id);
ASSERT_EQ(assign_action_slot(nullptr, armature_object->id), ActionSlotAssignmentResult::OK);
/* Keying the second object should go into the same action, creating a new
* slot and channel bag. */

View File

@@ -72,59 +72,8 @@ ActionSlotAssignmentResult assign_action_slot(NlaStrip &strip,
{
BLI_assert(strip.act);
AnimData *adt = BKE_animdata_from_id(&animated_id);
BLI_assert_msg(adt,
"animrig::nla::assign_action_slot assumes, since there is a strip, that it is "
"owned by the given ID");
Action &action = strip.act->wrap();
/* Check that the slot can actually be assigned. */
if (slot_to_assign) {
if (!action.slots().as_span().contains(slot_to_assign)) {
return ActionSlotAssignmentResult::SlotNotFromAction;
}
if (!slot_to_assign->is_suitable_for(animated_id)) {
return ActionSlotAssignmentResult::SlotNotSuitable;
}
}
Slot *slot_to_unassign = action.slot_for_handle(strip.action_slot_handle);
/* Un-assign the currently-assigned slot first, so the code below doesn't find
* this reference any more. */
strip.action_slot_handle = Slot::unassigned;
/* See if there are any other uses of this action slot on this ID, and if
* not, remove the ID from the slot's user map. */
if (slot_to_unassign) {
const bool is_directly_assigned = (adt->action == strip.act &&
adt->slot_handle == slot_to_unassign->handle);
if (!is_directly_assigned && !is_nla_referencing_slot(*adt, action, slot_to_unassign->handle))
{
slot_to_unassign->users_remove(animated_id);
}
}
if (!slot_to_assign) {
/* Make sure that the stored Slot name is up to date. The slot name might have
* changed in a way that wasn't copied into the ADT yet (for example when the
* Action is linked from another file), so better copy the name to be sure
* that it can be transparently reassigned later.
*
* TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure
* this name is always correct. */
STRNCPY_UTF8(strip.action_slot_name, slot_to_unassign->name);
return ActionSlotAssignmentResult::OK;
}
strip.action_slot_handle = slot_to_assign->handle;
STRNCPY_UTF8(strip.action_slot_name, slot_to_assign->name);
slot_to_assign->users_add(animated_id);
return ActionSlotAssignmentResult::OK;
return generic_assign_action_slot(
slot_to_assign, animated_id, strip.act, strip.action_slot_handle, strip.action_slot_name);
}
ActionSlotAssignmentResult assign_action_slot_handle(NlaStrip &strip,
@@ -139,19 +88,4 @@ ActionSlotAssignmentResult assign_action_slot_handle(NlaStrip &strip,
return assign_action_slot(strip, slot_to_assign, animated_id);
}
bool is_nla_referencing_slot(const AnimData &adt,
const Action &action,
const slot_handle_t slot_handle)
{
const bool looped_until_the_end = bke::nla::foreach_strip_adt(adt, [&](NlaStrip *strip) {
const bool found_the_slot = (strip->act == &action &&
strip->action_slot_handle == slot_handle);
/* Keep looping until the slot handle is found. */
return !found_the_slot;
});
return !looped_until_the_end;
}
} // namespace blender::animrig::nla

View File

@@ -62,17 +62,6 @@ class NLASlottedActionTest : public testing::Test {
}
};
TEST_F(NLASlottedActionTest, assign_slot_to_id)
{
Slot &slot = action->slot_add_for_id(cube->id);
ASSERT_TRUE(action->assign_id(&slot, cube->id));
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));
ASSERT_TRUE(action->assign_id(nullptr, cube->id));
EXPECT_FALSE(slot.runtime_users().contains(&cube->id));
}
TEST_F(NLASlottedActionTest, assign_slot_to_nla_strip)
{
ASSERT_EQ(action->id.us, 0);
@@ -117,8 +106,7 @@ TEST_F(NLASlottedActionTest, assign_slot_to_nla_strip)
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));
/* Unassign the slot, but keep the Action assigned. */
EXPECT_EQ(nla::assign_action_slot(*strip, nullptr, cube->id),
nla::ActionSlotAssignmentResult::OK);
EXPECT_EQ(nla::assign_action_slot(*strip, nullptr, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(strip->action_slot_handle, Slot::unassigned);
EXPECT_STREQ(strip->action_slot_name, slot.name);
EXPECT_EQ(action->id.us, 1);
@@ -159,14 +147,12 @@ TEST_F(NLASlottedActionTest, assign_slot_to_multiple_strips)
EXPECT_EQ(strip1->action_slot_handle, Slot::unassigned);
/* Assign the slot 'manually'. */
EXPECT_EQ(nla::assign_action_slot(*strip1, &slot, cube->id),
nla::ActionSlotAssignmentResult::OK);
EXPECT_EQ(nla::assign_action_slot(*strip1, &slot, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(strip1->action_slot_handle, slot.handle);
/* Assign the Action + slot to the second strip.*/
EXPECT_FALSE(nla::assign_action(*strip2, *action, cube->id));
EXPECT_EQ(nla::assign_action_slot(*strip2, &slot, cube->id),
nla::ActionSlotAssignmentResult::OK);
EXPECT_EQ(nla::assign_action_slot(*strip2, &slot, cube->id), ActionSlotAssignmentResult::OK);
/* The cube should be registered as user of the slot. */
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));

View File

@@ -209,7 +209,7 @@ bool BKE_animdata_set_action(ReportList *reports, ID *id, bAction *act)
return true;
}
animrig::Action &action = act->wrap();
return animrig::assign_action(action, *id);
return animrig::assign_action(&action, *id);
#else
return animdata_set_action(reports, id, &adt->action, act);
#endif // WITH_ANIM_BAKLAVA

View File

@@ -2344,7 +2344,12 @@ bool BKE_nla_tweakmode_enter(const OwnedAnimData owned_adt)
animrig::Action &strip_action = activeStrip->act->wrap();
if (strip_action.is_action_layered()) {
animrig::Slot *strip_slot = strip_action.slot_for_handle(activeStrip->action_slot_handle);
strip_action.assign_id(strip_slot, owned_adt.owner_id);
if (animrig::assign_action_and_slot(&strip_action, strip_slot, owned_adt.owner_id) !=
animrig::ActionSlotAssignmentResult::OK)
{
printf("NLA tweak-mode enter - could not assign slot %s\n",
strip_slot ? strip_slot->name : "-unassigned-");
}
}
else {
adt.action = activeStrip->act;

View File

@@ -68,8 +68,10 @@ TEST_F(ActionFilterTest, slots_expanded_or_not)
{
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action->slot_add();
ASSERT_TRUE(action->assign_id(&slot_cube, cube->id));
ASSERT_TRUE(action->assign_id(&slot_suzanne, suzanne->id));
assign_action(action, cube->id);
assign_action(action, suzanne->id);
ASSERT_EQ(assign_action_slot(&slot_cube, cube->id), ActionSlotAssignmentResult::OK);
ASSERT_EQ(assign_action_slot(&slot_suzanne, suzanne->id), ActionSlotAssignmentResult::OK);
Layer &layer = action->layer_add("Kübus layer");
KeyframeStrip &key_strip = layer.strip_add(Strip::Type::Keyframe).as<KeyframeStrip>();
@@ -226,7 +228,7 @@ TEST_F(ActionFilterTest, layered_action_active_fcurves)
Slot &slot_cube = action->slot_add();
/* The Action+Slot has to be assigned to what the bAnimContext thinks is the active Object.
* See the BLI_assert_msg() call in the ANIMCONT_ACTION case of ANIM_animdata_filter(). */
ASSERT_TRUE(action->assign_id(&slot_cube, cube->id));
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
Layer &layer = action->layer_add("Kübus layer");
KeyframeStrip &key_strip = layer.strip_add(Strip::Type::Keyframe).as<KeyframeStrip>();

View File

@@ -737,17 +737,18 @@ static bool slot_new_for_object_poll(bContext *C)
static int slot_new_for_object_exec(bContext *C, wmOperator * /*op*/)
{
using namespace blender;
using namespace blender::animrig;
Object *object = CTX_data_active_object(C);
animrig::Action *action = animrig::get_action(object->id);
Action *action = get_action(object->id);
BLI_assert_msg(action, "The poll function should have ensured the Action is not NULL");
animrig::Slot &slot = action->slot_add_for_id(object->id);
Slot &slot = action->slot_add_for_id(object->id);
{ /* Assign the newly created slot. */
const bool ok = action->assign_id(&slot, object->id);
BLI_assert_msg(ok, "Assigning a slot that was made for this ID should always work");
UNUSED_VARS_NDEBUG(ok);
const ActionSlotAssignmentResult result = assign_action_slot(&slot, object->id);
BLI_assert_msg(result == ActionSlotAssignmentResult::OK,
"Assigning a slot that was made for this ID should always work");
UNUSED_VARS_NDEBUG(result);
}
DEG_relations_tag_update(CTX_data_main(C));
@@ -785,12 +786,15 @@ static int convert_action_exec(bContext *C, wmOperator * /*op*/)
animrig::Action *layered_action = animrig::convert_to_layered_action(*bmain, legacy_action);
/* We did already check if the action can be converted. */
BLI_assert(layered_action != nullptr);
animrig::assign_action(layered_action, object->id);
animrig::unassign_action(object->id);
BLI_assert(layered_action->slot_array_num == 1);
BLI_assert(layered_action->slots().size() == 1);
animrig::Slot *slot = layered_action->slot(0);
layered_action->slot_name_set(*bmain, *slot, object->id.name);
layered_action->assign_id(slot, object->id);
const animrig::ActionSlotAssignmentResult result = animrig::assign_action_slot(slot, object->id);
BLI_assert(result == animrig::ActionSlotAssignmentResult::OK);
UNUSED_VARS_NDEBUG(result);
ANIM_id_update(bmain, &object->id);
DEG_relations_tag_update(bmain);

View File

@@ -1148,10 +1148,14 @@ typedef struct AnimData {
bAction *action;
/**
* Identifier for which ActionSlot of the above Animation is actually animating this
* Identifier for which ActionSlot of the above Action is actually animating this
* data-block.
*
* Do not set this directly, use one of the assignment functions in ANIM_action.hh instead.
*
* This can be set to `blender::animrig::Slot::unassigned` when no slot is assigned. Note that
* this field being set to any other value does NOT guarantee that there is a slot with that
* handle, as it might have been deleted from the Action.
*/
int32_t slot_handle;
/**

View File

@@ -1370,22 +1370,29 @@ static void rna_Action_deselect_keys(bAction *act)
bool rna_Action_id_poll(PointerRNA *ptr, PointerRNA value)
{
ID *srcId = ptr->owner_id;
bAction *act = (bAction *)value.owner_id;
bAction *dna_action = (bAction *)value.owner_id;
if (act) {
if (!dna_action) {
return false;
}
animrig::Action &action = dna_action->wrap();
if (action.is_action_legacy()) {
/* there can still be actions that will have undefined id-root
* (i.e. floating "action-library" members) which we will not
* be able to resolve an idroot for automatically, so let these through
*/
if (act->idroot == 0) {
if (action.idroot == 0) {
return 1;
}
else if (srcId) {
return GS(srcId->name) == act->idroot;
return GS(srcId->name) == action.idroot;
}
}
return 0;
/* Layered Actions can always be assigned. */
BLI_assert(action.idroot == 0);
return true;
}
/* Used to check if an action (value pointer)

View File

@@ -164,18 +164,14 @@ static PointerRNA rna_AnimData_action_get(PointerRNA *ptr)
static void rna_AnimData_action_set(PointerRNA *ptr, PointerRNA value, ReportList * /*reports*/)
{
# ifdef WITH_ANIM_BAKLAVA
using namespace blender::animrig;
BLI_assert(ptr->owner_id);
ID &animated_id = *ptr->owner_id;
/* TODO: protect against altering action in NLA tweak mode, see BKE_animdata_action_editable() */
bAction *action = static_cast<bAction *>(value.data);
if (!action) {
blender::animrig::unassign_action(animated_id);
return;
}
blender::animrig::assign_action(action->wrap(), animated_id);
Action *action = static_cast<Action *>(value.data);
assign_action(action, animated_id);
# else
ID *ownerId = ptr->owner_id;
BKE_animdata_set_action(nullptr, ownerId, static_cast<bAction *>(value.data));
@@ -263,7 +259,8 @@ static void rna_AnimData_action_slot_handle_set(
}
blender::animrig::Slot *slot = action->slot_for_handle(new_slot_handle);
if (!action->assign_id(slot, animated_id)) {
/* TODO: switch over the possible assignment results, and improve the error message. */
if (assign_action_slot(slot, animated_id) != animrig::ActionSlotAssignmentResult::OK) {
if (slot) {
WM_reportf(RPT_ERROR,
"Action '%s' slot '%s' (%d) could not be assigned to %s",
@@ -310,44 +307,32 @@ static PointerRNA rna_AnimData_action_slot_get(PointerRNA *ptr)
static void rna_AnimData_action_slot_set(PointerRNA *ptr, PointerRNA value, ReportList *reports)
{
using animrig::Action;
using animrig::Slot;
using namespace blender::animrig;
ID *animated_id = ptr->owner_id;
BLI_assert(animated_id); /* Otherwise there is nothing to own this AnimData. */
/* A 'None' value for the slot is always valid, regardless of whether an
* Action was assigned or not. */
ActionSlot *dna_slot = static_cast<ActionSlot *>(value.data);
if (!dna_slot) {
animrig::unassign_slot(*animated_id);
return;
}
Slot *slot = dna_slot ? &dna_slot->wrap() : nullptr;
AnimData &adt = rna_animdata(ptr);
if (!adt.action) {
BKE_report(reports, RPT_ERROR, "Cannot set slot without an assigned Action.");
return;
}
BLI_assert(BKE_animdata_from_id(animated_id) == &adt);
Action &action = adt.action->wrap();
Slot &slot = dna_slot->wrap();
if (!action.slots().as_span().contains(&slot)) {
BKE_reportf(reports,
RPT_ERROR,
"This slot (%s) does not belong to the assigned Action (%s)",
slot.name,
action.id.name + 2);
return;
}
if (!action.assign_id(&slot, *animated_id)) {
/* TODO: make assign_id() return a different type that gives us more info about what went
* wrong. */
BKE_reportf(reports, RPT_ERROR, "Cannot assign slot %s to %s.", slot.name, animated_id->name);
return;
switch (assign_action_slot(slot, *animated_id)) {
case ActionSlotAssignmentResult::OK:
break;
case ActionSlotAssignmentResult::SlotNotFromAction:
BKE_reportf(
reports, RPT_ERROR, "This slot (%s) does not belong to the assigned Action", slot->name);
break;
case ActionSlotAssignmentResult::SlotNotSuitable:
BKE_reportf(reports,
RPT_ERROR,
"This slot (%s) is not suitable for this data-block type (%c%c)",
slot->name,
animated_id->name[0],
animated_id->name[1]);
break;
case ActionSlotAssignmentResult::MissingAction:
BKE_report(reports, RPT_ERROR, "Cannot set slot without an assigned Action.");
break;
}
}
@@ -677,9 +662,9 @@ static void rna_KeyingSet_name_set(PointerRNA *ptr, const char *value)
if (adt && adt->action) {
bActionGroup *agrp;
/* lazy check - should really find the F-Curve for the affected path and check its group
* but this way should be faster and work well for most cases, as long as there are no
* conflicts
/* lazy check - should really find the F-Curve for the affected path and check its
* group but this way should be faster and work well for most cases, as long as there
* are no conflicts
*/
for (agrp = static_cast<bActionGroup *>(adt->action->groups.first); agrp;
agrp = agrp->next)
@@ -969,8 +954,8 @@ bool rna_AnimaData_override_apply(Main *bmain, RNAPropertyOverrideApplyContext &
UNUSED_VARS_NDEBUG(ptr_storage, len_dst, len_src, len_storage, opop);
/* AnimData is a special case, since you cannot edit/replace it, it's either existent or not.
* Further more, when an animdata is added to the linked reference later on, the one created for
* the liboverride needs to be 'merged', such that its overridable data is kept. */
* Further more, when an animdata is added to the linked reference later on, the one created
* for the liboverride needs to be 'merged', such that its overridable data is kept. */
AnimData *adt_dst = static_cast<AnimData *>(RNA_property_pointer_get(ptr_dst, prop_dst).data);
AnimData *adt_src = static_cast<AnimData *>(RNA_property_pointer_get(ptr_src, prop_src).data);
@@ -987,9 +972,9 @@ bool rna_AnimaData_override_apply(Main *bmain, RNAPropertyOverrideApplyContext &
return true;
}
else if (adt_dst != nullptr && adt_src != nullptr) {
/* Override had to create an anim data, but now its reference also has one, need to merge them
* by keeping the few overridable data from the liboverride, while using the animdata of the
* reference.
/* Override had to create an anim data, but now its reference also has one, need to merge
* them by keeping the few overridable data from the liboverride, while using the animdata of
* the reference.
*
* Note that this case will not be encountered when the linked reference data already had
* anim data, since there will be no operation for the animdata pointer itself then, only

View File

@@ -468,14 +468,12 @@ static void nlastrip_assign_action_slot(NlaStrip &strip,
{
using namespace blender::animrig;
const nla::ActionSlotAssignmentResult result = nla::assign_action_slot(strip, slot, animated_id);
const ActionSlotAssignmentResult result = nla::assign_action_slot(strip, slot, animated_id);
switch (result) {
case nla::ActionSlotAssignmentResult::OK:
case ActionSlotAssignmentResult::OK:
break;
case nla::ActionSlotAssignmentResult::SlotNotFromAction:
BLI_assert_msg(slot,
"'not a slot from this Action' error can only occur when there is a slot");
case ActionSlotAssignmentResult::SlotNotFromAction:
WM_reportf(RPT_ERROR,
"Slot '%s' cannot be assigned to Strip '%s' as it does not belong to the "
"already-assigned Action '%s'",
@@ -483,14 +481,17 @@ static void nlastrip_assign_action_slot(NlaStrip &strip,
strip.name,
action.id.name + 2);
break;
case nla::ActionSlotAssignmentResult::SlotNotSuitable:
BLI_assert_msg(slot, "'slot not suitable' error can only occur when there is a slot");
case ActionSlotAssignmentResult::SlotNotSuitable:
WM_reportf(RPT_ERROR,
"Action '%s' slot '%s' is not suitable to animate a strip of %s",
action.id.name + 2,
slot->name,
animated_id.name + 2);
break;
case ActionSlotAssignmentResult::MissingAction:
WM_reportf(
RPT_ERROR, "Strip '%s' has no Action assigned, cannot assign Action slot", strip.name);
break;
}
}
@@ -1060,7 +1061,7 @@ static void rna_def_nlastrip(BlenderRNA *brna)
nullptr,
nullptr);
RNA_def_property_ui_text(
prop, "Action Slots", "The list action slots suitable for this NLA strip");
prop, "Action Slots", "The list of action slots suitable for this NLA strip");
# endif /* WITH_ANIM_BAKLAVA */
/* Action extents */