Blender V5.0
keyframes_keylist_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
7#include "ANIM_action.hh"
8#include "ANIM_fcurve.hh"
9
10#include "ED_anim_api.hh"
12
13#include "DNA_anim_types.h"
14#include "DNA_curve_types.h"
15
16#include "MEM_guardedalloc.h"
17
18#include "BKE_action.hh"
19#include "BKE_armature.hh"
20#include "BKE_fcurve.hh"
21#include "BKE_global.hh"
22#include "BKE_idtype.hh"
23#include "BKE_lib_id.hh"
24#include "BKE_main.hh"
25#include "BKE_object.hh"
26
27#include "BLI_listbase.h"
28#include "BLI_string_utf8.h"
29
30#include "CLG_log.h"
31#include "testing/testing.h"
32
33#include <functional>
34#include <optional>
35
37
38const float KEYLIST_NEAR_ERROR = 0.1;
39const float FRAME_STEP = 0.005;
40
41/* Build FCurve with keys on frames 10, 20, and 30. */
42static void build_fcurve(FCurve &fcurve)
43{
44 fcurve.totvert = 3;
45 fcurve.bezt = MEM_calloc_arrayN<BezTriple>(fcurve.totvert, "BezTriples");
46 fcurve.bezt[0].vec[1][0] = 10.0f;
47 fcurve.bezt[0].vec[1][1] = 1.0f;
48 fcurve.bezt[1].vec[1][0] = 20.0f;
49 fcurve.bezt[1].vec[1][1] = 2.0f;
50 fcurve.bezt[2].vec[1][0] = 30.0f;
51 fcurve.bezt[2].vec[1][1] = 1.0f;
52}
53
55{
56 FCurve *fcurve = BKE_fcurve_create();
57 build_fcurve(*fcurve);
58
59 AnimKeylist *keylist = ED_keylist_create();
60 fcurve_to_keylist(nullptr, fcurve, keylist, 0, {-FLT_MAX, FLT_MAX}, false);
61 BKE_fcurve_free(fcurve);
62
64 return keylist;
65}
66
67static void assert_act_key_column(const ActKeyColumn *column,
68 const std::optional<float> expected_frame)
69{
70 if (expected_frame.has_value()) {
71 ASSERT_NE(column, nullptr) << "Expected a frame to be found at " << *expected_frame;
72 EXPECT_NEAR(column->cfra, *expected_frame, KEYLIST_NEAR_ERROR);
73 }
74 else {
75 EXPECT_EQ(column, nullptr) << "Expected no frame to be found, but found " << column->cfra;
76 }
77}
78
79using KeylistFindFunction = std::function<const ActKeyColumn *(const AnimKeylist *, float)>;
80
81static void check_keylist_find_range(const AnimKeylist *keylist,
82 KeylistFindFunction keylist_find_func,
83 const float frame_from,
84 const float frame_to,
85 const std::optional<float> expected_frame)
86{
87 float cfra = frame_from;
88 for (; cfra < frame_to; cfra += FRAME_STEP) {
89 const ActKeyColumn *found = keylist_find_func(keylist, cfra);
90 assert_act_key_column(found, expected_frame);
91 }
92}
93
94static void check_keylist_find_next_range(const AnimKeylist *keylist,
95 const float frame_from,
96 const float frame_to,
97 const std::optional<float> expected_frame)
98{
99 check_keylist_find_range(keylist, ED_keylist_find_next, frame_from, frame_to, expected_frame);
100}
101
102TEST(keylist, find_next)
103{
104 AnimKeylist *keylist = create_test_keylist();
105
106 check_keylist_find_next_range(keylist, 0.0f, 9.99f, 10.0f);
107 check_keylist_find_next_range(keylist, 10.0f, 19.99f, 20.0f);
108 check_keylist_find_next_range(keylist, 20.0f, 29.99f, 30.0f);
109 check_keylist_find_next_range(keylist, 30.0f, 39.99f, std::nullopt);
110
111 ED_keylist_free(keylist);
112}
113
115 const float frame_from,
116 const float frame_to,
117 const std::optional<float> expected_frame)
118{
119 check_keylist_find_range(keylist, ED_keylist_find_prev, frame_from, frame_to, expected_frame);
120}
121
122TEST(keylist, find_prev)
123{
124 AnimKeylist *keylist = create_test_keylist();
125
126 check_keylist_find_prev_range(keylist, 0.0f, 10.00f, std::nullopt);
127 check_keylist_find_prev_range(keylist, 10.01f, 20.00f, 10.0f);
128 check_keylist_find_prev_range(keylist, 20.01f, 30.00f, 20.0f);
129 check_keylist_find_prev_range(keylist, 30.01f, 49.99f, 30.0f);
130
131 ED_keylist_free(keylist);
132}
133
135 const float frame_from,
136 const float frame_to,
137 const std::optional<float> expected_frame)
138{
139 check_keylist_find_range(keylist, ED_keylist_find_exact, frame_from, frame_to, expected_frame);
140}
141
142TEST(keylist, find_exact)
143{
144 AnimKeylist *keylist = create_test_keylist();
145
146 check_keylist_find_exact_range(keylist, 0.0f, 9.99f, std::nullopt);
147 check_keylist_find_exact_range(keylist, 9.9901f, 10.01f, 10.0f);
148 check_keylist_find_exact_range(keylist, 10.01f, 19.99f, std::nullopt);
149 check_keylist_find_exact_range(keylist, 19.9901f, 20.01f, 20.0f);
150 check_keylist_find_exact_range(keylist, 20.01f, 29.99f, std::nullopt);
151 check_keylist_find_exact_range(keylist, 29.9901f, 30.01f, 30.0f);
152 check_keylist_find_exact_range(keylist, 30.01f, 49.99f, std::nullopt);
153
154 ED_keylist_free(keylist);
155}
156
157TEST(keylist, find_closest)
158{
159 AnimKeylist *keylist = create_test_keylist();
160
161 {
162 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, -1);
163 EXPECT_EQ(closest->cfra, 10.0);
164 }
165
166 {
167 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 10);
168 EXPECT_EQ(closest->cfra, 10.0);
169 }
170
171 {
172 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 14.999);
173 EXPECT_EQ(closest->cfra, 10.0);
174 }
175 {
176 /* When the distance between key columns is equal, the previous column is chosen */
177 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 15);
178 EXPECT_EQ(closest->cfra, 10.0);
179 }
180 {
181 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 15.001);
182 EXPECT_EQ(closest->cfra, 20.0);
183 }
184 {
185 const ActKeyColumn *closest = ED_keylist_find_closest(keylist, 30.001);
186 EXPECT_EQ(closest->cfra, 30.0);
187 }
188 ED_keylist_free(keylist);
189}
190
191class KeylistSummaryTest : public testing::Test {
192 public:
200
201 SpaceAction saction = {nullptr};
202 bAnimContext ac = {nullptr};
203
204 static void SetUpTestSuite()
205 {
206 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
207 CLG_init();
208
209 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
211 }
212
213 static void TearDownTestSuite()
214 {
215 CLG_exit();
216 }
217
218 void SetUp() override
219 {
221 G_MAIN = bmain; /* For BKE_animdata_free(). */
222
223 action = &BKE_id_new<bAction>(bmain, "ACÄnimåtië")->wrap();
225
226 armature_data = BKE_armature_add(bmain, "ARArmature");
227 bone1 = reinterpret_cast<Bone *>(MEM_callocN(sizeof(Bone), "KeylistSummaryTest"));
228 bone2 = reinterpret_cast<Bone *>(MEM_callocN(sizeof(Bone), "KeylistSummaryTest"));
229 STRNCPY_UTF8(bone1->name, "Bone.001");
230 STRNCPY_UTF8(bone2->name, "Bone.002");
231 BLI_addtail(&armature_data->bonebase, bone1);
232 BLI_addtail(&armature_data->bonebase, bone2);
234
236 armature->data = armature_data;
238
239 /*
240 * Fill in the common bits for the mock bAnimContext, for an Action editor.
241 *
242 * Tests should fill in:
243 * - ac.obact
244 * - ac.active_action_user (= &ac.obact.id)
245 */
246 saction.ads.filterflag = eDopeSheet_FilterFlag(0);
247 ac.bmain = bmain;
248 ac.datatype = ANIMCONT_ACTION;
249 ac.data = action;
250 ac.spacetype = SPACE_ACTION;
251 ac.sl = reinterpret_cast<SpaceLink *>(&saction);
252 ac.ads = &saction.ads;
253 ac.active_action = action;
254 }
255
256 void TearDown() override
257 {
258 ac.obact = nullptr;
259 ac.active_action = nullptr;
260 ac.active_action_user = nullptr;
261
263 G_MAIN = nullptr;
264 }
265};
266
267TEST_F(KeylistSummaryTest, slot_summary_simple)
268{
269 /* Test that a key summary is generated correctly for a slot that's animating
270 * an object's transforms. */
271
272 using namespace blender::animrig;
273
274 Slot &slot_cube = action->slot_add_for_id(cube->id);
275 ASSERT_EQ(ActionSlotAssignmentResult::OK, assign_action_and_slot(action, &slot_cube, cube->id));
276 Channelbag &channelbag = action_channelbag_ensure(*action, cube->id);
277
278 FCurve &loc_x = channelbag.fcurve_ensure(bmain, {"location", 0});
279 FCurve &loc_y = channelbag.fcurve_ensure(bmain, {"location", 1});
280 FCurve &loc_z = channelbag.fcurve_ensure(bmain, {"location", 2});
281
282 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_x, {1.0, 0.0}, {}, {}));
283 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_x, {2.0, 1.0}, {}, {}));
284 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_y, {2.0, 2.0}, {}, {}));
285 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_y, {3.0, 3.0}, {}, {}));
286 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_z, {2.0, 4.0}, {}, {}));
287 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&loc_z, {5.0, 5.0}, {}, {}));
288
289 /* Generate slot summary keylist. */
290 AnimKeylist *keylist = ED_keylist_create();
291 ac.obact = cube;
292 ac.active_action_user = &cube->id;
294 &ac, &cube->id, *action, slot_cube.handle, keylist, 0, {0.0, 6.0});
296
297 const ActKeyColumn *col_0 = ED_keylist_find_exact(keylist, 0.0);
298 const ActKeyColumn *col_1 = ED_keylist_find_exact(keylist, 1.0);
299 const ActKeyColumn *col_2 = ED_keylist_find_exact(keylist, 2.0);
300 const ActKeyColumn *col_3 = ED_keylist_find_exact(keylist, 3.0);
301 const ActKeyColumn *col_4 = ED_keylist_find_exact(keylist, 4.0);
302 const ActKeyColumn *col_5 = ED_keylist_find_exact(keylist, 5.0);
303 const ActKeyColumn *col_6 = ED_keylist_find_exact(keylist, 6.0);
304
305 /* Check that we only have columns at the frames with keys. */
306 EXPECT_EQ(nullptr, col_0);
307 EXPECT_NE(nullptr, col_1);
308 EXPECT_NE(nullptr, col_2);
309 EXPECT_NE(nullptr, col_3);
310 EXPECT_EQ(nullptr, col_4);
311 EXPECT_NE(nullptr, col_5);
312 EXPECT_EQ(nullptr, col_6);
313
314 /* Check that the right number of keys are indicated in each column. */
315 EXPECT_EQ(1, col_1->totkey);
316 EXPECT_EQ(3, col_2->totkey);
317 EXPECT_EQ(1, col_3->totkey);
318 EXPECT_EQ(1, col_5->totkey);
319
320 ED_keylist_free(keylist);
321}
322
323TEST_F(KeylistSummaryTest, slot_summary_bone_selection)
324{
325 /* Test that a key summary is generated correctly, excluding keys for
326 * unselected bones when filter-by-selection is on. */
327
328 using namespace blender::animrig;
329
330 Slot &slot_armature = action->slot_add_for_id(armature->id);
332 assign_action_and_slot(action, &slot_armature, armature->id));
333 Channelbag &channelbag = action_channelbag_ensure(*action, armature->id);
334
335 FCurve &bone1_loc_x = channelbag.fcurve_ensure(
336 bmain, {"pose.bones[\"Bone.001\"].location", 0, {}, {}, "Bone.001"});
337 FCurve &bone2_loc_x = channelbag.fcurve_ensure(
338 bmain, {"pose.bones[\"Bone.002\"].location", 0, {}, {}, "Bone.002"});
339
340 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&bone1_loc_x, {1.0, 0.0}, {}, {}));
341 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&bone1_loc_x, {2.0, 1.0}, {}, {}));
342 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&bone2_loc_x, {2.0, 2.0}, {}, {}));
343 ASSERT_EQ(SingleKeyingResult::SUCCESS, insert_vert_fcurve(&bone2_loc_x, {3.0, 3.0}, {}, {}));
344
345 /* Select only Bone.001. */
346 bPoseChannel *pose_bone1 = BKE_pose_channel_find_name(armature->pose, bone1->name);
347 ASSERT_NE(pose_bone1, nullptr);
348 pose_bone1->flag |= POSE_SELECTED;
349 bPoseChannel *pose_bone2 = BKE_pose_channel_find_name(armature->pose, bone2->name);
350 pose_bone2->flag &= ~POSE_SELECTED;
351
352 /* Generate slot summary keylist. */
353 AnimKeylist *keylist = ED_keylist_create();
354 saction.ads.filterflag = ADS_FILTER_ONLYSEL; /* Filter by selection. */
355 ac.obact = armature;
356 ac.active_action_user = &armature->id;
357 ac.filters.flag = eDopeSheet_FilterFlag(saction.ads.filterflag);
359 &ac, &armature->id, *action, slot_armature.handle, keylist, 0, {0.0, 6.0});
361
362 const ActKeyColumn *col_1 = ED_keylist_find_exact(keylist, 1.0);
363 const ActKeyColumn *col_2 = ED_keylist_find_exact(keylist, 2.0);
364 const ActKeyColumn *col_3 = ED_keylist_find_exact(keylist, 3.0);
365
366 /* Check that we only have columns at the frames with keys for Bone.001. */
367 EXPECT_NE(nullptr, col_1);
368 EXPECT_NE(nullptr, col_2);
369 EXPECT_EQ(nullptr, col_3);
370
371 /* Check that the right number of keys are indicated in each column. */
372 EXPECT_EQ(1, col_1->totkey);
373 EXPECT_EQ(1, col_2->totkey);
374
375 ED_keylist_free(keylist);
376}
377
378} // namespace blender::editor::animation::tests
Functions and classes to work with Actions.
Functions to modify FCurves.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
void BKE_armature_bone_hash_make(bArmature *arm)
void BKE_pose_ensure(Main *bmain, Object *ob, bArmature *arm, bool do_id_user)
bArmature * BKE_armature_add(Main *bmain, const char *name)
FCurve * BKE_fcurve_create()
void BKE_fcurve_free(FCurve *fcu)
#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_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
#define STRNCPY_UTF8(dst, src)
void CLG_exit()
Definition clog.cc:880
void CLG_init()
Definition clog.cc:873
eDopeSheet_FilterFlag
@ ADS_FILTER_ONLYSEL
@ POSE_SELECTED
@ OB_EMPTY
@ OB_ARMATURE
@ SPACE_ACTION
@ ANIMCONT_ACTION
Read Guarded memory(de)allocation.
bool closest(btVector3 &v)
FCurve & fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
nullptr float
const ActKeyColumn * ED_keylist_find_prev(const AnimKeylist *keylist, const float cfra)
void action_slot_summary_to_keylist(bAnimContext *ac, ID *animated_id, animrig::Action &action, const animrig::slot_handle_t slot_handle, AnimKeylist *keylist, const int saction_flag, blender::float2 range)
void fcurve_to_keylist(AnimData *adt, FCurve *fcu, AnimKeylist *keylist, const int saction_flag, blender::float2 range, const bool use_nla_remapping)
const ActKeyColumn * ED_keylist_find_closest(const AnimKeylist *keylist, const float cfra)
void ED_keylist_prepare_for_direct_access(AnimKeylist *keylist)
AnimKeylist * ED_keylist_create()
void ED_keylist_free(AnimKeylist *keylist)
const ActKeyColumn * ED_keylist_find_next(const AnimKeylist *keylist, const float cfra)
const ActKeyColumn * ED_keylist_find_exact(const AnimKeylist *keylist, const float cfra)
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id)
Channelbag & action_channelbag_ensure(bAction &dna_action, ID &animated_id)
SingleKeyingResult insert_vert_fcurve(FCurve *fcu, const float2 position, const KeyframeSettings &settings, eInsertKeyFlags flag)
Main Key-framing API call.
static void check_keylist_find_next_range(const AnimKeylist *keylist, const float frame_from, const float frame_to, const std::optional< float > expected_frame)
static void check_keylist_find_exact_range(const AnimKeylist *keylist, const float frame_from, const float frame_to, const std::optional< float > expected_frame)
static void assert_act_key_column(const ActKeyColumn *column, const std::optional< float > expected_frame)
static void build_fcurve(FCurve &fcurve)
static void check_keylist_find_prev_range(const AnimKeylist *keylist, const float frame_from, const float frame_to, const std::optional< float > expected_frame)
std::function< const ActKeyColumn *(const AnimKeylist *, float)> KeylistFindFunction
static void check_keylist_find_range(const AnimKeylist *keylist, KeylistFindFunction keylist_find_func, const float frame_from, const float frame_to, const std::optional< float > expected_frame)
TEST_F(KeylistSummaryTest, slot_summary_simple)
#define FLT_MAX
Definition stdcycles.h:14
float vec[3][3]
BezTriple * bezt
unsigned int totvert