Blender V4.3
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_fcurve.hh"
11#include "BKE_global.hh"
12#include "BKE_idtype.hh"
13#include "BKE_lib_id.hh"
14#include "BKE_main.hh"
15#include "BKE_object.hh"
16
17#include "DNA_anim_types.h"
18#include "DNA_object_types.h"
19#include "DNA_space_types.h"
20
21#include "ED_anim_api.hh"
22
23#include "BLI_listbase.h"
24
25#include "CLG_log.h"
26#include "testing/testing.h"
27
29class ActionFilterTest : public testing::Test {
30 public:
35
36 static void SetUpTestSuite()
37 {
38 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
39 CLG_init();
40
41 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
43 }
44
45 static void TearDownTestSuite()
46 {
47 CLG_exit();
48 }
49
50 void SetUp() override
51 {
53 G_MAIN = bmain; /* For BKE_animdata_free(). */
54
55 action = &static_cast<bAction *>(BKE_id_new(bmain, ID_AC, "ACÄnimåtië"))->wrap();
58 }
59
60 void TearDown() override
61 {
63 G_MAIN = nullptr;
64 }
65};
66
67TEST_F(ActionFilterTest, slots_expanded_or_not)
68{
69 Slot &slot_cube = action->slot_add();
70 Slot &slot_suzanne = action->slot_add();
71 ASSERT_TRUE(assign_action(action, cube->id));
72 ASSERT_TRUE(assign_action(action, suzanne->id));
73 ASSERT_EQ(assign_action_slot(&slot_cube, cube->id), ActionSlotAssignmentResult::OK);
74 ASSERT_EQ(assign_action_slot(&slot_suzanne, suzanne->id), ActionSlotAssignmentResult::OK);
75
76 Layer &layer = action->layer_add("Kübus layer");
77 Strip &key_strip = layer.strip_add(*action, Strip::Type::Keyframe);
78 StripKeyframeData &strip_data = key_strip.data<StripKeyframeData>(*action);
79
80 /* Create multiple FCurves for multiple Slots. */
81 const KeyframeSettings settings = get_keyframe_settings(false);
82 ASSERT_EQ(
84 strip_data.keyframe_insert(bmain, slot_cube, {"location", 0}, {1.0f, 0.25f}, settings));
85 ASSERT_EQ(
87 strip_data.keyframe_insert(bmain, slot_cube, {"location", 1}, {1.0f, 0.25f}, settings));
88 ASSERT_EQ(
90 strip_data.keyframe_insert(bmain, slot_suzanne, {"location", 0}, {1.0f, 0.25f}, settings));
91 ASSERT_EQ(
93 strip_data.keyframe_insert(bmain, slot_suzanne, {"location", 1}, {1.0f, 0.25f}, settings));
94
95 ChannelBag *cube_channel_bag = strip_data.channelbag_for_slot(slot_cube);
96 ASSERT_NE(nullptr, cube_channel_bag);
97 FCurve *fcu_cube_loc_x = cube_channel_bag->fcurve_find({"location", 0});
98 FCurve *fcu_cube_loc_y = cube_channel_bag->fcurve_find({"location", 1});
99 ASSERT_NE(nullptr, fcu_cube_loc_x);
100 ASSERT_NE(nullptr, fcu_cube_loc_y);
101
102 /* Mock an bAnimContext for the Animation editor, with the above Animation showing. */
103 SpaceAction saction = {nullptr};
104 saction.action = action;
105 saction.action_slot_handle = slot_cube.handle;
107
108 bAnimContext ac = {nullptr};
109 ac.bmain = bmain;
111 ac.data = action;
113 ac.sl = reinterpret_cast<SpaceLink *>(&saction);
114 ac.obact = cube;
115 ac.ads = &saction.ads;
116
117 { /* Test with collapsed slots. */
118 slot_cube.set_expanded(false);
119 slot_suzanne.set_expanded(false);
120
121 /* This should produce 2 slots and no FCurves. */
122 ListBase anim_data = {nullptr, nullptr};
126 const int num_entries = ANIM_animdata_filter(
127 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
128 EXPECT_EQ(2, num_entries);
129 EXPECT_EQ(2, BLI_listbase_count(&anim_data));
130
131 ASSERT_GE(num_entries, 1)
132 << "Missing 1st ANIMTYPE_ACTION_SLOT entry, stopping to prevent crash";
133 const bAnimListElem *first_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
136 EXPECT_EQ(&cube->id, first_ale->id) << "id should be the animated ID (" << cube->id.name
137 << ") but is (" << first_ale->id->name << ")";
138 EXPECT_EQ(cube->adt, first_ale->adt) << "adt should be the animated ID's animation data";
139 EXPECT_EQ(&action->id, first_ale->fcurve_owner_id) << "fcurve_owner_id should be the Action";
140 EXPECT_EQ(&action->id, first_ale->key_data) << "key_data should be the Action";
141 EXPECT_EQ(&slot_cube, first_ale->data);
142 EXPECT_EQ(slot_cube.slot_flags, first_ale->flag);
143
144 ASSERT_GE(num_entries, 2)
145 << "Missing 2nd ANIMTYPE_ACTION_SLOT entry, stopping to prevent crash";
146 const bAnimListElem *second_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
147 EXPECT_EQ(ANIMTYPE_ACTION_SLOT, second_ale->type);
148 EXPECT_EQ(&slot_suzanne, second_ale->data);
149 /* Assume the rest is set correctly, as it's the same code as tested above. */
150
151 ANIM_animdata_freelist(&anim_data);
152 }
153
154 { /* Test with one expanded and one collapsed slot. */
155 slot_cube.set_expanded(true);
156 slot_suzanne.set_expanded(false);
157
158 /* This should produce 2 slots and 2 FCurves. */
159 ListBase anim_data = {nullptr, nullptr};
163 const int num_entries = ANIM_animdata_filter(
164 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
165 EXPECT_EQ(4, num_entries);
166 EXPECT_EQ(4, BLI_listbase_count(&anim_data));
167
168 /* First should be Cube slot. */
169 ASSERT_GE(num_entries, 1) << "Missing 1st ale, stopping to prevent crash";
170 const bAnimListElem *ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
172 EXPECT_EQ(&slot_cube, ale->data);
173
174 /* After that the Cube's FCurves. */
175 ASSERT_GE(num_entries, 2) << "Missing 2nd ale, stopping to prevent crash";
176 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
178 EXPECT_EQ(fcu_cube_loc_x, ale->data);
179 EXPECT_EQ(slot_cube.handle, ale->slot_handle);
180
181 ASSERT_GE(num_entries, 3) << "Missing 3rd ale, stopping to prevent crash";
182 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 2));
184 EXPECT_EQ(fcu_cube_loc_y, ale->data);
185 EXPECT_EQ(slot_cube.handle, ale->slot_handle);
186
187 /* And finally the Suzanne slot. */
188 ASSERT_GE(num_entries, 4) << "Missing 4th ale, stopping to prevent crash";
189 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 3));
191 EXPECT_EQ(&slot_suzanne, ale->data);
192
193 ANIM_animdata_freelist(&anim_data);
194 }
195
196 { /* Test one expanded and one collapsed slot, and one Slot and one FCurve selected. */
197 slot_cube.set_expanded(true);
198 slot_cube.set_selected(false);
199 slot_suzanne.set_expanded(false);
200 slot_suzanne.set_selected(true);
201
202 fcu_cube_loc_x->flag &= ~FCURVE_SELECTED;
203 fcu_cube_loc_y->flag |= FCURVE_SELECTED;
204
205 /* This should produce 1 slot and 1 FCurve. */
206 ListBase anim_data = {nullptr, nullptr};
210 const int num_entries = ANIM_animdata_filter(
211 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
212 EXPECT_EQ(2, num_entries);
213 EXPECT_EQ(2, BLI_listbase_count(&anim_data));
214
215 /* First should be Cube's selected FCurve. */
216 const bAnimListElem *ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
218 EXPECT_EQ(fcu_cube_loc_y, ale->data);
219
220 /* Second the Suzanne slot, as that's the only selected slot. */
221 ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 1));
223 EXPECT_EQ(&slot_suzanne, ale->data);
224
225 ANIM_animdata_freelist(&anim_data);
226 }
227}
228
229TEST_F(ActionFilterTest, layered_action_active_fcurves)
230{
231 Slot &slot_cube = action->slot_add();
232 /* The Action+Slot has to be assigned to what the bAnimContext thinks is the active Object.
233 * See the BLI_assert_msg() call in the ANIMCONT_ACTION case of ANIM_animdata_filter(). */
234 ASSERT_EQ(assign_action_and_slot(action, &slot_cube, cube->id), ActionSlotAssignmentResult::OK);
235
236 Layer &layer = action->layer_add("Kübus layer");
237 Strip &key_strip = layer.strip_add(*action, Strip::Type::Keyframe);
238 StripKeyframeData &strip_data = key_strip.data<StripKeyframeData>(*action);
239
240 /* Create multiple FCurves. */
241 const KeyframeSettings settings = get_keyframe_settings(false);
242 ASSERT_EQ(
244 strip_data.keyframe_insert(bmain, slot_cube, {"location", 0}, {1.0f, 0.25f}, settings));
245 ASSERT_EQ(
247 strip_data.keyframe_insert(bmain, slot_cube, {"location", 1}, {1.0f, 0.25f}, settings));
248
249 /* Set one F-Curve as the active one, and the other as inactive. The latter is necessary because
250 * by default the first curve is automatically marked active, but that's too trivial a test case
251 * (it's too easy to mistakenly just return the first-seen F-Curve). */
252 ChannelBag *cube_channel_bag = strip_data.channelbag_for_slot(slot_cube);
253 ASSERT_NE(nullptr, cube_channel_bag);
254 FCurve *fcurve_active = cube_channel_bag->fcurve_find({"location", 1});
255 fcurve_active->flag |= FCURVE_ACTIVE;
256 FCurve *fcurve_other = cube_channel_bag->fcurve_find({"location", 0});
257 fcurve_other->flag &= ~FCURVE_ACTIVE;
258
259 /* Mock an bAnimContext for the Action editor. */
260 SpaceAction saction = {nullptr};
261 saction.action = action;
262 saction.action_slot_handle = slot_cube.handle;
264
265 bAnimContext ac = {nullptr};
266 ac.bmain = bmain;
268 ac.data = action;
270 ac.sl = reinterpret_cast<SpaceLink *>(&saction);
271 ac.obact = cube;
272 ac.ads = &saction.ads;
273
274 {
275 /* This should produce just the active F-Curve. */
276 ListBase anim_data = {nullptr, nullptr};
279 const int num_entries = ANIM_animdata_filter(
280 &ac, &anim_data, filter, ac.data, eAnimCont_Types(ac.datatype));
281 EXPECT_EQ(1, num_entries);
282 EXPECT_EQ(1, BLI_listbase_count(&anim_data));
283
284 const bAnimListElem *first_ale = static_cast<bAnimListElem *>(BLI_findlink(&anim_data, 0));
285 EXPECT_EQ(ANIMTYPE_FCURVE, first_ale->type);
286 EXPECT_EQ(ALE_FCURVE, first_ale->datatype);
287 EXPECT_EQ(fcurve_active, first_ale->data);
288
289 ANIM_animdata_freelist(&anim_data);
290 }
291}
292
293} // 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:127
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1482
Main * BKE_main_new(void)
Definition main.cc:45
void BKE_main_free(Main *bmain)
Definition main.cc:175
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 struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
void CLG_exit(void)
Definition clog.c:706
void CLG_init(void)
Definition clog.c:699
@ ID_AC
@ ADS_FILTER_ALL_SLOTS
@ 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:457
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(FCurveDescriptor fcurve_descriptor) const
SingleKeyingResult keyframe_insert(Main *bmain, const Slot &slot, FCurveDescriptor fcurve_descriptor, float2 time_value, const KeyframeSettings &settings, eInsertKeyFlags insert_key_flags=INSERTKEY_NOFLAGS)
const ChannelBag * channelbag_for_slot(const Slot &slot) const
const T & data(const Action &owning_action) const
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)
float wrap(float value, float max, float min)
Definition node_math.h:71
char name[66]
Definition DNA_ID.h:425
int32_t action_slot_handle
SpaceLink * sl
eAnimCont_Types datatype
bDopeSheet * ads
eSpace_Type spacetype
Object * obact
int32_t slot_handle
AnimData * adt
eAnim_ChannelType type
eAnim_KeyType datatype