Files
test2/source/blender/animrig/intern/nla_test.cc
Sybren A. Stüvel 12fe0dd0cf 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
2024-09-16 19:31:35 +02:00

172 lines
5.5 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_action.hh"
#include "ANIM_nla.hh"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_fcurve.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_nla.hh"
#include "BKE_object.hh"
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "BLI_listbase.h"
#include "BLI_string_utf8.h"
#include <limits>
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::animrig::nla::tests {
class NLASlottedActionTest : public testing::Test {
public:
Main *bmain;
Action *action;
Object *cube;
static void SetUpTestSuite()
{
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
CLG_init();
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
action = static_cast<Action *>(BKE_id_new(bmain, ID_AC, "ACÄnimåtië"));
action->id.us = 0; /* Nothing references this yet. */
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "Küüübus");
cube->id.us = 0; /* Nothing references this yet. */
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(NLASlottedActionTest, assign_slot_to_nla_strip)
{
ASSERT_EQ(action->id.us, 0);
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
NlaTrack *track = BKE_nlatrack_new_tail(&adt->nla_tracks, false);
/* Create a strip. This automatically assigns the Action, but for now with the old flow. */
NlaStrip *strip = BKE_nlastrip_new(action, cube->id);
BKE_nlatrack_add_strip(track, strip, false);
EXPECT_EQ(strip->action_slot_handle, Slot::unassigned);
EXPECT_STREQ(strip->action_slot_name, "");
/* Unassign the Action that was automatically assigned via BKE_nlastrip_new(). */
nla::unassign_action(*strip, cube->id);
EXPECT_EQ(strip->act, nullptr);
EXPECT_EQ(action->id.us, 0);
/* Assign an Action with an unrelated slot. This should not be picked. */
action->slot_add();
/* Assign the Action. */
EXPECT_FALSE(nla::assign_action(*strip, *action, cube->id));
EXPECT_EQ(strip->action_slot_handle, Slot::unassigned);
EXPECT_STREQ(strip->action_slot_name, "");
EXPECT_EQ(action->id.us, 1);
EXPECT_EQ(strip->act, action);
/* Unassign the Action. */
nla::unassign_action(*strip, cube->id);
EXPECT_EQ(strip->act, nullptr);
EXPECT_EQ(action->id.us, 0);
/* Create a slot for this ID. Assigning the Action should auto-pick it. */
Slot &slot = action->slot_add_for_id(cube->id);
EXPECT_TRUE(nla::assign_action(*strip, *action, cube->id));
EXPECT_EQ(strip->action_slot_handle, slot.handle);
EXPECT_STREQ(strip->action_slot_name, slot.name);
EXPECT_EQ(action->id.us, 1);
EXPECT_EQ(strip->act, action);
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), ActionSlotAssignmentResult::OK);
EXPECT_EQ(strip->action_slot_handle, Slot::unassigned);
EXPECT_STREQ(strip->action_slot_name, slot.name);
EXPECT_EQ(action->id.us, 1);
EXPECT_EQ(strip->act, action);
EXPECT_FALSE(slot.runtime_users().contains(&cube->id));
/* Unassign the Action, then reassign it. It should pick the same slot again. */
nla::unassign_action(*strip, cube->id);
EXPECT_TRUE(nla::assign_action(*strip, *action, cube->id));
EXPECT_EQ(strip->action_slot_handle, slot.handle);
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));
}
TEST_F(NLASlottedActionTest, assign_slot_to_multiple_strips)
{
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
NlaTrack *track = BKE_nlatrack_new_tail(&adt->nla_tracks, false);
/* Create two strips. This automatically assigns the Action, but for now with
* the old flow (so no slots). */
NlaStrip *strip1 = BKE_nlastrip_new(action, cube->id);
strip1->start = 1;
strip1->end = 4;
NlaStrip *strip2 = BKE_nlastrip_new(action, cube->id);
strip1->start = 47;
strip1->end = 327;
ASSERT_TRUE(BKE_nlatrack_add_strip(track, strip1, false));
ASSERT_TRUE(BKE_nlatrack_add_strip(track, strip2, false));
ASSERT_EQ(1, BLI_listbase_count(&adt->nla_tracks));
ASSERT_EQ(2, BLI_listbase_count(&track->strips));
nla::unassign_action(*strip1, cube->id);
nla::unassign_action(*strip2, cube->id);
/* Create an unrelated slot, it should not be auto-picked. */
Slot &slot = action->slot_add();
EXPECT_FALSE(nla::assign_action(*strip1, *action, cube->id));
EXPECT_EQ(strip1->action_slot_handle, Slot::unassigned);
/* Assign the slot 'manually'. */
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), ActionSlotAssignmentResult::OK);
/* The cube should be registered as user of the slot. */
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));
nla::unassign_action(*strip1, cube->id);
/* The cube should still be registered as user of the slot, as there is a 2nd
* strip that references it. */
EXPECT_TRUE(slot.runtime_users().contains(&cube->id));
/* Remove the last use of this slot. */
nla::unassign_action(*strip2, cube->id);
EXPECT_FALSE(slot.runtime_users().contains(&cube->id));
}
} // namespace blender::animrig::nla::tests