Blender V5.0
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.hh"
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 "CLG_log.h"
33#include "testing/testing.h"
34
36class KeyframingTest : public testing::Test {
37 public:
39
40 /* For standard single-action testing. */
43
44 /* For pose bone single-action testing. */
48
49 /* For NLA testing. */
53
54 /* For action reuse testing. */
61
62 static void SetUpTestSuite()
63 {
64 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
65 CLG_init();
66
67 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
69 }
70
71 static void TearDownTestSuite()
72 {
73 CLG_exit();
74 }
75
76 void SetUp() override
77 {
79
80 object = BKE_object_add_only_object(bmain, OB_EMPTY, "Empty");
82
83 Bone *bone = MEM_callocN<Bone>("BONE");
84 STRNCPY(bone->name, "Bone");
85
86 armature = BKE_armature_add(bmain, "Armature");
87 BLI_addtail(&armature->bonebase, bone);
88
93
96 nla_action = BKE_id_new<bAction>(bmain, "NLAAction");
97 this->ensure_action_is_legacy(*nla_action);
98
101 cube_mesh = BKE_mesh_add(bmain, "cube_mesh");
103 /* Removing the implicit id user. Using BKE_mesh_assign_object increments the user count which
104 * would leave it at 2 otherwise. */
105 id_us_min(&cube_mesh->id);
107 material = BKE_material_add(bmain, "material");
109
110 id_us_min(&material->id);
112
113 /* Set up an NLA system with a single NLA track with a single offset-in-time
114 * NLA strip, and make that strip active and in tweak mode. */
116 NlaTrack *track = BKE_nlatrack_new_head(&adt->nla_tracks, false);
118 NlaStrip *strip = BKE_nlastack_add_strip({object_with_nla->id, *adt}, false);
119 track->flag |= NLATRACK_ACTIVE;
120 strip->flag |= NLASTRIP_FLAG_ACTIVE;
121 strip->start = -10.0;
122 strip->end = 990.0;
123 strip->actstart = 0.0;
124 strip->actend = 1000.0;
125 strip->scale = 1.0;
128 }
129
130 void TearDown() override
131 {
133 }
134
142 {
143 AnimData *adt = BKE_animdata_from_id(&id);
144 BLI_assert(!adt || !adt->action);
146
147 bAction &action = animrig::action_add(*bmain, "LegacyAction");
148 this->ensure_action_is_legacy(action);
149
150 const bool ok = assign_action(&action, id);
151 BLI_assert(ok);
153 }
154
162 {
163 bActionGroup *new_group = MEM_callocN<bActionGroup>(__func__);
164 STRNCPY_UTF8(new_group->name, "Legacy Forcer");
165 BLI_addtail(&action.groups, new_group);
166 }
167};
168
169/* ------------------------------------------------------------
170 * Tests for `insert_keyframes()` with layered actions.
171 */
172
173/* Keying a non-array property. */
174TEST_F(KeyframingTest, insert_keyframes__layered_action__non_array_property)
175{
176 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
177
178 /* First time should create:
179 * - AnimData
180 * - Action
181 * - Slot
182 * - Layer
183 * - Infinite KeyframeStrip
184 * - FCurve with a single key
185 */
186 object->empty_drawsize = 42.0;
187 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
188 &object_rna_pointer,
189 std::nullopt,
190 {{"empty_display_size"}},
191 1.0,
192 anim_eval_context,
196 ASSERT_NE(nullptr, object->adt);
197 ASSERT_NE(nullptr, object->adt->action);
198 Action &action = object->adt->action->wrap();
199
200 /* The action has a slot, it's named properly, and it's correctly assigned
201 * to the object. */
202 ASSERT_EQ(1, action.slots().size());
203 Slot *slot = action.slot(0);
204 EXPECT_STREQ(object->id.name, slot->identifier);
205 EXPECT_STREQ(object->adt->last_slot_identifier, slot->identifier);
206 EXPECT_EQ(object->adt->slot_handle, slot->handle);
207
208 /* We have the default layer and strip. */
209 ASSERT_TRUE(action.is_action_layered());
210 ASSERT_EQ(1, action.layers().size());
211 ASSERT_EQ(1, action.layer(0)->strips().size());
212 EXPECT_TRUE(strlen(action.layer(0)->name) > 0);
213 Strip *strip = action.layer(0)->strip(0);
214 ASSERT_TRUE(strip->is_infinite());
215 ASSERT_EQ(Strip::Type::Keyframe, strip->type());
216 StripKeyframeData *strip_data = &strip->data<StripKeyframeData>(action);
217
218 /* We have a channel bag for the slot. */
219 Channelbag *channelbag = strip_data->channelbag_for_slot(*slot);
220 ASSERT_NE(nullptr, channelbag);
221
222 /* The fcurves in the channel bag are what we expect. */
223 EXPECT_EQ(1, channelbag->fcurves().size());
224 const FCurve *fcurve = channelbag->fcurve_find({"empty_display_size", 0});
225 ASSERT_NE(nullptr, fcurve);
226 ASSERT_NE(nullptr, fcurve->bezt);
227 EXPECT_EQ(1, fcurve->totvert);
228 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
229 EXPECT_EQ(42.0, fcurve->bezt[0].vec[1][1]);
230
231 /* Second time inserting with a different value on the same frame should
232 * simply replace the key. */
233 object->empty_drawsize = 86.0;
234 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
235 &object_rna_pointer,
236 std::nullopt,
237 {{"empty_display_size"}},
238 1.0,
239 anim_eval_context,
243 EXPECT_EQ(1, fcurve->totvert);
244 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
245 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
246
247 /* Third time inserting on a different time should add a second key. */
248 object->empty_drawsize = 7.0;
249 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
250 &object_rna_pointer,
251 std::nullopt,
252 {{"empty_display_size"}},
253 10.0,
254 anim_eval_context,
258 EXPECT_EQ(2, fcurve->totvert);
259 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
260 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
261 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
262 EXPECT_EQ(7.0, fcurve->bezt[1].vec[1][1]);
263}
264
265TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse)
266{
267 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
268 CombinedKeyingResult result_ob;
269 result_ob = insert_keyframes(bmain,
270 &armature_object_rna_pointer,
271 std::nullopt,
272 {{"location"}},
273 10.0,
274 anim_eval_context,
277
278 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
279 ASSERT_TRUE(armature_object->adt != nullptr);
280 ASSERT_TRUE(armature_object->adt->action != nullptr);
281
282 PointerRNA armature_rna_pointer = RNA_id_pointer_create(&armature->id);
283
284 result_ob = insert_keyframes(bmain,
285 &armature_rna_pointer,
286 std::nullopt,
287 {{"display_type"}},
288 10.0,
289 anim_eval_context,
292 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
293 ASSERT_TRUE(armature->adt != nullptr);
294 ASSERT_TRUE(armature->adt->action != nullptr);
295
296 /* Action is expected to be reused between object and data. */
297 ASSERT_EQ(armature->adt->action, armature_object->adt->action);
298
299 Action &action = armature->adt->action->wrap();
300 /* Should have two slots now. */
301 ASSERT_EQ(action.slot_array_num, 2);
302 for (Slot *slot : action.slots()) {
303 ASSERT_TRUE(slot->idtype == ID_AR || slot->idtype == ID_OB);
304 }
305}
306
307TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse_material)
308{
309 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
310 CombinedKeyingResult result_ob;
311
312 result_ob = insert_keyframes(bmain,
313 &material_rna_pointer,
314 std::nullopt,
315 {{"pass_index"}},
316 1.0,
317 anim_eval_context,
320
321 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
322 ASSERT_TRUE(material->adt != nullptr);
323 ASSERT_TRUE(material->adt->action != nullptr);
324
325 result_ob = insert_keyframes(bmain,
326 &cube_rna_pointer,
327 std::nullopt,
328 {{"location"}},
329 1.0,
330 anim_eval_context,
333
334 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
335 ASSERT_TRUE(cube->adt != nullptr);
336 ASSERT_TRUE(cube->adt->action != nullptr);
337
338 /* Actions are not shared between object and material. */
339 ASSERT_NE(cube->adt->action, material->adt->action);
340
341 result_ob = insert_keyframes(bmain,
342 &cube_mesh_rna_pointer,
343 std::nullopt,
344 {{"remesh_voxel_size"}},
345 1.0,
346 anim_eval_context,
349
350 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
351 ASSERT_TRUE(cube_mesh->adt != nullptr);
352 ASSERT_TRUE(cube_mesh->adt->action != nullptr);
353
354 /* Reuse between Object and object data. */
355 ASSERT_EQ(cube_mesh->adt->action, cube->adt->action);
356 /* Still no reuse from mesh to material. */
357 ASSERT_NE(cube_mesh->adt->action, material->adt->action);
358
359 Action &action = cube->adt->action->wrap();
360 /* Should have two slots now. */
361 ASSERT_EQ(action.slot_array_num, 2);
362
363 /* Material action should have only 1 slot. */
364 ASSERT_EQ(material->adt->action->wrap().slot_array_num, 1);
365
366 for (Slot *slot : action.slots()) {
367 ASSERT_TRUE(slot->idtype == ID_ME || slot->idtype == ID_OB);
368 ASSERT_NE(slot->idtype, ID_MA);
369 }
370}
371
372TEST_F(KeyframingTest, insert_keyframes__layered_action__action_reuse_multiuser)
373{
374 Object *another_object = BKE_object_add_only_object(bmain, OB_MESH, "another_object");
375 PointerRNA another_object_rna_pointer = RNA_id_pointer_create(&another_object->id);
376 BKE_mesh_assign_object(bmain, another_object, cube_mesh);
377
378 ASSERT_EQ(ID_REFCOUNTING_USERS(&cube_mesh->id), 2);
379
380 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
381 CombinedKeyingResult result_ob;
382
383 result_ob = insert_keyframes(bmain,
384 &cube_rna_pointer,
385 std::nullopt,
386 {{"location"}},
387 1.0,
388 anim_eval_context,
391
392 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
393 ASSERT_TRUE(cube->adt != nullptr);
394 ASSERT_TRUE(cube->adt->action != nullptr);
395
396 result_ob = insert_keyframes(bmain,
397 &cube_mesh_rna_pointer,
398 std::nullopt,
399 {{"remesh_voxel_size"}},
400 1.0,
401 anim_eval_context,
404
405 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 1);
406 ASSERT_TRUE(cube_mesh->adt != nullptr);
407 ASSERT_TRUE(cube_mesh->adt->action != nullptr);
408
409 /* When an ID is used more than once, the action should not be reused. */
410 ASSERT_NE(cube->adt->action, cube_mesh->adt->action);
411
412 result_ob = insert_keyframes(bmain,
413 &another_object_rna_pointer,
414 std::nullopt,
415 {{"location"}},
416 1.0,
417 anim_eval_context,
420
421 ASSERT_EQ(result_ob.get_count(SingleKeyingResult::SUCCESS), 3);
422 ASSERT_TRUE(another_object->adt != nullptr);
423 ASSERT_TRUE(another_object->adt->action != nullptr);
424
425 /* Given that those two objects are connected by a mesh (which due to this has two users) the
426 * action shouldn't be reused between them. */
427 ASSERT_NE(cube->adt->action, another_object->adt->action);
428}
429
430/* Keying a single element of an array property. */
431TEST_F(KeyframingTest, insert_keyframes__layered_action__single_element)
432{
433 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
434
436 &object_rna_pointer,
437 std::nullopt,
438 {{"rotation_euler", std::nullopt, 0}},
439 1.0,
440 anim_eval_context,
443
445 ASSERT_NE(nullptr, object->adt);
446 ASSERT_NE(nullptr, object->adt->action);
447 Action &action = object->adt->action->wrap();
448 ASSERT_EQ(1, action.slots().size());
449 ASSERT_EQ(1, action.layers().size());
450 ASSERT_EQ(1, action.layer(0)->strips().size());
451 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
452 ASSERT_EQ(1, strip_data->channelbags().size());
453 Channelbag *channelbag = strip_data->channelbag(0);
454
455 EXPECT_EQ(1, channelbag->fcurves().size());
456 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 0}));
457}
458
459/* Keying all elements of an array property. */
460TEST_F(KeyframingTest, insert_keyframes__layered_action__all_elements)
461{
462 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
463
465 &object_rna_pointer,
466 std::nullopt,
467 {{"rotation_euler"}},
468 1.0,
469 anim_eval_context,
472
474 ASSERT_NE(nullptr, object->adt);
475 ASSERT_NE(nullptr, object->adt->action);
476 Action &action = object->adt->action->wrap();
477 ASSERT_EQ(1, action.slots().size());
478 ASSERT_EQ(1, action.layers().size());
479 ASSERT_EQ(1, action.layer(0)->strips().size());
480 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
481 ASSERT_EQ(1, strip_data->channelbags().size());
482 Channelbag *channelbag = strip_data->channelbag(0);
483
484 EXPECT_EQ(3, channelbag->fcurves().size());
485 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 0}));
486 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 1}));
487 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 2}));
488}
489
490/* Keying a pose bone from its own RNA pointer. */
491TEST_F(KeyframingTest, insert_keyframes__layered_action__pose_bone_rna_pointer)
492{
493 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
494 bPoseChannel *pchan = BKE_pose_channel_find_name(armature_object->pose, "Bone");
495 PointerRNA pose_bone_rna_pointer = RNA_pointer_create_discrete(
496 &armature_object->id, &RNA_PoseBone, pchan);
497
499 &pose_bone_rna_pointer,
500 std::nullopt,
501 {{"rotation_euler", std::nullopt, 0}},
502 1.0,
503 anim_eval_context,
506
508 ASSERT_NE(nullptr, armature_object->adt);
509 ASSERT_NE(nullptr, armature_object->adt->action);
510 Action &action = armature_object->adt->action->wrap();
511 ASSERT_EQ(1, action.slots().size());
512 ASSERT_EQ(1, action.layers().size());
513 ASSERT_EQ(1, action.layer(0)->strips().size());
514 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
515 ASSERT_EQ(1, strip_data->channelbags().size());
516 Channelbag *channelbag = strip_data->channelbag(0);
517
518 EXPECT_EQ(1, channelbag->fcurves().size());
519 EXPECT_NE(nullptr, channelbag->fcurve_find({"pose.bones[\"Bone\"].rotation_euler", 0}));
520}
521
522/* Keying a pose bone from its owning ID's RNA pointer. */
523TEST_F(KeyframingTest, insert_keyframes__pose_bone_owner_id_pointer)
524{
525 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
526
528 bmain,
529 &armature_object_rna_pointer,
530 std::nullopt,
531 {{"pose.bones[\"Bone\"].rotation_euler", std::nullopt, 0}},
532 1.0,
533 anim_eval_context,
536
538 ASSERT_NE(nullptr, armature_object->adt);
539 ASSERT_NE(nullptr, armature_object->adt->action);
540 Action &action = armature_object->adt->action->wrap();
541 ASSERT_EQ(1, action.slots().size());
542 ASSERT_EQ(1, action.layers().size());
543 ASSERT_EQ(1, action.layer(0)->strips().size());
544 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
545 ASSERT_EQ(1, strip_data->channelbags().size());
546 Channelbag *channelbag = strip_data->channelbag(0);
547
548 EXPECT_EQ(1, channelbag->fcurves().size());
549 EXPECT_NE(nullptr, channelbag->fcurve_find({"pose.bones[\"Bone\"].rotation_euler", 0}));
550}
551
552/* Keying multiple elements of multiple properties at once. */
553TEST_F(KeyframingTest, insert_keyframes__layered_action__multiple_properties)
554{
555 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
556
558 &object_rna_pointer,
559 std::nullopt,
560 {
561 {"empty_display_size"},
562 {"location"},
563 {"rotation_euler", std::nullopt, 0},
564 {"rotation_euler", std::nullopt, 2},
565 },
566 1.0,
567 anim_eval_context,
570
572 ASSERT_NE(nullptr, object->adt);
573 ASSERT_NE(nullptr, object->adt->action);
574 Action &action = object->adt->action->wrap();
575 ASSERT_EQ(1, action.slots().size());
576 ASSERT_EQ(1, action.layers().size());
577 ASSERT_EQ(1, action.layer(0)->strips().size());
578 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
579 ASSERT_EQ(1, strip_data->channelbags().size());
580 Channelbag *channelbag = strip_data->channelbag(0);
581
582 EXPECT_EQ(6, channelbag->fcurves().size());
583 EXPECT_NE(nullptr, channelbag->fcurve_find({"empty_display_size", 0}));
584 EXPECT_NE(nullptr, channelbag->fcurve_find({"location", 0}));
585 EXPECT_NE(nullptr, channelbag->fcurve_find({"location", 1}));
586 EXPECT_NE(nullptr, channelbag->fcurve_find({"location", 2}));
587 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 0}));
588 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 2}));
589}
590
591/* Keying more than one ID on the same action. */
592TEST_F(KeyframingTest, insert_keyframes__layered_action__multiple_ids)
593{
594 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
595
596 /* First object should crate the action and get a slot and channel bag. */
597 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
598 &object_rna_pointer,
599 std::nullopt,
600 {{"empty_display_size"}},
601 1.0,
602 anim_eval_context,
606 ASSERT_NE(nullptr, object->adt);
607 ASSERT_NE(nullptr, object->adt->action);
608 Action &action = object->adt->action->wrap();
609
610 /* The action has a slot and it's assigned to the first object. */
611 ASSERT_EQ(1, action.slots().size());
612 Slot *slot_1 = action.slot_for_handle(object->adt->slot_handle);
613 ASSERT_NE(nullptr, slot_1);
614 EXPECT_STREQ(object->id.name, slot_1->identifier);
615 EXPECT_STREQ(object->adt->last_slot_identifier, slot_1->identifier);
616
617 /* Get the keyframe strip. */
618 ASSERT_TRUE(action.is_action_layered());
619 ASSERT_EQ(1, action.layers().size());
620 ASSERT_EQ(1, action.layer(0)->strips().size());
621 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
622
623 /* We have a single channel bag, and it's for the first object's slot. */
624 ASSERT_EQ(1, strip_data->channelbags().size());
625 Channelbag *channelbag_1 = strip_data->channelbag_for_slot(*slot_1);
626 ASSERT_NE(nullptr, channelbag_1);
627
628 /* Assign the action to the second object, with no slot. */
629 ASSERT_TRUE(assign_action(&action, armature_object->id));
630 ASSERT_EQ(assign_action_slot(nullptr, armature_object->id), ActionSlotAssignmentResult::OK);
631
632 /* Keying the second object should go into the same action, creating a new
633 * slot and channel bag. */
634 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
635 &armature_object_rna_pointer,
636 std::nullopt,
637 {{"empty_display_size"}},
638 1.0,
639 anim_eval_context,
643
644 ASSERT_EQ(2, action.slots().size());
645 Slot *slot_2 = action.slot_for_handle(armature_object->adt->slot_handle);
646 ASSERT_NE(nullptr, slot_2);
647 EXPECT_STREQ(armature_object->id.name, slot_2->identifier);
648 EXPECT_STREQ(armature_object->adt->last_slot_identifier, slot_2->identifier);
649
650 ASSERT_EQ(2, strip_data->channelbags().size());
651 Channelbag *channelbag_2 = strip_data->channelbag_for_slot(*slot_2);
652 ASSERT_NE(nullptr, channelbag_2);
653}
654
655/* Keying an object with an already-existing legacy action should do legacy
656 * keying. */
657TEST_F(KeyframingTest, insert_keyframes__baklava_legacy_action)
658{
659 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
660
661 /* Create a legacy Action and assign it the legacy way. */
662 {
663 bAction *action = BKE_id_new<bAction>(bmain, "LegacyAction");
664 action_fcurve_ensure_legacy(bmain, action, nullptr, nullptr, {"testprop", 47});
665 BKE_animdata_ensure_id(&object->id)->action = action;
666 }
667
668 bAction *action = object->adt->action;
669 EXPECT_TRUE(action->wrap().is_action_legacy());
670 EXPECT_FALSE(action->wrap().is_action_layered());
671 EXPECT_EQ(1, BLI_listbase_count(&action->curves));
672
673 /* Insert more keys, which should also get inserted as part of the same legacy
674 * action, not a layered action. */
675 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
676 &object_rna_pointer,
677 std::nullopt,
678 {{"location"}},
679 1.0,
680 anim_eval_context,
684
685 EXPECT_EQ(action, object->adt->action);
686 EXPECT_TRUE(action->wrap().is_action_legacy());
687 EXPECT_FALSE(action->wrap().is_action_layered());
688 EXPECT_EQ(4, BLI_listbase_count(&action->curves));
689}
690
691/* Keying with the "Only Insert Available" flag. */
692TEST_F(KeyframingTest, insert_keyframes__layered_action__only_available)
693{
694 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
695
696 /* First attempt should fail, because there are no fcurves yet. */
697 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
698 &object_rna_pointer,
699 std::nullopt,
700 {{"rotation_euler"}},
701 1.0,
702 anim_eval_context,
705
707
708 /* It's unclear why an AnimData should be created if keying fails
709 * here. It may even be undesirable. This check is just here to ensure no
710 * *unintentional* changes in behavior. */
711 ASSERT_NE(nullptr, object->adt);
712 /* No action is created when using the flag INSERTKEY_AVAILABLE on an
713 * object without an action. */
714 ASSERT_EQ(nullptr, object->adt->action);
715
716 /* Insert a key on two of the elements without using the flag so that there
717 * will be two fcurves. */
718 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
719 &object_rna_pointer,
720 std::nullopt,
721 {
722 {"rotation_euler", std::nullopt, 0},
723 {"rotation_euler", std::nullopt, 2},
724 },
725 1.0,
726 anim_eval_context,
729
730 /* If an action is created, it should be the default action with one
731 * layer and an infinite keyframe strip. */
732 Action &action = object->adt->action->wrap();
733 ASSERT_EQ(1, action.slots().size());
734 ASSERT_EQ(1, action.layers().size());
735 ASSERT_EQ(1, action.layer(0)->strips().size());
736 EXPECT_EQ(object->adt->slot_handle, action.slot(0)->handle);
737 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
738
740 ASSERT_EQ(1, strip_data->channelbags().size());
741 Channelbag *channelbag = strip_data->channelbag(0);
742
743 /* Second attempt should succeed with two keys, because two of the elements
744 * now have fcurves. */
745 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
746 &object_rna_pointer,
747 std::nullopt,
748 {{"rotation_euler"}},
749 1.0,
750 anim_eval_context,
753
755 EXPECT_EQ(2, channelbag->fcurves().size());
756 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 0}));
757 EXPECT_NE(nullptr, channelbag->fcurve_find({"rotation_euler", 2}));
758}
759
760/* Keying with the "Only Replace" flag. */
761TEST_F(KeyframingTest, insert_keyframes__layered_action__only_replace)
762{
763 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
764
765 /* First attempt should fail, because there are no fcurves yet. */
766 object->rot[0] = 42.0;
767 object->rot[1] = 42.0;
768 object->rot[2] = 42.0;
769 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
770 &object_rna_pointer,
771 std::nullopt,
772 {{"rotation_euler"}},
773 1.0,
774 anim_eval_context,
778
779 /* Insert a key for two of the elements so that there will be two fcurves with
780 * one key each. */
781 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
782 &object_rna_pointer,
783 std::nullopt,
784 {
785 {"rotation_euler", std::nullopt, 0},
786 {"rotation_euler", std::nullopt, 2},
787 },
788 1.0,
789 anim_eval_context,
793 ASSERT_NE(nullptr, object->adt);
794 ASSERT_NE(nullptr, object->adt->action);
795 Action &action = object->adt->action->wrap();
796 ASSERT_EQ(1, action.slots().size());
797 ASSERT_EQ(1, action.layers().size());
798 ASSERT_EQ(1, action.layer(0)->strips().size());
799 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
800 ASSERT_EQ(1, strip_data->channelbags().size());
801 Channelbag *channelbag = strip_data->channelbag(0);
802
803 ASSERT_EQ(2, channelbag->fcurves().size());
804 const FCurve *fcurve_x = channelbag->fcurve_find({"rotation_euler", 0});
805 const FCurve *fcurve_z = channelbag->fcurve_find({"rotation_euler", 2});
806 EXPECT_EQ(1, fcurve_x->totvert);
807 EXPECT_EQ(1, fcurve_z->totvert);
808 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
809 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
810 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
811 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
812
813 /* Second attempt should also fail, because we insert on a different frame
814 * than the two keys we just created. */
815 object->rot[0] = 86.0;
816 object->rot[1] = 86.0;
817 object->rot[2] = 86.0;
818 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
819 &object_rna_pointer,
820 std::nullopt,
821 {{"rotation_euler"}},
822 5.0,
823 anim_eval_context,
827 EXPECT_EQ(2, channelbag->fcurves().size());
828 EXPECT_EQ(1, fcurve_x->totvert);
829 EXPECT_EQ(1, fcurve_z->totvert);
830 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
831 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
832 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
833 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
834
835 /* The third attempt, keying on the original frame, should succeed and replace
836 * the existing key on each fcurve. */
837 const CombinedKeyingResult result_4 = insert_keyframes(bmain,
838 &object_rna_pointer,
839 std::nullopt,
840 {{"rotation_euler"}},
841 1.0,
842 anim_eval_context,
846 EXPECT_EQ(2, channelbag->fcurves().size());
847 EXPECT_EQ(1, fcurve_x->totvert);
848 EXPECT_EQ(1, fcurve_z->totvert);
849 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
850 EXPECT_EQ(86.0, fcurve_x->bezt[0].vec[1][1]);
851 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
852 EXPECT_EQ(86.0, fcurve_z->bezt[0].vec[1][1]);
853}
854
855/* Keying with the "Only Insert Needed" flag. */
856TEST_F(KeyframingTest, insert_keyframes__layered_action__only_needed)
857{
858 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
859
860 /* First attempt should succeed, because there are no fcurves yet. */
861 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
862 &object_rna_pointer,
863 std::nullopt,
864 {{"rotation_euler"}},
865 1.0,
866 anim_eval_context,
870 ASSERT_NE(nullptr, object->adt);
871 ASSERT_NE(nullptr, object->adt->action);
872 Action &action = object->adt->action->wrap();
873 ASSERT_EQ(1, action.slots().size());
874 ASSERT_EQ(1, action.layers().size());
875 ASSERT_EQ(1, action.layer(0)->strips().size());
876 StripKeyframeData *strip_data = &action.layer(0)->strip(0)->data<StripKeyframeData>(action);
877 ASSERT_EQ(1, strip_data->channelbags().size());
878 Channelbag *channelbag = strip_data->channelbag(0);
879
880 ASSERT_EQ(3, channelbag->fcurves().size());
881 const FCurve *fcurve_x = channelbag->fcurve_find({"rotation_euler", 0});
882 const FCurve *fcurve_y = channelbag->fcurve_find({"rotation_euler", 1});
883 const FCurve *fcurve_z = channelbag->fcurve_find({"rotation_euler", 2});
884 EXPECT_EQ(1, fcurve_x->totvert);
885 EXPECT_EQ(1, fcurve_y->totvert);
886 EXPECT_EQ(1, fcurve_z->totvert);
887
888 /* Second attempt should fail, because there is now an fcurve for the
889 * property, but its value matches the current property value. */
890 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
891 &object_rna_pointer,
892 std::nullopt,
893 {{"rotation_euler"}},
894 10.0,
895 anim_eval_context,
899 EXPECT_EQ(3, channelbag->fcurves().size());
900 EXPECT_EQ(1, fcurve_x->totvert);
901 EXPECT_EQ(1, fcurve_y->totvert);
902 EXPECT_EQ(1, fcurve_z->totvert);
903
904 /* Third attempt should succeed on two elements, because we change the value
905 * of those elements to differ from the existing fcurves. */
906 object->rot[0] = 123.0;
907 object->rot[2] = 123.0;
908 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
909 &object_rna_pointer,
910 std::nullopt,
911 {{"rotation_euler"}},
912 10.0,
913 anim_eval_context,
916
918 EXPECT_EQ(3, channelbag->fcurves().size());
919 EXPECT_EQ(2, fcurve_x->totvert);
920 EXPECT_EQ(1, fcurve_y->totvert);
921 EXPECT_EQ(2, fcurve_z->totvert);
922}
923
924/* ------------------------------------------------------------
925 * Tests for `insert_keyframes()` with legacy actions.
926 */
927
928/* Keying a non-array property. */
929TEST_F(KeyframingTest, insert_keyframes__legacy_action__non_array_property)
930{
931 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
932
933 /* First time should create the AnimData, Action, and FCurve with a single
934 * key. */
935 object->empty_drawsize = 42.0;
936 ensure_legacy_action_assigned(object->id);
937 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
938 &object_rna_pointer,
939 std::nullopt,
940 {{"empty_display_size"}},
941 1.0,
942 anim_eval_context,
946 ASSERT_NE(nullptr, object->adt);
947 ASSERT_NE(nullptr, object->adt->action);
948 EXPECT_EQ(1, BLI_listbase_count(&object->adt->action->curves));
949 FCurve *fcurve = BKE_fcurve_find(&object->adt->action->curves, "empty_display_size", 0);
950 ASSERT_NE(nullptr, fcurve);
951 ASSERT_NE(nullptr, fcurve->bezt);
952 EXPECT_EQ(1, fcurve->totvert);
953 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
954 EXPECT_EQ(42.0, fcurve->bezt[0].vec[1][1]);
955
956 /* Second time inserting with a different value on the same frame should
957 * simply replace the key. */
958 object->empty_drawsize = 86.0;
959 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
960 &object_rna_pointer,
961 std::nullopt,
962 {{"empty_display_size"}},
963 1.0,
964 anim_eval_context,
968 EXPECT_EQ(1, fcurve->totvert);
969 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
970 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
971
972 /* Third time inserting on a different time should add a second key. */
973 object->empty_drawsize = 7.0;
974 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
975 &object_rna_pointer,
976 std::nullopt,
977 {{"empty_display_size"}},
978 10.0,
979 anim_eval_context,
983 EXPECT_EQ(2, fcurve->totvert);
984 EXPECT_EQ(1.0, fcurve->bezt[0].vec[1][0]);
985 EXPECT_EQ(86.0, fcurve->bezt[0].vec[1][1]);
986 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
987 EXPECT_EQ(7.0, fcurve->bezt[1].vec[1][1]);
988}
989
990/* Passing the frame number explicitly vs not. */
991TEST_F(KeyframingTest, insert_keyframes__legacy_action__optional_frame)
992{
993 /* If the frame number is not explicitly passed, the eval frame from the
994 * animation evaluation context should be used. */
995 AnimationEvalContext anim_eval_context = {nullptr, 5.0};
996
997 object->rotmode = ROT_MODE_XYZ;
998 ensure_legacy_action_assigned(object->id);
999 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1000 &object_rna_pointer,
1001 std::nullopt,
1002 {{"rotation_mode"}},
1003 std::nullopt,
1004 anim_eval_context,
1008 FCurve *fcurve = BKE_fcurve_find(&object->adt->action->curves, "rotation_mode", 0);
1009 EXPECT_EQ(5.0, fcurve->bezt[0].vec[1][0]);
1010 EXPECT_EQ(float(ROT_MODE_XYZ), fcurve->bezt[0].vec[1][1]);
1011
1012 /* If the frame number *is* explicitly passed, it should be used. */
1013 object->rotmode = ROT_MODE_QUAT;
1014 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1015 &object_rna_pointer,
1016 std::nullopt,
1017 {{"rotation_mode"}},
1018 10.0,
1019 anim_eval_context,
1023 EXPECT_EQ(5.0, fcurve->bezt[0].vec[1][0]);
1024 EXPECT_EQ(float(ROT_MODE_XYZ), fcurve->bezt[0].vec[1][1]);
1025 EXPECT_EQ(10.0, fcurve->bezt[1].vec[1][0]);
1026 EXPECT_EQ(float(ROT_MODE_QUAT), fcurve->bezt[1].vec[1][1]);
1027}
1028
1029/* Passing the channel group explicitly vs not. */
1030TEST_F(KeyframingTest, insert_keyframes__legacy_action__optional_channel_group)
1031{
1032 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1033
1034 /* If the channel group is not explicitly passed, the default should be used. */
1035 ensure_legacy_action_assigned(object->id);
1036 const CombinedKeyingResult result_1 = insert_keyframes(
1037 bmain,
1038 &object_rna_pointer,
1039 std::nullopt,
1040 {{"location", std::nullopt, 0}, {"visible_shadow"}},
1041 1.0,
1042 anim_eval_context,
1046
1047 /* Location X should get the default transform group. */
1048 FCurve *fcurve_location_x = BKE_fcurve_find(&object->adt->action->curves, "location", 0);
1049 ASSERT_NE(nullptr, fcurve_location_x->grp);
1050 EXPECT_EQ(0, strcmp("Object Transforms", fcurve_location_x->grp->name));
1051
1052 /* Shadow visibility should get no group. */
1053 FCurve *fcurve_visible_shadow = BKE_fcurve_find(
1054 &object->adt->action->curves, "visible_shadow", 0);
1055 ASSERT_EQ(nullptr, fcurve_visible_shadow->grp);
1056
1057 /* If the channel group *is* explicitly passed, it should override the default. */
1058 const CombinedKeyingResult result_2 = insert_keyframes(
1059 bmain,
1060 &object_rna_pointer,
1061 "Foo",
1062 {{"location", std::nullopt, 1}, {"hide_render"}},
1063 1.0,
1064 anim_eval_context,
1068
1069 /* Both location Y and render visibility should get the "Foo" group. */
1070 FCurve *fcurve_location_y = BKE_fcurve_find(&object->adt->action->curves, "location", 1);
1071 ASSERT_NE(nullptr, fcurve_location_y->grp);
1072 EXPECT_EQ(0, strcmp("Foo", fcurve_location_y->grp->name));
1073 FCurve *fcurve_hide_render = BKE_fcurve_find(&object->adt->action->curves, "hide_render", 0);
1074 ASSERT_NE(nullptr, fcurve_hide_render->grp);
1075 EXPECT_EQ(0, strcmp("Foo", fcurve_hide_render->grp->name));
1076}
1077
1078/* Keying a single element of an array property. */
1079TEST_F(KeyframingTest, insert_keyframes__single_element)
1080{
1081 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1082
1083 ensure_legacy_action_assigned(object->id);
1085 &object_rna_pointer,
1086 std::nullopt,
1087 {{"rotation_euler", std::nullopt, 0}},
1088 1.0,
1089 anim_eval_context,
1092
1094 ASSERT_NE(nullptr, object->adt);
1095 ASSERT_NE(nullptr, object->adt->action);
1096 EXPECT_EQ(1, BLI_listbase_count(&object->adt->action->curves));
1097 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1098}
1099
1100/* Keying all elements of an array property. */
1101TEST_F(KeyframingTest, insert_keyframes__legacy_action__all_elements)
1102{
1103 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1104
1105 ensure_legacy_action_assigned(object->id);
1107 &object_rna_pointer,
1108 std::nullopt,
1109 {{"rotation_euler"}},
1110 1.0,
1111 anim_eval_context,
1114
1116 ASSERT_NE(nullptr, object->adt);
1117 ASSERT_NE(nullptr, object->adt->action);
1118 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1119 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1120 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 1));
1121 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1122}
1123
1124/* Keying a pose bone from its own RNA pointer. */
1125TEST_F(KeyframingTest, insert_keyframes__legacy_action__pose_bone_rna_pointer)
1126{
1127 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1128 bPoseChannel *pchan = BKE_pose_channel_find_name(armature_object->pose, "Bone");
1129 PointerRNA pose_bone_rna_pointer = RNA_pointer_create_discrete(
1130 &armature_object->id, &RNA_PoseBone, pchan);
1131
1132 ensure_legacy_action_assigned(armature_object->id);
1134 &pose_bone_rna_pointer,
1135 std::nullopt,
1136 {{"rotation_euler", std::nullopt, 0}},
1137 1.0,
1138 anim_eval_context,
1141
1143 ASSERT_NE(nullptr, armature_object->adt);
1144 ASSERT_NE(nullptr, armature_object->adt->action);
1145 EXPECT_EQ(1, BLI_listbase_count(&armature_object->adt->action->curves));
1146 EXPECT_NE(nullptr,
1148 &armature_object->adt->action->curves, "pose.bones[\"Bone\"].rotation_euler", 0));
1149}
1150
1151/* Keying a pose bone from its owning ID's RNA pointer. */
1152TEST_F(KeyframingTest, insert_keyframes__legacy_action__pose_bone_owner_id_pointer)
1153{
1154 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1155
1156 ensure_legacy_action_assigned(armature_object->id);
1158 bmain,
1159 &armature_object_rna_pointer,
1160 std::nullopt,
1161 {{"pose.bones[\"Bone\"].rotation_euler", std::nullopt, 0}},
1162 1.0,
1163 anim_eval_context,
1166
1168 ASSERT_NE(nullptr, armature_object->adt);
1169 ASSERT_NE(nullptr, armature_object->adt->action);
1170 EXPECT_EQ(1, BLI_listbase_count(&armature_object->adt->action->curves));
1171 EXPECT_NE(nullptr,
1173 &armature_object->adt->action->curves, "pose.bones[\"Bone\"].rotation_euler", 0));
1174}
1175
1176/* Keying multiple elements of multiple properties at once. */
1177TEST_F(KeyframingTest, insert_keyframes__legacy_action__multiple_properties)
1178{
1179 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1180
1181 ensure_legacy_action_assigned(object->id);
1183
1184 insert_keyframes(bmain,
1185 &object_rna_pointer,
1186 std::nullopt,
1187 {
1188 {"empty_display_size"},
1189 {"location"},
1190 {"rotation_euler", std::nullopt, 0},
1191 {"rotation_euler", std::nullopt, 2},
1192 },
1193 1.0,
1194 anim_eval_context,
1197
1199 ASSERT_NE(nullptr, object->adt);
1200 ASSERT_NE(nullptr, object->adt->action);
1201 EXPECT_EQ(6, BLI_listbase_count(&object->adt->action->curves));
1202 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "empty_display_size", 0));
1203 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 0));
1204 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 1));
1205 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "location", 2));
1206 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1207 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1208}
1209
1210/* Keying with the "Only Insert Available" flag. */
1211TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_available)
1212{
1213 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1214
1215 /* First attempt should fail, because there are no fcurves yet. */
1216 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1217 &object_rna_pointer,
1218 std::nullopt,
1219 {{"rotation_euler"}},
1220 1.0,
1221 anim_eval_context,
1224
1226
1227 /* It's unclear why an AnimData should be created if keying fails
1228 * here. It may even be undesirable. This check is just here to ensure no
1229 * *unintentional* changes in behavior. */
1230 ASSERT_NE(nullptr, object->adt);
1231 /* No action is created when using the flag INSERTKEY_AVAILABLE on an
1232 * object without an action. */
1233 ASSERT_EQ(nullptr, object->adt->action);
1234
1235 /* This will create & assign an Action, which is necessary for the
1236 * insert_keyframes() function to take the 'legacy Action' code path. */
1237 ensure_legacy_action_assigned(object->id);
1238
1239 /* Insert a key on two of the elements without using the flag so that there
1240 * will be two fcurves. */
1241 insert_keyframes(bmain,
1242 &object_rna_pointer,
1243 std::nullopt,
1244 {
1245 {"rotation_euler", std::nullopt, 0},
1246 {"rotation_euler", std::nullopt, 2},
1247 },
1248 1.0,
1249 anim_eval_context,
1252
1253 /* Second attempt should succeed with two keys, because two of the elements
1254 * now have fcurves. */
1255 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1256 &object_rna_pointer,
1257 std::nullopt,
1258 {{"rotation_euler"}},
1259 1.0,
1260 anim_eval_context,
1263
1264 EXPECT_EQ(2, result_2.get_count(SingleKeyingResult::SUCCESS));
1265 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1266 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0));
1267 EXPECT_NE(nullptr, BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2));
1268}
1269
1270/* Keying with the "Only Replace" flag. */
1271TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_replace)
1272{
1273 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1274
1275 /* First attempt should fail, because there are no fcurves yet. */
1276 object->rot[0] = 42.0;
1277 object->rot[1] = 42.0;
1278 object->rot[2] = 42.0;
1279 ensure_legacy_action_assigned(object->id);
1280 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1281 &object_rna_pointer,
1282 std::nullopt,
1283 {{"rotation_euler"}},
1284 1.0,
1285 anim_eval_context,
1289
1290 /* Insert a key for two of the elements so that there will be two fcurves with
1291 * one key each. */
1292 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1293 &object_rna_pointer,
1294 std::nullopt,
1295 {
1296 {"rotation_euler", std::nullopt, 0},
1297 {"rotation_euler", std::nullopt, 2},
1298 },
1299 1.0,
1300 anim_eval_context,
1304 ASSERT_NE(nullptr, object->adt);
1305 ASSERT_NE(nullptr, object->adt->action);
1306 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1307 FCurve *fcurve_x = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0);
1308 FCurve *fcurve_z = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2);
1309 ASSERT_NE(nullptr, fcurve_x);
1310 ASSERT_NE(nullptr, fcurve_z);
1311 ASSERT_NE(nullptr, fcurve_x->bezt);
1312 ASSERT_NE(nullptr, fcurve_z->bezt);
1313 EXPECT_EQ(1, fcurve_x->totvert);
1314 EXPECT_EQ(1, fcurve_z->totvert);
1315 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1316 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
1317 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1318 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
1319
1320 /* Second attempt should also fail, because we insert on a different frame
1321 * than the two keys we just created. */
1322 object->rot[0] = 86.0;
1323 object->rot[1] = 86.0;
1324 object->rot[2] = 86.0;
1325 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
1326 &object_rna_pointer,
1327 std::nullopt,
1328 {{"rotation_euler"}},
1329 5.0,
1330 anim_eval_context,
1334 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1335 EXPECT_EQ(1, fcurve_x->totvert);
1336 EXPECT_EQ(1, fcurve_z->totvert);
1337 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1338 EXPECT_EQ(42.0, fcurve_x->bezt[0].vec[1][1]);
1339 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1340 EXPECT_EQ(42.0, fcurve_z->bezt[0].vec[1][1]);
1341
1342 /* The third attempt, keying on the original frame, should succeed and replace
1343 * the existing key on each fcurve. */
1344 const CombinedKeyingResult result_4 = insert_keyframes(bmain,
1345 &object_rna_pointer,
1346 std::nullopt,
1347 {{"rotation_euler"}},
1348 1.0,
1349 anim_eval_context,
1353 EXPECT_EQ(2, BLI_listbase_count(&object->adt->action->curves));
1354 EXPECT_EQ(1, fcurve_x->totvert);
1355 EXPECT_EQ(1, fcurve_z->totvert);
1356 EXPECT_EQ(1.0, fcurve_x->bezt[0].vec[1][0]);
1357 EXPECT_EQ(86.0, fcurve_x->bezt[0].vec[1][1]);
1358 EXPECT_EQ(1.0, fcurve_z->bezt[0].vec[1][0]);
1359 EXPECT_EQ(86.0, fcurve_z->bezt[0].vec[1][1]);
1360}
1361
1362/* Keying with the "Only Insert Needed" flag. */
1363TEST_F(KeyframingTest, insert_keyframes__legacy_action__only_needed)
1364{
1365 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1366
1367 /* First attempt should succeed, because there are no fcurves yet. */
1368 ensure_legacy_action_assigned(object->id);
1369 const CombinedKeyingResult result_1 = insert_keyframes(bmain,
1370 &object_rna_pointer,
1371 std::nullopt,
1372 {{"rotation_euler"}},
1373 1.0,
1374 anim_eval_context,
1377
1379 ASSERT_NE(nullptr, object->adt);
1380 ASSERT_NE(nullptr, object->adt->action);
1381 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1382 FCurve *fcurve_x = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 0);
1383 FCurve *fcurve_y = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 1);
1384 FCurve *fcurve_z = BKE_fcurve_find(&object->adt->action->curves, "rotation_euler", 2);
1385 ASSERT_NE(nullptr, fcurve_x);
1386 ASSERT_NE(nullptr, fcurve_y);
1387 ASSERT_NE(nullptr, fcurve_z);
1388
1389 /* Second attempt should fail, because there is now an fcurve for the
1390 * property, but its value matches the current property value. */
1391 anim_eval_context.eval_time = 10.0;
1392 const CombinedKeyingResult result_2 = insert_keyframes(bmain,
1393 &object_rna_pointer,
1394 std::nullopt,
1395 {{"rotation_euler"}},
1396 10.0,
1397 anim_eval_context,
1400
1402 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1403 EXPECT_EQ(1, fcurve_x->totvert);
1404 EXPECT_EQ(1, fcurve_y->totvert);
1405 EXPECT_EQ(1, fcurve_z->totvert);
1406
1407 /* Third attempt should succeed on two elements, because we change the value
1408 * of those elements to differ from the existing fcurves. */
1409 object->rot[0] = 123.0;
1410 object->rot[2] = 123.0;
1411 const CombinedKeyingResult result_3 = insert_keyframes(bmain,
1412 &object_rna_pointer,
1413 std::nullopt,
1414 {{"rotation_euler"}},
1415 10.0,
1416 anim_eval_context,
1419
1421 EXPECT_EQ(3, BLI_listbase_count(&object->adt->action->curves));
1422 EXPECT_EQ(2, fcurve_x->totvert);
1423 EXPECT_EQ(1, fcurve_y->totvert);
1424 EXPECT_EQ(2, fcurve_z->totvert);
1425}
1426
1427/* Inserting a key into an NLA strip that has a time offset should remap the
1428 * key's time to the local time of the strip. */
1429TEST_F(KeyframingTest, insert_keyframes__nla_time_remapping)
1430{
1431 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1432
1434 &object_with_nla_rna_pointer,
1435 std::nullopt,
1436 {{"location", std::nullopt, 0}},
1437 1.0,
1438 anim_eval_context,
1441
1443 EXPECT_EQ(1, BLI_listbase_count(&nla_action->curves));
1444 FCurve *fcurve = BKE_fcurve_find(&nla_action->curves, "location", 0);
1445 ASSERT_NE(nullptr, fcurve);
1446 ASSERT_NE(nullptr, fcurve->bezt);
1447 EXPECT_EQ(1, fcurve->totvert);
1448 EXPECT_EQ(11.0, fcurve->bezt[0].vec[1][0]);
1449}
1450
1451/* ------------------------------------------------------------
1452 * Testing a special case of the NLA system:
1453 * When keying a strip with the "replace" or "combine" mix mode, keying a single
1454 * quaternion element should be treated as keying all quaternion elements in an
1455 * all-or-nothing fashion.
1456 */
1457
1458/* With no special keyframing flags *all* quaternion elements should get keyed
1459 * if any of them are. */
1460TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla)
1461{
1462 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1463 object_with_nla->rotmode = ROT_MODE_QUAT;
1464
1466 &object_with_nla_rna_pointer,
1467 std::nullopt,
1468 {{"rotation_quaternion", std::nullopt, 0}},
1469 1.0,
1470 anim_eval_context,
1473
1475}
1476
1477/* With the "Only Insert Available" flag enabled, keys for all four elements
1478 * should be inserted if *any* of the elements have fcurves already, and
1479 * otherwise none of the elements should be keyed. */
1480TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla__only_available)
1481{
1482 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1483 object_with_nla->rotmode = ROT_MODE_QUAT;
1484
1485 /* There are no fcurves at all yet, so all elements getting no keys is what
1486 * should happen. */
1487 const CombinedKeyingResult result_1 = insert_keyframes(
1488 bmain,
1489 &object_with_nla_rna_pointer,
1490 std::nullopt,
1491 {{"rotation_quaternion", std::nullopt, 0}},
1492 1.0,
1493 anim_eval_context,
1497
1498 /* Create an fcurve and key for a single quaternion channel. */
1499 PointerRNA id_rna_ptr = RNA_id_pointer_create(&object_with_nla->id);
1501 bmain, nla_action, nullptr, &id_rna_ptr, {"rotation_quaternion", 0});
1503 insert_vert_fcurve(fcu, {1.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
1504
1505 /* Now that there is one fcurve, all elements should get keyed. */
1506 const CombinedKeyingResult result_2 = insert_keyframes(
1507 bmain,
1508 &object_with_nla_rna_pointer,
1509 std::nullopt,
1510 {{"rotation_quaternion", std::nullopt, 0}},
1511 1.0,
1512 anim_eval_context,
1515 EXPECT_EQ(4, result_2.get_count(SingleKeyingResult::SUCCESS));
1516}
1517
1518/* With the "Only Replace" flag enabled, keys for all four elements should be
1519 * inserted if *any* of them replace an existing key, and otherwise none of them
1520 * should. */
1521TEST_F(KeyframingTest, insert_keyframes__legacy_action__quaternion_on_nla__only_replace)
1522{
1523 AnimationEvalContext anim_eval_context = {nullptr, 1.0};
1524 object_with_nla->rotmode = ROT_MODE_QUAT;
1525
1526 /* First time should insert nothing, since there are no fcurves yet. */
1527 const CombinedKeyingResult result_1 = insert_keyframes(
1528 bmain,
1529 &object_with_nla_rna_pointer,
1530 std::nullopt,
1531 {{"rotation_quaternion", std::nullopt, 0}},
1532 1.0,
1533 anim_eval_context,
1537
1538 /* Directly create an fcurve and key for a single quaternion channel. */
1539 PointerRNA id_rna_ptr = RNA_id_pointer_create(&object_with_nla->id);
1541 bmain, nla_action, nullptr, &id_rna_ptr, {"rotation_quaternion", 0});
1543 insert_vert_fcurve(fcu, {11.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
1544
1545 /* Second time should also insert nothing, since we attempt to insert on a
1546 * frame other than the one with the key. */
1547 const CombinedKeyingResult result_2 = insert_keyframes(
1548 bmain,
1549 &object_with_nla_rna_pointer,
1550 std::nullopt,
1551 {{"rotation_quaternion", std::nullopt, 0}},
1552 5.0,
1553 anim_eval_context,
1556 EXPECT_EQ(0, result_2.get_count(SingleKeyingResult::SUCCESS));
1557
1558 /* Third time should succeed and key all elements, since we're inserting on a
1559 * frame where one of the elements already has a key.
1560 * NOTE: because of NLA time remapping, this 1.0 is the same as the 11.0 we
1561 * used above when inserting directly into the fcurve. */
1562 const CombinedKeyingResult result_3 = insert_keyframes(
1563 bmain,
1564 &object_with_nla_rna_pointer,
1565 std::nullopt,
1566 {{"rotation_quaternion", std::nullopt, 0}},
1567 1.0,
1568 anim_eval_context,
1572}
1573
1574} // 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:97
AnimData * BKE_animdata_from_id(const ID *id)
Definition anim_data.cc:83
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_find(ListBase *list, const char rna_path[], int array_index)
void BKE_idtype_init()
Definition idtype.cc:121
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1514
void id_us_min(ID *id)
Definition lib_id.cc:366
Main * BKE_main_new()
Definition main.cc:89
void BKE_main_free(Main *bmain)
Definition main.cc:192
General operations, lookup, etc. for materials.
@ BKE_MAT_ASSIGN_OBDATA
Material * BKE_material_add(Main *bmain, const char *name)
void BKE_object_material_assign(Main *bmain, Object *ob, Material *ma, short act, int assign_type)
Mesh * BKE_mesh_add(Main *bmain, const char *name)
void BKE_mesh_assign_object(Main *bmain, Object *ob, Mesh *mesh)
NlaStrip * BKE_nlastack_add_strip(OwnedAnimData owned_adt, const bool is_liboverride)
NlaTrack * BKE_nlatrack_new_head(ListBase *nla_tracks, bool is_liboverride)
bool BKE_nla_tweakmode_enter(OwnedAnimData owned_adt)
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
#define BLI_assert(a)
Definition BLI_assert.h:46
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
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define STRNCPY_UTF8(dst, src)
#define UNUSED_VARS_NDEBUG(...)
void CLG_exit()
Definition clog.cc:880
void CLG_init()
Definition clog.cc:873
#define ID_REFCOUNTING_USERS(id)
Definition DNA_ID.h:681
@ ID_AR
@ ID_MA
@ 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
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(const 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_for_slot(const Slot &slot) const
blender::Span< const Channelbag * > channelbags() const
const Channelbag * channelbag(int64_t index) const
const T & data(const Action &owning_action) const
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
Action & action_add(Main &bmain, StringRefNull name)
FCurve * action_fcurve_ensure_legacy(Main *bmain, bAction *act, const char group[], PointerRNA *ptr, const 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_discrete(ID *id, StructRNA *type, void *data)
PointerRNA RNA_id_pointer_create(ID *id)
char identifier[258]
bAction * action
ListBase nla_tracks
float vec[3][3]
char name[64]
bActionGroup * grp
BezTriple * bezt
unsigned int totvert
Definition DNA_ID.h:414
short blendmode
struct AnimData * adt
ListBase curves
ListBase groups