Files
test/source/blender/animrig/intern/action_test.cc
Michal Krupa fdaaea6328 Core: Increase MAX_ID_NAME length from 66 to 258 (Blender 5.0)
Change the maximum data-block name from 64 to 256 bytes by increasing MAX_ID_NAME value.

Also increase a few related non-ID data name max size, essentially the action slots identifiers, as these are the primary key used to match an Action's slot to an ID by name.

Other sub-data (bones, modifiers, etc.) lengths are not modified here, as these can be made actual dynamic strings in the future, while keeping (a reasonable level of) forward compatibility, during the course of Blender 5 release cycles.

Implements #137608.

Co-authored-by: Bastien Montagne <bastien@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/137196
2025-06-19 16:39:20 +02:00

2346 lines
93 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_action.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_object.hh"
#include "DNA_action_defaults.h"
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "RNA_access.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include <limits>
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::animrig::tests {
TEST(action, low_level_initialisation)
{
bAction *action = BKE_id_new_nomain<bAction>("NewAction");
EXPECT_NE(action->last_slot_handle, 0)
<< "bAction::last_slot_handle should not be initialised to 0";
BKE_id_free(nullptr, action);
}
class ActionLayersTest : public testing::Test {
public:
Main *bmain;
Action *action;
Object *cube;
Object *suzanne;
Object *bob;
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 = BKE_id_new<Action>(bmain, "ACÄnimåtië");
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "Küüübus");
suzanne = BKE_object_add_only_object(bmain, OB_EMPTY, "OBSuzanne");
bob = BKE_object_add_only_object(bmain, OB_EMPTY, "OBBob");
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(ActionLayersTest, add_layer)
{
Layer &layer = action->layer_add("layer name");
EXPECT_EQ(action->layer(0), &layer);
EXPECT_EQ("layer name", std::string(layer.name));
EXPECT_EQ(1.0f, layer.influence) << "Expected DNA defaults to be used.";
EXPECT_EQ(0, action->layer_active_index)
<< "Expected newly added layer to become the active layer.";
ASSERT_EQ(0, layer.strips().size()) << "Expected newly added layer to have no strip.";
}
TEST_F(ActionLayersTest, add_layer__reset_idroot)
{
/* An empty Action is a valid legacy Action, and thus can have its idroot set to a non-zero
* value. If such an Action gets a layer, it no longer is a valid legacy Action, and thus its
* idtype should be reset to zero. */
action->idroot = ID_CA; /* Fake that this was assigned to a camera data-block. */
ASSERT_NE(0, action->idroot) << "action->idroot should not be zero at the start of this test.";
action->layer_add("layer name");
EXPECT_EQ(0, action->idroot)
<< "action->idroot should get reset when the Action becomes layered.";
}
TEST_F(ActionLayersTest, remove_layer)
{
Layer &layer0 = action->layer_add("Test Læür nul");
Layer &layer1 = action->layer_add("Test Læür één");
Layer &layer2 = action->layer_add("Test Læür twee");
/* Add some strips to check that they are freed correctly too (implicitly by the
* memory leak checker). */
layer0.strip_add(*action, Strip::Type::Keyframe);
layer1.strip_add(*action, Strip::Type::Keyframe);
layer2.strip_add(*action, Strip::Type::Keyframe);
{ /* Test removing a layer that is not owned. */
Action *other_anim = BKE_id_new<Action>(bmain, "ACOtherAnim");
Layer &other_layer = other_anim->layer_add("Another Layer");
EXPECT_FALSE(action->layer_remove(other_layer))
<< "Removing a layer not owned by the Action should be gracefully rejected";
BKE_id_free(bmain, &other_anim->id);
}
EXPECT_TRUE(action->layer_remove(layer1));
EXPECT_EQ(2, action->layers().size());
EXPECT_STREQ(layer0.name, action->layer(0)->name);
EXPECT_STREQ(layer2.name, action->layer(1)->name);
EXPECT_TRUE(action->layer_remove(layer2));
EXPECT_EQ(1, action->layers().size());
EXPECT_STREQ(layer0.name, action->layer(0)->name);
EXPECT_TRUE(action->layer_remove(layer0));
EXPECT_EQ(0, action->layers().size());
}
TEST_F(ActionLayersTest, add_strip)
{
Layer &layer = action->layer_add("Test Læür");
Strip &strip = layer.strip_add(*action, Strip::Type::Keyframe);
ASSERT_EQ(1, layer.strips().size());
EXPECT_EQ(&strip, layer.strip(0));
constexpr float inf = std::numeric_limits<float>::infinity();
EXPECT_EQ(-inf, strip.frame_start) << "Expected strip to be infinite.";
EXPECT_EQ(inf, strip.frame_end) << "Expected strip to be infinite.";
EXPECT_EQ(0, strip.frame_offset) << "Expected infinite strip to have no offset.";
Strip &another_strip = layer.strip_add(*action, Strip::Type::Keyframe);
ASSERT_EQ(2, layer.strips().size());
EXPECT_EQ(&another_strip, layer.strip(1));
EXPECT_EQ(-inf, another_strip.frame_start) << "Expected strip to be infinite.";
EXPECT_EQ(inf, another_strip.frame_end) << "Expected strip to be infinite.";
EXPECT_EQ(0, another_strip.frame_offset) << "Expected infinite strip to have no offset.";
/* Add some keys to check that also the strip data is freed correctly. */
const KeyframeSettings settings = get_keyframe_settings(false);
Slot &slot = action->slot_add();
strip.data<StripKeyframeData>(*action).keyframe_insert(
bmain, slot, {"location", 0}, {1.0f, 47.0f}, settings);
another_strip.data<StripKeyframeData>(*action).keyframe_insert(
bmain, slot, {"location", 0}, {1.0f, 47.0f}, settings);
}
TEST_F(ActionLayersTest, remove_strip)
{
Layer &layer = action->layer_add("Test Læür");
Strip &strip0 = layer.strip_add(*action, Strip::Type::Keyframe);
Strip &strip1 = layer.strip_add(*action, Strip::Type::Keyframe);
Strip &strip2 = layer.strip_add(*action, Strip::Type::Keyframe);
Strip &strip3 = layer.strip_add(*action, Strip::Type::Keyframe);
StripKeyframeData &strip_data0 = strip0.data<StripKeyframeData>(*action);
StripKeyframeData &strip_data1 = strip1.data<StripKeyframeData>(*action);
StripKeyframeData &strip_data2 = strip2.data<StripKeyframeData>(*action);
StripKeyframeData &strip_data3 = strip3.data<StripKeyframeData>(*action);
/* Add some keys to check that also the strip data is freed correctly. */
const KeyframeSettings settings = get_keyframe_settings(false);
Slot &slot = action->slot_add();
strip_data0.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data1.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 48.0f}, settings);
strip_data2.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 49.0f}, settings);
strip_data3.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 50.0f}, settings);
EXPECT_EQ(4, action->strip_keyframe_data().size());
EXPECT_EQ(0, strip0.data_index);
EXPECT_EQ(1, strip1.data_index);
EXPECT_EQ(2, strip2.data_index);
EXPECT_EQ(3, strip3.data_index);
EXPECT_TRUE(layer.strip_remove(*action, strip1));
EXPECT_EQ(3, action->strip_keyframe_data().size());
EXPECT_EQ(3, layer.strips().size());
EXPECT_EQ(&strip0, layer.strip(0));
EXPECT_EQ(&strip2, layer.strip(1));
EXPECT_EQ(&strip3, layer.strip(2));
EXPECT_EQ(0, strip0.data_index);
EXPECT_EQ(2, strip2.data_index);
EXPECT_EQ(1, strip3.data_index); /* Swapped in when removing strip 1's data. */
EXPECT_EQ(&strip_data0, &strip0.data<StripKeyframeData>(*action));
EXPECT_EQ(&strip_data2, &strip2.data<StripKeyframeData>(*action));
EXPECT_EQ(&strip_data3, &strip3.data<StripKeyframeData>(*action));
EXPECT_TRUE(layer.strip_remove(*action, strip2));
EXPECT_EQ(2, action->strip_keyframe_data().size());
EXPECT_EQ(2, layer.strips().size());
EXPECT_EQ(&strip0, layer.strip(0));
EXPECT_EQ(&strip3, layer.strip(1));
EXPECT_EQ(0, strip0.data_index);
EXPECT_EQ(1, strip3.data_index);
EXPECT_EQ(&strip_data0, &strip0.data<StripKeyframeData>(*action));
EXPECT_EQ(&strip_data3, &strip3.data<StripKeyframeData>(*action));
EXPECT_TRUE(layer.strip_remove(*action, strip3));
EXPECT_EQ(1, action->strip_keyframe_data().size());
EXPECT_EQ(1, layer.strips().size());
EXPECT_EQ(&strip0, layer.strip(0));
EXPECT_EQ(0, strip0.data_index);
EXPECT_EQ(&strip_data0, &strip0.data<StripKeyframeData>(*action));
EXPECT_TRUE(layer.strip_remove(*action, strip0));
EXPECT_EQ(0, action->strip_keyframe_data().size());
EXPECT_EQ(0, layer.strips().size());
{ /* Test removing a strip that is not owned. */
Layer &other_layer = action->layer_add("Another Layer");
Strip &other_strip = other_layer.strip_add(*action, Strip::Type::Keyframe);
EXPECT_FALSE(layer.strip_remove(*action, other_strip))
<< "Removing a strip not owned by the layer should be gracefully rejected";
}
}
/* NOTE: this test creates strip instances in a bespoke way for the purpose of
* exercising the strip removal code, because at the time of writing we don't
* have a proper API for creating strip instances. When such an API is added,
* this test should be updated to use it. */
TEST_F(ActionLayersTest, remove_strip_instances)
{
Layer &layer = action->layer_add("Test Læür");
Strip &strip0 = layer.strip_add(*action, Strip::Type::Keyframe);
Strip &strip1 = layer.strip_add(*action, Strip::Type::Keyframe);
Strip &strip2 = layer.strip_add(*action, Strip::Type::Keyframe);
/* Make on of the strips an instance of another. */
strip0.data_index = strip1.data_index;
StripKeyframeData &strip_data_0_1 = strip0.data<StripKeyframeData>(*action);
StripKeyframeData &strip_data_2 = strip2.data<StripKeyframeData>(*action);
/* Add some keys to check that also the strip data is freed correctly. */
const KeyframeSettings settings = get_keyframe_settings(false);
Slot &slot = action->slot_add();
strip_data_0_1.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data_2.keyframe_insert(bmain, slot, {"location", 0}, {1.0f, 48.0f}, settings);
EXPECT_EQ(3, action->strip_keyframe_data().size());
EXPECT_EQ(1, strip0.data_index);
EXPECT_EQ(1, strip1.data_index);
EXPECT_EQ(2, strip2.data_index);
/* Removing an instance should not delete the underlying data as long as there
* is still another strip using it. */
EXPECT_TRUE(layer.strip_remove(*action, strip1));
EXPECT_EQ(3, action->strip_keyframe_data().size());
EXPECT_EQ(2, layer.strips().size());
EXPECT_EQ(&strip0, layer.strip(0));
EXPECT_EQ(&strip2, layer.strip(1));
EXPECT_EQ(1, strip0.data_index);
EXPECT_EQ(2, strip2.data_index);
EXPECT_EQ(&strip_data_0_1, &strip0.data<StripKeyframeData>(*action));
EXPECT_EQ(&strip_data_2, &strip2.data<StripKeyframeData>(*action));
/* Removing the last user of strip data should also delete the data. */
EXPECT_TRUE(layer.strip_remove(*action, strip0));
EXPECT_EQ(2, action->strip_keyframe_data().size());
EXPECT_EQ(1, layer.strips().size());
EXPECT_EQ(&strip2, layer.strip(0));
EXPECT_EQ(1, strip2.data_index);
EXPECT_EQ(&strip_data_2, &strip2.data<StripKeyframeData>(*action));
}
TEST_F(ActionLayersTest, add_slot)
{
{ /* Creating an 'unused' Slot should just be called 'Slot'. */
Slot &slot = action->slot_add();
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, action->last_slot_handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, slot.handle);
EXPECT_STREQ("XXSlot", slot.identifier);
EXPECT_EQ(0, slot.idtype);
}
{ /* Creating a Slot for a specific ID should name it after the ID. */
Slot &slot = action->slot_add_for_id(cube->id);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, action->last_slot_handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, slot.handle);
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)
{
/* An empty Action is a valid legacy Action, and thus can have its idroot set
* to a non-zero value. If such an Action gets a slot, it no longer is a
* valid legacy Action, and thus its idtype should be reset to zero. */
action->idroot = ID_CA; /* Fake that this was assigned to a camera data-block. */
ASSERT_NE(0, action->idroot) << "action->idroot should not be zero at the start of this test.";
action->slot_add();
EXPECT_EQ(0, action->idroot)
<< "action->idroot should get reset when the Action becomes layered.";
}
TEST_F(ActionLayersTest, add_slot_multiple)
{
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action->slot_add();
EXPECT_TRUE(assign_action(action, cube->id));
EXPECT_EQ(assign_action_slot(&slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_TRUE(assign_action(action, suzanne->id));
EXPECT_EQ(assign_action_slot(&slot_suzanne, suzanne->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, action->last_slot_handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, slot_cube.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, slot_suzanne.handle);
}
TEST_F(ActionLayersTest, slot_remove)
{
{ /* Canary test: removing a just-created slot on an otherwise empty Action should work. */
Slot &slot = action->slot_add();
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, slot.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, action->last_slot_handle);
EXPECT_TRUE(action->slot_remove(slot));
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 1, action->last_slot_handle)
<< "Removing a slot should not change the last-used slot handle.";
EXPECT_EQ(0, action->slot_array_num);
}
{ /* Removing a non-existing slot should return false. */
Slot slot;
EXPECT_FALSE(action->slot_remove(slot));
}
{ /* Removing a slot should remove its Channelbag. */
Slot &slot = action->slot_add();
const slot_handle_t slot_handle = slot.handle;
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, slot.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, action->last_slot_handle);
/* Create an F-Curve in a Channelbag for the slot. */
action->layer_keystrip_ensure();
StripKeyframeData &strip_data = action->layer(0)->strip(0)->data<StripKeyframeData>(*action);
Channelbag &channelbag = strip_data.channelbag_for_slot_ensure(slot);
channelbag.fcurve_create_unique(bmain, {"location", 1});
/* Remove the slot. */
EXPECT_TRUE(action->slot_remove(slot));
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 2, action->last_slot_handle)
<< "Removing a slot should not change the last-used slot handle.";
EXPECT_EQ(0, action->slot_array_num);
/* Check that its channelbag is gone. */
Channelbag *found_cbag = strip_data.channelbag_for_slot(slot_handle);
EXPECT_EQ(found_cbag, nullptr);
}
{ /* Removing one slot should leave the other two in place. */
Slot &slot1 = action->slot_add();
Slot &slot2 = action->slot_add();
Slot &slot3 = action->slot_add();
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 3, slot1.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 4, slot2.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 5, slot3.handle);
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 5, action->last_slot_handle);
/* For referencing the slot handle after the slot is removed. */
const slot_handle_t slot2_handle = slot2.handle;
/* Create a Channel-bag for each slot. */
action->layer_keystrip_ensure();
StripKeyframeData &strip_data = action->layer(0)->strip(0)->data<StripKeyframeData>(*action);
strip_data.channelbag_for_slot_ensure(slot1);
strip_data.channelbag_for_slot_ensure(slot2);
strip_data.channelbag_for_slot_ensure(slot3);
/* Remove the slot. */
EXPECT_TRUE(action->slot_remove(slot2));
EXPECT_EQ(DNA_DEFAULT_ACTION_LAST_SLOT_HANDLE + 5, action->last_slot_handle);
/* Check the correct slot + channel-bag are removed. */
EXPECT_EQ(action->slot_for_handle(slot1.handle), &slot1);
EXPECT_EQ(action->slot_for_handle(slot2_handle), nullptr);
EXPECT_EQ(action->slot_for_handle(slot3.handle), &slot3);
EXPECT_NE(strip_data.channelbag_for_slot(slot1.handle), nullptr);
EXPECT_EQ(strip_data.channelbag_for_slot(slot2_handle), nullptr);
EXPECT_NE(strip_data.channelbag_for_slot(slot3.handle), nullptr);
}
{ /* Removing an in-use slot doesn't un-assign it from its users.
* This is not that important, but it covers the current behavior. */
Slot &slot = action->slot_add_for_id(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, removed_slot_handle);
}
{ /* Creating a slot after removing one should not reuse its handle. */
action->last_slot_handle = 3; /* To create independence between sub-tests. */
Slot &slot1 = action->slot_add();
ASSERT_EQ(4, slot1.handle);
ASSERT_EQ(4, action->last_slot_handle);
ASSERT_TRUE(action->slot_remove(slot1));
Slot &slot2 = action->slot_add();
EXPECT_EQ(5, slot2.handle);
EXPECT_EQ(5, action->last_slot_handle);
}
}
TEST_F(ActionLayersTest, slot_move_to_index)
{
Slot &slot_a = action->slot_add_for_id_type(ID_ME);
Slot &slot_b = action->slot_add_for_id_type(ID_CA);
Slot &slot_cube = action->slot_add_for_id(cube->id);
Slot &slot_suzanne = action->slot_add_for_id(suzanne->id);
assign_action_and_slot(action, &slot_cube, cube->id);
assign_action_and_slot(action, &slot_suzanne, suzanne->id);
const slot_handle_t handle_a = slot_a.handle;
const slot_handle_t handle_b = slot_b.handle;
const slot_handle_t handle_cube = slot_cube.handle;
const slot_handle_t handle_suzanne = slot_suzanne.handle;
ASSERT_EQ(action->slot(0)->handle, handle_a);
ASSERT_EQ(action->slot(0)->idtype_string(), "ME");
ASSERT_EQ(action->slot(1)->handle, handle_b);
ASSERT_EQ(action->slot(1)->idtype_string(), "CA");
ASSERT_EQ(action->slot(2)->handle, handle_cube);
ASSERT_EQ(action->slot(2)->idtype_string(), "OB");
ASSERT_EQ(action->slot(2)->users(*bmain)[0], &cube->id);
ASSERT_EQ(action->slot(3)->handle, handle_suzanne);
ASSERT_EQ(action->slot(3)->idtype_string(), "OB");
ASSERT_EQ(action->slot(3)->users(*bmain)[0], &suzanne->id);
/* First "move" a slot to its own location, which should do nothing. */
action->slot_move_to_index(slot_b, 1);
EXPECT_EQ(action->slot(0)->handle, handle_a);
EXPECT_EQ(action->slot(0)->idtype_string(), "ME");
EXPECT_EQ(action->slot(1)->handle, handle_b);
EXPECT_EQ(action->slot(1)->idtype_string(), "CA");
EXPECT_EQ(action->slot(2)->handle, handle_cube);
EXPECT_EQ(action->slot(2)->idtype_string(), "OB");
EXPECT_EQ(action->slot(2)->users(*bmain)[0], &cube->id);
EXPECT_EQ(action->slot(3)->handle, handle_suzanne);
EXPECT_EQ(action->slot(3)->idtype_string(), "OB");
EXPECT_EQ(action->slot(3)->users(*bmain)[0], &suzanne->id);
/* Then move slots around in various ways. */
action->slot_move_to_index(slot_a, 2);
EXPECT_EQ(action->slot(0)->handle, handle_b);
EXPECT_EQ(action->slot(0)->idtype_string(), "CA");
EXPECT_EQ(action->slot(1)->handle, handle_cube);
EXPECT_EQ(action->slot(1)->idtype_string(), "OB");
EXPECT_EQ(action->slot(1)->users(*bmain)[0], &cube->id);
EXPECT_EQ(action->slot(2)->handle, handle_a);
EXPECT_EQ(action->slot(2)->idtype_string(), "ME");
EXPECT_EQ(action->slot(3)->handle, handle_suzanne);
EXPECT_EQ(action->slot(3)->idtype_string(), "OB");
EXPECT_EQ(action->slot(3)->users(*bmain)[0], &suzanne->id);
action->slot_move_to_index(slot_suzanne, 1);
EXPECT_EQ(action->slot(0)->handle, handle_b);
EXPECT_EQ(action->slot(0)->idtype_string(), "CA");
EXPECT_EQ(action->slot(1)->handle, handle_suzanne);
EXPECT_EQ(action->slot(1)->idtype_string(), "OB");
EXPECT_EQ(action->slot(1)->users(*bmain)[0], &suzanne->id);
EXPECT_EQ(action->slot(2)->handle, handle_cube);
EXPECT_EQ(action->slot(2)->idtype_string(), "OB");
EXPECT_EQ(action->slot(2)->users(*bmain)[0], &cube->id);
EXPECT_EQ(action->slot(3)->handle, handle_a);
EXPECT_EQ(action->slot(3)->idtype_string(), "ME");
action->slot_move_to_index(slot_cube, 3);
EXPECT_EQ(action->slot(0)->handle, handle_b);
EXPECT_EQ(action->slot(0)->idtype_string(), "CA");
EXPECT_EQ(action->slot(1)->handle, handle_suzanne);
EXPECT_EQ(action->slot(1)->idtype_string(), "OB");
EXPECT_EQ(action->slot(1)->users(*bmain)[0], &suzanne->id);
EXPECT_EQ(action->slot(2)->handle, handle_a);
EXPECT_EQ(action->slot(2)->idtype_string(), "ME");
EXPECT_EQ(action->slot(3)->handle, handle_cube);
EXPECT_EQ(action->slot(3)->idtype_string(), "OB");
EXPECT_EQ(action->slot(3)->users(*bmain)[0], &cube->id);
action->slot_move_to_index(slot_suzanne, 0);
EXPECT_EQ(action->slot(0)->handle, handle_suzanne);
EXPECT_EQ(action->slot(0)->idtype_string(), "OB");
EXPECT_EQ(action->slot(0)->users(*bmain)[0], &suzanne->id);
EXPECT_EQ(action->slot(1)->handle, handle_b);
EXPECT_EQ(action->slot(1)->idtype_string(), "CA");
EXPECT_EQ(action->slot(2)->handle, handle_a);
EXPECT_EQ(action->slot(2)->idtype_string(), "ME");
EXPECT_EQ(action->slot(3)->handle, handle_cube);
EXPECT_EQ(action->slot(3)->idtype_string(), "OB");
EXPECT_EQ(action->slot(3)->users(*bmain)[0], &cube->id);
}
TEST_F(ActionLayersTest, action_assign_id)
{
/* Assign to the only, 'virgin' Slot, should always work. */
Slot &slot_cube = action->slot_add();
ASSERT_NE(nullptr, slot_cube.runtime);
ASSERT_STREQ(slot_cube.identifier, "XXSlot");
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.identifier, "OBSlot");
EXPECT_STREQ(slot_cube.identifier, cube->adt->last_slot_identifier)
<< "The slot identifier should be copied to the adt";
EXPECT_TRUE(slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Cube to be registered as animated by its slot.";
/* Assign another ID to the same Slot. */
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, suzanne->id),
ActionSlotAssignmentResult::OK);
EXPECT_STREQ(slot_cube.identifier, "OBSlot");
EXPECT_STREQ(slot_cube.identifier, cube->adt->last_slot_identifier)
<< "The slot identifier should be copied to the adt";
EXPECT_TRUE(slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Suzanne to be registered as animated by the Cube slot.";
{ /* Assign Cube to another action+slot without unassigning first. */
Action *another_anim = BKE_id_new<Action>(bmain, "ACOtherAnim");
Slot &another_slot = another_anim->slot_add();
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_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";
EXPECT_FALSE(slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Cube to no longer be registered as animated by the Cube slot.";
EXPECT_TRUE(slot_cube_2.users(*bmain).contains(&cube->id))
<< "Expecting Cube to be registered as animated by the 'cube_2' slot.";
}
{ /* Unassign the Action. */
const int user_count_pre = action->id.us;
EXPECT_TRUE(unassign_action(cube->id));
ASSERT_EQ(action->id.us, user_count_pre - 1)
<< "Unassigning an Action should lower its user count";
ASSERT_EQ(2, action->slots().size()) << "Expecting the Action to have two Slots";
EXPECT_FALSE(action->slot(0)->users(*bmain).contains(&cube->id))
<< "Expecting Cube to no longer be registered as animated by any slot.";
EXPECT_FALSE(action->slot(1)->users(*bmain).contains(&cube->id))
<< "Expecting Cube to no longer be registered as animated by any slot.";
}
/* Assign Cube to another 'virgin' slot. This should not cause a name
* collision between the Slots. */
Slot &another_slot_cube = action->slot_add();
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.identifier) << "The slot should be uniquely named";
EXPECT_STREQ("OBSlot.002", cube->adt->last_slot_identifier)
<< "The slot identifier should be copied to the adt";
EXPECT_TRUE(another_slot_cube.users(*bmain).contains(&cube->id))
<< "Expecting Cube to be registered as animated by the 'another_slot_cube' slot.";
/* 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"));
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.";
BKE_id_free(nullptr, mesh);
}
TEST_F(ActionLayersTest, rename_slot)
{
Slot &slot_cube = action->slot_add();
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.identifier);
EXPECT_STREQ(slot_cube.identifier, cube->adt->last_slot_identifier)
<< "The slot identifier should be copied to the adt";
action->slot_identifier_define(slot_cube, "OBNew Slot Name");
EXPECT_STREQ("OBNew Slot Name", slot_cube.identifier);
/* At this point the slot identifier will not have been copied to the cube
* AnimData. However, I don't want to test for that here, as it's not exactly
* desirable behavior, but more of a side-effect of the current
* implementation. */
action->slot_identifier_propagate(*bmain, slot_cube);
EXPECT_STREQ("OBNew Slot Name", cube->adt->last_slot_identifier);
/* Rename via the display name, which should propagate to the ADT. */
action->slot_display_name_set(*bmain, slot_cube, "Slot's New Display Name");
EXPECT_STREQ("OBSlot's New Display Name", slot_cube.identifier);
EXPECT_STREQ("OBSlot's New Display Name", cube->adt->last_slot_identifier);
/* Finally, do another rename, do NOT call the propagate function, then
* unassign. This should still result in the correct slot name being stored
* on the ADT. */
action->slot_identifier_define(slot_cube, "OBEven Newer Name");
EXPECT_TRUE(unassign_action(cube->id));
EXPECT_STREQ("OBEven Newer Name", cube->adt->last_slot_identifier);
}
TEST_F(ActionLayersTest, slot_identifier_ensure_prefix)
{
class AccessibleSlot : public Slot {
public:
void identifier_ensure_prefix()
{
Slot::identifier_ensure_prefix();
}
};
Slot &raw_slot = action->slot_add();
AccessibleSlot &slot = static_cast<AccessibleSlot &>(raw_slot);
ASSERT_STREQ("XXSlot", slot.identifier);
ASSERT_EQ(0, slot.idtype);
/* Check defaults, idtype zeroed. */
slot.identifier_ensure_prefix();
EXPECT_STREQ("XXSlot", slot.identifier);
/* idtype CA, default name. */
slot.idtype = ID_CA;
slot.identifier_ensure_prefix();
EXPECT_STREQ("CASlot", slot.identifier);
/* idtype ME, explicit name of other idtype. */
action->slot_identifier_define(slot, "CANewName");
slot.idtype = ID_ME;
slot.identifier_ensure_prefix();
EXPECT_STREQ("MENewName", slot.identifier);
/* Zeroing out idtype. */
slot.idtype = 0;
slot.identifier_ensure_prefix();
EXPECT_STREQ("XXNewName", slot.identifier);
}
TEST_F(ActionLayersTest, slot_identifier_prefix)
{
Slot &slot = action->slot_add();
EXPECT_EQ("XX", slot.idtype_string());
EXPECT_EQ("XX", slot.identifier_prefix());
slot.idtype = ID_CA;
EXPECT_EQ("CA", slot.idtype_string());
EXPECT_EQ("XX", slot.identifier_prefix());
slot.identifier_ensure_prefix();
EXPECT_EQ("CA", slot.idtype_string());
EXPECT_EQ("CA", slot.identifier_prefix());
}
TEST_F(ActionLayersTest, rename_slot_identifier_collision)
{
Slot &slot1 = action->slot_add();
Slot &slot2 = action->slot_add();
action->slot_identifier_define(slot1, "New Slot Name");
action->slot_identifier_define(slot2, "New Slot Name");
EXPECT_STREQ("New Slot Name", slot1.identifier);
EXPECT_STREQ("New Slot Name.001", slot2.identifier);
}
TEST_F(ActionLayersTest, generic_slot_for_autoassign)
{
/* ===
* Empty case, no slots exist yet and the ID doesn't even have an AnimData. */
EXPECT_EQ(nullptr, generic_slot_for_autoassign(cube->id, *this->action, ""));
/* ===
* Slot exists with the same name & type as the ID, but the ID doesn't have any AnimData yet.
* These should nevertheless be matched up. */
Slot &slot = action->slot_add();
slot.handle = 327;
STRNCPY_UTF8(slot.identifier, "OBKüüübus");
slot.idtype = GS(cube->id.name);
EXPECT_EQ(&slot, generic_slot_for_autoassign(cube->id, *this->action, ""));
/* ===
* Slot exists with the same name & type as the ID, and the ID has an AnimData with the same
* slot identifier, but a different slot_handle. Since the Action has not yet been
* assigned to this ID, the slot_handle should be ignored, and the slot identifier used for
* matching. */
/* Create a slot with a handle that should be ignored. */
Slot &other_slot = action->slot_add();
other_slot.handle = 47;
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
adt->action = nullptr;
/* Configure adt to use the handle of one slot, and the identifier of the other. */
adt->slot_handle = other_slot.handle;
STRNCPY_UTF8(adt->last_slot_identifier, slot.identifier);
EXPECT_EQ(&slot,
generic_slot_for_autoassign(cube->id, *this->action, cube->adt->last_slot_identifier));
/* ===
* Assigned slot info exists, but doesn't match anything in the action data of the cube. This
* should fall back to using the ID name. */
adt->slot_handle = 161;
STRNCPY_UTF8(adt->last_slot_identifier, "¿¿What's this??");
EXPECT_EQ(&slot,
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" behavior, 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. */
EXPECT_EQ(nullptr, action->slot_active_get());
action->slot_active_set(Slot::unassigned);
EXPECT_EQ(nullptr, action->slot_active_get());
}
{ /* Single slot case. */
Slot &slot_cube = *assign_action_ensure_slot_for_keying(*action, cube->id);
EXPECT_EQ(nullptr, action->slot_active_get())
<< "Adding the first slot should not change what is the active slot.";
action->slot_active_set(slot_cube.handle);
EXPECT_EQ(&slot_cube, action->slot_active_get())
<< "It should be possible to activate the only available slot";
EXPECT_TRUE(slot_cube.is_active());
action->slot_active_set(Slot::unassigned);
EXPECT_EQ(nullptr, action->slot_active_get())
<< "It should be possible to de-activate the only available slot";
EXPECT_FALSE(slot_cube.is_active());
}
{
/* Multiple slots case. */
Slot &slot_cube = *action->slot(0);
action->slot_active_set(slot_cube.handle);
Slot &slot_suz = *assign_action_ensure_slot_for_keying(*action, suzanne->id);
Slot &slot_bob = *assign_action_ensure_slot_for_keying(*action, bob->id);
EXPECT_EQ(&slot_cube, action->slot_active_get())
<< "Adding a subsequent slot should not change what is the active slot.";
EXPECT_TRUE(slot_cube.is_active());
action->slot_active_set(slot_suz.handle);
EXPECT_EQ(&slot_suz, action->slot_active_get());
EXPECT_FALSE(slot_cube.is_active());
EXPECT_TRUE(slot_suz.is_active());
EXPECT_FALSE(slot_bob.is_active());
action->slot_active_set(slot_bob.handle);
EXPECT_EQ(&slot_bob, action->slot_active_get());
EXPECT_FALSE(slot_cube.is_active());
EXPECT_FALSE(slot_suz.is_active());
EXPECT_TRUE(slot_bob.is_active());
action->slot_active_set(Slot::unassigned);
EXPECT_EQ(nullptr, action->slot_active_get());
EXPECT_FALSE(slot_cube.is_active());
EXPECT_FALSE(slot_suz.is_active());
EXPECT_FALSE(slot_bob.is_active());
}
}
TEST_F(ActionLayersTest, assign_action_ensure_slot_for_keying)
{
{ /* Slotless Action, should create a typed slot. */
Action &action = action_add(*this->bmain, "ACEmpty");
Slot *chosen_slot = assign_action_ensure_slot_for_keying(action, cube->id);
ASSERT_NE(nullptr, chosen_slot);
EXPECT_EQ(ID_OB, chosen_slot->idtype);
EXPECT_STREQ("OBKüüübus", chosen_slot->identifier);
}
{ /* Single slot with same name as ID, Action not yet assigned.
* Should assign the Action and the slot. */
Action &action = action_add(*this->bmain, "ACAction");
const Slot &slot_for_id = action.slot_add_for_id(cube->id);
Slot *chosen_slot = assign_action_ensure_slot_for_keying(action, cube->id);
ASSERT_NE(nullptr, chosen_slot);
EXPECT_EQ(&slot_for_id, chosen_slot) << "The expected slot should be chosen";
EXPECT_EQ(cube->adt->action, &action) << "The Action should be assigned";
EXPECT_EQ(cube->adt->slot_handle, chosen_slot->handle) << "The chosen slot should be assigned";
}
{ /* Single slot with same name as ID, Action already assigned but not the slot.
* Should create new slot. */
Action &action = action_add(*this->bmain, "ACAction");
const Slot &slot_for_id = action.slot_add_for_id(cube->id);
ASSERT_EQ(ActionSlotAssignmentResult::OK, assign_action_and_slot(&action, nullptr, cube->id));
Slot *chosen_slot = assign_action_ensure_slot_for_keying(action, cube->id);
ASSERT_NE(nullptr, chosen_slot);
EXPECT_NE(&slot_for_id, chosen_slot) << "A new slot should be chosen";
EXPECT_STREQ("OBKüüübus.001", chosen_slot->identifier);
EXPECT_EQ(cube->adt->action, &action) << "The Action should be assigned";
EXPECT_EQ(cube->adt->slot_handle, chosen_slot->handle) << "The chosen slot should be assigned";
}
{ /* Single untyped slot, Action already assigned but not the slot. Should assign the untyped
* slot. */
Action &action = action_add(*this->bmain, "ACAction");
/* Assign the Action before adding the untyped slot, otherwise the slot gets assigned & thus
* typed. */
ASSERT_EQ(ActionSlotAssignmentResult::OK, assign_action_and_slot(&action, nullptr, cube->id));
Slot &untyped_slot = action.slot_add();
action.slot_identifier_define(untyped_slot, "XXJust A Slot");
Slot *chosen_slot = assign_action_ensure_slot_for_keying(action, cube->id);
ASSERT_NE(nullptr, chosen_slot);
EXPECT_EQ(&untyped_slot, chosen_slot) << "The untyped slot should be chosen";
EXPECT_TRUE(untyped_slot.has_idtype()) << "Slot should have gotten an ID type";
EXPECT_STREQ("OBJust A Slot", untyped_slot.identifier);
EXPECT_EQ(cube->adt->action, &action) << "The Action should be assigned";
EXPECT_EQ(cube->adt->slot_handle, chosen_slot->handle) << "The chosen slot should be assigned";
}
}
TEST_F(ActionLayersTest, strip)
{
constexpr float inf = std::numeric_limits<float>::infinity();
Layer &layer0 = action->layer_add("Test Læür nul");
Strip &strip = layer0.strip_add(*action, Strip::Type::Keyframe);
strip.resize(-inf, inf);
EXPECT_TRUE(strip.contains_frame(0.0f));
EXPECT_TRUE(strip.contains_frame(-100000.0f));
EXPECT_TRUE(strip.contains_frame(100000.0f));
EXPECT_TRUE(strip.is_last_frame(inf));
strip.resize(1.0f, 2.0f);
EXPECT_FALSE(strip.contains_frame(0.0f))
<< "Strip should not contain frames before its first frame";
EXPECT_TRUE(strip.contains_frame(1.0f)) << "Strip should contain its first frame.";
EXPECT_TRUE(strip.contains_frame(2.0f)) << "Strip should contain its last frame.";
EXPECT_FALSE(strip.contains_frame(2.0001f))
<< "Strip should not contain frames after its last frame";
EXPECT_FALSE(strip.is_last_frame(1.0f));
EXPECT_FALSE(strip.is_last_frame(1.5f));
EXPECT_FALSE(strip.is_last_frame(1.9999f));
EXPECT_TRUE(strip.is_last_frame(2.0f));
EXPECT_FALSE(strip.is_last_frame(2.0001f));
/* Same test as above, but with much larger end frame number. This is 2 hours at 24 FPS. */
strip.resize(1.0f, 172800.0f);
EXPECT_TRUE(strip.contains_frame(172800.0f)) << "Strip should contain its last frame.";
EXPECT_FALSE(strip.contains_frame(172800.1f))
<< "Strip should not contain frames after its last frame";
/* You can't get much closer to the end frame before it's considered equal. */
EXPECT_FALSE(strip.is_last_frame(172799.925f));
EXPECT_TRUE(strip.is_last_frame(172800.0f));
EXPECT_FALSE(strip.is_last_frame(172800.075f));
}
TEST_F(ActionLayersTest, KeyframeStrip__keyframe_insert)
{
Slot &slot = action->slot_add();
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(*action, Strip::Type::Keyframe);
StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
const KeyframeSettings settings = get_keyframe_settings(false);
SingleKeyingResult result_loc_a = strip_data.keyframe_insert(
bmain, slot, {"location", 0}, {1.0f, 47.0f}, settings);
ASSERT_EQ(SingleKeyingResult::SUCCESS, result_loc_a)
<< "Expected keyframe insertion to be successful";
/* Check the strip was created correctly, with the channels for the slot. */
ASSERT_EQ(1, strip_data.channelbags().size());
Channelbag *channels = strip_data.channelbag(0);
EXPECT_EQ(slot.handle, channels->slot_handle);
/* Insert a second key, should insert into the same FCurve as before. */
SingleKeyingResult result_loc_b = strip_data.keyframe_insert(
bmain, slot, {"location", 0}, {5.0f, 47.1f}, settings);
EXPECT_EQ(SingleKeyingResult::SUCCESS, result_loc_b);
ASSERT_EQ(1, channels->fcurves().size()) << "Expect insertion with the same (slot/rna "
"path/array index) tuple to go into the same FCurve";
EXPECT_EQ(2, channels->fcurves()[0]->totvert)
<< "Expect insertion with the same (slot/rna path/array index) tuple to go into the same "
"FCurve";
EXPECT_EQ(47.0f, evaluate_fcurve(channels->fcurves()[0], 1.0f));
EXPECT_EQ(47.1f, evaluate_fcurve(channels->fcurves()[0], 5.0f));
/* Insert another key for another property, should create another FCurve. */
SingleKeyingResult result_rot = strip_data.keyframe_insert(
bmain, slot, {"rotation_quaternion", 0}, {1.0f, 0.25f}, settings);
EXPECT_EQ(SingleKeyingResult::SUCCESS, result_rot);
ASSERT_EQ(2, channels->fcurves().size()) << "Expected a second FCurve to be created.";
EXPECT_EQ(2, channels->fcurves()[0]->totvert);
EXPECT_EQ(1, channels->fcurves()[1]->totvert);
}
TEST_F(ActionLayersTest, is_action_assignable_to)
{
EXPECT_TRUE(is_action_assignable_to(nullptr, ID_OB))
<< "nullptr Actions should be assignable to any type.";
EXPECT_TRUE(is_action_assignable_to(nullptr, ID_CA))
<< "nullptr Actions should be assignable to any type.";
EXPECT_TRUE(is_action_assignable_to(action, ID_OB))
<< "Empty Actions should be assignable to any type.";
EXPECT_TRUE(is_action_assignable_to(action, ID_CA))
<< "Empty Actions should be assignable to any type.";
/* Make the Action a legacy one. */
FCurve fake_fcurve;
BLI_addtail(&action->curves, &fake_fcurve);
ASSERT_FALSE(action->is_empty());
ASSERT_TRUE(action->is_action_legacy());
ASSERT_EQ(0, action->idroot);
EXPECT_TRUE(is_action_assignable_to(action, ID_OB))
<< "Legacy Actions with idroot=0 should be assignable to any type.";
EXPECT_TRUE(is_action_assignable_to(action, ID_CA))
<< "Legacy Actions with idroot=0 should be assignable to any type.";
/* Set the legacy idroot. */
action->idroot = ID_CA;
EXPECT_FALSE(is_action_assignable_to(action, ID_OB))
<< "Legacy Actions with idroot=ID_CA should NOT be assignable to ID_OB.";
EXPECT_TRUE(is_action_assignable_to(action, ID_CA))
<< "Legacy Actions with idroot=CA should be assignable to ID_CA.";
/* Make the Action a layered one. */
BLI_poptail(&action->curves);
action->layer_add("layer");
ASSERT_EQ(0, action->idroot) << "Adding a layer should clear the idroot.";
EXPECT_TRUE(is_action_assignable_to(action, ID_OB))
<< "Layered Actions should be assignable to any type.";
EXPECT_TRUE(is_action_assignable_to(action, ID_CA))
<< "Layered Actions should be assignable to any type.";
}
TEST_F(ActionLayersTest, action_slot_get_id_for_keying__empty_action)
{
EXPECT_TRUE(assign_action(action, cube->id));
/* Double-check that the action is considered empty for the test. */
EXPECT_TRUE(action->is_empty());
/* None should return an ID, since there are no slots yet which could have this ID assigned.
* Assignment of the Action itself (cube) shouldn't matter. */
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, 0, &cube->id));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, 0, nullptr));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, 0, &suzanne->id));
}
TEST_F(ActionLayersTest, action_slot_get_id_for_keying__legacy_action)
{
FCurve *fcurve = action_fcurve_ensure_legacy(bmain, action, nullptr, nullptr, {"location", 0});
EXPECT_FALSE(fcurve == nullptr);
EXPECT_TRUE(assign_action(action, cube->id));
/* Double-check that the action is considered legacy for the test. */
EXPECT_TRUE(action->is_action_legacy());
/* A `primary_id` that uses the action should get returned. Every other case
* should return nullptr. */
EXPECT_EQ(&cube->id, action_slot_get_id_for_keying(*bmain, *action, 0, &cube->id));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, 0, nullptr));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, 0, &suzanne->id));
}
TEST_F(ActionLayersTest, action_slot_get_id_for_keying__layered_action)
{
Slot &slot = action->slot_add();
/* Double-check that the action is considered layered for the test. */
EXPECT_TRUE(action->is_action_layered());
/* A slot with no users should never return a user. */
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, slot.handle, nullptr));
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. */
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. */
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));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, slot.handle, nullptr));
EXPECT_EQ(nullptr, action_slot_get_id_for_keying(*bmain, *action, slot.handle, &bob->id));
}
TEST_F(ActionLayersTest, conversion_to_layered)
{
EXPECT_TRUE(action->is_empty());
FCurve *legacy_fcu_0 = action_fcurve_ensure_legacy(
bmain, action, "Test", nullptr, {"location", 0});
FCurve *legacy_fcu_1 = action_fcurve_ensure_legacy(
bmain, action, "Test", nullptr, {"location", 1});
KeyframeSettings settings;
settings.handle = HD_AUTO;
settings.interpolation = BEZT_IPO_BEZ;
settings.keyframe_type = BEZT_KEYTYPE_KEYFRAME;
insert_vert_fcurve(legacy_fcu_0, {0, 0}, settings, INSERTKEY_NOFLAGS);
insert_vert_fcurve(legacy_fcu_0, {1, 1}, settings, INSERTKEY_NOFLAGS);
add_fmodifier(&legacy_fcu_1->modifiers, FMODIFIER_TYPE_NOISE, legacy_fcu_1);
Action *converted = convert_to_layered_action(*bmain, *action);
ASSERT_TRUE(converted != action);
EXPECT_STREQ(converted->id.name, "ACACÄnimåtië_layered");
Strip *strip = converted->layer(0)->strip(0);
StripKeyframeData &strip_data = strip->data<StripKeyframeData>(*converted);
Channelbag *bag = strip_data.channelbag(0);
ASSERT_EQ(bag->fcurve_array_num, 2);
ASSERT_EQ(bag->fcurve_array[0]->totvert, 2);
ASSERT_EQ(BLI_listbase_count(&action->groups), 1);
ASSERT_EQ(BLI_listbase_count(&converted->groups), 0);
ASSERT_EQ(bag->channel_groups().size(), 1);
bActionGroup *group = bag->channel_group(0);
ASSERT_EQ(group->fcurve_range_length, 2);
ASSERT_STREQ(group->name, "Test");
ASSERT_TRUE(bag->fcurve_array[0]->modifiers.first == nullptr);
ASSERT_TRUE(bag->fcurve_array[1]->modifiers.first != nullptr);
constexpr char id_name_max[] =
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3_____";
BLI_STATIC_ASSERT(std::string::traits_type::length(id_name_max) == MAX_ID_NAME - 2 - 1,
"Wrong 'max length' name");
Action *long_name_action = BKE_id_new<Action>(bmain, id_name_max);
action_fcurve_ensure_legacy(bmain, long_name_action, "Long", nullptr, {"location", 0});
/* The long name is shortened to make space for "_layered". */
constexpr char id_name_max_converted[] =
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAME-3______"
"name_for_an_action_that_is_exactly_255_bytes_MAX_ID_NAM_layered";
BLI_STATIC_ASSERT(std::string::traits_type::length(id_name_max_converted) == MAX_ID_NAME - 2 - 1,
"Wrong 'max length' name");
converted = convert_to_layered_action(*bmain, *long_name_action);
EXPECT_STREQ(BKE_id_name(converted->id), id_name_max_converted);
}
TEST_F(ActionLayersTest, conversion_to_layered_action_groups)
{
EXPECT_TRUE(action->is_empty());
action_fcurve_ensure_legacy(bmain, action, "Test", nullptr, {"location", 0});
action_fcurve_ensure_legacy(bmain, action, "Test", nullptr, {"rotation_euler", 1});
action_fcurve_ensure_legacy(bmain, action, "Test_Two", nullptr, {"scale", 1});
action_fcurve_ensure_legacy(bmain, action, "Test_Three", nullptr, {"show_name", 1});
action_fcurve_ensure_legacy(bmain, action, "Test_Rename", nullptr, {"show_axis", 1});
bActionGroup *rename_group = static_cast<bActionGroup *>(BLI_findlink(&action->groups, 3));
ASSERT_NE(rename_group, nullptr);
ASSERT_STREQ(rename_group->name, "Test_Rename");
/* Forcing a duplicate name which was allowed by legacy actions. */
STRNCPY_UTF8(rename_group->name, "Test");
Action *converted = convert_to_layered_action(*bmain, *action);
Strip *strip = converted->layer(0)->strip(0);
StripKeyframeData &strip_data = strip->data<StripKeyframeData>(*converted);
Channelbag *bag = strip_data.channelbag(0);
ASSERT_EQ(BLI_listbase_count(&converted->groups), 0);
ASSERT_EQ(bag->channel_groups().size(), 4);
bActionGroup *test_group = bag->channel_group(0);
EXPECT_STREQ(test_group->name, "Test");
EXPECT_EQ(test_group->fcurve_range_length, 2);
bActionGroup *test_two_group = bag->channel_group(1);
EXPECT_STREQ(test_two_group->name, "Test_Two");
EXPECT_EQ(test_two_group->fcurve_range_length, 1);
EXPECT_STREQ(bag->fcurve_array[test_two_group->fcurve_range_start]->rna_path, "scale");
bActionGroup *test_three_group = bag->channel_group(2);
EXPECT_STREQ(test_three_group->name, "Test_Three");
EXPECT_EQ(test_three_group->fcurve_range_length, 1);
EXPECT_STREQ(bag->fcurve_array[test_three_group->fcurve_range_start]->rna_path, "show_name");
bActionGroup *test_rename_group = bag->channel_group(3);
EXPECT_STREQ(test_rename_group->name, "Test.001");
EXPECT_EQ(test_rename_group->fcurve_range_length, 1);
EXPECT_STREQ(bag->fcurve_array[test_rename_group->fcurve_range_start]->rna_path, "show_axis");
ASSERT_NE(converted, action);
}
TEST_F(ActionLayersTest, empty_to_layered)
{
ASSERT_TRUE(action->is_empty());
Action *converted = convert_to_layered_action(*bmain, *action);
ASSERT_TRUE(converted != action);
ASSERT_TRUE(converted->is_action_layered());
ASSERT_FALSE(converted->is_action_legacy());
}
TEST_F(ActionLayersTest, action_move_slot)
{
Action *action_2 = BKE_id_new<Action>(bmain, "Action 2");
EXPECT_TRUE(action->is_empty());
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action_2->slot_add();
EXPECT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(assign_action_and_slot(action_2, &slot_suzanne, suzanne->id),
ActionSlotAssignmentResult::OK);
PointerRNA cube_rna_pointer = RNA_id_pointer_create(&cube->id);
PointerRNA suzanne_rna_pointer = RNA_id_pointer_create(&suzanne->id);
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
action_fcurve_ensure_ex(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"location", 0});
action_fcurve_ensure_ex(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"rotation_euler", 1});
ASSERT_EQ(action->layer_array_num, 1);
ASSERT_EQ(action_2->layer_array_num, 1);
Layer *layer_1 = action->layer(0);
Layer *layer_2 = action_2->layer(0);
ASSERT_EQ(layer_1->strip_array_num, 1);
ASSERT_EQ(layer_2->strip_array_num, 1);
StripKeyframeData &strip_data_1 = layer_1->strip(0)->data<StripKeyframeData>(*action);
StripKeyframeData &strip_data_2 = layer_2->strip(0)->data<StripKeyframeData>(*action_2);
ASSERT_EQ(strip_data_1.channelbag_array_num, 1);
ASSERT_EQ(strip_data_2.channelbag_array_num, 1);
Channelbag *bag_1 = strip_data_1.channelbag(0);
Channelbag *bag_2 = strip_data_2.channelbag(0);
ASSERT_EQ(bag_1->fcurve_array_num, 2);
ASSERT_EQ(bag_2->fcurve_array_num, 2);
move_slot(*bmain, slot_suzanne, *action_2, *action);
ASSERT_EQ(strip_data_1.channelbag_array_num, 2);
ASSERT_EQ(strip_data_2.channelbag_array_num, 0);
ASSERT_EQ(action->slot_array_num, 2);
ASSERT_EQ(action_2->slot_array_num, 0);
/* Action should have been reassigned. */
ASSERT_EQ(action, cube->adt->action);
ASSERT_EQ(action, suzanne->adt->action);
}
TEST_F(ActionLayersTest, action_move_slot_without_channelbag)
{
Action *action_2 = BKE_id_new<Action>(bmain, "Action 2");
EXPECT_TRUE(action->is_empty());
Slot &slot_cube = action->slot_add();
Slot &slot_suzanne = action_2->slot_add();
EXPECT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
EXPECT_EQ(assign_action_and_slot(action_2, &slot_suzanne, suzanne->id),
ActionSlotAssignmentResult::OK);
PointerRNA cube_rna_pointer = RNA_id_pointer_create(&cube->id);
PointerRNA suzanne_rna_pointer = RNA_id_pointer_create(&suzanne->id);
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
/* Make sure action_2 has a keyframe strip, but without a channelbag. */
action_2->layer_add("Bagless").strip_add(*action_2, Strip::Type::Keyframe);
ASSERT_EQ(action->layer_array_num, 1);
ASSERT_EQ(action_2->layer_array_num, 1);
Layer *layer_1 = action->layer(0);
Layer *layer_2 = action_2->layer(0);
ASSERT_EQ(layer_1->strip_array_num, 1);
ASSERT_EQ(layer_2->strip_array_num, 1);
StripKeyframeData &strip_data_1 = layer_1->strip(0)->data<StripKeyframeData>(*action);
StripKeyframeData &strip_data_2 = layer_2->strip(0)->data<StripKeyframeData>(*action_2);
ASSERT_EQ(strip_data_1.channelbag_array_num, 1);
ASSERT_EQ(strip_data_2.channelbag_array_num, 0)
<< "the keyframe strip of action_2 should NOT have a channelbag in this test";
Channelbag *bag_1 = strip_data_1.channelbag(0);
ASSERT_EQ(bag_1->fcurve_array_num, 2);
move_slot(*bmain, slot_suzanne, *action_2, *action);
ASSERT_EQ(strip_data_1.channelbag_array_num, 1);
ASSERT_EQ(strip_data_2.channelbag_array_num, 0);
ASSERT_EQ(action->slot_array_num, 2);
ASSERT_EQ(action_2->slot_array_num, 0);
/* Action should have been reassigned. */
ASSERT_EQ(action, cube->adt->action);
ASSERT_EQ(action, suzanne->adt->action);
}
TEST_F(ActionLayersTest, action_duplicate_slot)
{
ASSERT_TRUE(action->is_empty());
Slot &slot_cube = action->slot_add();
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
PointerRNA cube_rna_pointer = RNA_id_pointer_create(&cube->id);
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
ASSERT_EQ(action->layer_array_num, 1);
Layer *layer = action->layer(0);
ASSERT_EQ(layer->strip_array_num, 1);
StripKeyframeData &strip_data = layer->strip(0)->data<StripKeyframeData>(*action);
ASSERT_EQ(strip_data.channelbag_array_num, 1);
Channelbag *bag = strip_data.channelbag(0);
ASSERT_EQ(bag->fcurve_array_num, 2);
/* Duplicate the slot and check it for uniqueness within the Action. */
Slot &dupli_slot = duplicate_slot(*action, slot_cube);
EXPECT_NE(dupli_slot.identifier, slot_cube.identifier);
EXPECT_NE(dupli_slot.handle, slot_cube.handle);
ASSERT_EQ(action->slot_array_num, 2);
EXPECT_EQ(&dupli_slot, action->slot(1));
/* Check the channelbag has been duplicated correctly. */
ASSERT_EQ(strip_data.channelbag_array_num, 2);
Channelbag *dupli_bag = strip_data.channelbag(1);
EXPECT_EQ(dupli_bag->slot_handle, dupli_slot.handle);
EXPECT_EQ(dupli_bag->fcurve_array_num, 2);
/* Check the original channelbag is untouched. */
EXPECT_EQ(bag->slot_handle, slot_cube.handle);
EXPECT_EQ(bag->fcurve_array_num, 2);
/* The slot should NOT have been reassigned. */
EXPECT_EQ(action, cube->adt->action);
EXPECT_EQ(slot_cube.handle, cube->adt->slot_handle);
}
TEST_F(ActionLayersTest, action_duplicate_slot_without_channelbag)
{
ASSERT_TRUE(action->is_empty());
Slot &slot_cube = action->slot_add();
ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
/* Create a keyframe strip, but without any channelbags. */
action->layer_keystrip_ensure();
ASSERT_EQ(action->layer_array_num, 1);
Layer *layer = action->layer(0);
ASSERT_EQ(layer->strip_array_num, 1);
StripKeyframeData &strip_data = layer->strip(0)->data<StripKeyframeData>(*action);
ASSERT_EQ(strip_data.channelbag_array_num, 0);
/* Duplicate the slot and check it for uniqueness within the Action. */
Slot &dupli_slot = duplicate_slot(*action, slot_cube);
EXPECT_NE(dupli_slot.identifier, slot_cube.identifier);
EXPECT_NE(dupli_slot.handle, slot_cube.handle);
ASSERT_EQ(action->slot_array_num, 2);
EXPECT_EQ(&dupli_slot, action->slot(1));
/* Check there are still no channelbags. */
EXPECT_EQ(strip_data.channelbag_array_num, 0);
/* The slot should NOT have been reassigned. */
EXPECT_EQ(action, cube->adt->action);
EXPECT_EQ(slot_cube.handle, cube->adt->slot_handle);
}
/*-----------------------------------------------------------*/
/* Allocate fcu->bezt, and also return a unique_ptr to it for easily freeing the memory. */
static void allocate_keyframes(FCurve &fcu, const size_t num_keyframes)
{
fcu.bezt = MEM_calloc_arrayN<BezTriple>(num_keyframes, __func__);
}
/* Append keyframe, assumes that fcu->bezt is allocated and has enough space. */
static void add_keyframe(FCurve &fcu, float x, float y)
{
/* The insert_keyframe functions are in the editors, so we cannot link to those here. */
BezTriple the_keyframe = {};
/* Copied from insert_vert_fcurve() in `keyframing.cc`. */
the_keyframe.vec[0][0] = x - 1.0f;
the_keyframe.vec[0][1] = y;
the_keyframe.vec[1][0] = x;
the_keyframe.vec[1][1] = y;
the_keyframe.vec[2][0] = x + 1.0f;
the_keyframe.vec[2][1] = y;
memcpy(&fcu.bezt[fcu.totvert], &the_keyframe, sizeof(the_keyframe));
fcu.totvert++;
}
static void add_fcurve_to_action(Action &action, FCurve &fcu)
{
Slot &slot = action.slot_array_num > 0 ? *action.slot(0) : action.slot_add();
action.layer_keystrip_ensure();
StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
Channelbag &cbag = strip_data.channelbag_for_slot_ensure(slot);
cbag.fcurve_append(fcu);
}
class ActionQueryTest : public testing::Test {
public:
Main *bmain;
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();
}
void TearDown() override
{
BKE_main_free(bmain);
}
Action &action_new()
{
return *BKE_id_new<Action>(bmain, "ACÄnimåtië");
}
};
TEST_F(ActionQueryTest, BKE_action_frame_range_calc)
{
/* No FCurves. */
{
const Action &empty = action_new();
EXPECT_EQ((float2{0.0f, 0.0f}), empty.get_frame_range_of_keys(false));
}
/* One curve with one key. */
{
FCurve &fcu = *MEM_callocN<FCurve>(__func__);
allocate_keyframes(fcu, 1);
add_keyframe(fcu, 1.0f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.0f);
}
/* Two curves with one key each on different frames. */
{
FCurve &fcu1 = *MEM_callocN<FCurve>(__func__);
FCurve &fcu2 = *MEM_callocN<FCurve>(__func__);
allocate_keyframes(fcu1, 1);
allocate_keyframes(fcu2, 1);
add_keyframe(fcu1, 1.0f, 2.0f);
add_keyframe(fcu2, 1.5f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu1);
add_fcurve_to_action(action, fcu2);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.5f);
}
/* One curve with two keys. */
{
FCurve &fcu = *MEM_callocN<FCurve>(__func__);
allocate_keyframes(fcu, 2);
add_keyframe(fcu, 1.0f, 2.0f);
add_keyframe(fcu, 1.5f, 2.0f);
Action &action = action_new();
add_fcurve_to_action(action, fcu);
const float2 frame_range = action.get_frame_range_of_keys(false);
EXPECT_FLOAT_EQ(frame_range[0], 1.0f);
EXPECT_FLOAT_EQ(frame_range[1], 1.5f);
}
/* TODO: action with fcurve modifiers. */
}
/*-----------------------------------------------------------*/
class ChannelbagTest : public testing::Test {
public:
Channelbag *channelbag;
static void SetUpTestSuite() {}
static void TearDownTestSuite() {}
void SetUp() override
{
channelbag = new Channelbag();
}
void TearDown() override
{
delete channelbag;
}
};
TEST_F(ChannelbagTest, fcurve_create_many)
{
FCurve &existing1 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
FCurve &existing2 = channelbag->fcurve_ensure(nullptr, {"fcu0", 1, {}, {}, "group0"});
FCurve &existing3 = channelbag->fcurve_ensure(nullptr, {"fcu1", 1, {}, {}, "group1"});
FCurve &existing4 = channelbag->fcurve_ensure(nullptr, {"fcu_", 0});
ASSERT_EQ(2, channelbag->channel_groups().size());
ASSERT_EQ(4, channelbag->fcurves().size());
FCurveDescriptor desc[] = {
/* New group. */
{"fcu2", 0, {}, {}, "group2"},
{"fcu2", 1, {}, {}, "group2"},
{"fcu2", 2, {}, {}, "group2"},
/* Existing groups. */
{"fcu3", 0, {}, {}, "group1"},
{"fcu4", 0, {}, {}, "group0"},
{"fcu5", 0, {}, {}, "group1"},
{"fcu6", 0, {}, {}, "group0"},
{"fcu7", 0, {}, {}, "group2"},
/* No group. */
{"fcu8", 0},
{"fcu8", 1},
/* Empty rna path, should return null. */
{"", 0, {}, {}, "irrelevant"},
/* Should return null since such curves already exist. */
{"fcu0", 1, {}, {}, "irrelevant"},
{"fcu5", 0, {}, {}, "also unused"},
{"fcu2", 0, {}, {}, "group2"},
{"fcu6", 0},
};
Vector<FCurve *> fcurves = channelbag->fcurve_create_many(nullptr, {desc, ARRAY_SIZE(desc)});
ASSERT_EQ(15, fcurves.size());
EXPECT_STREQ("group2", fcurves[0]->grp->name);
EXPECT_STREQ("group2", fcurves[1]->grp->name);
EXPECT_STREQ("group2", fcurves[2]->grp->name);
EXPECT_STREQ("group1", fcurves[3]->grp->name);
EXPECT_STREQ("group0", fcurves[4]->grp->name);
EXPECT_STREQ("group1", fcurves[5]->grp->name);
EXPECT_STREQ("group0", fcurves[6]->grp->name);
EXPECT_STREQ("group2", fcurves[7]->grp->name);
EXPECT_EQ(nullptr, fcurves[8]->grp);
EXPECT_EQ(nullptr, fcurves[9]->grp);
EXPECT_EQ(nullptr, fcurves[10]);
EXPECT_EQ(nullptr, fcurves[11]);
EXPECT_EQ(nullptr, fcurves[12]);
EXPECT_EQ(nullptr, fcurves[13]);
EXPECT_EQ(nullptr, fcurves[14]);
EXPECT_EQ(3, channelbag->channel_groups().size());
EXPECT_EQ(14, channelbag->fcurves().size());
EXPECT_STREQ("group0", existing1.grp->name);
EXPECT_STREQ("group0", existing2.grp->name);
EXPECT_STREQ("group1", existing3.grp->name);
EXPECT_EQ(nullptr, existing4.grp);
}
TEST_F(ChannelbagTest, fcurve_move_to_index)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "group0"});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "group1"});
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "group1"});
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0});
ASSERT_EQ(5, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
bActionGroup &group0 = *channelbag->channel_group(0);
bActionGroup &group1 = *channelbag->channel_group(1);
/* Moving an fcurve to where it already is should be fine. */
channelbag->fcurve_move_to_index(fcu0, 0);
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu1, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group1, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
/* Move to first. */
channelbag->fcurve_move_to_index(fcu4, 0);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&fcu4, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(2));
EXPECT_EQ(&fcu2, channelbag->fcurve(3));
EXPECT_EQ(&fcu3, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu4.grp);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group1, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu3.grp);
/* Move to last. */
channelbag->fcurve_move_to_index(fcu1, 4);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&fcu4, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu1, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu4.grp);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group1, fcu3.grp);
EXPECT_EQ(nullptr, fcu1.grp);
/* Move to middle. */
channelbag->fcurve_move_to_index(fcu4, 2);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu4, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu1, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu2.grp);
EXPECT_EQ(&group1, fcu4.grp);
EXPECT_EQ(&group1, fcu3.grp);
EXPECT_EQ(nullptr, fcu1.grp);
}
TEST_F(ChannelbagTest, channel_group_create)
{
ASSERT_TRUE(channelbag->channel_groups().is_empty());
bActionGroup &group0 = channelbag->channel_group_create("Foo");
ASSERT_EQ(channelbag->channel_groups().size(), 1);
EXPECT_EQ(StringRef{group0.name}, StringRef{"Foo"});
EXPECT_EQ(group0.fcurve_range_start, 0);
EXPECT_EQ(group0.fcurve_range_length, 0);
EXPECT_EQ(&group0, channelbag->channel_group(0));
/* Set for testing purposes. Does not reflect actual fcurves in this test. */
group0.fcurve_range_length = 2;
bActionGroup &group1 = channelbag->channel_group_create("Bar");
ASSERT_EQ(channelbag->channel_groups().size(), 2);
EXPECT_EQ(StringRef{group1.name}, StringRef{"Bar"});
EXPECT_EQ(group1.fcurve_range_start, 2);
EXPECT_EQ(group1.fcurve_range_length, 0);
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
/* Set for testing purposes. Does not reflect actual fcurves in this test. */
group1.fcurve_range_length = 1;
bActionGroup &group2 = channelbag->channel_group_create("Yar");
ASSERT_EQ(channelbag->channel_groups().size(), 3);
EXPECT_EQ(StringRef{group2.name}, StringRef{"Yar"});
EXPECT_EQ(group2.fcurve_range_start, 3);
EXPECT_EQ(group2.fcurve_range_length, 0);
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(&group2, channelbag->channel_group(2));
}
TEST_F(ChannelbagTest, channel_group_remove)
{
bActionGroup &group0 = channelbag->channel_group_create("Group0");
bActionGroup &group1 = channelbag->channel_group_create("Group1");
bActionGroup &group2 = channelbag->channel_group_create("Group2");
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "Group0"});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "Group0"});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "Group2"});
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "Group2"});
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0});
ASSERT_EQ(3, channelbag->channel_groups().size());
ASSERT_EQ(5, channelbag->fcurves().size());
/* Attempt to remove a group that's not in the channel bag. Shouldn't do
* anything. */
bActionGroup bogus;
EXPECT_EQ(false, channelbag->channel_group_remove(bogus));
ASSERT_EQ(3, channelbag->channel_groups().size());
ASSERT_EQ(5, channelbag->fcurves().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(&group2, channelbag->channel_group(2));
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu1, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group2, fcu2.grp);
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
/* Removing an empty group shouldn't affect the fcurves at all. */
EXPECT_EQ(true, channelbag->channel_group_remove(group1));
ASSERT_EQ(2, channelbag->channel_groups().size());
ASSERT_EQ(5, channelbag->fcurves().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group2, channelbag->channel_group(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu1, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group2, fcu2.grp);
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
/* Removing a group that's not at the end of the group array should move its
* fcurves to be just after the grouped fcurves. */
EXPECT_EQ(true, channelbag->channel_group_remove(group0));
ASSERT_EQ(1, channelbag->channel_groups().size());
ASSERT_EQ(5, channelbag->fcurves().size());
EXPECT_EQ(&group2, channelbag->channel_group(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(0));
EXPECT_EQ(&fcu3, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(&fcu1, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(nullptr, fcu1.grp);
EXPECT_EQ(&group2, fcu2.grp);
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
/* Removing a group at the end of the group array shouldn't move its
* fcurves. */
EXPECT_EQ(true, channelbag->channel_group_remove(group2));
ASSERT_EQ(0, channelbag->channel_groups().size());
ASSERT_EQ(5, channelbag->fcurves().size());
EXPECT_EQ(&fcu2, channelbag->fcurve(0));
EXPECT_EQ(&fcu3, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(&fcu1, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(nullptr, fcu1.grp);
EXPECT_EQ(nullptr, fcu2.grp);
EXPECT_EQ(nullptr, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
}
TEST_F(ChannelbagTest, channel_group_find)
{
bActionGroup &group0a = channelbag->channel_group_create("Foo");
bActionGroup &group1a = channelbag->channel_group_create("Bar");
bActionGroup &group2a = channelbag->channel_group_create("Yar");
bActionGroup *group0b = channelbag->channel_group_find("Foo");
bActionGroup *group1b = channelbag->channel_group_find("Bar");
bActionGroup *group2b = channelbag->channel_group_find("Yar");
EXPECT_EQ(&group0a, group0b);
EXPECT_EQ(&group1a, group1b);
EXPECT_EQ(&group2a, group2b);
EXPECT_EQ(nullptr, channelbag->channel_group_find("Wat"));
}
TEST_F(ChannelbagTest, channel_group_ensure)
{
bActionGroup &group0 = channelbag->channel_group_create("Foo");
bActionGroup &group1 = channelbag->channel_group_create("Bar");
EXPECT_EQ(channelbag->channel_groups().size(), 2);
EXPECT_EQ(&group0, &channelbag->channel_group_ensure("Foo"));
EXPECT_EQ(channelbag->channel_groups().size(), 2);
EXPECT_EQ(&group1, &channelbag->channel_group_ensure("Bar"));
EXPECT_EQ(channelbag->channel_groups().size(), 2);
bActionGroup &group2 = channelbag->channel_group_ensure("Yar");
ASSERT_EQ(channelbag->channel_groups().size(), 3);
EXPECT_EQ(&group2, channelbag->channel_group(2));
}
TEST_F(ChannelbagTest, channel_group_fcurve_creation)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0});
EXPECT_EQ(1, channelbag->fcurves().size());
EXPECT_TRUE(channelbag->channel_groups().is_empty());
/* If an fcurve already exists, then ensuring it with a channel group in the
* fcurve descriptor should NOT add it that group, nor should the group be
* created if it doesn't already exist. */
channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
EXPECT_EQ(1, channelbag->fcurves().size());
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_TRUE(channelbag->channel_groups().is_empty());
/* Creating a new fcurve with a channel group in the fcurve descriptor should
* create the group and put the fcurve in it. This also implies that the
* fcurve will be added before any non-grouped fcurves in the array. */
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "group0"});
ASSERT_EQ(2, channelbag->fcurves().size());
ASSERT_EQ(1, channelbag->channel_groups().size());
bActionGroup &group0 = *channelbag->channel_group(0);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
/* Creating a new fcurve with a second channel group in the fcurve descriptor
* should create the group and put the fcurve in it. This also implies that
* the fcurve will be added before non-grouped fcurves, but after other
* grouped ones. */
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "group1"});
ASSERT_EQ(3, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
bActionGroup &group1 = *channelbag->channel_group(1);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
/* Creating a new fcurve with the first channel group again should put it at
* the end of that group. */
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "group0"});
ASSERT_EQ(4, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu3, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu0, channelbag->fcurve(3));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group0, fcu3.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
/* Finally, creating a new fcurve with the second channel group again should
* also put it at the end of that group. */
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0, {}, {}, "group1"});
ASSERT_EQ(5, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu3, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu4, channelbag->fcurve(3));
EXPECT_EQ(&fcu0, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group0, fcu3.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group1, fcu4.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
}
TEST_F(ChannelbagTest, channel_group_fcurve_removal)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "group0"});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "group1"});
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "group1"});
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0});
ASSERT_EQ(5, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
bActionGroup &group0 = *channelbag->channel_group(0);
bActionGroup &group1 = *channelbag->channel_group(1);
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group1, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->fcurve_remove(fcu3);
ASSERT_EQ(4, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->fcurve_remove(fcu0);
ASSERT_EQ(3, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->fcurve_remove(fcu1);
ASSERT_EQ(2, channelbag->fcurves().size());
ASSERT_EQ(1, channelbag->channel_groups().size());
EXPECT_EQ(&group1, channelbag->channel_group(0));
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->fcurve_remove(fcu4);
ASSERT_EQ(1, channelbag->fcurves().size());
ASSERT_EQ(1, channelbag->channel_groups().size());
EXPECT_EQ(&group1, channelbag->channel_group(0));
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&group1, fcu2.grp);
channelbag->fcurve_remove(fcu2);
ASSERT_EQ(0, channelbag->fcurves().size());
ASSERT_EQ(0, channelbag->channel_groups().size());
}
TEST_F(ChannelbagTest, channel_group_move_to_index)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "group1"});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "group1"});
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "group2"});
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0});
ASSERT_EQ(5, channelbag->fcurves().size());
ASSERT_EQ(3, channelbag->channel_groups().size());
bActionGroup &group0 = *channelbag->channel_group(0);
bActionGroup &group1 = *channelbag->channel_group(1);
bActionGroup &group2 = *channelbag->channel_group(2);
channelbag->channel_group_move_to_index(group0, 2);
EXPECT_EQ(&group1, channelbag->channel_group(0));
EXPECT_EQ(&group2, channelbag->channel_group(1));
EXPECT_EQ(&group0, channelbag->channel_group(2));
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(2, group2.fcurve_range_start);
EXPECT_EQ(1, group2.fcurve_range_length);
EXPECT_EQ(3, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu3, channelbag->fcurve(2));
EXPECT_EQ(&fcu0, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group1, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->channel_group_move_to_index(group1, 1);
EXPECT_EQ(&group2, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(&group0, channelbag->channel_group(2));
EXPECT_EQ(0, group2.fcurve_range_start);
EXPECT_EQ(1, group2.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(3, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(&fcu3, channelbag->fcurve(0));
EXPECT_EQ(&fcu1, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(&fcu0, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(&group1, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(nullptr, fcu4.grp);
channelbag->channel_group_move_to_index(group0, 0);
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group2, channelbag->channel_group(1));
EXPECT_EQ(&group1, channelbag->channel_group(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group2.fcurve_range_start);
EXPECT_EQ(1, group2.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu3, channelbag->fcurve(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(2));
EXPECT_EQ(&fcu2, channelbag->fcurve(3));
EXPECT_EQ(&fcu4, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu0.grp);
EXPECT_EQ(&group2, fcu3.grp);
EXPECT_EQ(&group1, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
}
TEST_F(ChannelbagTest, channel_group_move_fcurve_into)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0});
bActionGroup &group0 = channelbag->channel_group_create("group0");
bActionGroup &group1 = channelbag->channel_group_create("group1");
ASSERT_EQ(3, channelbag->fcurves().size());
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu1, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
ASSERT_EQ(2, channelbag->channel_groups().size());
EXPECT_EQ(&group0, channelbag->channel_group(0));
EXPECT_EQ(&group1, channelbag->channel_group(1));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(0, group0.fcurve_range_length);
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(0, group1.fcurve_range_length);
channelbag->fcurve_assign_to_channel_group(fcu2, group1);
EXPECT_EQ(&fcu2, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(0, group0.fcurve_range_length);
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
channelbag->fcurve_assign_to_channel_group(fcu1, group0);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
channelbag->fcurve_assign_to_channel_group(fcu0, group1);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
channelbag->fcurve_assign_to_channel_group(fcu0, group0);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&fcu2, channelbag->fcurve(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(2, group0.fcurve_range_length);
EXPECT_EQ(2, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
channelbag->fcurve_assign_to_channel_group(fcu1, group1);
EXPECT_EQ(&fcu0, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu1, channelbag->fcurve(2));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
}
TEST_F(ChannelbagTest, channel_group_fcurve_ungroup)
{
FCurve &fcu0 = channelbag->fcurve_ensure(nullptr, {"fcu0", 0, {}, {}, "group0"});
FCurve &fcu1 = channelbag->fcurve_ensure(nullptr, {"fcu1", 0, {}, {}, "group0"});
FCurve &fcu2 = channelbag->fcurve_ensure(nullptr, {"fcu2", 0, {}, {}, "group1"});
FCurve &fcu3 = channelbag->fcurve_ensure(nullptr, {"fcu3", 0, {}, {}, "group1"});
FCurve &fcu4 = channelbag->fcurve_ensure(nullptr, {"fcu4", 0});
ASSERT_EQ(5, channelbag->fcurves().size());
ASSERT_EQ(2, channelbag->channel_groups().size());
bActionGroup &group0 = *channelbag->channel_group(0);
bActionGroup &group1 = *channelbag->channel_group(1);
/* Attempting to ungroup an fcurve that's not in the channel bag should fail. */
FCurve bogus = {};
EXPECT_FALSE(channelbag->fcurve_ungroup(bogus));
/* Attempting to ungroup an fcurve that's already ungrouped is fine. */
EXPECT_TRUE(channelbag->fcurve_ungroup(fcu4));
/* Ungroup each fcurve until all are ungrouped. */
EXPECT_TRUE(channelbag->fcurve_ungroup(fcu0));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(2, group1.fcurve_range_length);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu3, channelbag->fcurve(2));
EXPECT_EQ(&fcu4, channelbag->fcurve(3));
EXPECT_EQ(&fcu0, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(&group1, fcu3.grp);
EXPECT_EQ(nullptr, fcu4.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_TRUE(channelbag->fcurve_ungroup(fcu3));
EXPECT_EQ(0, group0.fcurve_range_start);
EXPECT_EQ(1, group0.fcurve_range_length);
EXPECT_EQ(1, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&fcu1, channelbag->fcurve(0));
EXPECT_EQ(&fcu2, channelbag->fcurve(1));
EXPECT_EQ(&fcu4, channelbag->fcurve(2));
EXPECT_EQ(&fcu0, channelbag->fcurve(3));
EXPECT_EQ(&fcu3, channelbag->fcurve(4));
EXPECT_EQ(&group0, fcu1.grp);
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(nullptr, fcu3.grp);
EXPECT_TRUE(channelbag->fcurve_ungroup(fcu1));
EXPECT_EQ(1, channelbag->channel_groups().size());
EXPECT_EQ(&group1, channelbag->channel_group(0));
EXPECT_EQ(0, group1.fcurve_range_start);
EXPECT_EQ(1, group1.fcurve_range_length);
EXPECT_EQ(&fcu2, channelbag->fcurve(0));
EXPECT_EQ(&fcu4, channelbag->fcurve(1));
EXPECT_EQ(&fcu0, channelbag->fcurve(2));
EXPECT_EQ(&fcu3, channelbag->fcurve(3));
EXPECT_EQ(&fcu1, channelbag->fcurve(4));
EXPECT_EQ(&group1, fcu2.grp);
EXPECT_EQ(nullptr, fcu4.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(nullptr, fcu3.grp);
EXPECT_EQ(nullptr, fcu1.grp);
EXPECT_TRUE(channelbag->fcurve_ungroup(fcu2));
EXPECT_EQ(0, channelbag->channel_groups().size());
EXPECT_EQ(&fcu4, channelbag->fcurve(0));
EXPECT_EQ(&fcu0, channelbag->fcurve(1));
EXPECT_EQ(&fcu3, channelbag->fcurve(2));
EXPECT_EQ(&fcu1, channelbag->fcurve(3));
EXPECT_EQ(&fcu2, channelbag->fcurve(4));
EXPECT_EQ(nullptr, fcu4.grp);
EXPECT_EQ(nullptr, fcu0.grp);
EXPECT_EQ(nullptr, fcu3.grp);
EXPECT_EQ(nullptr, fcu1.grp);
EXPECT_EQ(nullptr, fcu2.grp);
}
/*-----------------------------------------------------------*/
class ActionFCurveMoveTest : public testing::Test {
public:
Main *bmain;
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();
}
void TearDown() override
{
BKE_main_free(bmain);
}
static FCurve *fcurve_create(const StringRefNull rna_path, const int array_index)
{
FCurve *fcurve = BKE_fcurve_create();
fcurve->rna_path = BLI_strdupn(rna_path.c_str(), array_index);
return fcurve;
};
};
TEST_F(ActionFCurveMoveTest, test_fcurve_move_legacy)
{
Action &action_src = action_add(*this->bmain, "SourceAction");
Action &action_dst = action_add(*this->bmain, "DestinationAction");
/* Add F-Curves to source Action. */
BLI_addtail(&action_src.curves, fcurve_create("source_prop", 0));
FCurve *fcurve_to_move = fcurve_create("source_prop", 2);
BLI_addtail(&action_src.curves, fcurve_to_move);
/* Add F-Curves to destination Action. */
BLI_addtail(&action_dst.curves, fcurve_create("dest_prop", 0));
ASSERT_TRUE(action_src.is_action_legacy());
ASSERT_TRUE(action_dst.is_action_legacy());
action_fcurve_move(action_dst, Slot::unassigned, action_src, *fcurve_to_move);
EXPECT_TRUE(action_src.is_action_legacy());
EXPECT_TRUE(action_dst.is_action_legacy());
EXPECT_EQ(-1, BLI_findindex(&action_src.curves, fcurve_to_move))
<< "F-Curve should no longer exist in source Action";
EXPECT_EQ(1, BLI_findindex(&action_dst.curves, fcurve_to_move))
<< "F-Curve should exist in destination Action";
EXPECT_EQ(1, BLI_listbase_count(&action_src.curves))
<< "Source Action should still have the other F-Curve";
EXPECT_EQ(2, BLI_listbase_count(&action_dst.curves))
<< "Destination Action should have its original and the moved F-Curve";
}
TEST_F(ActionFCurveMoveTest, test_fcurve_move_layered)
{
Action &action_src = action_add(*this->bmain, "SourceAction");
Action &action_dst = action_add(*this->bmain, "DestinationAction");
/* Add F-Curves to source Action. */
Slot &slot_src = action_src.slot_add();
action_src.layer_keystrip_ensure();
StripKeyframeData &strip_data_src = action_src.layer(0)->strip(0)->data<StripKeyframeData>(
action_src);
Channelbag &cbag_src = strip_data_src.channelbag_for_slot_ensure(slot_src);
cbag_src.fcurve_ensure(this->bmain, {"source_prop", 0});
FCurve &fcurve_to_move = cbag_src.fcurve_ensure(this->bmain, {"source_prop", 2});
bActionGroup &group_src = cbag_src.channel_group_create("Gröpje");
cbag_src.fcurve_assign_to_channel_group(fcurve_to_move, group_src);
/* Add F-Curves to destination Action. */
Slot &slot_dst = action_dst.slot_add();
action_dst.layer_keystrip_ensure();
StripKeyframeData &strip_data_dst = action_dst.layer(0)->strip(0)->data<StripKeyframeData>(
action_dst);
Channelbag &cbag_dst = strip_data_dst.channelbag_for_slot_ensure(slot_dst);
cbag_dst.fcurve_ensure(this->bmain, {"dest_prop", 0});
ASSERT_TRUE(action_src.is_action_layered());
ASSERT_TRUE(action_dst.is_action_layered());
action_fcurve_move(action_dst, slot_dst.handle, action_src, fcurve_to_move);
EXPECT_TRUE(action_src.is_action_layered());
EXPECT_TRUE(action_dst.is_action_layered());
EXPECT_EQ(nullptr, cbag_src.fcurve_find({fcurve_to_move.rna_path, fcurve_to_move.array_index}))
<< "F-Curve should no longer exist in source Action";
EXPECT_EQ(&fcurve_to_move,
cbag_dst.fcurve_find({fcurve_to_move.rna_path, fcurve_to_move.array_index}))
<< "F-Curve should exist in destination Action";
EXPECT_EQ(1, cbag_src.fcurves().size()) << "Source Action should still have the other F-Curve";
EXPECT_EQ(2, cbag_dst.fcurves().size())
<< "Destination Action should have its original and the moved F-Curve";
bActionGroup *group_dst = cbag_dst.channel_group_find("Gröpje");
ASSERT_NE(nullptr, group_dst) << "Expected channel group to be created";
ASSERT_EQ(group_dst, fcurve_to_move.grp) << "Expected group membership to move as well";
}
} // namespace blender::animrig::tests