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 BLI_assert(dna_action->id.us == 1);
1190 id_us_min(&dna_action->id);
1191 return dna_action->wrap();
1192}
1193
1194bool assign_action(bAction *action, ID &animated_id)
1195{
1196 AnimData *adt = BKE_animdata_ensure_id(&animated_id);
1197 if (!adt) {
1198 return false;
1199 }
1200 return assign_action(action, {animated_id, *adt});
1201}
1202
1203bool assign_action(bAction *action, const OwnedAnimData owned_adt)
1204{
1205 if (!BKE_animdata_action_editable(&owned_adt.adt)) {
1206 /* Cannot remove, otherwise things turn to custard. */
1207 BKE_report(nullptr, RPT_ERROR, "Cannot change action, as it is still being edited in NLA");
1208 return false;
1209 }
1210
1211 return generic_assign_action(owned_adt.owner_id,
1212 action,
1213 owned_adt.adt.action,
1214 owned_adt.adt.slot_handle,
1215 owned_adt.adt.last_slot_identifier);
1216}
1217
1218bool assign_tmpaction(bAction *action, const OwnedAnimData owned_adt)
1219{
1220 return generic_assign_action(owned_adt.owner_id,
1221 action,
1222 owned_adt.adt.tmpact,
1223 owned_adt.adt.tmp_slot_handle,
1224 owned_adt.adt.tmp_last_slot_identifier);
1225}
1226
1227bool unassign_action(ID &animated_id)
1228{
1229 return assign_action(nullptr, animated_id);
1230}
1231
1233{
1234 return assign_action(nullptr, owned_adt);
1235}
1236
1238{
1239 AnimData *adt = BKE_animdata_from_id(&animated_id);
1240 Slot *slot;
1241
1242 /* Find a suitable slot, but be stricter about when to allow searching by name
1243 * than generic_slot_for_autoassign(...). */
1244 if (adt && adt->action == &action) {
1245 /* The slot handle is only valid when this action is already assigned.
1246 * Otherwise it's meaningless. */
1247 slot = action.slot_for_handle(adt->slot_handle);
1248
1249 /* If this Action is already assigned, a search by name is inappropriate, as it might
1250 * re-assign an intentionally-unassigned slot. */
1251 }
1252 else {
1253 /* In this case a by-name search is ok, so defer to generic_slot_for_autoassign(). */
1254 slot = generic_slot_for_autoassign(animated_id, action, adt ? adt->last_slot_identifier : "");
1255 }
1256
1257 /* As a last resort, if there is only one slot and it has no ID type yet, use that. This is what
1258 * gets created for the backwards compatibility RNA API, for example to allow
1259 * `action.fcurves.new()`. Key insertion should use that slot as well. */
1260 if (!slot && action.slots().size() == 1) {
1261 Slot *first_slot = action.slot(0);
1262 if (!first_slot->has_idtype()) {
1263 slot = first_slot;
1264 }
1265 }
1266
1267 /* If no suitable slot was found, create a new one. */
1268 if (!slot || !slot->is_suitable_for(animated_id)) {
1269 slot = &action.slot_add_for_id(animated_id);
1270 }
1271
1272 /* Only try to assign the Action to the ID if it is not already assigned.
1273 * Assignment can fail when the ID is in NLA Tweak mode. */
1274 const bool is_correct_action = adt && adt->action == &action;
1275 if (!is_correct_action && !assign_action(&action, animated_id)) {
1276 return nullptr;
1277 }
1278
1279 const bool is_correct_slot = adt && adt->slot_handle == slot->handle;
1280 if (!is_correct_slot && assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK)
1281 {
1282 /* This should never happen, as a few lines above a new slot is created for
1283 * this ID if the found one wasn't deemed suitable. */
1285 return nullptr;
1286 }
1287
1288 return slot;
1289}
1290
1291static bool is_id_using_action_slot(const ID &animated_id,
1292 const Action &action,
1293 const slot_handle_t slot_handle)
1294{
1295 auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool {
1296 const bool is_used = (&used_action == &action && used_slot_handle == slot_handle);
1297 return !is_used; /* Stop searching when we found a use of this Action+Slot. */
1298 };
1299
1300 const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use);
1301 return !looped_until_end;
1302}
1303
1304bool generic_assign_action(ID &animated_id,
1305 bAction *action_to_assign,
1306 bAction *&action_ptr_ref,
1307 slot_handle_t &slot_handle_ref,
1308 char *slot_identifier)
1309{
1310 BLI_assert(slot_identifier);
1311
1312 if (action_to_assign && legacy::action_treat_as_legacy(*action_to_assign)) {
1313 /* Check that the Action is suitable for this ID type.
1314 * This is only necessary for legacy Actions. */
1315 if (!BKE_animdata_action_ensure_idroot(&animated_id, action_to_assign)) {
1317 nullptr,
1318 RPT_ERROR,
1319 "Could not set action '%s' to animate ID '%s', as it does not have suitably rooted "
1320 "paths for this purpose",
1321 action_to_assign->id.name + 2,
1322 animated_id.name);
1323 return false;
1324 }
1325 }
1326
1327 /* Un-assign any previously-assigned Action first. */
1328 if (action_ptr_ref) {
1329 /* Un-assign the slot. This will always succeed, so no need to check the result. */
1330 if (slot_handle_ref != Slot::unassigned) {
1332 nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1335 }
1336
1337 /* Un-assign the Action itself. */
1338 id_us_min(&action_ptr_ref->id);
1339 action_ptr_ref = nullptr;
1340 }
1341
1342 if (!action_to_assign) {
1343 /* Un-assigning was the point, so the work is done. */
1344 return true;
1345 }
1346
1347 /* Assign the new Action. */
1348 action_ptr_ref = action_to_assign;
1349 id_us_plus(&action_ptr_ref->id);
1350
1351 /* Auto-assign a slot. */
1352 Slot *slot = generic_slot_for_autoassign(animated_id, action_ptr_ref->wrap(), slot_identifier);
1354 slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1357
1358 return true;
1359}
1360
1362 Action &action,
1363 const StringRefNull last_slot_identifier)
1364{
1365 /* The slot-finding code in assign_action_ensure_slot_for_keying() is very
1366 * similar to the code here (differences are documented there). It is very
1367 * likely that changes in the logic here should be applied there as well. */
1368
1369 /* Try the slot identifier, if it is set. */
1370 if (!last_slot_identifier.is_empty()) {
1371 /* If the last-used slot identifier was 'untyped', i.e. started with XX, see if something more
1372 * specific to this ID type exists.
1373 *
1374 * If there is any choice in the matter, the more specific slot is chosen. In other words, in
1375 * this case:
1376 *
1377 * - last_slot_identifier = `XXSlot`
1378 * - both `XXSlot` and `OBSlot` exist on the Action (where `OB` represents the ID type of
1379 * `animated_id`).
1380 *
1381 * the `OBSlot` should be chosen. This means that `XXSlot` NOT being auto-assigned if there is
1382 * an alternative. Since untyped slots are bound on assignment, this design keeps the Action
1383 * as-is, which means that the `XXSlot` remains untyped and thus the user is free to assign
1384 * this to another ID type if desired. */
1385
1386 const bool last_used_identifier_is_typed = last_slot_identifier.substr(0, 2) !=
1387 slot_untyped_prefix;
1388 if (!last_used_identifier_is_typed) {
1389 const std::string with_idtype_prefix = StringRef(animated_id.name, 2) +
1390 last_slot_identifier.substr(2);
1391 Slot *slot = action.slot_find_by_identifier(with_idtype_prefix);
1392 if (slot && slot->is_suitable_for(animated_id)) {
1393 return slot;
1394 }
1395 }
1396
1397 /* See if the actual last-used slot identifier can be matched. */
1398 Slot *slot = action.slot_find_by_identifier(last_slot_identifier);
1399 if (slot && slot->is_suitable_for(animated_id)) {
1400 return slot;
1401 }
1402
1403 /* If the last-used slot identifier was IDSomething, and XXSomething exists (where ID = the
1404 * ID code of the animated ID), fall back to the XX. If slot `IDSomething` existed, the code
1405 * above would have already returned it. */
1406 if (last_used_identifier_is_typed) {
1407 const std::string with_untyped_prefix = StringRef(slot_untyped_prefix) +
1408 last_slot_identifier.substr(2);
1409 Slot *slot = action.slot_find_by_identifier(with_untyped_prefix);
1410 if (slot && slot->is_suitable_for(animated_id)) {
1411 return slot;
1412 }
1413 }
1414 }
1415
1416 /* Search for the ID name (which includes the ID type). */
1417 {
1418 Slot *slot = action.slot_find_by_identifier(animated_id.name);
1419 if (slot && slot->is_suitable_for(animated_id)) {
1420 return slot;
1421 }
1422 }
1423
1424 /* If there is only one slot, and it is not specific to any ID type, use that.
1425 *
1426 * This should only trigger in some special cases, like legacy Actions that were converted to
1427 * slotted Actions by the versioning code, where the legacy Action was never assigned to anything
1428 * (and thus had idroot = 0).
1429 *
1430 * This might seem overly specific, and for convenience of automatically auto-assigning a slot,
1431 * it might be tempting to remove the "slot->has_idtype()" check. However, that would make the
1432 * following workflow significantly more cumbersome:
1433 *
1434 * - Animate `Cube`. This creates `CubeAction` with a single slot `OBCube`.
1435 * - Assign `CubeAction` to `Suzanne`, with the intent of animating both `Cube` and `Suzanne`
1436 * with the same Action.
1437 * - This should **not** auto-assign the `OBCube` slot to `Suzanne`, as that will overwrite any
1438 * property of `Suzanne` with the animated values for the `OBCube` slot.
1439 *
1440 * Recovering from this will be hard, as an undo will revert both the overwriting of properties
1441 * and the assignment of the Action. */
1442 if (action.slots().size() == 1) {
1443 Slot *slot = action.slot(0);
1444 if (!slot->has_idtype()) {
1445 return slot;
1446 }
1447 }
1448
1449 return nullptr;
1450}
1451
1453 ID &animated_id,
1454 bAction *&action_ptr_ref,
1455 slot_handle_t &slot_handle_ref,
1456 char *slot_identifier)
1457{
1458 BLI_assert(slot_identifier);
1459 if (!action_ptr_ref) {
1460 /* No action assigned yet, so no way to assign a slot. */
1462 }
1463
1464 Action &action = action_ptr_ref->wrap();
1465
1466 /* Check that the slot can actually be assigned. */
1467 if (slot_to_assign) {
1468 if (!action.slots().contains(slot_to_assign)) {
1470 }
1471
1472 if (!slot_to_assign->is_suitable_for(animated_id)) {
1474 }
1475 }
1476
1477 Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref);
1478
1479 /* If there was a previously-assigned slot, unassign it first. */
1480 slot_handle_ref = Slot::unassigned;
1481 if (slot_to_unassign) {
1482 /* Make sure that the stored Slot identifier is up to date. The slot identifier might have
1483 * changed in a way that wasn't copied into the ADT yet (for example when the
1484 * Action is linked from another file), so better copy the identifier to be sure
1485 * that it can be transparently reassigned later.
1486 *
1487 * TODO: Replace this with a BLI_assert() that the identifier is as expected, and "simply"
1488 * ensure this identifier is always correct. */
1489 BLI_strncpy_utf8(slot_identifier, slot_to_unassign->identifier, Slot::identifier_length_max);
1490
1491 /* If this was the last use of this slot, remove this ID from its users. */
1492 if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) {
1493 slot_to_unassign->users_remove(animated_id);
1494 }
1495 }
1496
1497 if (!slot_to_assign) {
1499 }
1500
1501 action.slot_setup_for_id(*slot_to_assign, animated_id);
1502 slot_handle_ref = slot_to_assign->handle;
1503 BLI_strncpy_utf8(slot_identifier, slot_to_assign->identifier, Slot::identifier_length_max);
1504 slot_to_assign->users_add(animated_id);
1505
1507}
1508
1510 ID &animated_id,
1511 bAction *&action_ptr_ref,
1512 slot_handle_t &slot_handle_ref,
1513 char *slot_identifier)
1514{
1515 if (slot_handle_to_assign == Slot::unassigned && !action_ptr_ref) {
1516 /* No Action assigned, so no slot was used anyway. Just blindly assign the
1517 * 'unassigned' handle. */
1518 slot_handle_ref = Slot::unassigned;
1520 }
1521
1522 if (!action_ptr_ref) {
1523 /* No Action to verify the slot handle is valid. As the slot handle will be
1524 * completely ignored when re-assigning an Action, better to refuse setting
1525 * it altogether. This will make bugs more obvious. */
1527 }
1528
1529 Slot *slot = action_ptr_ref->wrap().slot_for_handle(slot_handle_to_assign);
1531 slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
1532}
1533
1534bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
1535{
1536 if (!dna_action) {
1537 /* Clearing the Action is always possible. */
1538 return true;
1539 }
1540
1541 if (dna_action->idroot == 0) {
1542 /* This is either a never-assigned legacy action, or a layered action. In
1543 * any case, it can be assigned to any ID. */
1544 return true;
1545 }
1546
1547 const animrig::Action &action = dna_action->wrap();
1548 if (legacy::action_treat_as_legacy(action)) {
1549 /* Legacy Actions can only be assigned if their idroot matches. Empty
1550 * Actions are considered both 'layered' and 'legacy' at the same time,
1551 * hence this condition checks for 'not layered' rather than 'legacy'. */
1552 return action.idroot == id_code;
1553 }
1554
1555 return true;
1556}
1557
1559{
1560 AnimData *adt = BKE_animdata_from_id(&animated_id);
1561 if (!adt) {
1563 }
1564
1566 slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->last_slot_identifier);
1567}
1568
1570 Slot *slot_to_assign,
1571 ID &animated_id)
1572{
1573 if (!assign_action(action, animated_id)) {
1575 }
1576 return assign_action_slot(slot_to_assign, animated_id);
1577}
1578
1580 const slot_handle_t slot_handle,
1581 const OwnedAnimData owned_adt)
1582{
1583 if (!assign_tmpaction(action, owned_adt)) {
1585 }
1586 return generic_assign_action_slot_handle(slot_handle,
1587 owned_adt.owner_id,
1588 owned_adt.adt.tmpact,
1589 owned_adt.adt.tmp_slot_handle,
1590 owned_adt.adt.tmp_last_slot_identifier);
1591}
1592
1593Action *get_action(ID &animated_id)
1594{
1595 AnimData *adt = BKE_animdata_from_id(&animated_id);
1596 if (!adt) {
1597 return nullptr;
1598 }
1599 if (!adt->action) {
1600 return nullptr;
1601 }
1602 return &adt->action->wrap();
1603}
1604
1605std::optional<std::pair<Action *, Slot *>> get_action_slot_pair(ID &animated_id)
1606{
1607 AnimData *adt = BKE_animdata_from_id(&animated_id);
1608 if (!adt || !adt->action) {
1609 /* Not animated by any Action. */
1610 return std::nullopt;
1611 }
1612
1613 Action &action = adt->action->wrap();
1614 Slot *slot = action.slot_for_handle(adt->slot_handle);
1615 if (!slot) {
1616 /* Will not receive any animation from this Action. */
1617 return std::nullopt;
1618 }
1619
1620 return std::make_pair(&action, slot);
1621}
1622
1623/* ----- ActionStrip implementation ----------- */
1624
1625Strip &Strip::create(Action &owning_action, const Strip::Type type)
1626{
1627 /* Create the strip. */
1628 ActionStrip *strip = MEM_callocN<ActionStrip>(__func__);
1630 strip->strip_type = int8_t(type);
1631
1632 /* Create the strip's data on the owning Action. */
1633 switch (type) {
1634 case Strip::Type::Keyframe: {
1635 StripKeyframeData *strip_data = MEM_new<StripKeyframeData>(__func__);
1636 strip->data_index = owning_action.strip_keyframe_data_append(strip_data);
1637 break;
1638 }
1639 }
1640
1641 /* This can happen if someone forgets to add a strip type in the `switch`
1642 * above, or if someone is evil and passes an invalid strip type to this
1643 * function. */
1644 BLI_assert_msg(strip->data_index != -1, "Newly created strip has no data.");
1645
1646 return strip->wrap();
1647}
1648
1649bool Strip::is_infinite() const
1650{
1651 return this->frame_start == -std::numeric_limits<float>::infinity() &&
1652 this->frame_end == std::numeric_limits<float>::infinity();
1653}
1654
1655bool Strip::contains_frame(const float frame_time) const
1656{
1657 return this->frame_start <= frame_time && frame_time <= this->frame_end;
1658}
1659
1660bool Strip::is_last_frame(const float frame_time) const
1661{
1662 /* Maybe this needs a more advanced equality check. Implement that when
1663 * we have an actual example case that breaks. */
1664 return this->frame_end == frame_time;
1665}
1666
1667void Strip::resize(const float frame_start, const float frame_end)
1668{
1670 BLI_assert_msg(frame_start < std::numeric_limits<float>::infinity(),
1671 "only the end frame can be at positive infinity");
1672 BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
1673 "only the start frame can be at negative infinity");
1674 this->frame_start = frame_start;
1675 this->frame_end = frame_end;
1676}
1677
1678template<>
1680{
1682
1683 return *owning_action.strip_keyframe_data()[this->data_index];
1684}
1686{
1688
1689 return *owning_action.strip_keyframe_data()[this->data_index];
1690}
1691
1692/* ----- ActionStripKeyframeData implementation ----------- */
1693
1696{
1698 __func__);
1699 Span<const Channelbag *> channelbags_src = other.channelbags();
1700 for (int i : channelbags_src.index_range()) {
1701 this->channelbag_array[i] = MEM_new<animrig::Channelbag>(__func__, *other.channelbag(i));
1702 }
1703}
1704
1706{
1707 for (Channelbag *channelbag_for_slot : this->channelbags()) {
1708 MEM_delete(channelbag_for_slot);
1709 }
1711 this->channelbag_array_num = 0;
1712}
1713
1725{
1726 return &this->channelbag_array[index]->wrap();
1727}
1729{
1730 return &this->channelbag_array[index]->wrap();
1731}
1733{
1734 for (const Channelbag *channels : this->channelbags()) {
1735 if (channels->slot_handle == slot_handle) {
1736 return channels;
1737 }
1738 }
1739 return nullptr;
1740}
1742{
1743 for (int64_t index = 0; index < this->channelbag_array_num; index++) {
1744 if (this->channelbag(index) == &channelbag) {
1745 return index;
1746 }
1747 }
1748 return -1;
1749}
1751{
1752 const auto *const_this = const_cast<const StripKeyframeData *>(this);
1753 const auto *const_channels = const_this->channelbag_for_slot(slot_handle);
1754 return const_cast<Channelbag *>(const_channels);
1755}
1757{
1758 return this->channelbag_for_slot(slot.handle);
1759}
1761{
1762 return this->channelbag_for_slot(slot.handle);
1763}
1764
1769
1771{
1772 BLI_assert_msg(channelbag_for_slot(slot_handle) == nullptr,
1773 "Cannot add channelbag for already-registered slot");
1774 BLI_assert_msg(slot_handle != Slot::unassigned, "Cannot add channelbag for 'unassigned' slot");
1775
1776 Channelbag &channels = MEM_new<ActionChannelbag>(__func__)->wrap();
1777 channels.slot_handle = slot_handle;
1778
1780 &this->channelbag_array, &this->channelbag_array_num, &channels);
1781
1782 return channels;
1783}
1784
1789
1791{
1792 Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
1793 if (channelbag != nullptr) {
1794 return *channelbag;
1795 }
1796 return this->channelbag_for_slot_add(slot_handle);
1797}
1798
1799static void channelbag_ptr_destructor(ActionChannelbag **dna_channelbag_ptr)
1800{
1801 Channelbag &channelbag = (*dna_channelbag_ptr)->wrap();
1802 MEM_delete(&channelbag);
1803};
1804
1806{
1807 const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove);
1808 if (channelbag_index < 0) {
1809 return false;
1810 }
1811
1813 &this->channelbag_array_num,
1814 nullptr,
1815 channelbag_index,
1817
1818 return true;
1819}
1820
1822{
1823 Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
1824 if (!channelbag) {
1825 return;
1826 }
1827 this->channelbag_remove(*channelbag);
1828}
1829
1831 const slot_handle_t target_slot_handle)
1832{
1833 BLI_assert(!this->channelbag_for_slot(target_slot_handle));
1834
1835 const Channelbag *source_cbag = this->channelbag_for_slot(source_slot_handle);
1836 if (!source_cbag) {
1837 return;
1838 }
1839
1840 Channelbag &target_cbag = *MEM_new<animrig::Channelbag>(__func__, *source_cbag);
1841 target_cbag.slot_handle = target_slot_handle;
1842
1844 &this->channelbag_array, &this->channelbag_array_num, &target_cbag);
1845}
1846
1847const FCurve *Channelbag::fcurve_find(const FCurveDescriptor &fcurve_descriptor) const
1848{
1849 return animrig::fcurve_find(this->fcurves(), fcurve_descriptor);
1850}
1851
1853{
1854 /* Intermediate variable needed to disambiguate const/non-const overloads. */
1855 Span<FCurve *> fcurves = this->fcurves();
1856 return animrig::fcurve_find(fcurves, fcurve_descriptor);
1857}
1858
1859FCurve &Channelbag::fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
1860{
1861 if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) {
1862 return *existing_fcurve;
1863 }
1864 return this->fcurve_create(bmain, fcurve_descriptor);
1865}
1866
1868{
1869 if (this->fcurve_find(fcurve_descriptor)) {
1870 return nullptr;
1871 }
1872 return &this->fcurve_create(bmain, fcurve_descriptor);
1873}
1874
1875FCurve &Channelbag::fcurve_create(Main *bmain, const FCurveDescriptor &fcurve_descriptor)
1876{
1877 FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor);
1878
1879 if (this->fcurve_array_num == 0) {
1880 new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
1881 }
1882
1883 bActionGroup *group = fcurve_descriptor.channel_group.has_value() ?
1884 &this->channel_group_ensure(*fcurve_descriptor.channel_group) :
1885 nullptr;
1886 const int insert_index = group ? group->fcurve_range_start + group->fcurve_range_length :
1887 this->fcurve_array_num;
1888 BLI_assert(insert_index <= this->fcurve_array_num);
1889
1890 grow_array_and_insert(&this->fcurve_array, &this->fcurve_array_num, insert_index, new_fcurve);
1891 if (group) {
1892 group->fcurve_range_length += 1;
1893 this->restore_channel_group_invariants();
1894 }
1895
1896 if (bmain) {
1898 }
1899
1900 return *new_fcurve;
1901}
1902
1904 Span<FCurveDescriptor> fcurve_descriptors)
1905{
1906 const int prev_fcurve_num = this->fcurve_array_num;
1907 const int add_fcurve_num = int(fcurve_descriptors.size());
1908 const bool make_first_active = prev_fcurve_num == 0;
1909
1910 /* Figure out which path+index combinations already exist. */
1911 struct CurvePathIndex {
1912 StringRefNull rna_path;
1913 int array_index;
1914 bool operator==(const CurvePathIndex &o) const
1915 {
1916 /* Check indices first, cheaper than a string comparison. */
1917 return this->array_index == o.array_index && this->rna_path == o.rna_path;
1918 }
1919 uint64_t hash() const
1920 {
1921 return get_default_hash(this->rna_path, this->array_index);
1922 }
1923 };
1924 Set<CurvePathIndex> unique_curves;
1925 unique_curves.reserve(prev_fcurve_num);
1926 for (FCurve *fcurve : this->fcurves()) {
1927 CurvePathIndex path_index;
1928 path_index.rna_path = StringRefNull(fcurve->rna_path ? fcurve->rna_path : "");
1929 path_index.array_index = fcurve->array_index;
1930 unique_curves.add(path_index);
1931 }
1932
1933 /* Grow curves array with enough space for new curves. */
1934 grow_array(&this->fcurve_array, &this->fcurve_array_num, add_fcurve_num);
1935
1936 /* Add the new curves. */
1937 Vector<FCurve *> new_fcurves;
1938 new_fcurves.resize(add_fcurve_num);
1939 int curve_index = prev_fcurve_num;
1940 for (int i = 0; i < add_fcurve_num; i++) {
1941 const FCurveDescriptor &desc = fcurve_descriptors[i];
1942
1943 CurvePathIndex path_index;
1944 path_index.rna_path = desc.rna_path;
1945 path_index.array_index = desc.array_index;
1946 if (desc.rna_path.is_empty() || !unique_curves.add(path_index)) {
1947 /* Empty input path, or such curve already exists. */
1948 new_fcurves[i] = nullptr;
1949 continue;
1950 }
1951
1953 new_fcurves[i] = fcurve;
1954
1955 this->fcurve_array[curve_index] = fcurve;
1956 if (desc.channel_group.has_value()) {
1957 bActionGroup *group = &this->channel_group_ensure(*desc.channel_group);
1958 const int insert_index = group->fcurve_range_start + group->fcurve_range_length;
1959 BLI_assert(insert_index <= this->fcurve_array_num);
1960 /* Insert curve into proper array place at the end of the group. Note: this can
1961 * still lead to quadratic complexity, in practice was not found to be an issue yet. */
1963 this->fcurve_array, this->fcurve_array_num, curve_index, curve_index + 1, insert_index);
1964 group->fcurve_range_length++;
1965
1966 /* Update curve start ranges of the following groups. */
1967 int index = this->channel_group_find_index(group);
1968 BLI_assert(index >= 0 && index < this->group_array_num);
1969 for (index = index + 1; index < this->group_array_num; index++) {
1970 this->group_array[index]->fcurve_range_start++;
1971 }
1972 }
1973 curve_index++;
1974 }
1975
1976 if (this->fcurve_array_num != curve_index) {
1977 /* Some curves were not created, resize to final amount. */
1979 &this->fcurve_array, &this->fcurve_array_num, this->fcurve_array_num - curve_index);
1980 }
1981
1982 if (make_first_active) {
1983 /* Set first created curve as active. */
1984 for (FCurve *fcurve : new_fcurves) {
1985 if (fcurve != nullptr) {
1986 fcurve->flag |= FCURVE_ACTIVE;
1987 break;
1988 }
1989 }
1990 }
1991
1992 this->restore_channel_group_invariants();
1993 if (bmain) {
1995 }
1996 return new_fcurves;
1997}
1998
2000{
2001 /* Appended F-Curves don't belong to any group yet, so better make sure their
2002 * group pointer reflects that. */
2003 fcurve.grp = nullptr;
2004
2006}
2007
2008static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
2009{
2010 BKE_fcurve_free(*fcurve_ptr);
2011};
2012
2013bool Channelbag::fcurve_remove(FCurve &fcurve_to_remove)
2014{
2015 if (!this->fcurve_detach(fcurve_to_remove)) {
2016 return false;
2017 }
2018 BKE_fcurve_free(&fcurve_to_remove);
2019 return true;
2020}
2021
2023{
2024 /* Grab the pointer before it's detached, so we can free it after. */
2025 FCurve *fcurve_to_remove = this->fcurve(fcurve_index);
2026
2027 this->fcurve_detach_by_index(fcurve_index);
2028
2029 BKE_fcurve_free(fcurve_to_remove);
2030}
2031
2032static void fcurve_ptr_noop_destructor(FCurve ** /*fcurve_ptr*/) {}
2033
2034bool Channelbag::fcurve_detach(FCurve &fcurve_to_detach)
2035{
2036 const int64_t fcurve_index = this->fcurves().first_index_try(&fcurve_to_detach);
2037 if (fcurve_index < 0) {
2038 return false;
2039 }
2040 this->fcurve_detach_by_index(fcurve_index);
2041 return true;
2042}
2043
2045{
2046 BLI_assert(fcurve_index >= 0);
2047 BLI_assert(fcurve_index < this->fcurve_array_num);
2048
2049 const int group_index = this->channel_group_containing_index(fcurve_index);
2050 if (group_index != -1) {
2051 bActionGroup *group = this->channel_group(group_index);
2052
2053 group->fcurve_range_length -= 1;
2054 if (group->fcurve_range_length <= 0) {
2055 const int group_index = this->channel_groups().first_index_try(group);
2056 this->channel_group_remove_raw(group_index);
2057 }
2058 }
2059
2061 &this->fcurve_array_num,
2062 nullptr,
2063 fcurve_index,
2065
2066 this->restore_channel_group_invariants();
2067
2068 /* As an optimization, this function could call `DEG_relations_tag_update(bmain)` to prune any
2069 * relationships that are now no longer necessary. This is not needed for correctness of the
2070 * depsgraph evaluation results though. */
2071}
2072
2074{
2075 BLI_assert(to_fcurve_index >= 0 && to_fcurve_index < this->fcurves().size());
2076
2077 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2078 BLI_assert_msg(fcurve_index >= 0, "FCurve not in this channel bag.");
2079
2081 this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_fcurve_index);
2082
2083 this->restore_channel_group_invariants();
2084}
2085
2087{
2089
2090 /* Since all F-Curves are gone, the groups are all empty. */
2091 for (bActionGroup *group : channel_groups()) {
2092 group->fcurve_range_start = 0;
2093 group->fcurve_range_length = 0;
2094 }
2095}
2096
2098{
2099 /* #BKE_fcurve_get_cycle_type() only looks at the first modifier to see if it's a Cycle modifier,
2100 * so if we're going to add one, better make sure it's the first one.
2101 *
2102 * BUT: #add_fmodifier() only allows adding a Cycle modifier when there are none yet, so that's
2103 * all that we need to check for here.
2104 */
2105 if (!BLI_listbase_is_empty(&fcurve.modifiers)) {
2106 return;
2107 }
2108
2110}
2111
2122static void cyclic_keying_ensure_cycle_range_exists(FCurve &fcurve, const float2 cycle_range)
2123{
2124 /* This is basically a copy of the legacy function `make_new_fcurve_cyclic()`
2125 * in `keyframing.cc`, except that it's limited to only one thing (ensuring
2126 * two keys exist to make cycling possible). Creating the F-Curve modifier is
2127 * the responsibility of another function. */
2128
2129 if (fcurve.totvert != 1 || fcurve.bezt == nullptr) {
2130 return;
2131 }
2132
2133 const float period = cycle_range[1] - cycle_range[0];
2134 if (period < 0.1f) {
2135 return;
2136 }
2137
2138 /* Move the one existing keyframe into the cycle range. */
2139 const float frame_offset = fcurve.bezt[0].vec[1][0] - cycle_range[0];
2140 const float fix = floorf(frame_offset / period) * period;
2141
2142 fcurve.bezt[0].vec[0][0] -= fix;
2143 fcurve.bezt[0].vec[1][0] -= fix;
2144 fcurve.bezt[0].vec[2][0] -= fix;
2145
2146 /* Reallocate the array to make space for the 2nd point. */
2147 fcurve.totvert++;
2148 fcurve.bezt = static_cast<BezTriple *>(
2149 MEM_reallocN(fcurve.bezt, sizeof(BezTriple) * fcurve.totvert));
2150
2151 /* Duplicate and offset the keyframe. */
2152 fcurve.bezt[1] = fcurve.bezt[0];
2153 fcurve.bezt[1].vec[0][0] += period;
2154 fcurve.bezt[1].vec[1][0] += period;
2155 fcurve.bezt[1].vec[2][0] += period;
2156}
2157
2159 const Slot &slot,
2160 const FCurveDescriptor &fcurve_descriptor,
2161 const float2 time_value,
2162 const KeyframeSettings &settings,
2163 const eInsertKeyFlags insert_key_flags,
2164 const std::optional<float2> cycle_range)
2165{
2166 /* Get the fcurve, or create one if it doesn't exist and the keying flags
2167 * allow. */
2168 FCurve *fcurve = nullptr;
2169 if (key_insertion_may_create_fcurve(insert_key_flags)) {
2170 fcurve = &this->channelbag_for_slot_ensure(slot).fcurve_ensure(bmain, fcurve_descriptor);
2171 }
2172 else {
2173 Channelbag *channels = this->channelbag_for_slot(slot);
2174 if (channels != nullptr) {
2175 fcurve = channels->fcurve_find(fcurve_descriptor);
2176 }
2177 }
2178
2179 if (!fcurve) {
2180 std::fprintf(stderr,
2181 "FCurve %s[%d] for slot %s was not created due to either the Only Insert "
2182 "Available setting or Replace keyframing mode.\n",
2183 fcurve_descriptor.rna_path.c_str(),
2184 fcurve_descriptor.array_index,
2185 slot.identifier);
2187 }
2188
2189 if (!BKE_fcurve_is_keyframable(fcurve)) {
2190 /* TODO: handle this properly, in a way that can be communicated to the user. */
2191 std::fprintf(stderr,
2192 "FCurve %s[%d] for slot %s doesn't allow inserting keys.\n",
2193 fcurve_descriptor.rna_path.c_str(),
2194 fcurve_descriptor.array_index,
2195 slot.identifier);
2197 }
2198
2199 if (cycle_range && (*cycle_range)[0] < (*cycle_range)[1]) {
2200 /* Cyclic keying consists of three things:
2201 * - Ensure there is a Cycle modifier on the F-Curve.
2202 * - Ensure the start and end of the cycle have explicit keys, so that the
2203 * cycle modifier knows how to cycle (it doesn't look at the Action, and
2204 * as long as the period is correct, the first/last keys don't have to
2205 * align with the Action start/end).
2206 * - Offset the key to insert so that it falls within the cycle range.
2207 */
2209 cyclic_keying_ensure_cycle_range_exists(*fcurve, *cycle_range);
2210 /* Offsetting the key doesn't have to happen here, as insert_vert_fcurve()
2211 * takes care of that. */
2212 }
2213
2214 const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
2215 fcurve, time_value, settings, insert_key_flags);
2216
2217 if (insert_vert_result != SingleKeyingResult::SUCCESS) {
2218 std::fprintf(stderr,
2219 "Could not insert key into FCurve %s[%d] for slot %s.\n",
2220 fcurve_descriptor.rna_path.c_str(),
2221 fcurve_descriptor.array_index,
2222 slot.identifier);
2223 return insert_vert_result;
2224 }
2225
2226 if (fcurve_descriptor.prop_type) {
2227 update_autoflags_fcurve_direct(fcurve, *fcurve_descriptor.prop_type);
2228 }
2229
2231}
2232
2233/* ActionChannelbag implementation. */
2234
2236{
2237 this->slot_handle = other.slot_handle;
2238
2239 this->fcurve_array_num = other.fcurve_array_num;
2241 for (int i = 0; i < other.fcurve_array_num; i++) {
2242 const FCurve *fcu_src = other.fcurve_array[i];
2243 this->fcurve_array[i] = BKE_fcurve_copy(fcu_src);
2244 }
2245
2246 this->group_array_num = other.group_array_num;
2248 for (int i = 0; i < other.group_array_num; i++) {
2249 const bActionGroup *group_src = other.group_array[i];
2250 this->group_array[i] = static_cast<bActionGroup *>(MEM_dupallocN(group_src));
2251 this->group_array[i]->channelbag = this;
2252 }
2253
2254 /* BKE_fcurve_copy() resets the FCurve's group pointer. Which is good, because the groups are
2255 * duplicated too. This sets the group pointers to the correct values. */
2256 this->restore_channel_group_invariants();
2257}
2258
2260{
2261 for (FCurve *fcu : this->fcurves()) {
2262 BKE_fcurve_free(fcu);
2263 }
2265 this->fcurve_array_num = 0;
2266
2267 for (bActionGroup *group : this->channel_groups()) {
2268 MEM_SAFE_FREE(group);
2269 }
2271 this->group_array_num = 0;
2272}
2273
2282const FCurve *Channelbag::fcurve(const int64_t index) const
2283{
2284 return this->fcurve_array[index];
2285}
2287{
2288 return this->fcurve_array[index];
2289}
2290
2300{
2301 BLI_assert(index < this->group_array_num);
2302 return this->group_array[index];
2303}
2305{
2306 BLI_assert(index < this->group_array_num);
2307 return this->group_array[index];
2308}
2309
2311{
2312 for (const bActionGroup *group : this->channel_groups()) {
2313 if (name == StringRef{group->name}) {
2314 return group;
2315 }
2316 }
2317
2318 return nullptr;
2319}
2320
2322{
2323 for (int i = 0; i < this->group_array_num; i++) {
2324 if (this->group_array[i] == group) {
2325 return i;
2326 }
2327 }
2328 return -1;
2329}
2330
2332{
2333 /* Intermediate variable needed to disambiguate const/non-const overloads. */
2334 Span<bActionGroup *> groups = this->channel_groups();
2335 for (bActionGroup *group : groups) {
2336 if (name == StringRef{group->name}) {
2337 return group;
2338 }
2339 }
2340
2341 return nullptr;
2342}
2343
2344int Channelbag::channel_group_containing_index(const int fcurve_array_index)
2345{
2346 int i = 0;
2347 for (const bActionGroup *group : this->channel_groups()) {
2348 if (fcurve_array_index >= group->fcurve_range_start &&
2349 fcurve_array_index < (group->fcurve_range_start + group->fcurve_range_length))
2350 {
2351 return i;
2352 }
2353 i++;
2354 }
2355
2356 return -1;
2357}
2358
2360{
2361 bActionGroup *new_group = MEM_callocN<bActionGroup>(__func__);
2362
2363 /* Find the end fcurve index of the current channel groups, to be used as the
2364 * start of the new channel group. */
2365 int fcurve_index = 0;
2366 const int length = this->channel_groups().size();
2367 if (length > 0) {
2368 const bActionGroup *last = this->channel_group(length - 1);
2369 fcurve_index = last->fcurve_range_start + last->fcurve_range_length;
2370 }
2371 new_group->fcurve_range_start = fcurve_index;
2372
2373 new_group->channelbag = this;
2374
2375 /* Make it selected. */
2376 new_group->flag = AGRP_SELECTED;
2377
2378 /* Ensure it has a unique name.
2379 *
2380 * Note that this only happens here (upon creation). The user can later rename
2381 * groups to have duplicate names. This is stupid, but it's how the legacy
2382 * system worked, and at the time of writing this code we're just trying to
2383 * match that system's behavior, even when it's goofy. */
2384 std::string unique_name = BLI_uniquename_cb(
2385 [&](const StringRef name) {
2386 for (const bActionGroup *group : this->channel_groups()) {
2387 if (STREQ(group->name, name.data())) {
2388 return true;
2389 }
2390 }
2391 return false;
2392 },
2393 '.',
2394 name[0] == '\0' ? DATA_("Group") : name);
2395
2396 STRNCPY_UTF8(new_group->name, unique_name.c_str());
2397
2398 grow_array_and_append(&this->group_array, &this->group_array_num, new_group);
2399
2400 return *new_group;
2401}
2402
2404{
2405 bActionGroup *group = this->channel_group_find(name);
2406 if (group) {
2407 return *group;
2408 }
2409
2410 return this->channel_group_create(name);
2411}
2412
2414{
2415 const int group_index = this->channel_groups().first_index_try(&group);
2416 if (group_index == -1) {
2417 return false;
2418 }
2419
2420 /* Move the group's fcurves to just past the end of where the grouped
2421 * fcurves will be after this group is removed. */
2422 const bActionGroup *last_group = this->channel_groups().last();
2423 BLI_assert(last_group != nullptr);
2424 const int to_index = last_group->fcurve_range_start + last_group->fcurve_range_length -
2425 group.fcurve_range_length;
2427 this->fcurve_array_num,
2428 group.fcurve_range_start,
2430 to_index);
2431
2432 this->channel_group_remove_raw(group_index);
2433 this->restore_channel_group_invariants();
2434
2435 return true;
2436}
2437
2438void Channelbag::channel_group_move_to_index(bActionGroup &group, const int to_group_index)
2439{
2440 BLI_assert(to_group_index >= 0 && to_group_index < this->channel_groups().size());
2441
2442 const int group_index = this->channel_groups().first_index_try(&group);
2443 BLI_assert_msg(group_index >= 0, "Group not in this channel bag.");
2444
2445 /* Shallow copy, to track which fcurves should be moved in the second step. */
2446 const bActionGroup pre_move_group = group;
2447
2448 /* First we move the group to its new position. The call to
2449 * `restore_channel_group_invariants()` is necessary to update the group's
2450 * fcurve range (as well as the ranges of the other groups) to match its new
2451 * position in the group array. */
2453 this->group_array, this->group_array_num, group_index, group_index + 1, to_group_index);
2454 this->restore_channel_group_invariants();
2455
2456 /* Move the fcurves that were part of `group` (as recorded in
2457 *`pre_move_group`) to their new positions (now in `group`) so that they're
2458 * part of `group` again. */
2460 this->fcurve_array_num,
2461 pre_move_group.fcurve_range_start,
2462 pre_move_group.fcurve_range_start + pre_move_group.fcurve_range_length,
2463 group.fcurve_range_start);
2464 this->restore_channel_group_invariants();
2465}
2466
2467void Channelbag::channel_group_remove_raw(const int group_index)
2468{
2469 BLI_assert(group_index >= 0 && group_index < this->channel_groups().size());
2470
2471 MEM_SAFE_FREE(this->group_array[group_index]);
2472 shrink_array_and_remove(&this->group_array, &this->group_array_num, group_index);
2473}
2474
2475void Channelbag::restore_channel_group_invariants()
2476{
2477 /* Shift channel groups. */
2478 {
2479 int start_index = 0;
2480 for (bActionGroup *group : this->channel_groups()) {
2481 group->fcurve_range_start = start_index;
2482 start_index += group->fcurve_range_length;
2483 }
2484
2485 /* Double-check that this didn't push any of the groups off the end of the
2486 * fcurve array. */
2487 BLI_assert(start_index <= this->fcurve_array_num);
2488 }
2489
2490 /* Recompute fcurves' group pointers. */
2491 {
2492 for (FCurve *fcurve : this->fcurves()) {
2493 fcurve->grp = nullptr;
2494 }
2495 for (bActionGroup *group : this->channel_groups()) {
2496 for (FCurve *fcurve : group->wrap().fcurves()) {
2497 fcurve->grp = group;
2498 }
2499 }
2500 }
2501}
2502
2504{
2505 return this->channelbag == nullptr;
2506}
2507
2509{
2510 BLI_assert(!this->is_legacy());
2511
2512 if (this->fcurve_range_length == 0) {
2513 return {};
2514 }
2515
2516 return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
2517 this->fcurve_range_length);
2518}
2519
2521{
2522 BLI_assert(!this->is_legacy());
2523
2524 if (this->fcurve_range_length == 0) {
2525 return {};
2526 }
2527
2528 return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
2529 this->fcurve_range_length);
2530}
2531
2532/* Utility function implementations. */
2533
2535 const slot_handle_t slot_handle)
2536{
2538
2539 if (slot_handle == Slot::unassigned) {
2540 return nullptr;
2541 }
2542
2543 for (const animrig::Layer *layer : action.layers()) {
2544 for (const animrig::Strip *strip : layer->strips()) {
2545 switch (strip->type()) {
2546 case animrig::Strip::Type::Keyframe: {
2547 const animrig::StripKeyframeData &strip_data = strip->data<animrig::StripKeyframeData>(
2548 action);
2549 const animrig::Channelbag *bag = strip_data.channelbag_for_slot(slot_handle);
2550 if (bag) {
2551 return bag;
2552 }
2553 }
2554 }
2555 }
2556 }
2557
2558 return nullptr;
2559}
2560
2562{
2564 const_cast<const Action &>(action), slot_handle);
2565 return const_cast<animrig::Channelbag *>(const_bag);
2566}
2567
2569{
2570 BLI_assert(action.is_action_layered());
2572 animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
2573 if (!bag) {
2574 return {};
2575 }
2576 return bag->fcurves();
2577}
2578
2580{
2581 BLI_assert(action.is_action_layered());
2583 const animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
2584 if (!bag) {
2585 return {};
2586 }
2587 return bag->fcurves();
2588}
2589
2591{
2592 if (act == nullptr) {
2593 return nullptr;
2594 }
2595
2596 Action &action = act->wrap();
2597 if (action.is_action_legacy()) {
2598 return BKE_fcurve_find(
2599 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2600 }
2601
2603 Layer *layer = action.layer(0);
2604 if (!layer) {
2605 return nullptr;
2606 }
2607 Strip *strip = layer->strip(0);
2608 if (!strip) {
2609 return nullptr;
2610 }
2611
2612 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2613
2614 for (Channelbag *channelbag : strip_data.channelbags()) {
2615 FCurve *fcu = channelbag->fcurve_find(fcurve_descriptor);
2616 if (fcu) {
2617 return fcu;
2618 }
2619 }
2620
2621 return nullptr;
2622}
2623
2625{
2626 return fcurve_find_in_action_slot(adt.action, adt.slot_handle, fcurve_descriptor);
2627}
2628
2630 const slot_handle_t slot_handle,
2631 const FCurveDescriptor &fcurve_descriptor)
2632{
2633 if (act == nullptr) {
2634 return nullptr;
2635 }
2636
2637 Action &action = act->wrap();
2638 if (action.is_action_legacy()) {
2639 return BKE_fcurve_find(
2640 &act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
2641 }
2642
2643 Channelbag *cbag = channelbag_for_action_slot(action, slot_handle);
2644 if (!cbag) {
2645 return nullptr;
2646 }
2647 return cbag->fcurve_find(fcurve_descriptor);
2648}
2649
2651 const StringRefNull collection_rna_path,
2652 const StringRefNull data_name)
2653{
2654 BLI_assert(!collection_rna_path.is_empty());
2655
2656 const size_t quoted_name_size = data_name.size() + 1;
2657 char *quoted_name = static_cast<char *>(alloca(quoted_name_size));
2658
2659 if (!fcurve.rna_path) {
2660 return false;
2661 }
2662 /* Skipping names longer than `quoted_name_size` is OK since we're after an exact match. */
2664 fcurve.rna_path, collection_rna_path.c_str(), quoted_name, quoted_name_size))
2665 {
2666 return false;
2667 }
2668 if (quoted_name != data_name) {
2669 return false;
2670 }
2671
2672 return true;
2673}
2674
2676 const slot_handle_t slot_handle,
2677 FunctionRef<bool(const FCurve &fcurve)> predicate)
2678{
2679 BLI_assert(act);
2680
2681 Vector<FCurve *> found;
2682
2683 foreach_fcurve_in_action_slot(act->wrap(), slot_handle, [&](FCurve &fcurve) {
2684 if (predicate(fcurve)) {
2685 found.append(&fcurve);
2686 }
2687 });
2688
2689 return found;
2690}
2691
2693 FunctionRef<bool(const FCurve &fcurve)> predicate)
2694{
2695 Vector<FCurve *> found;
2696
2697 for (FCurve *fcurve : fcurves) {
2698 if (predicate(*fcurve)) {
2699 found.append(fcurve);
2700 }
2701 }
2702
2703 return found;
2704}
2705
2707 FunctionRef<bool(const FCurve &fcurve)> predicate)
2708{
2709 Vector<FCurve *> found;
2710
2711 LISTBASE_FOREACH (FCurve *, fcurve, &fcurves) {
2712 if (predicate(*fcurve)) {
2713 found.append(fcurve);
2714 }
2715 }
2716
2717 return found;
2718}
2719
2721 bAction *act,
2722 const char group[],
2723 PointerRNA *ptr,
2724 const FCurveDescriptor &fcurve_descriptor)
2725{
2726 if (act == nullptr) {
2727 return nullptr;
2728 }
2729
2731 return action_fcurve_ensure_legacy(bmain, act, group, ptr, fcurve_descriptor);
2732 }
2733
2734 /* NOTE: for layered actions we require the following:
2735 *
2736 * - `ptr` is non-null.
2737 * - `ptr` has an `owner_id` that already uses `act`.
2738 *
2739 * This isn't for any principled reason, but rather is because adding
2740 * support for layered actions to this function was a fix to make Follow
2741 * Path animation work properly with layered actions (see PR #124353), and
2742 * those are the requirements the Follow Path code conveniently met.
2743 * Moreover those requirements were also already met by the other call sites
2744 * that potentially call this function with layered actions.
2745 *
2746 * Trying to puzzle out what "should" happen when these requirements don't
2747 * hold, or if this is even the best place to handle the layered action
2748 * cases at all, was leading to discussion of larger changes than made sense
2749 * to tackle at that point. */
2750 BLI_assert(ptr != nullptr);
2751 if (ptr == nullptr || ptr->owner_id == nullptr) {
2752 return nullptr;
2753 }
2754
2755 return &action_fcurve_ensure(bmain, *act, *ptr->owner_id, fcurve_descriptor);
2756}
2757
2759{
2760 Action &action = dna_action.wrap();
2761 BLI_assert(get_action(animated_id) == &action);
2762
2763 /* Ensure the id has an assigned slot. */
2764 Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
2765 /* A nullptr here means the ID type is not animatable. But since the Action is already assigned,
2766 * it is certain that the ID is actually animatable. */
2767 BLI_assert(slot);
2768
2769 action.layer_keystrip_ensure();
2770
2772 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2773
2774 return strip_data.channelbag_for_slot_ensure(*slot);
2775}
2776
2778 bAction &dna_action,
2779 ID &animated_id,
2780 const FCurveDescriptor &fcurve_descriptor)
2781{
2782 Channelbag &channelbag = action_channelbag_ensure(dna_action, animated_id);
2783 return channelbag.fcurve_ensure(bmain, fcurve_descriptor);
2784}
2785
2787 bAction *act,
2788 const char group[],
2789 PointerRNA *ptr,
2790 const FCurveDescriptor &fcurve_descriptor)
2791{
2792 if (!act) {
2793 return nullptr;
2794 }
2795
2796 BLI_assert(act->wrap().is_empty() || act->wrap().is_action_legacy());
2797
2798 /* Try to find f-curve matching for this setting.
2799 * - add if not found and allowed to add one
2800 * TODO: add auto-grouping support? how this works will need to be resolved
2801 */
2802 FCurve *fcu = animrig::fcurve_find_in_action(act, fcurve_descriptor);
2803
2804 if (fcu != nullptr) {
2805 return fcu;
2806 }
2807
2808 /* Determine the property (sub)type if we can. */
2809 std::optional<PropertyType> prop_type = std::nullopt;
2810 std::optional<PropertySubType> prop_subtype = std::nullopt;
2811 if (ptr != nullptr) {
2812 PropertyRNA *resolved_prop;
2813 PointerRNA resolved_ptr;
2814 PointerRNA id_ptr = RNA_id_pointer_create(ptr->owner_id);
2815 const bool resolved = RNA_path_resolve_property(
2816 &id_ptr, fcurve_descriptor.rna_path.c_str(), &resolved_ptr, &resolved_prop);
2817 if (resolved) {
2818 prop_type = RNA_property_type(resolved_prop);
2819 prop_subtype = RNA_property_subtype(resolved_prop);
2820 }
2821 }
2822
2823 BLI_assert_msg(!fcurve_descriptor.prop_type.has_value(),
2824 "Did not expect a prop_type to be passed in. This is fine, but does need some "
2825 "changes to action_fcurve_ensure_legacy() to deal with it");
2826 BLI_assert_msg(!fcurve_descriptor.prop_subtype.has_value(),
2827 "Did not expect a prop_subtype to be passed in. This is fine, but does need some "
2828 "changes to action_fcurve_ensure_legacy() to deal with it");
2830 {fcurve_descriptor.rna_path, fcurve_descriptor.array_index, prop_type, prop_subtype});
2831
2832 if (BLI_listbase_is_empty(&act->curves)) {
2833 fcu->flag |= FCURVE_ACTIVE;
2834 }
2835
2836 if (group) {
2837 bActionGroup *agrp = BKE_action_group_find_name(act, group);
2838
2839 if (agrp == nullptr) {
2840 agrp = action_groups_add_new(act, group);
2841
2842 /* Sync bone group colors if applicable. */
2843 if (ptr && (ptr->type == &RNA_PoseBone) && ptr->data) {
2844 const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
2846 }
2847 }
2848
2849 action_groups_add_channel(act, agrp, fcu);
2850 }
2851 else {
2852 BLI_addtail(&act->curves, fcu);
2853 }
2854
2855 /* New f-curve was added, meaning it's possible that it affects
2856 * dependency graph component which wasn't previously animated.
2857 */
2859
2860 return fcu;
2861}
2862
2864{
2865 if (action_fcurve_detach(action, fcu)) {
2866 BKE_fcurve_free(&fcu);
2867 return true;
2868 }
2869
2870 return false;
2871}
2872
2873bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
2874{
2875 if (action.is_action_legacy()) {
2876 return BLI_remlink_safe(&action.curves, &fcurve_to_detach);
2877 }
2878
2879 for (Layer *layer : action.layers()) {
2880 for (Strip *strip : layer->strips()) {
2881 if (!(strip->type() == Strip::Type::Keyframe)) {
2882 continue;
2883 }
2884 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
2885 for (Channelbag *bag : strip_data.channelbags()) {
2886 const bool is_detached = bag->fcurve_detach(fcurve_to_detach);
2887 if (is_detached) {
2888 return true;
2889 }
2890 }
2891 }
2892 }
2893 return false;
2894}
2895
2897 const slot_handle_t action_slot,
2898 FCurve &fcurve_to_attach,
2899 std::optional<StringRefNull> group_name)
2900{
2902 BLI_addtail(&action.curves, &fcurve_to_attach);
2903 return;
2904 }
2905
2906 Slot *slot = action.slot_for_handle(action_slot);
2907 BLI_assert(slot);
2908 if (!slot) {
2909 printf("Cannot find slot handle %d on Action %s, unable to attach F-Curve %s[%d] to it!\n",
2910 action_slot,
2911 action.id.name + 2,
2912 fcurve_to_attach.rna_path,
2913 fcurve_to_attach.array_index);
2914 return;
2915 }
2916
2917 action.layer_keystrip_ensure();
2918 StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
2919 Channelbag &cbag = strip_data.channelbag_for_slot_ensure(*slot);
2920 cbag.fcurve_append(fcurve_to_attach);
2921
2922 if (group_name) {
2923 bActionGroup &group = cbag.channel_group_ensure(*group_name);
2924 cbag.fcurve_assign_to_channel_group(fcurve_to_attach, group);
2925 }
2926}
2927
2929 const slot_handle_t action_slot_dst,
2930 Action &action_src,
2931 FCurve &fcurve)
2932{
2933 /* Store the group name locally, as the group will be removed if this was its
2934 * last F-Curve. */
2935 std::optional<std::string> group_name;
2936 if (fcurve.grp) {
2937 group_name = fcurve.grp->name;
2938 }
2939
2940 const bool is_detached = action_fcurve_detach(action_src, fcurve);
2941 BLI_assert(is_detached);
2942 UNUSED_VARS_NDEBUG(is_detached);
2943
2944 action_fcurve_attach(action_dst, action_slot_dst, fcurve, group_name);
2945}
2946
2947void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src)
2948{
2949 while (!channelbag_src.fcurves().is_empty()) {
2950 FCurve &fcurve = *channelbag_src.fcurve(0);
2951
2952 /* Store the group name locally, as the group will be removed if this was its
2953 * last F-Curve. */
2954 std::optional<std::string> group_name;
2955 if (fcurve.grp) {
2956 group_name = fcurve.grp->name;
2957 }
2958
2959 const bool is_detached = channelbag_src.fcurve_detach(fcurve);
2960 BLI_assert(is_detached);
2961 UNUSED_VARS_NDEBUG(is_detached);
2962
2963 channelbag_dst.fcurve_append(fcurve);
2964
2965 if (group_name) {
2966 bActionGroup &group = channelbag_dst.channel_group_ensure(*group_name);
2967 channelbag_dst.fcurve_assign_to_channel_group(fcurve, group);
2968 }
2969 }
2970}
2971
2973{
2974 if (this->channel_groups().first_index_try(&to_group) == -1) {
2975 return false;
2976 }
2977
2978 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
2979 if (fcurve_index == -1) {
2980 return false;
2981 }
2982
2983 if (fcurve.grp == &to_group) {
2984 return true;
2985 }
2986
2987 /* Remove fcurve from old group, if it belongs to one. */
2988 if (fcurve.grp != nullptr) {
2989 fcurve.grp->fcurve_range_length--;
2990 if (fcurve.grp->fcurve_range_length == 0) {
2991 const int group_index = this->channel_groups().first_index_try(fcurve.grp);
2992 this->channel_group_remove_raw(group_index);
2993 }
2994 this->restore_channel_group_invariants();
2995 }
2996
2998 this->fcurve_array_num,
2999 fcurve_index,
3000 fcurve_index + 1,
3001 to_group.fcurve_range_start + to_group.fcurve_range_length);
3002 to_group.fcurve_range_length++;
3003
3004 this->restore_channel_group_invariants();
3005
3006 return true;
3007}
3008
3010{
3011 const int fcurve_index = this->fcurves().first_index_try(&fcurve);
3012 if (fcurve_index == -1) {
3013 return false;
3014 }
3015
3016 if (fcurve.grp == nullptr) {
3017 return true;
3018 }
3019
3020 bActionGroup *old_group = fcurve.grp;
3021
3023 this->fcurve_array_num,
3024 fcurve_index,
3025 fcurve_index + 1,
3026 this->fcurve_array_num - 1);
3027
3028 old_group->fcurve_range_length--;
3029 if (old_group->fcurve_range_length == 0) {
3030 const int old_group_index = this->channel_groups().first_index_try(old_group);
3031 this->channel_group_remove_raw(old_group_index);
3032 }
3033
3034 this->restore_channel_group_invariants();
3035
3036 return true;
3037}
3038
3040 Action &action,
3041 const slot_handle_t slot_handle,
3042 ID *primary_id)
3043{
3045 if (primary_id && get_action(*primary_id) == &action) {
3046 return primary_id;
3047 }
3048 return nullptr;
3049 }
3050
3051 Slot *slot = action.slot_for_handle(slot_handle);
3052 if (slot == nullptr) {
3053 return nullptr;
3054 }
3055
3056 blender::Span<ID *> users = slot->users(bmain);
3057 if (users.size() == 1) {
3058 /* We only do this for `users.size() == 1` and not `users.size() >= 1`
3059 * because when there's more than one user it's ambiguous which user we
3060 * should return, and that would be unpredictable for end users of Blender.
3061 * We also expect that to be a corner case anyway. So instead we let that
3062 * case either get disambiguated by the primary ID in the case below, or
3063 * return null. */
3064 return users[0];
3065 }
3066 if (users.contains(primary_id)) {
3067 return primary_id;
3068 }
3069
3070 return nullptr;
3071}
3072
3073ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
3074{
3075 blender::Span<ID *> users = slot.users(bmain);
3076 if (users.is_empty()) {
3077 return nullptr;
3078 }
3079 if (users.contains(primary_id)) {
3080 return primary_id;
3081 }
3082 return users[0];
3083}
3084
3085slot_handle_t first_slot_handle(const ::bAction &dna_action)
3086{
3087 const Action &action = dna_action.wrap();
3088 if (action.slot_array_num == 0) {
3089 return Slot::unassigned;
3090 }
3091 return action.slot_array[0]->handle;
3092}
3093
3095{
3096 if (action.is_action_legacy()) {
3097 return;
3098 }
3099 if (action.layers().is_empty()) {
3100 return;
3101 }
3102 BLI_assert(action.layers().size() == 1);
3103
3105}
3106
3108{
3109 if (layer.strips().is_empty()) {
3110 return;
3111 }
3112 BLI_assert(layer.strips().size() == 1);
3113
3115}
3116
3118{
3119 UNUSED_VARS_NDEBUG(strip);
3120 BLI_assert(strip.type() == Strip::Type::Keyframe);
3121 BLI_assert(strip.is_infinite());
3122 BLI_assert(strip.frame_offset == 0.0);
3123}
3124
3125Action *convert_to_layered_action(Main &bmain, const Action &legacy_action)
3126{
3127 if (!legacy_action.is_action_legacy()) {
3128 return nullptr;
3129 }
3130
3131 std::string suffix = "_layered";
3132 /* In case the legacy action has a long name it is shortened to make space for the suffix. */
3133 char legacy_name[MAX_ID_NAME - 10];
3134 /* Offsetting the id.name to remove the ID prefix (AC) which gets added back later. */
3135 STRNCPY_UTF8(legacy_name, legacy_action.id.name + 2);
3136
3137 const std::string layered_action_name = std::string(legacy_name) + suffix;
3138 bAction *dna_action = BKE_action_add(&bmain, layered_action_name.c_str());
3139
3140 Action &converted_action = dna_action->wrap();
3141 Slot &slot = converted_action.slot_add();
3142 Layer &layer = converted_action.layer_add(legacy_action.id.name);
3143 Strip &strip = layer.strip_add(converted_action, Strip::Type::Keyframe);
3144 BLI_assert(strip.data<StripKeyframeData>(converted_action).channelbag_array_num == 0);
3145 Channelbag *bag = &strip.data<StripKeyframeData>(converted_action).channelbag_for_slot_add(slot);
3146
3147 const int fcu_count = BLI_listbase_count(&legacy_action.curves);
3148 bag->fcurve_array = MEM_calloc_arrayN<FCurve *>(fcu_count, "Convert to layered action");
3149 bag->fcurve_array_num = fcu_count;
3150
3151 int i = 0;
3152 blender::Map<FCurve *, FCurve *> old_new_fcurve_map;
3153 LISTBASE_FOREACH_INDEX (FCurve *, fcu, &legacy_action.curves, i) {
3154 bag->fcurve_array[i] = BKE_fcurve_copy(fcu);
3155 bag->fcurve_array[i]->grp = nullptr;
3156 old_new_fcurve_map.add(fcu, bag->fcurve_array[i]);
3157 }
3158
3159 LISTBASE_FOREACH (bActionGroup *, group, &legacy_action.groups) {
3160 /* The resulting group might not have the same name, because the legacy system allowed
3161 * duplicate names while the new system ensures uniqueness. */
3162 bActionGroup &converted_group = bag->channel_group_create(group->name);
3163 LISTBASE_FOREACH (FCurve *, fcu, &group->channels) {
3164 if (fcu->grp != group) {
3165 /* Since the group listbase points to the action listbase, it won't stop iterating when
3166 * reaching the end of the group but iterate to the end of the action FCurves. */
3167 break;
3168 }
3169 FCurve *new_fcurve = old_new_fcurve_map.lookup(fcu);
3170 bag->fcurve_assign_to_channel_group(*new_fcurve, converted_group);
3171 }
3172 }
3173
3174 return &converted_action;
3175}
3176
3182static void clone_slot(const Slot &from, Slot &to)
3183{
3184 ActionSlotRuntimeHandle *runtime = to.runtime;
3185 slot_handle_t handle = to.handle;
3186 *reinterpret_cast<ActionSlot *>(&to) = *reinterpret_cast<const ActionSlot *>(&from);
3187 to.runtime = runtime;
3188 to.handle = handle;
3189}
3190
3191void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_action)
3192{
3193 BLI_assert(from_action.slots().contains(&source_slot));
3194 BLI_assert(&from_action != &to_action);
3195
3196 /* No merging of strips or layers is handled. All data is put into the assumed single strip. */
3199
3200 Slot &target_slot = to_action.slot_add();
3201 clone_slot(source_slot, target_slot);
3202 slot_identifier_ensure_unique(to_action, target_slot);
3203
3204 if (!from_action.layers().is_empty() && !from_action.layer(0)->strips().is_empty()) {
3205 StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data<StripKeyframeData>(
3206 from_action);
3207 Channelbag *channelbag = from_strip_data.channelbag_for_slot(source_slot.handle);
3208 /* It's perfectly fine for a slot to not have a channelbag on each keyframe strip. */
3209 if (channelbag) {
3210 /* Only create the layer & keyframe strip if there is a channelbag to move
3211 * into it. Otherwise it's better to keep the Action lean, and defer their
3212 * creation when keys are inserted. */
3213 to_action.layer_keystrip_ensure();
3214 StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
3215 to_action);
3216 channelbag->slot_handle = target_slot.handle;
3218 &to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag);
3219 const int index = from_strip_data.find_channelbag_index(*channelbag);
3221 &from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
3222 }
3223 }
3224
3225 /* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */
3226 for (ID *user : source_slot.users(bmain)) {
3227 const auto assign_other_action = [&](ID & /* animated_id */,
3228 bAction *&action_ptr_ref,
3229 slot_handle_t &slot_handle_ref,
3230 char *slot_identifier) -> bool {
3231 /* Only reassign if the reference is actually from the same action. Could be from a different
3232 * action when using the NLA or action constraints. */
3233 if (action_ptr_ref != &from_action) {
3234 return true;
3235 }
3236
3237 { /* Assign the Action. */
3238 const bool assign_ok = generic_assign_action(
3239 *user, &to_action, action_ptr_ref, slot_handle_ref, slot_identifier);
3240 BLI_assert_msg(assign_ok, "Expecting slotted Actions to always be assignable");
3241 UNUSED_VARS_NDEBUG(assign_ok);
3242 }
3243 { /* Assign the Slot. */
3245 &target_slot, *user, action_ptr_ref, slot_handle_ref, slot_identifier);
3248 }
3249
3250 /* TODO: move the tagging of animated IDs into generic_assign_action() and
3251 * generic_assign_action_slot(), as that's closer to the modification of
3252 * the animated ID.
3253 *
3254 * This line was added here for now, to fix #136388 with minimal impact on
3255 * other code, so that the fix can be easily back-ported to Blender 4.4. */
3257 return true;
3258 };
3259 foreach_action_slot_use_with_references(*user, assign_other_action);
3260 }
3261
3262 from_action.slot_remove(source_slot);
3263}
3264
3265Slot &duplicate_slot(Action &action, const Slot &slot)
3266{
3267 BLI_assert(action.slots().contains(const_cast<Slot *>(&slot)));
3268
3269 /* Duplicate the slot itself. */
3270 Slot &cloned_slot = action.slot_add();
3271 clone_slot(slot, cloned_slot);
3272 slot_identifier_ensure_unique(action, cloned_slot);
3273
3274 /* Duplicate each Channelbag for the source slot. */
3275 for (int i = 0; i < action.strip_keyframe_data_array_num; i++) {
3276 StripKeyframeData &strip_data = action.strip_keyframe_data_array[i]->wrap();
3277 strip_data.slot_data_duplicate(slot.handle, cloned_slot.handle);
3278 }
3279
3280 /* The ID has changed, and so it needs to be re-evaluated. Animation does not
3281 * have to be flushed since nothing is using this slot yet. */
3283
3284 return cloned_slot;
3285}
3286
3287} // 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_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
int us
Definition DNA_ID.h:425
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:4227