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:
Sybren A. Stüvel
2024-03-04 18:02:49 +01:00
parent f8efd4fad0
commit 38878b4ac2
17 changed files with 1531 additions and 2 deletions

View File

@@ -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")),
),
)

View File

@@ -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");

View File

@@ -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

View File

@@ -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. */

View 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

View File

@@ -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;

View File

@@ -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

View File

@@ -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},

View File

@@ -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;

View 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

View File

@@ -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);

View File

@@ -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",

View File

@@ -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;

View File

@@ -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,

View File

@@ -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)

View File

@@ -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

View 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()