Blender V5.0
anim_filter_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "ANIM_action.hh"
6#include "ANIM_fcurve.hh"
7
8#include "BKE_action.hh"
9#include "BKE_anim_data.hh"
10#include "BKE_global.hh"
11#include "BKE_idtype.hh"
12#include "BKE_lib_id.hh"
13#include "BKE_main.hh"
14#include "BKE_object.hh"
15
16#include "DNA_anim_types.h"
17#include "DNA_object_types.h"
18#include "DNA_space_types.h"
19
20#include "ED_anim_api.hh"
21
22#include "BLI_listbase.h"
23
24#include "CLG_log.h"
25#include "testing/testing.h"
26
28class ActionFilterTest : public testing::Test {
29 public:
34
35 static void SetUpTestSuite()
36 {
37 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
38 CLG_init();
39
40 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
42 }
43
44 static void TearDownTestSuite()
45 {
46 CLG_exit();
47 }
48
49 void SetUp() override
50 {
52 G_MAIN = bmain; /* For BKE_animdata_free(). */
53
54 action = &BKE_id_new<bAction>(bmain, "ACÄnimåtië")->wrap();
57 }
58
59 void TearDown() override
60 {
62 G_MAIN = nullptr;
63 }
64};
65
66TEST_F(ActionFilterTest, slots_expanded_or_not)
67{
68 Slot &slot_cube = action->slot_add();
69 Slot &slot_suzanne = action->slot_add();
70 ASSERT_TRUE(assign_action(action, cube->id));
71 ASSERT_TRUE(assign_action(action, suzanne->id));
72 ASSERT_EQ(assign_action_slot(&slot_cube, cube->id), ActionSlotAssignmentResult::OK);
73 ASSERT_EQ(assign_action_slot(&slot_suzanne, suzanne->id), ActionSlotAssignmentResult::OK);
74
75 Layer &layer = action->layer_add("Kübus layer");
76 Strip &key_strip = layer.strip_add(*action, Strip::Type::Keyframe);
77 StripKeyframeData &strip_data = key_strip.data<StripKeyframeData>(*action);
78
79 /* Create multiple FCurves for multiple Slots. */
80 const KeyframeSettings settings = get_keyframe_settings(false);
81 ASSERT_EQ(
83 strip_data.keyframe_insert(bmain, slot_cube, {"location", 0}, {1.0f, 0.25f}, settings));
84 ASSERT_EQ(
86 strip_data.keyframe_insert(bmain, slot_cube, {"location", 1}, {1.0f, 0.25f}, settings));
87 ASSERT_EQ(
89 strip_data.keyframe_insert(bmain, slot_suzanne, {"location", 0}, {1.0f, 0.25f}, settings));
90 ASSERT_EQ(
92 strip_data.keyframe_insert(bmain, slot_suzanne, {"location", 1}, {1.0f, 0.25f}, settings));
93
94 Channelbag *cube_channelbag = strip_data.channelbag_for_slot(slot_cube);
95 ASSERT_NE(nullptr, cube_channelbag);
96 FCurve *fcu_cube_loc_x = cube_channelbag->fcurve_find({"location", 0});
97 FCurve *fcu_cube_loc_y = cube_channelbag->fcurve_find({"location", 1});
98 ASSERT_NE(nullptr, fcu_cube_loc_x);
99 ASSERT_NE(nullptr, fcu_cube_loc_y);
100
101 /* Mock an bAnimContext for the Animation editor, with the above Animation showing. */
102 SpaceAction saction = {nullptr};
104
105 bAnimContext ac = {nullptr};
106 ac.bmain = bmain;
108 ac.data = action;
110 ac.sl = reinterpret_cast<SpaceLink *>(&saction);
111 ac.obact = cube;
112 ac.active_action = action;
113 ac.active_action_user = &cube->id;
114 ac.ads = &saction.ads;
115
116 { /* Test with collapsed slots. */
117 slot_cube.set_expanded(false);
118 slot_suzanne.set_expanded(false);
119
120 /* This should produce 2 slots and no FCurves. */
121 ListBase anim_data = {nullptr, nullptr};
125 const int num_entries = ANIM_animdata_filter(
126 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
127 EXPECT_EQ(2, num_entries);
128 EXPECT_EQ(2, BLI_listbase_count(&anim_data));
129
130 ASSERT_GE(num_entries, 1)
131 << "Missing 1st ANIMTYPE_ACTION_SLOT entry, stopping to prevent crash";
132 const bAnimListElem *first_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
135 EXPECT_EQ(&cube->id, first_ale->id) << "id should be the animated ID (" << cube->id.name
136 << ") but is (" << first_ale->id->name << ")";
137 EXPECT_EQ(cube->adt, first_ale->adt) << "adt should be the animated ID's animation data";
138 EXPECT_EQ(&action->id, first_ale->fcurve_owner_id) << "fcurve_owner_id should be the Action";
139 EXPECT_EQ(&action->id, first_ale->key_data) << "key_data should be the Action";
140 EXPECT_EQ(&slot_cube, first_ale->data);
141 EXPECT_EQ(slot_cube.slot_flags, first_ale->flag);
142
143 ASSERT_GE(num_entries, 2)
144 << "Missing 2nd ANIMTYPE_ACTION_SLOT entry, stopping to prevent crash";
145 const bAnimListElem *second_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
146 EXPECT_EQ(ANIMTYPE_ACTION_SLOT, second_ale->type);
147 EXPECT_EQ(&slot_suzanne, second_ale->data);
148 /* Assume the rest is set correctly, as it's the same code as tested above. */
149
150 ANIM_animdata_freelist(&anim_data);
151 }
152
153 { /* Test with one expanded and one collapsed slot. */
154 slot_cube.set_expanded(true);
155 slot_suzanne.set_expanded(false);
156
157 /* This should produce 2 slots and 2 FCurves. */
158 ListBase anim_data = {nullptr, nullptr};
162 const int num_entries = ANIM_animdata_filter(
163 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
164 EXPECT_EQ(4, num_entries);
165 EXPECT_EQ(4, BLI_listbase_count(&anim_data));
166
167 /* First should be Cube slot. */
168 ASSERT_GE(num_entries, 1) << "Missing 1st ale, stopping to prevent crash";
169 const bAnimListElem *ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
171 EXPECT_EQ(&slot_cube, ale->data);
172
173 /* After that the Cube's FCurves. */
174 ASSERT_GE(num_entries, 2) << "Missing 2nd ale, stopping to prevent crash";
175 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
177 EXPECT_EQ(fcu_cube_loc_x, ale->data);
178 EXPECT_EQ(slot_cube.handle, ale->slot_handle);
179
180 ASSERT_GE(num_entries, 3) << "Missing 3rd ale, stopping to prevent crash";
181 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 2));
183 EXPECT_EQ(fcu_cube_loc_y, ale->data);
184 EXPECT_EQ(slot_cube.handle, ale->slot_handle);
185
186 /* And finally the Suzanne slot. */
187 ASSERT_GE(num_entries, 4) << "Missing 4th ale, stopping to prevent crash";
188 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 3));
190 EXPECT_EQ(&slot_suzanne, ale->data);
191
192 ANIM_animdata_freelist(&anim_data);
193 }
194
195 { /* Test one expanded and one collapsed slot, and one Slot and one FCurve selected. */
196 slot_cube.set_expanded(true);
197 slot_cube.set_selected(false);
198 slot_suzanne.set_expanded(false);
199 slot_suzanne.set_selected(true);
200
201 fcu_cube_loc_x->flag &= ~FCURVE_SELECTED;
202 fcu_cube_loc_y->flag |= FCURVE_SELECTED;
203
204 /* This should produce 1 slot and 1 FCurve. */
205 ListBase anim_data = {nullptr, nullptr};
209 const int num_entries = ANIM_animdata_filter(
210 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
211 EXPECT_EQ(2, num_entries);
212 EXPECT_EQ(2, BLI_listbase_count(&anim_data));
213
214 /* First should be Cube's selected FCurve. */
215 const bAnimListElem *ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
217 EXPECT_EQ(fcu_cube_loc_y, ale->data);
218
219 /* Second the Suzanne slot, as that's the only selected slot. */
220 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
222 EXPECT_EQ(&slot_suzanne, ale->data);
223
224 ANIM_animdata_freelist(&anim_data);
225 }
226}
227
228TEST_F(ActionFilterTest, layered_action_active_fcurves)
229{
230 Slot &slot_cube = action->slot_add();
231 /* The Action+Slot has to be assigned to what the bAnimContext thinks is the active Object.
232 * See the BLI_assert_msg() call in the ANIMCONT_ACTION case of ANIM_animdata_filter(). */
233 ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
234
235 Layer &layer = action->layer_add("Kübus layer");
236 Strip &key_strip = layer.strip_add(*action, Strip::Type::Keyframe);
237 StripKeyframeData &strip_data = key_strip.data<StripKeyframeData>(*action);
238
239 /* Create multiple FCurves. */
240 const KeyframeSettings settings = get_keyframe_settings(false);
241 ASSERT_EQ(
243 strip_data.keyframe_insert(bmain, slot_cube, {"location", 0}, {1.0f, 0.25f}, settings));
244 ASSERT_EQ(
246 strip_data.keyframe_insert(bmain, slot_cube, {"location", 1}, {1.0f, 0.25f}, settings));
247
248 /* Set one F-Curve as the active one, and the other as inactive. The latter is necessary because
249 * by default the first curve is automatically marked active, but that's too trivial a test case
250 * (it's too easy to mistakenly just return the first-seen F-Curve). */
251 Channelbag *cube_channelbag = strip_data.channelbag_for_slot(slot_cube);
252 ASSERT_NE(nullptr, cube_channelbag);
253 FCurve *fcurve_active = cube_channelbag->fcurve_find({"location", 1});
254 fcurve_active->flag |= FCURVE_ACTIVE;
255 FCurve *fcurve_other = cube_channelbag->fcurve_find({"location", 0});
256 fcurve_other->flag &= ~FCURVE_ACTIVE;
257
258 /* Mock an bAnimContext for the Action editor. */
259 SpaceAction saction = {nullptr};
261
262 bAnimContext ac = {nullptr};
263 ac.bmain = bmain;
265 ac.data = action;
267 ac.sl = reinterpret_cast<SpaceLink *>(&saction);
268 ac.obact = cube;
269 ac.active_action = action;
270 ac.active_action_user = &cube->id;
271 ac.ads = &saction.ads;
272
273 {
274 /* This should produce just the active F-Curve. */
275 ListBase anim_data = {nullptr, nullptr};
278 const int num_entries = ANIM_animdata_filter(
279 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
280 EXPECT_EQ(1, num_entries);
281 EXPECT_EQ(1, BLI_listbase_count(&anim_data));
282
283 const bAnimListElem *first_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
284 EXPECT_EQ(ANIMTYPE_FCURVE, first_ale->type);
285 EXPECT_EQ(ALE_FCURVE, first_ale->datatype);
286 EXPECT_EQ(fcurve_active, first_ale->data);
287
288 ANIM_animdata_freelist(&anim_data);
289 }
290}
291
292} // namespace blender::animrig::tests
Functions and classes to work with Actions.
Functions to modify FCurves.
Blender kernel action and pose functionality.
#define G_MAIN
void BKE_idtype_init()
Definition idtype.cc:121
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1514
Main * BKE_main_new()
Definition main.cc:89
void BKE_main_free(Main *bmain)
Definition main.cc:192
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
void CLG_exit()
Definition clog.cc:880
void CLG_init()
Definition clog.cc:873
eDopeSheet_FilterFlag
@ FCURVE_ACTIVE
@ FCURVE_SELECTED
Object is a sort of wrapper for general info.
@ OB_EMPTY
@ SPACE_ACTION
@ ANIMTYPE_ACTION_SLOT
@ ANIMTYPE_FCURVE
@ ALE_FCURVE
@ ALE_ACTION_SLOT
eAnimCont_Types
@ ANIMCONT_ACTION
eAnimFilter_Flags
@ ANIMFILTER_ACTIVE
@ ANIMFILTER_FOREDIT
@ ANIMFILTER_DATA_VISIBLE
@ ANIMFILTER_LIST_VISIBLE
@ ANIMFILTER_LIST_CHANNELS
@ ANIMFILTER_NODUPLIS
@ ANIMFILTER_FCURVESONLY
@ ANIMFILTER_SEL
void ANIM_animdata_freelist(ListBase *anim_data)
Definition anim_deps.cc:463
size_t ANIM_animdata_filter(bAnimContext *ac, ListBase *anim_data, const eAnimFilter_Flags filter_mode, void *data, const eAnimCont_Types datatype)
const FCurve * fcurve_find(const FCurveDescriptor &fcurve_descriptor) const
Strip & strip_add(Action &owning_action, Strip::Type strip_type)
const Channelbag * channelbag_for_slot(const Slot &slot) const
SingleKeyingResult keyframe_insert(Main *bmain, const Slot &slot, const FCurveDescriptor &fcurve_descriptor, float2 time_value, const KeyframeSettings &settings, eInsertKeyFlags insert_key_flags=INSERTKEY_NOFLAGS, std::optional< float2 > cycle_range=std::nullopt)
const T & data(const Action &owning_action) const
#define filter
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
KeyframeSettings get_keyframe_settings(bool from_userprefs)
ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id)
bool assign_action(bAction *action, ID &animated_id)
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
char name[258]
Definition DNA_ID.h:432
SpaceLink * sl
eAnimCont_Types datatype
bDopeSheet * ads
ID * active_action_user
bAction * active_action
eSpace_Type spacetype
Object * obact
int32_t slot_handle
AnimData * adt
eAnim_ChannelType type
eAnim_KeyType datatype