Files
test2/source/blender/animrig/intern/action_iterators_test.cc
Nathan Vegdahl 989634c0a1 Fix: handle embedded IDs when upgrading to slotted actions
The versioning code that upgrades legacy actions to new slotted actions
also needs to properly assign slots to the IDs that use those upgraded
actions. It was doing this correctly except for not traversing into and
assigning slots to embedded IDs.

This commit adds the code to handle embedded IDs as well.

Additionally, this changes how mismatched `id_type`s are handled when upgrading
actions. Rather than refusing to assign the slot created during the upgrade if
the `id_type` doesn't match the ID, we assign it anyway with a warning. The
rationale is that this represents a case where the Action `idroot` was already
mismatched, and it turns out that has always been possible. So we now opt to
simply preserve that state of affairs rather than attempt to "fix" it.

Pull Request: https://projects.blender.org/blender/blender/pulls/129002
2024-10-15 11:04:04 +02:00

159 lines
5.3 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_action.hh"
#include "ANIM_action_iterators.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::animrig::tests {
class ActionIteratorsTest : public testing::Test {
public:
Main *bmain;
Action *action;
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, "ACLayeredAction"));
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
{
Slot &cube_slot = action->slot_add();
Slot &monkey_slot = action->slot_add();
EXPECT_TRUE(action->is_action_layered());
/* Try iterating an empty action. */
blender::Vector<FCurve *> no_fcurves;
foreach_fcurve_in_action_slot(
*action, cube_slot.handle, [&](FCurve &fcurve) { no_fcurves.append(&fcurve); });
ASSERT_TRUE(no_fcurves.is_empty());
Layer &layer = action->layer_add("Layer One");
Strip &strip = layer.strip_add(*action, Strip::Type::Keyframe);
StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
const KeyframeSettings settings = get_keyframe_settings(false);
/* Insert 3 FCurves for each slot. */
for (int i = 0; i < 3; i++) {
SingleKeyingResult result_cube = strip_data.keyframe_insert(
bmain, cube_slot, {"location", i}, {1.0f, 0.0f}, settings);
ASSERT_EQ(SingleKeyingResult::SUCCESS, result_cube)
<< "Expected keyframe insertion to be successful";
SingleKeyingResult result_monkey = strip_data.keyframe_insert(
bmain, monkey_slot, {"rotation", i}, {1.0f, 0.0f}, settings);
ASSERT_EQ(SingleKeyingResult::SUCCESS, result_monkey)
<< "Expected keyframe insertion to be successful";
}
/* Get all FCurves. */
blender::Vector<FCurve *> cube_fcurves;
foreach_fcurve_in_action_slot(
*action, cube_slot.handle, [&](FCurve &fcurve) { cube_fcurves.append(&fcurve); });
ASSERT_EQ(cube_fcurves.size(), 3);
for (FCurve *fcurve : cube_fcurves) {
ASSERT_STREQ(fcurve->rna_path, "location");
}
/* Get only FCurves with index 0 which should be 1. */
blender::Vector<FCurve *> monkey_fcurves;
foreach_fcurve_in_action_slot(*action, monkey_slot.handle, [&](FCurve &fcurve) {
if (fcurve.array_index == 0) {
monkey_fcurves.append(&fcurve);
}
});
ASSERT_EQ(monkey_fcurves.size(), 1);
ASSERT_STREQ(monkey_fcurves[0]->rna_path, "rotation");
/* Slots handles are just numbers. Passing in a slot handle that doesn't exist should return
* nothing. */
blender::Vector<FCurve *> invalid_slot_fcurves;
foreach_fcurve_in_action_slot(*action,
monkey_slot.handle + cube_slot.handle,
[&](FCurve &fcurve) { invalid_slot_fcurves.append(&fcurve); });
ASSERT_TRUE(invalid_slot_fcurves.is_empty());
}
TEST_F(ActionIteratorsTest, foreach_action_slot_use_with_references)
{
/* Create a cube and assign the Action + a slot. */
Object *cube = static_cast<Object *>(BKE_id_new(bmain, ID_OB, "OBCube"));
Slot *slot_cube = assign_action_ensure_slot_for_keying(*action, cube->id);
ASSERT_NE(slot_cube, nullptr);
/* Create another Action with slot to assign. */
Action &other_action =
static_cast<bAction *>(BKE_id_new(bmain, ID_AC, "ACAnotherAction"))->wrap();
Slot &another_slot = other_action.slot_add();
std::optional<ActionSlotAssignmentResult> slot_assignment_result;
bool all_assigns_ok = true;
const auto assign_other_action = [&](ID & /* animated_id */,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_name) -> bool {
/* Assign the other Action. */
all_assigns_ok &= generic_assign_action(
cube->id, &other_action, action_ptr_ref, slot_handle_ref, slot_name);
/* Assign the slot of the other Action. */
slot_assignment_result = generic_assign_action_slot(
&another_slot, cube->id, action_ptr_ref, slot_handle_ref, slot_name);
return true;
};
foreach_action_slot_use_with_references(cube->id, assign_other_action);
ASSERT_TRUE(all_assigns_ok);
/* Check the result, the slot assignment should have been changed. */
ASSERT_TRUE(slot_assignment_result.has_value());
EXPECT_EQ(ActionSlotAssignmentResult::OK, slot_assignment_result.value());
std::optional<std::pair<Action *, Slot *>> action_and_slot = get_action_slot_pair(cube->id);
ASSERT_TRUE(action_and_slot.has_value());
EXPECT_EQ(&other_action, action_and_slot->first)
<< "Expected Action " << other_action.id.name << " but found "
<< action_and_slot->first->id.name;
EXPECT_EQ(&another_slot, action_and_slot->second)
<< "Expected Slot " << another_slot.name << " but found " << action_and_slot->second->name;
}
} // namespace blender::animrig::tests