Blender V4.3
animrig/intern/action.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10#include "DNA_action_types.h"
11#include "DNA_anim_types.h"
12#include "DNA_array_utils.hh"
13#include "DNA_defaults.h"
14#include "DNA_scene_types.h"
15
16#include "BLI_listbase.h"
18#include "BLI_map.hh"
19#include "BLI_math_base.h"
20#include "BLI_string.h"
21#include "BLI_string_utf8.h"
22#include "BLI_string_utils.hh"
23#include "BLI_utildefines.h"
24
25#include "BKE_action.hh"
26#include "BKE_anim_data.hh"
27#include "BKE_fcurve.hh"
28#include "BKE_lib_id.hh"
29#include "BKE_main.hh"
30#include "BKE_nla.hh"
31#include "BKE_preview_image.hh"
32#include "BKE_report.hh"
33
34#include "RNA_access.hh"
35#include "RNA_path.hh"
36#include "RNA_prototypes.hh"
37
38#include "ED_keyframing.hh"
39
40#include "MEM_guardedalloc.h"
41
42#include "BLT_translation.hh"
43
45
46#include "ANIM_action.hh"
48#include "ANIM_action_legacy.hh"
49#include "ANIM_animdata.hh"
50#include "ANIM_fcurve.hh"
51#include "ANIM_nla.hh"
52
53#include "action_runtime.hh"
54
55#include "atomic_ops.h"
56
57#include <cstdio>
58#include <cstring>
59
60namespace blender::animrig {
61
62namespace {
70constexpr const char *slot_default_name = "Slot";
71constexpr const char *slot_unbound_prefix = "XX";
72
73constexpr const char *layer_default_name = "Layer";
74
75} // namespace
76
78{
80 return layer->wrap();
81}
82
83/* Copied from source/blender/blenkernel/intern/grease_pencil.cc.
84 * Keep an eye on DNA_array_utils.hh; we may want to move these functions in there. */
85template<typename T> static void grow_array(T **array, int *num, const int add_num)
86{
87 BLI_assert(add_num > 0);
88 const int new_array_num = *num + add_num;
89 T *new_array = MEM_cnew_array<T>(new_array_num, "animrig::action/grow_array");
90
93
94 *array = new_array;
95 *num = new_array_num;
96}
97
98template<typename T> static void grow_array_and_append(T **array, int *num, T item)
99{
100 grow_array(array, num, 1);
101 (*array)[*num - 1] = item;
102}
103
104template<typename T>
105static void grow_array_and_insert(T **array, int *num, const int index, T item)
106{
107 BLI_assert(index >= 0 && index <= *num);
108 const int new_array_num = *num + 1;
109 T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
110
111 blender::uninitialized_relocate_n(*array, index, new_array);
112 new_array[index] = item;
113 blender::uninitialized_relocate_n(*array + index, *num - index, new_array + index + 1);
114
116
117 *array = new_array;
118 *num = new_array_num;
119}
120
121template<typename T> static void shrink_array(T **array, int *num, const int shrink_num)
122{
123 BLI_assert(shrink_num > 0);
124 const int new_array_num = *num - shrink_num;
125 T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
126
127 blender::uninitialized_move_n(*array, new_array_num, new_array);
129
130 *array = new_array;
131 *num = new_array_num;
132}
133
134template<typename T> static void shrink_array_and_remove(T **array, int *num, const int index)
135{
136 BLI_assert(index >= 0 && index < *num);
137 const int new_array_num = *num - 1;
138 T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
139
140 blender::uninitialized_move_n(*array, index, new_array);
141 blender::uninitialized_move_n(*array + index + 1, *num - index - 1, new_array + index);
143
144 *array = new_array;
145 *num = new_array_num;
146}
147
153template<typename T> static void shrink_array_and_swap_remove(T **array, int *num, const int index)
154{
155 BLI_assert(index >= 0 && index < *num);
156 const int new_array_num = *num - 1;
157 T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
158
159 blender::uninitialized_move_n(*array, index, new_array);
160 if (index < new_array_num) {
161 new_array[index] = (*array)[new_array_num];
162 blender::uninitialized_move_n(*array + index + 1, *num - index - 2, new_array + index + 1);
163 }
165
166 *array = new_array;
167 *num = new_array_num;
168}
169
179template<typename T>
181 T *array, const int num, const int range_start, const int range_end, const int to)
182{
183 BLI_assert(range_start <= range_end);
184 BLI_assert(range_end <= num);
185 BLI_assert(to <= num + range_start - range_end);
187
188 if (ELEM(range_start, range_end, to)) {
189 return;
190 }
191
192 if (to < range_start) {
193 T *start = array + to;
194 T *mid = array + range_start;
195 T *end = array + range_end;
196 std::rotate(start, mid, end);
197 }
198 else {
199 T *start = array + range_start;
200 T *mid = array + range_end;
201 T *end = array + to + range_end - range_start;
202 std::rotate(start, mid, end);
203 }
204}
205
206/* ----- Action implementation ----------- */
207
209{
210 /* The check for emptiness has to include the check for an empty `groups` ListBase because of the
211 * animation filtering code. With the functions `rearrange_action_channels` and
212 * `join_groups_action_temp` the ownership of FCurves is temporarily transferred to the `groups`
213 * ListBase leaving `curves` potentially empty. */
214 return this->layer_array_num == 0 && this->slot_array_num == 0 &&
215 BLI_listbase_is_empty(&this->curves) && BLI_listbase_is_empty(&this->groups);
216}
218{
219 /* This is a valid legacy Action only if there is no layered info. */
220 return this->layer_array_num == 0 && this->slot_array_num == 0;
221}
223{
224 /* This is a valid layered Action if there is ANY layered info (because that
225 * takes precedence) or when there is no legacy info. */
226 return this->layer_array_num > 0 || this->slot_array_num > 0 ||
227 (BLI_listbase_is_empty(&this->curves) && BLI_listbase_is_empty(&this->groups));
228}
229
231{
232 return blender::Span<const Layer *>{reinterpret_cast<Layer **>(this->layer_array),
233 this->layer_array_num};
234}
236{
237 return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
238 this->layer_array_num};
239}
240const Layer *Action::layer(const int64_t index) const
241{
242 return &this->layer_array[index]->wrap();
243}
245{
246 return &this->layer_array[index]->wrap();
247}
248
249Layer &Action::layer_add(const std::optional<StringRefNull> name)
250{
251 Layer &new_layer = ActionLayer_alloc();
252 if (name.has_value()) {
253 STRNCPY_UTF8(new_layer.name, name.value().c_str());
254 }
255 else {
256 STRNCPY_UTF8(new_layer.name, layer_default_name);
257 }
258
260 this->layer_active_index = this->layer_array_num - 1;
261
262 /* If this is the first layer in this Action, it means that it could have been
263 * used as a legacy Action before. As a result, this->idroot may be non-zero
264 * while it should be zero for layered Actions.
265 *
266 * And since setting this to 0 when it is already supposed to be 0 is fine,
267 * there is no check for whether this is actually the first layer. */
268 this->idroot = 0;
269
270 return new_layer;
271}
272
273static void layer_ptr_destructor(ActionLayer **dna_layer_ptr)
274{
275 Layer &layer = (*dna_layer_ptr)->wrap();
276 MEM_delete(&layer);
277};
278
279bool Action::layer_remove(Layer &layer_to_remove)
280{
281 const int64_t layer_index = this->find_layer_index(layer_to_remove);
282 if (layer_index < 0) {
283 return false;
284 }
285
287 &this->layer_array_num,
288 &this->layer_active_index,
289 layer_index,
291 return true;
292}
293
295{
296 /* Ensure a layer. */
297 Layer *layer;
298 if (this->layers().is_empty()) {
299 layer = &this->layer_add(DATA_(layer_default_name));
300 }
301 else {
302 layer = this->layer(0);
303 }
304
305 /* Ensure a keyframe Strip. */
306 if (layer->strips().is_empty()) {
307 layer->strip_add(*this, Strip::Type::Keyframe);
308 }
309
310 /* Within the limits of Baklava Phase 1, the above code should not have
311 * created more than one layer, or more than one strip on the layer. And if a
312 * layer + strip already existed, that must have been a keyframe strip. */
314}
315
317{
318 for (const int64_t layer_index : this->layers().index_range()) {
319 const Layer *visit_layer = this->layer(layer_index);
320 if (visit_layer == &layer) {
321 return layer_index;
322 }
323 }
324 return -1;
325}
326
328{
329 for (const int64_t slot_index : this->slots().index_range()) {
330 const Slot *visit_slot = this->slot(slot_index);
331 if (visit_slot == &slot) {
332 return slot_index;
333 }
334 }
335 return -1;
336}
337
339{
340 return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
341}
343{
344 return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
345}
346const Slot *Action::slot(const int64_t index) const
347{
348 return &this->slot_array[index]->wrap();
349}
351{
352 return &this->slot_array[index]->wrap();
353}
354
356{
357 const Slot *slot = const_cast<const Action *>(this)->slot_for_handle(handle);
358 return const_cast<Slot *>(slot);
359}
360
362{
363 if (handle == Slot::unassigned) {
364 return nullptr;
365 }
366
367 /* TODO: implement hash-map lookup. */
368 for (const Slot *slot : slots()) {
369 if (slot->handle == handle) {
370 return slot;
371 }
372 }
373 return nullptr;
374}
375
376static void slot_name_ensure_unique(Action &action, Slot &slot)
377{
378 /* Cannot capture parameters by reference in the lambda, as that would change its signature
379 * and no longer be compatible with BLI_uniquename_cb(). That's why this struct is necessary. */
380 struct DupNameCheckData {
381 Action &action;
382 Slot &slot;
383 };
384 DupNameCheckData check_data = {action, slot};
385
386 auto check_name_is_used = [](void *arg, const char *name) -> bool {
387 DupNameCheckData *data = static_cast<DupNameCheckData *>(arg);
388 for (const Slot *slot : data->action.slots()) {
389 if (slot == &data->slot) {
390 /* Don't compare against the slot that's being renamed. */
391 continue;
392 }
393 if (STREQ(slot->name, name)) {
394 return true;
395 }
396 }
397 return false;
398 };
399
400 BLI_uniquename_cb(check_name_is_used, &check_data, "", '.', slot.name, sizeof(slot.name));
401}
402
403void Action::slot_name_set(Main &bmain, Slot &slot, const StringRefNull new_name)
404{
405 /* TODO: maybe this function should only set the 'name without prefix' aka the 'display name'.
406 * That way only `this->id_type` is responsible for the prefix. I (Sybren) think that's easier to
407 * determine when the code is a bit more mature, and we can see what the majority of the calls to
408 * this function actually do/need. */
409
410 this->slot_name_define(slot, new_name);
411 this->slot_name_propagate(bmain, slot);
412}
413
414void Action::slot_name_define(Slot &slot, const StringRefNull new_name)
415{
417 "Action Slots must be large enough for a 2-letter ID code + the display name");
418 STRNCPY_UTF8(slot.name, new_name.c_str());
420}
421
422void Action::slot_name_propagate(Main &bmain, const Slot &slot)
423{
424 /* Just loop over all animatable IDs in the main database. */
425 ListBase *lb;
426 ID *id;
427 FOREACH_MAIN_LISTBASE_BEGIN (&bmain, lb) {
429 if (!id_can_have_animdata(id)) {
430 /* This ID type cannot have any animation, so ignore all and continue to
431 * the next ID type. */
432 break;
433 }
434
436 if (!adt || adt->action != this) {
437 /* Not animated by this Action. */
438 continue;
439 }
440 if (adt->slot_handle != slot.handle) {
441 /* Not animated by this Slot. */
442 continue;
443 }
444
445 /* Ensure the Slot name on the AnimData is correct. */
447 }
449 }
451}
452
454{
455 for (Slot *slot : slots()) {
456 if (STREQ(slot->name, slot_name.c_str())) {
457 return slot;
458 }
459 }
460 return nullptr;
461}
462
463Slot &Action::slot_allocate()
464{
465 Slot &slot = *MEM_new<Slot>(__func__);
466 this->last_slot_handle++;
467 BLI_assert_msg(this->last_slot_handle > 0, "Action Slot handle overflow");
468 slot.handle = this->last_slot_handle;
469
470 /* Set the default flags. These cannot be set via the 'DNA defaults' system,
471 * as that would require knowing which bit corresponds with which flag. That's
472 * only known to the C++ wrapper code. */
473 slot.set_expanded(true);
474 return slot;
475}
476
478{
479 Slot &slot = this->slot_allocate();
480
481 /* Assign the default name and the 'unbound' name prefix. */
482 STRNCPY_UTF8(slot.name, slot_unbound_prefix);
483 BLI_strncpy_utf8(slot.name + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.name) - 2);
484
485 /* Append the Slot to the Action. */
487
489
490 /* If this is the first slot in this Action, it means that it could have
491 * been used as a legacy Action before. As a result, this->idroot may be
492 * non-zero while it should be zero for layered Actions.
493 *
494 * And since setting this to 0 when it is already supposed to be 0 is fine,
495 * there is no check for whether this is actually the first layer. */
496 this->idroot = 0;
497
498 return slot;
499}
500
501Slot &Action::slot_add_for_id(const ID &animated_id)
502{
503 Slot &slot = this->slot_add();
504
505 slot.idtype = GS(animated_id.name);
506 this->slot_name_define(slot, animated_id.name);
507
508 /* No need to call anim.slot_name_propagate() as nothing will be using
509 * this brand new Slot yet. */
510
511 return slot;
512}
513
514static void slot_ptr_destructor(ActionSlot **dna_slot_ptr)
515{
516 Slot &slot = (*dna_slot_ptr)->wrap();
517 MEM_delete(&slot);
518};
519
520bool Action::slot_remove(Slot &slot_to_remove)
521{
522 /* Check that this slot belongs to this Action. */
523 const int64_t slot_index = this->find_slot_index(slot_to_remove);
524 if (slot_index < 0) {
525 return false;
526 }
527
528 /* Remove the slot's data from each layer. */
529 for (Layer *layer : this->layers()) {
530 layer->slot_data_remove(*this, slot_to_remove.handle);
531 }
532
533 /* Don't bother un-assigning this slot from its users. The slot handle will
534 * not be reused by a new slot anyway. */
535
536 /* Remove the actual slot. */
538 &this->slot_array, &this->slot_array_num, nullptr, slot_index, slot_ptr_destructor);
539 return true;
540}
541
543{
544 for (Slot *slot : slots()) {
545 slot->set_active(slot->handle == slot_handle);
546 }
547}
548
550{
551 for (Slot *slot : slots()) {
552 if (slot->is_active()) {
553 return slot;
554 }
555 }
556 return nullptr;
557}
558
560{
561 AnimData *adt = BKE_animdata_from_id(&animated_id);
562
563 /* The slot handle is only valid when this action has already been
564 * assigned. Otherwise it's meaningless. */
565 if (adt && adt->action == this) {
566 Slot *slot = this->slot_for_handle(adt->slot_handle);
567 if (slot && slot->is_suitable_for(animated_id)) {
568 return slot;
569 }
570 }
571
572 /* Try the slot name from the AnimData, if it is set. */
573 if (adt && adt->slot_name[0]) {
574 Slot *slot = this->slot_find_by_name(adt->slot_name);
575 if (slot && slot->is_suitable_for(animated_id)) {
576 return slot;
577 }
578 }
579
580 /* As a last resort, search for the ID name. */
581 Slot *slot = this->slot_find_by_name(animated_id.name);
582 if (slot && slot->is_suitable_for(animated_id)) {
583 return slot;
584 }
585
586 return nullptr;
587}
588
589bool Action::is_slot_animated(const slot_handle_t slot_handle) const
590{
591 if (slot_handle == Slot::unassigned) {
592 return false;
593 }
594
595 Span<const FCurve *> fcurves = fcurves_for_action_slot(*this, slot_handle);
596 return !fcurves.is_empty();
597}
598
608
610{
611 BLI_assert(index >= 0 && index < this->strip_keyframe_data_array_num);
612
613 /* Make sure the data isn't being used anywhere. */
614 for (const Layer *layer : this->layers()) {
615 for (const Strip *strip : layer->strips()) {
616 if (strip->type() == Strip::Type::Keyframe && strip->data_index == index) {
617 return;
618 }
619 }
620 }
621
622 /* Free the item to be removed. */
623 MEM_delete<StripKeyframeData>(
624 static_cast<StripKeyframeData *>(this->strip_keyframe_data_array[index]));
625
626 /* Remove the item, swapping in the item at the end of the array. */
628 &this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, index);
629
630 /* Update strips that pointed at the swapped-in item.
631 *
632 * Note that we don't special-case the corner-case where the removed data was
633 * at the end of the array, but it ends up not mattering because then
634 * `old_index == index`. */
635 const int old_index = this->strip_keyframe_data_array_num;
636 for (Layer *layer : this->layers()) {
637 for (Strip *strip : layer->strips()) {
638 if (strip->type() == Strip::Type::Keyframe && strip->data_index == old_index) {
639 strip->data_index = index;
640 }
641 }
642 }
643}
644
646{
647 /* The reinterpret cast is needed because `strip_keyframe_data_array` is for
648 * pointers to the C type `ActionStripKeyframeData`, but we want the C++
649 * wrapper type `StripKeyframeData`. */
651 reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
653}
655{
656 /* The reinterpret cast is needed because `strip_keyframe_data_array` is for
657 * pointers to the C type `ActionStripKeyframeData`, but we want the C++
658 * wrapper type `StripKeyframeData`. */
660 reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
662}
663
665{
667
668 if (this->layers().is_empty()) {
669 return nullptr;
670 }
671
672 return this->layer(0);
673}
674
675void Action::slot_name_ensure_prefix(Slot &slot)
676{
679}
680
681void Action::slot_setup_for_id(Slot &slot, const ID &animated_id)
682{
683 if (slot.has_idtype()) {
684 BLI_assert(slot.idtype == GS(animated_id.name));
685 return;
686 }
687
688 slot.idtype = GS(animated_id.name);
689 this->slot_name_ensure_prefix(slot);
690}
691
692bool Action::has_keyframes(const slot_handle_t action_slot_handle) const
693{
694 if (this->is_action_legacy()) {
695 /* Old BKE_action_has_motion(const bAction *act) implementation. */
696 LISTBASE_FOREACH (const FCurve *, fcu, &this->curves) {
697 if (fcu->totvert) {
698 return true;
699 }
700 }
701 return false;
702 }
703
704 for (const FCurve *fcu : fcurves_for_action_slot(*this, action_slot_handle)) {
705 if (fcu->totvert) {
706 return true;
707 }
708 }
709 return false;
710}
711
713{
714 bool found_key = false;
715 float found_key_frame = 0.0f;
716
717 for (const FCurve *fcu : legacy::fcurves_all(this)) {
718 switch (fcu->totvert) {
719 case 0:
720 /* No keys, so impossible to come to a conclusion on this curve alone. */
721 continue;
722 case 1:
723 /* Single key, which is the complex case, so handle below. */
724 break;
725 default:
726 /* Multiple keys, so there is animation. */
727 return false;
728 }
729
730 const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] :
731 fcu->fpt[0].vec[0];
732 if (!found_key) {
733 found_key = true;
734 found_key_frame = this_key_frame;
735 continue;
736 }
737
738 /* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise
739 * with these comparisons. */
740 if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) {
741 /* This key differs from the already-found key, so this Action represents animation. */
742 return false;
743 }
744 }
745
746 /* There is only a single frame if we found at least one key. */
747 return found_key;
748}
749
751{
752 return (this->flag & ACT_FRAME_RANGE) && (this->flag & ACT_CYCLIC);
753}
754
756static float2 get_frame_range_of_fcurves(Span<const FCurve *> fcurves, bool include_modifiers);
757
759{
760 if (this->flag & ACT_FRAME_RANGE) {
761 return {this->frame_start, this->frame_end};
762 }
763
765 return get_frame_range_of_fcurves(all_fcurves, false);
766}
767
769{
770 if (this->flag & ACT_FRAME_RANGE) {
771 return {this->frame_start, this->frame_end};
772 }
773
774 Vector<const FCurve *> legacy_fcurves;
775 Span<const FCurve *> fcurves_to_consider;
776
777 if (this->is_action_layered()) {
778 fcurves_to_consider = fcurves_for_action_slot(*this, slot_handle);
779 }
780 else {
781 legacy_fcurves = legacy::fcurves_all(this);
782 fcurves_to_consider = legacy_fcurves;
783 }
784
785 return get_frame_range_of_fcurves(fcurves_to_consider, false);
786}
787
788float2 Action::get_frame_range_of_keys(const bool include_modifiers) const
789{
790 return get_frame_range_of_fcurves(legacy::fcurves_all(this), include_modifiers);
791}
792
794 const bool include_modifiers)
795{
796 float min = 999999999.0f, max = -999999999.0f;
797 bool foundvert = false, foundmod = false;
798
799 for (const FCurve *fcu : fcurves) {
800 /* if curve has keyframes, consider them first */
801 if (fcu->totvert) {
802 float nmin, nmax;
803
804 /* get extents for this curve
805 * - no "selected only", since this is often used in the backend
806 * - no "minimum length" (we will apply this later), otherwise
807 * single-keyframe curves will increase the overall length by
808 * a phantom frame (#50354)
809 */
810 BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
811
812 /* compare to the running tally */
813 min = min_ff(min, nmin);
814 max = max_ff(max, nmax);
815
816 foundvert = true;
817 }
818
819 /* if include_modifiers is enabled, need to consider modifiers too
820 * - only really care about the last modifier
821 */
822 if ((include_modifiers) && (fcu->modifiers.last)) {
823 FModifier *fcm = static_cast<FModifier *>(fcu->modifiers.last);
824
825 /* only use the maximum sensible limits of the modifiers if they are more extreme */
826 switch (fcm->type) {
827 case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */
828 {
829 FMod_Limits *fmd = (FMod_Limits *)fcm->data;
830
831 if (fmd->flag & FCM_LIMIT_XMIN) {
832 min = min_ff(min, fmd->rect.xmin);
833 }
834 if (fmd->flag & FCM_LIMIT_XMAX) {
835 max = max_ff(max, fmd->rect.xmax);
836 }
837 break;
838 }
839 case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */
840 {
841 FMod_Cycles *fmd = (FMod_Cycles *)fcm->data;
842
843 if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) {
844 min = MINAFRAMEF;
845 }
846 if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) {
847 max = MAXFRAMEF;
848 }
849 break;
850 }
851 /* TODO: function modifier may need some special limits */
852
853 default: /* all other standard modifiers are on the infinite range... */
854 min = MINAFRAMEF;
855 max = MAXFRAMEF;
856 break;
857 }
858
859 foundmod = true;
860 }
861
862 /* This block is here just so that editors/IDEs do not get confused about the two opening
863 * curly braces in the `#ifdef WITH_ANIM_BAKLAVA` block above, but one closing curly brace
864 * here. */
865 }
866
867 if (foundvert || foundmod) {
868 return float2{max_ff(min, MINAFRAMEF), min_ff(max, MAXFRAMEF)};
869 }
870
871 return float2{0.0f, 0.0f};
872}
873
874/* ----- ActionLayer implementation ----------- */
875
877{
878 ActionLayer *copy = MEM_cnew<ActionLayer>(allocation_name.c_str());
879 *copy = *reinterpret_cast<const ActionLayer *>(this);
880
881 /* Make a shallow copy of the Strips, without copying their data. */
882 copy->strip_array = MEM_cnew_array<ActionStrip *>(this->strip_array_num,
883 allocation_name.c_str());
884 for (int i : this->strips().index_range()) {
885 Strip *strip_copy = MEM_new<Strip>(allocation_name.c_str(), *this->strip(i));
886 copy->strip_array[i] = strip_copy;
887 }
888
889 return &copy->wrap();
890}
891
893{
894 for (Strip *strip : this->strips()) {
895 MEM_delete(strip);
896 }
898 this->strip_array_num = 0;
899}
900
902{
903 return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
904 this->strip_array_num};
905}
907{
908 return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
909 this->strip_array_num};
910}
911const Strip *Layer::strip(const int64_t index) const
912{
913 return &this->strip_array[index]->wrap();
914}
916{
917 return &this->strip_array[index]->wrap();
918}
919
920Strip &Layer::strip_add(Action &owning_action, const Strip::Type strip_type)
921{
922 Strip &strip = Strip::create(owning_action, strip_type);
923
924 /* Add the new strip to the strip array. */
926
927 return strip;
928}
929
930static void strip_ptr_destructor(ActionStrip **dna_strip_ptr)
931{
932 Strip &strip = (*dna_strip_ptr)->wrap();
933 MEM_delete(&strip);
934};
935
936bool Layer::strip_remove(Action &owning_action, Strip &strip)
937{
938 const int64_t strip_index = this->find_strip_index(strip);
939 if (strip_index < 0) {
940 return false;
941 }
942
943 const Strip::Type strip_type = strip.type();
944 const int data_index = strip.data_index;
945
947 &this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor);
948
949 /* It's important that we do this *after* removing the strip itself
950 * (immediately above), because otherwise the strip will be found as a
951 * still-existing user of the strip data and thus the strip data won't be
952 * removed even if this strip was the last user. */
953 switch (strip_type) {
954 case Strip::Type::Keyframe:
955 owning_action.strip_keyframe_data_remove_if_unused(data_index);
956 break;
957 }
958
959 return true;
960}
961
963{
964 for (const int64_t strip_index : this->strips().index_range()) {
965 const Strip *visit_strip = this->strip(strip_index);
966 if (visit_strip == &strip) {
967 return strip_index;
968 }
969 }
970 return -1;
971}
972
973void Layer::slot_data_remove(Action &owning_action, const slot_handle_t slot_handle)
974{
975 for (Strip *strip : this->strips()) {
976 strip->slot_data_remove(owning_action, slot_handle);
977 }
978}
979
980/* ----- ActionSlot implementation ----------- */
981
983{
984 memset(this, 0, sizeof(*this));
985 this->runtime = MEM_new<SlotRuntime>(__func__);
986}
987
988Slot::Slot(const Slot &other)
989{
990 memcpy(this, &other, sizeof(*this));
991 this->runtime = MEM_new<SlotRuntime>(__func__);
992}
993
995{
996 MEM_delete(this->runtime);
997}
998
1000{
1001 BLI_assert(!this->runtime);
1002 this->runtime = MEM_new<SlotRuntime>(__func__);
1003}
1004
1005bool Slot::is_suitable_for(const ID &animated_id) const
1006{
1007 if (!this->has_idtype()) {
1008 /* Without specific ID type set, this Slot can animate any ID. */
1009 return true;
1010 }
1011
1012 /* Check that the ID type is compatible with this slot. */
1013 const int animated_idtype = GS(animated_id.name);
1014 return this->idtype == animated_idtype;
1015}
1016
1018{
1019 return this->idtype != 0;
1020}
1021
1023{
1024 return static_cast<Slot::Flags>(this->slot_flags);
1025}
1027{
1028 return this->slot_flags & uint8_t(Flags::Expanded);
1029}
1030void Slot::set_expanded(const bool expanded)
1031{
1032 if (expanded) {
1034 }
1035 else {
1037 }
1038}
1039
1041{
1042 return this->slot_flags & uint8_t(Flags::Selected);
1043}
1044void Slot::set_selected(const bool selected)
1045{
1046 if (selected) {
1048 }
1049 else {
1051 }
1052}
1053
1055{
1056 return this->slot_flags & uint8_t(Flags::Active);
1057}
1058void Slot::set_active(const bool active)
1059{
1060 if (active) {
1062 }
1063 else {
1065 }
1066}
1067
1069{
1072 }
1073 BLI_assert(this->runtime);
1074 return this->runtime->users.as_span();
1075}
1076
1078{
1079 BLI_assert_msg(this->runtime, "Slot::runtime should always be allocated");
1080 return this->runtime->users;
1081}
1082
1083void Slot::users_add(ID &animated_id)
1084{
1085 BLI_assert(this->runtime);
1086 this->runtime->users.append_non_duplicates(&animated_id);
1087}
1088
1089void Slot::users_remove(ID &animated_id)
1090{
1091 BLI_assert(this->runtime);
1092 Vector<ID *> &users = this->runtime->users;
1093
1094 const int64_t vector_index = users.first_index_of_try(&animated_id);
1095 if (vector_index < 0) {
1096 return;
1097 }
1098
1099 users.remove_and_reorder(vector_index);
1100}
1101
1103{
1104 bmain.is_action_slot_to_id_map_dirty = true;
1105}
1106
1108{
1109 if (!this->has_idtype()) {
1110 return slot_unbound_prefix;
1111 }
1112
1113 char name[3] = {0};
1114 *reinterpret_cast<short *>(name) = this->idtype;
1115 return name;
1116}
1117
1119{
1120 BLI_assert(StringRef(this->name).size() >= name_length_min);
1121
1122 /* Avoid accessing an uninitialized part of the string accidentally. */
1123 if (this->name[0] == '\0' || this->name[1] == '\0') {
1124 return "";
1125 }
1126 return this->name + 2;
1127}
1128
1130{
1131 BLI_assert(StringRef(this->name).size() >= name_length_min);
1132
1133 if (StringRef(this->name).size() < 2) {
1134 /* The code below would overwrite the trailing 0-byte. */
1135 this->name[2] = '\0';
1136 }
1137
1138 if (!this->has_idtype()) {
1139 /* A zero idtype is not going to convert to a two-character string, so we
1140 * need to explicitly assign the default prefix. */
1141 this->name[0] = slot_unbound_prefix[0];
1142 this->name[1] = slot_unbound_prefix[1];
1143 return;
1144 }
1145
1146 *reinterpret_cast<short *>(this->name) = this->idtype;
1147}
1148
1149/* ----- Functions ----------- */
1150
1152{
1153 bAction *dna_action = BKE_action_add(&bmain, name.c_str());
1154 id_us_clear_real(&dna_action->id);
1155 return dna_action->wrap();
1156}
1157
1158bool assign_action(bAction *action, ID &animated_id)
1159{
1160 AnimData *adt = BKE_animdata_ensure_id(&animated_id);
1161 if (!adt) {
1162 return false;
1163 }
1164 return assign_action(action, {animated_id, *adt});
1165}
1166
1167bool assign_action(bAction *action, const OwnedAnimData owned_adt)
1168{
1169 if (!BKE_animdata_action_editable(&owned_adt.adt)) {
1170 /* Cannot remove, otherwise things turn to custard. */
1171 BKE_report(nullptr, RPT_ERROR, "Cannot change action, as it is still being edited in NLA");
1172 return false;
1173 }
1174
1175 return generic_assign_action(owned_adt.owner_id,
1176 action,
1177 owned_adt.adt.action,
1178 owned_adt.adt.slot_handle,
1179 owned_adt.adt.slot_name);
1180}
1181
1182bool assign_tmpaction(bAction *action, const OwnedAnimData owned_adt)
1183{
1184 return generic_assign_action(owned_adt.owner_id,
1185 action,
1186 owned_adt.adt.tmpact,
1187 owned_adt.adt.tmp_slot_handle,
1188 owned_adt.adt.tmp_slot_name);
1189}
1190
1191bool unassign_action(ID &animated_id)
1192{
1193 return assign_action(nullptr, animated_id);
1194}
1195
1197{
1198 return assign_action(nullptr, owned_adt);
1199}
1200
1202{
1203 Slot *slot;
1204
1205 /* Find a suitable slot, but be stricter when to allow searching by name than
1206 * action.find_suitable_slot_for(animated_id). */
1207 {
1208 AnimData *adt = BKE_animdata_from_id(&animated_id);
1209
1210 if (adt && adt->action == &action) {
1211 /* The slot handle is only valid when this action is already assigned.
1212 * Otherwise it's meaningless. */
1213 slot = action.slot_for_handle(adt->slot_handle);
1214
1215 /* If this Action is already assigned, a search by name is inappropriate, as it might
1216 * re-assign an intentionally-unassigned slot. */
1217 }
1218 else {
1219 /* Try the slot name from the AnimData, if it is set. */
1220 if (adt && adt->slot_name[0]) {
1221 slot = action.slot_find_by_name(adt->slot_name);
1222 }
1223 else {
1224 /* As a last resort, search for the ID name. */
1225 slot = action.slot_find_by_name(animated_id.name);
1226 }
1227 }
1228 }
1229
1230 if (!slot || !slot->is_suitable_for(animated_id)) {
1231 slot = &action.slot_add_for_id(animated_id);
1232 }
1233
1234 if (!assign_action(&action, animated_id)) {
1235 return nullptr;
1236 }
1237
1238 if (assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK) {
1239 /* This should never happen, as a few lines above a new slot is created for
1240 * this ID if the found one wasn't deemed suitable. */
1242 return nullptr;
1243 }
1244
1245 return slot;
1246}
1247
1248static bool is_id_using_action_slot(const ID &animated_id,
1249 const Action &action,
1250 const slot_handle_t slot_handle)
1251{
1252 auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool {
1253 const bool is_used = (&used_action == &action && used_slot_handle == slot_handle);
1254 return !is_used; /* Stop searching when we found a use of this Action+Slot. */
1255 };
1256
1257 const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use);
1258 return !looped_until_end;
1259}
1260
1261bool generic_assign_action(ID &animated_id,
1262 bAction *action_to_assign,
1263 bAction *&action_ptr_ref,
1264 slot_handle_t &slot_handle_ref,
1265 char *slot_name)
1266{
1267 BLI_assert(slot_name);
1268
1269 if (action_to_assign && legacy::action_treat_as_legacy(*action_to_assign)) {
1270 /* Check that the Action is suitable for this ID type.
1271 * This is only necessary for legacy Actions. */
1272 if (!BKE_animdata_action_ensure_idroot(&animated_id, action_to_assign)) {
1274 nullptr,
1275 RPT_ERROR,
1276 "Could not set action '%s' to animate ID '%s', as it does not have suitably rooted "
1277 "paths for this purpose",
1278 action_to_assign->id.name + 2,
1279 animated_id.name);
1280 return false;
1281 }
1282 }
1283
1284 /* Un-assign any previously-assigned Action first. */
1285 if (action_ptr_ref) {
1286 /* Un-assign the slot. This will always succeed, so no need to check the result. */
1287 if (slot_handle_ref != Slot::unassigned) {
1289 nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_name);
1291 UNUSED_VARS_NDEBUG(result);
1292 }
1293
1294 /* Un-assign the Action itself. */
1295 id_us_min(&action_ptr_ref->id);
1296 action_ptr_ref = nullptr;
1297 }
1298
1299 if (!action_to_assign) {
1300 /* Un-assigning was the point, so the work is done. */
1301 return true;
1302 }
1303
1304 /* Assign the new Action. */
1305 action_ptr_ref = action_to_assign;
1306 id_us_plus(&action_ptr_ref->id);
1307
1308 /* Assign the slot. Legacy Actions do not have slots, so for those `slot` will always be
1309 * `nullptr`, which is perfectly acceptable for generic_assign_action_slot(). */
1310 Slot *slot = action_to_assign->wrap().find_suitable_slot_for(animated_id);
1312 slot, animated_id, action_ptr_ref, slot_handle_ref, slot_name);
1314 UNUSED_VARS_NDEBUG(result);
1315
1316 return true;
1317}
1318
1320 ID &animated_id,
1321 bAction *&action_ptr_ref,
1322 slot_handle_t &slot_handle_ref,
1323 char *slot_name)
1324{
1325 BLI_assert(slot_name);
1326 if (!action_ptr_ref) {
1327 /* No action assigned yet, so no way to assign a slot. */
1329 }
1330
1331 Action &action = action_ptr_ref->wrap();
1332
1333 /* Check that the slot can actually be assigned. */
1334 if (slot_to_assign) {
1335 if (!action.slots().contains(slot_to_assign)) {
1337 }
1338
1339 if (!slot_to_assign->is_suitable_for(animated_id)) {
1341 }
1342 }
1343
1344 Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref);
1345
1346 /* If there was a previously-assigned slot, unassign it first. */
1347 slot_handle_ref = Slot::unassigned;
1348 if (slot_to_unassign) {
1349 /* Make sure that the stored Slot name is up to date. The slot name might have
1350 * changed in a way that wasn't copied into the ADT yet (for example when the
1351 * Action is linked from another file), so better copy the name to be sure
1352 * that it can be transparently reassigned later.
1353 *
1354 * TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure
1355 * this name is always correct. */
1356 BLI_strncpy_utf8(slot_name, slot_to_unassign->name, Slot::name_length_max);
1357
1358 /* If this was the last use of this slot, remove this ID from its users. */
1359 if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) {
1360 slot_to_unassign->users_remove(animated_id);
1361 }
1362 }
1363
1364 if (!slot_to_assign) {
1366 }
1367
1368 action.slot_setup_for_id(*slot_to_assign, animated_id);
1369 slot_handle_ref = slot_to_assign->handle;
1370 BLI_strncpy_utf8(slot_name, slot_to_assign->name, Slot::name_length_max);
1371 slot_to_assign->users_add(animated_id);
1372
1374}
1375
1377 ID &animated_id,
1378 bAction *&action_ptr_ref,
1379 slot_handle_t &slot_handle_ref,
1380 char *slot_name)
1381{
1382 if (slot_handle_to_assign == Slot::unassigned && !action_ptr_ref) {
1383 /* No Action assigned, so no slot was used anyway. Just blindly assign the
1384 * 'unassigned' handle. */
1385 slot_handle_ref = Slot::unassigned;
1387 }
1388
1389 if (!action_ptr_ref) {
1390 /* No Action to verify the slot handle is valid. As the slot handle will be
1391 * completely ignored when re-assigning an Action, better to refuse setting
1392 * it altogether. This will make bugs more obvious. */
1394 }
1395
1396 Slot *slot = action_ptr_ref->wrap().slot_for_handle(slot_handle_to_assign);
1397 return generic_assign_action_slot(slot, animated_id, action_ptr_ref, slot_handle_ref, slot_name);
1398}
1399
1400bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
1401{
1402 if (!dna_action) {
1403 /* Clearing the Action is always possible. */
1404 return true;
1405 }
1406
1407 if (dna_action->idroot == 0) {
1408 /* This is either a never-assigned legacy action, or a layered action. In
1409 * any case, it can be assigned to any ID. */
1410 return true;
1411 }
1412
1413 const animrig::Action &action = dna_action->wrap();
1414 if (legacy::action_treat_as_legacy(action)) {
1415 /* Legacy Actions can only be assigned if their idroot matches. Empty
1416 * Actions are considered both 'layered' and 'legacy' at the same time,
1417 * hence this condition checks for 'not layered' rather than 'legacy'. */
1418 return action.idroot == id_code;
1419 }
1420
1421 return true;
1422}
1423
1425{
1426 AnimData *adt = BKE_animdata_from_id(&animated_id);
1427 if (!adt) {
1429 }
1430
1432 slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->slot_name);
1433}
1434
1436 Slot *slot_to_assign,
1437 ID &animated_id)
1438{
1439 if (!assign_action(action, animated_id)) {
1441 }
1442 return assign_action_slot(slot_to_assign, animated_id);
1443}
1444
1445Action *get_action(ID &animated_id)
1446{
1447 /* TODO: rename to get_action(). */
1448
1449 AnimData *adt = BKE_animdata_from_id(&animated_id);
1450 if (!adt) {
1451 return nullptr;
1452 }
1453 if (!adt->action) {
1454 return nullptr;
1455 }
1456 return &adt->action->wrap();
1457}
1458
1459std::optional<std::pair<Action *, Slot *>> get_action_slot_pair(ID &animated_id)
1460{
1461 AnimData *adt = BKE_animdata_from_id(&animated_id);
1462 if (!adt || !adt->action) {
1463 /* Not animated by any Action. */
1464 return std::nullopt;
1465 }
1466
1467 Action &action = adt->action->wrap();
1468 Slot *slot = action.slot_for_handle(adt->slot_handle);
1469 if (!slot) {
1470 /* Will not receive any animation from this Action. */
1471 return std::nullopt;
1472 }
1473
1474 return std::make_pair(&action, slot);
1475}
1476
1477/* ----- ActionStrip implementation ----------- */
1478
1479Strip &Strip::create(Action &owning_action, const Strip::Type type)
1480{
1481 /* Create the strip. */
1482 ActionStrip *strip = MEM_cnew<ActionStrip>(__func__);
1483 memcpy(strip, DNA_struct_default_get(ActionStrip), sizeof(*strip));
1484 strip->strip_type = int8_t(type);
1485
1486 /* Create the strip's data on the owning Action. */
1487 switch (type) {
1488 case Strip::Type::Keyframe: {
1489 StripKeyframeData *strip_data = MEM_new<StripKeyframeData>(__func__);
1490 strip->data_index = owning_action.strip_keyframe_data_append(strip_data);
1491 break;
1492 }
1493 }
1494
1495 /* This can happen if someone forgets to add a strip type in the `switch`
1496 * above, or if someone is evil and passes an invalid strip type to this
1497 * function. */
1498 BLI_assert_msg(strip->data_index != -1, "Newly created strip has no data.");
1499
1500 return strip->wrap();
1501}
1502
1503bool Strip::is_infinite() const
1504{
1505 return this->frame_start == -std::numeric_limits<float>::infinity() &&
1506 this->frame_end == std::numeric_limits<float>::infinity();
1507}
1508
1509bool Strip::contains_frame(const float frame_time) const
1510{
1511 return this->frame_start <= frame_time && frame_time <= this->frame_end;
1512}
1513
1514bool Strip::is_last_frame(const float frame_time) const
1515{
1516 /* Maybe this needs a more advanced equality check. Implement that when
1517 * we have an actual example case that breaks. */
1518 return this->frame_end == frame_time;
1519}
1520
1521void Strip::resize(const float frame_start, const float frame_end)
1522{
1524 BLI_assert_msg(frame_start < std::numeric_limits<float>::infinity(),
1525 "only the end frame can be at positive infinity");
1526 BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
1527 "only the start frame can be at negative infinity");
1528 this->frame_start = frame_start;
1529 this->frame_end = frame_end;
1530}
1531
1532template<>
1533const StripKeyframeData &Strip::data<StripKeyframeData>(const Action &owning_action) const
1534{
1536
1537 return *owning_action.strip_keyframe_data()[this->data_index];
1538}
1539template<> StripKeyframeData &Strip::data<StripKeyframeData>(Action &owning_action)
1540{
1542
1543 return *owning_action.strip_keyframe_data()[this->data_index];
1544}
1545
1546void Strip::slot_data_remove(Action &owning_action, const slot_handle_t slot_handle)
1547{
1548 switch (this->type()) {
1549 case Type::Keyframe:
1550 this->data<StripKeyframeData>(owning_action).slot_data_remove(slot_handle);
1551 }
1552}
1553
1554/* ----- ActionStripKeyframeData implementation ----------- */
1555
1557{
1558 memcpy(this, &other, sizeof(*this));
1559
1560 this->channelbag_array = MEM_cnew_array<ActionChannelBag *>(other.channelbag_array_num,
1561 __func__);
1562 Span<const ChannelBag *> channelbags_src = other.channelbags();
1563 for (int i : channelbags_src.index_range()) {
1564 this->channelbag_array[i] = MEM_new<animrig::ChannelBag>(__func__, *other.channelbag(i));
1565 }
1566}
1567
1569{
1570 for (ChannelBag *channelbag_for_slot : this->channelbags()) {
1571 MEM_delete(channelbag_for_slot);
1572 }
1574 this->channelbag_array_num = 0;
1575}
1576
1588{
1589 return &this->channelbag_array[index]->wrap();
1590}
1592{
1593 return &this->channelbag_array[index]->wrap();
1594}
1596{
1597 for (const ChannelBag *channels : this->channelbags()) {
1598 if (channels->slot_handle == slot_handle) {
1599 return channels;
1600 }
1601 }
1602 return nullptr;
1603}
1605{
1606 for (int64_t index = 0; index < this->channelbag_array_num; index++) {
1607 if (this->channelbag(index) == &channelbag) {
1608 return index;
1609 }
1610 }
1611 return -1;
1612}
1614{
1615 const auto *const_this = const_cast<const StripKeyframeData *>(this);
1616 const auto *const_channels = const_this->channelbag_for_slot(slot_handle);
1617 return const_cast<ChannelBag *>(const_channels);
1618}
1620{
1621 return this->channelbag_for_slot(slot.handle);
1622}
1624{
1625 return this->channelbag_for_slot(slot.handle);
1626}
1627
1629{
1630 BLI_assert_msg(channelbag_for_slot(slot) == nullptr,
1631 "Cannot add chans-for-slot for already-registered slot");
1632
1633 ChannelBag &channels = MEM_new<ActionChannelBag>(__func__)->wrap();
1634 channels.slot_handle = slot.handle;
1635
1637 &this->channelbag_array, &this->channelbag_array_num, &channels);
1638
1639 return channels;
1640}
1641
1643{
1644 ChannelBag *channel_bag = this->channelbag_for_slot(slot);
1645 if (channel_bag != nullptr) {
1646 return *channel_bag;
1647 }
1648 return this->channelbag_for_slot_add(slot);
1649}
1650
1651static void channelbag_ptr_destructor(ActionChannelBag **dna_channelbag_ptr)
1652{
1653 ChannelBag &channelbag = (*dna_channelbag_ptr)->wrap();
1654 MEM_delete(&channelbag);
1655};
1656
1658{
1659 const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove);
1660 if (channelbag_index < 0) {
1661 return false;
1662 }
1663
1665 &this->channelbag_array_num,
1666 nullptr,
1667 channelbag_index,
1669
1670 return true;
1671}
1672
1674{
1675 ChannelBag *channelbag = this->channelbag_for_slot(slot_handle);
1676 if (!channelbag) {
1677 return;
1678 }
1679 this->channelbag_remove(*channelbag);
1680}
1681
1682const FCurve *ChannelBag::fcurve_find(const FCurveDescriptor fcurve_descriptor) const
1683{
1684 return animrig::fcurve_find(this->fcurves(), fcurve_descriptor);
1685}
1686
1688{
1689 /* Intermediate variable needed to disambiguate const/non-const overloads. */
1690 Span<FCurve *> fcurves = this->fcurves();
1691 return animrig::fcurve_find(fcurves, fcurve_descriptor);
1692}
1693
1695{
1696 if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) {
1697 return *existing_fcurve;
1698 }
1699 return this->fcurve_create(bmain, fcurve_descriptor);
1700}
1701
1703{
1704 if (this->fcurve_find(fcurve_descriptor)) {
1705 return nullptr;
1706 }
1707 return &this->fcurve_create(bmain, fcurve_descriptor);
1708}
1709
1711{
1712 FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor);
1713
1714 if (this->fcurve_array_num == 0) {
1715 new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
1716 }
1717
1718 bActionGroup *group = fcurve_descriptor.channel_group.has_value() ?
1719 &this->channel_group_ensure(*fcurve_descriptor.channel_group) :
1720 nullptr;
1721 const int insert_index = group ? group->fcurve_range_start + group->fcurve_range_length :
1722 this->fcurve_array_num;
1723 BLI_assert(insert_index <= this->fcurve_array_num);
1724
1725 grow_array_and_insert(&this->fcurve_array, &this->fcurve_array_num, insert_index, new_fcurve);
1726 if (group) {
1727 group->fcurve_range_length += 1;
1728 this->restore_channel_group_invariants();
1729 }
1730
1731 if (bmain) {
1733 }
1734
1735 return *new_fcurve;
1736}
1737
1739{
1740 /* Appended F-Curves don't belong to any group yet, so better make sure their
1741 * group pointer reflects that. */
1742 fcurve.grp = nullptr;
1743
1744 grow_array_and_append(&this->fcurve_array, &this->fcurve_array_num, &fcurve);
1745}
1746
1747static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
1748{
1749 BKE_fcurve_free(*fcurve_ptr);
1750};
1751
1752bool ChannelBag::fcurve_remove(FCurve &fcurve_to_remove)
1753{
1754 if (!this->fcurve_detach(fcurve_to_remove)) {
1755 return false;
1756 }
1757 BKE_fcurve_free(&fcurve_to_remove);
1758 return true;
1759}
1760
1762{
1763 /* Grab the pointer before it's detached, so we can free it after. */
1764 FCurve *fcurve_to_remove = this->fcurve(fcurve_index);
1765
1766 this->fcurve_detach_by_index(fcurve_index);
1767
1768 BKE_fcurve_free(fcurve_to_remove);
1769}
1770
1771static void fcurve_ptr_noop_destructor(FCurve ** /*fcurve_ptr*/) {}
1772
1773bool ChannelBag::fcurve_detach(FCurve &fcurve_to_detach)
1774{
1775 const int64_t fcurve_index = this->fcurves().first_index_try(&fcurve_to_detach);
1776 if (fcurve_index < 0) {
1777 return false;
1778 }
1779 this->fcurve_detach_by_index(fcurve_index);
1780 return true;
1781}
1782
1784{
1785 BLI_assert(fcurve_index >= 0);
1786 BLI_assert(fcurve_index < this->fcurve_array_num);
1787
1788 const int group_index = this->channel_group_containing_index(fcurve_index);
1789 if (group_index != -1) {
1790 bActionGroup *group = this->channel_group(group_index);
1791
1792 group->fcurve_range_length -= 1;
1793 if (group->fcurve_range_length <= 0) {
1794 const int group_index = this->channel_groups().first_index_try(group);
1795 this->channel_group_remove_raw(group_index);
1796 }
1797 }
1798
1800 &this->fcurve_array_num,
1801 nullptr,
1802 fcurve_index,
1804
1805 this->restore_channel_group_invariants();
1806
1807 /* As an optimization, this function could call `DEG_relations_tag_update(bmain)` to prune any
1808 * relationships that are now no longer necessary. This is not needed for correctness of the
1809 * depsgraph evaluation results though. */
1810}
1811
1812void ChannelBag::fcurve_move(FCurve &fcurve, int to_fcurve_index)
1813{
1814 BLI_assert(to_fcurve_index >= 0 && to_fcurve_index < this->fcurves().size());
1815
1816 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
1817 BLI_assert_msg(fcurve_index >= 0, "FCurve not in this channel bag.");
1818
1820 this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_fcurve_index);
1821
1822 this->restore_channel_group_invariants();
1823}
1824
1826{
1828
1829 /* Since all F-Curves are gone, the groups are all empty. */
1830 for (bActionGroup *group : channel_groups()) {
1831 group->fcurve_range_start = 0;
1832 group->fcurve_range_length = 0;
1833 }
1834}
1835
1837 const Slot &slot,
1838 const FCurveDescriptor fcurve_descriptor,
1839 const float2 time_value,
1840 const KeyframeSettings &settings,
1841 const eInsertKeyFlags insert_key_flags)
1842{
1843 /* Get the fcurve, or create one if it doesn't exist and the keying flags
1844 * allow. */
1845 FCurve *fcurve = nullptr;
1846 if (key_insertion_may_create_fcurve(insert_key_flags)) {
1847 fcurve = &this->channelbag_for_slot_ensure(slot).fcurve_ensure(bmain, fcurve_descriptor);
1848 }
1849 else {
1850 ChannelBag *channels = this->channelbag_for_slot(slot);
1851 if (channels != nullptr) {
1852 fcurve = channels->fcurve_find(fcurve_descriptor);
1853 }
1854 }
1855
1856 if (!fcurve) {
1857 std::fprintf(stderr,
1858 "FCurve %s[%d] for slot %s was not created due to either the Only Insert "
1859 "Available setting or Replace keyframing mode.\n",
1860 fcurve_descriptor.rna_path.c_str(),
1861 fcurve_descriptor.array_index,
1862 slot.name);
1864 }
1865
1866 if (!BKE_fcurve_is_keyframable(fcurve)) {
1867 /* TODO: handle this properly, in a way that can be communicated to the user. */
1868 std::fprintf(stderr,
1869 "FCurve %s[%d] for slot %s doesn't allow inserting keys.\n",
1870 fcurve_descriptor.rna_path.c_str(),
1871 fcurve_descriptor.array_index,
1872 slot.name);
1874 }
1875
1876 const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
1877 fcurve, time_value, settings, insert_key_flags);
1878
1879 if (insert_vert_result != SingleKeyingResult::SUCCESS) {
1880 std::fprintf(stderr,
1881 "Could not insert key into FCurve %s[%d] for slot %s.\n",
1882 fcurve_descriptor.rna_path.c_str(),
1883 fcurve_descriptor.array_index,
1884 slot.name);
1885 return insert_vert_result;
1886 }
1887
1889}
1890
1891/* ActionChannelBag implementation. */
1892
1894{
1895 this->slot_handle = other.slot_handle;
1896
1897 this->fcurve_array_num = other.fcurve_array_num;
1898 this->fcurve_array = MEM_cnew_array<FCurve *>(other.fcurve_array_num, __func__);
1899 for (int i = 0; i < other.fcurve_array_num; i++) {
1900 const FCurve *fcu_src = other.fcurve_array[i];
1901 this->fcurve_array[i] = BKE_fcurve_copy(fcu_src);
1902 }
1903
1904 this->group_array_num = other.group_array_num;
1905 this->group_array = MEM_cnew_array<bActionGroup *>(other.group_array_num, __func__);
1906 for (int i = 0; i < other.group_array_num; i++) {
1907 const bActionGroup *group_src = other.group_array[i];
1908 this->group_array[i] = static_cast<bActionGroup *>(MEM_dupallocN(group_src));
1909 this->group_array[i]->channel_bag = this;
1910 }
1911
1912 /* BKE_fcurve_copy() resets the FCurve's group pointer. Which is good, because the groups are
1913 * duplicated too. This sets the group pointers to the correct values. */
1914 this->restore_channel_group_invariants();
1915}
1916
1918{
1919 for (FCurve *fcu : this->fcurves()) {
1920 BKE_fcurve_free(fcu);
1921 }
1923 this->fcurve_array_num = 0;
1924
1925 for (bActionGroup *group : this->channel_groups()) {
1926 MEM_SAFE_FREE(group);
1927 }
1929 this->group_array_num = 0;
1930}
1931
1940const FCurve *ChannelBag::fcurve(const int64_t index) const
1941{
1942 return this->fcurve_array[index];
1943}
1945{
1946 return this->fcurve_array[index];
1947}
1948
1958{
1959 BLI_assert(index < this->group_array_num);
1960 return this->group_array[index];
1961}
1963{
1964 BLI_assert(index < this->group_array_num);
1965 return this->group_array[index];
1966}
1967
1969{
1970 for (const bActionGroup *group : this->channel_groups()) {
1971 if (name == StringRef{group->name}) {
1972 return group;
1973 }
1974 }
1975
1976 return nullptr;
1977}
1978
1980{
1981 /* Intermediate variable needed to disambiguate const/non-const overloads. */
1982 Span<bActionGroup *> groups = this->channel_groups();
1983 for (bActionGroup *group : groups) {
1984 if (name == StringRef{group->name}) {
1985 return group;
1986 }
1987 }
1988
1989 return nullptr;
1990}
1991
1992int ChannelBag::channel_group_containing_index(const int fcurve_array_index)
1993{
1994 int i = 0;
1995 for (const bActionGroup *group : this->channel_groups()) {
1996 if (fcurve_array_index >= group->fcurve_range_start &&
1997 fcurve_array_index < (group->fcurve_range_start + group->fcurve_range_length))
1998 {
1999 return i;
2000 }
2001 i++;
2002 }
2003
2004 return -1;
2005}
2006
2008{
2009 bActionGroup *new_group = static_cast<bActionGroup *>(
2010 MEM_callocN(sizeof(bActionGroup), __func__));
2011
2012 /* Find the end fcurve index of the current channel groups, to be used as the
2013 * start of the new channel group. */
2014 int fcurve_index = 0;
2015 const int length = this->channel_groups().size();
2016 if (length > 0) {
2017 const bActionGroup *last = this->channel_group(length - 1);
2018 fcurve_index = last->fcurve_range_start + last->fcurve_range_length;
2019 }
2020 new_group->fcurve_range_start = fcurve_index;
2021
2022 new_group->channel_bag = this;
2023
2024 /* Make it selected. */
2025 new_group->flag = AGRP_SELECTED;
2026
2027 /* Ensure it has a unique name.
2028 *
2029 * Note that this only happens here (upon creation). The user can later rename
2030 * groups to have duplicate names. This is stupid, but it's how the legacy
2031 * system worked, and at the time of writing this code we're just trying to
2032 * match that system's behavior, even when it's goofy.*/
2033 std::string unique_name = BLI_uniquename_cb(
2034 [&](const StringRef name) {
2035 for (const bActionGroup *group : this->channel_groups()) {
2036 if (STREQ(group->name, name.data())) {
2037 return true;
2038 }
2039 }
2040 return false;
2041 },
2042 '.',
2043 name[0] == '\0' ? DATA_("Group") : name);
2044
2045 STRNCPY_UTF8(new_group->name, unique_name.c_str());
2046
2047 grow_array_and_append(&this->group_array, &this->group_array_num, new_group);
2048
2049 return *new_group;
2050}
2051
2053{
2054 bActionGroup *group = this->channel_group_find(name);
2055 if (group) {
2056 return *group;
2057 }
2058
2059 return this->channel_group_create(name);
2060}
2061
2063{
2064 const int group_index = this->channel_groups().first_index_try(&group);
2065 if (group_index == -1) {
2066 return false;
2067 }
2068
2069 /* Move the group's fcurves to just past the end of where the grouped
2070 * fcurves will be after this group is removed. */
2071 const bActionGroup *last_group = this->channel_groups().last();
2072 BLI_assert(last_group != nullptr);
2073 const int to_index = last_group->fcurve_range_start + last_group->fcurve_range_length -
2074 group.fcurve_range_length;
2076 this->fcurve_array_num,
2077 group.fcurve_range_start,
2078 group.fcurve_range_start + group.fcurve_range_length,
2079 to_index);
2080
2081 this->channel_group_remove_raw(group_index);
2082 this->restore_channel_group_invariants();
2083
2084 return true;
2085}
2086
2087void ChannelBag::channel_group_move(bActionGroup &group, const int to_group_index)
2088{
2089 BLI_assert(to_group_index >= 0 && to_group_index < this->channel_groups().size());
2090
2091 const int group_index = this->channel_groups().first_index_try(&group);
2092 BLI_assert_msg(group_index >= 0, "Group not in this channel bag.");
2093
2094 /* Shallow copy, to track which fcurves should be moved in the second step. */
2095 const bActionGroup pre_move_group = group;
2096
2097 /* First we move the group to its new position. The call to
2098 * `restore_channel_group_invariants()` is necessary to update the group's
2099 * fcurve range (as well as the ranges of the other groups) to match its new
2100 * position in the group array. */
2102 this->group_array, this->group_array_num, group_index, group_index + 1, to_group_index);
2103 this->restore_channel_group_invariants();
2104
2105 /* Move the fcurves that were part of `group` (as recorded in
2106 *`pre_move_group`) to their new positions (now in `group`) so that they're
2107 * part of `group` again. */
2109 this->fcurve_array_num,
2110 pre_move_group.fcurve_range_start,
2111 pre_move_group.fcurve_range_start + pre_move_group.fcurve_range_length,
2112 group.fcurve_range_start);
2113 this->restore_channel_group_invariants();
2114}
2115
2116void ChannelBag::channel_group_remove_raw(const int group_index)
2117{
2118 BLI_assert(group_index >= 0 && group_index < this->channel_groups().size());
2119
2120 MEM_SAFE_FREE(this->group_array[group_index]);
2121 shrink_array_and_remove(&this->group_array, &this->group_array_num, group_index);
2122}
2123
2124void ChannelBag::restore_channel_group_invariants()
2125{
2126 /* Shift channel groups. */
2127 {
2128 int start_index = 0;
2129 for (bActionGroup *group : this->channel_groups()) {
2130 group->fcurve_range_start = start_index;
2131 start_index += group->fcurve_range_length;
2132 }
2133
2134 /* Double-check that this didn't push any of the groups off the end of the
2135 * fcurve array. */
2136 BLI_assert(start_index <= this->fcurve_array_num);
2137 }
2138
2139 /* Recompute fcurves' group pointers. */
2140 {
2141 for (FCurve *fcurve : this->fcurves()) {
2142 fcurve->grp = nullptr;
2143 }
2144 for (bActionGroup *group : this->channel_groups()) {
2145 for (FCurve *fcurve : group->wrap().fcurves()) {
2146 fcurve->grp = group;
2147 }
2148 }
2149 }
2150}
2151
2153{
2154 return this->channel_bag == nullptr;
2155}
2156
2158{
2159 BLI_assert(!this->is_legacy());
2160
2161 if (this->fcurve_range_length == 0) {
2162 return {};
2163 }
2164
2165 return this->channel_bag->wrap().fcurves().slice(this->fcurve_range_start,
2166 this->fcurve_range_length);
2167}
2168
2170{
2171 BLI_assert(!this->is_legacy());
2172
2173 if (this->fcurve_range_length == 0) {
2174 return {};
2175 }
2176
2177 return this->channel_bag->wrap().fcurves().slice(this->fcurve_range_start,
2178 this->fcurve_range_length);
2179}
2180
2181/* Utility function implementations. */
2182
2184 const slot_handle_t slot_handle)
2185{
2187
2188 if (slot_handle == Slot::unassigned) {
2189 return nullptr;
2190 }
2191
2192 for (const animrig::Layer *layer : action.layers()) {
2193 for (const animrig::Strip *strip : layer->strips()) {
2194 switch (strip->type()) {
2195 case animrig::Strip::Type::Keyframe: {
2196 const animrig::StripKeyframeData &strip_data = strip->data<animrig::StripKeyframeData>(
2197 action);
2198 const animrig::ChannelBag *bag = strip_data.channelbag_for_slot(slot_handle);
2199 if (bag) {
2200 return bag;
2201 }
2202 }
2203 }
2204 }
2205 }
2206
2207 return nullptr;
2208}
2209
2211{
2213 const_cast<const Action &>(action), slot_handle);
2214 return const_cast<animrig::ChannelBag *>(const_bag);
2215}
2216
2218{
2219 BLI_assert(action.is_action_layered());
2221 animrig::ChannelBag *bag = channelbag_for_action_slot(action, slot_handle);
2222 if (!bag) {
2223 return {};
2224 }
2225 return bag->fcurves();
2226}
2227
2229{
2230 BLI_assert(action.is_action_layered());
2232 const animrig::ChannelBag *bag = channelbag_for_action_slot(action, slot_handle);
2233 if (!bag) {
2234 return {};
2235 }
2236 return bag->fcurves();
2237}
2238
2240{
2241 if (act == nullptr) {
2242 return nullptr;
2243 }
2244
2245 Action &action = act->wrap();
2246 if (action.is_action_legacy()) {
2247 return BKE_fcurve_find(
2248 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2249 }
2250
2252 Layer *layer = action.layer(0);
2253 if (!layer) {
2254 return nullptr;
2255 }
2256 Strip *strip = layer->strip(0);
2257 if (!strip) {
2258 return nullptr;
2259 }
2260
2261 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2262
2263 for (ChannelBag *channelbag : strip_data.channelbags()) {
2264 FCurve *fcu = channelbag->fcurve_find(fcurve_descriptor);
2265 if (fcu) {
2266 return fcu;
2267 }
2268 }
2269
2270 return nullptr;
2271}
2272
2274{
2275 return fcurve_find_in_action_slot(adt.action, adt.slot_handle, fcurve_descriptor);
2276}
2277
2279 const slot_handle_t slot_handle,
2280 FCurveDescriptor fcurve_descriptor)
2281{
2282 if (act == nullptr) {
2283 return nullptr;
2284 }
2285
2286 Action &action = act->wrap();
2287 if (action.is_action_legacy()) {
2288 return BKE_fcurve_find(
2289 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2290 }
2291
2292 ChannelBag *cbag = channelbag_for_action_slot(action, slot_handle);
2293 if (!cbag) {
2294 return nullptr;
2295 }
2296 return cbag->fcurve_find(fcurve_descriptor);
2297}
2298
2300 const StringRefNull collection_rna_path,
2301 const StringRefNull data_name)
2302{
2303 BLI_assert(!collection_rna_path.is_empty());
2304
2305 const size_t quoted_name_size = data_name.size() + 1;
2306 char *quoted_name = static_cast<char *>(alloca(quoted_name_size));
2307
2308 if (!fcurve.rna_path) {
2309 return false;
2310 }
2311 /* Skipping names longer than `quoted_name_size` is OK since we're after an exact match. */
2313 fcurve.rna_path, collection_rna_path.c_str(), quoted_name, quoted_name_size))
2314 {
2315 return false;
2316 }
2317 if (quoted_name != data_name) {
2318 return false;
2319 }
2320
2321 return true;
2322}
2323
2325 const slot_handle_t slot_handle,
2326 FunctionRef<bool(const FCurve &fcurve)> predicate)
2327{
2328 BLI_assert(act);
2329
2330 Vector<FCurve *> found;
2331
2332 foreach_fcurve_in_action_slot(act->wrap(), slot_handle, [&](FCurve &fcurve) {
2333 if (predicate(fcurve)) {
2334 found.append(&fcurve);
2335 }
2336 });
2337
2338 return found;
2339}
2340
2342 bAction *act,
2343 const char group[],
2344 PointerRNA *ptr,
2345 FCurveDescriptor fcurve_descriptor)
2346{
2347 if (act == nullptr) {
2348 return nullptr;
2349 }
2350
2352 /* NOTE: for layered actions we require the following:
2353 *
2354 * - `ptr` is non-null.
2355 * - `ptr` has an `owner_id` that already uses `act`.
2356 *
2357 * This isn't for any principled reason, but rather is because adding
2358 * support for layered actions to this function was a fix to make Follow
2359 * Path animation work properly with layered actions (see PR #124353), and
2360 * those are the requirements the Follow Path code conveniently met.
2361 * Moreover those requirements were also already met by the other call sites
2362 * that potentially call this function with layered actions.
2363 *
2364 * Trying to puzzle out what "should" happen when these requirements don't
2365 * hold, or if this is even the best place to handle the layered action
2366 * cases at all, was leading to discussion of larger changes than made sense
2367 * to tackle at that point. */
2368 Action &action = act->wrap();
2369
2370 BLI_assert(ptr != nullptr);
2371 if (ptr == nullptr || ptr->owner_id == nullptr) {
2372 return nullptr;
2373 }
2374 ID &animated_id = *ptr->owner_id;
2375 BLI_assert(get_action(animated_id) == &action);
2376 if (get_action(animated_id) != &action) {
2377 return nullptr;
2378 }
2379
2380 /* Ensure the id has an assigned slot. */
2381 Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
2382 if (!slot) {
2383 /* This means the ID type is not animatable. */
2384 return nullptr;
2385 }
2386
2387 action.layer_keystrip_ensure();
2388
2390 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2391
2392 return &strip_data.channelbag_for_slot_ensure(*slot).fcurve_ensure(bmain, fcurve_descriptor);
2393 }
2394
2395 /* Try to find f-curve matching for this setting.
2396 * - add if not found and allowed to add one
2397 * TODO: add auto-grouping support? how this works will need to be resolved
2398 */
2399 FCurve *fcu = animrig::fcurve_find_in_action(act, fcurve_descriptor);
2400
2401 if (fcu != nullptr) {
2402 return fcu;
2403 }
2404
2405 /* Determine the property subtype if we can. */
2406 std::optional<PropertySubType> prop_subtype = std::nullopt;
2407 if (ptr != nullptr) {
2408 PropertyRNA *resolved_prop;
2409 PointerRNA resolved_ptr;
2411 const bool resolved = RNA_path_resolve_property(
2412 &id_ptr, fcurve_descriptor.rna_path.c_str(), &resolved_ptr, &resolved_prop);
2413 if (resolved) {
2414 prop_subtype = RNA_property_subtype(resolved_prop);
2415 }
2416 }
2417
2418 BLI_assert_msg(!fcurve_descriptor.prop_subtype.has_value(),
2419 "Did not expect a prop_subtype to be passed in. This is fine, but does need some "
2420 "changes to action_fcurve_ensure() to deal with it");
2422 {fcurve_descriptor.rna_path, fcurve_descriptor.array_index, prop_subtype});
2423
2424 if (BLI_listbase_is_empty(&act->curves)) {
2425 fcu->flag |= FCURVE_ACTIVE;
2426 }
2427
2428 if (group) {
2429 bActionGroup *agrp = BKE_action_group_find_name(act, group);
2430
2431 if (agrp == nullptr) {
2432 agrp = action_groups_add_new(act, group);
2433
2434 /* Sync bone group colors if applicable. */
2435 if (ptr && (ptr->type == &RNA_PoseBone) && ptr->data) {
2436 const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
2438 }
2439 }
2440
2441 action_groups_add_channel(act, agrp, fcu);
2442 }
2443 else {
2444 BLI_addtail(&act->curves, fcu);
2445 }
2446
2447 /* New f-curve was added, meaning it's possible that it affects
2448 * dependency graph component which wasn't previously animated.
2449 */
2451
2452 return fcu;
2453}
2454
2456{
2457 BLI_assert(action.is_action_layered());
2458
2459 for (Layer *layer : action.layers()) {
2460 for (Strip *strip : layer->strips()) {
2461 if (!(strip->type() == Strip::Type::Keyframe)) {
2462 continue;
2463 }
2464 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2465 for (ChannelBag *bag : strip_data.channelbags()) {
2466 const bool removed = bag->fcurve_remove(fcu);
2467 if (removed) {
2468 return true;
2469 }
2470 }
2471 }
2472 }
2473 return false;
2474}
2475
2476bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
2477{
2478 if (action.is_action_legacy()) {
2479 return BLI_remlink_safe(&action.curves, &fcurve_to_detach);
2480 }
2481
2482 for (Layer *layer : action.layers()) {
2483 for (Strip *strip : layer->strips()) {
2484 if (!(strip->type() == Strip::Type::Keyframe)) {
2485 continue;
2486 }
2487 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2488 for (ChannelBag *bag : strip_data.channelbags()) {
2489 const bool is_detached = bag->fcurve_detach(fcurve_to_detach);
2490 if (is_detached) {
2491 return true;
2492 }
2493 }
2494 }
2495 }
2496 return false;
2497}
2498
2500 const slot_handle_t action_slot,
2501 FCurve &fcurve_to_attach,
2502 std::optional<StringRefNull> group_name)
2503{
2505 BLI_addtail(&action.curves, &fcurve_to_attach);
2506 return;
2507 }
2508
2509 Slot *slot = action.slot_for_handle(action_slot);
2510 BLI_assert(slot);
2511 if (!slot) {
2512 printf("Cannot find slot handle %d on Action %s, unable to attach F-Curve %s[%d] to it!\n",
2513 action_slot,
2514 action.id.name + 2,
2515 fcurve_to_attach.rna_path,
2516 fcurve_to_attach.array_index);
2517 return;
2518 }
2519
2520 action.layer_keystrip_ensure();
2521 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2522 ChannelBag &cbag = strip_data.channelbag_for_slot_ensure(*slot);
2523 cbag.fcurve_append(fcurve_to_attach);
2524
2525 if (group_name) {
2526 bActionGroup &group = cbag.channel_group_ensure(*group_name);
2527 cbag.fcurve_assign_to_channel_group(fcurve_to_attach, group);
2528 }
2529}
2530
2532 const slot_handle_t action_slot_dst,
2533 Action &action_src,
2534 FCurve &fcurve)
2535{
2536 /* Store the group name locally, as the group will be removed if this was its
2537 * last F-Curve. */
2538 std::optional<std::string> group_name;
2539 if (fcurve.grp) {
2540 group_name = fcurve.grp->name;
2541 }
2542
2543 const bool is_detached = action_fcurve_detach(action_src, fcurve);
2544 BLI_assert(is_detached);
2545 UNUSED_VARS_NDEBUG(is_detached);
2546
2547 action_fcurve_attach(action_dst, action_slot_dst, fcurve, group_name);
2548}
2549
2550bool ChannelBag::fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
2551{
2552 if (this->channel_groups().first_index_try(&to_group) == -1) {
2553 return false;
2554 }
2555
2556 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2557 if (fcurve_index == -1) {
2558 return false;
2559 }
2560
2561 if (fcurve.grp == &to_group) {
2562 return true;
2563 }
2564
2565 /* Remove fcurve from old group, if it belongs to one. */
2566 if (fcurve.grp != nullptr) {
2567 fcurve.grp->fcurve_range_length--;
2568 if (fcurve.grp->fcurve_range_length == 0) {
2569 const int group_index = this->channel_groups().first_index_try(fcurve.grp);
2570 this->channel_group_remove_raw(group_index);
2571 }
2572 this->restore_channel_group_invariants();
2573 }
2574
2575 array_shift_range(this->fcurve_array,
2576 this->fcurve_array_num,
2577 fcurve_index,
2578 fcurve_index + 1,
2579 to_group.fcurve_range_start + to_group.fcurve_range_length);
2580 to_group.fcurve_range_length++;
2581
2582 this->restore_channel_group_invariants();
2583
2584 return true;
2585}
2586
2587bool ChannelBag::fcurve_ungroup(FCurve &fcurve)
2588{
2589 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2590 if (fcurve_index == -1) {
2591 return false;
2592 }
2593
2594 if (fcurve.grp == nullptr) {
2595 return true;
2596 }
2597
2598 bActionGroup *old_group = fcurve.grp;
2599
2600 array_shift_range(this->fcurve_array,
2601 this->fcurve_array_num,
2602 fcurve_index,
2603 fcurve_index + 1,
2604 this->fcurve_array_num - 1);
2605
2606 old_group->fcurve_range_length--;
2607 if (old_group->fcurve_range_length == 0) {
2608 const int old_group_index = this->channel_groups().first_index_try(old_group);
2609 this->channel_group_remove_raw(old_group_index);
2610 }
2611
2612 this->restore_channel_group_invariants();
2613
2614 return true;
2615}
2616
2618 Action &action,
2619 const slot_handle_t slot_handle,
2620 ID *primary_id)
2621{
2623 if (primary_id && get_action(*primary_id) == &action) {
2624 return primary_id;
2625 }
2626 return nullptr;
2627 }
2628
2629 Slot *slot = action.slot_for_handle(slot_handle);
2630 if (slot == nullptr) {
2631 return nullptr;
2632 }
2633
2634 blender::Span<ID *> users = slot->users(bmain);
2635 if (users.size() == 1) {
2636 /* We only do this for `users.size() == 1` and not `users.size() >= 1`
2637 * because when there's more than one user it's ambiguous which user we
2638 * should return, and that would be unpredictable for end users of Blender.
2639 * We also expect that to be a corner case anyway. So instead we let that
2640 * case either get disambiguated by the primary ID in the case below, or
2641 * return null. */
2642 return users[0];
2643 }
2644 if (users.contains(primary_id)) {
2645 return primary_id;
2646 }
2647
2648 return nullptr;
2649}
2650
2651ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
2652{
2653 blender::Span<ID *> users = slot.users(bmain);
2654 if (users.is_empty()) {
2655 return nullptr;
2656 }
2657 if (users.contains(primary_id)) {
2658 return primary_id;
2659 }
2660 return users[0];
2661}
2662
2663slot_handle_t first_slot_handle(const ::bAction &dna_action)
2664{
2665 const Action &action = dna_action.wrap();
2666 if (action.slot_array_num == 0) {
2667 return Slot::unassigned;
2668 }
2669 return action.slot_array[0]->handle;
2670}
2671
2673{
2674 if (action.is_action_legacy()) {
2675 return;
2676 }
2677 if (action.layers().is_empty()) {
2678 return;
2679 }
2680 BLI_assert(action.layers().size() == 1);
2681
2683}
2684
2686{
2687 if (layer.strips().is_empty()) {
2688 return;
2689 }
2690 BLI_assert(layer.strips().size() == 1);
2691
2692 assert_baklava_phase_1_invariants(*layer.strip(0));
2693}
2694
2696{
2697 UNUSED_VARS_NDEBUG(strip);
2698 BLI_assert(strip.type() == Strip::Type::Keyframe);
2699 BLI_assert(strip.is_infinite());
2700 BLI_assert(strip.frame_offset == 0.0);
2701}
2702
2703Action *convert_to_layered_action(Main &bmain, const Action &legacy_action)
2704{
2705 if (!legacy_action.is_action_legacy()) {
2706 return nullptr;
2707 }
2708
2709 std::string suffix = "_layered";
2710 /* In case the legacy action has a long name it is shortened to make space for the suffix. */
2711 char legacy_name[MAX_ID_NAME - 10];
2712 /* Offsetting the id.name to remove the ID prefix (AC) which gets added back later. */
2713 STRNCPY_UTF8(legacy_name, legacy_action.id.name + 2);
2714
2715 const std::string layered_action_name = std::string(legacy_name) + suffix;
2716 bAction *dna_action = BKE_action_add(&bmain, layered_action_name.c_str());
2717
2718 Action &converted_action = dna_action->wrap();
2719 Slot &slot = converted_action.slot_add();
2720 Layer &layer = converted_action.layer_add(legacy_action.id.name);
2721 Strip &strip = layer.strip_add(converted_action, Strip::Type::Keyframe);
2722 BLI_assert(strip.data<StripKeyframeData>(converted_action).channelbag_array_num == 0);
2723 ChannelBag *bag = &strip.data<StripKeyframeData>(converted_action).channelbag_for_slot_add(slot);
2724
2725 const int fcu_count = BLI_listbase_count(&legacy_action.curves);
2726 bag->fcurve_array = MEM_cnew_array<FCurve *>(fcu_count, "Convert to layered action");
2727 bag->fcurve_array_num = fcu_count;
2728
2729 int i = 0;
2730 blender::Map<FCurve *, FCurve *> old_new_fcurve_map;
2731 LISTBASE_FOREACH_INDEX (FCurve *, fcu, &legacy_action.curves, i) {
2732 bag->fcurve_array[i] = BKE_fcurve_copy(fcu);
2733 bag->fcurve_array[i]->grp = nullptr;
2734 old_new_fcurve_map.add(fcu, bag->fcurve_array[i]);
2735 }
2736
2737 LISTBASE_FOREACH (bActionGroup *, group, &legacy_action.groups) {
2738 /* The resulting group might not have the same name, because the legacy system allowed
2739 * duplicate names while the new system ensures uniqueness. */
2740 bActionGroup &converted_group = bag->channel_group_create(group->name);
2741 LISTBASE_FOREACH (FCurve *, fcu, &group->channels) {
2742 if (fcu->grp != group) {
2743 /* Since the group listbase points to the action listbase, it won't stop iterating when
2744 * reaching the end of the group but iterate to the end of the action FCurves. */
2745 break;
2746 }
2747 FCurve *new_fcurve = old_new_fcurve_map.lookup(fcu);
2748 bag->fcurve_assign_to_channel_group(*new_fcurve, converted_group);
2749 }
2750 }
2751
2752 return &converted_action;
2753}
2754
2760static void clone_slot(Slot &from, Slot &to)
2761{
2762 ActionSlotRuntimeHandle *runtime = to.runtime;
2763 slot_handle_t handle = to.handle;
2764 *reinterpret_cast<ActionSlot *>(&to) = *reinterpret_cast<ActionSlot *>(&from);
2765 to.runtime = runtime;
2766 to.handle = handle;
2767}
2768
2769void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_action)
2770{
2771 BLI_assert(from_action.slots().contains(&source_slot));
2772 BLI_assert(&from_action != &to_action);
2773
2774 /* No merging of strips or layers is handled. All data is put into the assumed single strip. */
2777
2778 StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data<StripKeyframeData>(
2779 from_action);
2780 StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
2781 to_action);
2782
2783 Slot &target_slot = to_action.slot_add();
2784 clone_slot(source_slot, target_slot);
2785 slot_name_ensure_unique(to_action, target_slot);
2786
2787 ChannelBag *channel_bag = from_strip_data.channelbag_for_slot(source_slot.handle);
2788 BLI_assert(channel_bag != nullptr);
2789 channel_bag->slot_handle = target_slot.handle;
2790 grow_array_and_append<ActionChannelBag *>(
2791 &to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channel_bag);
2792 int index = from_strip_data.find_channelbag_index(*channel_bag);
2793 shrink_array_and_remove<ActionChannelBag *>(
2794 &from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
2795
2796 /* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */
2797 for (ID *user : source_slot.users(bmain)) {
2798 const auto assign_other_action =
2799 [&](bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_name) -> bool {
2800 /* Only reassign if the reference is actually from the same action. Could be from a different
2801 * action when using the NLA or action constraints. */
2802 if (action_ptr_ref != &from_action) {
2803 return true;
2804 }
2805
2806 { /* Assign the Action. */
2807 const bool assign_ok = generic_assign_action(
2808 *user, &to_action, action_ptr_ref, slot_handle_ref, slot_name);
2809 BLI_assert_msg(assign_ok, "Expecting slotted Actions to always be assignable");
2810 UNUSED_VARS_NDEBUG(assign_ok);
2811 }
2812 { /* Assign the Slot. */
2814 &target_slot, *user, action_ptr_ref, slot_handle_ref, slot_name);
2815 BLI_assert(result == ActionSlotAssignmentResult::OK);
2816 UNUSED_VARS_NDEBUG(result);
2817 }
2818 return true;
2819 };
2820 foreach_action_slot_use_with_references(*user, assign_other_action);
2821 }
2822
2823 from_action.slot_remove(source_slot);
2824}
2825
2826} // namespace blender::animrig
Functions and classes to work with Actions.
Functionality to iterate an Action in various ways.
Functions for backward compatibility with the legacy Action API.
Functions to work with AnimData.
Functions to modify FCurves.
Blender kernel action and pose functionality.
void action_group_colors_set_from_posebone(bActionGroup *grp, const bPoseChannel *pchan)
void action_groups_add_channel(bAction *act, bActionGroup *agrp, FCurve *fcurve)
bAction * BKE_action_add(Main *bmain, const char name[])
bActionGroup * action_groups_add_new(bAction *act, const char name[])
bActionGroup * BKE_action_group_find_name(bAction *act, const char name[])
bool BKE_animdata_action_ensure_idroot(const ID *owner, bAction *action)
Definition anim_data.cc:232
AnimData * BKE_animdata_ensure_id(ID *id)
Definition anim_data.cc:103
bool id_can_have_animdata(const ID *id)
Definition anim_data.cc:79
bool BKE_animdata_action_editable(const AnimData *adt)
Definition anim_data.cc:224
AnimData * BKE_animdata_from_id(const ID *id)
Definition anim_data.cc:89
FCurve * BKE_fcurve_copy(const FCurve *fcu)
bool BKE_fcurve_is_keyframable(const FCurve *fcu)
FCurve * BKE_fcurve_find(ListBase *list, const char rna_path[], int array_index)
void BKE_fcurve_free(FCurve *fcu)
bool BKE_fcurve_calc_range(const FCurve *fcu, float *r_min, float *r_max, bool selected_keys_only)
void id_us_plus(ID *id)
Definition lib_id.cc:351
void id_us_clear_real(ID *id)
Definition lib_id.cc:324
void id_us_min(ID *id)
Definition lib_id.cc:359
#define FOREACH_MAIN_LISTBASE_ID_END
Definition BKE_main.hh:469
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id)
Definition BKE_main.hh:463
#define FOREACH_MAIN_LISTBASE_END
Definition BKE_main.hh:481
#define FOREACH_MAIN_LISTBASE_BEGIN(_bmain, _lb)
Definition BKE_main.hh:474
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:125
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
BLI_INLINE bool BLI_listbase_is_empty(const struct ListBase *lb)
#define LISTBASE_FOREACH(type, var, list)
#define LISTBASE_FOREACH_INDEX(type, var, list, index_var)
bool BLI_remlink_safe(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:153
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
int BLI_listbase_count(const struct ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
MINLINE float max_ff(float a, float b)
MINLINE float min_ff(float a, float b)
MINLINE int compare_ff(float a, float b, float max_diff)
bool bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, size_t result_maxncpy)
Definition string.c:517
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STRNCPY_UTF8(dst, src)
size_t void BLI_uniquename_cb(UniquenameCheckCallback unique_check, void *arg, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(1
#define ARRAY_SIZE(arr)
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
#define STREQ(a, b)
#define DATA_(msgid)
void DEG_relations_tag_update(Main *bmain)
#define MAX_ID_NAME
Definition DNA_ID.h:377
ID_Type
@ AGRP_SELECTED
@ ACT_FRAME_RANGE
@ ACT_CYCLIC
struct ActionSlotRuntimeHandle ActionSlotRuntimeHandle
@ FCM_EXTRAPOLATE_NONE
@ FCM_LIMIT_XMIN
@ FCM_LIMIT_XMAX
eInsertKeyFlags
@ FMODIFIER_TYPE_CYCLES
@ FMODIFIER_TYPE_LIMITS
@ FCURVE_ACTIVE
#define DNA_struct_default_get(struct_name)
#define DNA_struct_default_alloc(struct_name)
#define MAXFRAMEF
#define MINAFRAMEF
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue channels(Deprecated)") DefNode(ShaderNode
Internal C++ functions to deal with Actions, Slots, and their runtime data.
Provides wrapper around system-specific atomic primitives, and some extensions (faked-atomic operatio...
static bool visit_strip(NlaStrip *strip, blender::FunctionRef< bool(NlaStrip *)> callback)
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:271
const Value & lookup(const Key &key) const
Definition BLI_map.hh:506
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
constexpr bool is_empty() const
Definition BLI_span.hh:261
constexpr bool is_empty() const
constexpr int64_t size() const
constexpr const char * c_str() const
int64_t first_index_of_try(const T &value) const
void slot_active_set(slot_handle_t slot_handle)
Slot & slot_add_for_id(const ID &animated_id)
float2 get_frame_range_of_slot(slot_handle_t slot_handle) const ATTR_WARN_UNUSED_RESULT
void slot_name_define(Slot &slot, StringRefNull new_name)
Slot * slot_find_by_name(StringRefNull slot_name)
float2 get_frame_range() const ATTR_WARN_UNUSED_RESULT
bool is_cyclic() const ATTR_WARN_UNUSED_RESULT
int strip_keyframe_data_append(StripKeyframeData *strip_data)
const Layer * layer(int64_t index) const
void slot_name_propagate(Main &bmain, const Slot &slot)
int64_t find_slot_index(const Slot &slot) const
const Slot * slot(int64_t index) const
blender::Span< const Layer * > layers() const
bool has_keyframes(slot_handle_t action_slot_handle) const ATTR_WARN_UNUSED_RESULT
Slot * find_suitable_slot_for(const ID &animated_id)
void strip_keyframe_data_remove_if_unused(int index)
void slot_name_set(Main &bmain, Slot &slot, StringRefNull new_name)
int64_t find_layer_index(const Layer &layer) const
blender::Span< const Slot * > slots() const
bool layer_remove(Layer &layer_to_remove)
float2 get_frame_range_of_keys(bool include_modifiers) const ATTR_WARN_UNUSED_RESULT
bool slot_remove(Slot &slot_to_remove)
Slot * slot_for_handle(slot_handle_t handle)
bool has_single_frame() const ATTR_WARN_UNUSED_RESULT
Span< const StripKeyframeData * > strip_keyframe_data() const
bool is_slot_animated(slot_handle_t slot_handle) const
void slot_setup_for_id(Slot &slot, const ID &animated_id)
Layer & layer_add(std::optional< StringRefNull > name)
void fcurve_move(FCurve &fcurve, int to_fcurve_index)
bActionGroup & channel_group_create(StringRefNull name)
const bActionGroup * channel_group(int64_t index) const
bActionGroup & channel_group_ensure(StringRefNull name)
void channel_group_move(bActionGroup &group, int to_group_index)
const FCurve * fcurve(int64_t index) const
const bActionGroup * channel_group_find(StringRef name) const
bool fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
FCurve & fcurve_ensure(Main *bmain, FCurveDescriptor fcurve_descriptor)
bool fcurve_detach(FCurve &fcurve_to_detach)
FCurve * fcurve_create_unique(Main *bmain, FCurveDescriptor fcurve_descriptor)
void fcurve_remove_by_index(int64_t fcurve_array_index)
const FCurve * fcurve_find(FCurveDescriptor fcurve_descriptor) const
FCurve & fcurve_create(Main *bmain, FCurveDescriptor fcurve_descriptor)
bool channel_group_remove(bActionGroup &group)
blender::Span< const bActionGroup * > channel_groups() const
blender::Span< const FCurve * > fcurves() const
bool fcurve_remove(FCurve &fcurve_to_remove)
void fcurve_detach_by_index(int64_t fcurve_array_index)
int channel_group_containing_index(int fcurve_array_index)
bool strip_remove(Action &owning_action, Strip &strip)
void slot_data_remove(Action &owning_action, slot_handle_t slot_handle)
blender::Span< const Strip * > strips() const
Layer * duplicate_with_shallow_strip_copies(StringRefNull allocation_name) const
const Strip * strip(int64_t index) const
Strip & strip_add(Action &owning_action, Strip::Type strip_type)
int64_t find_strip_index(const Strip &strip) const
std::string name_prefix_for_idtype() const
static void users_invalidate(Main &bmain)
static constexpr int name_length_max
void users_add(ID &animated_id)
bool is_suitable_for(const ID &animated_id) const
static constexpr int name_length_min
StringRefNull name_without_prefix() const
Span< ID * > users(Main &bmain) const
static constexpr slot_handle_t unassigned
void users_remove(ID &animated_id)
const ChannelBag * channelbag(int64_t index) const
ChannelBag & channelbag_for_slot_ensure(const Slot &slot)
SingleKeyingResult keyframe_insert(Main *bmain, const Slot &slot, FCurveDescriptor fcurve_descriptor, float2 time_value, const KeyframeSettings &settings, eInsertKeyFlags insert_key_flags=INSERTKEY_NOFLAGS)
void slot_data_remove(slot_handle_t slot_handle)
bool channelbag_remove(ChannelBag &channelbag_to_remove)
const ChannelBag * channelbag_for_slot(const Slot &slot) const
blender::Span< const ChannelBag * > channelbags() const
static constexpr Strip::Type TYPE
int64_t find_channelbag_index(const ChannelBag &channelbag) const
ChannelBag & channelbag_for_slot_add(const Slot &slot)
void slot_data_remove(Action &owning_action, slot_handle_t slot_handle)
const T & data(const Action &owning_action) const
#define printf
int users
#define GS(x)
Definition iris.cc:202
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
void *(* MEM_dupallocN)(const void *vmemh)
Definition mallocn.cc:39
void rebuild_slot_user_cache(Main &bmain)
Vector< const FCurve * > fcurves_all(const bAction *action)
bool action_treat_as_legacy(const bAction &action)
void action_fcurve_attach(Action &action, slot_handle_t action_slot, FCurve &fcurve_to_attach, std::optional< StringRefNull > group_name)
void foreach_fcurve_in_action_slot(Action &action, slot_handle_t handle, FunctionRef< void(FCurve &fcurve)> callback)
bool action_fcurve_remove(Action &action, FCurve &fcu)
static void shrink_array_and_swap_remove(T **array, int *num, const int index)
void assert_baklava_phase_1_invariants(const Action &action)
static void strip_ptr_destructor(ActionStrip **dna_strip_ptr)
bool fcurve_matches_collection_path(const FCurve &fcurve, StringRefNull collection_rna_path, StringRefNull data_name)
static bool is_id_using_action_slot(const ID &animated_id, const Action &action, const slot_handle_t slot_handle)
static animrig::Layer & ActionLayer_alloc()
bool foreach_action_slot_use_with_references(ID &animated_id, FunctionRef< bool(bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_name)> callback)
static void slot_ptr_destructor(ActionSlot **dna_slot_ptr)
static void array_shift_range(T *array, const int num, const int range_start, const int range_end, const int to)
Vector< FCurve * > fcurves_in_action_slot_filtered(bAction *act, slot_handle_t slot_handle, FunctionRef< bool(const FCurve &fcurve)> predicate)
Slot * assign_action_ensure_slot_for_keying(Action &action, ID &animated_id)
FCurve * action_fcurve_ensure(Main *bmain, bAction *act, const char group[], PointerRNA *ptr, FCurveDescriptor fcurve_descriptor)
FCurve * fcurve_find_in_assigned_slot(AnimData &adt, FCurveDescriptor fcurve_descriptor)
void action_fcurve_move(Action &action_dst, slot_handle_t action_slot_dst, Action &action_src, FCurve &fcurve)
Action & action_add(Main &bmain, StringRefNull name)
static void channelbag_ptr_destructor(ActionChannelBag **dna_channelbag_ptr)
static void layer_ptr_destructor(ActionLayer **dna_layer_ptr)
slot_handle_t first_slot_handle(const ::bAction &dna_action)
static void grow_array_and_append(T **array, int *num, T item)
static float2 get_frame_range_of_fcurves(Span< const FCurve * > fcurves, bool include_modifiers)
ID * action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
const FCurve * fcurve_find(Span< const FCurve * > fcurves, FCurveDescriptor fcurve_descriptor)
ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign, ID &animated_id, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_name) ATTR_WARN_UNUSED_RESULT
FCurve * fcurve_find_in_action_slot(bAction *act, slot_handle_t slot_handle, FCurveDescriptor fcurve_descriptor)
bool generic_assign_action(ID &animated_id, bAction *action_to_assign, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_name)
ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id)
SingleKeyingResult insert_vert_fcurve(FCurve *fcu, const float2 position, const KeyframeSettings &settings, eInsertKeyFlags flag)
Main Key-framing API call.
Span< FCurve * > fcurves_for_action_slot(Action &action, slot_handle_t slot_handle)
ID * action_slot_get_id_for_keying(Main &bmain, Action &action, slot_handle_t slot_handle, ID *primary_id)
static void shrink_array_and_remove(T **array, int *num, const int index)
bool is_action_assignable_to(const bAction *dna_action, ID_Type id_code) ATTR_WARN_UNUSED_RESULT
static void grow_array_and_insert(T **array, int *num, const int index, T item)
decltype(::ActionSlot::handle) slot_handle_t
Action * get_action(ID &animated_id)
bool assign_tmpaction(bAction *action, OwnedAnimData owned_adt)
static void fcurve_ptr_noop_destructor(FCurve **)
static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
Action * convert_to_layered_action(Main &bmain, const Action &legacy_action)
bool unassign_action(ID &animated_id)
ActionSlotAssignmentResult generic_assign_action_slot_handle(slot_handle_t slot_handle_to_assign, ID &animated_id, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_name) ATTR_WARN_UNUSED_RESULT
bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
bool assign_action(bAction *action, ID &animated_id)
std::optional< std::pair< Action *, Slot * > > get_action_slot_pair(ID &animated_id)
FCurve * fcurve_find_in_action(bAction *act, FCurveDescriptor fcurve_descriptor)
static void grow_array(T **array, int *num, const int add_num)
bool key_insertion_may_create_fcurve(eInsertKeyFlags insert_key_flags)
static void slot_name_ensure_unique(Action &action, Slot &slot)
static void shrink_array(T **array, int *num, const int shrink_num)
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
const animrig::ChannelBag * channelbag_for_action_slot(const Action &action, slot_handle_t slot_handle)
void move_slot(Main &bmain, Slot &slot, Action &from_action, Action &to_action)
bool foreach_action_slot_use(const ID &animated_id, FunctionRef< bool(const Action &action, slot_handle_t slot_handle)> callback)
static void clone_slot(Slot &from, Slot &to)
FCurve * create_fcurve_for_channel(FCurveDescriptor fcurve_descriptor)
void remove_index(T **items, int *items_num, int *active_index, const int index, void(*destruct_item)(T *))
void clear(T **items, int *items_num, int *active_index, void(*destruct_item)(T *))
void uninitialized_relocate_n(T *src, int64_t n, T *dst)
void uninitialized_move_n(T *src, int64_t n, T *dst)
float wrap(float value, float max, float min)
Definition node_math.h:71
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)
static void unique_name(bNode *node)
PropertySubType RNA_property_subtype(PropertyRNA *prop)
PointerRNA RNA_id_pointer_create(ID *id)
bool RNA_path_resolve_property(const PointerRNA *ptr, const char *path, PointerRNA *r_ptr, PropertyRNA **r_prop)
Definition rna_path.cc:553
#define min(a, b)
Definition sort.c:32
__int64 int64_t
Definition stdint.h:89
unsigned char uint8_t
Definition stdint.h:78
signed char int8_t
Definition stdint.h:75
struct FCurve ** fcurve_array
struct bActionGroup ** group_array
struct ActionStrip ** strip_array
ActionSlotRuntimeHandle * runtime
struct ActionChannelBag ** channelbag_array
bAction * action
int32_t slot_handle
char tmp_slot_name[66]
int32_t tmp_slot_handle
bAction * tmpact
char slot_name[66]
bActionGroup * grp
char * rna_path
int array_index
Definition DNA_ID.h:413
char name[66]
Definition DNA_ID.h:425
bool is_action_slot_to_id_map_dirty
Definition BKE_main.hh:205
AnimData & adt
ID * owner_id
Definition RNA_types.hh:40
StructRNA * type
Definition RNA_types.hh:41
void * data
Definition RNA_types.hh:42
struct ActionChannelBag * channel_bag
struct ActionSlot ** slot_array
ListBase curves
struct ActionStripKeyframeData ** strip_keyframe_data_array
int32_t last_slot_handle
float frame_start
struct ActionLayer ** layer_array
int strip_keyframe_data_array_num
int layer_active_index
ListBase groups
std::optional< blender::StringRefNull > channel_group
std::optional< PropertySubType > prop_subtype
float xmax
float xmin
PointerRNA * ptr
Definition wm_files.cc:4126