Anim: add Animation data-block management functions
Add code (including RNA wrappers) for: - Creating, removing, and accessing `Animation` data-blocks. - Creating and removing layers, strips, and bindings on those `Animation` data-blocks. - Accessing those via RNA. Note that this does not include assignment to any animated data-block, so it is of limited practical use. For more info, see #113594. Pull Request: https://projects.blender.org/blender/blender/pulls/118677
This commit is contained in:
@@ -2680,6 +2680,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
|
||||
({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")),
|
||||
({"property": "use_extension_repos"}, ("/blender/blender/issues/117286", "#117286")),
|
||||
({"property": "use_extension_utils"}, ("/blender/blender/issues/117286", "#117286")),
|
||||
({"property": "use_animation_baklava"}, ("/blender/blender/pulls/114098", "#114098")),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -62,14 +62,73 @@ class Animation : public ::Animation {
|
||||
const Layer *layer(int64_t index) const;
|
||||
Layer *layer(int64_t index);
|
||||
|
||||
Layer &layer_add(StringRefNull name);
|
||||
|
||||
/**
|
||||
* Remove the layer from this animation.
|
||||
*
|
||||
* After this call, the passed reference is no longer valid, as the memory
|
||||
* will have been freed. Any strips on the layer will be freed too.
|
||||
*
|
||||
* \return true when the layer was found & removed, false if it wasn't found.
|
||||
*/
|
||||
bool layer_remove(Layer &layer_to_remove);
|
||||
|
||||
/* Animation Binding access. */
|
||||
blender::Span<const Binding *> bindings() const;
|
||||
blender::MutableSpan<Binding *> bindings();
|
||||
const Binding *binding(int64_t index) const;
|
||||
Binding *binding(int64_t index);
|
||||
|
||||
Binding *binding_for_handle(binding_handle_t handle);
|
||||
const Binding *binding_for_handle(binding_handle_t handle) const;
|
||||
|
||||
/**
|
||||
* Set the binding name.
|
||||
*
|
||||
* This has to be done on the Animation level to ensure each binding has a
|
||||
* unique name within the Animation.
|
||||
*
|
||||
* \see Animation::binding_name_define
|
||||
* \see Animation::binding_name_propagate
|
||||
*/
|
||||
void binding_name_set(Main &bmain, Binding &binding, StringRefNull new_name);
|
||||
|
||||
/**
|
||||
* Set the binding name, and ensure it is unique.
|
||||
*
|
||||
* This function usually isn't necessary, call #binding_name_set instead.
|
||||
*
|
||||
* \see Animation::binding_name_set
|
||||
* \see Animation::binding_name_propagate
|
||||
*/
|
||||
void binding_name_define(Binding &binding, StringRefNull new_name);
|
||||
|
||||
/**
|
||||
* Update the `AnimData::animation_binding_name` field of any ID that is animated by
|
||||
* this.Binding.
|
||||
*
|
||||
* Should be called after `binding_name_define(binding)`. This is implemented as a separate
|
||||
* function due to the need to access bmain, which is available in the RNA on-property-update
|
||||
* handler, but not in the RNA property setter.
|
||||
*/
|
||||
void binding_name_propagate(Main &bmain, const Binding &binding);
|
||||
|
||||
Binding *binding_find_by_name(StringRefNull binding_name);
|
||||
|
||||
Binding *binding_for_id(const ID &animated_id);
|
||||
const Binding *binding_for_id(const ID &animated_id) const;
|
||||
|
||||
Binding &binding_add();
|
||||
/** Free all data in the `Animation`. Doesn't delete the `Animation` itself. */
|
||||
void free_data();
|
||||
|
||||
protected:
|
||||
/** Return the layer's index, or -1 if not found in this animation. */
|
||||
int64_t find_layer_index(const Layer &layer) const;
|
||||
|
||||
private:
|
||||
Binding &binding_allocate();
|
||||
};
|
||||
static_assert(sizeof(Animation) == sizeof(::Animation),
|
||||
"DNA struct and its C++ wrapper must have the same size");
|
||||
@@ -172,6 +231,21 @@ class Layer : public ::AnimationLayer {
|
||||
blender::MutableSpan<Strip *> strips();
|
||||
const Strip *strip(int64_t index) const;
|
||||
Strip *strip(int64_t index);
|
||||
Strip &strip_add(Strip::Type strip_type);
|
||||
|
||||
/**
|
||||
* Remove the strip from this layer.
|
||||
*
|
||||
* After this call, the passed reference is no longer valid, as the memory
|
||||
* will have been freed.
|
||||
*
|
||||
* \return true when the strip was found & removed, false if it wasn't found.
|
||||
*/
|
||||
bool strip_remove(Strip &strip);
|
||||
|
||||
protected:
|
||||
/** Return the strip's index, or -1 if not found in this layer. */
|
||||
int64_t find_strip_index(const Strip &strip) const;
|
||||
};
|
||||
static_assert(sizeof(Layer) == sizeof(::AnimationLayer),
|
||||
"DNA struct and its C++ wrapper must have the same size");
|
||||
@@ -195,6 +269,9 @@ class Binding : public ::AnimationBinding {
|
||||
Binding() = default;
|
||||
Binding(const Binding &other) = default;
|
||||
~Binding() = default;
|
||||
|
||||
/** Return whether this Binding is usable by this ID type. */
|
||||
bool is_suitable_for(const ID &animated_id) const;
|
||||
};
|
||||
static_assert(sizeof(Binding) == sizeof(::AnimationBinding),
|
||||
"DNA struct and its C++ wrapper must have the same size");
|
||||
@@ -213,6 +290,23 @@ class KeyframeStrip : public ::KeyframeAnimationStrip {
|
||||
blender::MutableSpan<ChannelBag *> channelbags();
|
||||
const ChannelBag *channelbag(int64_t index) const;
|
||||
ChannelBag *channelbag(int64_t index);
|
||||
|
||||
/**
|
||||
* Find the animation channels for this binding.
|
||||
*
|
||||
* \return nullptr if there is none yet for this binding.
|
||||
*/
|
||||
const ChannelBag *channelbag_for_binding(const Binding &binding) const;
|
||||
ChannelBag *channelbag_for_binding(const Binding &binding);
|
||||
const ChannelBag *channelbag_for_binding(binding_handle_t binding_handle) const;
|
||||
ChannelBag *channelbag_for_binding(binding_handle_t binding_handle);
|
||||
|
||||
/**
|
||||
* Add the animation channels for this binding.
|
||||
*
|
||||
* Should only be called when there is no `ChannelBag` for this binding yet.
|
||||
*/
|
||||
ChannelBag &channelbag_for_binding_add(const Binding &binding);
|
||||
};
|
||||
static_assert(sizeof(KeyframeStrip) == sizeof(::KeyframeAnimationStrip),
|
||||
"DNA struct and its C++ wrapper must have the same size");
|
||||
|
||||
@@ -54,6 +54,7 @@ set(LIB
|
||||
PRIVATE bf_editor_interface
|
||||
PRIVATE bf::intern::guardedalloc
|
||||
PRIVATE bf::intern::atomic
|
||||
PRIVATE bf::intern::clog
|
||||
)
|
||||
|
||||
|
||||
@@ -64,6 +65,7 @@ if(WITH_GTESTS)
|
||||
set(TEST_INC
|
||||
)
|
||||
set(TEST_SRC
|
||||
intern/animation_test.cc
|
||||
intern/bone_collections_test.cc
|
||||
)
|
||||
set(TEST_LIB
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "DNA_anim_defaults.h"
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_array_utils.hh"
|
||||
#include "DNA_defaults.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
@@ -33,6 +34,64 @@
|
||||
|
||||
namespace blender::animrig {
|
||||
|
||||
static animrig::Layer &animationlayer_alloc()
|
||||
{
|
||||
AnimationLayer *layer = DNA_struct_default_alloc(AnimationLayer);
|
||||
return layer->wrap();
|
||||
}
|
||||
static animrig::Strip &animationstrip_alloc_infinite(const Strip::Type type)
|
||||
{
|
||||
AnimationStrip *strip = nullptr;
|
||||
switch (type) {
|
||||
case Strip::Type::Keyframe: {
|
||||
KeyframeAnimationStrip *key_strip = MEM_new<KeyframeAnimationStrip>(__func__);
|
||||
strip = &key_strip->strip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BLI_assert_msg(strip, "unsupported strip type");
|
||||
|
||||
/* Copy the default AnimationStrip fields into the allocated data-block. */
|
||||
memcpy(strip, DNA_struct_default_get(AnimationStrip), 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 ----------- */
|
||||
|
||||
blender::Span<const Layer *> Animation::layers() const
|
||||
@@ -54,6 +113,52 @@ Layer *Animation::layer(const int64_t index)
|
||||
return &this->layer_array[index]->wrap();
|
||||
}
|
||||
|
||||
Layer &Animation::layer_add(const StringRefNull name)
|
||||
{
|
||||
using namespace blender::animrig;
|
||||
|
||||
Layer &new_layer = animationlayer_alloc();
|
||||
STRNCPY_UTF8(new_layer.name, name.c_str());
|
||||
|
||||
grow_array_and_append<::AnimationLayer *>(
|
||||
&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(AnimationLayer **dna_layer_ptr)
|
||||
{
|
||||
Layer &layer = (*dna_layer_ptr)->wrap();
|
||||
MEM_delete(&layer);
|
||||
};
|
||||
|
||||
bool Animation::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 Animation::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 *> Animation::bindings() const
|
||||
{
|
||||
return blender::Span<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
|
||||
@@ -73,6 +178,145 @@ Binding *Animation::binding(const int64_t index)
|
||||
return &this->binding_array[index]->wrap();
|
||||
}
|
||||
|
||||
Binding *Animation::binding_for_handle(const binding_handle_t handle)
|
||||
{
|
||||
const Binding *binding = const_cast<const Animation *>(this)->binding_for_handle(handle);
|
||||
return const_cast<Binding *>(binding);
|
||||
}
|
||||
|
||||
const Binding *Animation::binding_for_handle(const binding_handle_t handle) const
|
||||
{
|
||||
/* TODO: implement hashmap lookup. */
|
||||
for (const Binding *binding : bindings()) {
|
||||
if (binding->handle == handle) {
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void anim_binding_name_ensure_unique(Animation &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 {
|
||||
Animation &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));
|
||||
}
|
||||
|
||||
void Animation::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 Animation::binding_name_define(Binding &binding, const StringRefNull new_name)
|
||||
{
|
||||
STRNCPY_UTF8(binding.name, new_name.c_str());
|
||||
anim_binding_name_ensure_unique(*this, binding);
|
||||
}
|
||||
|
||||
void Animation::binding_name_propagate(Main &bmain, const Binding &binding)
|
||||
{
|
||||
/* Just loop over all animatable IDs in the main dataabase. */
|
||||
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->animation != 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 *Animation::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 *Animation::binding_for_id(const ID &animated_id)
|
||||
{
|
||||
const Binding *binding = const_cast<const Animation *>(this)->binding_for_id(animated_id);
|
||||
return const_cast<Binding *>(binding);
|
||||
}
|
||||
|
||||
const Binding *Animation::binding_for_id(const ID &animated_id) const
|
||||
{
|
||||
const AnimData *adt = BKE_animdata_from_id(&animated_id);
|
||||
|
||||
/* Note that there is no check that `adt->animation` is actually `this`. */
|
||||
|
||||
const Binding *binding = this->binding_for_handle(adt->binding_handle);
|
||||
if (!binding) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!binding->is_suitable_for(animated_id)) {
|
||||
return nullptr;
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
Binding &Animation::binding_allocate()
|
||||
{
|
||||
Binding &binding = MEM_new<AnimationBinding>(__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 &Animation::binding_add()
|
||||
{
|
||||
Binding &binding = this->binding_allocate();
|
||||
|
||||
/* Append the Binding to the animation data-block. */
|
||||
grow_array_and_append<::AnimationBinding *>(
|
||||
&this->binding_array, &this->binding_array_num, &binding);
|
||||
|
||||
return binding;
|
||||
}
|
||||
|
||||
void Animation::free_data()
|
||||
{
|
||||
/* Free layers. */
|
||||
@@ -131,8 +375,55 @@ Strip *Layer::strip(const int64_t index)
|
||||
return &this->strip_array[index]->wrap();
|
||||
}
|
||||
|
||||
Strip &Layer::strip_add(const Strip::Type strip_type)
|
||||
{
|
||||
Strip &strip = animationstrip_alloc_infinite(strip_type);
|
||||
|
||||
/* Add the new strip to the strip array. */
|
||||
grow_array_and_append<::AnimationStrip *>(&this->strip_array, &this->strip_array_num, &strip);
|
||||
|
||||
return strip;
|
||||
}
|
||||
|
||||
static void strip_ptr_destructor(AnimationStrip **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;
|
||||
}
|
||||
|
||||
/* ----- AnimationBinding implementation ----------- */
|
||||
|
||||
bool Binding::is_suitable_for(const ID &animated_id) const
|
||||
{
|
||||
/* Check that the ID type is compatible with this binding. */
|
||||
const int animated_idtype = GS(animated_id.name);
|
||||
return this->idtype == 0 || this->idtype == animated_idtype;
|
||||
}
|
||||
|
||||
/* ----- AnimationStrip implementation ----------- */
|
||||
|
||||
Strip *Strip::duplicate(const StringRefNull allocation_name) const
|
||||
@@ -216,6 +507,46 @@ 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)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
BLI_assert_msg(channelbag_for_binding(binding) == nullptr,
|
||||
"Cannot add chans-for-binding for already-registered binding");
|
||||
#endif
|
||||
|
||||
ChannelBag &channels = MEM_new<AnimationChannelBag>(__func__)->wrap();
|
||||
channels.binding_handle = binding.handle;
|
||||
|
||||
grow_array_and_append<AnimationChannelBag *>(
|
||||
&this->channelbags_array, &this->channelbags_array_num, &channels);
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
/* AnimationChannelBag implementation. */
|
||||
|
||||
|
||||
178
source/blender/animrig/intern/animation_test.cc
Normal file
178
source/blender/animrig/intern/animation_test.cc
Normal file
@@ -0,0 +1,178 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "ANIM_animation.hh"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_animation.hh"
|
||||
#include "BKE_fcurve.hh"
|
||||
#include "BKE_idtype.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "CLG_log.h"
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender::animrig::tests {
|
||||
class AnimationLayersTest : public testing::Test {
|
||||
public:
|
||||
Main *bmain;
|
||||
Animation *anim;
|
||||
Object *cube;
|
||||
Object *suzanne;
|
||||
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialised properly. */
|
||||
CLG_init();
|
||||
|
||||
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
|
||||
BKE_idtype_init();
|
||||
}
|
||||
|
||||
static void TearDownTestSuite()
|
||||
{
|
||||
CLG_exit();
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
bmain = BKE_main_new();
|
||||
anim = static_cast<Animation *>(BKE_id_new(bmain, ID_AN, "ANÄnimåtië"));
|
||||
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "Küüübus");
|
||||
suzanne = BKE_object_add_only_object(bmain, OB_EMPTY, "OBSuzanne");
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
BKE_main_free(bmain);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(AnimationLayersTest, add_layer)
|
||||
{
|
||||
Layer &layer = anim->layer_add("layer name");
|
||||
|
||||
EXPECT_EQ(anim->layer(0), &layer);
|
||||
EXPECT_EQ("layer name", std::string(layer.name));
|
||||
EXPECT_EQ(1.0f, layer.influence) << "Expected DNA defaults to be used.";
|
||||
EXPECT_EQ(0, anim->layer_active_index)
|
||||
<< "Expected newly added layer to become the active layer.";
|
||||
ASSERT_EQ(0, layer.strips().size()) << "Expected newly added layer to have no strip.";
|
||||
}
|
||||
|
||||
TEST_F(AnimationLayersTest, remove_layer)
|
||||
{
|
||||
Layer &layer0 = anim->layer_add("Test Læür nul");
|
||||
Layer &layer1 = anim->layer_add("Test Læür één");
|
||||
Layer &layer2 = anim->layer_add("Test Læür twee");
|
||||
|
||||
/* Add some strips to check that they are freed correctly too (implicitly by the
|
||||
* memory leak checker). */
|
||||
layer0.strip_add(Strip::Type::Keyframe);
|
||||
layer1.strip_add(Strip::Type::Keyframe);
|
||||
layer2.strip_add(Strip::Type::Keyframe);
|
||||
|
||||
{ /* Test removing a layer that is not owned. */
|
||||
Animation *other_anim = static_cast<Animation *>(BKE_id_new(bmain, ID_AN, "ANOtherAnim"));
|
||||
Layer &other_layer = other_anim->layer_add("Another Layer");
|
||||
EXPECT_FALSE(anim->layer_remove(other_layer))
|
||||
<< "Removing a layer not owned by the animation should be gracefully rejected";
|
||||
BKE_id_free(bmain, &other_anim->id);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(anim->layer_remove(layer1));
|
||||
EXPECT_EQ(2, anim->layers().size());
|
||||
EXPECT_STREQ(layer0.name, anim->layer(0)->name);
|
||||
EXPECT_STREQ(layer2.name, anim->layer(1)->name);
|
||||
|
||||
EXPECT_TRUE(anim->layer_remove(layer2));
|
||||
EXPECT_EQ(1, anim->layers().size());
|
||||
EXPECT_STREQ(layer0.name, anim->layer(0)->name);
|
||||
|
||||
EXPECT_TRUE(anim->layer_remove(layer0));
|
||||
EXPECT_EQ(0, anim->layers().size());
|
||||
}
|
||||
|
||||
TEST_F(AnimationLayersTest, add_strip)
|
||||
{
|
||||
Layer &layer = anim->layer_add("Test Læür");
|
||||
|
||||
Strip &strip = layer.strip_add(Strip::Type::Keyframe);
|
||||
ASSERT_EQ(1, layer.strips().size());
|
||||
EXPECT_EQ(&strip, layer.strip(0));
|
||||
|
||||
constexpr float inf = std::numeric_limits<float>::infinity();
|
||||
EXPECT_EQ(-inf, strip.frame_start) << "Expected strip to be infinite.";
|
||||
EXPECT_EQ(inf, strip.frame_end) << "Expected strip to be infinite.";
|
||||
EXPECT_EQ(0, strip.frame_offset) << "Expected infinite strip to have no offset.";
|
||||
|
||||
Strip &another_strip = layer.strip_add(Strip::Type::Keyframe);
|
||||
ASSERT_EQ(2, layer.strips().size());
|
||||
EXPECT_EQ(&another_strip, layer.strip(1));
|
||||
|
||||
EXPECT_EQ(-inf, another_strip.frame_start) << "Expected strip to be infinite.";
|
||||
EXPECT_EQ(inf, another_strip.frame_end) << "Expected strip to be infinite.";
|
||||
EXPECT_EQ(0, another_strip.frame_offset) << "Expected infinite strip to have no offset.";
|
||||
}
|
||||
|
||||
TEST_F(AnimationLayersTest, remove_strip)
|
||||
{
|
||||
Layer &layer = anim->layer_add("Test Læür");
|
||||
Strip &strip0 = layer.strip_add(Strip::Type::Keyframe);
|
||||
Strip &strip1 = layer.strip_add(Strip::Type::Keyframe);
|
||||
Strip &strip2 = layer.strip_add(Strip::Type::Keyframe);
|
||||
|
||||
EXPECT_TRUE(layer.strip_remove(strip1));
|
||||
EXPECT_EQ(2, layer.strips().size());
|
||||
EXPECT_EQ(&strip0, layer.strip(0));
|
||||
EXPECT_EQ(&strip2, layer.strip(1));
|
||||
|
||||
EXPECT_TRUE(layer.strip_remove(strip2));
|
||||
EXPECT_EQ(1, layer.strips().size());
|
||||
EXPECT_EQ(&strip0, layer.strip(0));
|
||||
|
||||
EXPECT_TRUE(layer.strip_remove(strip0));
|
||||
EXPECT_EQ(0, layer.strips().size());
|
||||
|
||||
{ /* Test removing a strip that is not owned. */
|
||||
Layer &other_layer = anim->layer_add("Another Layer");
|
||||
Strip &other_strip = other_layer.strip_add(Strip::Type::Keyframe);
|
||||
|
||||
EXPECT_FALSE(layer.strip_remove(other_strip))
|
||||
<< "Removing a strip not owned by the layer should be gracefully rejected";
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AnimationLayersTest, add_binding)
|
||||
{
|
||||
Binding &binding = anim->binding_add();
|
||||
EXPECT_EQ(1, anim->last_binding_handle);
|
||||
EXPECT_EQ(1, binding.handle);
|
||||
|
||||
EXPECT_STREQ("", binding.name);
|
||||
EXPECT_EQ(0, binding.idtype);
|
||||
}
|
||||
|
||||
TEST_F(AnimationLayersTest, rename_binding_name_collision)
|
||||
{
|
||||
Binding &binding1 = anim->binding_add();
|
||||
Binding &binding2 = anim->binding_add();
|
||||
|
||||
anim->binding_name_define(binding1, "New Binding Name");
|
||||
anim->binding_name_define(binding2, "New Binding Name");
|
||||
EXPECT_STREQ("New Binding Name", binding1.name);
|
||||
EXPECT_STREQ("New Binding Name.001", binding2.name);
|
||||
}
|
||||
|
||||
} // namespace blender::animrig::tests
|
||||
@@ -723,8 +723,8 @@ typedef struct UserDef_Experimental {
|
||||
char use_extension_repos;
|
||||
char use_extension_utils;
|
||||
char use_grease_pencil_version3_convert_on_load;
|
||||
char use_animation_baklava;
|
||||
|
||||
char _pad[1];
|
||||
/** `makesdna` does not allow empty structs. */
|
||||
} UserDef_Experimental;
|
||||
|
||||
|
||||
@@ -110,8 +110,10 @@ endif()
|
||||
if(WITH_EXPERIMENTAL_FEATURES)
|
||||
add_definitions(-DWITH_SIMULATION_DATABLOCK)
|
||||
add_definitions(-DWITH_GREASE_PENCIL_V3)
|
||||
add_definitions(-DWITH_ANIM_BAKLAVA)
|
||||
list(APPEND DEFSRC
|
||||
rna_grease_pencil.cc
|
||||
rna_animation_id.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -235,6 +237,7 @@ set(INC
|
||||
.
|
||||
..
|
||||
../../asset_system
|
||||
../../animrig
|
||||
../../blenfont
|
||||
../../blenkernel
|
||||
../../blenlib
|
||||
|
||||
@@ -4765,6 +4765,9 @@ static RNAProcessItem PROCESS_ITEMS[] = {
|
||||
{"rna_texture.cc", "rna_texture_api.cc", RNA_def_texture},
|
||||
{"rna_action.cc", "rna_action_api.cc", RNA_def_action},
|
||||
{"rna_animation.cc", "rna_animation_api.cc", RNA_def_animation},
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
{"rna_animation_id.cc", nullptr, RNA_def_animation_id},
|
||||
#endif
|
||||
{"rna_animviz.cc", nullptr, RNA_def_animviz},
|
||||
{"rna_armature.cc", "rna_armature_api.cc", RNA_def_armature},
|
||||
{"rna_attribute.cc", nullptr, RNA_def_attribute},
|
||||
|
||||
@@ -123,6 +123,9 @@ static const EnumPropertyItem rna_enum_override_library_property_operation_items
|
||||
const IDFilterEnumPropertyItem rna_enum_id_type_filter_items[] = {
|
||||
/* Datablocks */
|
||||
{FILTER_ID_AC, "filter_action", ICON_ACTION, "Actions", "Show Action data-blocks"},
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
{FILTER_ID_AN, "filter_animation", ICON_ACTION, "Animations", "Show Animation data-blocks"},
|
||||
#endif
|
||||
{FILTER_ID_AR,
|
||||
"filter_armature",
|
||||
ICON_ARMATURE_DATA,
|
||||
@@ -375,6 +378,11 @@ short RNA_type_to_ID_code(const StructRNA *type)
|
||||
if (base_type == &RNA_Action) {
|
||||
return ID_AC;
|
||||
}
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
if (base_type == &RNA_Animation) {
|
||||
return ID_AN;
|
||||
}
|
||||
# endif
|
||||
if (base_type == &RNA_Armature) {
|
||||
return ID_AR;
|
||||
}
|
||||
@@ -498,6 +506,9 @@ StructRNA *ID_code_to_RNA_type(short idcode)
|
||||
case ID_AC:
|
||||
return &RNA_Action;
|
||||
case ID_AN:
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
return &RNA_Animation;
|
||||
# endif
|
||||
break;
|
||||
case ID_AR:
|
||||
return &RNA_Armature;
|
||||
|
||||
714
source/blender/makesrna/intern/rna_animation_id.cc
Normal file
714
source/blender/makesrna/intern/rna_animation_id.cc
Normal file
@@ -0,0 +1,714 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup RNA
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "DNA_anim_types.h"
|
||||
|
||||
#include "ANIM_animation.hh"
|
||||
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "rna_internal.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
using namespace blender;
|
||||
|
||||
const EnumPropertyItem rna_enum_layer_mix_mode_items[] = {
|
||||
{int(animrig::Layer::MixMode::Replace),
|
||||
"REPLACE",
|
||||
0,
|
||||
"Replace",
|
||||
"Channels in this layer override the same channels from underlying layers"},
|
||||
{int(animrig::Layer::MixMode::Offset),
|
||||
"OFFSET",
|
||||
0,
|
||||
"Offset",
|
||||
"Channels in this layer are added to underlying layers as sequential operations"},
|
||||
{int(animrig::Layer::MixMode::Add),
|
||||
"ADD",
|
||||
0,
|
||||
"Add",
|
||||
"Channels in this layer are added to underlying layers on a per-channel basis"},
|
||||
{int(animrig::Layer::MixMode::Subtract),
|
||||
"SUBTRACT",
|
||||
0,
|
||||
"Subtract",
|
||||
"Channels in this layer are subtracted to underlying layers on a per-channel basis"},
|
||||
{int(animrig::Layer::MixMode::Multiply),
|
||||
"MULTIPLY",
|
||||
0,
|
||||
"Multiply",
|
||||
"Channels in this layer are multiplied with underlying layers on a per-channel basis"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
const EnumPropertyItem rna_enum_strip_type_items[] = {
|
||||
{int(animrig::Strip::Type::Keyframe),
|
||||
"KEYFRAME",
|
||||
0,
|
||||
"Keyframe",
|
||||
"Strip containing keyframes on F-Curves"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
# include "ANIM_animation.hh"
|
||||
|
||||
# include "DEG_depsgraph.hh"
|
||||
|
||||
# include <fmt/format.h>
|
||||
|
||||
static animrig::Animation &rna_animation(const PointerRNA *ptr)
|
||||
{
|
||||
return reinterpret_cast<Animation *>(ptr->owner_id)->wrap();
|
||||
}
|
||||
|
||||
static animrig::Binding &rna_data_binding(const PointerRNA *ptr)
|
||||
{
|
||||
return reinterpret_cast<AnimationBinding *>(ptr->data)->wrap();
|
||||
}
|
||||
|
||||
static animrig::Layer &rna_data_layer(const PointerRNA *ptr)
|
||||
{
|
||||
return reinterpret_cast<AnimationLayer *>(ptr->data)->wrap();
|
||||
}
|
||||
|
||||
static animrig::Strip &rna_data_strip(const PointerRNA *ptr)
|
||||
{
|
||||
return reinterpret_cast<AnimationStrip *>(ptr->data)->wrap();
|
||||
}
|
||||
|
||||
static void rna_Animation_tag_animupdate(Main *, Scene *, PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
DEG_id_tag_update(&anim.id, ID_RECALC_ANIMATION);
|
||||
}
|
||||
|
||||
static animrig::KeyframeStrip &rna_data_keyframe_strip(const PointerRNA *ptr)
|
||||
{
|
||||
animrig::Strip &strip = reinterpret_cast<AnimationStrip *>(ptr->data)->wrap();
|
||||
return strip.as<animrig::KeyframeStrip>();
|
||||
}
|
||||
|
||||
static animrig::ChannelBag &rna_data_channelbag(const PointerRNA *ptr)
|
||||
{
|
||||
return reinterpret_cast<AnimationChannelBag *>(ptr->data)->wrap();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void rna_iterator_array_begin(CollectionPropertyIterator *iter, Span<T *> items)
|
||||
{
|
||||
rna_iterator_array_begin(iter, (void *)items.data(), sizeof(T *), items.size(), 0, nullptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void rna_iterator_array_begin(CollectionPropertyIterator *iter, MutableSpan<T *> items)
|
||||
{
|
||||
rna_iterator_array_begin(iter, (void *)items.data(), sizeof(T *), items.size(), 0, nullptr);
|
||||
}
|
||||
|
||||
static AnimationBinding *rna_Animation_bindings_new(Animation *anim_id,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
ID *animated_id)
|
||||
{
|
||||
if (animated_id == nullptr) {
|
||||
BKE_report(reports,
|
||||
RPT_ERROR,
|
||||
"A binding without animated ID cannot be created at the moment; if you need it, "
|
||||
"please file a bug report");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
animrig::Animation &anim = anim_id->wrap();
|
||||
animrig::Binding &binding = anim.binding_add();
|
||||
/* TODO: actually set binding->idtype to this ID's type. */
|
||||
anim.binding_name_define(binding, animated_id->name);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||
return &binding;
|
||||
}
|
||||
|
||||
static void rna_iterator_animation_layers_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
rna_iterator_array_begin(iter, anim.layers());
|
||||
}
|
||||
|
||||
static int rna_iterator_animation_layers_length(PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
return anim.layers().size();
|
||||
}
|
||||
|
||||
static AnimationLayer *rna_Animation_layers_new(Animation *dna_animation,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
const char *name)
|
||||
{
|
||||
animrig::Animation &anim = dna_animation->wrap();
|
||||
|
||||
if (anim.layers().size() >= 1) {
|
||||
/* Not allowed to have more than one layer, for now. This limitation is in
|
||||
* place until working with multiple animated IDs is fleshed out better. */
|
||||
BKE_report(reports, RPT_ERROR, "An Animation may not have more than one layer");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
animrig::Layer &layer = anim.layer_add(name);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||
return &layer;
|
||||
}
|
||||
|
||||
void rna_Animation_layers_remove(Animation *dna_animation,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
AnimationLayer *dna_layer)
|
||||
{
|
||||
animrig::Animation &anim = dna_animation->wrap();
|
||||
animrig::Layer &layer = dna_layer->wrap();
|
||||
if (!anim.layer_remove(layer)) {
|
||||
BKE_report(reports, RPT_ERROR, "This layer does not belong to this animation");
|
||||
return;
|
||||
}
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||
DEG_id_tag_update(&anim.id, ID_RECALC_ANIMATION);
|
||||
}
|
||||
|
||||
static void rna_iterator_animation_bindings_begin(CollectionPropertyIterator *iter,
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
rna_iterator_array_begin(iter, anim.bindings());
|
||||
}
|
||||
|
||||
static int rna_iterator_animation_bindings_length(PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
return anim.bindings().size();
|
||||
}
|
||||
|
||||
static std::optional<std::string> rna_AnimationBinding_path(const PointerRNA *ptr)
|
||||
{
|
||||
animrig::Binding &binding = rna_data_binding(ptr);
|
||||
|
||||
char name_esc[sizeof(binding.name) * 2];
|
||||
BLI_str_escape(name_esc, binding.name, sizeof(name_esc));
|
||||
return fmt::format("bindings[\"{}\"]", name_esc);
|
||||
}
|
||||
|
||||
static void rna_AnimationBinding_name_set(PointerRNA *ptr, const char *name)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
animrig::Binding &binding = rna_data_binding(ptr);
|
||||
|
||||
anim.binding_name_define(binding, name);
|
||||
}
|
||||
|
||||
static void rna_AnimationBinding_name_update(Main *bmain, Scene *, PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
animrig::Binding &binding = rna_data_binding(ptr);
|
||||
|
||||
anim.binding_name_propagate(*bmain, binding);
|
||||
}
|
||||
|
||||
static std::optional<std::string> rna_AnimationLayer_path(const PointerRNA *ptr)
|
||||
{
|
||||
animrig::Layer &layer = rna_data_layer(ptr);
|
||||
|
||||
char name_esc[sizeof(layer.name) * 2];
|
||||
BLI_str_escape(name_esc, layer.name, sizeof(name_esc));
|
||||
return fmt::format("layers[\"{}\"]", name_esc);
|
||||
}
|
||||
|
||||
static void rna_iterator_animationlayer_strips_begin(CollectionPropertyIterator *iter,
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
animrig::Layer &layer = rna_data_layer(ptr);
|
||||
rna_iterator_array_begin(iter, layer.strips());
|
||||
}
|
||||
|
||||
static int rna_iterator_animationlayer_strips_length(PointerRNA *ptr)
|
||||
{
|
||||
animrig::Layer &layer = rna_data_layer(ptr);
|
||||
return layer.strips().size();
|
||||
}
|
||||
|
||||
AnimationStrip *rna_AnimationStrips_new(AnimationLayer *dna_layer,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
const int type)
|
||||
{
|
||||
const animrig::Strip::Type strip_type = animrig::Strip::Type(type);
|
||||
|
||||
animrig::Layer &layer = dna_layer->wrap();
|
||||
|
||||
if (layer.strips().size() >= 1) {
|
||||
/* Not allowed to have more than one strip, for now. This limitation is in
|
||||
* place until working with layers is fleshed out better. */
|
||||
BKE_report(reports, RPT_ERROR, "A layer may not have more than one strip");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
animrig::Strip &strip = layer.strip_add(strip_type);
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||
return &strip;
|
||||
}
|
||||
|
||||
void rna_AnimationStrips_remove(ID *animation_id,
|
||||
AnimationLayer *dna_layer,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
AnimationStrip *dna_strip)
|
||||
{
|
||||
animrig::Layer &layer = dna_layer->wrap();
|
||||
animrig::Strip &strip = dna_strip->wrap();
|
||||
if (!layer.strip_remove(strip)) {
|
||||
BKE_report(reports, RPT_ERROR, "This strip does not belong to this layer");
|
||||
return;
|
||||
}
|
||||
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||
DEG_id_tag_update(animation_id, ID_RECALC_ANIMATION);
|
||||
}
|
||||
|
||||
static StructRNA *rna_AnimationStrip_refine(PointerRNA *ptr)
|
||||
{
|
||||
animrig::Strip &strip = rna_data_strip(ptr);
|
||||
switch (strip.type()) {
|
||||
case animrig::Strip::Type::Keyframe:
|
||||
return &RNA_KeyframeAnimationStrip;
|
||||
}
|
||||
return &RNA_UnknownType;
|
||||
}
|
||||
|
||||
static std::optional<std::string> rna_AnimationStrip_path(const PointerRNA *ptr)
|
||||
{
|
||||
animrig::Animation &anim = rna_animation(ptr);
|
||||
animrig::Strip &strip_to_find = rna_data_strip(ptr);
|
||||
|
||||
for (animrig::Layer *layer : anim.layers()) {
|
||||
Span<animrig::Strip *> strips = layer->strips();
|
||||
const int index = strips.first_index_try(&strip_to_find);
|
||||
if (index < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PointerRNA layer_ptr = RNA_pointer_create(&anim.id, &RNA_AnimationLayer, layer);
|
||||
const std::optional<std::string> layer_path = rna_AnimationLayer_path(&layer_ptr);
|
||||
BLI_assert_msg(layer_path, "Every animation layer should have a valid RNA path.");
|
||||
const std::string strip_path = fmt::format("{}.strips[{}]", *layer_path, index);
|
||||
return strip_path;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void rna_iterator_keyframestrip_channelbags_begin(CollectionPropertyIterator *iter,
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
animrig::KeyframeStrip &key_strip = rna_data_keyframe_strip(ptr);
|
||||
rna_iterator_array_begin(iter, key_strip.channelbags());
|
||||
}
|
||||
|
||||
static int rna_iterator_keyframestrip_channelbags_length(PointerRNA *ptr)
|
||||
{
|
||||
animrig::KeyframeStrip &key_strip = rna_data_keyframe_strip(ptr);
|
||||
return key_strip.channelbags().size();
|
||||
}
|
||||
|
||||
static void rna_iterator_ChannelBag_fcurves_begin(CollectionPropertyIterator *iter,
|
||||
PointerRNA *ptr)
|
||||
{
|
||||
animrig::ChannelBag &bag = rna_data_channelbag(ptr);
|
||||
rna_iterator_array_begin(iter, bag.fcurves());
|
||||
}
|
||||
|
||||
static int rna_iterator_ChannelBag_fcurves_length(PointerRNA *ptr)
|
||||
{
|
||||
animrig::ChannelBag &bag = rna_data_channelbag(ptr);
|
||||
return bag.fcurves().size();
|
||||
}
|
||||
|
||||
static AnimationChannelBag *rna_KeyframeAnimationStrip_channels(
|
||||
KeyframeAnimationStrip *self, const animrig::binding_handle_t binding_handle)
|
||||
{
|
||||
animrig::KeyframeStrip &key_strip = self->wrap();
|
||||
return key_strip.channelbag_for_binding(binding_handle);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void rna_def_animation_bindings(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "AnimationBindings");
|
||||
srna = RNA_def_struct(brna, "AnimationBindings", nullptr);
|
||||
RNA_def_struct_sdna(srna, "Animation");
|
||||
RNA_def_struct_ui_text(srna, "Animation Bindings", "Collection of animation bindings");
|
||||
|
||||
/* Animation.bindings.new(...) */
|
||||
func = RNA_def_function(srna, "new", "rna_Animation_bindings_new");
|
||||
RNA_def_function_ui_description(func, "Add a binding to the animation");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(
|
||||
func, "animated_id", "ID", "Data-Block", "Data-block that will be animated by this binding");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(
|
||||
func, "binding", "AnimationBinding", "", "Newly created animation binding");
|
||||
RNA_def_function_return(func, parm);
|
||||
}
|
||||
|
||||
static void rna_def_animation_layers(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "AnimationLayers");
|
||||
srna = RNA_def_struct(brna, "AnimationLayers", nullptr);
|
||||
RNA_def_struct_sdna(srna, "Animation");
|
||||
RNA_def_struct_ui_text(srna, "Animation Layers", "Collection of animation layers");
|
||||
|
||||
/* Animation.layers.new(...) */
|
||||
func = RNA_def_function(srna, "new", "rna_Animation_layers_new");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(
|
||||
func,
|
||||
"Add a layer to the Animation. Currently an Animation can only have at most one layer");
|
||||
parm = RNA_def_string(func,
|
||||
"name",
|
||||
nullptr,
|
||||
sizeof(AnimationLayer::name) - 1,
|
||||
"Name",
|
||||
"Name of the layer, will be made unique within the Animation data-block");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(func, "layer", "AnimationLayer", "", "Newly created animation layer");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
/* Animation.layers.remove(layer) */
|
||||
func = RNA_def_function(srna, "remove", "rna_Animation_layers_remove");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(func, "Remove the layer from the animation");
|
||||
parm = RNA_def_pointer(
|
||||
func, "anim_layer", "AnimationLayer", "Animation Layer", "The layer to remove");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
|
||||
static void rna_def_animation(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "Animation", "ID");
|
||||
RNA_def_struct_sdna(srna, "Animation");
|
||||
RNA_def_struct_ui_text(srna, "Animation", "A collection of animation layers");
|
||||
RNA_def_struct_ui_icon(srna, ICON_ACTION);
|
||||
|
||||
prop = RNA_def_property(srna, "last_binding_handle", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
/* Collection properties .*/
|
||||
prop = RNA_def_property(srna, "bindings", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "AnimationBinding");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_iterator_animation_bindings_begin",
|
||||
"rna_iterator_array_next",
|
||||
"rna_iterator_array_end",
|
||||
"rna_iterator_array_dereference_get",
|
||||
"rna_iterator_animation_bindings_length",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_ui_text(prop, "Bindings", "The list of bindings in this animation data-block");
|
||||
rna_def_animation_bindings(brna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "layers", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "AnimationLayer");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_iterator_animation_layers_begin",
|
||||
"rna_iterator_array_next",
|
||||
"rna_iterator_array_end",
|
||||
"rna_iterator_array_dereference_get",
|
||||
"rna_iterator_animation_layers_length",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_ui_text(prop, "Layers", "The list of layers that make up this Animation");
|
||||
rna_def_animation_layers(brna, prop);
|
||||
}
|
||||
|
||||
static void rna_def_animation_binding(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "AnimationBinding", nullptr);
|
||||
RNA_def_struct_path_func(srna, "rna_AnimationBinding_path");
|
||||
RNA_def_struct_ui_text(
|
||||
srna,
|
||||
"Animation Binding",
|
||||
"Identifier for a set of channels in this Animation, that can be used by a data-block "
|
||||
"to specify what it gets animated by");
|
||||
|
||||
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_AnimationBinding_name_set");
|
||||
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_AnimationBinding_name_update");
|
||||
RNA_def_struct_ui_text(
|
||||
srna,
|
||||
"Binding Name",
|
||||
"Used when connecting an Animation to a data-block, to find the correct binding handle");
|
||||
|
||||
prop = RNA_def_property(srna, "handle", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_struct_ui_text(srna,
|
||||
"Binding Handle",
|
||||
"Number specific to this Binding, unique within the Animation data-block"
|
||||
"This is used, for example, on a KeyframeAnimationStrip to look up the "
|
||||
"AnimationChannelBag for this Binding");
|
||||
}
|
||||
|
||||
static void rna_def_animationlayer_strips(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "AnimationStrips");
|
||||
srna = RNA_def_struct(brna, "AnimationStrips", nullptr);
|
||||
RNA_def_struct_sdna(srna, "AnimationLayer");
|
||||
RNA_def_struct_ui_text(srna, "Animation Strips", "Collection of animation strips");
|
||||
|
||||
/* Layer.strips.new(type='...') */
|
||||
func = RNA_def_function(srna, "new", "rna_AnimationStrips_new");
|
||||
RNA_def_function_ui_description(func,
|
||||
"Add a new strip to the layer. Currently a layer can only have "
|
||||
"one strip, with infinite boundaries");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_enum(func,
|
||||
"type",
|
||||
rna_enum_strip_type_items,
|
||||
int(animrig::Strip::Type::Keyframe),
|
||||
"Type",
|
||||
"The type of strip to create");
|
||||
/* Return value. */
|
||||
parm = RNA_def_pointer(func, "strip", "AnimationStrip", "", "Newly created animation strip");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
/* Layer.strips.remove(strip) */
|
||||
func = RNA_def_function(srna, "remove", "rna_AnimationStrips_remove");
|
||||
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(func, "Remove the strip from the animation layer");
|
||||
parm = RNA_def_pointer(
|
||||
func, "anim_strip", "AnimationStrip", "Animation Strip", "The strip to remove");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
|
||||
static void rna_def_animation_layer(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "AnimationLayer", nullptr);
|
||||
RNA_def_struct_ui_text(srna, "Animation Layer", "");
|
||||
RNA_def_struct_path_func(srna, "rna_AnimationLayer_path");
|
||||
|
||||
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
|
||||
RNA_def_struct_name_property(srna, prop);
|
||||
|
||||
prop = RNA_def_property(srna, "influence", PROP_FLOAT, PROP_FACTOR);
|
||||
RNA_def_property_range(prop, 0.0f, 1.0f);
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Influence", "How much of this layer is used when blending into the lower layers");
|
||||
RNA_def_property_ui_range(prop, 0.0, 1.0, 3, 2);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_Animation_tag_animupdate");
|
||||
|
||||
prop = RNA_def_property(srna, "mix_mode", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "layer_mix_mode");
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Mix Mode", "How animation of this layer is blended into the lower layers");
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_enum_items(prop, rna_enum_layer_mix_mode_items);
|
||||
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_Animation_tag_animupdate");
|
||||
|
||||
/* Collection properties .*/
|
||||
prop = RNA_def_property(srna, "strips", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "AnimationStrip");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_iterator_animationlayer_strips_begin",
|
||||
"rna_iterator_array_next",
|
||||
"rna_iterator_array_end",
|
||||
"rna_iterator_array_dereference_get",
|
||||
"rna_iterator_animationlayer_strips_length",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_ui_text(prop, "Strips", "The list of strips that are on this animation layer");
|
||||
|
||||
rna_def_animationlayer_strips(brna, prop);
|
||||
}
|
||||
|
||||
static void rna_def_keyframestrip_channelbags(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
RNA_def_property_srna(cprop, "AnimationChannelBags");
|
||||
srna = RNA_def_struct(brna, "AnimationChannelBags", nullptr);
|
||||
RNA_def_struct_sdna(srna, "KeyframeAnimationStrip");
|
||||
RNA_def_struct_ui_text(
|
||||
srna,
|
||||
"Animation Channels for Bindings",
|
||||
"For each animation binding, a list of animation channels that are meant for that binding");
|
||||
}
|
||||
|
||||
static void rna_def_animation_keyframe_strip(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "KeyframeAnimationStrip", "AnimationStrip");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "Keyframe Animation Strip", "Strip with a set of F-Curves for each animation binding");
|
||||
|
||||
prop = RNA_def_property(srna, "channelbags", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "AnimationChannelBag");
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_iterator_keyframestrip_channelbags_begin",
|
||||
"rna_iterator_array_next",
|
||||
"rna_iterator_array_end",
|
||||
"rna_iterator_array_dereference_get",
|
||||
"rna_iterator_keyframestrip_channelbags_length",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
rna_def_keyframestrip_channelbags(brna, prop);
|
||||
|
||||
{
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
/* KeyframeStrip.channels(...). */
|
||||
func = RNA_def_function(srna, "channels", "rna_KeyframeAnimationStrip_channels");
|
||||
RNA_def_function_ui_description(func, "Find the AnimationChannelBag for a specific Binding");
|
||||
parm = RNA_def_int(func,
|
||||
"binding_handle",
|
||||
0,
|
||||
0,
|
||||
INT_MAX,
|
||||
"Binding Handle",
|
||||
"Number that identifies a specific animation binding",
|
||||
0,
|
||||
INT_MAX);
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
parm = RNA_def_pointer(func, "channels", "AnimationChannelBag", "Channels", "");
|
||||
RNA_def_function_return(func, parm);
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_def_animation_strip(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "AnimationStrip", nullptr);
|
||||
RNA_def_struct_ui_text(srna, "Animation Strip", "");
|
||||
RNA_def_struct_path_func(srna, "rna_AnimationStrip_path");
|
||||
RNA_def_struct_refine_func(srna, "rna_AnimationStrip_refine");
|
||||
|
||||
static const EnumPropertyItem prop_type_items[] = {
|
||||
{int(animrig::Strip::Type::Keyframe),
|
||||
"KEYFRAME",
|
||||
0,
|
||||
"Keyframe",
|
||||
"Strip with a set of F-Curves for each animation binding"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "strip_type");
|
||||
RNA_def_property_enum_items(prop, prop_type_items);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
/* Define Strip subclasses. */
|
||||
rna_def_animation_keyframe_strip(brna);
|
||||
}
|
||||
|
||||
static void rna_def_channelbag_for_binding_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
RNA_def_property_srna(cprop, "AnimationChannelBagFCurves");
|
||||
srna = RNA_def_struct(brna, "AnimationChannelBagFCurves", nullptr);
|
||||
RNA_def_struct_sdna(srna, "bAnimationChannelBag");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "F-Curves", "Collection of F-Curves for a specific animation binding");
|
||||
}
|
||||
|
||||
static void rna_def_animation_channelbag(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
srna = RNA_def_struct(brna, "AnimationChannelBag", nullptr);
|
||||
RNA_def_struct_ui_text(
|
||||
srna,
|
||||
"Animation Channel Bag",
|
||||
"Collection of animation channels, typically associated with an animation binding");
|
||||
|
||||
prop = RNA_def_property(srna, "binding_handle", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
prop = RNA_def_property(srna, "fcurves", PROP_COLLECTION, PROP_NONE);
|
||||
RNA_def_property_collection_funcs(prop,
|
||||
"rna_iterator_ChannelBag_fcurves_begin",
|
||||
"rna_iterator_array_next",
|
||||
"rna_iterator_array_end",
|
||||
"rna_iterator_array_dereference_get",
|
||||
"rna_iterator_ChannelBag_fcurves_length",
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr);
|
||||
RNA_def_property_struct_type(prop, "FCurve");
|
||||
RNA_def_property_ui_text(prop, "F-Curves", "The individual F-Curves that animate the binding");
|
||||
rna_def_channelbag_for_binding_fcurves(brna, prop);
|
||||
}
|
||||
|
||||
void RNA_def_animation_id(BlenderRNA *brna)
|
||||
{
|
||||
rna_def_animation(brna);
|
||||
rna_def_animation_binding(brna);
|
||||
rna_def_animation_layer(brna);
|
||||
rna_def_animation_strip(brna);
|
||||
rna_def_animation_channelbag(brna);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -139,6 +139,9 @@ extern BlenderRNA BLENDER_RNA;
|
||||
void RNA_def_ID(BlenderRNA *brna);
|
||||
void RNA_def_action(BlenderRNA *brna);
|
||||
void RNA_def_animation(BlenderRNA *brna);
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_animation_id(BlenderRNA *brna);
|
||||
#endif
|
||||
void RNA_def_animviz(BlenderRNA *brna);
|
||||
void RNA_def_armature(BlenderRNA *brna);
|
||||
void RNA_def_attribute(BlenderRNA *brna);
|
||||
@@ -486,6 +489,9 @@ void RNA_def_main_speakers(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_sounds(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_armatures(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_main_animations(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
#endif
|
||||
void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_palettes(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_gpencil_legacy(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
|
||||
@@ -90,6 +90,9 @@ static void rna_Main_filepath_set(PointerRNA *ptr, const char *value)
|
||||
}
|
||||
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(actions)
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(animations)
|
||||
# endif
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(armatures)
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(brushes)
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(cachefiles)
|
||||
@@ -319,6 +322,14 @@ void RNA_def_main(BlenderRNA *brna)
|
||||
"Actions",
|
||||
"Action data-blocks",
|
||||
RNA_def_main_actions},
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
{"animations",
|
||||
"Animation",
|
||||
"rna_Main_animations_begin",
|
||||
"animations",
|
||||
"Animation data-blocks",
|
||||
RNA_def_main_animations},
|
||||
# endif
|
||||
{"particles",
|
||||
"ParticleSettings",
|
||||
"rna_Main_particles_begin",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
# include "BKE_action.h"
|
||||
# include "BKE_animation.hh"
|
||||
# include "BKE_armature.hh"
|
||||
# include "BKE_brush.hh"
|
||||
# include "BKE_camera.h"
|
||||
@@ -65,6 +66,7 @@
|
||||
# include "DEG_depsgraph_build.hh"
|
||||
# include "DEG_depsgraph_query.hh"
|
||||
|
||||
# include "DNA_anim_types.h"
|
||||
# include "DNA_armature_types.h"
|
||||
# include "DNA_brush_types.h"
|
||||
# include "DNA_camera_types.h"
|
||||
@@ -635,6 +637,22 @@ static bAction *rna_Main_actions_new(Main *bmain, const char *name)
|
||||
return act;
|
||||
}
|
||||
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
static Animation *rna_Main_animations_new(Main *bmain, const char *name)
|
||||
{
|
||||
char safe_name[MAX_ID_NAME - 2];
|
||||
rna_idname_validate(name, safe_name);
|
||||
|
||||
Animation *anim = BKE_animation_add(bmain, safe_name);
|
||||
id_fake_user_clear(&anim->id);
|
||||
id_us_min(&anim->id);
|
||||
|
||||
WM_main_add_notifier(NC_ID | NA_ADDED, nullptr);
|
||||
|
||||
return anim;
|
||||
}
|
||||
# endif
|
||||
|
||||
static ParticleSettings *rna_Main_particles_new(Main *bmain, const char *name)
|
||||
{
|
||||
char safe_name[MAX_ID_NAME - 2];
|
||||
@@ -821,6 +839,9 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(speakers, speakers, ID_SPK)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(sounds, sounds, ID_SO)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(armatures, armatures, ID_AR)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(actions, actions, ID_AC)
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(animations, animations, ID_AN)
|
||||
# endif
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(particles, particles, ID_PA)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(palettes, palettes, ID_PAL)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(gpencils, gpencils, ID_GD_LEGACY)
|
||||
@@ -1881,6 +1902,49 @@ void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
parm = RNA_def_boolean(func, "value", false, "Value", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_main_animations(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "BlendDataAnimations");
|
||||
srna = RNA_def_struct(brna, "BlendDataAnimations", nullptr);
|
||||
RNA_def_struct_sdna(srna, "Main");
|
||||
RNA_def_struct_ui_text(srna, "Main Animations", "Collection of animation data-blocks");
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_Main_animations_new");
|
||||
RNA_def_function_ui_description(func, "Add a new animation data-block to the main database");
|
||||
parm = RNA_def_string(func, "name", "Animation", 0, "", "Name for the new data-block");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
/* return type */
|
||||
parm = RNA_def_pointer(func, "animation", "Animation", "", "New animation data-block");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "remove", "rna_Main_ID_remove");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(func,
|
||||
"Remove an animation data-block from the current blendfile");
|
||||
parm = RNA_def_pointer(func, "animation", "Animation", "", "Animation to remove");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
|
||||
RNA_def_boolean(
|
||||
func, "do_unlink", true, "", "Unlink all usages of this animation before deleting it");
|
||||
RNA_def_boolean(func,
|
||||
"do_id_user",
|
||||
true,
|
||||
"",
|
||||
"Decrement user counter of all datablocks used by this animation");
|
||||
RNA_def_boolean(
|
||||
func, "do_ui_user", true, "", "Make sure interface does not reference this animation");
|
||||
|
||||
/* Defined via RNA_MAIN_LISTBASE_FUNCS_DEF. */
|
||||
func = RNA_def_function(srna, "tag", "rna_Main_animations_tag");
|
||||
parm = RNA_def_boolean(func, "value", false, "Value", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
# endif
|
||||
void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
@@ -3441,7 +3441,11 @@ static const EnumPropertyItem dt_uv_items[] = {
|
||||
static IDFilterEnumPropertyItem rna_enum_space_file_id_filter_categories[] = {
|
||||
/* Categories */
|
||||
{FILTER_ID_SCE, "category_scene", ICON_SCENE_DATA, "Scenes", "Show scenes"},
|
||||
{FILTER_ID_AC, "category_animation", ICON_ANIM_DATA, "Animations", "Show animation data"},
|
||||
{FILTER_ID_AC | FILTER_ID_AN,
|
||||
"category_animation",
|
||||
ICON_ANIM_DATA,
|
||||
"Animations",
|
||||
"Show animation data"},
|
||||
{FILTER_ID_OB | FILTER_ID_GR,
|
||||
"category_object",
|
||||
ICON_OUTLINER_COLLECTION,
|
||||
|
||||
@@ -7218,6 +7218,13 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Extensions Development Utilities", "Developer support utilities for extensions");
|
||||
RNA_def_property_update(prop, 0, "rna_userdef_update");
|
||||
|
||||
prop = RNA_def_property(srna, "use_animation_baklava", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "use_animation_baklava", 1);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Animation: Project Baklava",
|
||||
"Enable the new multi-data-block, layered animation system");
|
||||
RNA_def_property_update(prop, 0, "rna_userdef_update");
|
||||
}
|
||||
|
||||
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
|
||||
@@ -386,6 +386,14 @@ add_blender_test(
|
||||
--testdir "${TEST_SRC_DIR}/animation"
|
||||
)
|
||||
|
||||
if(WITH_EXPERIMENTAL_FEATURES)
|
||||
# Only run with Project Baklava enabled.
|
||||
add_blender_test(
|
||||
bl_animation_id
|
||||
--python ${CMAKE_CURRENT_LIST_DIR}/bl_animation_id.py
|
||||
)
|
||||
endif()
|
||||
|
||||
add_blender_test(
|
||||
bl_animation_keyframing
|
||||
--python ${CMAKE_CURRENT_LIST_DIR}/bl_animation_keyframing.py
|
||||
|
||||
92
tests/python/bl_animation_id.py
Normal file
92
tests/python/bl_animation_id.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# SPDX-FileCopyrightText: 2020-2023 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
import bpy
|
||||
|
||||
"""
|
||||
blender -b --factory-startup --python tests/python/bl_animation_id.py
|
||||
"""
|
||||
|
||||
|
||||
class LimitationsTest(unittest.TestCase):
|
||||
"""Test artificial limitations for the Animation data-block.
|
||||
|
||||
Certain limitations are in place to keep development & testing focused.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
anims = bpy.data.animations
|
||||
while anims:
|
||||
anims.remove(anims[0])
|
||||
|
||||
def test_initial_layers(self):
|
||||
"""Test that upon creation an Animation has no layers/strips."""
|
||||
anim = bpy.data.animations.new('TestAnim')
|
||||
self.assertEqual([], anim.layers[:])
|
||||
|
||||
def test_limited_layers_strips(self):
|
||||
"""Test that there can only be one layer with one strip."""
|
||||
|
||||
anim = bpy.data.animations.new('TestAnim')
|
||||
layer = anim.layers.new(name="Layer")
|
||||
self.assertEqual([], layer.strips[:])
|
||||
strip = layer.strips.new(type='KEYFRAME')
|
||||
|
||||
# Adding a 2nd layer should be forbidden.
|
||||
with self.assertRaises(RuntimeError):
|
||||
anim.layers.new(name="Forbidden Layer")
|
||||
self.assertEqual([layer], anim.layers[:])
|
||||
|
||||
# Adding a 2nd strip should be forbidden.
|
||||
with self.assertRaises(RuntimeError):
|
||||
layer.strips.new(type='KEYFRAME')
|
||||
self.assertEqual([strip], layer.strips[:])
|
||||
|
||||
def test_limited_strip_api(self):
|
||||
"""Test that strips have no frame start/end/offset properties."""
|
||||
|
||||
anim = bpy.data.animations.new('TestAnim')
|
||||
layer = anim.layers.new(name="Layer")
|
||||
strip = layer.strips.new(type='KEYFRAME')
|
||||
|
||||
self.assertFalse(hasattr(strip, 'frame_start'))
|
||||
self.assertFalse(hasattr(strip, 'frame_end'))
|
||||
self.assertFalse(hasattr(strip, 'frame_offset'))
|
||||
|
||||
|
||||
class DataPathTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
anims = bpy.data.animations
|
||||
while anims:
|
||||
anims.remove(anims[0])
|
||||
|
||||
def test_repr(self):
|
||||
anim = bpy.data.animations.new('TestAnim')
|
||||
|
||||
layer = anim.layers.new(name="Layer")
|
||||
self.assertEqual("bpy.data.animations['TestAnim'].layers[\"Layer\"]", repr(layer))
|
||||
|
||||
strip = layer.strips.new(type='KEYFRAME')
|
||||
self.assertEqual("bpy.data.animations['TestAnim'].layers[\"Layer\"].strips[0]", repr(strip))
|
||||
|
||||
|
||||
def main():
|
||||
global args
|
||||
import argparse
|
||||
|
||||
argv = [sys.argv[0]]
|
||||
if '--' in sys.argv:
|
||||
argv += sys.argv[sys.argv.index('--') + 1:]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
args, remaining = parser.parse_known_args(argv)
|
||||
|
||||
unittest.main(argv=remaining)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user