/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup animrig */ #include "DNA_action_types.h" #include "DNA_anim_types.h" #include "DNA_array_utils.hh" #include "DNA_scene_types.h" #include "BLI_listbase.h" #include "BLI_map.hh" #include "BLI_math_base.h" #include "BLI_string.h" #include "BLI_string_utf8.h" #include "BLI_string_utils.hh" #include "BLI_utildefines.h" #include "BKE_action.hh" #include "BKE_anim_data.hh" #include "BKE_fcurve.hh" #include "BKE_lib_id.hh" #include "BKE_library.hh" #include "BKE_main.hh" #include "BKE_nla.hh" #include "BKE_report.hh" #include "RNA_access.hh" #include "RNA_path.hh" #include "RNA_prototypes.hh" #include "MEM_guardedalloc.h" #include "BLT_translation.hh" #include "DEG_depsgraph.hh" #include "DEG_depsgraph_build.hh" #include "ANIM_action.hh" #include "ANIM_action_iterators.hh" #include "ANIM_action_legacy.hh" #include "ANIM_animdata.hh" #include "ANIM_fcurve.hh" #include "CLG_log.h" #include "action_runtime.hh" #include #include namespace blender { static CLG_LogRef LOG = {"anim.action"}; namespace animrig { namespace { /** * Default identifier for action slots. The first two characters in the identifier indicate the ID * type of whatever is animated by it. * * Since the ID type might not be determined when the slot is created, the prefix starts out at * XX (see below). Note that no code should use this XX value; use Slot::has_idtype() instead. */ constexpr const char *slot_default_name = "Slot"; /** * Slot identifier prefix for untyped slots (i.e. where `Slot::has_idtype()` returns `false`). */ constexpr const char *slot_untyped_prefix = "XX"; constexpr const char *layer_default_name = "Layer"; } // namespace static animrig::Layer &ActionLayer_alloc() { ActionLayer *layer = MEM_new(__func__); return layer->wrap(); } /* Copied from source/blender/blenkernel/intern/grease_pencil.cc. * Keep an eye on DNA_array_utils.hh; we may want to move these functions in there. */ template static void grow_array(T **array, int *num, const int add_num) { BLI_assert(add_num > 0); const int new_array_num = *num + add_num; T *new_array = MEM_new_array_zeroed(new_array_num, "animrig::action/grow_array"); uninitialized_relocate_n(*array, *num, new_array); MEM_SAFE_DELETE(*array); *array = new_array; *num = new_array_num; } template static void grow_array_and_append(T **array, int *num, T item) { grow_array(array, num, 1); (*array)[*num - 1] = item; } template static void grow_array_and_insert(T **array, int *num, const int index, T item) { BLI_assert(index >= 0 && index <= *num); const int new_array_num = *num + 1; T *new_array = MEM_new_array_zeroed(new_array_num, __func__); uninitialized_relocate_n(*array, index, new_array); new_array[index] = item; uninitialized_relocate_n(*array + index, *num - index, new_array + index + 1); MEM_SAFE_DELETE(*array); *array = new_array; *num = new_array_num; } template static void shrink_array(T **array, int *num, const int shrink_num) { BLI_assert(shrink_num > 0); const int new_array_num = *num - shrink_num; if (new_array_num == 0) { MEM_delete(*array); *array = nullptr; *num = 0; return; } T *new_array = MEM_new_array_zeroed(new_array_num, __func__); uninitialized_move_n(*array, new_array_num, new_array); MEM_delete(*array); *array = new_array; *num = new_array_num; } template static void shrink_array_and_remove(T **array, int *num, const int index) { BLI_assert(index >= 0 && index < *num); const int new_array_num = *num - 1; T *new_array = MEM_new_array_zeroed(new_array_num, __func__); uninitialized_move_n(*array, index, new_array); uninitialized_move_n(*array + index + 1, *num - index - 1, new_array + index); MEM_delete(*array); *array = new_array; *num = new_array_num; } /** * Same as `shrink_array_and_remove()` above, except instead of shifting all the * elements after the removed item over to fill the gap, it just swaps in the last * element to where the removed element was. */ template static void shrink_array_and_swap_remove(T **array, int *num, const int index) { BLI_assert(index >= 0 && index < *num); const int new_array_num = *num - 1; T *new_array = MEM_new_array_zeroed(new_array_num, __func__); uninitialized_move_n(*array, index, new_array); if (index < new_array_num) { new_array[index] = (*array)[new_array_num]; uninitialized_move_n(*array + index + 1, *num - index - 2, new_array + index + 1); } MEM_delete(*array); *array = new_array; *num = new_array_num; } /** * Moves the given (end exclusive) range to index `to`, shifting other items * before/after to make room. * * The range is moved such that the *start* ends up at `to`. * * `to` *must* be far away enough from the end of the array for the entire range * to be moved there without spilling over the end of the array. */ template static void array_shift_range( T *array, const int num, const int range_start, const int range_end, const int to) { BLI_assert(range_start <= range_end); BLI_assert(range_end <= num); BLI_assert(to <= num + range_start - range_end); UNUSED_VARS_NDEBUG(num); if (ELEM(range_start, range_end, to)) { return; } if (to < range_start) { T *start = array + to; T *mid = array + range_start; T *end = array + range_end; std::rotate(start, mid, end); } else { T *start = array + range_start; T *mid = array + range_end; T *end = array + to + range_end - range_start; std::rotate(start, mid, end); } } /* ----- Action implementation ----------- */ bool Action::is_empty() const { /* The check for emptiness has to include the check for an empty `groups` ListBaseT because of * the animation filtering code. With the functions `rearrange_action_channels` and * `join_groups_action_temp` the ownership of FCurves is temporarily transferred to the `groups` * ListBaseT leaving `curves` potentially empty. */ return this->layer_array_num == 0 && this->slot_array_num == 0 && this->curves.is_empty() && this->groups.is_empty(); } Span Action::layers() const { return Span{reinterpret_cast(this->layer_array), this->layer_array_num}; } Span Action::layers() { return Span{reinterpret_cast(this->layer_array), this->layer_array_num}; } const Layer *Action::layer(const int64_t index) const { return &this->layer_array[index]->wrap(); } Layer *Action::layer(const int64_t index) { return &this->layer_array[index]->wrap(); } Layer &Action::layer_add(const std::optional name) { Layer &new_layer = ActionLayer_alloc(); if (name.has_value()) { STRNCPY_UTF8(new_layer.name, name.value().c_str()); } else { STRNCPY_UTF8(new_layer.name, DATA_(layer_default_name)); } grow_array_and_append( &this->layer_array, &this->layer_array_num, &new_layer); this->layer_active_index = this->layer_array_num - 1; /* If this is the first layer in this Action, it means that it could have been * used as a legacy Action before. As a result, this->idroot may be non-zero * while it should be zero for layered Actions. * * And since setting this to 0 when it is already supposed to be 0 is fine, * there is no check for whether this is actually the first layer. */ this->idroot = 0; return new_layer; } static void layer_ptr_destructor(ActionLayer **dna_layer_ptr) { Layer &layer = (*dna_layer_ptr)->wrap(); MEM_delete(&layer); }; bool Action::layer_remove(Layer &layer_to_remove) { const int64_t layer_index = this->find_layer_index(layer_to_remove); if (layer_index < 0) { return false; } dna::array::remove_index(&this->layer_array, &this->layer_array_num, &this->layer_active_index, layer_index, layer_ptr_destructor); return true; } void Action::layer_keystrip_ensure() { /* Ensure a layer. */ Layer *layer; if (this->layers().is_empty()) { layer = &this->layer_add(DATA_(layer_default_name)); } else { layer = this->layer(0); } /* Ensure a keyframe Strip. */ if (layer->strips().is_empty()) { layer->strip_add(*this, Strip::Type::Keyframe); } /* Within the limits of Baklava Phase 1, the above code should not have * created more than one layer, or more than one strip on the layer. And if a * layer + strip already existed, that must have been a keyframe strip. */ assert_baklava_phase_1_invariants(*this); } int64_t Action::find_layer_index(const Layer &layer) const { for (const int64_t layer_index : this->layers().index_range()) { const Layer *visit_layer = this->layer(layer_index); if (visit_layer == &layer) { return layer_index; } } return -1; } int64_t Action::find_slot_index(const Slot &slot) const { for (const int64_t slot_index : this->slots().index_range()) { const Slot *visit_slot = this->slot(slot_index); if (visit_slot == &slot) { return slot_index; } } return -1; } Span Action::slots() const { return Span{reinterpret_cast(this->slot_array), this->slot_array_num}; } Span Action::slots() { return Span{reinterpret_cast(this->slot_array), this->slot_array_num}; } const Slot *Action::slot(const int64_t index) const { return &this->slot_array[index]->wrap(); } Slot *Action::slot(const int64_t index) { return &this->slot_array[index]->wrap(); } Slot *Action::slot_for_handle(const slot_handle_t handle) { const Slot *slot = const_cast(this)->slot_for_handle(handle); return const_cast(slot); } const Slot *Action::slot_for_handle(const slot_handle_t handle) const { if (handle == Slot::unassigned) { return nullptr; } /* TODO: implement hash-map lookup. */ for (const Slot *slot : slots()) { if (slot->handle == handle) { return slot; } } return nullptr; } static void slot_identifier_ensure_unique(Action &action, Slot &slot) { auto check_name_is_used = [&](const StringRef name) -> bool { for (const Slot *slot_iter : action.slots()) { if (slot_iter == &slot) { /* Don't compare against the slot that's being renamed. */ continue; } if (slot_iter->identifier == name) { return true; } } return false; }; BLI_uniquename_cb(check_name_is_used, "", '.', slot.identifier, sizeof(slot.identifier)); } void Action::slot_display_name_set(Main &bmain, Slot &slot, StringRefNull new_display_name) { this->slot_display_name_define(slot, new_display_name); this->slot_identifier_propagate(bmain, slot); } void Action::slot_display_name_define(Slot &slot, StringRefNull new_display_name) { BLI_assert_msg(StringRef(new_display_name).size() >= 1, "Action Slot display names must not be empty"); BLI_assert_msg(StringRef(slot.identifier).size() >= 2, "Action Slot's existing identifier lacks the two-character type prefix, which " "would make the display name copy meaningless due to early null termination."); BLI_strncpy_utf8(slot.identifier + 2, new_display_name.c_str(), ARRAY_SIZE(slot.identifier) - 2); slot_identifier_ensure_unique(*this, slot); } void Action::slot_idtype_define(Slot &slot, ID_Type idtype) { slot.idtype = idtype; slot.identifier_ensure_prefix(); slot_identifier_ensure_unique(*this, slot); } void Action::slot_identifier_set(Main &bmain, Slot &slot, const StringRefNull new_identifier) { /* TODO: maybe this function should only set the 'identifier without prefix' aka the 'display * name'. That way only `this->id_type` is responsible for the prefix. I (Sybren) think that's * easier to determine when the code is a bit more mature, and we can see what the majority of * the calls to this function actually do/need. */ this->slot_identifier_define(slot, new_identifier); this->slot_identifier_propagate(bmain, slot); } void Action::slot_identifier_define(Slot &slot, const StringRefNull new_identifier) { BLI_assert_msg( StringRef(new_identifier).size() >= Slot::identifier_length_min, "Action Slot identifiers must be large enough for a 2-letter ID code + the display name"); STRNCPY_UTF8(slot.identifier, new_identifier.c_str()); slot_identifier_ensure_unique(*this, slot); } void Action::slot_identifier_propagate(Main &bmain, const Slot &slot) { /* Just loop over all animatable IDs in the main database. */ ListBaseT *lb; ID *id; FOREACH_MAIN_LISTBASE_BEGIN (&bmain, lb) { FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) { if (!id_can_have_animdata(id)) { /* This ID type cannot have any animation, so ignore all and continue to * the next ID type. */ break; } AnimData *adt = BKE_animdata_from_id(id); if (!adt || adt->action != this) { /* Not animated by this Action. */ continue; } if (adt->slot_handle != slot.handle) { /* Not animated by this Slot. */ continue; } /* Ensure the Slot identifier on the AnimData is correct. */ STRNCPY_UTF8(adt->last_slot_identifier, slot.identifier); } FOREACH_MAIN_LISTBASE_ID_END; } FOREACH_MAIN_LISTBASE_END; } Slot *Action::slot_find_by_identifier(const StringRefNull slot_identifier) { for (Slot *slot : slots()) { if (STREQ(slot->identifier, slot_identifier.c_str())) { return slot; } } return nullptr; } Slot &Action::slot_allocate() { Slot &slot = *MEM_new(__func__); this->last_slot_handle++; BLI_assert_msg(this->last_slot_handle > 0, "Action Slot handle overflow"); slot.handle = this->last_slot_handle; /* Set the default flags. These cannot be set via the 'DNA defaults' system, * as that would require knowing which bit corresponds with which flag. That's * only known to the C++ wrapper code. */ slot.set_expanded(true); return slot; } Slot &Action::slot_add() { Slot &slot = this->slot_allocate(); /* Assign the default name and the 'untyped' identifier prefix. */ STRNCPY_UTF8(slot.identifier, slot_untyped_prefix); BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2); /* Append the Slot to the Action. */ grow_array_and_append(&this->slot_array, &this->slot_array_num, &slot); slot_identifier_ensure_unique(*this, slot); /* If this is the first slot in this Action, it means that it could have * been used as a legacy Action before. As a result, this->idroot may be * non-zero while it should be zero for layered Actions. * * And since setting this to 0 when it is already supposed to be 0 is fine, * there is no check for whether this is actually the first layer. */ this->idroot = 0; return slot; } Slot &Action::slot_add_for_id_type(const ID_Type idtype) { Slot &slot = this->slot_add(); slot.idtype = idtype; slot.identifier_ensure_prefix(); BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2); slot_identifier_ensure_unique(*this, slot); /* No need to call anim.slot_identifier_propagate() as nothing will be using * this brand new Slot yet. */ return slot; } Slot &Action::slot_add_for_id(const ID &animated_id) { Slot &slot = this->slot_add(); slot.idtype = GS(animated_id.name); /* Determine the identifier for this slot, prioritizing transparent * auto-selection when toggling between Actions. That's why the last-used slot * identifier is used here, and the ID name only as fallback. */ const AnimData *adt = BKE_animdata_from_id(&animated_id); const StringRefNull last_slot_identifier = adt ? adt->last_slot_identifier : ""; StringRefNull slot_identifier = last_slot_identifier; if (slot_identifier.is_empty()) { slot_identifier = animated_id.name; } this->slot_identifier_define(slot, slot_identifier); /* No need to call anim.slot_identifier_propagate() as nothing will be using * this brand new Slot yet. */ /* The last-used slot might have had a different ID type through some quirk (changes to linked * data, for example). So better ensure that the identifier prefix is correct on this new slot, * instead of relying for 100% on the old one. */ slot.identifier_ensure_prefix(); return slot; } static void slot_ptr_destructor(ActionSlot **dna_slot_ptr) { Slot &slot = (*dna_slot_ptr)->wrap(); MEM_delete(&slot); }; bool Action::slot_remove(Slot &slot_to_remove) { /* Check that this slot belongs to this Action. */ const int64_t slot_index = this->find_slot_index(slot_to_remove); if (slot_index < 0) { return false; } /* Remove the slot's data from each keyframe strip. */ for (StripKeyframeData *strip_data : this->strip_keyframe_data()) { strip_data->slot_data_remove(slot_to_remove.handle); } /* Don't bother un-assigning this slot from its users. The slot handle will * not be reused by a new slot anyway. */ /* Remove the actual slot. */ dna::array::remove_index( &this->slot_array, &this->slot_array_num, nullptr, slot_index, slot_ptr_destructor); return true; } void Action::slot_move_to_index(Slot &slot, const int to_slot_index) { BLI_assert(this->slots().index_range().contains(to_slot_index)); const int from_slot_index = this->slots().first_index_try(&slot); BLI_assert_msg(from_slot_index >= 0, "Slot not in this action."); array_shift_range( this->slot_array, this->slot_array_num, from_slot_index, from_slot_index + 1, to_slot_index); } void Action::slot_active_set(const slot_handle_t slot_handle) { for (Slot *slot : slots()) { slot->set_active(slot->handle == slot_handle); } } Slot *Action::slot_active_get() { for (Slot *slot : slots()) { if (slot->is_active()) { return slot; } } return nullptr; } bool Action::is_slot_animated(const slot_handle_t slot_handle) const { if (slot_handle == Slot::unassigned) { return false; } Span fcurves = fcurves_for_action_slot(*this, slot_handle); return !fcurves.is_empty(); } int Action::strip_keyframe_data_append(StripKeyframeData *strip_data) { BLI_assert(strip_data != nullptr); grow_array_and_append( &this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, strip_data); return this->strip_keyframe_data_array_num - 1; } void Action::strip_keyframe_data_remove_if_unused(const int index) { BLI_assert(index >= 0 && index < this->strip_keyframe_data_array_num); /* Make sure the data isn't being used anywhere. */ for (const Layer *layer : this->layers()) { for (const Strip *strip : layer->strips()) { if (strip->type() == Strip::Type::Keyframe && strip->data_index == index) { return; } } } /* Free the item to be removed. */ MEM_delete( static_cast(this->strip_keyframe_data_array[index])); /* Remove the item, swapping in the item at the end of the array. */ shrink_array_and_swap_remove( &this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, index); /* Update strips that pointed at the swapped-in item. * * Note that we don't special-case the corner-case where the removed data was * at the end of the array, but it ends up not mattering because then * `old_index == index`. */ const int old_index = this->strip_keyframe_data_array_num; for (Layer *layer : this->layers()) { for (Strip *strip : layer->strips()) { if (strip->type() == Strip::Type::Keyframe && strip->data_index == old_index) { strip->data_index = index; } } } } Span Action::strip_keyframe_data() const { /* The reinterpret cast is needed because `strip_keyframe_data_array` is for * pointers to the C type `ActionStripKeyframeData`, but we want the C++ * wrapper type `StripKeyframeData`. */ return Span{ reinterpret_cast(this->strip_keyframe_data_array), this->strip_keyframe_data_array_num}; } Span Action::strip_keyframe_data() { /* The reinterpret cast is needed because `strip_keyframe_data_array` is for * pointers to the C type `ActionStripKeyframeData`, but we want the C++ * wrapper type `StripKeyframeData`. */ return Span{ reinterpret_cast(this->strip_keyframe_data_array), this->strip_keyframe_data_array_num}; } Layer *Action::get_layer_for_keyframing() { assert_baklava_phase_1_invariants(*this); if (this->layers().is_empty()) { return nullptr; } return this->layer(0); } void Action::slot_identifier_ensure_prefix(Slot &slot) { slot.identifier_ensure_prefix(); slot_identifier_ensure_unique(*this, slot); } void Action::slot_setup_for_id(Slot &slot, const ID &animated_id) { if (!ID_IS_EDITABLE(this) || ID_IS_OVERRIDE_LIBRARY(this)) { /* Do not write to linked data. For now, also avoid changing the slot identifier on an * override. Actions cannot have library overrides at the moment, and when they do, this should * actually get designed. For now, it's better to avoid editing data than editing too much. */ return; } if (slot.has_idtype()) { BLI_assert(slot.idtype == GS(animated_id.name)); return; } slot.idtype = GS(animated_id.name); this->slot_identifier_ensure_prefix(slot); } bool Action::has_keyframes(const slot_handle_t action_slot_handle) const { for (const FCurve *fcu : fcurves_for_action_slot(*this, action_slot_handle)) { if (fcu->totvert) { return true; } } return false; } bool Action::has_single_frame() const { bool found_key = false; float found_key_frame = 0.0f; for (const FCurve *fcu : legacy::fcurves_all(this)) { switch (fcu->totvert) { case 0: /* No keys, so impossible to come to a conclusion on this curve alone. */ continue; case 1: /* Single key, which is the complex case, so handle below. */ break; default: /* Multiple keys, so there is animation. */ return false; } const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] : fcu->fpt[0].vec[0]; if (!found_key) { found_key = true; found_key_frame = this_key_frame; continue; } /* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise * with these comparisons. */ if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) { /* This key differs from the already-found key, so this Action represents animation. */ return false; } } /* There is only a single frame if we found at least one key. */ return found_key; } bool Action::is_cyclic() const { return (this->flag & ACT_FRAME_RANGE) && (this->flag & ACT_CYCLIC); } /** Return the frame range of the span of keys. */ static float2 get_frame_range_of_fcurves(Span fcurves, bool include_modifiers); float2 Action::get_frame_range() const { if (this->flag & ACT_FRAME_RANGE) { return {this->frame_start, this->frame_end}; } Vector all_fcurves = legacy::fcurves_all(this); return get_frame_range_of_fcurves(all_fcurves, false); } float2 Action::get_frame_range_of_slot(const slot_handle_t slot_handle) const { if (this->flag & ACT_FRAME_RANGE) { return {this->frame_start, this->frame_end}; } Span fcurves_to_consider = fcurves_for_action_slot(*this, slot_handle); return get_frame_range_of_fcurves(fcurves_to_consider, false); } float2 Action::get_frame_range_of_keys(const bool include_modifiers) const { return get_frame_range_of_fcurves(legacy::fcurves_all(this), include_modifiers); } static float2 get_frame_range_of_fcurves(Span fcurves, const bool include_modifiers) { float min = 999999999.0f, max = -999999999.0f; bool foundvert = false, foundmod = false; for (const FCurve *fcu : fcurves) { /* if curve has keyframes, consider them first */ if (fcu->totvert) { float nmin, nmax; /* get extents for this curve * - no "selected only", since this is often used in the backend * - no "minimum length" (we will apply this later), otherwise * single-keyframe curves will increase the overall length by * a phantom frame (#50354) */ BKE_fcurve_calc_range(fcu, &nmin, &nmax, false); /* compare to the running tally */ min = min_ff(min, nmin); max = max_ff(max, nmax); foundvert = true; } /* if include_modifiers is enabled, need to consider modifiers too * - only really care about the last modifier */ if ((include_modifiers) && (fcu->modifiers.last)) { FModifier *fcm = static_cast(fcu->modifiers.last); /* only use the maximum sensible limits of the modifiers if they are more extreme */ switch (fcm->type) { case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */ { FMod_Limits *fmd = static_cast(fcm->data); if (fmd->flag & FCM_LIMIT_XMIN) { min = min_ff(min, fmd->rect.xmin); } if (fmd->flag & FCM_LIMIT_XMAX) { max = max_ff(max, fmd->rect.xmax); } break; } case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */ { FMod_Cycles *fmd = static_cast(fcm->data); if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) { min = MINAFRAMEF; } if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) { max = MAXFRAMEF; } break; } /* TODO: function modifier may need some special limits */ default: /* all other standard modifiers are on the infinite range... */ min = MINAFRAMEF; max = MAXFRAMEF; break; } foundmod = true; } } if (foundvert || foundmod) { return float2{max_ff(min, MINAFRAMEF), min_ff(max, MAXFRAMEF)}; } return float2{0.0f, 0.0f}; } /* ----- ActionLayer implementation ----------- */ Layer *Layer::duplicate_with_shallow_strip_copies(const StringRefNull allocation_name) const { ActionLayer *copy = MEM_new(allocation_name.c_str()); *copy = *reinterpret_cast(this); /* Make a shallow copy of the Strips, without copying their data. */ copy->strip_array = MEM_new_array_zeroed(this->strip_array_num, allocation_name.c_str()); for (int i : this->strips().index_range()) { Strip *strip_copy = MEM_new(allocation_name.c_str(), *this->strip(i)); copy->strip_array[i] = strip_copy; } return ©->wrap(); } Layer::~Layer() { for (Strip *strip : this->strips()) { MEM_delete(strip); } MEM_SAFE_DELETE(this->strip_array); this->strip_array_num = 0; } Span Layer::strips() const { return Span{reinterpret_cast(this->strip_array), this->strip_array_num}; } Span Layer::strips() { return Span{reinterpret_cast(this->strip_array), this->strip_array_num}; } const Strip *Layer::strip(const int64_t index) const { return &this->strip_array[index]->wrap(); } Strip *Layer::strip(const int64_t index) { return &this->strip_array[index]->wrap(); } Strip &Layer::strip_add(Action &owning_action, const Strip::Type strip_type) { Strip &strip = Strip::create(owning_action, strip_type); /* Add the new strip to the strip array. */ grow_array_and_append( &this->strip_array, &this->strip_array_num, &strip); return strip; } static void strip_ptr_destructor(ActionStrip **dna_strip_ptr) { Strip &strip = (*dna_strip_ptr)->wrap(); MEM_delete(&strip); }; bool Layer::strip_remove(Action &owning_action, Strip &strip) { const int64_t strip_index = this->find_strip_index(strip); if (strip_index < 0) { return false; } const Strip::Type strip_type = strip.type(); const int data_index = strip.data_index; dna::array::remove_index( &this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor); /* It's important that we do this *after* removing the strip itself * (immediately above), because otherwise the strip will be found as a * still-existing user of the strip data and thus the strip data won't be * removed even if this strip was the last user. */ switch (strip_type) { case Strip::Type::Keyframe: owning_action.strip_keyframe_data_remove_if_unused(data_index); break; } return true; } int64_t Layer::find_strip_index(const Strip &strip) const { for (const int64_t strip_index : this->strips().index_range()) { const Strip *visit_strip = this->strip(strip_index); if (visit_strip == &strip) { return strip_index; } } return -1; } /* ----- ActionSlot implementation ----------- */ Slot::Slot() { /* Zero-initialize the DNA struct. 'this' is a C++ class, and shouldn't be `memset` like this. */ _DNA_internal_memzero(this, sizeof(ActionSlot)); this->runtime = MEM_new(__func__); } Slot::Slot(const Slot &other) : ActionSlot(other) { this->runtime = MEM_new(__func__); } Slot::~Slot() { MEM_delete(this->runtime); } void Slot::blend_read_post() { BLI_assert(!this->runtime); this->runtime = MEM_new(__func__); } bool Slot::is_suitable_for(const ID &animated_id) const { if (!this->has_idtype()) { /* Without specific ID type set, this Slot can animate any ID. */ return true; } /* Check that the ID type is compatible with this slot. */ const int animated_idtype = GS(animated_id.name); return this->idtype == animated_idtype; } bool Slot::has_idtype() const { return this->idtype != 0; } Slot::Flags Slot::flags() const { return static_cast(this->slot_flags); } bool Slot::is_expanded() const { return this->slot_flags & uint8_t(Flags::Expanded); } void Slot::set_expanded(const bool expanded) { if (expanded) { this->slot_flags |= uint8_t(Flags::Expanded); } else { this->slot_flags &= ~uint8_t(Flags::Expanded); } } bool Slot::is_selected() const { return this->slot_flags & uint8_t(Flags::Selected); } void Slot::set_selected(const bool selected) { if (selected) { this->slot_flags |= uint8_t(Flags::Selected); } else { this->slot_flags &= ~uint8_t(Flags::Selected); } } bool Slot::is_active() const { return this->slot_flags & uint8_t(Flags::Active); } void Slot::set_active(const bool active) { if (active) { this->slot_flags |= uint8_t(Flags::Active); } else { this->slot_flags &= ~uint8_t(Flags::Active); } } Span Slot::users(Main &bmain) const { if (bmain.is_action_slot_to_id_map_dirty) { internal::rebuild_slot_user_cache(bmain); } BLI_assert(this->runtime); return this->runtime->users.as_span(); } Vector Slot::runtime_users() { BLI_assert_msg(this->runtime, "Slot::runtime should always be allocated"); return this->runtime->users; } void Slot::users_add(ID &animated_id) { BLI_assert(this->runtime); this->runtime->users.append_non_duplicates(&animated_id); } void Slot::users_remove(ID &animated_id) { BLI_assert(this->runtime); Vector &users = this->runtime->users; /* Even though users_add() ensures that there are no duplicates, there's still things like * pointer swapping etc. that can happen via the foreach-id looping code. That means that the * entries in the user map are not 100% under control of the user_add() and user_remove() * function, and thus we cannot assume that there are no duplicates. */ users.remove_if([&](const ID *user) { return user == &animated_id; }); } void Slot::users_invalidate(Main &bmain) { bmain.is_action_slot_to_id_map_dirty = true; } std::string Slot::idtype_string() const { if (!this->has_idtype()) { return slot_untyped_prefix; } char name[3] = {0}; *reinterpret_cast(name) = this->idtype; return name; } StringRef Slot::identifier_prefix() const { StringRef identifier(this->identifier); BLI_assert(identifier.size() >= 2); return identifier.substr(0, 2); } StringRefNull Slot::identifier_without_prefix() const { BLI_assert(StringRef(this->identifier).size() >= identifier_length_min); /* Avoid accessing an uninitialized part of the string accidentally. */ if (this->identifier[0] == '\0' || this->identifier[1] == '\0') { return ""; } return this->identifier + 2; } void Slot::identifier_ensure_prefix() { BLI_assert(StringRef(this->identifier).size() >= identifier_length_min); if (StringRef(this->identifier).size() < 2) { /* The code below would overwrite the trailing 0-byte. */ this->identifier[2] = '\0'; } if (!this->has_idtype()) { /* A zero idtype is not going to convert to a two-character string, so we * need to explicitly assign the default prefix. */ this->identifier[0] = slot_untyped_prefix[0]; this->identifier[1] = slot_untyped_prefix[1]; return; } *reinterpret_cast(this->identifier) = this->idtype; } /* ----- Functions ----------- */ Action &action_add(Main &bmain, const StringRefNull name) { bAction *dna_action = BKE_action_add(&bmain, name.c_str()); BLI_assert(dna_action->id.us == 1); id_us_min(&dna_action->id); return dna_action->wrap(); } bool assign_action(bAction *action, ID &animated_id) { AnimData *adt = BKE_animdata_ensure_id(&animated_id); if (!adt) { return false; } return assign_action(action, {animated_id, *adt}); } bool assign_action(bAction *action, const OwnedAnimData owned_adt) { if (!BKE_animdata_action_editable(&owned_adt.adt)) { /* Cannot remove, otherwise things turn to custard. */ BKE_report(nullptr, RPT_ERROR, "Cannot change action, as it is still being edited in NLA"); return false; } return generic_assign_action(owned_adt.owner_id, action, owned_adt.adt.action, owned_adt.adt.slot_handle, owned_adt.adt.last_slot_identifier); } bool assign_tmpaction(bAction *action, const OwnedAnimData owned_adt) { return generic_assign_action(owned_adt.owner_id, action, owned_adt.adt.tmpact, owned_adt.adt.tmp_slot_handle, owned_adt.adt.tmp_last_slot_identifier); } bool unassign_action(ID &animated_id) { return assign_action(nullptr, animated_id); } bool unassign_action(OwnedAnimData owned_adt) { return assign_action(nullptr, owned_adt); } Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id) { AnimData *adt = BKE_animdata_from_id(&animated_id); Slot *slot; /* Find a suitable slot, but be stricter about when to allow searching by name * than generic_slot_for_autoassign(...). */ if (adt && adt->action == &action) { /* The slot handle is only valid when this action is already assigned. * Otherwise it's meaningless. */ slot = action.slot_for_handle(adt->slot_handle); /* If this Action is already assigned, a search by name is inappropriate, as it might * re-assign an intentionally-unassigned slot. */ } else { /* In this case a by-name search is ok, so defer to generic_slot_for_autoassign(). */ slot = generic_slot_for_autoassign(animated_id, action, adt ? adt->last_slot_identifier : ""); } /* As a last resort, if there is only one slot and it has no ID type yet, use that. This is what * gets created for the backwards compatibility RNA API, for example to allow * `action.fcurves.new()`. Key insertion should use that slot as well. */ if (!slot && action.slots().size() == 1) { Slot *first_slot = action.slot(0); if (!first_slot->has_idtype()) { slot = first_slot; } } /* If no suitable slot was found, create a new one. */ if (!slot || !slot->is_suitable_for(animated_id)) { slot = &action.slot_add_for_id(animated_id); } /* Only try to assign the Action to the ID if it is not already assigned. * Assignment can fail when the ID is in NLA Tweak mode. */ const bool is_correct_action = adt && adt->action == &action; if (!is_correct_action && !assign_action(&action, animated_id)) { return nullptr; } const bool is_correct_slot = adt && adt->slot_handle == slot->handle; if (!is_correct_slot && assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK) { /* This should never happen, as a few lines above a new slot is created for * this ID if the found one wasn't deemed suitable. */ BLI_assert_unreachable(); return nullptr; } return slot; } static bool is_id_using_action_slot(const ID &animated_id, const Action &action, const slot_handle_t slot_handle) { auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool { const bool is_used = (&used_action == &action && used_slot_handle == slot_handle); return !is_used; /* Stop searching when we found a use of this Action+Slot. */ }; const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use); return !looped_until_end; } bool generic_assign_action(ID &animated_id, bAction *action_to_assign, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_identifier) { BLI_assert(slot_identifier); /* Un-assign any previously-assigned Action first. */ if (action_ptr_ref) { /* Un-assign the slot. This will always succeed, so no need to check the result. */ if (slot_handle_ref != Slot::unassigned) { const ActionSlotAssignmentResult result = generic_assign_action_slot( nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier); BLI_assert(result == ActionSlotAssignmentResult::OK); UNUSED_VARS_NDEBUG(result); } /* Un-assign the Action itself. */ id_us_min(&action_ptr_ref->id); action_ptr_ref = nullptr; } if (!action_to_assign) { /* Un-assigning was the point, so the work is done. */ return true; } /* Assign the new Action. */ action_ptr_ref = action_to_assign; id_us_plus(&action_ptr_ref->id); /* Auto-assign a slot. */ Slot *slot = generic_slot_for_autoassign(animated_id, action_ptr_ref->wrap(), slot_identifier); const ActionSlotAssignmentResult result = generic_assign_action_slot( slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier); BLI_assert(result == ActionSlotAssignmentResult::OK); UNUSED_VARS_NDEBUG(result); return true; } Slot *generic_slot_for_autoassign(const ID &animated_id, Action &action, const StringRefNull last_slot_identifier) { /* The slot-finding code in assign_action_ensure_slot_for_keying() is very * similar to the code here (differences are documented there). It is very * likely that changes in the logic here should be applied there as well. */ /* Try the slot identifier, if it is set. */ if (!last_slot_identifier.is_empty()) { /* If the last-used slot identifier was 'untyped', i.e. started with XX, see if something more * specific to this ID type exists. * * If there is any choice in the matter, the more specific slot is chosen. In other words, in * this case: * * - last_slot_identifier = `XXSlot` * - both `XXSlot` and `OBSlot` exist on the Action (where `OB` represents the ID type of * `animated_id`). * * the `OBSlot` should be chosen. This means that `XXSlot` NOT being auto-assigned if there is * an alternative. Since untyped slots are bound on assignment, this design keeps the Action * as-is, which means that the `XXSlot` remains untyped and thus the user is free to assign * this to another ID type if desired. */ const bool last_used_identifier_is_typed = last_slot_identifier.substr(0, 2) != slot_untyped_prefix; if (!last_used_identifier_is_typed) { const std::string with_idtype_prefix = StringRef(animated_id.name, 2) + last_slot_identifier.substr(2); Slot *slot = action.slot_find_by_identifier(with_idtype_prefix); if (slot && slot->is_suitable_for(animated_id)) { return slot; } } /* See if the actual last-used slot identifier can be matched. */ Slot *slot = action.slot_find_by_identifier(last_slot_identifier); if (slot && slot->is_suitable_for(animated_id)) { return slot; } /* If the last-used slot identifier was IDSomething, and XXSomething exists (where ID = the * ID code of the animated ID), fall back to the XX. If slot `IDSomething` existed, the code * above would have already returned it. */ if (last_used_identifier_is_typed) { const std::string with_untyped_prefix = StringRef(slot_untyped_prefix) + last_slot_identifier.substr(2); Slot *slot = action.slot_find_by_identifier(with_untyped_prefix); if (slot && slot->is_suitable_for(animated_id)) { return slot; } } } /* Search for the ID name (which includes the ID type). */ { Slot *slot = action.slot_find_by_identifier(animated_id.name); if (slot && slot->is_suitable_for(animated_id)) { return slot; } } /* If there is only one slot, and it is not specific to any ID type, use that. * * This should only trigger in some special cases, like legacy Actions that were converted to * slotted Actions by the versioning code, where the legacy Action was never assigned to anything * (and thus had idroot = 0). * * This might seem overly specific, and for convenience of automatically auto-assigning a slot, * it might be tempting to remove the "slot->has_idtype()" check. However, that would make the * following workflow significantly more cumbersome: * * - Animate `Cube`. This creates `CubeAction` with a single slot `OBCube`. * - Assign `CubeAction` to `Suzanne`, with the intent of animating both `Cube` and `Suzanne` * with the same Action. * - This should **not** auto-assign the `OBCube` slot to `Suzanne`, as that will overwrite any * property of `Suzanne` with the animated values for the `OBCube` slot. * * Recovering from this will be hard, as an undo will revert both the overwriting of properties * and the assignment of the Action. */ if (action.slots().size() == 1) { Slot *slot = action.slot(0); if (!slot->has_idtype()) { return slot; } } return nullptr; } 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) { BLI_assert(slot_identifier); if (!action_ptr_ref) { /* No action assigned yet, so no way to assign a slot. */ return ActionSlotAssignmentResult::MissingAction; } Action &action = action_ptr_ref->wrap(); /* Check that the slot can actually be assigned. */ if (slot_to_assign) { if (!action.slots().contains(slot_to_assign)) { return ActionSlotAssignmentResult::SlotNotFromAction; } if (!slot_to_assign->is_suitable_for(animated_id)) { return ActionSlotAssignmentResult::SlotNotSuitable; } } Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref); /* If there was a previously-assigned slot, unassign it first. */ slot_handle_ref = Slot::unassigned; if (slot_to_unassign) { /* Make sure that the stored Slot identifier is up to date. The slot identifier might have * changed in a way that wasn't copied into the ADT yet (for example when the * Action is linked from another file), so better copy the identifier to be sure * that it can be transparently reassigned later. * * TODO: Replace this with a BLI_assert() that the identifier is as expected, and "simply" * ensure this identifier is always correct. */ BLI_strncpy_utf8(slot_identifier, slot_to_unassign->identifier, Slot::identifier_length_max); /* If this was the last use of this slot, remove this ID from its users. */ if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) { slot_to_unassign->users_remove(animated_id); } } if (!slot_to_assign) { return ActionSlotAssignmentResult::OK; } action.slot_setup_for_id(*slot_to_assign, animated_id); slot_handle_ref = slot_to_assign->handle; BLI_strncpy_utf8(slot_identifier, slot_to_assign->identifier, Slot::identifier_length_max); slot_to_assign->users_add(animated_id); return ActionSlotAssignmentResult::OK; } 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) { if (slot_handle_to_assign == Slot::unassigned && !action_ptr_ref) { /* No Action assigned, so no slot was used anyway. Just blindly assign the * 'unassigned' handle. */ slot_handle_ref = Slot::unassigned; return ActionSlotAssignmentResult::OK; } if (!action_ptr_ref) { /* No Action to verify the slot handle is valid. As the slot handle will be * completely ignored when re-assigning an Action, better to refuse setting * it altogether. This will make bugs more obvious. */ return ActionSlotAssignmentResult::MissingAction; } Slot *slot = action_ptr_ref->wrap().slot_for_handle(slot_handle_to_assign); return generic_assign_action_slot( slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier); } ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id) { AnimData *adt = BKE_animdata_from_id(&animated_id); if (!adt) { return ActionSlotAssignmentResult::MissingAction; } return generic_assign_action_slot( slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->last_slot_identifier); } ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id) { if (!assign_action(action, animated_id)) { return ActionSlotAssignmentResult::MissingAction; } return assign_action_slot(slot_to_assign, animated_id); } ActionSlotAssignmentResult assign_tmpaction_and_slot_handle(bAction *action, const slot_handle_t slot_handle, const OwnedAnimData owned_adt) { if (!assign_tmpaction(action, owned_adt)) { return ActionSlotAssignmentResult::MissingAction; } return generic_assign_action_slot_handle(slot_handle, owned_adt.owner_id, owned_adt.adt.tmpact, owned_adt.adt.tmp_slot_handle, owned_adt.adt.tmp_last_slot_identifier); } Action *get_action(ID &animated_id) { AnimData *adt = BKE_animdata_from_id(&animated_id); if (!adt) { return nullptr; } if (!adt->action) { return nullptr; } return &adt->action->wrap(); } std::optional> get_action_slot_pair(ID &animated_id) { AnimData *adt = BKE_animdata_from_id(&animated_id); if (!adt || !adt->action) { /* Not animated by any Action. */ return std::nullopt; } Action &action = adt->action->wrap(); Slot *slot = action.slot_for_handle(adt->slot_handle); if (!slot) { /* Will not receive any animation from this Action. */ return std::nullopt; } return std::make_pair(&action, slot); } /* ----- ActionStrip implementation ----------- */ Strip &Strip::create(Action &owning_action, const Strip::Type type) { /* Create the strip. */ ActionStrip *strip = MEM_new(__func__); strip->strip_type = int8_t(type); /* Create the strip's data on the owning Action. */ switch (type) { case Strip::Type::Keyframe: { StripKeyframeData *strip_data = MEM_new(__func__); strip->data_index = owning_action.strip_keyframe_data_append(strip_data); break; } } /* This can happen if someone forgets to add a strip type in the `switch` * above, or if someone is evil and passes an invalid strip type to this * function. */ BLI_assert_msg(strip->data_index != -1, "Newly created strip has no data."); return strip->wrap(); } bool Strip::is_infinite() const { return this->frame_start == -std::numeric_limits::infinity() && this->frame_end == std::numeric_limits::infinity(); } bool Strip::contains_frame(const float frame_time) const { return this->frame_start <= frame_time && frame_time <= this->frame_end; } bool Strip::is_last_frame(const float frame_time) const { /* Maybe this needs a more advanced equality check. Implement that when * we have an actual example case that breaks. */ return this->frame_end == frame_time; } void Strip::resize(const float frame_start, const float frame_end) { BLI_assert(frame_start <= frame_end); BLI_assert_msg(frame_start < std::numeric_limits::infinity(), "only the end frame can be at positive infinity"); BLI_assert_msg(frame_end > -std::numeric_limits::infinity(), "only the start frame can be at negative infinity"); this->frame_start = frame_start; this->frame_end = frame_end; } template<> const StripKeyframeData &Strip::data(const Action &owning_action) const { BLI_assert(this->type() == StripKeyframeData::TYPE); return *owning_action.strip_keyframe_data()[this->data_index]; } template<> StripKeyframeData &Strip::data(Action &owning_action) { BLI_assert(this->type() == StripKeyframeData::TYPE); return *owning_action.strip_keyframe_data()[this->data_index]; } /* ----- ActionStripKeyframeData implementation ----------- */ StripKeyframeData::StripKeyframeData(const StripKeyframeData &other) : ActionStripKeyframeData(other) { this->channelbag_array = MEM_new_array_zeroed(other.channelbag_array_num, __func__); Span channelbags_src = other.channelbags(); for (int i : channelbags_src.index_range()) { this->channelbag_array[i] = MEM_new(__func__, *other.channelbag(i)); } } StripKeyframeData::~StripKeyframeData() { for (Channelbag *channelbag_for_slot : this->channelbags()) { MEM_delete(channelbag_for_slot); } MEM_SAFE_DELETE(this->channelbag_array); this->channelbag_array_num = 0; } Span StripKeyframeData::channelbags() const { return Span{reinterpret_cast(this->channelbag_array), this->channelbag_array_num}; } Span StripKeyframeData::channelbags() { return Span{reinterpret_cast(this->channelbag_array), this->channelbag_array_num}; } const Channelbag *StripKeyframeData::channelbag(const int64_t index) const { return &this->channelbag_array[index]->wrap(); } Channelbag *StripKeyframeData::channelbag(const int64_t index) { return &this->channelbag_array[index]->wrap(); } const Channelbag *StripKeyframeData::channelbag_for_slot(const slot_handle_t slot_handle) const { for (const Channelbag *channels : this->channelbags()) { if (channels->slot_handle == slot_handle) { return channels; } } return nullptr; } int64_t StripKeyframeData::find_channelbag_index(const Channelbag &channelbag) const { for (int64_t index = 0; index < this->channelbag_array_num; index++) { if (this->channelbag(index) == &channelbag) { return index; } } return -1; } Channelbag *StripKeyframeData::channelbag_for_slot(const slot_handle_t slot_handle) { const auto *const_this = const_cast(this); const auto *const_channels = const_this->channelbag_for_slot(slot_handle); return const_cast(const_channels); } const Channelbag *StripKeyframeData::channelbag_for_slot(const Slot &slot) const { return this->channelbag_for_slot(slot.handle); } Channelbag *StripKeyframeData::channelbag_for_slot(const Slot &slot) { return this->channelbag_for_slot(slot.handle); } Channelbag &StripKeyframeData::channelbag_for_slot_add(const Slot &slot) { return this->channelbag_for_slot_add(slot.handle); } Channelbag &StripKeyframeData::channelbag_for_slot_add(const slot_handle_t slot_handle) { BLI_assert_msg(channelbag_for_slot(slot_handle) == nullptr, "Cannot add channelbag for already-registered slot"); BLI_assert_msg(slot_handle != Slot::unassigned, "Cannot add channelbag for 'unassigned' slot"); Channelbag &channels = MEM_new(__func__)->wrap(); channels.slot_handle = slot_handle; grow_array_and_append( &this->channelbag_array, &this->channelbag_array_num, &channels); return channels; } Channelbag &StripKeyframeData::channelbag_for_slot_ensure(const Slot &slot) { return this->channelbag_for_slot_ensure(slot.handle); } Channelbag &StripKeyframeData::channelbag_for_slot_ensure(const slot_handle_t slot_handle) { Channelbag *channelbag = this->channelbag_for_slot(slot_handle); if (channelbag != nullptr) { return *channelbag; } return this->channelbag_for_slot_add(slot_handle); } static void channelbag_ptr_destructor(ActionChannelbag **dna_channelbag_ptr) { Channelbag &channelbag = (*dna_channelbag_ptr)->wrap(); MEM_delete(&channelbag); }; bool StripKeyframeData::channelbag_remove(Channelbag &channelbag_to_remove) { const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove); if (channelbag_index < 0) { return false; } dna::array::remove_index(&this->channelbag_array, &this->channelbag_array_num, nullptr, channelbag_index, channelbag_ptr_destructor); return true; } void StripKeyframeData::slot_data_remove(const slot_handle_t slot_handle) { Channelbag *channelbag = this->channelbag_for_slot(slot_handle); if (!channelbag) { return; } this->channelbag_remove(*channelbag); } void StripKeyframeData::slot_data_duplicate(const slot_handle_t source_slot_handle, const slot_handle_t target_slot_handle) { BLI_assert(!this->channelbag_for_slot(target_slot_handle)); const Channelbag *source_cbag = this->channelbag_for_slot(source_slot_handle); if (!source_cbag) { return; } Channelbag &target_cbag = *MEM_new(__func__, *source_cbag); target_cbag.slot_handle = target_slot_handle; grow_array_and_append( &this->channelbag_array, &this->channelbag_array_num, &target_cbag); } const FCurve *Channelbag::fcurve_find(const FCurveDescriptor &fcurve_descriptor) const { return animrig::fcurve_find(this->fcurves(), fcurve_descriptor); } FCurve *Channelbag::fcurve_find(const FCurveDescriptor &fcurve_descriptor) { /* Intermediate variable needed to disambiguate const/non-const overloads. */ Span fcurves = this->fcurves(); return animrig::fcurve_find(fcurves, fcurve_descriptor); } FCurve &Channelbag::fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor) { if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) { return *existing_fcurve; } return this->fcurve_create(bmain, fcurve_descriptor); } FCurve *Channelbag::fcurve_create_unique(Main *bmain, const FCurveDescriptor &fcurve_descriptor) { if (this->fcurve_find(fcurve_descriptor)) { return nullptr; } return &this->fcurve_create(bmain, fcurve_descriptor); } FCurve &Channelbag::fcurve_create(Main *bmain, const FCurveDescriptor &fcurve_descriptor) { FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor); if (this->fcurve_array_num == 0) { new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */ } bActionGroup *group = fcurve_descriptor.channel_group.has_value() ? &this->channel_group_ensure(*fcurve_descriptor.channel_group) : nullptr; const int insert_index = group ? group->fcurve_range_start + group->fcurve_range_length : this->fcurve_array_num; BLI_assert(insert_index <= this->fcurve_array_num); grow_array_and_insert(&this->fcurve_array, &this->fcurve_array_num, insert_index, new_fcurve); if (group) { group->fcurve_range_length += 1; this->restore_channel_group_invariants(); } if (bmain) { DEG_relations_tag_update(bmain); } return *new_fcurve; } Vector Channelbag::fcurve_create_many(Main *bmain, Span fcurve_descriptors) { const int prev_fcurve_num = this->fcurve_array_num; const int add_fcurve_num = int(fcurve_descriptors.size()); const bool make_first_active = prev_fcurve_num == 0; /* Figure out which path+index combinations already exist. */ struct CurvePathIndex { StringRefNull rna_path; int array_index; bool operator==(const CurvePathIndex &o) const { /* Check indices first, cheaper than a string comparison. */ return this->array_index == o.array_index && this->rna_path == o.rna_path; } uint64_t hash() const { return get_default_hash(this->rna_path, this->array_index); } }; Set unique_curves; unique_curves.reserve(prev_fcurve_num); for (FCurve *fcurve : this->fcurves()) { CurvePathIndex path_index; path_index.rna_path = StringRefNull(fcurve->rna_path ? fcurve->rna_path : ""); path_index.array_index = fcurve->array_index; unique_curves.add(path_index); } /* Grow curves array with enough space for new curves. */ grow_array(&this->fcurve_array, &this->fcurve_array_num, add_fcurve_num); /* Add the new curves. */ Vector new_fcurves; new_fcurves.resize(add_fcurve_num); int curve_index = prev_fcurve_num; for (int i = 0; i < add_fcurve_num; i++) { const FCurveDescriptor &desc = fcurve_descriptors[i]; CurvePathIndex path_index; path_index.rna_path = desc.rna_path; path_index.array_index = desc.array_index; if (desc.rna_path.is_empty() || !unique_curves.add(path_index)) { /* Empty input path, or such curve already exists. */ new_fcurves[i] = nullptr; continue; } FCurve *fcurve = create_fcurve_for_channel(desc); new_fcurves[i] = fcurve; this->fcurve_array[curve_index] = fcurve; if (desc.channel_group.has_value()) { bActionGroup *group = &this->channel_group_ensure(*desc.channel_group); const int insert_index = group->fcurve_range_start + group->fcurve_range_length; BLI_assert(insert_index <= this->fcurve_array_num); /* Insert curve into proper array place at the end of the group. Note: this can * still lead to quadratic complexity, in practice was not found to be an issue yet. */ array_shift_range( this->fcurve_array, this->fcurve_array_num, curve_index, curve_index + 1, insert_index); group->fcurve_range_length++; /* Update curve start ranges of the following groups. */ int index = this->channel_group_find_index(group); BLI_assert(index >= 0 && index < this->group_array_num); for (index = index + 1; index < this->group_array_num; index++) { this->group_array[index]->fcurve_range_start++; } } curve_index++; } if (this->fcurve_array_num != curve_index) { /* Some curves were not created, resize to final amount. */ shrink_array( &this->fcurve_array, &this->fcurve_array_num, this->fcurve_array_num - curve_index); } if (make_first_active) { /* Set first created curve as active. */ for (FCurve *fcurve : new_fcurves) { if (fcurve != nullptr) { fcurve->flag |= FCURVE_ACTIVE; break; } } } this->restore_channel_group_invariants(); if (bmain) { DEG_relations_tag_update(bmain); } return new_fcurves; } FCurve &Channelbag::fcurve_clone(const FCurve &old_fcurve, const StringRefNull new_path, const int new_array_index, const StringRef new_group_name) { FCurve *new_fcurve = this->fcurve_find({new_path, new_array_index}); if (new_fcurve) { MEM_delete(new_fcurve->bezt); new_fcurve->bezt = MEM_dupalloc(old_fcurve.bezt); MEM_delete(new_fcurve->fpt); new_fcurve->fpt = MEM_dupalloc(old_fcurve.fpt); new_fcurve->totvert = old_fcurve.totvert; } else { new_fcurve = BKE_fcurve_copy(&old_fcurve); MEM_delete(new_fcurve->rna_path); new_fcurve->rna_path = BLI_strdup(new_path.data()); new_fcurve->array_index = new_array_index; this->fcurve_append(*new_fcurve); } bActionGroup &agrp = this->channel_group_ensure(new_group_name.data()); this->fcurve_assign_to_channel_group(*new_fcurve, agrp); return *new_fcurve; } void Channelbag::fcurve_append(FCurve &fcurve) { /* Appended F-Curves don't belong to any group yet, so better make sure their * group pointer reflects that. */ fcurve.grp = nullptr; grow_array_and_append(&this->fcurve_array, &this->fcurve_array_num, &fcurve); } static void fcurve_ptr_destructor(FCurve **fcurve_ptr) { BKE_fcurve_free(*fcurve_ptr); }; bool Channelbag::fcurve_remove(FCurve &fcurve_to_remove) { if (!this->fcurve_detach(fcurve_to_remove)) { return false; } BKE_fcurve_free(&fcurve_to_remove); return true; } void Channelbag::fcurve_remove_by_index(const int64_t fcurve_index) { /* Grab the pointer before it's detached, so we can free it after. */ FCurve *fcurve_to_remove = this->fcurve(fcurve_index); this->fcurve_detach_by_index(fcurve_index); BKE_fcurve_free(fcurve_to_remove); } static void fcurve_ptr_noop_destructor(FCurve ** /*fcurve_ptr*/) {} bool Channelbag::fcurve_detach(FCurve &fcurve_to_detach) { const int64_t fcurve_index = this->fcurves().first_index_try(&fcurve_to_detach); if (fcurve_index < 0) { return false; } this->fcurve_detach_by_index(fcurve_index); return true; } void Channelbag::fcurve_detach_by_index(const int64_t fcurve_index) { BLI_assert(fcurve_index >= 0); BLI_assert(fcurve_index < this->fcurve_array_num); const int group_index = this->channel_group_containing_index(fcurve_index); if (group_index != -1) { bActionGroup *group = this->channel_group(group_index); group->fcurve_range_length -= 1; if (group->fcurve_range_length <= 0) { const int group_index = this->channel_groups().first_index_try(group); this->channel_group_remove_raw(group_index); } } dna::array::remove_index(&this->fcurve_array, &this->fcurve_array_num, nullptr, fcurve_index, fcurve_ptr_noop_destructor); this->restore_channel_group_invariants(); /* As an optimization, this function could call `DEG_relations_tag_update(bmain)` to prune any * relationships that are now no longer necessary. This is not needed for correctness of the * depsgraph evaluation results though. */ } void Channelbag::fcurve_move_to_index(FCurve &fcurve, int to_fcurve_index) { BLI_assert(to_fcurve_index >= 0 && to_fcurve_index < this->fcurves().size()); const int fcurve_index = this->fcurves().first_index_try(&fcurve); BLI_assert_msg(fcurve_index >= 0, "FCurve not in this channel bag."); array_shift_range( this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_fcurve_index); this->restore_channel_group_invariants(); } void Channelbag::fcurves_clear() { dna::array::clear(&this->fcurve_array, &this->fcurve_array_num, nullptr, fcurve_ptr_destructor); /* Since all F-Curves are gone, the groups are all empty. */ for (bActionGroup *group : channel_groups()) { group->fcurve_range_start = 0; group->fcurve_range_length = 0; } } static void cyclic_keying_ensure_modifier(FCurve &fcurve) { /* #BKE_fcurve_get_cycle_type() only looks at the first modifier to see if it's a Cycle modifier, * so if we're going to add one, better make sure it's the first one. * * BUT: #add_fmodifier() only allows adding a Cycle modifier when there are none yet, so that's * all that we need to check for here. */ if (!fcurve.modifiers.is_empty()) { return; } add_fmodifier(&fcurve.modifiers, FMODIFIER_TYPE_CYCLES, &fcurve); } /** * Ensure there are at least two keys in this F-Curve, in order to define A cycle range. * * That range does NOT have to be the same as `cycle_range` -- that parameter is only used to * insert a 2nd key when there is only one. * * \note This function ONLY does something when there is a single keyframe. If there are more, the * first and last keys already define the cycle range. If that range is not the same as the * `cycle_range` parameter, it's seen as an animator's choice and won't be adjusted. */ static void cyclic_keying_ensure_cycle_range_exists(FCurve &fcurve, const float2 cycle_range) { /* This is basically a copy of the legacy function `make_new_fcurve_cyclic()` * in `keyframing.cc`, except that it's limited to only one thing (ensuring * two keys exist to make cycling possible). Creating the F-Curve modifier is * the responsibility of another function. */ if (fcurve.totvert != 1 || fcurve.bezt == nullptr) { return; } const float period = cycle_range[1] - cycle_range[0]; if (period < 0.1f) { return; } /* Move the one existing keyframe into the cycle range. */ const float frame_offset = fcurve.bezt[0].vec[1][0] - cycle_range[0]; const float fix = floorf(frame_offset / period) * period; fcurve.bezt[0].vec[0][0] -= fix; fcurve.bezt[0].vec[1][0] -= fix; fcurve.bezt[0].vec[2][0] -= fix; /* Reallocate the array to make space for the 2nd point. */ fcurve.totvert++; fcurve.bezt = static_cast( MEM_realloc_uninitialized(fcurve.bezt, sizeof(BezTriple) * fcurve.totvert)); /* Duplicate and offset the keyframe. */ fcurve.bezt[1] = fcurve.bezt[0]; fcurve.bezt[1].vec[0][0] += period; fcurve.bezt[1].vec[1][0] += period; fcurve.bezt[1].vec[2][0] += period; } SingleKeyingResult StripKeyframeData::keyframe_insert(Main *bmain, const Slot &slot, const FCurveDescriptor &fcurve_descriptor, const float2 time_value, const KeyframeSettings &settings, const eInsertKeyFlags insert_key_flags, const std::optional cycle_range) { /* Get the fcurve, or create one if it doesn't exist and the keying flags * allow. */ FCurve *fcurve = nullptr; if (key_insertion_may_create_fcurve(insert_key_flags)) { fcurve = &this->channelbag_for_slot_ensure(slot).fcurve_ensure(bmain, fcurve_descriptor); } else { Channelbag *channels = this->channelbag_for_slot(slot); if (channels != nullptr) { fcurve = channels->fcurve_find(fcurve_descriptor); } } if (!fcurve) { CLOG_WARN(&LOG, "FCurve %s[%d] for slot %s was not created due to either the Only Insert " "Available setting or Replace keyframing mode.\n", fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index, slot.identifier); return SingleKeyingResult::CANNOT_CREATE_FCURVE; } if (!BKE_fcurve_is_keyframable(*fcurve)) { /* TODO: handle this properly, in a way that can be communicated to the user. */ CLOG_WARN(&LOG, "FCurve %s[%d] for slot %s doesn't allow inserting keys.\n", fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index, slot.identifier); return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE; } if (cycle_range && (*cycle_range)[0] < (*cycle_range)[1]) { /* Cyclic keying consists of three things: * - Ensure there is a Cycle modifier on the F-Curve. * - Ensure the start and end of the cycle have explicit keys, so that the * cycle modifier knows how to cycle (it doesn't look at the Action, and * as long as the period is correct, the first/last keys don't have to * align with the Action start/end). * - Offset the key to insert so that it falls within the cycle range. */ cyclic_keying_ensure_modifier(*fcurve); cyclic_keying_ensure_cycle_range_exists(*fcurve, *cycle_range); /* Offsetting the key doesn't have to happen here, as insert_vert_fcurve() * takes care of that. */ } const SingleKeyingResult insert_vert_result = insert_vert_fcurve( fcurve, time_value, settings, insert_key_flags); if (insert_vert_result != SingleKeyingResult::SUCCESS) { CLOG_WARN(&LOG, "Could not insert key into FCurve %s[%d] for slot %s.\n", fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index, slot.identifier); return insert_vert_result; } if (fcurve_descriptor.prop_type) { update_autoflags_fcurve_direct(fcurve, *fcurve_descriptor.prop_type); } return SingleKeyingResult::SUCCESS; } /* ActionChannelbag implementation. */ Channelbag::Channelbag(const Channelbag &other) { this->slot_handle = other.slot_handle; this->fcurve_array_num = other.fcurve_array_num; this->fcurve_array = MEM_new_array_zeroed(other.fcurve_array_num, __func__); for (int i = 0; i < other.fcurve_array_num; i++) { const FCurve *fcu_src = other.fcurve_array[i]; this->fcurve_array[i] = BKE_fcurve_copy(fcu_src); } this->group_array_num = other.group_array_num; this->group_array = MEM_new_array_zeroed(other.group_array_num, __func__); for (int i = 0; i < other.group_array_num; i++) { const bActionGroup *group_src = other.group_array[i]; this->group_array[i] = MEM_dupalloc(group_src); this->group_array[i]->channelbag = this; } /* BKE_fcurve_copy() resets the FCurve's group pointer. Which is good, because the groups are * duplicated too. This sets the group pointers to the correct values. */ this->restore_channel_group_invariants(); } Channelbag::~Channelbag() { for (FCurve *fcu : this->fcurves()) { BKE_fcurve_free(fcu); } MEM_SAFE_DELETE(this->fcurve_array); this->fcurve_array_num = 0; for (bActionGroup *group : this->channel_groups()) { MEM_SAFE_DELETE(group); } MEM_SAFE_DELETE(this->group_array); this->group_array_num = 0; } Span Channelbag::fcurves() const { return Span{this->fcurve_array, this->fcurve_array_num}; } Span Channelbag::fcurves() { return Span{this->fcurve_array, this->fcurve_array_num}; } const FCurve *Channelbag::fcurve(const int64_t index) const { return this->fcurve_array[index]; } FCurve *Channelbag::fcurve(const int64_t index) { return this->fcurve_array[index]; } Span Channelbag::channel_groups() const { return Span{this->group_array, this->group_array_num}; } Span Channelbag::channel_groups() { return Span{this->group_array, this->group_array_num}; } const bActionGroup *Channelbag::channel_group(const int64_t index) const { BLI_assert(index < this->group_array_num); return this->group_array[index]; } bActionGroup *Channelbag::channel_group(const int64_t index) { BLI_assert(index < this->group_array_num); return this->group_array[index]; } const bActionGroup *Channelbag::channel_group_find(const StringRef name) const { for (const bActionGroup *group : this->channel_groups()) { if (name == StringRef{group->name}) { return group; } } return nullptr; } int Channelbag::channel_group_find_index(const bActionGroup *group) const { for (int i = 0; i < this->group_array_num; i++) { if (this->group_array[i] == group) { return i; } } return -1; } bActionGroup *Channelbag::channel_group_find(const StringRef name) { /* Intermediate variable needed to disambiguate const/non-const overloads. */ Span groups = this->channel_groups(); for (bActionGroup *group : groups) { if (name == StringRef{group->name}) { return group; } } return nullptr; } int Channelbag::channel_group_containing_index(const int fcurve_array_index) { int i = 0; for (const bActionGroup *group : this->channel_groups()) { if (fcurve_array_index >= group->fcurve_range_start && fcurve_array_index < (group->fcurve_range_start + group->fcurve_range_length)) { return i; } i++; } return -1; } bActionGroup &Channelbag::channel_group_create(StringRefNull name) { bActionGroup *new_group = MEM_new(__func__); /* Find the end fcurve index of the current channel groups, to be used as the * start of the new channel group. */ int fcurve_index = 0; const int length = this->channel_groups().size(); if (length > 0) { const bActionGroup *last = this->channel_group(length - 1); fcurve_index = last->fcurve_range_start + last->fcurve_range_length; } new_group->fcurve_range_start = fcurve_index; new_group->channelbag = this; /* Make it selected. */ new_group->flag = AGRP_SELECTED; /* Ensure it has a unique name. * * Note that this only happens here (upon creation). The user can later rename * groups to have duplicate names. This is stupid, but it's how the legacy * system worked, and at the time of writing this code we're just trying to * match that system's behavior, even when it's goofy. */ std::string unique_name = BLI_uniquename_cb( [&](const StringRef name) { for (const bActionGroup *group : this->channel_groups()) { if (STREQ(group->name, name.data())) { return true; } } return false; }, '.', name[0] == '\0' ? DATA_("Group") : name); STRNCPY_UTF8(new_group->name, unique_name.c_str()); grow_array_and_append(&this->group_array, &this->group_array_num, new_group); return *new_group; } bActionGroup &Channelbag::channel_group_ensure(StringRefNull name) { bActionGroup *group = this->channel_group_find(name); if (group) { return *group; } return this->channel_group_create(name); } bool Channelbag::channel_group_remove(bActionGroup &group) { const int group_index = this->channel_groups().first_index_try(&group); if (group_index == -1) { return false; } /* Move the group's fcurves to just past the end of where the grouped * fcurves will be after this group is removed. */ const bActionGroup *last_group = this->channel_groups().last(); BLI_assert(last_group != nullptr); const int to_index = last_group->fcurve_range_start + last_group->fcurve_range_length - group.fcurve_range_length; array_shift_range(this->fcurve_array, this->fcurve_array_num, group.fcurve_range_start, group.fcurve_range_start + group.fcurve_range_length, to_index); this->channel_group_remove_raw(group_index); this->restore_channel_group_invariants(); return true; } void Channelbag::channel_group_move_to_index(bActionGroup &group, const int to_group_index) { BLI_assert(to_group_index >= 0 && to_group_index < this->channel_groups().size()); const int group_index = this->channel_groups().first_index_try(&group); BLI_assert_msg(group_index >= 0, "Group not in this channel bag."); /* Shallow copy, to track which fcurves should be moved in the second step. */ const bActionGroup pre_move_group = group; /* First we move the group to its new position. The call to * `restore_channel_group_invariants()` is necessary to update the group's * fcurve range (as well as the ranges of the other groups) to match its new * position in the group array. */ array_shift_range( this->group_array, this->group_array_num, group_index, group_index + 1, to_group_index); this->restore_channel_group_invariants(); /* Move the fcurves that were part of `group` (as recorded in `pre_move_group`) * to their new positions (now in `group`) so that they're part of `group` again. */ array_shift_range(this->fcurve_array, this->fcurve_array_num, pre_move_group.fcurve_range_start, pre_move_group.fcurve_range_start + pre_move_group.fcurve_range_length, group.fcurve_range_start); this->restore_channel_group_invariants(); } void Channelbag::channel_group_remove_raw(const int group_index) { BLI_assert(group_index >= 0 && group_index < this->channel_groups().size()); MEM_SAFE_DELETE(this->group_array[group_index]); shrink_array_and_remove(&this->group_array, &this->group_array_num, group_index); } void Channelbag::restore_channel_group_invariants() { /* Shift channel groups. */ { int start_index = 0; for (bActionGroup *group : this->channel_groups()) { group->fcurve_range_start = start_index; start_index += group->fcurve_range_length; } /* Double-check that this didn't push any of the groups off the end of the * fcurve array. */ BLI_assert(start_index <= this->fcurve_array_num); } /* Recompute fcurves' group pointers. */ { for (FCurve *fcurve : this->fcurves()) { fcurve->grp = nullptr; } for (bActionGroup *group : this->channel_groups()) { for (FCurve *fcurve : group->wrap().fcurves()) { fcurve->grp = group; } } } } Span ChannelGroup::fcurves() { if (this->fcurve_range_length == 0) { return {}; } return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start, this->fcurve_range_length); } Span ChannelGroup::fcurves() const { if (this->fcurve_range_length == 0) { return {}; } return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start, this->fcurve_range_length); } /* Utility function implementations. */ const animrig::Channelbag *channelbag_for_action_slot(const Action &action, const slot_handle_t slot_handle) { assert_baklava_phase_1_invariants(action); if (slot_handle == Slot::unassigned) { return nullptr; } for (const animrig::Layer *layer : action.layers()) { for (const animrig::Strip *strip : layer->strips()) { switch (strip->type()) { case animrig::Strip::Type::Keyframe: { const animrig::StripKeyframeData &strip_data = strip->data( action); const animrig::Channelbag *bag = strip_data.channelbag_for_slot(slot_handle); if (bag) { return bag; } } } } } return nullptr; } animrig::Channelbag *channelbag_for_action_slot(Action &action, const slot_handle_t slot_handle) { const animrig::Channelbag *const_bag = channelbag_for_action_slot( const_cast(action), slot_handle); return const_cast(const_bag); } Span fcurves_for_action_slot(Action &action, const slot_handle_t slot_handle) { assert_baklava_phase_1_invariants(action); animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle); if (!bag) { return {}; } return bag->fcurves(); } Span fcurves_for_action_slot(const Action &action, const slot_handle_t slot_handle) { assert_baklava_phase_1_invariants(action); const animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle); if (!bag) { return {}; } return bag->fcurves(); } FCurve *fcurve_find_in_action(bAction *act, const FCurveDescriptor &fcurve_descriptor) { if (act == nullptr) { return nullptr; } Action &action = act->wrap(); assert_baklava_phase_1_invariants(action); Layer *layer = action.layer(0); if (!layer) { return nullptr; } Strip *strip = layer->strip(0); if (!strip) { return nullptr; } StripKeyframeData &strip_data = strip->data(action); for (Channelbag *channelbag : strip_data.channelbags()) { FCurve *fcu = channelbag->fcurve_find(fcurve_descriptor); if (fcu) { return fcu; } } return nullptr; } FCurve *fcurve_find_in_assigned_slot(AnimData &adt, const FCurveDescriptor &fcurve_descriptor) { return fcurve_find_in_action_slot(adt.action, adt.slot_handle, fcurve_descriptor); } FCurve *fcurve_find_in_action_slot(bAction *act, const slot_handle_t slot_handle, const FCurveDescriptor &fcurve_descriptor) { if (act == nullptr) { return nullptr; } Action &action = act->wrap(); Channelbag *cbag = channelbag_for_action_slot(action, slot_handle); if (!cbag) { return nullptr; } return cbag->fcurve_find(fcurve_descriptor); } bool fcurve_matches_collection_path(const FCurve &fcurve, const StringRefNull collection_rna_path, const StringRefNull data_name) { BLI_assert(!collection_rna_path.is_empty()); const size_t quoted_name_size = data_name.size() + 1; char *quoted_name = static_cast(alloca(quoted_name_size)); if (!fcurve.rna_path) { return false; } /* Skipping names longer than `quoted_name_size` is OK since we're after an exact match. */ if (!BLI_str_quoted_substr( fcurve.rna_path, collection_rna_path.c_str(), quoted_name, quoted_name_size)) { return false; } if (quoted_name != data_name) { return false; } return true; } Vector fcurves_in_action_slot_filtered(bAction *act, const slot_handle_t slot_handle, FunctionRef predicate) { BLI_assert(act); Vector found; foreach_fcurve_in_action_slot(act->wrap(), slot_handle, [&](FCurve &fcurve) { if (predicate(fcurve)) { found.append(&fcurve); } }); return found; } Vector fcurves_in_span_filtered(Span fcurves, FunctionRef predicate) { Vector found; for (FCurve *fcurve : fcurves) { if (predicate(*fcurve)) { found.append(fcurve); } } return found; } Vector fcurves_in_listbase_filtered(ListBaseT fcurves, FunctionRef predicate) { Vector found; for (FCurve &fcurve : fcurves) { if (predicate(fcurve)) { found.append(&fcurve); } } return found; } FCurve *action_fcurve_ensure_ex(Main *bmain, bAction *act, PointerRNA *ptr, const FCurveDescriptor &fcurve_descriptor) { if (act == nullptr) { return nullptr; } /* NOTE: for layered actions we require the following: * * - `ptr` is non-null. * - `ptr` has an `owner_id` that already uses `act`. * * This isn't for any principled reason, but rather is because adding * support for layered actions to this function was a fix to make Follow * Path animation work properly with layered actions (see PR #124353), and * those are the requirements the Follow Path code conveniently met. * Moreover those requirements were also already met by the other call sites * that potentially call this function with layered actions. * * Trying to puzzle out what "should" happen when these requirements don't * hold, or if this is even the best place to handle the layered action * cases at all, was leading to discussion of larger changes than made sense * to tackle at that point. */ BLI_assert(ptr != nullptr); if (ptr == nullptr || ptr->owner_id == nullptr) { return nullptr; } return &action_fcurve_ensure(bmain, *act, *ptr->owner_id, fcurve_descriptor); } Channelbag &action_channelbag_ensure(bAction &dna_action, ID &animated_id) { Action &action = dna_action.wrap(); BLI_assert(get_action(animated_id) == &action); /* Ensure the id has an assigned slot. */ Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id); /* A nullptr here means the ID type is not animatable. But since the Action is already assigned, * it is certain that the ID is actually animatable. */ BLI_assert(slot); action.layer_keystrip_ensure(); assert_baklava_phase_1_invariants(action); StripKeyframeData &strip_data = action.layer(0)->strip(0)->data(action); return strip_data.channelbag_for_slot_ensure(*slot); } FCurve &action_fcurve_ensure(Main *bmain, bAction &dna_action, ID &animated_id, const FCurveDescriptor &fcurve_descriptor) { Channelbag &channelbag = action_channelbag_ensure(dna_action, animated_id); return channelbag.fcurve_ensure(bmain, fcurve_descriptor); } bool action_fcurve_remove(Action &action, FCurve &fcu) { if (action_fcurve_detach(action, fcu)) { BKE_fcurve_free(&fcu); return true; } return false; } bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach) { for (Layer *layer : action.layers()) { for (Strip *strip : layer->strips()) { if (!(strip->type() == Strip::Type::Keyframe)) { continue; } StripKeyframeData &strip_data = strip->data(action); for (Channelbag *bag : strip_data.channelbags()) { const bool is_detached = bag->fcurve_detach(fcurve_to_detach); if (is_detached) { return true; } } } } return false; } void action_fcurve_attach(Action &action, const slot_handle_t action_slot, FCurve &fcurve_to_attach, std::optional group_name) { Slot *slot = action.slot_for_handle(action_slot); BLI_assert(slot); if (!slot) { printf("Cannot find slot handle %d on Action %s, unable to attach F-Curve %s[%d] to it!\n", action_slot, action.id.name + 2, fcurve_to_attach.rna_path, fcurve_to_attach.array_index); return; } action.layer_keystrip_ensure(); StripKeyframeData &strip_data = action.layer(0)->strip(0)->data(action); Channelbag &cbag = strip_data.channelbag_for_slot_ensure(*slot); cbag.fcurve_append(fcurve_to_attach); if (group_name) { bActionGroup &group = cbag.channel_group_ensure(*group_name); cbag.fcurve_assign_to_channel_group(fcurve_to_attach, group); } } void action_fcurve_move(Action &action_dst, const slot_handle_t action_slot_dst, Action &action_src, FCurve &fcurve) { /* Store the group name locally, as the group will be removed if this was its * last F-Curve. */ std::optional group_name; if (fcurve.grp) { group_name = fcurve.grp->name; } const bool is_detached = action_fcurve_detach(action_src, fcurve); BLI_assert(is_detached); UNUSED_VARS_NDEBUG(is_detached); action_fcurve_attach(action_dst, action_slot_dst, fcurve, group_name); } void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src) { while (!channelbag_src.fcurves().is_empty()) { FCurve &fcurve = *channelbag_src.fcurve(0); /* Store the group name locally, as the group will be removed if this was its * last F-Curve. */ std::optional group_name; if (fcurve.grp) { group_name = fcurve.grp->name; } const bool is_detached = channelbag_src.fcurve_detach(fcurve); BLI_assert(is_detached); UNUSED_VARS_NDEBUG(is_detached); channelbag_dst.fcurve_append(fcurve); if (group_name) { bActionGroup &group = channelbag_dst.channel_group_ensure(*group_name); channelbag_dst.fcurve_assign_to_channel_group(fcurve, group); } } } bool Channelbag::fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group) { if (this->channel_groups().first_index_try(&to_group) == -1) { return false; } const int fcurve_index = this->fcurves().first_index_try(&fcurve); if (fcurve_index == -1) { return false; } if (fcurve.grp == &to_group) { return true; } /* Remove fcurve from old group, if it belongs to one. */ if (fcurve.grp != nullptr) { fcurve.grp->fcurve_range_length--; if (fcurve.grp->fcurve_range_length == 0) { const int group_index = this->channel_groups().first_index_try(fcurve.grp); this->channel_group_remove_raw(group_index); } this->restore_channel_group_invariants(); } array_shift_range(this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_group.fcurve_range_start + to_group.fcurve_range_length); to_group.fcurve_range_length++; this->restore_channel_group_invariants(); return true; } bool Channelbag::fcurve_ungroup(FCurve &fcurve) { const int fcurve_index = this->fcurves().first_index_try(&fcurve); if (fcurve_index == -1) { return false; } if (fcurve.grp == nullptr) { return true; } bActionGroup *old_group = fcurve.grp; array_shift_range(this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, this->fcurve_array_num - 1); old_group->fcurve_range_length--; if (old_group->fcurve_range_length == 0) { const int old_group_index = this->channel_groups().first_index_try(old_group); this->channel_group_remove_raw(old_group_index); } this->restore_channel_group_invariants(); return true; } ID *action_slot_get_id_for_keying(Main &bmain, Action &action, const slot_handle_t slot_handle, ID *primary_id) { Slot *slot = action.slot_for_handle(slot_handle); if (slot == nullptr) { return nullptr; } Span users = slot->users(bmain); if (users.size() == 1) { /* We only do this for `users.size() == 1` and not `users.size() >= 1` * because when there's more than one user it's ambiguous which user we * should return, and that would be unpredictable for end users of Blender. * We also expect that to be a corner case anyway. So instead we let that * case either get disambiguated by the primary ID in the case below, or * return null. */ return users[0]; } if (users.contains(primary_id)) { return primary_id; } return nullptr; } ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id) { Span users = slot.users(bmain); if (users.is_empty()) { return nullptr; } if (users.contains(primary_id)) { return primary_id; } return users[0]; } slot_handle_t first_slot_handle(const blender::bAction &dna_action) { const Action &action = dna_action.wrap(); if (action.slot_array_num == 0) { return Slot::unassigned; } return action.slot_array[0]->handle; } void assert_baklava_phase_1_invariants(const Action &action) { if (action.layers().is_empty()) { return; } BLI_assert(action.layers().size() == 1); assert_baklava_phase_1_invariants(*action.layer(0)); } void assert_baklava_phase_1_invariants(const Layer &layer) { if (layer.strips().is_empty()) { return; } BLI_assert(layer.strips().size() == 1); assert_baklava_phase_1_invariants(*layer.strip(0)); } void assert_baklava_phase_1_invariants(const Strip &strip) { UNUSED_VARS_NDEBUG(strip); BLI_assert(strip.type() == Strip::Type::Keyframe); BLI_assert(strip.is_infinite()); BLI_assert(strip.frame_offset == 0.0); } /** * Clone information from the given slot into this slot while retaining important info like the * slot handle and runtime data. This copies the identifier which might clash with other * identifiers on the action. Call `slot_identifier_ensure_unique` after. */ static void clone_slot(const Slot &from, Slot &to) { SlotRuntime *runtime = to.runtime; slot_handle_t handle = to.handle; *reinterpret_cast(&to) = *reinterpret_cast(&from); to.runtime = runtime; to.handle = handle; } void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_action) { BLI_assert(from_action.slots().contains(&source_slot)); BLI_assert(&from_action != &to_action); /* No merging of strips or layers is handled. All data is put into the assumed single strip. */ assert_baklava_phase_1_invariants(from_action); assert_baklava_phase_1_invariants(to_action); Slot &target_slot = to_action.slot_add(); clone_slot(source_slot, target_slot); slot_identifier_ensure_unique(to_action, target_slot); if (!from_action.layers().is_empty() && !from_action.layer(0)->strips().is_empty()) { StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data( from_action); Channelbag *channelbag = from_strip_data.channelbag_for_slot(source_slot.handle); /* It's perfectly fine for a slot to not have a channelbag on each keyframe strip. */ if (channelbag) { /* Only create the layer & keyframe strip if there is a channelbag to move * into it. Otherwise it's better to keep the Action lean, and defer their * creation when keys are inserted. */ to_action.layer_keystrip_ensure(); StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data( to_action); channelbag->slot_handle = target_slot.handle; grow_array_and_append( &to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag); const int index = from_strip_data.find_channelbag_index(*channelbag); shrink_array_and_remove( &from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index); } } /* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */ for (ID *user : source_slot.users(bmain)) { const auto assign_other_action = [&](ID & /* animated_id */, bAction *&action_ptr_ref, slot_handle_t &slot_handle_ref, char *slot_identifier) -> bool { /* Only reassign if the reference is actually from the same action. Could be from a different * action when using the NLA or action constraints. */ if (action_ptr_ref != &from_action) { return true; } { /* Assign the Action. */ const bool assign_ok = generic_assign_action( *user, &to_action, action_ptr_ref, slot_handle_ref, slot_identifier); BLI_assert_msg(assign_ok, "Expecting slotted Actions to always be assignable"); UNUSED_VARS_NDEBUG(assign_ok); } { /* Assign the Slot. */ const ActionSlotAssignmentResult result = generic_assign_action_slot( &target_slot, *user, action_ptr_ref, slot_handle_ref, slot_identifier); BLI_assert(result == ActionSlotAssignmentResult::OK); UNUSED_VARS_NDEBUG(result); } /* TODO: move the tagging of animated IDs into generic_assign_action() and * generic_assign_action_slot(), as that's closer to the modification of * the animated ID. * * This line was added here for now, to fix #136388 with minimal impact on * other code, so that the fix can be easily back-ported to Blender 4.4. */ DEG_id_tag_update(user, ID_RECALC_ANIMATION); return true; }; foreach_action_slot_use_with_references(*user, assign_other_action); } from_action.slot_remove(source_slot); } Slot &duplicate_slot(Action &action, const Slot &slot) { BLI_assert(action.slots().contains(const_cast(&slot))); /* Duplicate the slot itself. */ Slot &cloned_slot = action.slot_add(); clone_slot(slot, cloned_slot); slot_identifier_ensure_unique(action, cloned_slot); /* Duplicate each Channelbag for the source slot. */ for (int i = 0; i < action.strip_keyframe_data_array_num; i++) { StripKeyframeData &strip_data = action.strip_keyframe_data_array[i]->wrap(); strip_data.slot_data_duplicate(slot.handle, cloned_slot.handle); } /* The ID has changed, and so it needs to be re-evaluated. Animation does not * have to be flushed since nothing is using this slot yet. */ DEG_id_tag_update(&action.id, ID_RECALC_ANIMATION_NO_FLUSH); return cloned_slot; } } // namespace animrig } // namespace blender