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