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