Refactor: Anim: move animation.cc into action.cc
Move the content of `animation.cc` into `action.cc`. This is part of the removal of the `Animation` datablock, and the injection of its functionality into the `Action` datablock. The test file `animation_test.cc` is renamed to `action_test.cc`. No functional changes. Pull Request: https://projects.blender.org/blender/blender/pulls/122480
This commit is contained in:
@@ -22,7 +22,6 @@ set(INC_SYS
|
||||
set(SRC
|
||||
intern/action.cc
|
||||
intern/anim_rna.cc
|
||||
intern/animation.cc
|
||||
intern/animdata.cc
|
||||
intern/bone_collections.cc
|
||||
intern/bonecolor.cc
|
||||
@@ -68,7 +67,7 @@ if(WITH_GTESTS)
|
||||
set(TEST_INC
|
||||
)
|
||||
set(TEST_SRC
|
||||
intern/animation_test.cc
|
||||
intern/action_test.cc
|
||||
intern/bone_collections_test.cc
|
||||
intern/evaluation_test.cc
|
||||
)
|
||||
|
||||
@@ -6,21 +6,992 @@
|
||||
* \ingroup animrig
|
||||
*/
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
#include "BKE_action.h"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string.h"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
#include "DNA_action_defaults.h"
|
||||
#include "DNA_action_types.h"
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_array_utils.hh"
|
||||
#include "DNA_defaults.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_listbase_wrapper.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "BKE_action.h"
|
||||
#include "BKE_action.hh"
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_preview_image.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_path.hh"
|
||||
#include "RNA_prototypes.h"
|
||||
|
||||
#include "ED_keyframing.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
||||
#include "atomic_ops.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace blender::animrig {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Default name for animation bindings. The first two characters in the name indicate the ID type
|
||||
* of whatever is animated by it.
|
||||
*
|
||||
* Since the ID type may not be determined when the binding is created, the prefix starts out at
|
||||
* XX. Note that no code should use this XX value; use Binding::has_idtype() instead.
|
||||
*/
|
||||
constexpr const char *binding_default_name = "Binding";
|
||||
constexpr const char *binding_unbound_prefix = "XX";
|
||||
|
||||
} // namespace
|
||||
|
||||
static animrig::Layer &ActionLayer_alloc()
|
||||
{
|
||||
ActionLayer *layer = DNA_struct_default_alloc(ActionLayer);
|
||||
return layer->wrap();
|
||||
}
|
||||
static animrig::Strip &ActionStrip_alloc_infinite(const Strip::Type type)
|
||||
{
|
||||
ActionStrip *strip = nullptr;
|
||||
switch (type) {
|
||||
case Strip::Type::Keyframe: {
|
||||
KeyframeActionStrip *key_strip = MEM_new<KeyframeActionStrip>(__func__);
|
||||
strip = &key_strip->strip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_assert_msg(strip, "unsupported strip type");
|
||||
|
||||
/* Copy the default ActionStrip fields into the allocated data-block. */
|
||||
memcpy(strip, DNA_struct_default_get(ActionStrip), sizeof(*strip));
|
||||
return strip->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<typename T> 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 = reinterpret_cast<T *>(
|
||||
MEM_cnew_array<T *>(new_array_num, "animrig::animation/grow_array"));
|
||||
|
||||
blender::uninitialized_relocate_n(*array, *num, new_array);
|
||||
MEM_SAFE_FREE(*array);
|
||||
|
||||
*array = new_array;
|
||||
*num = new_array_num;
|
||||
}
|
||||
|
||||
template<typename T> static void grow_array_and_append(T **array, int *num, T item)
|
||||
{
|
||||
grow_array(array, num, 1);
|
||||
(*array)[*num - 1] = item;
|
||||
}
|
||||
|
||||
template<typename T> 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;
|
||||
T *new_array = reinterpret_cast<T *>(MEM_cnew_array<T *>(new_array_num, __func__));
|
||||
|
||||
blender::uninitialized_move_n(*array, new_array_num, new_array);
|
||||
MEM_freeN(*array);
|
||||
|
||||
*array = new_array;
|
||||
*num = new_array_num;
|
||||
}
|
||||
|
||||
/* ----- Animation implementation ----------- */
|
||||
|
||||
bool Action::is_empty() const
|
||||
{
|
||||
return this->layer_array_num == 0 && this->binding_array_num == 0 &&
|
||||
BLI_listbase_is_empty(&this->curves);
|
||||
}
|
||||
bool Action::is_action_legacy() const
|
||||
{
|
||||
/* This is a valid legacy Action only if there is no layered info. */
|
||||
return this->layer_array_num == 0 && this->binding_array_num == 0;
|
||||
}
|
||||
bool Action::is_action_layered() const
|
||||
{
|
||||
/* This is a valid layered Action if there is ANY layered info (because that
|
||||
* takes precedence) or when there is no legacy info. */
|
||||
return this->layer_array_num > 0 || this->binding_array_num > 0 ||
|
||||
BLI_listbase_is_empty(&this->curves);
|
||||
}
|
||||
|
||||
blender::Span<const Layer *> Action::layers() const
|
||||
{
|
||||
return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
|
||||
this->layer_array_num};
|
||||
}
|
||||
blender::MutableSpan<Layer *> Action::layers()
|
||||
{
|
||||
return blender::MutableSpan<Layer *>{reinterpret_cast<Layer **>(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 StringRefNull name)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
|
||||
Layer &new_layer = ActionLayer_alloc();
|
||||
STRNCPY_UTF8(new_layer.name, name.c_str());
|
||||
|
||||
grow_array_and_append<::ActionLayer *>(&this->layer_array, &this->layer_array_num, &new_layer);
|
||||
this->layer_active_index = this->layer_array_num - 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
blender::Span<const Binding *> Action::bindings() const
|
||||
{
|
||||
return blender::Span<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
|
||||
this->binding_array_num};
|
||||
}
|
||||
blender::MutableSpan<Binding *> Action::bindings()
|
||||
{
|
||||
return blender::MutableSpan<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
|
||||
this->binding_array_num};
|
||||
}
|
||||
const Binding *Action::binding(const int64_t index) const
|
||||
{
|
||||
return &this->binding_array[index]->wrap();
|
||||
}
|
||||
Binding *Action::binding(const int64_t index)
|
||||
{
|
||||
return &this->binding_array[index]->wrap();
|
||||
}
|
||||
|
||||
Binding *Action::binding_for_handle(const binding_handle_t handle)
|
||||
{
|
||||
const Binding *binding = const_cast<const Action *>(this)->binding_for_handle(handle);
|
||||
return const_cast<Binding *>(binding);
|
||||
}
|
||||
|
||||
const Binding *Action::binding_for_handle(const binding_handle_t handle) const
|
||||
{
|
||||
/* TODO: implement hash-map lookup. */
|
||||
for (const Binding *binding : bindings()) {
|
||||
if (binding->handle == handle) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void anim_binding_name_ensure_unique(Action &animation, Binding &binding)
|
||||
{
|
||||
/* Cannot capture parameters by reference in the lambda, as that would change its signature
|
||||
* and no longer be compatible with BLI_uniquename_cb(). That's why this struct is necessary. */
|
||||
struct DupNameCheckData {
|
||||
Action &anim;
|
||||
Binding &binding;
|
||||
};
|
||||
DupNameCheckData check_data = {animation, binding};
|
||||
|
||||
auto check_name_is_used = [](void *arg, const char *name) -> bool {
|
||||
DupNameCheckData *data = static_cast<DupNameCheckData *>(arg);
|
||||
for (const Binding *binding : data->anim.bindings()) {
|
||||
if (binding == &data->binding) {
|
||||
/* Don't compare against the binding that's being renamed. */
|
||||
continue;
|
||||
}
|
||||
if (STREQ(binding->name, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BLI_uniquename_cb(check_name_is_used, &check_data, "", '.', binding.name, sizeof(binding.name));
|
||||
}
|
||||
|
||||
/* TODO: maybe this function should only set the 'name 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. */
|
||||
void Action::binding_name_set(Main &bmain, Binding &binding, const StringRefNull new_name)
|
||||
{
|
||||
this->binding_name_define(binding, new_name);
|
||||
this->binding_name_propagate(bmain, binding);
|
||||
}
|
||||
|
||||
void Action::binding_name_define(Binding &binding, const StringRefNull new_name)
|
||||
{
|
||||
BLI_assert_msg(
|
||||
StringRef(new_name).size() >= Binding::name_length_min,
|
||||
"Animation Bindings must be large enough for a 2-letter ID code + the display name");
|
||||
STRNCPY_UTF8(binding.name, new_name.c_str());
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
}
|
||||
|
||||
void Action::binding_name_propagate(Main &bmain, const Binding &binding)
|
||||
{
|
||||
/* Just loop over all animatable IDs in the main database. */
|
||||
ListBase *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 Animation. */
|
||||
continue;
|
||||
}
|
||||
if (adt->binding_handle != binding.handle) {
|
||||
/* Not animated by this Binding. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ensure the Binding name on the AnimData is correct. */
|
||||
STRNCPY_UTF8(adt->binding_name, binding.name);
|
||||
}
|
||||
FOREACH_MAIN_LISTBASE_ID_END;
|
||||
}
|
||||
FOREACH_MAIN_LISTBASE_END;
|
||||
}
|
||||
|
||||
Binding *Action::binding_find_by_name(const StringRefNull binding_name)
|
||||
{
|
||||
for (Binding *binding : bindings()) {
|
||||
if (STREQ(binding->name, binding_name.c_str())) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Binding &Action::binding_allocate()
|
||||
{
|
||||
Binding &binding = MEM_new<ActionBinding>(__func__)->wrap();
|
||||
this->last_binding_handle++;
|
||||
BLI_assert_msg(this->last_binding_handle > 0, "Animation Binding handle overflow");
|
||||
binding.handle = this->last_binding_handle;
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding &Action::binding_add()
|
||||
{
|
||||
Binding &binding = this->binding_allocate();
|
||||
|
||||
/* Assign the default name and the 'unbound' name prefix. */
|
||||
STRNCPY_UTF8(binding.name, binding_unbound_prefix);
|
||||
BLI_strncpy_utf8(binding.name + 2, DATA_(binding_default_name), ARRAY_SIZE(binding.name) - 2);
|
||||
|
||||
/* Append the Binding to the animation data-block. */
|
||||
grow_array_and_append<::ActionBinding *>(
|
||||
&this->binding_array, &this->binding_array_num, &binding);
|
||||
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding &Action::binding_add_for_id(const ID &animated_id)
|
||||
{
|
||||
Binding &binding = this->binding_add();
|
||||
|
||||
binding.idtype = GS(animated_id.name);
|
||||
this->binding_name_define(binding, animated_id.name);
|
||||
|
||||
/* No need to call anim.binding_name_propagate() as nothing will be using
|
||||
* this brand new Binding yet. */
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding *Action::find_suitable_binding_for(const ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
||||
|
||||
/* The binding handle is only valid when this action has already been
|
||||
* assigned. Otherwise it's meaningless. */
|
||||
if (adt && adt->action == this) {
|
||||
Binding *binding = this->binding_for_handle(adt->binding_handle);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try the binding name from the AnimData, if it is set. */
|
||||
if (adt && adt->binding_name[0]) {
|
||||
Binding *binding = this->binding_find_by_name(adt->binding_name);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
/* As a last resort, search for the ID name. */
|
||||
Binding *binding = this->binding_find_by_name(animated_id.name);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Action::is_binding_animated(const binding_handle_t binding_handle) const
|
||||
{
|
||||
if (binding_handle == Binding::unassigned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<const FCurve *> fcurves = fcurves_for_animation(*this, binding_handle);
|
||||
return !fcurves.is_empty();
|
||||
}
|
||||
|
||||
bool Action::assign_id(Binding *binding, ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
|
||||
if (!adt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (adt->action && adt->action != this) {
|
||||
/* The caller should unassign the ID from its existing animation first, or
|
||||
* use the top-level function `assign_animation(anim, ID)`. */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (binding) {
|
||||
if (!binding->is_suitable_for(animated_id)) {
|
||||
return false;
|
||||
}
|
||||
this->binding_setup_for_id(*binding, animated_id);
|
||||
|
||||
adt->binding_handle = binding->handle;
|
||||
/* Always make sure the ID's binding name matches the assigned binding. */
|
||||
STRNCPY_UTF8(adt->binding_name, binding->name);
|
||||
}
|
||||
else {
|
||||
unassign_binding(*adt);
|
||||
}
|
||||
|
||||
if (!adt->action) {
|
||||
/* Due to the precondition check above, we know that adt->action is either 'this' (in which
|
||||
* case the user count is already correct) or `nullptr` (in which case this is a new reference,
|
||||
* and the user count should be increased). */
|
||||
id_us_plus(&this->id);
|
||||
adt->action = this;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Action::binding_name_ensure_prefix(Binding &binding)
|
||||
{
|
||||
binding.name_ensure_prefix();
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
}
|
||||
|
||||
void Action::binding_setup_for_id(Binding &binding, const ID &animated_id)
|
||||
{
|
||||
if (binding.has_idtype()) {
|
||||
BLI_assert(binding.idtype == GS(animated_id.name));
|
||||
return;
|
||||
}
|
||||
|
||||
binding.idtype = GS(animated_id.name);
|
||||
this->binding_name_ensure_prefix(binding);
|
||||
}
|
||||
|
||||
void Action::unassign_id(ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
||||
BLI_assert_msg(adt, "ID is not animated at all");
|
||||
BLI_assert_msg(adt->action == this, "ID is not assigned to this Animation");
|
||||
|
||||
unassign_binding(*adt);
|
||||
|
||||
id_us_min(&this->id);
|
||||
adt->action = nullptr;
|
||||
}
|
||||
|
||||
/* ----- ActionLayer implementation ----------- */
|
||||
|
||||
Layer::Layer(const Layer &other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
|
||||
/* Strips. */
|
||||
this->strip_array = MEM_cnew_array<ActionStrip *>(other.strip_array_num, __func__);
|
||||
for (int i : other.strips().index_range()) {
|
||||
this->strip_array[i] = other.strip(i)->duplicate(__func__);
|
||||
}
|
||||
}
|
||||
|
||||
Layer::~Layer()
|
||||
{
|
||||
for (Strip *strip : this->strips()) {
|
||||
MEM_delete(strip);
|
||||
}
|
||||
MEM_SAFE_FREE(this->strip_array);
|
||||
this->strip_array_num = 0;
|
||||
}
|
||||
|
||||
blender::Span<const Strip *> Layer::strips() const
|
||||
{
|
||||
return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
|
||||
this->strip_array_num};
|
||||
}
|
||||
blender::MutableSpan<Strip *> Layer::strips()
|
||||
{
|
||||
return blender::MutableSpan<Strip *>{reinterpret_cast<Strip **>(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(const Strip::Type strip_type)
|
||||
{
|
||||
Strip &strip = ActionStrip_alloc_infinite(strip_type);
|
||||
|
||||
/* Add the new strip to the strip array. */
|
||||
grow_array_and_append<::ActionStrip *>(&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(Strip &strip_to_remove)
|
||||
{
|
||||
const int64_t strip_index = this->find_strip_index(strip_to_remove);
|
||||
if (strip_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dna::array::remove_index(
|
||||
&this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* ----- ActionBinding implementation ----------- */
|
||||
|
||||
bool Binding::is_suitable_for(const ID &animated_id) const
|
||||
{
|
||||
if (!this->has_idtype()) {
|
||||
/* Without specific ID type set, this Binding can animate any ID. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check that the ID type is compatible with this binding. */
|
||||
const int animated_idtype = GS(animated_id.name);
|
||||
return this->idtype == animated_idtype;
|
||||
}
|
||||
|
||||
bool Binding::has_idtype() const
|
||||
{
|
||||
return this->idtype != 0;
|
||||
}
|
||||
|
||||
bool assign_animation(Action &anim, ID &animated_id)
|
||||
{
|
||||
unassign_animation(animated_id);
|
||||
|
||||
Binding *binding = anim.find_suitable_binding_for(animated_id);
|
||||
return anim.assign_id(binding, animated_id);
|
||||
}
|
||||
|
||||
void unassign_animation(ID &animated_id)
|
||||
{
|
||||
Action *anim = get_animation(animated_id);
|
||||
if (!anim) {
|
||||
return;
|
||||
}
|
||||
anim->unassign_id(animated_id);
|
||||
}
|
||||
|
||||
void unassign_binding(AnimData &adt)
|
||||
{
|
||||
/* Before unassigning, make sure that the stored Binding name is up to date. The binding name
|
||||
* might have changed in a way that wasn't copied into the ADT yet (for example when the
|
||||
* Animation data-block is linked from another file), so better copy the name to be sure that it
|
||||
* can be transparently reassigned later.
|
||||
*
|
||||
* TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure this
|
||||
* name is always correct. */
|
||||
if (adt.action) {
|
||||
const Action &anim = adt.action->wrap();
|
||||
const Binding *binding = anim.binding_for_handle(adt.binding_handle);
|
||||
if (binding) {
|
||||
STRNCPY_UTF8(adt.binding_name, binding->name);
|
||||
}
|
||||
}
|
||||
|
||||
adt.binding_handle = Binding::unassigned;
|
||||
}
|
||||
|
||||
/* TODO: rename to get_action(). */
|
||||
Action *get_animation(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::string Binding::name_prefix_for_idtype() const
|
||||
{
|
||||
if (!this->has_idtype()) {
|
||||
return binding_unbound_prefix;
|
||||
}
|
||||
|
||||
char name[3] = {0};
|
||||
*reinterpret_cast<short *>(name) = this->idtype;
|
||||
return name;
|
||||
}
|
||||
|
||||
StringRefNull Binding::name_without_prefix() const
|
||||
{
|
||||
BLI_assert(StringRef(this->name).size() >= name_length_min);
|
||||
|
||||
/* Avoid accessing an uninitialized part of the string accidentally. */
|
||||
if (this->name[0] == '\0' || this->name[1] == '\0') {
|
||||
return "";
|
||||
}
|
||||
return this->name + 2;
|
||||
}
|
||||
|
||||
void Binding::name_ensure_prefix()
|
||||
{
|
||||
BLI_assert(StringRef(this->name).size() >= name_length_min);
|
||||
|
||||
if (StringRef(this->name).size() < 2) {
|
||||
/* The code below would overwrite the trailing 0-byte. */
|
||||
this->name[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->name[0] = binding_unbound_prefix[0];
|
||||
this->name[1] = binding_unbound_prefix[1];
|
||||
return;
|
||||
}
|
||||
|
||||
*reinterpret_cast<short *>(this->name) = this->idtype;
|
||||
}
|
||||
|
||||
/* ----- ActionStrip implementation ----------- */
|
||||
|
||||
Strip *Strip::duplicate(const StringRefNull allocation_name) const
|
||||
{
|
||||
switch (this->type()) {
|
||||
case Type::Keyframe: {
|
||||
const KeyframeStrip &source = this->as<KeyframeStrip>();
|
||||
KeyframeStrip *copy = MEM_new<KeyframeStrip>(allocation_name.c_str(), source);
|
||||
return ©->strip.wrap();
|
||||
}
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Strip::~Strip()
|
||||
{
|
||||
switch (this->type()) {
|
||||
case Type::Keyframe:
|
||||
this->as<KeyframeStrip>().~KeyframeStrip();
|
||||
return;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
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<float>::infinity(),
|
||||
"only the end frame can be at positive infinity");
|
||||
BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
|
||||
"only the start frame can be at negative infinity");
|
||||
this->frame_start = frame_start;
|
||||
this->frame_end = frame_end;
|
||||
}
|
||||
|
||||
/* ----- KeyframeActionStrip implementation ----------- */
|
||||
|
||||
KeyframeStrip::KeyframeStrip(const KeyframeStrip &other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
|
||||
this->channelbags_array = MEM_cnew_array<ActionChannelBag *>(other.channelbags_array_num,
|
||||
__func__);
|
||||
Span<const ChannelBag *> channelbags_src = other.channelbags();
|
||||
for (int i : channelbags_src.index_range()) {
|
||||
this->channelbags_array[i] = MEM_new<animrig::ChannelBag>(__func__, *other.channelbag(i));
|
||||
}
|
||||
}
|
||||
|
||||
KeyframeStrip::~KeyframeStrip()
|
||||
{
|
||||
for (ChannelBag *channelbag_for_binding : this->channelbags()) {
|
||||
MEM_delete(channelbag_for_binding);
|
||||
}
|
||||
MEM_SAFE_FREE(this->channelbags_array);
|
||||
this->channelbags_array_num = 0;
|
||||
}
|
||||
|
||||
template<> bool Strip::is<KeyframeStrip>() const
|
||||
{
|
||||
return this->type() == Type::Keyframe;
|
||||
}
|
||||
|
||||
template<> KeyframeStrip &Strip::as<KeyframeStrip>()
|
||||
{
|
||||
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
|
||||
return *reinterpret_cast<KeyframeStrip *>(this);
|
||||
}
|
||||
|
||||
template<> const KeyframeStrip &Strip::as<KeyframeStrip>() const
|
||||
{
|
||||
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
|
||||
return *reinterpret_cast<const KeyframeStrip *>(this);
|
||||
}
|
||||
|
||||
blender::Span<const ChannelBag *> KeyframeStrip::channelbags() const
|
||||
{
|
||||
return blender::Span<ChannelBag *>{reinterpret_cast<ChannelBag **>(this->channelbags_array),
|
||||
this->channelbags_array_num};
|
||||
}
|
||||
blender::MutableSpan<ChannelBag *> KeyframeStrip::channelbags()
|
||||
{
|
||||
return blender::MutableSpan<ChannelBag *>{
|
||||
reinterpret_cast<ChannelBag **>(this->channelbags_array), this->channelbags_array_num};
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag(const int64_t index) const
|
||||
{
|
||||
return &this->channelbags_array[index]->wrap();
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag(const int64_t index)
|
||||
{
|
||||
return &this->channelbags_array[index]->wrap();
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag_for_binding(
|
||||
const binding_handle_t binding_handle) const
|
||||
{
|
||||
for (const ChannelBag *channels : this->channelbags()) {
|
||||
if (channels->binding_handle == binding_handle) {
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag_for_binding(const binding_handle_t binding_handle)
|
||||
{
|
||||
const auto *const_this = const_cast<const KeyframeStrip *>(this);
|
||||
const auto *const_channels = const_this->channelbag_for_binding(binding_handle);
|
||||
return const_cast<ChannelBag *>(const_channels);
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag_for_binding(const Binding &binding) const
|
||||
{
|
||||
return this->channelbag_for_binding(binding.handle);
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag_for_binding(const Binding &binding)
|
||||
{
|
||||
return this->channelbag_for_binding(binding.handle);
|
||||
}
|
||||
|
||||
ChannelBag &KeyframeStrip::channelbag_for_binding_add(const Binding &binding)
|
||||
{
|
||||
BLI_assert_msg(channelbag_for_binding(binding) == nullptr,
|
||||
"Cannot add chans-for-binding for already-registered binding");
|
||||
|
||||
ChannelBag &channels = MEM_new<ActionChannelBag>(__func__)->wrap();
|
||||
channels.binding_handle = binding.handle;
|
||||
|
||||
grow_array_and_append<ActionChannelBag *>(
|
||||
&this->channelbags_array, &this->channelbags_array_num, &channels);
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
FCurve *KeyframeStrip::fcurve_find(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index)
|
||||
{
|
||||
ChannelBag *channels = this->channelbag_for_binding(binding);
|
||||
if (channels == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Copy of the logic in BKE_fcurve_find(), but then compatible with our array-of-FCurves
|
||||
* instead of ListBase. */
|
||||
|
||||
for (FCurve *fcu : channels->fcurves()) {
|
||||
/* Check indices first, much cheaper than a string comparison. */
|
||||
/* Simple string-compare (this assumes that they have the same root...) */
|
||||
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
|
||||
return fcu;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FCurve &KeyframeStrip::fcurve_find_or_create(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index)
|
||||
{
|
||||
if (FCurve *existing_fcurve = this->fcurve_find(binding, rna_path, array_index)) {
|
||||
return *existing_fcurve;
|
||||
}
|
||||
|
||||
FCurve *new_fcurve = create_fcurve_for_channel(rna_path.c_str(), array_index);
|
||||
|
||||
ChannelBag *channels = this->channelbag_for_binding(binding);
|
||||
if (channels == nullptr) {
|
||||
channels = &this->channelbag_for_binding_add(binding);
|
||||
}
|
||||
|
||||
if (channels->fcurve_array_num == 0) {
|
||||
new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
|
||||
}
|
||||
|
||||
grow_array_and_append(&channels->fcurve_array, &channels->fcurve_array_num, new_fcurve);
|
||||
return *new_fcurve;
|
||||
}
|
||||
|
||||
SingleKeyingResult KeyframeStrip::keyframe_insert(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index,
|
||||
const float2 time_value,
|
||||
const KeyframeSettings &settings)
|
||||
{
|
||||
FCurve &fcurve = this->fcurve_find_or_create(binding, rna_path, array_index);
|
||||
|
||||
if (!BKE_fcurve_is_keyframable(&fcurve)) {
|
||||
/* TODO: handle this properly, in a way that can be communicated to the user. */
|
||||
std::fprintf(stderr,
|
||||
"FCurve %s[%d] for binding %s doesn't allow inserting keys.\n",
|
||||
rna_path.c_str(),
|
||||
array_index,
|
||||
binding.name);
|
||||
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
||||
}
|
||||
|
||||
/* TODO: Handle the eInsertKeyFlags. */
|
||||
const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
|
||||
&fcurve, time_value, settings, eInsertKeyFlags(0));
|
||||
|
||||
if (insert_vert_result != SingleKeyingResult::SUCCESS) {
|
||||
std::fprintf(stderr,
|
||||
"Could not insert key into FCurve %s[%d] for binding %s.\n",
|
||||
rna_path.c_str(),
|
||||
array_index,
|
||||
binding.name);
|
||||
return insert_vert_result;
|
||||
}
|
||||
|
||||
return SingleKeyingResult::SUCCESS;
|
||||
}
|
||||
|
||||
/* ActionChannelBag implementation. */
|
||||
|
||||
ChannelBag::ChannelBag(const ChannelBag &other)
|
||||
{
|
||||
this->binding_handle = other.binding_handle;
|
||||
this->fcurve_array_num = other.fcurve_array_num;
|
||||
|
||||
this->fcurve_array = MEM_cnew_array<FCurve *>(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);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelBag::~ChannelBag()
|
||||
{
|
||||
for (FCurve *fcu : this->fcurves()) {
|
||||
BKE_fcurve_free(fcu);
|
||||
}
|
||||
MEM_SAFE_FREE(this->fcurve_array);
|
||||
this->fcurve_array_num = 0;
|
||||
}
|
||||
|
||||
blender::Span<const FCurve *> ChannelBag::fcurves() const
|
||||
{
|
||||
return blender::Span<FCurve *>{this->fcurve_array, this->fcurve_array_num};
|
||||
}
|
||||
blender::MutableSpan<FCurve *> ChannelBag::fcurves()
|
||||
{
|
||||
return blender::MutableSpan<FCurve *>{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];
|
||||
}
|
||||
|
||||
const FCurve *ChannelBag::fcurve_find(const StringRefNull rna_path, const int array_index) const
|
||||
{
|
||||
for (const FCurve *fcu : this->fcurves()) {
|
||||
/* Check indices first, much cheaper than a string comparison. */
|
||||
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
|
||||
return fcu;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Utility function implementations. */
|
||||
|
||||
static const animrig::ChannelBag *channelbag_for_animation(const Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
if (binding_handle == Binding::unassigned) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const animrig::Layer *layer : anim.layers()) {
|
||||
for (const animrig::Strip *strip : layer->strips()) {
|
||||
switch (strip->type()) {
|
||||
case animrig::Strip::Type::Keyframe: {
|
||||
const animrig::KeyframeStrip &key_strip = strip->as<animrig::KeyframeStrip>();
|
||||
const animrig::ChannelBag *bag = key_strip.channelbag_for_binding(binding_handle);
|
||||
if (bag) {
|
||||
return bag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static animrig::ChannelBag *channelbag_for_animation(Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
const animrig::ChannelBag *const_bag = channelbag_for_animation(const_cast<const Action &>(anim),
|
||||
binding_handle);
|
||||
return const_cast<animrig::ChannelBag *>(const_bag);
|
||||
}
|
||||
|
||||
Span<FCurve *> fcurves_for_animation(Action &anim, const binding_handle_t binding_handle)
|
||||
{
|
||||
animrig::ChannelBag *bag = channelbag_for_animation(anim, binding_handle);
|
||||
if (!bag) {
|
||||
return {};
|
||||
}
|
||||
return bag->fcurves();
|
||||
}
|
||||
|
||||
Span<const FCurve *> fcurves_for_animation(const Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
const animrig::ChannelBag *bag = channelbag_for_animation(anim, binding_handle);
|
||||
if (!bag) {
|
||||
return {};
|
||||
}
|
||||
return bag->fcurves();
|
||||
}
|
||||
|
||||
FCurve *action_fcurve_find(bAction *act, const char rna_path[], const int array_index)
|
||||
{
|
||||
if (ELEM(nullptr, act, rna_path)) {
|
||||
|
||||
@@ -1,984 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "DNA_action_defaults.h"
|
||||
#include "DNA_action_types.h"
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_array_utils.hh"
|
||||
#include "DNA_defaults.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_listbase_wrapper.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
|
||||
#include "BKE_action.hh"
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_preview_image.hh"
|
||||
|
||||
#include "ED_keyframing.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "atomic_ops.h"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace blender::animrig {
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* Default name for animation bindings. The first two characters in the name indicate the ID type
|
||||
* of whatever is animated by it.
|
||||
*
|
||||
* Since the ID type may not be determined when the binding is created, the prefix starts out at
|
||||
* XX. Note that no code should use this XX value; use Binding::has_idtype() instead.
|
||||
*/
|
||||
constexpr const char *binding_default_name = "Binding";
|
||||
constexpr const char *binding_unbound_prefix = "XX";
|
||||
|
||||
} // namespace
|
||||
|
||||
static animrig::Layer &ActionLayer_alloc()
|
||||
{
|
||||
ActionLayer *layer = DNA_struct_default_alloc(ActionLayer);
|
||||
return layer->wrap();
|
||||
}
|
||||
static animrig::Strip &ActionStrip_alloc_infinite(const Strip::Type type)
|
||||
{
|
||||
ActionStrip *strip = nullptr;
|
||||
switch (type) {
|
||||
case Strip::Type::Keyframe: {
|
||||
KeyframeActionStrip *key_strip = MEM_new<KeyframeActionStrip>(__func__);
|
||||
strip = &key_strip->strip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_assert_msg(strip, "unsupported strip type");
|
||||
|
||||
/* Copy the default ActionStrip fields into the allocated data-block. */
|
||||
memcpy(strip, DNA_struct_default_get(ActionStrip), sizeof(*strip));
|
||||
return strip->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<typename T> 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 = reinterpret_cast<T *>(
|
||||
MEM_cnew_array<T *>(new_array_num, "animrig::animation/grow_array"));
|
||||
|
||||
blender::uninitialized_relocate_n(*array, *num, new_array);
|
||||
MEM_SAFE_FREE(*array);
|
||||
|
||||
*array = new_array;
|
||||
*num = new_array_num;
|
||||
}
|
||||
|
||||
template<typename T> static void grow_array_and_append(T **array, int *num, T item)
|
||||
{
|
||||
grow_array(array, num, 1);
|
||||
(*array)[*num - 1] = item;
|
||||
}
|
||||
|
||||
template<typename T> 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;
|
||||
T *new_array = reinterpret_cast<T *>(MEM_cnew_array<T *>(new_array_num, __func__));
|
||||
|
||||
blender::uninitialized_move_n(*array, new_array_num, new_array);
|
||||
MEM_freeN(*array);
|
||||
|
||||
*array = new_array;
|
||||
*num = new_array_num;
|
||||
}
|
||||
|
||||
/* ----- Animation implementation ----------- */
|
||||
|
||||
bool Action::is_empty() const
|
||||
{
|
||||
return this->layer_array_num == 0 && this->binding_array_num == 0 &&
|
||||
BLI_listbase_is_empty(&this->curves);
|
||||
}
|
||||
bool Action::is_action_legacy() const
|
||||
{
|
||||
/* This is a valid legacy Action only if there is no layered info. */
|
||||
return this->layer_array_num == 0 && this->binding_array_num == 0;
|
||||
}
|
||||
bool Action::is_action_layered() const
|
||||
{
|
||||
/* This is a valid layered Action if there is ANY layered info (because that
|
||||
* takes precedence) or when there is no legacy info. */
|
||||
return this->layer_array_num > 0 || this->binding_array_num > 0 ||
|
||||
BLI_listbase_is_empty(&this->curves);
|
||||
}
|
||||
|
||||
blender::Span<const Layer *> Action::layers() const
|
||||
{
|
||||
return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
|
||||
this->layer_array_num};
|
||||
}
|
||||
blender::MutableSpan<Layer *> Action::layers()
|
||||
{
|
||||
return blender::MutableSpan<Layer *>{reinterpret_cast<Layer **>(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 StringRefNull name)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
|
||||
Layer &new_layer = ActionLayer_alloc();
|
||||
STRNCPY_UTF8(new_layer.name, name.c_str());
|
||||
|
||||
grow_array_and_append<::ActionLayer *>(&this->layer_array, &this->layer_array_num, &new_layer);
|
||||
this->layer_active_index = this->layer_array_num - 1;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
blender::Span<const Binding *> Action::bindings() const
|
||||
{
|
||||
return blender::Span<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
|
||||
this->binding_array_num};
|
||||
}
|
||||
blender::MutableSpan<Binding *> Action::bindings()
|
||||
{
|
||||
return blender::MutableSpan<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
|
||||
this->binding_array_num};
|
||||
}
|
||||
const Binding *Action::binding(const int64_t index) const
|
||||
{
|
||||
return &this->binding_array[index]->wrap();
|
||||
}
|
||||
Binding *Action::binding(const int64_t index)
|
||||
{
|
||||
return &this->binding_array[index]->wrap();
|
||||
}
|
||||
|
||||
Binding *Action::binding_for_handle(const binding_handle_t handle)
|
||||
{
|
||||
const Binding *binding = const_cast<const Action *>(this)->binding_for_handle(handle);
|
||||
return const_cast<Binding *>(binding);
|
||||
}
|
||||
|
||||
const Binding *Action::binding_for_handle(const binding_handle_t handle) const
|
||||
{
|
||||
/* TODO: implement hash-map lookup. */
|
||||
for (const Binding *binding : bindings()) {
|
||||
if (binding->handle == handle) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void anim_binding_name_ensure_unique(Action &animation, Binding &binding)
|
||||
{
|
||||
/* Cannot capture parameters by reference in the lambda, as that would change its signature
|
||||
* and no longer be compatible with BLI_uniquename_cb(). That's why this struct is necessary. */
|
||||
struct DupNameCheckData {
|
||||
Action &anim;
|
||||
Binding &binding;
|
||||
};
|
||||
DupNameCheckData check_data = {animation, binding};
|
||||
|
||||
auto check_name_is_used = [](void *arg, const char *name) -> bool {
|
||||
DupNameCheckData *data = static_cast<DupNameCheckData *>(arg);
|
||||
for (const Binding *binding : data->anim.bindings()) {
|
||||
if (binding == &data->binding) {
|
||||
/* Don't compare against the binding that's being renamed. */
|
||||
continue;
|
||||
}
|
||||
if (STREQ(binding->name, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BLI_uniquename_cb(check_name_is_used, &check_data, "", '.', binding.name, sizeof(binding.name));
|
||||
}
|
||||
|
||||
/* TODO: maybe this function should only set the 'name 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. */
|
||||
void Action::binding_name_set(Main &bmain, Binding &binding, const StringRefNull new_name)
|
||||
{
|
||||
this->binding_name_define(binding, new_name);
|
||||
this->binding_name_propagate(bmain, binding);
|
||||
}
|
||||
|
||||
void Action::binding_name_define(Binding &binding, const StringRefNull new_name)
|
||||
{
|
||||
BLI_assert_msg(
|
||||
StringRef(new_name).size() >= Binding::name_length_min,
|
||||
"Animation Bindings must be large enough for a 2-letter ID code + the display name");
|
||||
STRNCPY_UTF8(binding.name, new_name.c_str());
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
}
|
||||
|
||||
void Action::binding_name_propagate(Main &bmain, const Binding &binding)
|
||||
{
|
||||
/* Just loop over all animatable IDs in the main database. */
|
||||
ListBase *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 Animation. */
|
||||
continue;
|
||||
}
|
||||
if (adt->binding_handle != binding.handle) {
|
||||
/* Not animated by this Binding. */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Ensure the Binding name on the AnimData is correct. */
|
||||
STRNCPY_UTF8(adt->binding_name, binding.name);
|
||||
}
|
||||
FOREACH_MAIN_LISTBASE_ID_END;
|
||||
}
|
||||
FOREACH_MAIN_LISTBASE_END;
|
||||
}
|
||||
|
||||
Binding *Action::binding_find_by_name(const StringRefNull binding_name)
|
||||
{
|
||||
for (Binding *binding : bindings()) {
|
||||
if (STREQ(binding->name, binding_name.c_str())) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Binding &Action::binding_allocate()
|
||||
{
|
||||
Binding &binding = MEM_new<ActionBinding>(__func__)->wrap();
|
||||
this->last_binding_handle++;
|
||||
BLI_assert_msg(this->last_binding_handle > 0, "Animation Binding handle overflow");
|
||||
binding.handle = this->last_binding_handle;
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding &Action::binding_add()
|
||||
{
|
||||
Binding &binding = this->binding_allocate();
|
||||
|
||||
/* Assign the default name and the 'unbound' name prefix. */
|
||||
STRNCPY_UTF8(binding.name, binding_unbound_prefix);
|
||||
BLI_strncpy_utf8(binding.name + 2, DATA_(binding_default_name), ARRAY_SIZE(binding.name) - 2);
|
||||
|
||||
/* Append the Binding to the animation data-block. */
|
||||
grow_array_and_append<::ActionBinding *>(
|
||||
&this->binding_array, &this->binding_array_num, &binding);
|
||||
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding &Action::binding_add_for_id(const ID &animated_id)
|
||||
{
|
||||
Binding &binding = this->binding_add();
|
||||
|
||||
binding.idtype = GS(animated_id.name);
|
||||
this->binding_name_define(binding, animated_id.name);
|
||||
|
||||
/* No need to call anim.binding_name_propagate() as nothing will be using
|
||||
* this brand new Binding yet. */
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding *Action::find_suitable_binding_for(const ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
||||
|
||||
/* The binding handle is only valid when this action has already been
|
||||
* assigned. Otherwise it's meaningless. */
|
||||
if (adt && adt->action == this) {
|
||||
Binding *binding = this->binding_for_handle(adt->binding_handle);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try the binding name from the AnimData, if it is set. */
|
||||
if (adt && adt->binding_name[0]) {
|
||||
Binding *binding = this->binding_find_by_name(adt->binding_name);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
/* As a last resort, search for the ID name. */
|
||||
Binding *binding = this->binding_find_by_name(animated_id.name);
|
||||
if (binding && binding->is_suitable_for(animated_id)) {
|
||||
return binding;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Action::is_binding_animated(const binding_handle_t binding_handle) const
|
||||
{
|
||||
if (binding_handle == Binding::unassigned) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Span<const FCurve *> fcurves = fcurves_for_animation(*this, binding_handle);
|
||||
return !fcurves.is_empty();
|
||||
}
|
||||
|
||||
bool Action::assign_id(Binding *binding, ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
|
||||
if (!adt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (adt->action && adt->action != this) {
|
||||
/* The caller should unassign the ID from its existing animation first, or
|
||||
* use the top-level function `assign_animation(anim, ID)`. */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (binding) {
|
||||
if (!binding->is_suitable_for(animated_id)) {
|
||||
return false;
|
||||
}
|
||||
this->binding_setup_for_id(*binding, animated_id);
|
||||
|
||||
adt->binding_handle = binding->handle;
|
||||
/* Always make sure the ID's binding name matches the assigned binding. */
|
||||
STRNCPY_UTF8(adt->binding_name, binding->name);
|
||||
}
|
||||
else {
|
||||
unassign_binding(*adt);
|
||||
}
|
||||
|
||||
if (!adt->action) {
|
||||
/* Due to the precondition check above, we know that adt->action is either 'this' (in which
|
||||
* case the user count is already correct) or `nullptr` (in which case this is a new reference,
|
||||
* and the user count should be increased). */
|
||||
id_us_plus(&this->id);
|
||||
adt->action = this;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Action::binding_name_ensure_prefix(Binding &binding)
|
||||
{
|
||||
binding.name_ensure_prefix();
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
}
|
||||
|
||||
void Action::binding_setup_for_id(Binding &binding, const ID &animated_id)
|
||||
{
|
||||
if (binding.has_idtype()) {
|
||||
BLI_assert(binding.idtype == GS(animated_id.name));
|
||||
return;
|
||||
}
|
||||
|
||||
binding.idtype = GS(animated_id.name);
|
||||
this->binding_name_ensure_prefix(binding);
|
||||
}
|
||||
|
||||
void Action::unassign_id(ID &animated_id)
|
||||
{
|
||||
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
||||
BLI_assert_msg(adt, "ID is not animated at all");
|
||||
BLI_assert_msg(adt->action == this, "ID is not assigned to this Animation");
|
||||
|
||||
unassign_binding(*adt);
|
||||
|
||||
id_us_min(&this->id);
|
||||
adt->action = nullptr;
|
||||
}
|
||||
|
||||
/* ----- ActionLayer implementation ----------- */
|
||||
|
||||
Layer::Layer(const Layer &other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
|
||||
/* Strips. */
|
||||
this->strip_array = MEM_cnew_array<ActionStrip *>(other.strip_array_num, __func__);
|
||||
for (int i : other.strips().index_range()) {
|
||||
this->strip_array[i] = other.strip(i)->duplicate(__func__);
|
||||
}
|
||||
}
|
||||
|
||||
Layer::~Layer()
|
||||
{
|
||||
for (Strip *strip : this->strips()) {
|
||||
MEM_delete(strip);
|
||||
}
|
||||
MEM_SAFE_FREE(this->strip_array);
|
||||
this->strip_array_num = 0;
|
||||
}
|
||||
|
||||
blender::Span<const Strip *> Layer::strips() const
|
||||
{
|
||||
return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
|
||||
this->strip_array_num};
|
||||
}
|
||||
blender::MutableSpan<Strip *> Layer::strips()
|
||||
{
|
||||
return blender::MutableSpan<Strip *>{reinterpret_cast<Strip **>(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(const Strip::Type strip_type)
|
||||
{
|
||||
Strip &strip = ActionStrip_alloc_infinite(strip_type);
|
||||
|
||||
/* Add the new strip to the strip array. */
|
||||
grow_array_and_append<::ActionStrip *>(&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(Strip &strip_to_remove)
|
||||
{
|
||||
const int64_t strip_index = this->find_strip_index(strip_to_remove);
|
||||
if (strip_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dna::array::remove_index(
|
||||
&this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* ----- ActionBinding implementation ----------- */
|
||||
|
||||
bool Binding::is_suitable_for(const ID &animated_id) const
|
||||
{
|
||||
if (!this->has_idtype()) {
|
||||
/* Without specific ID type set, this Binding can animate any ID. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check that the ID type is compatible with this binding. */
|
||||
const int animated_idtype = GS(animated_id.name);
|
||||
return this->idtype == animated_idtype;
|
||||
}
|
||||
|
||||
bool Binding::has_idtype() const
|
||||
{
|
||||
return this->idtype != 0;
|
||||
}
|
||||
|
||||
bool assign_animation(Action &anim, ID &animated_id)
|
||||
{
|
||||
unassign_animation(animated_id);
|
||||
|
||||
Binding *binding = anim.find_suitable_binding_for(animated_id);
|
||||
return anim.assign_id(binding, animated_id);
|
||||
}
|
||||
|
||||
void unassign_animation(ID &animated_id)
|
||||
{
|
||||
Action *anim = get_animation(animated_id);
|
||||
if (!anim) {
|
||||
return;
|
||||
}
|
||||
anim->unassign_id(animated_id);
|
||||
}
|
||||
|
||||
void unassign_binding(AnimData &adt)
|
||||
{
|
||||
/* Before unassigning, make sure that the stored Binding name is up to date. The binding name
|
||||
* might have changed in a way that wasn't copied into the ADT yet (for example when the
|
||||
* Animation data-block is linked from another file), so better copy the name to be sure that it
|
||||
* can be transparently reassigned later.
|
||||
*
|
||||
* TODO: Replace this with a BLI_assert() that the name is as expected, and "simply" ensure this
|
||||
* name is always correct. */
|
||||
if (adt.action) {
|
||||
const Action &anim = adt.action->wrap();
|
||||
const Binding *binding = anim.binding_for_handle(adt.binding_handle);
|
||||
if (binding) {
|
||||
STRNCPY_UTF8(adt.binding_name, binding->name);
|
||||
}
|
||||
}
|
||||
|
||||
adt.binding_handle = Binding::unassigned;
|
||||
}
|
||||
|
||||
/* TODO: rename to get_action(). */
|
||||
Action *get_animation(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::string Binding::name_prefix_for_idtype() const
|
||||
{
|
||||
if (!this->has_idtype()) {
|
||||
return binding_unbound_prefix;
|
||||
}
|
||||
|
||||
char name[3] = {0};
|
||||
*reinterpret_cast<short *>(name) = this->idtype;
|
||||
return name;
|
||||
}
|
||||
|
||||
StringRefNull Binding::name_without_prefix() const
|
||||
{
|
||||
BLI_assert(StringRef(this->name).size() >= name_length_min);
|
||||
|
||||
/* Avoid accessing an uninitialized part of the string accidentally. */
|
||||
if (this->name[0] == '\0' || this->name[1] == '\0') {
|
||||
return "";
|
||||
}
|
||||
return this->name + 2;
|
||||
}
|
||||
|
||||
void Binding::name_ensure_prefix()
|
||||
{
|
||||
BLI_assert(StringRef(this->name).size() >= name_length_min);
|
||||
|
||||
if (StringRef(this->name).size() < 2) {
|
||||
/* The code below would overwrite the trailing 0-byte. */
|
||||
this->name[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->name[0] = binding_unbound_prefix[0];
|
||||
this->name[1] = binding_unbound_prefix[1];
|
||||
return;
|
||||
}
|
||||
|
||||
*reinterpret_cast<short *>(this->name) = this->idtype;
|
||||
}
|
||||
|
||||
/* ----- ActionStrip implementation ----------- */
|
||||
|
||||
Strip *Strip::duplicate(const StringRefNull allocation_name) const
|
||||
{
|
||||
switch (this->type()) {
|
||||
case Type::Keyframe: {
|
||||
const KeyframeStrip &source = this->as<KeyframeStrip>();
|
||||
KeyframeStrip *copy = MEM_new<KeyframeStrip>(allocation_name.c_str(), source);
|
||||
return ©->strip.wrap();
|
||||
}
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Strip::~Strip()
|
||||
{
|
||||
switch (this->type()) {
|
||||
case Type::Keyframe:
|
||||
this->as<KeyframeStrip>().~KeyframeStrip();
|
||||
return;
|
||||
}
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
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<float>::infinity(),
|
||||
"only the end frame can be at positive infinity");
|
||||
BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
|
||||
"only the start frame can be at negative infinity");
|
||||
this->frame_start = frame_start;
|
||||
this->frame_end = frame_end;
|
||||
}
|
||||
|
||||
/* ----- KeyframeActionStrip implementation ----------- */
|
||||
|
||||
KeyframeStrip::KeyframeStrip(const KeyframeStrip &other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
|
||||
this->channelbags_array = MEM_cnew_array<ActionChannelBag *>(other.channelbags_array_num,
|
||||
__func__);
|
||||
Span<const ChannelBag *> channelbags_src = other.channelbags();
|
||||
for (int i : channelbags_src.index_range()) {
|
||||
this->channelbags_array[i] = MEM_new<animrig::ChannelBag>(__func__, *other.channelbag(i));
|
||||
}
|
||||
}
|
||||
|
||||
KeyframeStrip::~KeyframeStrip()
|
||||
{
|
||||
for (ChannelBag *channelbag_for_binding : this->channelbags()) {
|
||||
MEM_delete(channelbag_for_binding);
|
||||
}
|
||||
MEM_SAFE_FREE(this->channelbags_array);
|
||||
this->channelbags_array_num = 0;
|
||||
}
|
||||
|
||||
template<> bool Strip::is<KeyframeStrip>() const
|
||||
{
|
||||
return this->type() == Type::Keyframe;
|
||||
}
|
||||
|
||||
template<> KeyframeStrip &Strip::as<KeyframeStrip>()
|
||||
{
|
||||
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
|
||||
return *reinterpret_cast<KeyframeStrip *>(this);
|
||||
}
|
||||
|
||||
template<> const KeyframeStrip &Strip::as<KeyframeStrip>() const
|
||||
{
|
||||
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
|
||||
return *reinterpret_cast<const KeyframeStrip *>(this);
|
||||
}
|
||||
|
||||
blender::Span<const ChannelBag *> KeyframeStrip::channelbags() const
|
||||
{
|
||||
return blender::Span<ChannelBag *>{reinterpret_cast<ChannelBag **>(this->channelbags_array),
|
||||
this->channelbags_array_num};
|
||||
}
|
||||
blender::MutableSpan<ChannelBag *> KeyframeStrip::channelbags()
|
||||
{
|
||||
return blender::MutableSpan<ChannelBag *>{
|
||||
reinterpret_cast<ChannelBag **>(this->channelbags_array), this->channelbags_array_num};
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag(const int64_t index) const
|
||||
{
|
||||
return &this->channelbags_array[index]->wrap();
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag(const int64_t index)
|
||||
{
|
||||
return &this->channelbags_array[index]->wrap();
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag_for_binding(
|
||||
const binding_handle_t binding_handle) const
|
||||
{
|
||||
for (const ChannelBag *channels : this->channelbags()) {
|
||||
if (channels->binding_handle == binding_handle) {
|
||||
return channels;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag_for_binding(const binding_handle_t binding_handle)
|
||||
{
|
||||
const auto *const_this = const_cast<const KeyframeStrip *>(this);
|
||||
const auto *const_channels = const_this->channelbag_for_binding(binding_handle);
|
||||
return const_cast<ChannelBag *>(const_channels);
|
||||
}
|
||||
const ChannelBag *KeyframeStrip::channelbag_for_binding(const Binding &binding) const
|
||||
{
|
||||
return this->channelbag_for_binding(binding.handle);
|
||||
}
|
||||
ChannelBag *KeyframeStrip::channelbag_for_binding(const Binding &binding)
|
||||
{
|
||||
return this->channelbag_for_binding(binding.handle);
|
||||
}
|
||||
|
||||
ChannelBag &KeyframeStrip::channelbag_for_binding_add(const Binding &binding)
|
||||
{
|
||||
BLI_assert_msg(channelbag_for_binding(binding) == nullptr,
|
||||
"Cannot add chans-for-binding for already-registered binding");
|
||||
|
||||
ChannelBag &channels = MEM_new<ActionChannelBag>(__func__)->wrap();
|
||||
channels.binding_handle = binding.handle;
|
||||
|
||||
grow_array_and_append<ActionChannelBag *>(
|
||||
&this->channelbags_array, &this->channelbags_array_num, &channels);
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
FCurve *KeyframeStrip::fcurve_find(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index)
|
||||
{
|
||||
ChannelBag *channels = this->channelbag_for_binding(binding);
|
||||
if (channels == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Copy of the logic in BKE_fcurve_find(), but then compatible with our array-of-FCurves
|
||||
* instead of ListBase. */
|
||||
|
||||
for (FCurve *fcu : channels->fcurves()) {
|
||||
/* Check indices first, much cheaper than a string comparison. */
|
||||
/* Simple string-compare (this assumes that they have the same root...) */
|
||||
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
|
||||
return fcu;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FCurve &KeyframeStrip::fcurve_find_or_create(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index)
|
||||
{
|
||||
if (FCurve *existing_fcurve = this->fcurve_find(binding, rna_path, array_index)) {
|
||||
return *existing_fcurve;
|
||||
}
|
||||
|
||||
FCurve *new_fcurve = create_fcurve_for_channel(rna_path.c_str(), array_index);
|
||||
|
||||
ChannelBag *channels = this->channelbag_for_binding(binding);
|
||||
if (channels == nullptr) {
|
||||
channels = &this->channelbag_for_binding_add(binding);
|
||||
}
|
||||
|
||||
if (channels->fcurve_array_num == 0) {
|
||||
new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
|
||||
}
|
||||
|
||||
grow_array_and_append(&channels->fcurve_array, &channels->fcurve_array_num, new_fcurve);
|
||||
return *new_fcurve;
|
||||
}
|
||||
|
||||
SingleKeyingResult KeyframeStrip::keyframe_insert(const Binding &binding,
|
||||
const StringRefNull rna_path,
|
||||
const int array_index,
|
||||
const float2 time_value,
|
||||
const KeyframeSettings &settings)
|
||||
{
|
||||
FCurve &fcurve = this->fcurve_find_or_create(binding, rna_path, array_index);
|
||||
|
||||
if (!BKE_fcurve_is_keyframable(&fcurve)) {
|
||||
/* TODO: handle this properly, in a way that can be communicated to the user. */
|
||||
std::fprintf(stderr,
|
||||
"FCurve %s[%d] for binding %s doesn't allow inserting keys.\n",
|
||||
rna_path.c_str(),
|
||||
array_index,
|
||||
binding.name);
|
||||
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
||||
}
|
||||
|
||||
/* TODO: Handle the eInsertKeyFlags. */
|
||||
const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
|
||||
&fcurve, time_value, settings, eInsertKeyFlags(0));
|
||||
|
||||
if (insert_vert_result != SingleKeyingResult::SUCCESS) {
|
||||
std::fprintf(stderr,
|
||||
"Could not insert key into FCurve %s[%d] for binding %s.\n",
|
||||
rna_path.c_str(),
|
||||
array_index,
|
||||
binding.name);
|
||||
return insert_vert_result;
|
||||
}
|
||||
|
||||
return SingleKeyingResult::SUCCESS;
|
||||
}
|
||||
|
||||
/* ActionChannelBag implementation. */
|
||||
|
||||
ChannelBag::ChannelBag(const ChannelBag &other)
|
||||
{
|
||||
this->binding_handle = other.binding_handle;
|
||||
this->fcurve_array_num = other.fcurve_array_num;
|
||||
|
||||
this->fcurve_array = MEM_cnew_array<FCurve *>(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);
|
||||
}
|
||||
}
|
||||
|
||||
ChannelBag::~ChannelBag()
|
||||
{
|
||||
for (FCurve *fcu : this->fcurves()) {
|
||||
BKE_fcurve_free(fcu);
|
||||
}
|
||||
MEM_SAFE_FREE(this->fcurve_array);
|
||||
this->fcurve_array_num = 0;
|
||||
}
|
||||
|
||||
blender::Span<const FCurve *> ChannelBag::fcurves() const
|
||||
{
|
||||
return blender::Span<FCurve *>{this->fcurve_array, this->fcurve_array_num};
|
||||
}
|
||||
blender::MutableSpan<FCurve *> ChannelBag::fcurves()
|
||||
{
|
||||
return blender::MutableSpan<FCurve *>{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];
|
||||
}
|
||||
|
||||
const FCurve *ChannelBag::fcurve_find(const StringRefNull rna_path, const int array_index) const
|
||||
{
|
||||
for (const FCurve *fcu : this->fcurves()) {
|
||||
/* Check indices first, much cheaper than a string comparison. */
|
||||
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
|
||||
return fcu;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Utility function implementations. */
|
||||
|
||||
static const animrig::ChannelBag *channelbag_for_animation(const Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
if (binding_handle == Binding::unassigned) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const animrig::Layer *layer : anim.layers()) {
|
||||
for (const animrig::Strip *strip : layer->strips()) {
|
||||
switch (strip->type()) {
|
||||
case animrig::Strip::Type::Keyframe: {
|
||||
const animrig::KeyframeStrip &key_strip = strip->as<animrig::KeyframeStrip>();
|
||||
const animrig::ChannelBag *bag = key_strip.channelbag_for_binding(binding_handle);
|
||||
if (bag) {
|
||||
return bag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static animrig::ChannelBag *channelbag_for_animation(Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
const animrig::ChannelBag *const_bag = channelbag_for_animation(const_cast<const Action &>(anim),
|
||||
binding_handle);
|
||||
return const_cast<animrig::ChannelBag *>(const_bag);
|
||||
}
|
||||
|
||||
Span<FCurve *> fcurves_for_animation(Action &anim, const binding_handle_t binding_handle)
|
||||
{
|
||||
animrig::ChannelBag *bag = channelbag_for_animation(anim, binding_handle);
|
||||
if (!bag) {
|
||||
return {};
|
||||
}
|
||||
return bag->fcurves();
|
||||
}
|
||||
|
||||
Span<const FCurve *> fcurves_for_animation(const Action &anim,
|
||||
const binding_handle_t binding_handle)
|
||||
{
|
||||
const animrig::ChannelBag *bag = channelbag_for_animation(anim, binding_handle);
|
||||
if (!bag) {
|
||||
return {};
|
||||
}
|
||||
return bag->fcurves();
|
||||
}
|
||||
|
||||
} // namespace blender::animrig
|
||||
Reference in New Issue
Block a user