Blender V4.3
keyframing_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_keyframing.hh"
7
8#include "BKE_action.hh"
9#include "BKE_anim_data.hh"
10#include "BKE_animsys.h"
11#include "BKE_armature.hh"
12#include "BKE_fcurve.hh"
13#include "BKE_idtype.hh"
14#include "BKE_lib_id.hh"
15#include "BKE_main.hh"
16#include "BKE_material.h"
17#include "BKE_mesh.hh"
18#include "BKE_nla.hh"
19#include "BKE_object.hh"
20
21#include "DNA_anim_types.h"
22#include "DNA_material_types.h"
23#include "DNA_object_types.h"
24
25#include "RNA_access.hh"
26#include "RNA_prototypes.hh"
27
28#include "BLI_listbase.h"
29#include "BLI_string.h"
30#include "BLI_string_utf8.h"
31
32#include <limits>
33
34#include "CLG_log.h"
35#include "testing/testing.h"
36
38class KeyframingTest : public testing::Test {
39 public:
41
42 /* For standard single-action testing. */
45
46 /* For pose bone single-action testing. */
50
51 /* For NLA testing. */
55
56 /* For action reuse testing. */
63
64 static void SetUpTestSuite()
65 {
66 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
67 CLG_init();
68
69 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
71 }
72
73 static void TearDownTestSuite()
74 {
75 /* Ensure experimental baklava flag is turned off after all tests are run. */
76 U.flag &= ~USER_DEVELOPER_UI;
77 U.experimental.use_animation_baklava = 0;
78
79 CLG_exit();
80 }
81
82 void SetUp() override
83 {
84 /* Ensure experimental baklava flag is turned off first (to be enabled
85 * selectively in the layered action tests. */
86 U.flag &= ~USER_DEVELOPER_UI;
87 U.experimental.use_animation_baklava = 0;
88
90
91 object = BKE_object_add_only_object(bmain, OB_EMPTY, "Empty");
93
94 Bone *bone = static_cast<Bone *>(MEM_mallocN(sizeof(Bone), "BONE"));
95 memset(bone, 0, sizeof(Bone));
96 STRNCPY(bone->name, "Bone");
97
98 armature = BKE_armature_add(bmain, "Armature");
100
105
108 nla_action = static_cast<bAction *>(BKE_id_new(bmain, ID_AC, "NLAAction"));
109
112 cube_mesh = BKE_mesh_add(bmain, "cube_mesh");
114 /* Removing the implicit id user. Using BKE_mesh_assign_object increments the user count which
115 * would leave it at 2 otherwise. */
118 material = BKE_material_add(bmain, "material");
120
121 id_us_min(&material->id);
123
124 /* Set up an NLA system with a single NLA track with a single offset-in-time
125 * NLA strip, and make that strip active and in tweak mode. */
127 NlaTrack *track = BKE_nlatrack_new_head(&adt->nla_tracks, false);
129 track->flag |= NLATRACK_ACTIVE;
130 strip->flag |= NLASTRIP_FLAG_ACTIVE;
131 strip->start = -10.0;
132 strip->end = 990.0;
133 strip->actstart = 0.0;
134 strip->actend = 1000.0;
135 strip->scale = 1.0;
138 }
139
140 void TearDown() override
141 {
143 }
144};
145
146/* ------------------------------------------------------------
147 * Tests for `insert_keyframes()` with layered actions.
148 */
149
150/* Keying a non-array property. */
151TEST_F(KeyframingTest, insert_keyframes__layered_action__non_array_property)
152{
153 /* Turn on Baklava experimental flag. */
154 U.flag |= USER_DEVELOPER_UI;
155 U.experimental.use_animation_baklava = 1;
156
157 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
158
159 /* First time should create:
160 * - AnimData
161 * - Action
162 * - Slot
163 * - Layer
164 * - Infinite KeyframeStrip
165 * - FCurve with a single key
166 */
167 object->empty_drawsize = 42.0;
168 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
169 &object_rna_pointer,
170 std::nullopt,
171 {{"empty_display_size"}},
172 1.0,
173 anim_eval_context,
177 ASSERT_NE(nullptr, object->adt);
178 ASSERT_NE(nullptr, object->adt->action);
179 Action &action = object->adt->action->wrap();
180
181 /* The action has a slot, it's named properly, and it's correctly assigned
182 * to the object. */
183 ASSERT_EQ(1, action.slots().size());
184 Slot *slot = action.slot(0);
185 EXPECT_STREQ(object->id.name, slot->name);
186 EXPECT_STREQ(object->adt->slot_name, slot->name);
187 EXPECT_EQ(object->adt->slot_handle, slot->handle);
188
189 /* We have the default layer and strip. */
190 ASSERT_TRUE(action.is_action_layered());
191 ASSERT_EQ(1, action.layers().size());
192 ASSERT_EQ(1, action.layer(0)->strips().size());
193 EXPECT_TRUE(strlen(action.layer(0)->name) > 0);
194 Strip *strip = action.layer(0)->strip(0);
195 ASSERT_TRUE(strip->is_infinite());
196 ASSERT_EQ(Strip::Type::Keyframe, strip->type());
197 StripKeyframeData *strip_data = &strip->data<StripKeyframeData>(action);
198
199 /* We have a channel bag for the slot. */
200 ChannelBag *channel_bag = strip_data->channelbag_for_slot(*slot);
201 ASSERT_NE(nullptr, channel_bag);
202
203 /* The fcurves in the channel bag are what we expect. */
204 EXPECT_EQ(1, channel_bag->fcurves().size());
205 const FCurve *fcurve = channel_bag->fcurve_find({"empty_display_size", 0});
206 ASSERT_NE(nullptr, fcurve);
207 ASSERT_NE(nullptr, fcurve->bezt);
208 EXPECT_EQ(1, fcurve->totvert);
209 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
210 EXPECT_EQ(42.0, fcurve->bezt[0].vec[1][1]);
211
212 /* Second time inserting with a different value on the same frame should
213 * simply replace the key. */
214 object->empty_drawsize = 86.0;
215 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
216 &object_rna_pointer,
217 std::nullopt,
218 {{"empty_display_size"}},
219 1.0,
220 anim_eval_context,
224 EXPECT_EQ(1, fcurve->totvert);
225 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
226 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
227
228 /* Third time inserting on a different time should add a second key. */
229 object->empty_drawsize = 7.0;
230 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
231 &object_rna_pointer,
232 std::nullopt,
233 {{"empty_display_size"}},
234 10.0,
235 anim_eval_context,
239 EXPECT_EQ(2, fcurve->totvert);
240 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
241 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
242 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
243 EXPECT_EQ(7.0, fcurve->bezt[1].vec[1][1]);
244}
245
246TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse)
247{
248 /* Turn on Baklava experimental flag. */
249 U.flag |= USER_DEVELOPER_UI;
250 U.experimental.use_animation_baklava = 1;
251
252 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
253 CombinedKeyingResult result_ob;
254 result_ob = insert_keyframes(bmain,
255 &armature_object_rna_pointer,
256 std::nullopt,
257 {{"location"}},
258 10.0,
259 anim_eval_context,
262
263 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
264 ASSERT_TRUE(armature_object->adt != nullptr);
265 ASSERT_TRUE(armature_object->adt->action != nullptr);
266
267 PointerRNA armature_rna_pointer = RNA_id_pointer_create(&armature->id);
268
269 result_ob = insert_keyframes(bmain,
270 &armature_rna_pointer,
271 std::nullopt,
272 {{"display_type"}},
273 10.0,
274 anim_eval_context,
277 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
278 ASSERT_TRUE(armature->adt != nullptr);
279 ASSERT_TRUE(armature->adt->action != nullptr);
280
281 /* Action is expected to be reused between object and data. */
282 ASSERT_EQ(armature->adt->action, armature_object->adt->action);
283
284 Action &action = armature->adt->action->wrap();
285 /* Should have two slots now. */
286 ASSERT_EQ(action.slot_array_num, 2);
287 for (Slot *slot : action.slots()) {
288 ASSERT_TRUE(slot->idtype == ID_AR || slot->idtype == ID_OB);
289 }
290
291 U.experimental.use_animation_baklava = 0;
292 U.flag &= ~USER_DEVELOPER_UI;
293}
294
295TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse_material)
296{
297 U.flag |= USER_DEVELOPER_UI;
298 U.experimental.use_animation_baklava = 1;
299
300 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
301 CombinedKeyingResult result_ob;
302
303 result_ob = insert_keyframes(bmain,
304 &material_rna_pointer,
305 std::nullopt,
306 {{"pass_index"}},
307 1.0,
308 anim_eval_context,
311
312 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
313 ASSERT_TRUE(material->adt != nullptr);
314 ASSERT_TRUE(material->adt->action != nullptr);
315
316 result_ob = insert_keyframes(bmain,
317 &cube_rna_pointer,
318 std::nullopt,
319 {{"location"}},
320 1.0,
321 anim_eval_context,
324
325 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
326 ASSERT_TRUE(cube->adt != nullptr);
327 ASSERT_TRUE(cube->adt->action != nullptr);
328
329 /* Actions are not shared between object and material. */
330 ASSERT_NE(cube->adt->action, material->adt->action);
331
332 result_ob = insert_keyframes(bmain,
333 &cube_mesh_rna_pointer,
334 std::nullopt,
335 {{"remesh_voxel_size"}},
336 1.0,
337 anim_eval_context,
340
341 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
342 ASSERT_TRUE(cube_mesh->adt != nullptr);
343 ASSERT_TRUE(cube_mesh->adt->action != nullptr);
344
345 /* Reuse between Object and object data. */
346 ASSERT_EQ(cube_mesh->adt->action, cube->adt->action);
347 /* Still no reuse from mesh to material. */
348 ASSERT_NE(cube_mesh->adt->action, material->adt->action);
349
350 Action &action = cube->adt->action->wrap();
351 /* Should have two slots now. */
352 ASSERT_EQ(action.slot_array_num, 2);
353
354 /* Material action should have only 1 slot. */
355 ASSERT_EQ(material->adt->action->wrap().slot_array_num, 1);
356
357 for (Slot *slot : action.slots()) {
358 ASSERT_TRUE(slot->idtype == ID_ME || slot->idtype == ID_OB);
359 ASSERT_NE(slot->idtype, ID_MA);
360 }
361
362 U.experimental.use_animation_baklava = 0;
363 U.flag &= ~USER_DEVELOPER_UI;
364}
365
366TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse_multiuser)
367{
368 U.flag |= USER_DEVELOPER_UI;
369 U.experimental.use_animation_baklava = 1;
370
371 Object *another_object = BKE_object_add_only_object(bmain, OB_MESH, "another_object");
372 PointerRNA another_object_rna_pointer = RNA_id_pointer_create(&another_object->id);
373 BKE_mesh_assign_object(bmain, another_object, cube_mesh);
374
375 ASSERT_EQ(ID_REFCOUNTING_USERS(&cube_mesh->id), 2);
376
377 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
378 CombinedKeyingResult result_ob;
379
380 result_ob = insert_keyframes(bmain,
381 &cube_rna_pointer,
382 std::nullopt,
383 {{"location"}},
384 1.0,
385 anim_eval_context,
388
389 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
390 ASSERT_TRUE(cube->adt != nullptr);
391 ASSERT_TRUE(cube->adt->action != nullptr);
392
393 result_ob = insert_keyframes(bmain,
394 &cube_mesh_rna_pointer,
395 std::nullopt,
396 {{"remesh_voxel_size"}},
397 1.0,
398 anim_eval_context,
401
402 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
403 ASSERT_TRUE(cube_mesh->adt != nullptr);
404 ASSERT_TRUE(cube_mesh->adt->action != nullptr);
405
406 /* When an ID is used more than once, the action should not be reused. */
407 ASSERT_NE(cube->adt->action, cube_mesh->adt->action);
408
409 result_ob = insert_keyframes(bmain,
410 &another_object_rna_pointer,
411 std::nullopt,
412 {{"location"}},
413 1.0,
414 anim_eval_context,
417
418 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
419 ASSERT_TRUE(another_object->adt != nullptr);
420 ASSERT_TRUE(another_object->adt->action != nullptr);
421
422 /* Given that those two objects are connected by a mesh (which due to this has two users) the
423 * action shouldn't be reused between them. */
424 ASSERT_NE(cube->adt->action, another_object->adt->action);
425
426 U.experimental.use_animation_baklava = 0;
427 U.flag &= ~USER_DEVELOPER_UI;
428}
429
430/* Keying a single element of an array property. */
431TEST_F(KeyframingTest, insert_keyframes__layered_action__single_element)
432{
433 /* Turn on Baklava experimental flag. */
434 U.flag |= USER_DEVELOPER_UI;
435 U.experimental.use_animation_baklava = 1;
436
437 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
438
439 const CombinedKeyingResult result = insert_keyframes(bmain,
440 &object_rna_pointer,
441 std::nullopt,
442 {{"rotation_euler", std::nullopt, 0}},
443 1.0,
444 anim_eval_context,
447
448 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
449 ASSERT_NE(nullptr, object->adt);
450 ASSERT_NE(nullptr, object->adt->action);
451 Action &action = object->adt->action->wrap();
452 ASSERT_EQ(1, action.slots().size());
453 ASSERT_EQ(1, action.layers().size());
454 ASSERT_EQ(1, action.layer(0)->strips().size());
455 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
456 ASSERT_EQ(1, strip_data->channelbags().size());
457 ChannelBag *channel_bag = strip_data->channelbag(0);
458
459 EXPECT_EQ(1, channel_bag->fcurves().size());
460 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 0}));
461}
462
463/* Keying all elements of an array property. */
464TEST_F(KeyframingTest, insert_keyframes__layered_action__all_elements)
465{
466 /* Turn on Baklava experimental flag. */
467 U.flag |= USER_DEVELOPER_UI;
468 U.experimental.use_animation_baklava = 1;
469
470 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
471
472 const CombinedKeyingResult result = insert_keyframes(bmain,
473 &object_rna_pointer,
474 std::nullopt,
475 {{"rotation_euler"}},
476 1.0,
477 anim_eval_context,
480
481 EXPECT_EQ(3, result.get_count(SingleKeyingResult::SUCCESS));
482 ASSERT_NE(nullptr, object->adt);
483 ASSERT_NE(nullptr, object->adt->action);
484 Action &action = object->adt->action->wrap();
485 ASSERT_EQ(1, action.slots().size());
486 ASSERT_EQ(1, action.layers().size());
487 ASSERT_EQ(1, action.layer(0)->strips().size());
488 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
489 ASSERT_EQ(1, strip_data->channelbags().size());
490 ChannelBag *channel_bag = strip_data->channelbag(0);
491
492 EXPECT_EQ(3, channel_bag->fcurves().size());
493 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 0}));
494 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 1}));
495 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 2}));
496}
497
498/* Keying a pose bone from its own RNA pointer. */
499TEST_F(KeyframingTest, insert_keyframes__layered_action__pose_bone_rna_pointer)
500{
501 /* Turn on Baklava experimental flag. */
502 U.flag |= USER_DEVELOPER_UI;
503 U.experimental.use_animation_baklava = 1;
504
505 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
506 bPoseChannel *pchan = BKE_pose_channel_find_name(armature_object->pose, "Bone");
507 PointerRNA pose_bone_rna_pointer = RNA_pointer_create(
508 &armature_object->id, &RNA_PoseBone, pchan);
509
510 const CombinedKeyingResult result = insert_keyframes(bmain,
511 &pose_bone_rna_pointer,
512 std::nullopt,
513 {{"rotation_euler", std::nullopt, 0}},
514 1.0,
515 anim_eval_context,
518
519 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
520 ASSERT_NE(nullptr, armature_object->adt);
521 ASSERT_NE(nullptr, armature_object->adt->action);
522 Action &action = armature_object->adt->action->wrap();
523 ASSERT_EQ(1, action.slots().size());
524 ASSERT_EQ(1, action.layers().size());
525 ASSERT_EQ(1, action.layer(0)->strips().size());
526 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
527 ASSERT_EQ(1, strip_data->channelbags().size());
528 ChannelBag *channel_bag = strip_data->channelbag(0);
529
530 EXPECT_EQ(1, channel_bag->fcurves().size());
531 EXPECT_NE(nullptr, channel_bag->fcurve_find({"pose.bones[\"Bone\"].rotation_euler", 0}));
532}
533
534/* Keying a pose bone from its owning ID's RNA pointer. */
535TEST_F(KeyframingTest, insert_keyframes__pose_bone_owner_id_pointer)
536{
537 /* Turn on Baklava experimental flag. */
538 U.flag |= USER_DEVELOPER_UI;
539 U.experimental.use_animation_baklava = 1;
540
541 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
542
544 bmain,
545 &armature_object_rna_pointer,
546 std::nullopt,
547 {{"pose.bones[\"Bone\"].rotation_euler", std::nullopt, 0}},
548 1.0,
549 anim_eval_context,
552
553 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
554 ASSERT_NE(nullptr, armature_object->adt);
555 ASSERT_NE(nullptr, armature_object->adt->action);
556 Action &action = armature_object->adt->action->wrap();
557 ASSERT_EQ(1, action.slots().size());
558 ASSERT_EQ(1, action.layers().size());
559 ASSERT_EQ(1, action.layer(0)->strips().size());
560 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
561 ASSERT_EQ(1, strip_data->channelbags().size());
562 ChannelBag *channel_bag = strip_data->channelbag(0);
563
564 EXPECT_EQ(1, channel_bag->fcurves().size());
565 EXPECT_NE(nullptr, channel_bag->fcurve_find({"pose.bones[\"Bone\"].rotation_euler", 0}));
566}
567
568/* Keying multiple elements of multiple properties at once. */
569TEST_F(KeyframingTest, insert_keyframes__layered_action__multiple_properties)
570{
571 /* Turn on Baklava experimental flag. */
572 U.flag |= USER_DEVELOPER_UI;
573 U.experimental.use_animation_baklava = 1;
574
575 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
576
577 const CombinedKeyingResult result = insert_keyframes(bmain,
578 &object_rna_pointer,
579 std::nullopt,
580 {
581 {"empty_display_size"},
582 {"location"},
583 {"rotation_euler", std::nullopt, 0},
584 {"rotation_euler", std::nullopt, 2},
585 },
586 1.0,
587 anim_eval_context,
590
591 EXPECT_EQ(6, result.get_count(SingleKeyingResult::SUCCESS));
592 ASSERT_NE(nullptr, object->adt);
593 ASSERT_NE(nullptr, object->adt->action);
594 Action &action = object->adt->action->wrap();
595 ASSERT_EQ(1, action.slots().size());
596 ASSERT_EQ(1, action.layers().size());
597 ASSERT_EQ(1, action.layer(0)->strips().size());
598 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
599 ASSERT_EQ(1, strip_data->channelbags().size());
600 ChannelBag *channel_bag = strip_data->channelbag(0);
601
602 EXPECT_EQ(6, channel_bag->fcurves().size());
603 EXPECT_NE(nullptr, channel_bag->fcurve_find({"empty_display_size", 0}));
604 EXPECT_NE(nullptr, channel_bag->fcurve_find({"location", 0}));
605 EXPECT_NE(nullptr, channel_bag->fcurve_find({"location", 1}));
606 EXPECT_NE(nullptr, channel_bag->fcurve_find({"location", 2}));
607 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 0}));
608 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 2}));
609}
610
611/* Keying more than one ID on the same action. */
612TEST_F(KeyframingTest, insert_keyframes__layered_action__multiple_ids)
613{
614 /* Turn on Baklava experimental flag. */
615 U.flag |= USER_DEVELOPER_UI;
616 U.experimental.use_animation_baklava = 1;
617
618 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
619
620 /* First object should crate the action and get a slot and channel bag. */
621 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
622 &object_rna_pointer,
623 std::nullopt,
624 {{"empty_display_size"}},
625 1.0,
626 anim_eval_context,
630 ASSERT_NE(nullptr, object->adt);
631 ASSERT_NE(nullptr, object->adt->action);
632 Action &action = object->adt->action->wrap();
633
634 /* The action has a slot and it's assigned to the first object. */
635 ASSERT_EQ(1, action.slots().size());
636 Slot *slot_1 = action.slot_for_handle(object->adt->slot_handle);
637 ASSERT_NE(nullptr, slot_1);
638 EXPECT_STREQ(object->id.name, slot_1->name);
639 EXPECT_STREQ(object->adt->slot_name, slot_1->name);
640
641 /* Get the keyframe strip. */
642 ASSERT_TRUE(action.is_action_layered());
643 ASSERT_EQ(1, action.layers().size());
644 ASSERT_EQ(1, action.layer(0)->strips().size());
645 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
646
647 /* We have a single channel bag, and it's for the first object's slot. */
648 ASSERT_EQ(1, strip_data->channelbags().size());
649 ChannelBag *channel_bag_1 = strip_data->channelbag_for_slot(*slot_1);
650 ASSERT_NE(nullptr, channel_bag_1);
651
652 /* Assign the action to the second object, with no slot. */
653 ASSERT_TRUE(assign_action(&action, armature_object->id));
654 ASSERT_EQ(assign_action_slot(nullptr, armature_object->id), ActionSlotAssignmentResult::OK);
655
656 /* Keying the second object should go into the same action, creating a new
657 * slot and channel bag. */
658 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
659 &armature_object_rna_pointer,
660 std::nullopt,
661 {{"empty_display_size"}},
662 1.0,
663 anim_eval_context,
667
668 ASSERT_EQ(2, action.slots().size());
669 Slot *slot_2 = action.slot_for_handle(armature_object->adt->slot_handle);
670 ASSERT_NE(nullptr, slot_2);
671 EXPECT_STREQ(armature_object->id.name, slot_2->name);
672 EXPECT_STREQ(armature_object->adt->slot_name, slot_2->name);
673
674 ASSERT_EQ(2, strip_data->channelbags().size());
675 ChannelBag *channel_bag_2 = strip_data->channelbag_for_slot(*slot_2);
676 ASSERT_NE(nullptr, channel_bag_2);
677}
678
679/* Keying an object with an already-existing legacy action should do legacy
680 * keying. */
681TEST_F(KeyframingTest, insert_keyframes__baklava_legacy_action)
682{
683 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
684
685 /* Insert a key with the experimental flag off to create a legacy action. */
686 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
687 &object_rna_pointer,
688 std::nullopt,
689 {{"empty_display_size"}},
690 1.0,
691 anim_eval_context,
695
696 bAction *action = object->adt->action;
697 EXPECT_TRUE(action->wrap().is_action_legacy());
698 EXPECT_FALSE(action->wrap().is_action_layered());
699 EXPECT_EQ(1, BLI_listbase_count(&action->curves));
700
701 /* Turn on Baklava experimental flag. */
702 U.flag |= USER_DEVELOPER_UI;
703 U.experimental.use_animation_baklava = 1;
704
705 /* Insert more keys, which should also get inserted as part of the same legacy
706 * action, not a layered action. */
707 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
708 &object_rna_pointer,
709 std::nullopt,
710 {{"location"}},
711 1.0,
712 anim_eval_context,
716
717 EXPECT_EQ(action, object->adt->action);
718 EXPECT_TRUE(action->wrap().is_action_legacy());
719 EXPECT_FALSE(action->wrap().is_action_layered());
720 EXPECT_EQ(4, BLI_listbase_count(&action->curves));
721}
722
723/* Keying with the "Only Insert Available" flag. */
724TEST_F(KeyframingTest, insert_keyframes__layered_action__only_available)
725{
726 /* Turn on Baklava experimental flag. */
727 U.flag |= USER_DEVELOPER_UI;
728 U.experimental.use_animation_baklava = 1;
729
730 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
731
732 /* First attempt should fail, because there are no fcurves yet. */
733 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
734 &object_rna_pointer,
735 std::nullopt,
736 {{"rotation_euler"}},
737 1.0,
738 anim_eval_context,
741
743
744 /* It's unclear why an AnimData should be created if keying fails
745 * here. It may even be undesirable. This check is just here to ensure no
746 * *unintentional* changes in behavior. */
747 ASSERT_NE(nullptr, object->adt);
748 /* No action is created when using the flag INSERTKEY_AVAILABLE on an
749 * object without an action. */
750 ASSERT_EQ(nullptr, object->adt->action);
751
752 /* Insert a key on two of the elements without using the flag so that there
753 * will be two fcurves. */
754 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
755 &object_rna_pointer,
756 std::nullopt,
757 {
758 {"rotation_euler", std::nullopt, 0},
759 {"rotation_euler", std::nullopt, 2},
760 },
761 1.0,
762 anim_eval_context,
765
766 /* If an action is created, it should be the default action with one
767 * layer and an infinite keyframe strip. */
768 Action &action = object->adt->action->wrap();
769 ASSERT_EQ(1, action.slots().size());
770 ASSERT_EQ(1, action.layers().size());
771 ASSERT_EQ(1, action.layer(0)->strips().size());
772 EXPECT_EQ(object->adt->slot_handle, action.slot(0)->handle);
773 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
774
776 ASSERT_EQ(1, strip_data->channelbags().size());
777 ChannelBag *channel_bag = strip_data->channelbag(0);
778
779 /* Second attempt should succeed with two keys, because two of the elements
780 * now have fcurves. */
781 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
782 &object_rna_pointer,
783 std::nullopt,
784 {{"rotation_euler"}},
785 1.0,
786 anim_eval_context,
789
791 EXPECT_EQ(2, channel_bag->fcurves().size());
792 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 0}));
793 EXPECT_NE(nullptr, channel_bag->fcurve_find({"rotation_euler", 2}));
794}
795
796/* Keying with the "Only Replace" flag. */
797TEST_F(KeyframingTest, insert_keyframes__layered_action__only_replace)
798{
799 /* Turn on Baklava experimental flag. */
800 U.flag |= USER_DEVELOPER_UI;
801 U.experimental.use_animation_baklava = 1;
802
803 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
804
805 /* First attempt should fail, because there are no fcurves yet. */
806 object->rot[0] = 42.0;
807 object->rot[1] = 42.0;
808 object->rot[2] = 42.0;
809 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
810 &object_rna_pointer,
811 std::nullopt,
812 {{"rotation_euler"}},
813 1.0,
814 anim_eval_context,
818
819 /* Insert a key for two of the elements so that there will be two fcurves with
820 * one key each. */
821 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
822 &object_rna_pointer,
823 std::nullopt,
824 {
825 {"rotation_euler", std::nullopt, 0},
826 {"rotation_euler", std::nullopt, 2},
827 },
828 1.0,
829 anim_eval_context,
833 ASSERT_NE(nullptr, object->adt);
834 ASSERT_NE(nullptr, object->adt->action);
835 Action &action = object->adt->action->wrap();
836 ASSERT_EQ(1, action.slots().size());
837 ASSERT_EQ(1, action.layers().size());
838 ASSERT_EQ(1, action.layer(0)->strips().size());
839 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
840 ASSERT_EQ(1, strip_data->channelbags().size());
841 ChannelBag *channel_bag = strip_data->channelbag(0);
842
843 ASSERT_EQ(2, channel_bag->fcurves().size());
844 const FCurve *fcurve_x = channel_bag->fcurve_find({"rotation_euler", 0});
845 const FCurve *fcurve_z = channel_bag->fcurve_find({"rotation_euler", 2});
846 EXPECT_EQ(1, fcurve_x->totvert);
847 EXPECT_EQ(1, fcurve_z->totvert);
848 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
849 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
850 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
851 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
852
853 /* Second attempt should also fail, because we insert on a different frame
854 * than the two keys we just created. */
855 object->rot[0] = 86.0;
856 object->rot[1] = 86.0;
857 object->rot[2] = 86.0;
858 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
859 &object_rna_pointer,
860 std::nullopt,
861 {{"rotation_euler"}},
862 5.0,
863 anim_eval_context,
867 EXPECT_EQ(2, channel_bag->fcurves().size());
868 EXPECT_EQ(1, fcurve_x->totvert);
869 EXPECT_EQ(1, fcurve_z->totvert);
870 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
871 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
872 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
873 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
874
875 /* The third attempt, keying on the original frame, should succeed and replace
876 * the existing key on each fcurve. */
877 const CombinedKeyingResult result_4 = insert_keyframes(bmain,
878 &object_rna_pointer,
879 std::nullopt,
880 {{"rotation_euler"}},
881 1.0,
882 anim_eval_context,
886 EXPECT_EQ(2, channel_bag->fcurves().size());
887 EXPECT_EQ(1, fcurve_x->totvert);
888 EXPECT_EQ(1, fcurve_z->totvert);
889 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
890 EXPECT_EQ(86.0, fcurve_x->bezt[0].vec[1][1]);
891 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
892 EXPECT_EQ(86.0, fcurve_z->bezt[0].vec[1][1]);
893}
894
895/* Keying with the "Only Insert Needed" flag. */
896TEST_F(KeyframingTest, insert_keyframes__layered_action__only_needed)
897{
898 /* Turn on Baklava experimental flag. */
899 U.flag |= USER_DEVELOPER_UI;
900 U.experimental.use_animation_baklava = 1;
901
902 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
903
904 /* First attempt should succeed, because there are no fcurves yet. */
905 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
906 &object_rna_pointer,
907 std::nullopt,
908 {{"rotation_euler"}},
909 1.0,
910 anim_eval_context,
914 ASSERT_NE(nullptr, object->adt);
915 ASSERT_NE(nullptr, object->adt->action);
916 Action &action = object->adt->action->wrap();
917 ASSERT_EQ(1, action.slots().size());
918 ASSERT_EQ(1, action.layers().size());
919 ASSERT_EQ(1, action.layer(0)->strips().size());
920 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
921 ASSERT_EQ(1, strip_data->channelbags().size());
922 ChannelBag *channel_bag = strip_data->channelbag(0);
923
924 ASSERT_EQ(3, channel_bag->fcurves().size());
925 const FCurve *fcurve_x = channel_bag->fcurve_find({"rotation_euler", 0});
926 const FCurve *fcurve_y = channel_bag->fcurve_find({"rotation_euler", 1});
927 const FCurve *fcurve_z = channel_bag->fcurve_find({"rotation_euler", 2});
928 EXPECT_EQ(1, fcurve_x->totvert);
929 EXPECT_EQ(1, fcurve_y->totvert);
930 EXPECT_EQ(1, fcurve_z->totvert);
931
932 /* Second attempt should fail, because there is now an fcurve for the
933 * property, but its value matches the current property value. */
934 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
935 &object_rna_pointer,
936 std::nullopt,
937 {{"rotation_euler"}},
938 10.0,
939 anim_eval_context,
943 EXPECT_EQ(3, channel_bag->fcurves().size());
944 EXPECT_EQ(1, fcurve_x->totvert);
945 EXPECT_EQ(1, fcurve_y->totvert);
946 EXPECT_EQ(1, fcurve_z->totvert);
947
948 /* Third attempt should succeed on two elements, because we change the value
949 * of those elements to differ from the existing fcurves. */
950 object->rot[0] = 123.0;
951 object->rot[2] = 123.0;
952 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
953 &object_rna_pointer,
954 std::nullopt,
955 {{"rotation_euler"}},
956 10.0,
957 anim_eval_context,
960
962 EXPECT_EQ(3, channel_bag->fcurves().size());
963 EXPECT_EQ(2, fcurve_x->totvert);
964 EXPECT_EQ(1, fcurve_y->totvert);
965 EXPECT_EQ(2, fcurve_z->totvert);
966}
967
968/* ------------------------------------------------------------
969 * Tests for `insert_keyframes()` with legacy actions.
970 */
971
972/* Keying a non-array property. */
973TEST_F(KeyframingTest, insert_keyframes__legacy_action__non_array_property)
974{
975 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
976
977 /* First time should create the AnimData, Action, and FCurve with a single
978 * key. */
979 object->empty_drawsize = 42.0;
980 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
981 &object_rna_pointer,
982 std::nullopt,
983 {{"empty_display_size"}},
984 1.0,
985 anim_eval_context,
989 ASSERT_NE(nullptr, object->adt);
990 ASSERT_NE(nullptr, object->adt->action);
991 EXPECT_EQ(1, BLI_listbase_count(&object->adt->action->curves));
992 FCurve *fcurve = BKE_fcurve_find(&object->adt->action->curves, "empty_display_size", 0);
993 ASSERT_NE(nullptr, fcurve);
994 ASSERT_NE(nullptr, fcurve->bezt);
995 EXPECT_EQ(1, fcurve->totvert);
996 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
997 EXPECT_EQ(42.0, fcurve->bezt[0].vec[1][1]);
998
999 /* Second time inserting with a different value on the same frame should
1000 * simply replace the key. */
1001 object->empty_drawsize = 86.0;
1002 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1003 &object_rna_pointer,
1004 std::nullopt,
1005 {{"empty_display_size"}},
1006 1.0,
1007 anim_eval_context,
1011 EXPECT_EQ(1, fcurve->totvert);
1012 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
1013 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
1014
1015 /* Third time inserting on a different time should add a second key. */
1016 object->empty_drawsize = 7.0;
1017 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
1018 &object_rna_pointer,
1019 std::nullopt,
1020 {{"empty_display_size"}},
1021 10.0,
1022 anim_eval_context,
1026 EXPECT_EQ(2, fcurve->totvert);
1027 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
1028 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
1029 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
1030 EXPECT_EQ(7.0, fcurve->bezt[1].vec[1][1]);
1031}
1032
1033/* Passing the frame number explicitly vs not. */
1034TEST_F(KeyframingTest, insert_keyframes__legacy_action__optional_frame)
1035{
1036 /* If the frame number is not explicitly passed, the eval frame from the
1037 * animation evaluation context should be used. */
1038 AnimationEvalContext anim_eval_context = {nullptr, 5.0};
1039
1040 object->rotmode = ROT_MODE_XYZ;
1041 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1042 &object_rna_pointer,
1043 std::nullopt,
1044 {{"rotation_mode"}},
1045 std::nullopt,
1046 anim_eval_context,
1050 FCurve *fcurve = BKE_fcurve_find(&object->adt->action->curves, "rotation_mode", 0);
1051 EXPECT_EQ(5.0, fcurve->bezt[0].vec[1][0]);
1052 EXPECT_EQ(float(ROT_MODE_XYZ), fcurve->bezt[0].vec[1][1]);
1053
1054 /* If the frame number *is* explicitly passed, it should be used. */
1055 object->rotmode = ROT_MODE_QUAT;
1056 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1057 &object_rna_pointer,
1058 std::nullopt,
1059 {{"rotation_mode"}},
1060 10.0,
1061 anim_eval_context,
1065 EXPECT_EQ(5.0, fcurve->bezt[0].vec[1][0]);
1066 EXPECT_EQ(float(ROT_MODE_XYZ), fcurve->bezt[0].vec[1][1]);
1067 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
1068 EXPECT_EQ(float(ROT_MODE_QUAT), fcurve->bezt[1].vec[1][1]);
1069}
1070
1071/* Passing the channel group explicitly vs not. */
1072TEST_F(KeyframingTest, insert_keyframes__legacy_action__optional_channel_group)
1073{
1074 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1075
1076 /* If the channel group is not explicitly passed, the default should be used. */
1077 const CombinedKeyingResult result_1 = insert_keyframes(
1078 bmain,
1079 &object_rna_pointer,
1080 std::nullopt,
1081 {{"location", std::nullopt, 0}, {"visible_shadow"}},
1082 1.0,
1083 anim_eval_context,
1087
1088 /* Location X should get the default transform group. */
1089 FCurve *fcurve_location_x = BKE_fcurve_find(&object->adt->action->curves, "location", 0);
1090 ASSERT_NE(nullptr, fcurve_location_x->grp);
1091 EXPECT_EQ(0, strcmp("Object Transforms", fcurve_location_x->grp->name));
1092
1093 /* Shadow visibility should get no group. */
1094 FCurve *fcurve_visible_shadow = BKE_fcurve_find(
1095 &object->adt->action->curves, "visible_shadow", 0);
1096 ASSERT_EQ(nullptr, fcurve_visible_shadow->grp);
1097
1098 /* If the channel group *is* explicitly passed, it should override the default. */
1099 const CombinedKeyingResult result_2 = insert_keyframes(
1100 bmain,
1101 &object_rna_pointer,
1102 "Foo",
1103 {{"location", std::nullopt, 1}, {"hide_render"}},
1104 1.0,
1105 anim_eval_context,
1109
1110 /* Both location Y and render visibility should get the "Foo" group. */
1111 FCurve *fcurve_location_y = BKE_fcurve_find(&object->adt->action->curves, "location", 1);
1112 ASSERT_NE(nullptr, fcurve_location_y->grp);
1113 EXPECT_EQ(0, strcmp("Foo", fcurve_location_y->grp->name));
1114 FCurve *fcurve_hide_render = BKE_fcurve_find(&object->adt->action->curves, "hide_render", 0);
1115 ASSERT_NE(nullptr, fcurve_hide_render->grp);
1116 EXPECT_EQ(0, strcmp("Foo", fcurve_hide_render->grp->name));
1117}
1118
1119/* Keying a single element of an array property. */
1120TEST_F(KeyframingTest, insert_keyframes__single_element)
1121{
1122 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1123
1124 const CombinedKeyingResult result = insert_keyframes(bmain,
1125 &object_rna_pointer,
1126 std::nullopt,
1127 {{"rotation_euler", std::nullopt, 0}},
1128 1.0,
1129 anim_eval_context,
1132
1133 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
1134 ASSERT_NE(nullptr, object->adt);
1135 ASSERT_NE(nullptr, object->adt->action);
1136 EXPECT_EQ(1, BLI_listbase_count(&object->adt->action->curves));
1137 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1138}
1139
1140/* Keying all elements of an array property. */
1141TEST_F(KeyframingTest, insert_keyframes__legacy_action__all_elements)
1142{
1143 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1144
1145 const CombinedKeyingResult result = insert_keyframes(bmain,
1146 &object_rna_pointer,
1147 std::nullopt,
1148 {{"rotation_euler"}},
1149 1.0,
1150 anim_eval_context,
1153
1154 EXPECT_EQ(3, result.get_count(SingleKeyingResult::SUCCESS));
1155 ASSERT_NE(nullptr, object->adt);
1156 ASSERT_NE(nullptr, object->adt->action);
1157 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1158 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1159 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 1));
1160 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1161}
1162
1163/* Keying a pose bone from its own RNA pointer. */
1164TEST_F(KeyframingTest, insert_keyframes__legacy_action__pose_bone_rna_pointer)
1165{
1166 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1167 bPoseChannel *pchan = BKE_pose_channel_find_name(armature_object->pose, "Bone");
1168 PointerRNA pose_bone_rna_pointer = RNA_pointer_create(
1169 &armature_object->id, &RNA_PoseBone, pchan);
1170
1171 const CombinedKeyingResult result = insert_keyframes(bmain,
1172 &pose_bone_rna_pointer,
1173 std::nullopt,
1174 {{"rotation_euler", std::nullopt, 0}},
1175 1.0,
1176 anim_eval_context,
1179
1180 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
1181 ASSERT_NE(nullptr, armature_object->adt);
1182 ASSERT_NE(nullptr, armature_object->adt->action);
1183 EXPECT_EQ(1, BLI_listbase_count(&armature_object->adt->action->curves));
1184 EXPECT_NE(nullptr,
1186 &armature_object->adt->action->curves, "pose.bones[\"Bone\"].rotation_euler", 0));
1187}
1188
1189/* Keying a pose bone from its owning ID's RNA pointer. */
1190TEST_F(KeyframingTest, insert_keyframes__legacy_action__pose_bone_owner_id_pointer)
1191{
1192 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1193
1195 bmain,
1196 &armature_object_rna_pointer,
1197 std::nullopt,
1198 {{"pose.bones[\"Bone\"].rotation_euler", std::nullopt, 0}},
1199 1.0,
1200 anim_eval_context,
1203
1204 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
1205 ASSERT_NE(nullptr, armature_object->adt);
1206 ASSERT_NE(nullptr, armature_object->adt->action);
1207 EXPECT_EQ(1, BLI_listbase_count(&armature_object->adt->action->curves));
1208 EXPECT_NE(nullptr,
1210 &armature_object->adt->action->curves, "pose.bones[\"Bone\"].rotation_euler", 0));
1211}
1212
1213/* Keying multiple elements of multiple properties at once. */
1214TEST_F(KeyframingTest, insert_keyframes__legacy_action__multiple_properties)
1215{
1216 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1217
1218 const CombinedKeyingResult result =
1219
1220 insert_keyframes(bmain,
1221 &object_rna_pointer,
1222 std::nullopt,
1223 {
1224 {"empty_display_size"},
1225 {"location"},
1226 {"rotation_euler", std::nullopt, 0},
1227 {"rotation_euler", std::nullopt, 2},
1228 },
1229 1.0,
1230 anim_eval_context,
1233
1234 EXPECT_EQ(6, result.get_count(SingleKeyingResult::SUCCESS));
1235 ASSERT_NE(nullptr, object->adt);
1236 ASSERT_NE(nullptr, object->adt->action);
1237 EXPECT_EQ(6, BLI_listbase_count(&object->adt->action->curves));
1238 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "empty_display_size", 0));
1239 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 0));
1240 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 1));
1241 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 2));
1242 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1243 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1244}
1245
1246/* Keying with the "Only Insert Available" flag. */
1247TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_available)
1248{
1249 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1250
1251 /* First attempt should fail, because there are no fcurves yet. */
1252 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1253 &object_rna_pointer,
1254 std::nullopt,
1255 {{"rotation_euler"}},
1256 1.0,
1257 anim_eval_context,
1260
1262
1263 /* It's unclear why an AnimData should be created if keying fails
1264 * here. It may even be undesirable. This check is just here to ensure no
1265 * *unintentional* changes in behavior. */
1266 ASSERT_NE(nullptr, object->adt);
1267 /* No action is created when using the flag INSERTKEY_AVAILABLE on an
1268 * object without an action. */
1269 ASSERT_EQ(nullptr, object->adt->action);
1270
1271 /* Insert a key on two of the elements without using the flag so that there
1272 * will be two fcurves. */
1273 insert_keyframes(bmain,
1274 &object_rna_pointer,
1275 std::nullopt,
1276 {
1277 {"rotation_euler", std::nullopt, 0},
1278 {"rotation_euler", std::nullopt, 2},
1279 },
1280 1.0,
1281 anim_eval_context,
1284
1285 /* Second attempt should succeed with two keys, because two of the elements
1286 * now have fcurves. */
1287 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1288 &object_rna_pointer,
1289 std::nullopt,
1290 {{"rotation_euler"}},
1291 1.0,
1292 anim_eval_context,
1295
1296 EXPECT_EQ(2, result_2.get_count(SingleKeyingResult::SUCCESS));
1297 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1298 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1299 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1300}
1301
1302/* Keying with the "Only Replace" flag. */
1303TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_replace)
1304{
1305 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1306
1307 /* First attempt should fail, because there are no fcurves yet. */
1308 object->rot[0] = 42.0;
1309 object->rot[1] = 42.0;
1310 object->rot[2] = 42.0;
1311 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1312 &object_rna_pointer,
1313 std::nullopt,
1314 {{"rotation_euler"}},
1315 1.0,
1316 anim_eval_context,
1320
1321 /* Insert a key for two of the elements so that there will be two fcurves with
1322 * one key each. */
1323 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1324 &object_rna_pointer,
1325 std::nullopt,
1326 {
1327 {"rotation_euler", std::nullopt, 0},
1328 {"rotation_euler", std::nullopt, 2},
1329 },
1330 1.0,
1331 anim_eval_context,
1335 ASSERT_NE(nullptr, object->adt);
1336 ASSERT_NE(nullptr, object->adt->action);
1337 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1338 FCurve *fcurve_x = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0);
1339 FCurve *fcurve_z = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2);
1340 ASSERT_NE(nullptr, fcurve_x);
1341 ASSERT_NE(nullptr, fcurve_z);
1342 ASSERT_NE(nullptr, fcurve_x->bezt);
1343 ASSERT_NE(nullptr, fcurve_z->bezt);
1344 EXPECT_EQ(1, fcurve_x->totvert);
1345 EXPECT_EQ(1, fcurve_z->totvert);
1346 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1347 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
1348 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1349 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
1350
1351 /* Second attempt should also fail, because we insert on a different frame
1352 * than the two keys we just created. */
1353 object->rot[0] = 86.0;
1354 object->rot[1] = 86.0;
1355 object->rot[2] = 86.0;
1356 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
1357 &object_rna_pointer,
1358 std::nullopt,
1359 {{"rotation_euler"}},
1360 5.0,
1361 anim_eval_context,
1365 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1366 EXPECT_EQ(1, fcurve_x->totvert);
1367 EXPECT_EQ(1, fcurve_z->totvert);
1368 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1369 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
1370 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1371 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
1372
1373 /* The third attempt, keying on the original frame, should succeed and replace
1374 * the existing key on each fcurve. */
1375 const CombinedKeyingResult result_4 = insert_keyframes(bmain,
1376 &object_rna_pointer,
1377 std::nullopt,
1378 {{"rotation_euler"}},
1379 1.0,
1380 anim_eval_context,
1384 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1385 EXPECT_EQ(1, fcurve_x->totvert);
1386 EXPECT_EQ(1, fcurve_z->totvert);
1387 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1388 EXPECT_EQ(86.0, fcurve_x->bezt[0].vec[1][1]);
1389 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1390 EXPECT_EQ(86.0, fcurve_z->bezt[0].vec[1][1]);
1391}
1392
1393/* Keying with the "Only Insert Needed" flag. */
1394TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_needed)
1395{
1396 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1397
1398 /* First attempt should succeed, because there are no fcurves yet. */
1399 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1400 &object_rna_pointer,
1401 std::nullopt,
1402 {{"rotation_euler"}},
1403 1.0,
1404 anim_eval_context,
1407
1409 ASSERT_NE(nullptr, object->adt);
1410 ASSERT_NE(nullptr, object->adt->action);
1411 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1412 FCurve *fcurve_x = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0);
1413 FCurve *fcurve_y = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 1);
1414 FCurve *fcurve_z = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2);
1415 ASSERT_NE(nullptr, fcurve_x);
1416 ASSERT_NE(nullptr, fcurve_y);
1417 ASSERT_NE(nullptr, fcurve_z);
1418
1419 /* Second attempt should fail, because there is now an fcurve for the
1420 * property, but its value matches the current property value. */
1421 anim_eval_context.eval_time = 10.0;
1422 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1423 &object_rna_pointer,
1424 std::nullopt,
1425 {{"rotation_euler"}},
1426 10.0,
1427 anim_eval_context,
1430
1432 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1433 EXPECT_EQ(1, fcurve_x->totvert);
1434 EXPECT_EQ(1, fcurve_y->totvert);
1435 EXPECT_EQ(1, fcurve_z->totvert);
1436
1437 /* Third attempt should succeed on two elements, because we change the value
1438 * of those elements to differ from the existing fcurves. */
1439 object->rot[0] = 123.0;
1440 object->rot[2] = 123.0;
1441 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
1442 &object_rna_pointer,
1443 std::nullopt,
1444 {{"rotation_euler"}},
1445 10.0,
1446 anim_eval_context,
1449
1451 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1452 EXPECT_EQ(2, fcurve_x->totvert);
1453 EXPECT_EQ(1, fcurve_y->totvert);
1454 EXPECT_EQ(2, fcurve_z->totvert);
1455}
1456
1457/* Inserting a key into an NLA strip that has a time offset should remap the
1458 * key's time to the local time of the strip. */
1459TEST_F(KeyframingTest, insert_keyframes__nla_time_remapping)
1460{
1461 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1462
1463 const CombinedKeyingResult result = insert_keyframes(bmain,
1464 &object_with_nla_rna_pointer,
1465 std::nullopt,
1466 {{"location", std::nullopt, 0}},
1467 1.0,
1468 anim_eval_context,
1471
1472 EXPECT_EQ(1, result.get_count(SingleKeyingResult::SUCCESS));
1473 EXPECT_EQ(1, BLI_listbase_count(&nla_action->curves));
1474 FCurve *fcurve = BKE_fcurve_find(&nla_action->curves, "location", 0);
1475 ASSERT_NE(nullptr, fcurve);
1476 ASSERT_NE(nullptr, fcurve->bezt);
1477 EXPECT_EQ(1, fcurve->totvert);
1478 EXPECT_EQ(11.0, fcurve->bezt[0].vec[1][0]);
1479}
1480
1481/* ------------------------------------------------------------
1482 * Testing a special case of the NLA system:
1483 * When keying a strip with the "replace" or "combine" mix mode, keying a single
1484 * quaternion element should be treated as keying all quaternion elements in an
1485 * all-or-nothing fashion.
1486 */
1487
1488/* With no special keyframing flags *all* quaternion elements should get keyed
1489 * if any of them are. */
1490TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla)
1491{
1492 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1493 object_with_nla->rotmode = ROT_MODE_QUAT;
1494
1495 const CombinedKeyingResult result = insert_keyframes(bmain,
1496 &object_with_nla_rna_pointer,
1497 std::nullopt,
1498 {{"rotation_quaternion", std::nullopt, 0}},
1499 1.0,
1500 anim_eval_context,
1503
1504 EXPECT_EQ(4, result.get_count(SingleKeyingResult::SUCCESS));
1505}
1506
1507/* With the "Only Insert Available" flag enabled, keys for all four elements
1508 * should be inserted if *any* of the elements have fcurves already, and
1509 * otherwise none of the elements should be keyed. */
1510TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla__only_available)
1511{
1512 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1513 object_with_nla->rotmode = ROT_MODE_QUAT;
1514
1515 /* There are no fcurves at all yet, so all elements getting no keys is what
1516 * should happen. */
1517 const CombinedKeyingResult result_1 = insert_keyframes(
1518 bmain,
1519 &object_with_nla_rna_pointer,
1520 std::nullopt,
1521 {{"rotation_quaternion", std::nullopt, 0}},
1522 1.0,
1523 anim_eval_context,
1527
1528 /* Create an fcurve and key for a single quaternion channel. */
1529 PointerRNA id_rna_ptr = RNA_id_pointer_create(&object_with_nla->id);
1531 bmain, nla_action, nullptr, &id_rna_ptr, {"rotation_quaternion", 0});
1533 insert_vert_fcurve(fcu, {1.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
1534
1535 /* Now that there is one fcurve, all elements should get keyed. */
1536 const CombinedKeyingResult result_2 = insert_keyframes(
1537 bmain,
1538 &object_with_nla_rna_pointer,
1539 std::nullopt,
1540 {{"rotation_quaternion", std::nullopt, 0}},
1541 1.0,
1542 anim_eval_context,
1545 EXPECT_EQ(4, result_2.get_count(SingleKeyingResult::SUCCESS));
1546}
1547
1548/* With the "Only Replace" flag enabled, keys for all four elements should be
1549 * inserted if *any* of them replace an existing key, and otherwise none of them
1550 * should. */
1551TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla__only_replace)
1552{
1553 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1554 object_with_nla->rotmode = ROT_MODE_QUAT;
1555
1556 /* First time should insert nothing, since there are no fcurves yet. */
1557 const CombinedKeyingResult result_1 = insert_keyframes(
1558 bmain,
1559 &object_with_nla_rna_pointer,
1560 std::nullopt,
1561 {{"rotation_quaternion", std::nullopt, 0}},
1562 1.0,
1563 anim_eval_context,
1567
1568 /* Directly create an fcurve and key for a single quaternion channel. */
1569 PointerRNA id_rna_ptr = RNA_id_pointer_create(&object_with_nla->id);
1571 bmain, nla_action, nullptr, &id_rna_ptr, {"rotation_quaternion", 0});
1573 insert_vert_fcurve(fcu, {11.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
1574
1575 /* Second time should also insert nothing, since we attempt to insert on a
1576 * frame other than the one with the key. */
1577 const CombinedKeyingResult result_2 = insert_keyframes(
1578 bmain,
1579 &object_with_nla_rna_pointer,
1580 std::nullopt,
1581 {{"rotation_quaternion", std::nullopt, 0}},
1582 5.0,
1583 anim_eval_context,
1586 EXPECT_EQ(0, result_2.get_count(SingleKeyingResult::SUCCESS));
1587
1588 /* Third time should succeed and key all elements, since we're inserting on a
1589 * frame where one of the elements already has a key.
1590 * NOTE: because of NLA time remapping, this 1.0 is the same as the 11.0 we
1591 * used above when inserting directly into the fcurve.*/
1592 const CombinedKeyingResult result_3 = insert_keyframes(
1593 bmain,
1594 &object_with_nla_rna_pointer,
1595 std::nullopt,
1596 {{"rotation_quaternion", std::nullopt, 0}},
1597 1.0,
1598 anim_eval_context,
1602}
1603
1604} // namespace blender::animrig::tests
Functions and classes to work with Actions.
Functions to insert, delete or modify keyframes.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
AnimData * BKE_animdata_ensure_id(ID *id)
Definition anim_data.cc:103
void BKE_pose_ensure(Main *bmain, Object *ob, bArmature *arm, bool do_id_user)
Definition armature.cc:2822
bArmature * BKE_armature_add(Main *bmain, const char *name)
Definition armature.cc:514
FCurve * BKE_fcurve_find(ListBase *list, const char rna_path[], int array_index)
void BKE_idtype_init()
Definition idtype.cc:127
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1482
void id_us_min(ID *id)
Definition lib_id.cc:359
Main * BKE_main_new(void)
Definition main.cc:45
void BKE_main_free(Main *bmain)
Definition main.cc:175
General operations, lookup, etc. for materials.
void BKE_object_material_assign(struct Main *bmain, struct Object *ob, struct Material *ma, short act, int assign_type)
struct Material * BKE_material_add(struct Main *bmain, const char *name)
@ BKE_MAT_ASSIGN_OBDATA
Mesh * BKE_mesh_add(Main *bmain, const char *name)
void BKE_mesh_assign_object(Main *bmain, Object *ob, Mesh *mesh)
NlaTrack * BKE_nlatrack_new_head(ListBase *nla_tracks, bool is_liboverride)
bool BKE_nla_tweakmode_enter(OwnedAnimData owned_adt)
NlaStrip * BKE_nlastack_add_strip(OwnedAnimData owned_adt, bAction *act, bool is_liboverride)
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(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
#define STRNCPY(dst, src)
Definition BLI_string.h:593
void CLG_exit(void)
Definition clog.c:706
void CLG_init(void)
Definition clog.c:699
#define ID_REFCOUNTING_USERS(id)
Definition DNA_ID.h:642
@ ID_AR
@ ID_MA
@ ID_AC
@ ID_ME
@ ID_OB
@ ROT_MODE_QUAT
@ ROT_MODE_XYZ
@ NLASTRIP_FLAG_ACTIVE
@ INSERTKEY_REPLACE
@ INSERTKEY_NEEDED
@ INSERTKEY_AVAILABLE
@ INSERTKEY_NOFLAGS
@ NLASTRIP_MODE_COMBINE
@ NLATRACK_ACTIVE
@ HD_AUTO_ANIM
@ BEZT_IPO_BEZ
@ BEZT_KEYTYPE_KEYFRAME
Object is a sort of wrapper for general info.
@ OB_EMPTY
@ OB_ARMATURE
@ OB_MESH
@ USER_DEVELOPER_UI
unsigned int U
Definition btGjkEpa3.h:78
const Layer * layer(int64_t index) const
const Slot * slot(int64_t index) const
blender::Span< const Layer * > layers() const
blender::Span< const Slot * > slots() const
Slot * slot_for_handle(slot_handle_t handle)
const FCurve * fcurve_find(FCurveDescriptor fcurve_descriptor) const
blender::Span< const FCurve * > fcurves() const
int get_count(const SingleKeyingResult result) const
blender::Span< const Strip * > strips() const
const Strip * strip(int64_t index) const
const ChannelBag * channelbag(int64_t index) const
const ChannelBag * channelbag_for_slot(const Slot &slot) const
blender::Span< const ChannelBag * > channelbags() const
const T & data(const Action &owning_action) const
void *(* MEM_mallocN)(size_t len, const char *str)
Definition mallocn.cc:44
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
FCurve * action_fcurve_ensure(Main *bmain, bAction *act, const char group[], PointerRNA *ptr, FCurveDescriptor fcurve_descriptor)
CombinedKeyingResult insert_keyframes(Main *bmain, PointerRNA *struct_pointer, std::optional< StringRefNull > channel_group, const blender::Span< RNAPath > rna_paths, std::optional< float > scene_frame, const AnimationEvalContext &anim_eval_context, eBezTriple_KeyframeType key_type, eInsertKeyFlags insert_key_flags)
Main key-frame insertion API.
SingleKeyingResult insert_vert_fcurve(FCurve *fcu, const float2 position, const KeyframeSettings &settings, eInsertKeyFlags flag)
Main Key-framing API call.
bool assign_action(bAction *action, ID &animated_id)
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
PointerRNA RNA_pointer_create(ID *id, StructRNA *type, void *data)
PointerRNA RNA_id_pointer_create(ID *id)
bAction * action
ListBase nla_tracks
float vec[3][3]
char name[64]
bActionGroup * grp
BezTriple * bezt
unsigned int totvert
short blendmode
struct AnimData * adt
ListBase curves