Blender V5.0
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
8
9#include "DNA_action_types.h"
10#include "DNA_anim_types.h"
11#include "DNA_array_utils.hh"
12#include "DNA_defaults.h"
13#include "DNA_scene_types.h"
14
15#include "BLI_listbase.h"
16#include "BLI_map.hh"
17#include "BLI_math_base.h"
18#include "BLI_string.h"
19#include "BLI_string_utf8.h"
20#include "BLI_string_utils.hh"
21#include "BLI_utildefines.h"
22
23#include "BKE_action.hh"
24#include "BKE_anim_data.hh"
25#include "BKE_fcurve.hh"
26#include "BKE_lib_id.hh"
27#include "BKE_library.hh"
28#include "BKE_main.hh"
29#include "BKE_nla.hh"
30#include "BKE_report.hh"
31
32#include "RNA_access.hh"
33#include "RNA_path.hh"
34#include "RNA_prototypes.hh"
35
36#include "MEM_guardedalloc.h"
37
38#include "BLT_translation.hh"
39
40#include "DEG_depsgraph.hh"
42
43#include "ANIM_action.hh"
45#include "ANIM_action_legacy.hh"
46#include "ANIM_animdata.hh"
47#include "ANIM_fcurve.hh"
48
49#include "CLG_log.h"
50
51#include "action_runtime.hh"
52
53#include <cstdio>
54#include <cstring>
55
56static CLG_LogRef LOG = {"anim.action"};
57
58namespace blender::animrig {
59
60namespace {
68constexpr const char *slot_default_name = "Slot";
69
73constexpr const char *slot_untyped_prefix = "XX";
74
75constexpr const char *layer_default_name = "Layer";
76
77} // namespace
78
80{
82 return layer->wrap();
83}
84
85/* Copied from source/blender/blenkernel/intern/grease_pencil.cc.
86 * Keep an eye on DNA_array_utils.hh; we may want to move these functions in there. */
87template<typename T> static void grow_array(T **array, int *num, const int add_num)
88{
89 BLI_assert(add_num > 0);
90 const int new_array_num = *num + add_num;
91 T *new_array = MEM_calloc_arrayN<T>(new_array_num, "animrig::action/grow_array");
92
95
96 *array = new_array;
97 *num = new_array_num;
98}
99
100template<typename T> static void grow_array_and_append(T **array, int *num, T item)
101{
102 grow_array(array, num, 1);
103 (*array)[*num - 1] = item;
104}
105
106template<typename T>
107static void grow_array_and_insert(T **array, int *num, const int index, T item)
108{
109 BLI_assert(index >= 0 && index <= *num);
110 const int new_array_num = *num + 1;
111 T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
112
113 blender::uninitialized_relocate_n(*array, index, new_array);
114 new_array[index] = item;
115 blender::uninitialized_relocate_n(*array + index, *num - index, new_array + index + 1);
116
118
119 *array = new_array;
120 *num = new_array_num;
121}
122
123template<typename T> static void shrink_array(T **array, int *num, const int shrink_num)
124{
125 BLI_assert(shrink_num > 0);
126 const int new_array_num = *num - shrink_num;
127 if (new_array_num == 0) {
129 *array = nullptr;
130 *num = 0;
131 return;
132 }
133
134 T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
135
136 blender::uninitialized_move_n(*array, new_array_num, new_array);
138
139 *array = new_array;
140 *num = new_array_num;
141}
142
143template<typename T> static void shrink_array_and_remove(T **array, int *num, const int index)
144{
145 BLI_assert(index >= 0 && index < *num);
146 const int new_array_num = *num - 1;
147 T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
148
149 blender::uninitialized_move_n(*array, index, new_array);
150 blender::uninitialized_move_n(*array + index + 1, *num - index - 1, new_array + index);
152
153 *array = new_array;
154 *num = new_array_num;
155}
156
162template<typename T> static void shrink_array_and_swap_remove(T **array, int *num, const int index)
163{
164 BLI_assert(index >= 0 && index < *num);
165 const int new_array_num = *num - 1;
166 T *new_array = MEM_calloc_arrayN<T>(new_array_num, __func__);
167
168 blender::uninitialized_move_n(*array, index, new_array);
169 if (index < new_array_num) {
170 new_array[index] = (*array)[new_array_num];
171 blender::uninitialized_move_n(*array + index + 1, *num - index - 2, new_array + index + 1);
172 }
174
175 *array = new_array;
176 *num = new_array_num;
177}
178
188template<typename T>
190 T *array, const int num, const int range_start, const int range_end, const int to)
191{
192 BLI_assert(range_start <= range_end);
193 BLI_assert(range_end <= num);
194 BLI_assert(to <= num + range_start - range_end);
196
197 if (ELEM(range_start, range_end, to)) {
198 return;
199 }
200
201 if (to < range_start) {
202 T *start = array + to;
203 T *mid = array + range_start;
204 T *end = array + range_end;
205 std::rotate(start, mid, end);
206 }
207 else {
208 T *start = array + range_start;
209 T *mid = array + range_end;
210 T *end = array + to + range_end - range_start;
211 std::rotate(start, mid, end);
212 }
213}
214
215/* ----- Action implementation ----------- */
216
218{
219 /* The check for emptiness has to include the check for an empty `groups` ListBase because of the
220 * animation filtering code. With the functions `rearrange_action_channels` and
221 * `join_groups_action_temp` the ownership of FCurves is temporarily transferred to the `groups`
222 * ListBase leaving `curves` potentially empty. */
223 return this->layer_array_num == 0 && this->slot_array_num == 0 &&
225}
227{
228 /* This is a valid legacy Action only if there is no layered info. */
229 return this->layer_array_num == 0 && this->slot_array_num == 0;
230}
232{
233 /* This is a valid layered Action if there is ANY layered info (because that
234 * takes precedence) or when there is no legacy info. */
235 return this->layer_array_num > 0 || this->slot_array_num > 0 ||
237}
238
240{
241 return blender::Span<const Layer *>{reinterpret_cast<Layer **>(this->layer_array),
242 this->layer_array_num};
243}
245{
246 return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
247 this->layer_array_num};
248}
249const Layer *Action::layer(const int64_t index) const
250{
251 return &this->layer_array[index]->wrap();
252}
254{
255 return &this->layer_array[index]->wrap();
256}
257
258Layer &Action::layer_add(const std::optional<StringRefNull> name)
259{
260 Layer &new_layer = ActionLayer_alloc();
261 if (name.has_value()) {
262 STRNCPY_UTF8(new_layer.name, name.value().c_str());
263 }
264 else {
265 STRNCPY_UTF8(new_layer.name, DATA_(layer_default_name));
266 }
267
269 this->layer_active_index = this->layer_array_num - 1;
270
271 /* If this is the first layer in this Action, it means that it could have been
272 * used as a legacy Action before. As a result, this->idroot may be non-zero
273 * while it should be zero for layered Actions.
274 *
275 * And since setting this to 0 when it is already supposed to be 0 is fine,
276 * there is no check for whether this is actually the first layer. */
277 this->idroot = 0;
278
279 return new_layer;
280}
281
282static void layer_ptr_destructor(ActionLayer **dna_layer_ptr)
283{
284 Layer &layer = (*dna_layer_ptr)->wrap();
285 MEM_delete(&layer);
286};
287
288bool Action::layer_remove(Layer &layer_to_remove)
289{
290 const int64_t layer_index = this->find_layer_index(layer_to_remove);
291 if (layer_index < 0) {
292 return false;
293 }
294
296 &this->layer_array_num,
297 &this->layer_active_index,
298 layer_index,
300 return true;
301}
302
304{
305 /* Ensure a layer. */
306 Layer *layer;
307 if (this->layers().is_empty()) {
308 layer = &this->layer_add(DATA_(layer_default_name));
309 }
310 else {
311 layer = this->layer(0);
312 }
313
314 /* Ensure a keyframe Strip. */
315 if (layer->strips().is_empty()) {
316 layer->strip_add(*this, Strip::Type::Keyframe);
317 }
318
319 /* Within the limits of Baklava Phase 1, the above code should not have
320 * created more than one layer, or more than one strip on the layer. And if a
321 * layer + strip already existed, that must have been a keyframe strip. */
323}
324
326{
327 for (const int64_t layer_index : this->layers().index_range()) {
328 const Layer *visit_layer = this->layer(layer_index);
329 if (visit_layer == &layer) {
330 return layer_index;
331 }
332 }
333 return -1;
334}
335
337{
338 for (const int64_t slot_index : this->slots().index_range()) {
339 const Slot *visit_slot = this->slot(slot_index);
340 if (visit_slot == &slot) {
341 return slot_index;
342 }
343 }
344 return -1;
345}
346
348{
349 return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
350}
352{
353 return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
354}
355const Slot *Action::slot(const int64_t index) const
356{
357 return &this->slot_array[index]->wrap();
358}
360{
361 return &this->slot_array[index]->wrap();
362}
363
365{
366 const Slot *slot = const_cast<const Action *>(this)->slot_for_handle(handle);
367 return const_cast<Slot *>(slot);
368}
369
371{
372 if (handle == Slot::unassigned) {
373 return nullptr;
374 }
375
376 /* TODO: implement hash-map lookup. */
377 for (const Slot *slot : slots()) {
378 if (slot->handle == handle) {
379 return slot;
380 }
381 }
382 return nullptr;
383}
384
385static void slot_identifier_ensure_unique(Action &action, Slot &slot)
386{
387 auto check_name_is_used = [&](const StringRef name) -> bool {
388 for (const Slot *slot_iter : action.slots()) {
389 if (slot_iter == &slot) {
390 /* Don't compare against the slot that's being renamed. */
391 continue;
392 }
393 if (slot_iter->identifier == name) {
394 return true;
395 }
396 }
397 return false;
398 };
399
400 BLI_uniquename_cb(check_name_is_used, "", '.', slot.identifier, sizeof(slot.identifier));
401}
402
404{
405 this->slot_display_name_define(slot, new_display_name);
406 this->slot_identifier_propagate(bmain, slot);
407}
408
410{
411 BLI_assert_msg(StringRef(new_display_name).size() >= 1,
412 "Action Slot display names must not be empty");
413 BLI_assert_msg(StringRef(slot.identifier).size() >= 2,
414 "Action Slot's existing identifier lacks the two-character type prefix, which "
415 "would make the display name copy meaningless due to early null termination.");
416
417 BLI_strncpy_utf8(slot.identifier + 2, new_display_name.c_str(), ARRAY_SIZE(slot.identifier) - 2);
419}
420
422{
423 slot.idtype = idtype;
424 slot.identifier_ensure_prefix();
426}
427
428void Action::slot_identifier_set(Main &bmain, Slot &slot, const StringRefNull new_identifier)
429{
430 /* TODO: maybe this function should only set the 'identifier without prefix' aka the 'display
431 * name'. That way only `this->id_type` is responsible for the prefix. I (Sybren) think that's
432 * easier to determine when the code is a bit more mature, and we can see what the majority of
433 * the calls to this function actually do/need. */
434
435 this->slot_identifier_define(slot, new_identifier);
436 this->slot_identifier_propagate(bmain, slot);
437}
438
440{
442 StringRef(new_identifier).size() >= Slot::identifier_length_min,
443 "Action Slot identifiers must be large enough for a 2-letter ID code + the display name");
444 STRNCPY_UTF8(slot.identifier, new_identifier.c_str());
446}
447
449{
450 /* Just loop over all animatable IDs in the main database. */
451 ListBase *lb;
452 ID *id;
453 FOREACH_MAIN_LISTBASE_BEGIN (&bmain, lb) {
455 if (!id_can_have_animdata(id)) {
456 /* This ID type cannot have any animation, so ignore all and continue to
457 * the next ID type. */
458 break;
459 }
460
462 if (!adt || adt->action != this) {
463 /* Not animated by this Action. */
464 continue;
465 }
466 if (adt->slot_handle != slot.handle) {
467 /* Not animated by this Slot. */
468 continue;
469 }
470
471 /* Ensure the Slot identifier on the AnimData is correct. */
472 STRNCPY_UTF8(adt->last_slot_identifier, slot.identifier);
473 }
475 }
477}
478
480{
481 for (Slot *slot : slots()) {
482 if (STREQ(slot->identifier, slot_identifier.c_str())) {
483 return slot;
484 }
485 }
486 return nullptr;
487}
488
489Slot &Action::slot_allocate()
490{
491 Slot &slot = *MEM_new<Slot>(__func__);
492 this->last_slot_handle++;
493 BLI_assert_msg(this->last_slot_handle > 0, "Action Slot handle overflow");
494 slot.handle = this->last_slot_handle;
495
496 /* Set the default flags. These cannot be set via the 'DNA defaults' system,
497 * as that would require knowing which bit corresponds with which flag. That's
498 * only known to the C++ wrapper code. */
499 slot.set_expanded(true);
500 return slot;
501}
502
504{
505 Slot &slot = this->slot_allocate();
506
507 /* Assign the default name and the 'untyped' identifier prefix. */
508 STRNCPY_UTF8(slot.identifier, slot_untyped_prefix);
509 BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2);
510
511 /* Append the Slot to the Action. */
513
515
516 /* If this is the first slot in this Action, it means that it could have
517 * been used as a legacy Action before. As a result, this->idroot may be
518 * non-zero while it should be zero for layered Actions.
519 *
520 * And since setting this to 0 when it is already supposed to be 0 is fine,
521 * there is no check for whether this is actually the first layer. */
522 this->idroot = 0;
523
524 return slot;
525}
526
528{
529 Slot &slot = this->slot_add();
530
531 slot.idtype = idtype;
532 slot.identifier_ensure_prefix();
533 BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2);
535
536 /* No need to call anim.slot_identifier_propagate() as nothing will be using
537 * this brand new Slot yet. */
538
539 return slot;
540}
541
542Slot &Action::slot_add_for_id(const ID &animated_id)
543{
544 Slot &slot = this->slot_add();
545 slot.idtype = GS(animated_id.name);
546
547 /* Determine the identifier for this slot, prioritizing transparent
548 * auto-selection when toggling between Actions. That's why the last-used slot
549 * identifier is used here, and the ID name only as fallback. */
550 const AnimData *adt = BKE_animdata_from_id(&animated_id);
551 const StringRefNull last_slot_identifier = adt ? adt->last_slot_identifier : "";
552
553 StringRefNull slot_identifier = last_slot_identifier;
554 if (slot_identifier.is_empty()) {
555 slot_identifier = animated_id.name;
556 }
557
558 this->slot_identifier_define(slot, slot_identifier);
559 /* No need to call anim.slot_identifier_propagate() as nothing will be using
560 * this brand new Slot yet. */
561
562 /* The last-used slot might have had a different ID type through some quirk (changes to linked
563 * data, for example). So better ensure that the identifier prefix is correct on this new slot,
564 * instead of relying for 100% on the old one. */
565 slot.identifier_ensure_prefix();
566
567 return slot;
568}
569
570static void slot_ptr_destructor(ActionSlot **dna_slot_ptr)
571{
572 Slot &slot = (*dna_slot_ptr)->wrap();
573 MEM_delete(&slot);
574};
575
576bool Action::slot_remove(Slot &slot_to_remove)
577{
578 /* Check that this slot belongs to this Action. */
579 const int64_t slot_index = this->find_slot_index(slot_to_remove);
580 if (slot_index < 0) {
581 return false;
582 }
583
584 /* Remove the slot's data from each keyframe strip. */
585 for (StripKeyframeData *strip_data : this->strip_keyframe_data()) {
586 strip_data->slot_data_remove(slot_to_remove.handle);
587 }
588
589 /* Don't bother un-assigning this slot from its users. The slot handle will
590 * not be reused by a new slot anyway. */
591
592 /* Remove the actual slot. */
594 &this->slot_array, &this->slot_array_num, nullptr, slot_index, slot_ptr_destructor);
595 return true;
596}
597
598void Action::slot_move_to_index(Slot &slot, const int to_slot_index)
599{
600 BLI_assert(this->slots().index_range().contains(to_slot_index));
601
602 const int from_slot_index = this->slots().first_index_try(&slot);
603 BLI_assert_msg(from_slot_index >= 0, "Slot not in this action.");
604
606 this->slot_array, this->slot_array_num, from_slot_index, from_slot_index + 1, to_slot_index);
607}
608
610{
611 for (Slot *slot : slots()) {
612 slot->set_active(slot->handle == slot_handle);
613 }
614}
615
617{
618 for (Slot *slot : slots()) {
619 if (slot->is_active()) {
620 return slot;
621 }
622 }
623 return nullptr;
624}
625
626bool Action::is_slot_animated(const slot_handle_t slot_handle) const
627{
628 if (slot_handle == Slot::unassigned) {
629 return false;
630 }
631
632 Span<const FCurve *> fcurves = fcurves_for_action_slot(*this, slot_handle);
633 return !fcurves.is_empty();
634}
635
645
647{
648 BLI_assert(index >= 0 && index < this->strip_keyframe_data_array_num);
649
650 /* Make sure the data isn't being used anywhere. */
651 for (const Layer *layer : this->layers()) {
652 for (const Strip *strip : layer->strips()) {
653 if (strip->type() == Strip::Type::Keyframe && strip->data_index == index) {
654 return;
655 }
656 }
657 }
658
659 /* Free the item to be removed. */
660 MEM_delete<StripKeyframeData>(
661 static_cast<StripKeyframeData *>(this->strip_keyframe_data_array[index]));
662
663 /* Remove the item, swapping in the item at the end of the array. */
665 &this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, index);
666
667 /* Update strips that pointed at the swapped-in item.
668 *
669 * Note that we don't special-case the corner-case where the removed data was
670 * at the end of the array, but it ends up not mattering because then
671 * `old_index == index`. */
672 const int old_index = this->strip_keyframe_data_array_num;
673 for (Layer *layer : this->layers()) {
674 for (Strip *strip : layer->strips()) {
675 if (strip->type() == Strip::Type::Keyframe && strip->data_index == old_index) {
676 strip->data_index = index;
677 }
678 }
679 }
680}
681
683{
684 /* The reinterpret cast is needed because `strip_keyframe_data_array` is for
685 * pointers to the C type `ActionStripKeyframeData`, but we want the C++
686 * wrapper type `StripKeyframeData`. */
688 reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
690}
692{
693 /* The reinterpret cast is needed because `strip_keyframe_data_array` is for
694 * pointers to the C type `ActionStripKeyframeData`, but we want the C++
695 * wrapper type `StripKeyframeData`. */
697 reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
699}
700
702{
704
705 if (this->layers().is_empty()) {
706 return nullptr;
707 }
708
709 return this->layer(0);
710}
711
712void Action::slot_identifier_ensure_prefix(Slot &slot)
713{
716}
717
718void Action::slot_setup_for_id(Slot &slot, const ID &animated_id)
719{
720 if (!ID_IS_EDITABLE(this) || ID_IS_OVERRIDE_LIBRARY(this)) {
721 /* Do not write to linked data. For now, also avoid changing the slot identifier on an
722 * override. Actions cannot have library overrides at the moment, and when they do, this should
723 * actually get designed. For now, it's better to avoid editing data than editing too much. */
724 return;
725 }
726
727 if (slot.has_idtype()) {
728 BLI_assert(slot.idtype == GS(animated_id.name));
729 return;
730 }
731
732 slot.idtype = GS(animated_id.name);
733 this->slot_identifier_ensure_prefix(slot);
734}
735
736bool Action::has_keyframes(const slot_handle_t action_slot_handle) const
737{
738 if (this->is_action_legacy()) {
739 /* Old BKE_action_has_motion(const bAction *act) implementation. */
740 LISTBASE_FOREACH (const FCurve *, fcu, &this->curves) {
741 if (fcu->totvert) {
742 return true;
743 }
744 }
745 return false;
746 }
747
748 for (const FCurve *fcu : fcurves_for_action_slot(*this, action_slot_handle)) {
749 if (fcu->totvert) {
750 return true;
751 }
752 }
753 return false;
754}
755
757{
758 bool found_key = false;
759 float found_key_frame = 0.0f;
760
761 for (const FCurve *fcu : legacy::fcurves_all(this)) {
762 switch (fcu->totvert) {
763 case 0:
764 /* No keys, so impossible to come to a conclusion on this curve alone. */
765 continue;
766 case 1:
767 /* Single key, which is the complex case, so handle below. */
768 break;
769 default:
770 /* Multiple keys, so there is animation. */
771 return false;
772 }
773
774 const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] :
775 fcu->fpt[0].vec[0];
776 if (!found_key) {
777 found_key = true;
778 found_key_frame = this_key_frame;
779 continue;
780 }
781
782 /* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise
783 * with these comparisons. */
784 if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) {
785 /* This key differs from the already-found key, so this Action represents animation. */
786 return false;
787 }
788 }
789
790 /* There is only a single frame if we found at least one key. */
791 return found_key;
792}
793
795{
796 return (this->flag & ACT_FRAME_RANGE) && (this->flag & ACT_CYCLIC);
797}
798
800static float2 get_frame_range_of_fcurves(Span<const FCurve *> fcurves, bool include_modifiers);
801
803{
804 if (this->flag & ACT_FRAME_RANGE) {
805 return {this->frame_start, this->frame_end};
806 }
807
809 return get_frame_range_of_fcurves(all_fcurves, false);
810}
811
813{
814 if (this->flag & ACT_FRAME_RANGE) {
815 return {this->frame_start, this->frame_end};
816 }
817
818 Vector<const FCurve *> legacy_fcurves;
819 Span<const FCurve *> fcurves_to_consider;
820
821 if (this->is_action_layered()) {
822 fcurves_to_consider = fcurves_for_action_slot(*this, slot_handle);
823 }
824 else {
825 legacy_fcurves = legacy::fcurves_all(this);
826 fcurves_to_consider = legacy_fcurves;
827 }
828
829 return get_frame_range_of_fcurves(fcurves_to_consider, false);
830}
831
832float2 Action::get_frame_range_of_keys(const bool include_modifiers) const
833{
834 return get_frame_range_of_fcurves(legacy::fcurves_all(this), include_modifiers);
835}
836
838 const bool include_modifiers)
839{
840 float min = 999999999.0f, max = -999999999.0f;
841 bool foundvert = false, foundmod = false;
842
843 for (const FCurve *fcu : fcurves) {
844 /* if curve has keyframes, consider them first */
845 if (fcu->totvert) {
846 float nmin, nmax;
847
848 /* get extents for this curve
849 * - no "selected only", since this is often used in the backend
850 * - no "minimum length" (we will apply this later), otherwise
851 * single-keyframe curves will increase the overall length by
852 * a phantom frame (#50354)
853 */
854 BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
855
856 /* compare to the running tally */
857 min = min_ff(min, nmin);
858 max = max_ff(max, nmax);
859
860 foundvert = true;
861 }
862
863 /* if include_modifiers is enabled, need to consider modifiers too
864 * - only really care about the last modifier
865 */
866 if ((include_modifiers) && (fcu->modifiers.last)) {
867 FModifier *fcm = static_cast<FModifier *>(fcu->modifiers.last);
868
869 /* only use the maximum sensible limits of the modifiers if they are more extreme */
870 switch (fcm->type) {
871 case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */
872 {
873 FMod_Limits *fmd = static_cast<FMod_Limits *>(fcm->data);
874
875 if (fmd->flag & FCM_LIMIT_XMIN) {
876 min = min_ff(min, fmd->rect.xmin);
877 }
878 if (fmd->flag & FCM_LIMIT_XMAX) {
879 max = max_ff(max, fmd->rect.xmax);
880 }
881 break;
882 }
883 case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */
884 {
885 FMod_Cycles *fmd = static_cast<FMod_Cycles *>(fcm->data);
886
887 if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) {
888 min = MINAFRAMEF;
889 }
890 if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) {
891 max = MAXFRAMEF;
892 }
893 break;
894 }
895 /* TODO: function modifier may need some special limits */
896
897 default: /* all other standard modifiers are on the infinite range... */
898 min = MINAFRAMEF;
899 max = MAXFRAMEF;
900 break;
901 }
902
903 foundmod = true;
904 }
905 }
906
907 if (foundvert || foundmod) {
909 }
910
911 return float2{0.0f, 0.0f};
912}
913
914/* ----- ActionLayer implementation ----------- */
915
917{
918 ActionLayer *copy = MEM_callocN<ActionLayer>(allocation_name.c_str());
919 *copy = *reinterpret_cast<const ActionLayer *>(this);
920
921 /* Make a shallow copy of the Strips, without copying their data. */
923 allocation_name.c_str());
924 for (int i : this->strips().index_range()) {
925 Strip *strip_copy = MEM_new<Strip>(allocation_name.c_str(), *this->strip(i));
926 copy->strip_array[i] = strip_copy;
927 }
928
929 return &copy->wrap();
930}
931
933{
934 for (Strip *strip : this->strips()) {
935 MEM_delete(strip);
936 }
938 this->strip_array_num = 0;
939}
940
942{
943 return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
944 this->strip_array_num};
945}
947{
948 return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
949 this->strip_array_num};
950}
951const Strip *Layer::strip(const int64_t index) const
952{
953 return &this->strip_array[index]->wrap();
954}
956{
957 return &this->strip_array[index]->wrap();
958}
959
960Strip &Layer::strip_add(Action &owning_action, const Strip::Type strip_type)
961{
962 Strip &strip = Strip::create(owning_action, strip_type);
963
964 /* Add the new strip to the strip array. */
966
967 return strip;
968}
969
970static void strip_ptr_destructor(ActionStrip **dna_strip_ptr)
971{
972 Strip &strip = (*dna_strip_ptr)->wrap();
973 MEM_delete(&strip);
974};
975
976bool Layer::strip_remove(Action &owning_action, Strip &strip)
977{
978 const int64_t strip_index = this->find_strip_index(strip);
979 if (strip_index < 0) {
980 return false;
981 }
982
983 const Strip::Type strip_type = strip.type();
984 const int data_index = strip.data_index;
985
987 &this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor);
988
989 /* It's important that we do this *after* removing the strip itself
990 * (immediately above), because otherwise the strip will be found as a
991 * still-existing user of the strip data and thus the strip data won't be
992 * removed even if this strip was the last user. */
993 switch (strip_type) {
994 case Strip::Type::Keyframe:
995 owning_action.strip_keyframe_data_remove_if_unused(data_index);
996 break;
997 }
998
999 return true;
1000}
1001
1003{
1004 for (const int64_t strip_index : this->strips().index_range()) {
1005 const Strip *visit_strip = this->strip(strip_index);
1006 if (visit_strip == &strip) {
1007 return strip_index;
1008 }
1009 }
1010 return -1;
1011}
1012
1013/* ----- ActionSlot implementation ----------- */
1014
1016{
1017 /* Zero-initialize the DNA struct. 'this' is a C++ class, and shouldn't be memset like this. */
1018 memset(static_cast<ActionSlot *>(this), 0, sizeof(ActionSlot));
1019 this->runtime = MEM_new<SlotRuntime>(__func__);
1020}
1021
1022Slot::Slot(const Slot &other) : ActionSlot(other)
1023{
1024 this->runtime = MEM_new<SlotRuntime>(__func__);
1025}
1026
1028{
1029 MEM_delete(this->runtime);
1030}
1031
1033{
1034 BLI_assert(!this->runtime);
1035 this->runtime = MEM_new<SlotRuntime>(__func__);
1036}
1037
1038bool Slot::is_suitable_for(const ID &animated_id) const
1039{
1040 if (!this->has_idtype()) {
1041 /* Without specific ID type set, this Slot can animate any ID. */
1042 return true;
1043 }
1044
1045 /* Check that the ID type is compatible with this slot. */
1046 const int animated_idtype = GS(animated_id.name);
1047 return this->idtype == animated_idtype;
1048}
1049
1051{
1052 return this->idtype != 0;
1053}
1054
1056{
1057 return static_cast<Slot::Flags>(this->slot_flags);
1058}
1060{
1061 return this->slot_flags & uint8_t(Flags::Expanded);
1062}
1063void Slot::set_expanded(const bool expanded)
1064{
1065 if (expanded) {
1066 this->slot_flags |= uint8_t(Flags::Expanded);
1067 }
1068 else {
1069 this->slot_flags &= ~uint8_t(Flags::Expanded);
1070 }
1071}
1072
1074{
1075 return this->slot_flags & uint8_t(Flags::Selected);
1076}
1077void Slot::set_selected(const bool selected)
1078{
1079 if (selected) {
1080 this->slot_flags |= uint8_t(Flags::Selected);
1081 }
1082 else {
1083 this->slot_flags &= ~uint8_t(Flags::Selected);
1084 }
1085}
1086
1088{
1089 return this->slot_flags & uint8_t(Flags::Active);
1090}
1091void Slot::set_active(const bool active)
1092{
1093 if (active) {
1094 this->slot_flags |= uint8_t(Flags::Active);
1095 }
1096 else {
1097 this->slot_flags &= ~uint8_t(Flags::Active);
1098 }
1099}
1100
1102{
1105 }
1106 BLI_assert(this->runtime);
1107 return this->runtime->users.as_span();
1108}
1109
1111{
1112 BLI_assert_msg(this->runtime, "Slot::runtime should always be allocated");
1113 return this->runtime->users;
1114}
1115
1116void Slot::users_add(ID &animated_id)
1117{
1118 BLI_assert(this->runtime);
1119 this->runtime->users.append_non_duplicates(&animated_id);
1120}
1121
1122void Slot::users_remove(ID &animated_id)
1123{
1124 BLI_assert(this->runtime);
1125 Vector<ID *> &users = this->runtime->users;
1126
1127 /* Even though users_add() ensures that there are no duplicates, there's still things like
1128 * pointer swapping etc. that can happen via the foreach-id looping code. That means that the
1129 * entries in the user map are not 100% under control of the user_add() and user_remove()
1130 * function, and thus we cannot assume that there are no duplicates. */
1131 users.remove_if([&](const ID *user) { return user == &animated_id; });
1132}
1133
1135{
1136 bmain.is_action_slot_to_id_map_dirty = true;
1137}
1138
1139std::string Slot::idtype_string() const
1140{
1141 if (!this->has_idtype()) {
1142 return slot_untyped_prefix;
1143 }
1144
1145 char name[3] = {0};
1146 *reinterpret_cast<short *>(name) = this->idtype;
1147 return name;
1148}
1149
1151{
1153 BLI_assert(identifier.size() >= 2);
1154
1155 return identifier.substr(0, 2);
1156}
1157
1159{
1161
1162 /* Avoid accessing an uninitialized part of the string accidentally. */
1163 if (this->identifier[0] == '\0' || this->identifier[1] == '\0') {
1164 return "";
1165 }
1166 return this->identifier + 2;
1167}
1168
1170{
1172
1173 if (StringRef(this->identifier).size() < 2) {
1174 /* The code below would overwrite the trailing 0-byte. */
1175 this->identifier[2] = '\0';
1176 }
1177
1178 if (!this->has_idtype()) {
1179 /* A zero idtype is not going to convert to a two-character string, so we
1180 * need to explicitly assign the default prefix. */
1181 this->identifier[0] = slot_untyped_prefix[0];
1182 this->identifier[1] = slot_untyped_prefix[1];
1183 return;
1184 }
1185
1186 *reinterpret_cast<short *>(this->identifier) = this->idtype;
1187}
1188
1189/* ----- Functions ----------- */
1190
1192{
1193 bAction *dna_action = BKE_action_add(&bmain, name.c_str());
1194 id_us_clear_real(&dna_action->id);
1195 return dna_action->wrap();
1196}
1197
1198bool assign_action(bAction *action, ID &animated_id)
1199{
1200 AnimData *adt = BKE_animdata_ensure_id(&animated_id);
1201 if (!adt) {
1202 return false;
1203 }
1204 return assign_action(action, {animated_id, *adt});
1205}
1206
1207bool assign_action(bAction *action, const OwnedAnimData owned_adt)
1208{
1209 if (!BKE_animdata_action_editable(&owned_adt.adt)) {
1210 /* Cannot remove, otherwise things turn to custard. */
1211 BKE_report(nullptr, RPT_ERROR, "Cannot change action, as it is still being edited in NLA");
1212 return false;
1213 }
1214
1215 return generic_assign_action(owned_adt.owner_id,
1216 action,
1217 owned_adt.adt.action,
1218 owned_adt.adt.slot_handle,
1219 owned_adt.adt.last_slot_identifier);
1220}
1221
1222bool assign_tmpaction(bAction *action, const OwnedAnimData owned_adt)
1223{
1224 return generic_assign_action(owned_adt.owner_id,
1225 action,
1226 owned_adt.adt.tmpact,
1227 owned_adt.adt.tmp_slot_handle,
1228 owned_adt.adt.tmp_last_slot_identifier);
1229}
1230
1231bool unassign_action(ID &animated_id)
1232{
1233 return assign_action(nullptr, animated_id);
1234}
1235
1237{
1238 return assign_action(nullptr, owned_adt);
1239}
1240
1242{
1243 AnimData *adt = BKE_animdata_from_id(&animated_id);
1244 Slot *slot;
1245
1246 /* Find a suitable slot, but be stricter about when to allow searching by name
1247 * than generic_slot_for_autoassign(...). */
1248 if (adt && adt->action == &action) {
1249 /* The slot handle is only valid when this action is already assigned.
1250 * Otherwise it's meaningless. */
1251 slot = action.slot_for_handle(adt->slot_handle);
1252
1253 /* If this Action is already assigned, a search by name is inappropriate, as it might
1254 * re-assign an intentionally-unassigned slot. */
1255 }
1256 else {
1257 /* In this case a by-name search is ok, so defer to generic_slot_for_autoassign(). */
1258 slot = generic_slot_for_autoassign(animated_id, action, adt ? adt->last_slot_identifier : "");
1259 }
1260
1261 /* As a last resort, if there is only one slot and it has no ID type yet, use that. This is what
1262 * gets created for the backwards compatibility RNA API, for example to allow
1263 * `action.fcurves.new()`. Key insertion should use that slot as well. */
1264 if (!slot && action.slots().size() == 1) {
1265 Slot *first_slot = action.slot(0);
1266 if (!first_slot->has_idtype()) {
1267 slot = first_slot;
1268 }
1269 }
1270
1271 /* If no suitable slot was found, create a new one. */
1272 if (!slot || !slot->is_suitable_for(animated_id)) {
1273 slot = &action.slot_add_for_id(animated_id);
1274 }
1275
1276 /* Only try to assign the Action to the ID if it is not already assigned.
1277 * Assignment can fail when the ID is in NLA Tweak mode. */
1278 const bool is_correct_action = adt && adt->action == &action;
1279 if (!is_correct_action && !assign_action(&action, animated_id)) {
1280 return nullptr;
1281 }
1282
1283 const bool is_correct_slot = adt && adt->slot_handle == slot->handle;
1284 if (!is_correct_slot && assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK)
1285 {
1286 /* This should never happen, as a few lines above a new slot is created for
1287 * this ID if the found one wasn't deemed suitable. */
1289 return nullptr;
1290 }
1291
1292 return slot;
1293}
1294
1295static bool is_id_using_action_slot(const ID &animated_id,
1296 const Action &action,
1297 const slot_handle_t slot_handle)
1298{
1299 auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool {
1300 const bool is_used = (&used_action == &action && used_slot_handle == slot_handle);
1301 return !is_used; /* Stop searching when we found a use of this Action+Slot. */
1302 };
1303
1304 const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use);
1305 return !looped_until_end;
1306}
1307
1308bool generic_assign_action(ID &animated_id,
1309 bAction *action_to_assign,
1310 bAction *&action_ptr_ref,
1311 slot_handle_t &slot_handle_ref,
1312 char *slot_identifier)
1313{
1314 BLI_assert(slot_identifier);
1315
1316 if (action_to_assign && legacy::action_treat_as_legacy(*action_to_assign)) {
1317 /* Check that the Action is suitable for this ID type.
1318 * This is only necessary for legacy Actions. */
1319 if (!BKE_animdata_action_ensure_idroot(&animated_id, action_to_assign)) {
1321 nullptr,
1322 RPT_ERROR,
1323 "Could not set action '%s' to animate ID '%s', as it does not have suitably rooted "
1324 "paths for this purpose",
1325 action_to_assign->id.name + 2,
1326 animated_id.name);
1327 return false;
1328 }
1329 }
1330
1331 /* Un-assign any previously-assigned Action first. */
1332 if (action_ptr_ref) {
1333 /* Un-assign the slot. This will always succeed, so no need to check the result. */
1334 if (slot_handle_ref != Slot::unassigned) {
1336 nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1339 }
1340
1341 /* Un-assign the Action itself. */
1342 id_us_min(&action_ptr_ref->id);
1343 action_ptr_ref = nullptr;
1344 }
1345
1346 if (!action_to_assign) {
1347 /* Un-assigning was the point, so the work is done. */
1348 return true;
1349 }
1350
1351 /* Assign the new Action. */
1352 action_ptr_ref = action_to_assign;
1353 id_us_plus(&action_ptr_ref->id);
1354
1355 /* Auto-assign a slot. */
1356 Slot *slot = generic_slot_for_autoassign(animated_id, action_ptr_ref->wrap(), slot_identifier);
1358 slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1361
1362 return true;
1363}
1364
1366 Action &action,
1367 const StringRefNull last_slot_identifier)
1368{
1369 /* The slot-finding code in assign_action_ensure_slot_for_keying() is very
1370 * similar to the code here (differences are documented there). It is very
1371 * likely that changes in the logic here should be applied there as well. */
1372
1373 /* Try the slot identifier, if it is set. */
1374 if (!last_slot_identifier.is_empty()) {
1375 /* If the last-used slot identifier was 'untyped', i.e. started with XX, see if something more
1376 * specific to this ID type exists.
1377 *
1378 * If there is any choice in the matter, the more specific slot is chosen. In other words, in
1379 * this case:
1380 *
1381 * - last_slot_identifier = `XXSlot`
1382 * - both `XXSlot` and `OBSlot` exist on the Action (where `OB` represents the ID type of
1383 * `animated_id`).
1384 *
1385 * the `OBSlot` should be chosen. This means that `XXSlot` NOT being auto-assigned if there is
1386 * an alternative. Since untyped slots are bound on assignment, this design keeps the Action
1387 * as-is, which means that the `XXSlot` remains untyped and thus the user is free to assign
1388 * this to another ID type if desired. */
1389
1390 const bool last_used_identifier_is_typed = last_slot_identifier.substr(0, 2) !=
1391 slot_untyped_prefix;
1392 if (!last_used_identifier_is_typed) {
1393 const std::string with_idtype_prefix = StringRef(animated_id.name, 2) +
1394 last_slot_identifier.substr(2);
1395 Slot *slot = action.slot_find_by_identifier(with_idtype_prefix);
1396 if (slot && slot->is_suitable_for(animated_id)) {
1397 return slot;
1398 }
1399 }
1400
1401 /* See if the actual last-used slot identifier can be matched. */
1402 Slot *slot = action.slot_find_by_identifier(last_slot_identifier);
1403 if (slot && slot->is_suitable_for(animated_id)) {
1404 return slot;
1405 }
1406
1407 /* If the last-used slot identifier was IDSomething, and XXSomething exists (where ID = the
1408 * ID code of the animated ID), fall back to the XX. If slot `IDSomething` existed, the code
1409 * above would have already returned it. */
1410 if (last_used_identifier_is_typed) {
1411 const std::string with_untyped_prefix = StringRef(slot_untyped_prefix) +
1412 last_slot_identifier.substr(2);
1413 Slot *slot = action.slot_find_by_identifier(with_untyped_prefix);
1414 if (slot && slot->is_suitable_for(animated_id)) {
1415 return slot;
1416 }
1417 }
1418 }
1419
1420 /* Search for the ID name (which includes the ID type). */
1421 {
1422 Slot *slot = action.slot_find_by_identifier(animated_id.name);
1423 if (slot && slot->is_suitable_for(animated_id)) {
1424 return slot;
1425 }
1426 }
1427
1428 /* If there is only one slot, and it is not specific to any ID type, use that.
1429 *
1430 * This should only trigger in some special cases, like legacy Actions that were converted to
1431 * slotted Actions by the versioning code, where the legacy Action was never assigned to anything
1432 * (and thus had idroot = 0).
1433 *
1434 * This might seem overly specific, and for convenience of automatically auto-assigning a slot,
1435 * it might be tempting to remove the "slot->has_idtype()" check. However, that would make the
1436 * following workflow significantly more cumbersome:
1437 *
1438 * - Animate `Cube`. This creates `CubeAction` with a single slot `OBCube`.
1439 * - Assign `CubeAction` to `Suzanne`, with the intent of animating both `Cube` and `Suzanne`
1440 * with the same Action.
1441 * - This should **not** auto-assign the `OBCube` slot to `Suzanne`, as that will overwrite any
1442 * property of `Suzanne` with the animated values for the `OBCube` slot.
1443 *
1444 * Recovering from this will be hard, as an undo will revert both the overwriting of properties
1445 * and the assignment of the Action. */
1446 if (action.slots().size() == 1) {
1447 Slot *slot = action.slot(0);
1448 if (!slot->has_idtype()) {
1449 return slot;
1450 }
1451 }
1452
1453 return nullptr;
1454}
1455
1457 ID &animated_id,
1458 bAction *&action_ptr_ref,
1459 slot_handle_t &slot_handle_ref,
1460 char *slot_identifier)
1461{
1462 BLI_assert(slot_identifier);
1463 if (!action_ptr_ref) {
1464 /* No action assigned yet, so no way to assign a slot. */
1466 }
1467
1468 Action &action = action_ptr_ref->wrap();
1469
1470 /* Check that the slot can actually be assigned. */
1471 if (slot_to_assign) {
1472 if (!action.slots().contains(slot_to_assign)) {
1474 }
1475
1476 if (!slot_to_assign->is_suitable_for(animated_id)) {
1478 }
1479 }
1480
1481 Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref);
1482
1483 /* If there was a previously-assigned slot, unassign it first. */
1484 slot_handle_ref = Slot::unassigned;
1485 if (slot_to_unassign) {
1486 /* Make sure that the stored Slot identifier is up to date. The slot identifier might have
1487 * changed in a way that wasn't copied into the ADT yet (for example when the
1488 * Action is linked from another file), so better copy the identifier to be sure
1489 * that it can be transparently reassigned later.
1490 *
1491 * TODO: Replace this with a BLI_assert() that the identifier is as expected, and "simply"
1492 * ensure this identifier is always correct. */
1493 BLI_strncpy_utf8(slot_identifier, slot_to_unassign->identifier, Slot::identifier_length_max);
1494
1495 /* If this was the last use of this slot, remove this ID from its users. */
1496 if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) {
1497 slot_to_unassign->users_remove(animated_id);
1498 }
1499 }
1500
1501 if (!slot_to_assign) {
1503 }
1504
1505 action.slot_setup_for_id(*slot_to_assign, animated_id);
1506 slot_handle_ref = slot_to_assign->handle;
1507 BLI_strncpy_utf8(slot_identifier, slot_to_assign->identifier, Slot::identifier_length_max);
1508 slot_to_assign->users_add(animated_id);
1509
1511}
1512
1514 ID &animated_id,
1515 bAction *&action_ptr_ref,
1516 slot_handle_t &slot_handle_ref,
1517 char *slot_identifier)
1518{
1519 if (slot_handle_to_assign == Slot::unassigned && !action_ptr_ref) {
1520 /* No Action assigned, so no slot was used anyway. Just blindly assign the
1521 * 'unassigned' handle. */
1522 slot_handle_ref = Slot::unassigned;
1524 }
1525
1526 if (!action_ptr_ref) {
1527 /* No Action to verify the slot handle is valid. As the slot handle will be
1528 * completely ignored when re-assigning an Action, better to refuse setting
1529 * it altogether. This will make bugs more obvious. */
1531 }
1532
1533 Slot *slot = action_ptr_ref->wrap().slot_for_handle(slot_handle_to_assign);
1535 slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1536}
1537
1538bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
1539{
1540 if (!dna_action) {
1541 /* Clearing the Action is always possible. */
1542 return true;
1543 }
1544
1545 if (dna_action->idroot == 0) {
1546 /* This is either a never-assigned legacy action, or a layered action. In
1547 * any case, it can be assigned to any ID. */
1548 return true;
1549 }
1550
1551 const animrig::Action &action = dna_action->wrap();
1552 if (legacy::action_treat_as_legacy(action)) {
1553 /* Legacy Actions can only be assigned if their idroot matches. Empty
1554 * Actions are considered both 'layered' and 'legacy' at the same time,
1555 * hence this condition checks for 'not layered' rather than 'legacy'. */
1556 return action.idroot == id_code;
1557 }
1558
1559 return true;
1560}
1561
1563{
1564 AnimData *adt = BKE_animdata_from_id(&animated_id);
1565 if (!adt) {
1567 }
1568
1570 slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->last_slot_identifier);
1571}
1572
1574 Slot *slot_to_assign,
1575 ID &animated_id)
1576{
1577 if (!assign_action(action, animated_id)) {
1579 }
1580 return assign_action_slot(slot_to_assign, animated_id);
1581}
1582
1584 const slot_handle_t slot_handle,
1585 const OwnedAnimData owned_adt)
1586{
1587 if (!assign_tmpaction(action, owned_adt)) {
1589 }
1590 return generic_assign_action_slot_handle(slot_handle,
1591 owned_adt.owner_id,
1592 owned_adt.adt.tmpact,
1593 owned_adt.adt.tmp_slot_handle,
1594 owned_adt.adt.tmp_last_slot_identifier);
1595}
1596
1597Action *get_action(ID &animated_id)
1598{
1599 AnimData *adt = BKE_animdata_from_id(&animated_id);
1600 if (!adt) {
1601 return nullptr;
1602 }
1603 if (!adt->action) {
1604 return nullptr;
1605 }
1606 return &adt->action->wrap();
1607}
1608
1609std::optional<std::pair<Action *, Slot *>> get_action_slot_pair(ID &animated_id)
1610{
1611 AnimData *adt = BKE_animdata_from_id(&animated_id);
1612 if (!adt || !adt->action) {
1613 /* Not animated by any Action. */
1614 return std::nullopt;
1615 }
1616
1617 Action &action = adt->action->wrap();
1618 Slot *slot = action.slot_for_handle(adt->slot_handle);
1619 if (!slot) {
1620 /* Will not receive any animation from this Action. */
1621 return std::nullopt;
1622 }
1623
1624 return std::make_pair(&action, slot);
1625}
1626
1627/* ----- ActionStrip implementation ----------- */
1628
1629Strip &Strip::create(Action &owning_action, const Strip::Type type)
1630{
1631 /* Create the strip. */
1632 ActionStrip *strip = MEM_callocN<ActionStrip>(__func__);
1634 strip->strip_type = int8_t(type);
1635
1636 /* Create the strip's data on the owning Action. */
1637 switch (type) {
1638 case Strip::Type::Keyframe: {
1639 StripKeyframeData *strip_data = MEM_new<StripKeyframeData>(__func__);
1640 strip->data_index = owning_action.strip_keyframe_data_append(strip_data);
1641 break;
1642 }
1643 }
1644
1645 /* This can happen if someone forgets to add a strip type in the `switch`
1646 * above, or if someone is evil and passes an invalid strip type to this
1647 * function. */
1648 BLI_assert_msg(strip->data_index != -1, "Newly created strip has no data.");
1649
1650 return strip->wrap();
1651}
1652
1653bool Strip::is_infinite() const
1654{
1655 return this->frame_start == -std::numeric_limits<float>::infinity() &&
1656 this->frame_end == std::numeric_limits<float>::infinity();
1657}
1658
1659bool Strip::contains_frame(const float frame_time) const
1660{
1661 return this->frame_start <= frame_time && frame_time <= this->frame_end;
1662}
1663
1664bool Strip::is_last_frame(const float frame_time) const
1665{
1666 /* Maybe this needs a more advanced equality check. Implement that when
1667 * we have an actual example case that breaks. */
1668 return this->frame_end == frame_time;
1669}
1670
1671void Strip::resize(const float frame_start, const float frame_end)
1672{
1674 BLI_assert_msg(frame_start < std::numeric_limits<float>::infinity(),
1675 "only the end frame can be at positive infinity");
1676 BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
1677 "only the start frame can be at negative infinity");
1678 this->frame_start = frame_start;
1679 this->frame_end = frame_end;
1680}
1681
1682template<>
1684{
1686
1687 return *owning_action.strip_keyframe_data()[this->data_index];
1688}
1690{
1692
1693 return *owning_action.strip_keyframe_data()[this->data_index];
1694}
1695
1696/* ----- ActionStripKeyframeData implementation ----------- */
1697
1700{
1702 __func__);
1703 Span<const Channelbag *> channelbags_src = other.channelbags();
1704 for (int i : channelbags_src.index_range()) {
1705 this->channelbag_array[i] = MEM_new<animrig::Channelbag>(__func__, *other.channelbag(i));
1706 }
1707}
1708
1710{
1711 for (Channelbag *channelbag_for_slot : this->channelbags()) {
1712 MEM_delete(channelbag_for_slot);
1713 }
1715 this->channelbag_array_num = 0;
1716}
1717
1729{
1730 return &this->channelbag_array[index]->wrap();
1731}
1733{
1734 return &this->channelbag_array[index]->wrap();
1735}
1737{
1738 for (const Channelbag *channels : this->channelbags()) {
1739 if (channels->slot_handle == slot_handle) {
1740 return channels;
1741 }
1742 }
1743 return nullptr;
1744}
1746{
1747 for (int64_t index = 0; index < this->channelbag_array_num; index++) {
1748 if (this->channelbag(index) == &channelbag) {
1749 return index;
1750 }
1751 }
1752 return -1;
1753}
1755{
1756 const auto *const_this = const_cast<const StripKeyframeData *>(this);
1757 const auto *const_channels = const_this->channelbag_for_slot(slot_handle);
1758 return const_cast<Channelbag *>(const_channels);
1759}
1761{
1762 return this->channelbag_for_slot(slot.handle);
1763}
1765{
1766 return this->channelbag_for_slot(slot.handle);
1767}
1768
1773
1775{
1776 BLI_assert_msg(channelbag_for_slot(slot_handle) == nullptr,
1777 "Cannot add channelbag for already-registered slot");
1778 BLI_assert_msg(slot_handle != Slot::unassigned, "Cannot add channelbag for 'unassigned' slot");
1779
1780 Channelbag &channels = MEM_new<ActionChannelbag>(__func__)->wrap();
1781 channels.slot_handle = slot_handle;
1782
1784 &this->channelbag_array, &this->channelbag_array_num, &channels);
1785
1786 return channels;
1787}
1788
1793
1795{
1796 Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
1797 if (channelbag != nullptr) {
1798 return *channelbag;
1799 }
1800 return this->channelbag_for_slot_add(slot_handle);
1801}
1802
1803static void channelbag_ptr_destructor(ActionChannelbag **dna_channelbag_ptr)
1804{
1805 Channelbag &channelbag = (*dna_channelbag_ptr)->wrap();
1806 MEM_delete(&channelbag);
1807};
1808
1810{
1811 const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove);
1812 if (channelbag_index < 0) {
1813 return false;
1814 }
1815
1817 &this->channelbag_array_num,
1818 nullptr,
1819 channelbag_index,
1821
1822 return true;
1823}
1824
1826{
1827 Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
1828 if (!channelbag) {
1829 return;
1830 }
1831 this->channelbag_remove(*channelbag);
1832}
1833
1835 const slot_handle_t target_slot_handle)
1836{
1837 BLI_assert(!this->channelbag_for_slot(target_slot_handle));
1838
1839 const Channelbag *source_cbag = this->channelbag_for_slot(source_slot_handle);
1840 if (!source_cbag) {
1841 return;
1842 }
1843
1844 Channelbag &target_cbag = *MEM_new<animrig::Channelbag>(__func__, *source_cbag);
1845 target_cbag.slot_handle = target_slot_handle;
1846
1848 &this->channelbag_array, &this->channelbag_array_num, &target_cbag);
1849}
1850
1851const FCurve *Channelbag::fcurve_find(const FCurveDescriptor &fcurve_descriptor) const
1852{
1853 return animrig::fcurve_find(this->fcurves(), fcurve_descriptor);
1854}
1855
1857{
1858 /* Intermediate variable needed to disambiguate const/non-const overloads. */
1859 Span<FCurve *> fcurves = this->fcurves();
1860 return animrig::fcurve_find(fcurves, fcurve_descriptor);
1861}
1862
1863FCurve &Channelbag::fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
1864{
1865 if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) {
1866 return *existing_fcurve;
1867 }
1868 return this->fcurve_create(bmain, fcurve_descriptor);
1869}
1870
1872{
1873 if (this->fcurve_find(fcurve_descriptor)) {
1874 return nullptr;
1875 }
1876 return &this->fcurve_create(bmain, fcurve_descriptor);
1877}
1878
1879FCurve &Channelbag::fcurve_create(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
1880{
1881 FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor);
1882
1883 if (this->fcurve_array_num == 0) {
1884 new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
1885 }
1886
1887 bActionGroup *group = fcurve_descriptor.channel_group.has_value() ?
1888 &this->channel_group_ensure(*fcurve_descriptor.channel_group) :
1889 nullptr;
1890 const int insert_index = group ? group->fcurve_range_start + group->fcurve_range_length :
1891 this->fcurve_array_num;
1892 BLI_assert(insert_index <= this->fcurve_array_num);
1893
1894 grow_array_and_insert(&this->fcurve_array, &this->fcurve_array_num, insert_index, new_fcurve);
1895 if (group) {
1896 group->fcurve_range_length += 1;
1897 this->restore_channel_group_invariants();
1898 }
1899
1900 if (bmain) {
1902 }
1903
1904 return *new_fcurve;
1905}
1906
1908 Span<FCurveDescriptor> fcurve_descriptors)
1909{
1910 const int prev_fcurve_num = this->fcurve_array_num;
1911 const int add_fcurve_num = int(fcurve_descriptors.size());
1912 const bool make_first_active = prev_fcurve_num == 0;
1913
1914 /* Figure out which path+index combinations already exist. */
1915 struct CurvePathIndex {
1916 StringRefNull rna_path;
1917 int array_index;
1918 bool operator==(const CurvePathIndex &o) const
1919 {
1920 /* Check indices first, cheaper than a string comparison. */
1921 return this->array_index == o.array_index && this->rna_path == o.rna_path;
1922 }
1923 uint64_t hash() const
1924 {
1925 return get_default_hash(this->rna_path, this->array_index);
1926 }
1927 };
1928 Set<CurvePathIndex> unique_curves;
1929 unique_curves.reserve(prev_fcurve_num);
1930 for (FCurve *fcurve : this->fcurves()) {
1931 CurvePathIndex path_index;
1932 path_index.rna_path = StringRefNull(fcurve->rna_path ? fcurve->rna_path : "");
1933 path_index.array_index = fcurve->array_index;
1934 unique_curves.add(path_index);
1935 }
1936
1937 /* Grow curves array with enough space for new curves. */
1938 grow_array(&this->fcurve_array, &this->fcurve_array_num, add_fcurve_num);
1939
1940 /* Add the new curves. */
1941 Vector<FCurve *> new_fcurves;
1942 new_fcurves.resize(add_fcurve_num);
1943 int curve_index = prev_fcurve_num;
1944 for (int i = 0; i < add_fcurve_num; i++) {
1945 const FCurveDescriptor &desc = fcurve_descriptors[i];
1946
1947 CurvePathIndex path_index;
1948 path_index.rna_path = desc.rna_path;
1949 path_index.array_index = desc.array_index;
1950 if (desc.rna_path.is_empty() || !unique_curves.add(path_index)) {
1951 /* Empty input path, or such curve already exists. */
1952 new_fcurves[i] = nullptr;
1953 continue;
1954 }
1955
1957 new_fcurves[i] = fcurve;
1958
1959 this->fcurve_array[curve_index] = fcurve;
1960 if (desc.channel_group.has_value()) {
1961 bActionGroup *group = &this->channel_group_ensure(*desc.channel_group);
1962 const int insert_index = group->fcurve_range_start + group->fcurve_range_length;
1963 BLI_assert(insert_index <= this->fcurve_array_num);
1964 /* Insert curve into proper array place at the end of the group. Note: this can
1965 * still lead to quadratic complexity, in practice was not found to be an issue yet. */
1967 this->fcurve_array, this->fcurve_array_num, curve_index, curve_index + 1, insert_index);
1968 group->fcurve_range_length++;
1969
1970 /* Update curve start ranges of the following groups. */
1971 int index = this->channel_group_find_index(group);
1972 BLI_assert(index >= 0 && index < this->group_array_num);
1973 for (index = index + 1; index < this->group_array_num; index++) {
1974 this->group_array[index]->fcurve_range_start++;
1975 }
1976 }
1977 curve_index++;
1978 }
1979
1980 if (this->fcurve_array_num != curve_index) {
1981 /* Some curves were not created, resize to final amount. */
1983 &this->fcurve_array, &this->fcurve_array_num, this->fcurve_array_num - curve_index);
1984 }
1985
1986 if (make_first_active) {
1987 /* Set first created curve as active. */
1988 for (FCurve *fcurve : new_fcurves) {
1989 if (fcurve != nullptr) {
1990 fcurve->flag |= FCURVE_ACTIVE;
1991 break;
1992 }
1993 }
1994 }
1995
1996 this->restore_channel_group_invariants();
1997 if (bmain) {
1999 }
2000 return new_fcurves;
2001}
2002
2004{
2005 /* Appended F-Curves don't belong to any group yet, so better make sure their
2006 * group pointer reflects that. */
2007 fcurve.grp = nullptr;
2008
2010}
2011
2012static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
2013{
2014 BKE_fcurve_free(*fcurve_ptr);
2015};
2016
2017bool Channelbag::fcurve_remove(FCurve &fcurve_to_remove)
2018{
2019 if (!this->fcurve_detach(fcurve_to_remove)) {
2020 return false;
2021 }
2022 BKE_fcurve_free(&fcurve_to_remove);
2023 return true;
2024}
2025
2027{
2028 /* Grab the pointer before it's detached, so we can free it after. */
2029 FCurve *fcurve_to_remove = this->fcurve(fcurve_index);
2030
2031 this->fcurve_detach_by_index(fcurve_index);
2032
2033 BKE_fcurve_free(fcurve_to_remove);
2034}
2035
2036static void fcurve_ptr_noop_destructor(FCurve ** /*fcurve_ptr*/) {}
2037
2038bool Channelbag::fcurve_detach(FCurve &fcurve_to_detach)
2039{
2040 const int64_t fcurve_index = this->fcurves().first_index_try(&fcurve_to_detach);
2041 if (fcurve_index < 0) {
2042 return false;
2043 }
2044 this->fcurve_detach_by_index(fcurve_index);
2045 return true;
2046}
2047
2049{
2050 BLI_assert(fcurve_index >= 0);
2051 BLI_assert(fcurve_index < this->fcurve_array_num);
2052
2053 const int group_index = this->channel_group_containing_index(fcurve_index);
2054 if (group_index != -1) {
2055 bActionGroup *group = this->channel_group(group_index);
2056
2057 group->fcurve_range_length -= 1;
2058 if (group->fcurve_range_length <= 0) {
2059 const int group_index = this->channel_groups().first_index_try(group);
2060 this->channel_group_remove_raw(group_index);
2061 }
2062 }
2063
2065 &this->fcurve_array_num,
2066 nullptr,
2067 fcurve_index,
2069
2070 this->restore_channel_group_invariants();
2071
2072 /* As an optimization, this function could call `DEG_relations_tag_update(bmain)` to prune any
2073 * relationships that are now no longer necessary. This is not needed for correctness of the
2074 * depsgraph evaluation results though. */
2075}
2076
2078{
2079 BLI_assert(to_fcurve_index >= 0 && to_fcurve_index < this->fcurves().size());
2080
2081 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2082 BLI_assert_msg(fcurve_index >= 0, "FCurve not in this channel bag.");
2083
2085 this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_fcurve_index);
2086
2087 this->restore_channel_group_invariants();
2088}
2089
2091{
2093
2094 /* Since all F-Curves are gone, the groups are all empty. */
2095 for (bActionGroup *group : channel_groups()) {
2096 group->fcurve_range_start = 0;
2097 group->fcurve_range_length = 0;
2098 }
2099}
2100
2102{
2103 /* #BKE_fcurve_get_cycle_type() only looks at the first modifier to see if it's a Cycle modifier,
2104 * so if we're going to add one, better make sure it's the first one.
2105 *
2106 * BUT: #add_fmodifier() only allows adding a Cycle modifier when there are none yet, so that's
2107 * all that we need to check for here.
2108 */
2109 if (!BLI_listbase_is_empty(&fcurve.modifiers)) {
2110 return;
2111 }
2112
2114}
2115
2126static void cyclic_keying_ensure_cycle_range_exists(FCurve &fcurve, const float2 cycle_range)
2127{
2128 /* This is basically a copy of the legacy function `make_new_fcurve_cyclic()`
2129 * in `keyframing.cc`, except that it's limited to only one thing (ensuring
2130 * two keys exist to make cycling possible). Creating the F-Curve modifier is
2131 * the responsibility of another function. */
2132
2133 if (fcurve.totvert != 1 || fcurve.bezt == nullptr) {
2134 return;
2135 }
2136
2137 const float period = cycle_range[1] - cycle_range[0];
2138 if (period < 0.1f) {
2139 return;
2140 }
2141
2142 /* Move the one existing keyframe into the cycle range. */
2143 const float frame_offset = fcurve.bezt[0].vec[1][0] - cycle_range[0];
2144 const float fix = floorf(frame_offset / period) * period;
2145
2146 fcurve.bezt[0].vec[0][0] -= fix;
2147 fcurve.bezt[0].vec[1][0] -= fix;
2148 fcurve.bezt[0].vec[2][0] -= fix;
2149
2150 /* Reallocate the array to make space for the 2nd point. */
2151 fcurve.totvert++;
2152 fcurve.bezt = static_cast<BezTriple *>(
2153 MEM_reallocN(fcurve.bezt, sizeof(BezTriple) * fcurve.totvert));
2154
2155 /* Duplicate and offset the keyframe. */
2156 fcurve.bezt[1] = fcurve.bezt[0];
2157 fcurve.bezt[1].vec[0][0] += period;
2158 fcurve.bezt[1].vec[1][0] += period;
2159 fcurve.bezt[1].vec[2][0] += period;
2160}
2161
2163 const Slot &slot,
2164 const FCurveDescriptor &fcurve_descriptor,
2165 const float2 time_value,
2166 const KeyframeSettings &settings,
2167 const eInsertKeyFlags insert_key_flags,
2168 const std::optional<float2> cycle_range)
2169{
2170 /* Get the fcurve, or create one if it doesn't exist and the keying flags
2171 * allow. */
2172 FCurve *fcurve = nullptr;
2173 if (key_insertion_may_create_fcurve(insert_key_flags)) {
2174 fcurve = &this->channelbag_for_slot_ensure(slot).fcurve_ensure(bmain, fcurve_descriptor);
2175 }
2176 else {
2177 Channelbag *channels = this->channelbag_for_slot(slot);
2178 if (channels != nullptr) {
2179 fcurve = channels->fcurve_find(fcurve_descriptor);
2180 }
2181 }
2182
2183 if (!fcurve) {
2184 CLOG_WARN(&LOG,
2185 "FCurve %s[%d] for slot %s was not created due to either the Only Insert "
2186 "Available setting or Replace keyframing mode.\n",
2187 fcurve_descriptor.rna_path.c_str(),
2188 fcurve_descriptor.array_index,
2189 slot.identifier);
2191 }
2192
2193 if (!BKE_fcurve_is_keyframable(fcurve)) {
2194 /* TODO: handle this properly, in a way that can be communicated to the user. */
2195 CLOG_WARN(&LOG,
2196 "FCurve %s[%d] for slot %s doesn't allow inserting keys.\n",
2197 fcurve_descriptor.rna_path.c_str(),
2198 fcurve_descriptor.array_index,
2199 slot.identifier);
2201 }
2202
2203 if (cycle_range && (*cycle_range)[0] < (*cycle_range)[1]) {
2204 /* Cyclic keying consists of three things:
2205 * - Ensure there is a Cycle modifier on the F-Curve.
2206 * - Ensure the start and end of the cycle have explicit keys, so that the
2207 * cycle modifier knows how to cycle (it doesn't look at the Action, and
2208 * as long as the period is correct, the first/last keys don't have to
2209 * align with the Action start/end).
2210 * - Offset the key to insert so that it falls within the cycle range.
2211 */
2213 cyclic_keying_ensure_cycle_range_exists(*fcurve, *cycle_range);
2214 /* Offsetting the key doesn't have to happen here, as insert_vert_fcurve()
2215 * takes care of that. */
2216 }
2217
2218 const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
2219 fcurve, time_value, settings, insert_key_flags);
2220
2221 if (insert_vert_result != SingleKeyingResult::SUCCESS) {
2222 CLOG_WARN(&LOG,
2223 "Could not insert key into FCurve %s[%d] for slot %s.\n",
2224 fcurve_descriptor.rna_path.c_str(),
2225 fcurve_descriptor.array_index,
2226 slot.identifier);
2227 return insert_vert_result;
2228 }
2229
2230 if (fcurve_descriptor.prop_type) {
2231 update_autoflags_fcurve_direct(fcurve, *fcurve_descriptor.prop_type);
2232 }
2233
2235}
2236
2237/* ActionChannelbag implementation. */
2238
2240{
2241 this->slot_handle = other.slot_handle;
2242
2243 this->fcurve_array_num = other.fcurve_array_num;
2245 for (int i = 0; i < other.fcurve_array_num; i++) {
2246 const FCurve *fcu_src = other.fcurve_array[i];
2247 this->fcurve_array[i] = BKE_fcurve_copy(fcu_src);
2248 }
2249
2250 this->group_array_num = other.group_array_num;
2252 for (int i = 0; i < other.group_array_num; i++) {
2253 const bActionGroup *group_src = other.group_array[i];
2254 this->group_array[i] = static_cast<bActionGroup *>(MEM_dupallocN(group_src));
2255 this->group_array[i]->channelbag = this;
2256 }
2257
2258 /* BKE_fcurve_copy() resets the FCurve's group pointer. Which is good, because the groups are
2259 * duplicated too. This sets the group pointers to the correct values. */
2260 this->restore_channel_group_invariants();
2261}
2262
2264{
2265 for (FCurve *fcu : this->fcurves()) {
2266 BKE_fcurve_free(fcu);
2267 }
2269 this->fcurve_array_num = 0;
2270
2271 for (bActionGroup *group : this->channel_groups()) {
2272 MEM_SAFE_FREE(group);
2273 }
2275 this->group_array_num = 0;
2276}
2277
2286const FCurve *Channelbag::fcurve(const int64_t index) const
2287{
2288 return this->fcurve_array[index];
2289}
2291{
2292 return this->fcurve_array[index];
2293}
2294
2304{
2305 BLI_assert(index < this->group_array_num);
2306 return this->group_array[index];
2307}
2309{
2310 BLI_assert(index < this->group_array_num);
2311 return this->group_array[index];
2312}
2313
2315{
2316 for (const bActionGroup *group : this->channel_groups()) {
2317 if (name == StringRef{group->name}) {
2318 return group;
2319 }
2320 }
2321
2322 return nullptr;
2323}
2324
2326{
2327 for (int i = 0; i < this->group_array_num; i++) {
2328 if (this->group_array[i] == group) {
2329 return i;
2330 }
2331 }
2332 return -1;
2333}
2334
2336{
2337 /* Intermediate variable needed to disambiguate const/non-const overloads. */
2338 Span<bActionGroup *> groups = this->channel_groups();
2339 for (bActionGroup *group : groups) {
2340 if (name == StringRef{group->name}) {
2341 return group;
2342 }
2343 }
2344
2345 return nullptr;
2346}
2347
2348int Channelbag::channel_group_containing_index(const int fcurve_array_index)
2349{
2350 int i = 0;
2351 for (const bActionGroup *group : this->channel_groups()) {
2352 if (fcurve_array_index >= group->fcurve_range_start &&
2353 fcurve_array_index < (group->fcurve_range_start + group->fcurve_range_length))
2354 {
2355 return i;
2356 }
2357 i++;
2358 }
2359
2360 return -1;
2361}
2362
2364{
2365 bActionGroup *new_group = MEM_callocN<bActionGroup>(__func__);
2366
2367 /* Find the end fcurve index of the current channel groups, to be used as the
2368 * start of the new channel group. */
2369 int fcurve_index = 0;
2370 const int length = this->channel_groups().size();
2371 if (length > 0) {
2372 const bActionGroup *last = this->channel_group(length - 1);
2373 fcurve_index = last->fcurve_range_start + last->fcurve_range_length;
2374 }
2375 new_group->fcurve_range_start = fcurve_index;
2376
2377 new_group->channelbag = this;
2378
2379 /* Make it selected. */
2380 new_group->flag = AGRP_SELECTED;
2381
2382 /* Ensure it has a unique name.
2383 *
2384 * Note that this only happens here (upon creation). The user can later rename
2385 * groups to have duplicate names. This is stupid, but it's how the legacy
2386 * system worked, and at the time of writing this code we're just trying to
2387 * match that system's behavior, even when it's goofy. */
2388 std::string unique_name = BLI_uniquename_cb(
2389 [&](const StringRef name) {
2390 for (const bActionGroup *group : this->channel_groups()) {
2391 if (STREQ(group->name, name.data())) {
2392 return true;
2393 }
2394 }
2395 return false;
2396 },
2397 '.',
2398 name[0] == '\0' ? DATA_("Group") : name);
2399
2400 STRNCPY_UTF8(new_group->name, unique_name.c_str());
2401
2402 grow_array_and_append(&this->group_array, &this->group_array_num, new_group);
2403
2404 return *new_group;
2405}
2406
2408{
2409 bActionGroup *group = this->channel_group_find(name);
2410 if (group) {
2411 return *group;
2412 }
2413
2414 return this->channel_group_create(name);
2415}
2416
2418{
2419 const int group_index = this->channel_groups().first_index_try(&group);
2420 if (group_index == -1) {
2421 return false;
2422 }
2423
2424 /* Move the group's fcurves to just past the end of where the grouped
2425 * fcurves will be after this group is removed. */
2426 const bActionGroup *last_group = this->channel_groups().last();
2427 BLI_assert(last_group != nullptr);
2428 const int to_index = last_group->fcurve_range_start + last_group->fcurve_range_length -
2429 group.fcurve_range_length;
2431 this->fcurve_array_num,
2432 group.fcurve_range_start,
2434 to_index);
2435
2436 this->channel_group_remove_raw(group_index);
2437 this->restore_channel_group_invariants();
2438
2439 return true;
2440}
2441
2442void Channelbag::channel_group_move_to_index(bActionGroup &group, const int to_group_index)
2443{
2444 BLI_assert(to_group_index >= 0 && to_group_index < this->channel_groups().size());
2445
2446 const int group_index = this->channel_groups().first_index_try(&group);
2447 BLI_assert_msg(group_index >= 0, "Group not in this channel bag.");
2448
2449 /* Shallow copy, to track which fcurves should be moved in the second step. */
2450 const bActionGroup pre_move_group = group;
2451
2452 /* First we move the group to its new position. The call to
2453 * `restore_channel_group_invariants()` is necessary to update the group's
2454 * fcurve range (as well as the ranges of the other groups) to match its new
2455 * position in the group array. */
2457 this->group_array, this->group_array_num, group_index, group_index + 1, to_group_index);
2458 this->restore_channel_group_invariants();
2459
2460 /* Move the fcurves that were part of `group` (as recorded in
2461 *`pre_move_group`) to their new positions (now in `group`) so that they're
2462 * part of `group` again. */
2464 this->fcurve_array_num,
2465 pre_move_group.fcurve_range_start,
2466 pre_move_group.fcurve_range_start + pre_move_group.fcurve_range_length,
2467 group.fcurve_range_start);
2468 this->restore_channel_group_invariants();
2469}
2470
2471void Channelbag::channel_group_remove_raw(const int group_index)
2472{
2473 BLI_assert(group_index >= 0 && group_index < this->channel_groups().size());
2474
2475 MEM_SAFE_FREE(this->group_array[group_index]);
2476 shrink_array_and_remove(&this->group_array, &this->group_array_num, group_index);
2477}
2478
2479void Channelbag::restore_channel_group_invariants()
2480{
2481 /* Shift channel groups. */
2482 {
2483 int start_index = 0;
2484 for (bActionGroup *group : this->channel_groups()) {
2485 group->fcurve_range_start = start_index;
2486 start_index += group->fcurve_range_length;
2487 }
2488
2489 /* Double-check that this didn't push any of the groups off the end of the
2490 * fcurve array. */
2491 BLI_assert(start_index <= this->fcurve_array_num);
2492 }
2493
2494 /* Recompute fcurves' group pointers. */
2495 {
2496 for (FCurve *fcurve : this->fcurves()) {
2497 fcurve->grp = nullptr;
2498 }
2499 for (bActionGroup *group : this->channel_groups()) {
2500 for (FCurve *fcurve : group->wrap().fcurves()) {
2501 fcurve->grp = group;
2502 }
2503 }
2504 }
2505}
2506
2508{
2509 return this->channelbag == nullptr;
2510}
2511
2513{
2514 BLI_assert(!this->is_legacy());
2515
2516 if (this->fcurve_range_length == 0) {
2517 return {};
2518 }
2519
2520 return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
2521 this->fcurve_range_length);
2522}
2523
2525{
2526 BLI_assert(!this->is_legacy());
2527
2528 if (this->fcurve_range_length == 0) {
2529 return {};
2530 }
2531
2532 return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
2533 this->fcurve_range_length);
2534}
2535
2536/* Utility function implementations. */
2537
2539 const slot_handle_t slot_handle)
2540{
2542
2543 if (slot_handle == Slot::unassigned) {
2544 return nullptr;
2545 }
2546
2547 for (const animrig::Layer *layer : action.layers()) {
2548 for (const animrig::Strip *strip : layer->strips()) {
2549 switch (strip->type()) {
2550 case animrig::Strip::Type::Keyframe: {
2551 const animrig::StripKeyframeData &strip_data = strip->data<animrig::StripKeyframeData>(
2552 action);
2553 const animrig::Channelbag *bag = strip_data.channelbag_for_slot(slot_handle);
2554 if (bag) {
2555 return bag;
2556 }
2557 }
2558 }
2559 }
2560 }
2561
2562 return nullptr;
2563}
2564
2566{
2568 const_cast<const Action &>(action), slot_handle);
2569 return const_cast<animrig::Channelbag *>(const_bag);
2570}
2571
2573{
2574 BLI_assert(action.is_action_layered());
2576 animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
2577 if (!bag) {
2578 return {};
2579 }
2580 return bag->fcurves();
2581}
2582
2584{
2585 BLI_assert(action.is_action_layered());
2587 const animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
2588 if (!bag) {
2589 return {};
2590 }
2591 return bag->fcurves();
2592}
2593
2595{
2596 if (act == nullptr) {
2597 return nullptr;
2598 }
2599
2600 Action &action = act->wrap();
2601 if (action.is_action_legacy()) {
2602 return BKE_fcurve_find(
2603 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2604 }
2605
2607 Layer *layer = action.layer(0);
2608 if (!layer) {
2609 return nullptr;
2610 }
2611 Strip *strip = layer->strip(0);
2612 if (!strip) {
2613 return nullptr;
2614 }
2615
2616 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2617
2618 for (Channelbag *channelbag : strip_data.channelbags()) {
2619 FCurve *fcu = channelbag->fcurve_find(fcurve_descriptor);
2620 if (fcu) {
2621 return fcu;
2622 }
2623 }
2624
2625 return nullptr;
2626}
2627
2629{
2630 return fcurve_find_in_action_slot(adt.action, adt.slot_handle, fcurve_descriptor);
2631}
2632
2634 const slot_handle_t slot_handle,
2635 const FCurveDescriptor &fcurve_descriptor)
2636{
2637 if (act == nullptr) {
2638 return nullptr;
2639 }
2640
2641 Action &action = act->wrap();
2642 if (action.is_action_legacy()) {
2643 return BKE_fcurve_find(
2644 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2645 }
2646
2647 Channelbag *cbag = channelbag_for_action_slot(action, slot_handle);
2648 if (!cbag) {
2649 return nullptr;
2650 }
2651 return cbag->fcurve_find(fcurve_descriptor);
2652}
2653
2655 const StringRefNull collection_rna_path,
2656 const StringRefNull data_name)
2657{
2658 BLI_assert(!collection_rna_path.is_empty());
2659
2660 const size_t quoted_name_size = data_name.size() + 1;
2661 char *quoted_name = static_cast<char *>(alloca(quoted_name_size));
2662
2663 if (!fcurve.rna_path) {
2664 return false;
2665 }
2666 /* Skipping names longer than `quoted_name_size` is OK since we're after an exact match. */
2668 fcurve.rna_path, collection_rna_path.c_str(), quoted_name, quoted_name_size))
2669 {
2670 return false;
2671 }
2672 if (quoted_name != data_name) {
2673 return false;
2674 }
2675
2676 return true;
2677}
2678
2680 const slot_handle_t slot_handle,
2681 FunctionRef<bool(const FCurve &fcurve)> predicate)
2682{
2683 BLI_assert(act);
2684
2685 Vector<FCurve *> found;
2686
2687 foreach_fcurve_in_action_slot(act->wrap(), slot_handle, [&](FCurve &fcurve) {
2688 if (predicate(fcurve)) {
2689 found.append(&fcurve);
2690 }
2691 });
2692
2693 return found;
2694}
2695
2697 FunctionRef<bool(const FCurve &fcurve)> predicate)
2698{
2699 Vector<FCurve *> found;
2700
2701 for (FCurve *fcurve : fcurves) {
2702 if (predicate(*fcurve)) {
2703 found.append(fcurve);
2704 }
2705 }
2706
2707 return found;
2708}
2709
2711 FunctionRef<bool(const FCurve &fcurve)> predicate)
2712{
2713 Vector<FCurve *> found;
2714
2715 LISTBASE_FOREACH (FCurve *, fcurve, &fcurves) {
2716 if (predicate(*fcurve)) {
2717 found.append(fcurve);
2718 }
2719 }
2720
2721 return found;
2722}
2723
2725 bAction *act,
2726 const char group[],
2727 PointerRNA *ptr,
2728 const FCurveDescriptor &fcurve_descriptor)
2729{
2730 if (act == nullptr) {
2731 return nullptr;
2732 }
2733
2735 return action_fcurve_ensure_legacy(bmain, act, group, ptr, fcurve_descriptor);
2736 }
2737
2738 /* NOTE: for layered actions we require the following:
2739 *
2740 * - `ptr` is non-null.
2741 * - `ptr` has an `owner_id` that already uses `act`.
2742 *
2743 * This isn't for any principled reason, but rather is because adding
2744 * support for layered actions to this function was a fix to make Follow
2745 * Path animation work properly with layered actions (see PR #124353), and
2746 * those are the requirements the Follow Path code conveniently met.
2747 * Moreover those requirements were also already met by the other call sites
2748 * that potentially call this function with layered actions.
2749 *
2750 * Trying to puzzle out what "should" happen when these requirements don't
2751 * hold, or if this is even the best place to handle the layered action
2752 * cases at all, was leading to discussion of larger changes than made sense
2753 * to tackle at that point. */
2754 BLI_assert(ptr != nullptr);
2755 if (ptr == nullptr || ptr->owner_id == nullptr) {
2756 return nullptr;
2757 }
2758
2759 return &action_fcurve_ensure(bmain, *act, *ptr->owner_id, fcurve_descriptor);
2760}
2761
2763{
2764 Action &action = dna_action.wrap();
2765 BLI_assert(get_action(animated_id) == &action);
2766
2767 /* Ensure the id has an assigned slot. */
2768 Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
2769 /* A nullptr here means the ID type is not animatable. But since the Action is already assigned,
2770 * it is certain that the ID is actually animatable. */
2771 BLI_assert(slot);
2772
2773 action.layer_keystrip_ensure();
2774
2776 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2777
2778 return strip_data.channelbag_for_slot_ensure(*slot);
2779}
2780
2782 bAction &dna_action,
2783 ID &animated_id,
2784 const FCurveDescriptor &fcurve_descriptor)
2785{
2786 Channelbag &channelbag = action_channelbag_ensure(dna_action, animated_id);
2787 return channelbag.fcurve_ensure(bmain, fcurve_descriptor);
2788}
2789
2791 bAction *act,
2792 const char group[],
2793 PointerRNA *ptr,
2794 const FCurveDescriptor &fcurve_descriptor)
2795{
2796 if (!act) {
2797 return nullptr;
2798 }
2799
2800 BLI_assert(act->wrap().is_empty() || act->wrap().is_action_legacy());
2801
2802 /* Try to find f-curve matching for this setting.
2803 * - add if not found and allowed to add one
2804 * TODO: add auto-grouping support? how this works will need to be resolved
2805 */
2806 FCurve *fcu = animrig::fcurve_find_in_action(act, fcurve_descriptor);
2807
2808 if (fcu != nullptr) {
2809 return fcu;
2810 }
2811
2812 /* Determine the property (sub)type if we can. */
2813 std::optional<PropertyType> prop_type = std::nullopt;
2814 std::optional<PropertySubType> prop_subtype = std::nullopt;
2815 if (ptr != nullptr) {
2816 PropertyRNA *resolved_prop;
2817 PointerRNA resolved_ptr;
2818 PointerRNA id_ptr = RNA_id_pointer_create(ptr->owner_id);
2819 const bool resolved = RNA_path_resolve_property(
2820 &id_ptr, fcurve_descriptor.rna_path.c_str(), &resolved_ptr, &resolved_prop);
2821 if (resolved) {
2822 prop_type = RNA_property_type(resolved_prop);
2823 prop_subtype = RNA_property_subtype(resolved_prop);
2824 }
2825 }
2826
2827 BLI_assert_msg(!fcurve_descriptor.prop_type.has_value(),
2828 "Did not expect a prop_type to be passed in. This is fine, but does need some "
2829 "changes to action_fcurve_ensure_legacy() to deal with it");
2830 BLI_assert_msg(!fcurve_descriptor.prop_subtype.has_value(),
2831 "Did not expect a prop_subtype to be passed in. This is fine, but does need some "
2832 "changes to action_fcurve_ensure_legacy() to deal with it");
2834 {fcurve_descriptor.rna_path, fcurve_descriptor.array_index, prop_type, prop_subtype});
2835
2836 if (BLI_listbase_is_empty(&act->curves)) {
2837 fcu->flag |= FCURVE_ACTIVE;
2838 }
2839
2840 if (group) {
2841 bActionGroup *agrp = BKE_action_group_find_name(act, group);
2842
2843 if (agrp == nullptr) {
2844 agrp = action_groups_add_new(act, group);
2845
2846 /* Sync bone group colors if applicable. */
2847 if (ptr && (ptr->type == &RNA_PoseBone) && ptr->data) {
2848 const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
2850 }
2851 }
2852
2853 action_groups_add_channel(act, agrp, fcu);
2854 }
2855 else {
2856 BLI_addtail(&act->curves, fcu);
2857 }
2858
2859 /* New f-curve was added, meaning it's possible that it affects
2860 * dependency graph component which wasn't previously animated.
2861 */
2863
2864 return fcu;
2865}
2866
2868{
2869 if (action_fcurve_detach(action, fcu)) {
2870 BKE_fcurve_free(&fcu);
2871 return true;
2872 }
2873
2874 return false;
2875}
2876
2877bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
2878{
2879 if (action.is_action_legacy()) {
2880 return BLI_remlink_safe(&action.curves, &fcurve_to_detach);
2881 }
2882
2883 for (Layer *layer : action.layers()) {
2884 for (Strip *strip : layer->strips()) {
2885 if (!(strip->type() == Strip::Type::Keyframe)) {
2886 continue;
2887 }
2888 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2889 for (Channelbag *bag : strip_data.channelbags()) {
2890 const bool is_detached = bag->fcurve_detach(fcurve_to_detach);
2891 if (is_detached) {
2892 return true;
2893 }
2894 }
2895 }
2896 }
2897 return false;
2898}
2899
2901 const slot_handle_t action_slot,
2902 FCurve &fcurve_to_attach,
2903 std::optional<StringRefNull> group_name)
2904{
2906 BLI_addtail(&action.curves, &fcurve_to_attach);
2907 return;
2908 }
2909
2910 Slot *slot = action.slot_for_handle(action_slot);
2911 BLI_assert(slot);
2912 if (!slot) {
2913 printf("Cannot find slot handle %d on Action %s, unable to attach F-Curve %s[%d] to it!\n",
2914 action_slot,
2915 action.id.name + 2,
2916 fcurve_to_attach.rna_path,
2917 fcurve_to_attach.array_index);
2918 return;
2919 }
2920
2921 action.layer_keystrip_ensure();
2922 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2923 Channelbag &cbag = strip_data.channelbag_for_slot_ensure(*slot);
2924 cbag.fcurve_append(fcurve_to_attach);
2925
2926 if (group_name) {
2927 bActionGroup &group = cbag.channel_group_ensure(*group_name);
2928 cbag.fcurve_assign_to_channel_group(fcurve_to_attach, group);
2929 }
2930}
2931
2933 const slot_handle_t action_slot_dst,
2934 Action &action_src,
2935 FCurve &fcurve)
2936{
2937 /* Store the group name locally, as the group will be removed if this was its
2938 * last F-Curve. */
2939 std::optional<std::string> group_name;
2940 if (fcurve.grp) {
2941 group_name = fcurve.grp->name;
2942 }
2943
2944 const bool is_detached = action_fcurve_detach(action_src, fcurve);
2945 BLI_assert(is_detached);
2946 UNUSED_VARS_NDEBUG(is_detached);
2947
2948 action_fcurve_attach(action_dst, action_slot_dst, fcurve, group_name);
2949}
2950
2951void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src)
2952{
2953 while (!channelbag_src.fcurves().is_empty()) {
2954 FCurve &fcurve = *channelbag_src.fcurve(0);
2955
2956 /* Store the group name locally, as the group will be removed if this was its
2957 * last F-Curve. */
2958 std::optional<std::string> group_name;
2959 if (fcurve.grp) {
2960 group_name = fcurve.grp->name;
2961 }
2962
2963 const bool is_detached = channelbag_src.fcurve_detach(fcurve);
2964 BLI_assert(is_detached);
2965 UNUSED_VARS_NDEBUG(is_detached);
2966
2967 channelbag_dst.fcurve_append(fcurve);
2968
2969 if (group_name) {
2970 bActionGroup &group = channelbag_dst.channel_group_ensure(*group_name);
2971 channelbag_dst.fcurve_assign_to_channel_group(fcurve, group);
2972 }
2973 }
2974}
2975
2977{
2978 if (this->channel_groups().first_index_try(&to_group) == -1) {
2979 return false;
2980 }
2981
2982 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2983 if (fcurve_index == -1) {
2984 return false;
2985 }
2986
2987 if (fcurve.grp == &to_group) {
2988 return true;
2989 }
2990
2991 /* Remove fcurve from old group, if it belongs to one. */
2992 if (fcurve.grp != nullptr) {
2993 fcurve.grp->fcurve_range_length--;
2994 if (fcurve.grp->fcurve_range_length == 0) {
2995 const int group_index = this->channel_groups().first_index_try(fcurve.grp);
2996 this->channel_group_remove_raw(group_index);
2997 }
2998 this->restore_channel_group_invariants();
2999 }
3000
3002 this->fcurve_array_num,
3003 fcurve_index,
3004 fcurve_index + 1,
3005 to_group.fcurve_range_start + to_group.fcurve_range_length);
3006 to_group.fcurve_range_length++;
3007
3008 this->restore_channel_group_invariants();
3009
3010 return true;
3011}
3012
3014{
3015 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
3016 if (fcurve_index == -1) {
3017 return false;
3018 }
3019
3020 if (fcurve.grp == nullptr) {
3021 return true;
3022 }
3023
3024 bActionGroup *old_group = fcurve.grp;
3025
3027 this->fcurve_array_num,
3028 fcurve_index,
3029 fcurve_index + 1,
3030 this->fcurve_array_num - 1);
3031
3032 old_group->fcurve_range_length--;
3033 if (old_group->fcurve_range_length == 0) {
3034 const int old_group_index = this->channel_groups().first_index_try(old_group);
3035 this->channel_group_remove_raw(old_group_index);
3036 }
3037
3038 this->restore_channel_group_invariants();
3039
3040 return true;
3041}
3042
3044 Action &action,
3045 const slot_handle_t slot_handle,
3046 ID *primary_id)
3047{
3049 if (primary_id && get_action(*primary_id) == &action) {
3050 return primary_id;
3051 }
3052 return nullptr;
3053 }
3054
3055 Slot *slot = action.slot_for_handle(slot_handle);
3056 if (slot == nullptr) {
3057 return nullptr;
3058 }
3059
3060 blender::Span<ID *> users = slot->users(bmain);
3061 if (users.size() == 1) {
3062 /* We only do this for `users.size() == 1` and not `users.size() >= 1`
3063 * because when there's more than one user it's ambiguous which user we
3064 * should return, and that would be unpredictable for end users of Blender.
3065 * We also expect that to be a corner case anyway. So instead we let that
3066 * case either get disambiguated by the primary ID in the case below, or
3067 * return null. */
3068 return users[0];
3069 }
3070 if (users.contains(primary_id)) {
3071 return primary_id;
3072 }
3073
3074 return nullptr;
3075}
3076
3077ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
3078{
3079 blender::Span<ID *> users = slot.users(bmain);
3080 if (users.is_empty()) {
3081 return nullptr;
3082 }
3083 if (users.contains(primary_id)) {
3084 return primary_id;
3085 }
3086 return users[0];
3087}
3088
3089slot_handle_t first_slot_handle(const ::bAction &dna_action)
3090{
3091 const Action &action = dna_action.wrap();
3092 if (action.slot_array_num == 0) {
3093 return Slot::unassigned;
3094 }
3095 return action.slot_array[0]->handle;
3096}
3097
3099{
3100 if (action.is_action_legacy()) {
3101 return;
3102 }
3103 if (action.layers().is_empty()) {
3104 return;
3105 }
3106 BLI_assert(action.layers().size() == 1);
3107
3109}
3110
3112{
3113 if (layer.strips().is_empty()) {
3114 return;
3115 }
3116 BLI_assert(layer.strips().size() == 1);
3117
3119}
3120
3122{
3123 UNUSED_VARS_NDEBUG(strip);
3124 BLI_assert(strip.type() == Strip::Type::Keyframe);
3125 BLI_assert(strip.is_infinite());
3126 BLI_assert(strip.frame_offset == 0.0);
3127}
3128
3129Action *convert_to_layered_action(Main &bmain, const Action &legacy_action)
3130{
3131 if (!legacy_action.is_action_legacy()) {
3132 return nullptr;
3133 }
3134
3135 std::string suffix = "_layered";
3136 /* In case the legacy action has a long name it is shortened to make space for the suffix. */
3137 char legacy_name[MAX_ID_NAME - 10];
3138 /* Offsetting the id.name to remove the ID prefix (AC) which gets added back later. */
3139 STRNCPY_UTF8(legacy_name, legacy_action.id.name + 2);
3140
3141 const std::string layered_action_name = std::string(legacy_name) + suffix;
3142 bAction *dna_action = BKE_action_add(&bmain, layered_action_name.c_str());
3143
3144 Action &converted_action = dna_action->wrap();
3145 Slot &slot = converted_action.slot_add();
3146 Layer &layer = converted_action.layer_add(legacy_action.id.name);
3147 Strip &strip = layer.strip_add(converted_action, Strip::Type::Keyframe);
3148 BLI_assert(strip.data<StripKeyframeData>(converted_action).channelbag_array_num == 0);
3149 Channelbag *bag = &strip.data<StripKeyframeData>(converted_action).channelbag_for_slot_add(slot);
3150
3151 const int fcu_count = BLI_listbase_count(&legacy_action.curves);
3152 bag->fcurve_array = MEM_calloc_arrayN<FCurve *>(fcu_count, "Convert to layered action");
3153 bag->fcurve_array_num = fcu_count;
3154
3155 int i = 0;
3156 blender::Map<FCurve *, FCurve *> old_new_fcurve_map;
3157 LISTBASE_FOREACH_INDEX (FCurve *, fcu, &legacy_action.curves, i) {
3158 bag->fcurve_array[i] = BKE_fcurve_copy(fcu);
3159 bag->fcurve_array[i]->grp = nullptr;
3160 old_new_fcurve_map.add(fcu, bag->fcurve_array[i]);
3161 }
3162
3163 LISTBASE_FOREACH (bActionGroup *, group, &legacy_action.groups) {
3164 /* The resulting group might not have the same name, because the legacy system allowed
3165 * duplicate names while the new system ensures uniqueness. */
3166 bActionGroup &converted_group = bag->channel_group_create(group->name);
3167 LISTBASE_FOREACH (FCurve *, fcu, &group->channels) {
3168 if (fcu->grp != group) {
3169 /* Since the group listbase points to the action listbase, it won't stop iterating when
3170 * reaching the end of the group but iterate to the end of the action FCurves. */
3171 break;
3172 }
3173 FCurve *new_fcurve = old_new_fcurve_map.lookup(fcu);
3174 bag->fcurve_assign_to_channel_group(*new_fcurve, converted_group);
3175 }
3176 }
3177
3178 return &converted_action;
3179}
3180
3186static void clone_slot(const Slot &from, Slot &to)
3187{
3188 ActionSlotRuntimeHandle *runtime = to.runtime;
3189 slot_handle_t handle = to.handle;
3190 *reinterpret_cast<ActionSlot *>(&to) = *reinterpret_cast<const ActionSlot *>(&from);
3191 to.runtime = runtime;
3192 to.handle = handle;
3193}
3194
3195void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_action)
3196{
3197 BLI_assert(from_action.slots().contains(&source_slot));
3198 BLI_assert(&from_action != &to_action);
3199
3200 /* No merging of strips or layers is handled. All data is put into the assumed single strip. */
3203
3204 Slot &target_slot = to_action.slot_add();
3205 clone_slot(source_slot, target_slot);
3206 slot_identifier_ensure_unique(to_action, target_slot);
3207
3208 if (!from_action.layers().is_empty() && !from_action.layer(0)->strips().is_empty()) {
3209 StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data<StripKeyframeData>(
3210 from_action);
3211 Channelbag *channelbag = from_strip_data.channelbag_for_slot(source_slot.handle);
3212 /* It's perfectly fine for a slot to not have a channelbag on each keyframe strip. */
3213 if (channelbag) {
3214 /* Only create the layer & keyframe strip if there is a channelbag to move
3215 * into it. Otherwise it's better to keep the Action lean, and defer their
3216 * creation when keys are inserted. */
3217 to_action.layer_keystrip_ensure();
3218 StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
3219 to_action);
3220 channelbag->slot_handle = target_slot.handle;
3222 &to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag);
3223 const int index = from_strip_data.find_channelbag_index(*channelbag);
3225 &from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
3226 }
3227 }
3228
3229 /* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */
3230 for (ID *user : source_slot.users(bmain)) {
3231 const auto assign_other_action = [&](ID & /* animated_id */,
3232 bAction *&action_ptr_ref,
3233 slot_handle_t &slot_handle_ref,
3234 char *slot_identifier) -> bool {
3235 /* Only reassign if the reference is actually from the same action. Could be from a different
3236 * action when using the NLA or action constraints. */
3237 if (action_ptr_ref != &from_action) {
3238 return true;
3239 }
3240
3241 { /* Assign the Action. */
3242 const bool assign_ok = generic_assign_action(
3243 *user, &to_action, action_ptr_ref, slot_handle_ref, slot_identifier);
3244 BLI_assert_msg(assign_ok, "Expecting slotted Actions to always be assignable");
3245 UNUSED_VARS_NDEBUG(assign_ok);
3246 }
3247 { /* Assign the Slot. */
3249 &target_slot, *user, action_ptr_ref, slot_handle_ref, slot_identifier);
3252 }
3253
3254 /* TODO: move the tagging of animated IDs into generic_assign_action() and
3255 * generic_assign_action_slot(), as that's closer to the modification of
3256 * the animated ID.
3257 *
3258 * This line was added here for now, to fix #136388 with minimal impact on
3259 * other code, so that the fix can be easily back-ported to Blender 4.4. */
3261 return true;
3262 };
3263 foreach_action_slot_use_with_references(*user, assign_other_action);
3264 }
3265
3266 from_action.slot_remove(source_slot);
3267}
3268
3269Slot &duplicate_slot(Action &action, const Slot &slot)
3270{
3271 BLI_assert(action.slots().contains(const_cast<Slot *>(&slot)));
3272
3273 /* Duplicate the slot itself. */
3274 Slot &cloned_slot = action.slot_add();
3275 clone_slot(slot, cloned_slot);
3276 slot_identifier_ensure_unique(action, cloned_slot);
3277
3278 /* Duplicate each Channelbag for the source slot. */
3279 for (int i = 0; i < action.strip_keyframe_data_array_num; i++) {
3280 StripKeyframeData &strip_data = action.strip_keyframe_data_array[i]->wrap();
3281 strip_data.slot_data_duplicate(slot.handle, cloned_slot.handle);
3282 }
3283
3284 /* The ID has changed, and so it needs to be re-evaluated. Animation does not
3285 * have to be flushed since nothing is using this slot yet. */
3287
3288 return cloned_slot;
3289}
3290
3291} // 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:159
AnimData * BKE_animdata_ensure_id(ID *id)
Definition anim_data.cc:97
bool id_can_have_animdata(const ID *id)
Definition anim_data.cc:73
bool BKE_animdata_action_editable(const AnimData *adt)
Definition anim_data.cc:151
AnimData * BKE_animdata_from_id(const ID *id)
Definition anim_data.cc:83
FCurve * BKE_fcurve_copy(const FCurve *fcu)
bool BKE_fcurve_is_keyframable(const FCurve *fcu)
FModifier * add_fmodifier(ListBase *modifiers, int type, FCurve *owner_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:358
void id_us_clear_real(ID *id)
Definition lib_id.cc:331
void id_us_min(ID *id)
Definition lib_id.cc:366
#define FOREACH_MAIN_LISTBASE_ID_END
Definition BKE_main.hh:552
#define FOREACH_MAIN_LISTBASE_ID_BEGIN(_lb, _id)
Definition BKE_main.hh:546
#define FOREACH_MAIN_LISTBASE_END
Definition BKE_main.hh:564
#define FOREACH_MAIN_LISTBASE_BEGIN(_bmain, _lb)
Definition BKE_main.hh:557
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
@ RPT_ERROR
Definition BKE_report.hh:39
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
#define LISTBASE_FOREACH_INDEX(type, var, list, index_var)
bool BLI_remlink_safe(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:154
int BLI_listbase_count(const ListBase *listbase) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:524
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)
ATTR_WARN_UNUSED_RESULT const size_t num
bool bool BLI_str_quoted_substr(const char *__restrict str, const char *__restrict prefix, char *result, size_t result_maxncpy)
Definition string.cc:521
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(blender::FunctionRef< bool(blender::StringRefNull)> unique_check, const char *defname, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(2
#define ARRAY_SIZE(arr)
#define UNUSED_VARS_NDEBUG(...)
#define ELEM(...)
#define STREQ(a, b)
#define DATA_(msgid)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
@ ID_RECALC_ANIMATION
Definition DNA_ID.h:1077
@ ID_RECALC_ANIMATION_NO_FLUSH
Definition DNA_ID.h:1176
#define MAX_ID_NAME
Definition DNA_ID.h:373
#define ID_IS_EDITABLE(_id)
Definition DNA_ID.h:705
#define ID_IS_OVERRIDE_LIBRARY(_id)
Definition DNA_ID.h:730
ID_Type
@ AGRP_SELECTED
@ ACT_FRAME_RANGE
@ ACT_CYCLIC
struct ActionSlotRuntimeHandle ActionSlotRuntimeHandle
struct bActionGroup bActionGroup
@ FCM_EXTRAPOLATE_NONE
@ FCM_LIMIT_XMIN
@ FCM_LIMIT_XMAX
eInsertKeyFlags
@ FMODIFIER_TYPE_CYCLES
@ FMODIFIER_TYPE_LIMITS
@ FCURVE_ACTIVE
struct FCurve FCurve
#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)
#define MEM_reallocN(vmemh, len)
Internal C++ functions to deal with Actions, Slots, and their runtime data.
bool operator==(const AssetWeakReference &a, const AssetWeakReference &b)
static bool visit_strip(NlaStrip *strip, blender::FunctionRef< bool(NlaStrip *)> callback)
long long int int64_t
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
bool add(const Key &key, const Value &value)
Definition BLI_map.hh:295
const Value & lookup(const Key &key) const
Definition BLI_map.hh:545
void reserve(const int64_t n)
Definition BLI_set.hh:637
bool add(const Key &key)
Definition BLI_set.hh:248
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr int64_t size() const
constexpr const char * c_str() const
void append(const T &value)
void resize(const int64_t new_size)
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_display_name_define(Slot &slot, StringRefNull new_display_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)
void slot_identifier_propagate(Main &bmain, const Slot &slot)
const Layer * layer(int64_t index) const
void slot_identifier_set(Main &bmain, Slot &slot, StringRefNull new_identifier)
int64_t find_slot_index(const Slot &slot) const
const Slot * slot(int64_t index) const
void slot_idtype_define(Slot &slot, ID_Type idtype)
blender::Span< const Layer * > layers() const
void slot_move_to_index(Slot &slot, int to_slot_index)
bool has_keyframes(slot_handle_t action_slot_handle) const ATTR_WARN_UNUSED_RESULT
void slot_display_name_set(Main &bmain, Slot &slot, StringRefNull new_display_name)
void strip_keyframe_data_remove_if_unused(int index)
int64_t find_layer_index(const Layer &layer) const
Slot & slot_add_for_id_type(ID_Type idtype)
blender::Span< const Slot * > slots() const
bool layer_remove(Layer &layer_to_remove)
Slot * slot_find_by_identifier(StringRefNull slot_identifier)
float2 get_frame_range_of_keys(bool include_modifiers) const ATTR_WARN_UNUSED_RESULT
void slot_identifier_define(Slot &slot, StringRefNull new_identifier)
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)
bool channel_group_remove(bActionGroup &group)
const FCurve * fcurve(int64_t index) const
void fcurve_remove_by_index(int64_t fcurve_index)
bool fcurve_detach(FCurve &fcurve_to_detach)
void fcurve_move_to_index(FCurve &fcurve, int to_fcurve_index)
bool fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
bActionGroup & channel_group_create(StringRefNull name)
bool fcurve_remove(FCurve &fcurve_to_remove)
const bActionGroup * channel_group(int64_t index) const
FCurve & fcurve_create(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
const bActionGroup * channel_group_find(StringRef name) const
Vector< FCurve * > fcurve_create_many(Main *bmain, Span< FCurveDescriptor > fcurve_descriptors)
void fcurve_detach_by_index(int64_t fcurve_index)
int channel_group_find_index(const bActionGroup *group) const
FCurve & fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
FCurve * fcurve_create_unique(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
const FCurve * fcurve_find(const FCurveDescriptor &fcurve_descriptor) const
blender::Span< const FCurve * > fcurves() const
void channel_group_move_to_index(bActionGroup &group, int to_group_index)
int channel_group_containing_index(int fcurve_array_index)
blender::Span< const bActionGroup * > channel_groups() const
bActionGroup & channel_group_ensure(StringRefNull name)
bool strip_remove(Action &owning_action, Strip &strip)
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
static void users_invalidate(Main &bmain)
void users_add(ID &animated_id)
bool is_suitable_for(const ID &animated_id) const
std::string idtype_string() const
static constexpr int identifier_length_min
static constexpr int identifier_length_max
Span< ID * > users(Main &bmain) const
static constexpr slot_handle_t unassigned
StringRefNull identifier_without_prefix() const
void users_remove(ID &animated_id)
const Channelbag * channelbag_for_slot(const Slot &slot) const
int64_t find_channelbag_index(const Channelbag &channelbag) const
void slot_data_remove(slot_handle_t slot_handle)
void slot_data_duplicate(slot_handle_t source_slot_handle, slot_handle_t target_slot_handle)
static constexpr Strip::Type TYPE
Channelbag & channelbag_for_slot_add(const Slot &slot)
SingleKeyingResult keyframe_insert(Main *bmain, const Slot &slot, const FCurveDescriptor &fcurve_descriptor, float2 time_value, const KeyframeSettings &settings, eInsertKeyFlags insert_key_flags=INSERTKEY_NOFLAGS, std::optional< float2 > cycle_range=std::nullopt)
Channelbag & channelbag_for_slot_ensure(const Slot &slot)
blender::Span< const Channelbag * > channelbags() const
bool channelbag_remove(Channelbag &channelbag_to_remove)
const Channelbag * channelbag(int64_t index) const
const T & data(const Action &owning_action) const
int users
#define GS(x)
#define active
#define printf(...)
float length(VecOp< float, D >) RET
#define LOG(level)
Definition log.h:97
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
#define T
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()
void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src)
static void slot_ptr_destructor(ActionSlot **dna_slot_ptr)
Vector< FCurve * > fcurves_in_listbase_filtered(ListBase fcurves, FunctionRef< bool(const FCurve &fcurve)> predicate)
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 * create_fcurve_for_channel(const FCurveDescriptor &fcurve_descriptor)
void action_fcurve_move(Action &action_dst, slot_handle_t action_slot_dst, Action &action_src, FCurve &fcurve)
void update_autoflags_fcurve_direct(FCurve *fcu, PropertyType prop_type)
Action & action_add(Main &bmain, StringRefNull name)
const animrig::Channelbag * channelbag_for_action_slot(const Action &action, slot_handle_t slot_handle)
ActionSlotAssignmentResult assign_tmpaction_and_slot_handle(bAction *action, slot_handle_t slot_handle, OwnedAnimData owned_adt)
static void layer_ptr_destructor(ActionLayer **dna_layer_ptr)
slot_handle_t first_slot_handle(const ::bAction &dna_action)
static void cyclic_keying_ensure_modifier(FCurve &fcurve)
Slot & duplicate_slot(Action &action, const Slot &slot)
static void grow_array_and_append(T **array, int *num, T item)
Vector< FCurve * > fcurves_in_span_filtered(Span< FCurve * > fcurves, FunctionRef< bool(const FCurve &fcurve)> predicate)
bool generic_assign_action(ID &animated_id, bAction *action_to_assign, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_identifier)
static void clone_slot(const Slot &from, Slot &to)
static float2 get_frame_range_of_fcurves(Span< const FCurve * > fcurves, bool include_modifiers)
FCurve * action_fcurve_ensure_legacy(Main *bmain, bAction *act, const char group[], PointerRNA *ptr, const FCurveDescriptor &fcurve_descriptor)
static void cyclic_keying_ensure_cycle_range_exists(FCurve &fcurve, const float2 cycle_range)
ID * action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign, ID &animated_id, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_identifier)
ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id)
decltype(::ActionSlot::handle) slot_handle_t
bool is_action_assignable_to(const bAction *dna_action, ID_Type id_code)
Channelbag & action_channelbag_ensure(bAction &dna_action, ID &animated_id)
SingleKeyingResult insert_vert_fcurve(FCurve *fcu, const float2 position, const KeyframeSettings &settings, eInsertKeyFlags flag)
Main Key-framing API call.
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)
static void grow_array_and_insert(T **array, int *num, const int index, T item)
const FCurve * fcurve_find(Span< const FCurve * > fcurves, const FCurveDescriptor &fcurve_descriptor)
Action * get_action(ID &animated_id)
bool assign_tmpaction(bAction *action, OwnedAnimData owned_adt)
static void fcurve_ptr_noop_destructor(FCurve **)
FCurve * action_fcurve_ensure_ex(Main *bmain, bAction *act, const char group[], PointerRNA *ptr, const FCurveDescriptor &fcurve_descriptor)
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)
bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
bool assign_action(bAction *action, ID &animated_id)
FCurve * fcurve_find_in_assigned_slot(AnimData &adt, const FCurveDescriptor &fcurve_descriptor)
std::optional< std::pair< Action *, Slot * > > get_action_slot_pair(ID &animated_id)
static void grow_array(T **array, int *num, const int add_num)
FCurve & action_fcurve_ensure(Main *bmain, bAction &action, ID &animated_id, const FCurveDescriptor &fcurve_descriptor)
bool key_insertion_may_create_fcurve(eInsertKeyFlags insert_key_flags)
FCurve * fcurve_find_in_action(bAction *act, const FCurveDescriptor &fcurve_descriptor)
static void shrink_array(T **array, int *num, const int shrink_num)
static void channelbag_ptr_destructor(ActionChannelbag **dna_channelbag_ptr)
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_identifier)
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
bool foreach_action_slot_use_with_references(ID &animated_id, FunctionRef< bool(ID &animated_id, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *last_slot_identifier)> callback)
void move_slot(Main &bmain, Slot &slot, Action &from_action, Action &to_action)
Slot * generic_slot_for_autoassign(const ID &animated_id, Action &action, StringRefNull last_slot_identifier)
bool foreach_action_slot_use(const ID &animated_id, FunctionRef< bool(const Action &action, slot_handle_t slot_handle)> callback)
static void slot_identifier_ensure_unique(Action &action, Slot &slot)
FCurve * fcurve_find_in_action_slot(bAction *act, slot_handle_t slot_handle, const 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 *))
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
VecBase< float, 2 > float2
void uninitialized_relocate_n(T *src, int64_t n, T *dst)
void uninitialized_move_n(T *src, int64_t n, T *dst)
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)
static void unique_name(bNode *node)
#define hash
Definition noise_c.cc:154
const char * name
#define floorf
PropertyType RNA_property_type(PropertyRNA *prop)
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:560
#define min(a, b)
Definition sort.cc:36
struct bActionGroup ** group_array
struct FCurve ** fcurve_array
struct ActionStrip ** strip_array
char identifier[258]
ActionSlotRuntimeHandle * runtime
struct ActionChannelbag ** channelbag_array
char tmp_last_slot_identifier[258]
bAction * action
int32_t slot_handle
int32_t tmp_slot_handle
bAction * tmpact
char last_slot_identifier[258]
float vec[3][3]
bActionGroup * grp
char * rna_path
BezTriple * bezt
int array_index
unsigned int totvert
ListBase modifiers
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
bool is_action_slot_to_id_map_dirty
Definition BKE_main.hh:253
AnimData & adt
StripData * data
struct ActionChannelbag * channelbag
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
std::optional< PropertyType > prop_type
float xmax
float xmin
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
PointerRNA * ptr
Definition wm_files.cc:4238