Anim: DNA for Animation data-block

Introduce new DNA for the `Animation` data-block and its sub-data.

This includes the blenkernel code for reading & writing to blend files,
and for memory management (freeing, duplicating). Minimal C++ wrappers
are included, with just the functionality needed for blenkernel to do
its job.

The Outliner code is extended so that it knows about the new data-type,
nothing more.

For more info, see issue #113594.

Pull Request: https://projects.blender.org/blender/blender/pulls/119077
This commit is contained in:
Sybren A. Stüvel
2024-03-07 16:41:25 +01:00
parent 559ad5e0f9
commit 90ef46baa1
27 changed files with 1136 additions and 3 deletions

View File

@@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief Animation data-block functionality.
*/
#pragma once
#include "ANIM_fcurve.hh"
#include "DNA_anim_types.h"
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "BLI_string_ref.hh"
struct AnimationEvalContext;
struct FCurve;
struct ID;
struct Main;
struct PointerRNA;
namespace blender::animrig {
/* Forward declarations for the types defined later in this file. */
class Layer;
class Strip;
class Binding;
/* Use an alias for the Binding handle type to help disambiguate function parameters. */
using binding_handle_t = decltype(::AnimationBinding::handle);
/**
* Container of animation data for one or more animated IDs.
*
* Broadly an Animation consists of Layers, each Layer has Strips, and it's the
* Strips that eventually contain the animation data.
*
* Temporary limitation: each Animation can only contain one Layer.
*
* Which sub-set of that data drives the animation of which ID is determined by
* which Binding is associated with that ID.
*
* \see AnimData::animation
* \see AnimData::binding_handle
*/
class Animation : public ::Animation {
public:
Animation() = default;
/**
* Copy constructor is deleted, as code should use regular ID library
* management functions to duplicate this data-block.
*/
Animation(const Animation &other) = delete;
/* Animation Layers access. */
blender::Span<const Layer *> layers() const;
blender::MutableSpan<Layer *> layers();
const Layer *layer(int64_t index) const;
Layer *layer(int64_t index);
/* 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);
/** Free all data in the `Animation`. Doesn't delete the `Animation` itself. */
void free_data();
};
static_assert(sizeof(Animation) == sizeof(::Animation),
"DNA struct and its C++ wrapper must have the same size");
/**
* Strips contain the actual animation data.
*
* Although the data model allows for different strip types, currently only a
* single type is implemented: keyframe strips.
*/
class Strip : public ::AnimationStrip {
public:
Strip() = default;
/**
* Strip cannot be duplicated via the copy constructor. Either use a concrete
* strip type's copy constructor, or use Strip::duplicate().
*
* The reason why the copy constructor won't work is due to the double nature
* of the inheritance at play here:
*
* C-style inheritance: `KeyframeAnimationStrip` "inherits" `AnimationStrip"
* by embedding the latter. This means that any `KeyframeAnimationStrip *`
* can be reinterpreted as `AnimationStrip *`.
*
* C++-style inheritance: the C++ wrappers inherit the DNA structs, so
* `animrig::Strip` inherits `::AnimationStrip`, and
* `animrig::KeyframeStrip` inherits `::KeyframeAnimationStrip`.
*/
Strip(const Strip &other) = delete;
~Strip();
Strip *duplicate(StringRefNull allocation_name) const;
enum class Type : int8_t { Keyframe = 0 };
/**
* Strip type, so it's known which subclass this can be wrapped in without
* having to rely on C++ RTTI.
*/
Type type() const
{
return Type(this->strip_type);
}
template<typename T> bool is() const;
template<typename T> T &as();
template<typename T> const T &as() const;
};
static_assert(sizeof(Strip) == sizeof(::AnimationStrip),
"DNA struct and its C++ wrapper must have the same size");
/**
* Layers can be stacked on top of each other to define the animation. Each
* layer has a mix mode and an influence (0-1), which define how it is mixed
* with the layers below it.
*
* Layers contain one or more Strips, which in turn contain the animation data
* itself.
*
* Temporary limitation: at most one strip may exist on a layer, and it extends
* from negative to positive infinity.
*/
class Layer : public ::AnimationLayer {
public:
Layer() = default;
Layer(const Layer &other);
~Layer();
enum class Flags : uint8_t {
/* Set by default, cleared to mute. */
Enabled = (1 << 0),
/* When adding/removing a flag, also update the ENUM_OPERATORS() invocation below. */
};
Flags flags() const
{
return static_cast<Flags>(this->layer_flags);
}
enum class MixMode : int8_t {
/** Channels in this layer override the same channels from underlying layers. */
Replace = 0,
/** Channels in this layer are added to underlying layers as sequential operations. */
Offset = 1,
/** Channels in this layer are added to underlying layers on a per-channel basis. */
Add = 2,
/** Channels in this layer are subtracted to underlying layers on a per-channel basis. */
Subtract = 3,
/** Channels in this layer are multiplied with underlying layers on a per-channel basis. */
Multiply = 4,
};
MixMode mix_mode() const
{
return static_cast<MixMode>(this->layer_mix_mode);
}
/* Strip access. */
blender::Span<const Strip *> strips() const;
blender::MutableSpan<Strip *> strips();
const Strip *strip(int64_t index) const;
Strip *strip(int64_t index);
};
static_assert(sizeof(Layer) == sizeof(::AnimationLayer),
"DNA struct and its C++ wrapper must have the same size");
ENUM_OPERATORS(Layer::Flags, Layer::Flags::Enabled);
/**
* Identifier for a sub-set of the animation data inside an Animation data-block.
*
* An animatable ID specifies both an `Animation*` and an `AnimationBinding::handle`
* to identify which F-Curves (and in the future other animation data) it will
* be animated by.
*
* This is called an 'binding' because it acts like an binding socket of the
* Animation data-block, into which an animatable ID can be noodled.
*
* \see AnimData::binding_handle
*/
class Binding : public ::AnimationBinding {
public:
Binding() = default;
Binding(const Binding &other) = default;
~Binding() = default;
};
static_assert(sizeof(Binding) == sizeof(::AnimationBinding),
"DNA struct and its C++ wrapper must have the same size");
/**
* KeyframeStrips effectively contain a bag of F-Curves for each Binding.
*/
class KeyframeStrip : public ::KeyframeAnimationStrip {
public:
KeyframeStrip() = default;
KeyframeStrip(const KeyframeStrip &other);
~KeyframeStrip();
/* ChannelBag array access. */
blender::Span<const ChannelBag *> channelbags() const;
blender::MutableSpan<ChannelBag *> channelbags();
const ChannelBag *channelbag(int64_t index) const;
ChannelBag *channelbag(int64_t index);
};
static_assert(sizeof(KeyframeStrip) == sizeof(::KeyframeAnimationStrip),
"DNA struct and its C++ wrapper must have the same size");
template<> KeyframeStrip &Strip::as<KeyframeStrip>();
template<> const KeyframeStrip &Strip::as<KeyframeStrip>() const;
/**
* Collection of F-Curves, intended for a specific Binding handle.
*/
class ChannelBag : public ::AnimationChannelBag {
public:
ChannelBag() = default;
ChannelBag(const ChannelBag &other);
~ChannelBag();
/* FCurves access. */
blender::Span<const FCurve *> fcurves() const;
blender::MutableSpan<FCurve *> fcurves();
const FCurve *fcurve(int64_t index) const;
FCurve *fcurve(int64_t index);
};
static_assert(sizeof(ChannelBag) == sizeof(::AnimationChannelBag),
"DNA struct and its C++ wrapper must have the same size");
} // namespace blender::animrig
/* Wrap functions for the DNA structs. */
inline blender::animrig::Animation &Animation::wrap()
{
return *reinterpret_cast<blender::animrig::Animation *>(this);
}
inline const blender::animrig::Animation &Animation::wrap() const
{
return *reinterpret_cast<const blender::animrig::Animation *>(this);
}
inline blender::animrig::Layer &AnimationLayer::wrap()
{
return *reinterpret_cast<blender::animrig::Layer *>(this);
}
inline const blender::animrig::Layer &AnimationLayer::wrap() const
{
return *reinterpret_cast<const blender::animrig::Layer *>(this);
}
inline blender::animrig::Binding &AnimationBinding::wrap()
{
return *reinterpret_cast<blender::animrig::Binding *>(this);
}
inline const blender::animrig::Binding &AnimationBinding::wrap() const
{
return *reinterpret_cast<const blender::animrig::Binding *>(this);
}
inline blender::animrig::Strip &AnimationStrip::wrap()
{
return *reinterpret_cast<blender::animrig::Strip *>(this);
}
inline const blender::animrig::Strip &AnimationStrip::wrap() const
{
return *reinterpret_cast<const blender::animrig::Strip *>(this);
}
inline blender::animrig::KeyframeStrip &KeyframeAnimationStrip::wrap()
{
return *reinterpret_cast<blender::animrig::KeyframeStrip *>(this);
}
inline const blender::animrig::KeyframeStrip &KeyframeAnimationStrip::wrap() const
{
return *reinterpret_cast<const blender::animrig::KeyframeStrip *>(this);
}
inline blender::animrig::ChannelBag &AnimationChannelBag::wrap()
{
return *reinterpret_cast<blender::animrig::ChannelBag *>(this);
}
inline const blender::animrig::ChannelBag &AnimationChannelBag::wrap() const
{
return *reinterpret_cast<const blender::animrig::ChannelBag *>(this);
}

View File

@@ -21,6 +21,7 @@ set(INC_SYS
set(SRC
intern/action.cc
intern/anim_rna.cc
intern/animation.cc
intern/animdata.cc
intern/bone_collections.cc
intern/bonecolor.cc
@@ -31,6 +32,7 @@ set(SRC
intern/visualkey.cc
ANIM_action.hh
ANIM_animation.hh
ANIM_animdata.hh
ANIM_armature_iter.hh
ANIM_bone_collections.hh
@@ -50,6 +52,7 @@ set(LIB
bf::dna
PRIVATE bf_editor_interface
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::atomic
)

View File

@@ -0,0 +1,260 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "DNA_anim_defaults.h"
#include "DNA_anim_types.h"
#include "DNA_defaults.h"
#include "BLI_listbase.h"
#include "BLI_listbase_wrapper.hh"
#include "BLI_math_base.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BKE_anim_data.hh"
#include "BKE_animation.hh"
#include "BKE_fcurve.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "ED_keyframing.hh"
#include "MEM_guardedalloc.h"
#include "atomic_ops.h"
#include "ANIM_animation.hh"
#include "ANIM_fcurve.hh"
#include <cstdio>
#include <cstring>
namespace blender::animrig {
/* ----- Animation implementation ----------- */
blender::Span<const Layer *> Animation::layers() const
{
return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
this->layer_array_num};
}
blender::MutableSpan<Layer *> Animation::layers()
{
return blender::MutableSpan<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
this->layer_array_num};
}
const Layer *Animation::layer(const int64_t index) const
{
return &this->layer_array[index]->wrap();
}
Layer *Animation::layer(const int64_t index)
{
return &this->layer_array[index]->wrap();
}
blender::Span<const Binding *> Animation::bindings() const
{
return blender::Span<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
this->binding_array_num};
}
blender::MutableSpan<Binding *> Animation::bindings()
{
return blender::MutableSpan<Binding *>{reinterpret_cast<Binding **>(this->binding_array),
this->binding_array_num};
}
const Binding *Animation::binding(const int64_t index) const
{
return &this->binding_array[index]->wrap();
}
Binding *Animation::binding(const int64_t index)
{
return &this->binding_array[index]->wrap();
}
void Animation::free_data()
{
/* Free layers. */
for (Layer *layer : this->layers()) {
MEM_delete(layer);
}
MEM_SAFE_FREE(this->layer_array);
this->layer_array_num = 0;
/* Free bindings. */
for (Binding *binding : this->bindings()) {
MEM_delete(binding);
}
MEM_SAFE_FREE(this->binding_array);
this->binding_array_num = 0;
}
/* ----- AnimationLayer implementation ----------- */
Layer::Layer(const Layer &other)
{
memcpy(this, &other, sizeof(*this));
/* Strips. */
this->strip_array = MEM_cnew_array<AnimationStrip *>(other.strip_array_num, __func__);
for (int i : other.strips().index_range()) {
this->strip_array[i] = other.strip(i)->duplicate(__func__);
}
}
Layer::~Layer()
{
for (Strip *strip : this->strips()) {
MEM_delete(strip);
}
MEM_SAFE_FREE(this->strip_array);
this->strip_array_num = 0;
}
blender::Span<const Strip *> Layer::strips() const
{
return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
this->strip_array_num};
}
blender::MutableSpan<Strip *> Layer::strips()
{
return blender::MutableSpan<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
this->strip_array_num};
}
const Strip *Layer::strip(const int64_t index) const
{
return &this->strip_array[index]->wrap();
}
Strip *Layer::strip(const int64_t index)
{
return &this->strip_array[index]->wrap();
}
/* ----- AnimationBinding implementation ----------- */
/* ----- AnimationStrip implementation ----------- */
Strip *Strip::duplicate(const StringRefNull allocation_name) const
{
switch (this->type()) {
case Type::Keyframe: {
const KeyframeStrip &source = this->as<KeyframeStrip>();
KeyframeStrip *copy = MEM_new<KeyframeStrip>(allocation_name.c_str(), source);
return &copy->strip.wrap();
}
}
BLI_assert_unreachable();
return nullptr;
}
Strip::~Strip()
{
switch (this->type()) {
case Type::Keyframe:
this->as<KeyframeStrip>().~KeyframeStrip();
return;
}
BLI_assert_unreachable();
}
/* ----- KeyframeAnimationStrip implementation ----------- */
KeyframeStrip::KeyframeStrip(const KeyframeStrip &other)
{
memcpy(this, &other, sizeof(*this));
this->channelbags_array = MEM_cnew_array<AnimationChannelBag *>(other.channelbags_array_num,
__func__);
Span<const ChannelBag *> channelbags_src = other.channelbags();
for (int i : channelbags_src.index_range()) {
this->channelbags_array[i] = MEM_new<animrig::ChannelBag>(__func__, *other.channelbag(i));
}
}
KeyframeStrip::~KeyframeStrip()
{
for (ChannelBag *channelbag_for_binding : this->channelbags()) {
MEM_delete(channelbag_for_binding);
}
MEM_SAFE_FREE(this->channelbags_array);
this->channelbags_array_num = 0;
}
template<> bool Strip::is<KeyframeStrip>() const
{
return this->type() == Type::Keyframe;
}
template<> KeyframeStrip &Strip::as<KeyframeStrip>()
{
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
return *reinterpret_cast<KeyframeStrip *>(this);
}
template<> const KeyframeStrip &Strip::as<KeyframeStrip>() const
{
BLI_assert_msg(this->is<KeyframeStrip>(), "Strip is not a KeyframeStrip");
return *reinterpret_cast<const KeyframeStrip *>(this);
}
blender::Span<const ChannelBag *> KeyframeStrip::channelbags() const
{
return blender::Span<ChannelBag *>{reinterpret_cast<ChannelBag **>(this->channelbags_array),
this->channelbags_array_num};
}
blender::MutableSpan<ChannelBag *> KeyframeStrip::channelbags()
{
return blender::MutableSpan<ChannelBag *>{
reinterpret_cast<ChannelBag **>(this->channelbags_array), this->channelbags_array_num};
}
const ChannelBag *KeyframeStrip::channelbag(const int64_t index) const
{
return &this->channelbags_array[index]->wrap();
}
ChannelBag *KeyframeStrip::channelbag(const int64_t index)
{
return &this->channelbags_array[index]->wrap();
}
/* AnimationChannelBag implementation. */
ChannelBag::ChannelBag(const ChannelBag &other)
{
this->binding_handle = other.binding_handle;
this->fcurve_array_num = other.fcurve_array_num;
this->fcurve_array = MEM_cnew_array<FCurve *>(other.fcurve_array_num, __func__);
for (int i = 0; i < other.fcurve_array_num; i++) {
const FCurve *fcu_src = other.fcurve_array[i];
this->fcurve_array[i] = BKE_fcurve_copy(fcu_src);
}
}
ChannelBag::~ChannelBag()
{
for (FCurve *fcu : this->fcurves()) {
BKE_fcurve_free(fcu);
}
MEM_SAFE_FREE(this->fcurve_array);
this->fcurve_array_num = 0;
}
blender::Span<const FCurve *> ChannelBag::fcurves() const
{
return blender::Span<FCurve *>{this->fcurve_array, this->fcurve_array_num};
}
blender::MutableSpan<FCurve *> ChannelBag::fcurves()
{
return blender::MutableSpan<FCurve *>{this->fcurve_array, this->fcurve_array_num};
}
const FCurve *ChannelBag::fcurve(const int64_t index) const
{
return this->fcurve_array[index];
}
FCurve *ChannelBag::fcurve(const int64_t index)
{
return this->fcurve_array[index];
}
} // namespace blender::animrig

View File

@@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* This file only contains the memory management functions for the Animation
* data-block. For all other functionality, see `source/blender/animrig`.
*/
#pragma once
struct Animation;
struct Main;
Animation *BKE_animation_add(Main *bmain, const char name[]);

View File

@@ -273,6 +273,7 @@ extern IDTypeInfo IDType_ID_CV;
extern IDTypeInfo IDType_ID_PT;
extern IDTypeInfo IDType_ID_VO;
extern IDTypeInfo IDType_ID_GP;
extern IDTypeInfo IDType_ID_AN;
/** Empty shell mostly, but needed for read code. */
extern IDTypeInfo IDType_ID_LINK_PLACEHOLDER;

View File

@@ -210,6 +210,7 @@ struct Main {
ListBase collections;
ListBase armatures;
ListBase actions;
ListBase animations;
ListBase nodetrees;
ListBase brushes;
ListBase particles;

View File

@@ -55,6 +55,7 @@ set(SRC
intern/anim_path.cc
intern/anim_sys.cc
intern/anim_visualization.cc
intern/animation.cc
intern/anonymous_attribute_id.cc
intern/appdir.cc
intern/armature.cc
@@ -327,6 +328,7 @@ set(SRC
BKE_anim_data.hh
BKE_anim_path.h
BKE_anim_visualization.h
BKE_animation.hh
BKE_animsys.h
BKE_anonymous_attribute_id.hh
BKE_appdir.hh

View File

@@ -292,6 +292,7 @@ void BKE_animdata_foreach_id(AnimData *adt, LibraryForeachIDData *data)
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcu, data));
}
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, adt->animation, IDWALK_CB_USER);
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, adt->action, IDWALK_CB_USER);
BKE_LIB_FOREACHID_PROCESS_IDSUPER(data, adt->tmpact, IDWALK_CB_USER);

View File

@@ -0,0 +1,255 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file Animation data-block.
* \ingroup bke
*/
#include "BLI_map.hh"
#include "BLI_string_utf8.h"
#include "BLO_read_write.hh"
#include "BKE_animation.hh"
#include "BKE_fcurve.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_query.hh"
#include "BKE_main.hh"
#include "ANIM_animation.hh"
#include "DNA_anim_types.h"
#include "DNA_defaults.h"
#include "BLT_translation.hh"
struct BlendWriter;
struct BlendDataReader;
namespace blender::bke {
static void animation_copy_data(Main * /*bmain*/,
std::optional<Library *> /*owner_library*/,
ID *id_dst,
const ID *id_src,
const int /*flag*/)
{
Animation *dna_anim_dst = reinterpret_cast<Animation *>(id_dst);
animrig::Animation &anim_dst = dna_anim_dst->wrap();
const Animation *dna_anim_src = reinterpret_cast<const Animation *>(id_src);
const animrig::Animation &anim_src = dna_anim_src->wrap();
/* Copy all simple properties. */
anim_dst.layer_array_num = anim_src.layer_array_num;
anim_dst.layer_active_index = anim_src.layer_active_index;
anim_dst.binding_array_num = anim_src.binding_array_num;
anim_dst.last_binding_handle = anim_src.last_binding_handle;
/* Layers. */
anim_dst.layer_array = MEM_cnew_array<AnimationLayer *>(anim_src.layer_array_num, __func__);
for (int i : anim_src.layers().index_range()) {
anim_dst.layer_array[i] = MEM_new<animrig::Layer>(__func__, *anim_src.layer(i));
}
/* Bindings. */
anim_dst.binding_array = MEM_cnew_array<AnimationBinding *>(anim_src.binding_array_num,
__func__);
for (int i : anim_src.bindings().index_range()) {
anim_dst.binding_array[i] = MEM_new<animrig::Binding>(__func__, *anim_src.binding(i));
}
}
/** Free (or release) any data used by this animation (does not free the animation itself). */
static void animation_free_data(ID *id)
{
reinterpret_cast<Animation *>(id)->wrap().free_data();
}
static void animation_foreach_id(ID *id, LibraryForeachIDData *data)
{
animrig::Animation &anim = reinterpret_cast<Animation *>(id)->wrap();
for (animrig::Layer *layer : anim.layers()) {
for (animrig::Strip *strip : layer->strips()) {
switch (strip->type()) {
case animrig::Strip::Type::Keyframe: {
auto &key_strip = strip->as<animrig::KeyframeStrip>();
for (animrig::ChannelBag *channelbag_for_binding : key_strip.channelbags()) {
for (FCurve *fcurve : channelbag_for_binding->fcurves()) {
BKE_LIB_FOREACHID_PROCESS_FUNCTION_CALL(data, BKE_fcurve_foreach_id(fcurve, data));
}
}
}
}
}
}
}
static void write_channelbag(BlendWriter *writer, animrig::ChannelBag &channelbag)
{
BLO_write_struct(writer, AnimationChannelBag, &channelbag);
Span<FCurve *> fcurves = channelbag.fcurves();
BLO_write_pointer_array(writer, fcurves.size(), fcurves.data());
for (FCurve *fcurve : fcurves) {
BLO_write_struct(writer, FCurve, fcurve);
BKE_fcurve_blend_write_data(writer, fcurve);
}
}
static void write_keyframe_strip(BlendWriter *writer, animrig::KeyframeStrip &key_strip)
{
BLO_write_struct(writer, KeyframeAnimationStrip, &key_strip);
auto channelbags = key_strip.channelbags();
BLO_write_pointer_array(writer, channelbags.size(), channelbags.data());
for (animrig::ChannelBag *channelbag : channelbags) {
write_channelbag(writer, *channelbag);
}
}
static void write_strips(BlendWriter *writer, Span<animrig::Strip *> strips)
{
BLO_write_pointer_array(writer, strips.size(), strips.data());
for (animrig::Strip *strip : strips) {
switch (strip->type()) {
case animrig::Strip::Type::Keyframe: {
auto &key_strip = strip->as<animrig::KeyframeStrip>();
write_keyframe_strip(writer, key_strip);
}
}
}
}
static void write_layers(BlendWriter *writer, Span<animrig::Layer *> layers)
{
BLO_write_pointer_array(writer, layers.size(), layers.data());
for (animrig::Layer *layer : layers) {
BLO_write_struct(writer, AnimationLayer, layer);
write_strips(writer, layer->strips());
}
}
static void write_bindings(BlendWriter *writer, Span<animrig::Binding *> bindings)
{
BLO_write_pointer_array(writer, bindings.size(), bindings.data());
for (animrig::Binding *binding : bindings) {
BLO_write_struct(writer, AnimationBinding, binding);
}
}
static void animation_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
animrig::Animation &anim = reinterpret_cast<Animation *>(id)->wrap();
BLO_write_id_struct(writer, Animation, id_address, &anim.id);
BKE_id_blend_write(writer, &anim.id);
write_layers(writer, anim.layers());
write_bindings(writer, anim.bindings());
}
static void read_channelbag(BlendDataReader *reader, animrig::ChannelBag &channelbag)
{
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&channelbag.fcurve_array));
for (int i = 0; i < channelbag.fcurve_array_num; i++) {
BLO_read_data_address(reader, &channelbag.fcurve_array[i]);
BKE_fcurve_blend_read_data(reader, channelbag.fcurve_array[i]);
}
}
static void read_keyframe_strip(BlendDataReader *reader, animrig::KeyframeStrip &strip)
{
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&strip.channelbags_array));
for (int i = 0; i < strip.channelbags_array_num; i++) {
BLO_read_data_address(reader, &strip.channelbags_array[i]);
AnimationChannelBag *channelbag = strip.channelbags_array[i];
read_channelbag(reader, channelbag->wrap());
}
}
static void read_animation_layers(BlendDataReader *reader, animrig::Animation &anim)
{
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&anim.layer_array));
for (int layer_idx = 0; layer_idx < anim.layer_array_num; layer_idx++) {
BLO_read_data_address(reader, &anim.layer_array[layer_idx]);
AnimationLayer *layer = anim.layer_array[layer_idx];
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&layer->strip_array));
for (int strip_idx = 0; strip_idx < layer->strip_array_num; strip_idx++) {
BLO_read_data_address(reader, &layer->strip_array[strip_idx]);
AnimationStrip *dna_strip = layer->strip_array[strip_idx];
animrig::Strip &strip = dna_strip->wrap();
switch (strip.type()) {
case animrig::Strip::Type::Keyframe: {
read_keyframe_strip(reader, strip.as<animrig::KeyframeStrip>());
}
}
}
}
}
static void read_animation_bindings(BlendDataReader *reader, animrig::Animation &anim)
{
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&anim.binding_array));
for (int i = 0; i < anim.binding_array_num; i++) {
BLO_read_data_address(reader, &anim.binding_array[i]);
}
}
static void animation_blend_read_data(BlendDataReader *reader, ID *id)
{
animrig::Animation &animation = reinterpret_cast<Animation *>(id)->wrap();
read_animation_layers(reader, animation);
read_animation_bindings(reader, animation);
}
} // namespace blender::bke
IDTypeInfo IDType_ID_AN = {
/*id_code*/ ID_AN,
/*id_filter*/ FILTER_ID_AN,
/*dependencies_id_types*/ 0,
/*main_listbase_index*/ INDEX_ID_AN,
/*struct_size*/ sizeof(Animation),
/*name*/ "Animation",
/*name_plural*/ N_("animations"),
/*translation_context*/ BLT_I18NCONTEXT_ID_ANIMATION,
/*flags*/ IDTYPE_FLAGS_NO_ANIMDATA,
/*asset_type_info*/ nullptr,
/*init_data*/ nullptr,
/*copy_data*/ blender::bke::animation_copy_data,
/*free_data*/ blender::bke::animation_free_data,
/*make_local*/ nullptr,
/*foreach_id*/ blender::bke::animation_foreach_id,
/*foreach_cache*/ nullptr,
/*foreach_path*/ nullptr,
/*owner_pointer_get*/ nullptr,
/*blend_write*/ blender::bke::animation_blend_write,
/*blend_read_data*/ blender::bke::animation_blend_read_data,
/*blend_read_after_liblink*/ nullptr,
/*blend_read_undo_preserve*/ nullptr,
/*lib_override_apply_post*/ nullptr,
};
Animation *BKE_animation_add(Main *bmain, const char name[])
{
Animation *anim = static_cast<Animation *>(BKE_id_new(bmain, ID_AN, name));
return anim;
}

View File

@@ -83,6 +83,7 @@ static void id_type_init()
INIT_TYPE(ID_GR);
INIT_TYPE(ID_AR);
INIT_TYPE(ID_AC);
INIT_TYPE(ID_AN);
INIT_TYPE(ID_NT);
INIT_TYPE(ID_BR);
INIT_TYPE(ID_PA);
@@ -225,6 +226,7 @@ int BKE_idtype_idcode_to_index(const short idcode)
switch ((ID_Type)idcode) {
CASE_IDINDEX(AC);
CASE_IDINDEX(AN);
CASE_IDINDEX(AR);
CASE_IDINDEX(BR);
CASE_IDINDEX(CA);
@@ -284,6 +286,7 @@ int BKE_idtype_idfilter_to_index(const uint64_t id_filter)
switch (id_filter) {
CASE_IDINDEX(AC);
CASE_IDINDEX(AN);
CASE_IDINDEX(AR);
CASE_IDINDEX(BR);
CASE_IDINDEX(CA);

View File

@@ -849,6 +849,8 @@ ListBase *which_libbase(Main *bmain, short type)
return &(bmain->armatures);
case ID_AC:
return &(bmain->actions);
case ID_AN:
return &(bmain->animations);
case ID_NT:
return &(bmain->nodetrees);
case ID_BR:
@@ -894,6 +896,7 @@ int set_listbasepointers(Main *bmain, ListBase *lb[/*INDEX_ID_MAX*/])
/* Moved here to avoid problems when freeing with animato (aligorith). */
lb[INDEX_ID_AC] = &(bmain->actions);
lb[INDEX_ID_AN] = &(bmain->animations);
lb[INDEX_ID_KE] = &(bmain->shapekeys);

View File

@@ -86,6 +86,7 @@ const char *BLT_translate_do_new_dataname(const char *msgctxt, const char *msgid
/* ID-types contexts. */
/* WARNING! Keep it in sync with ID-types in `blenkernel/intern/idtype.cc`. */
#define BLT_I18NCONTEXT_ID_ACTION "Action"
#define BLT_I18NCONTEXT_ID_ANIMATION "Animation"
#define BLT_I18NCONTEXT_ID_ARMATURE "Armature"
#define BLT_I18NCONTEXT_ID_BRUSH "Brush"
#define BLT_I18NCONTEXT_ID_CACHEFILE "CacheFile"

View File

@@ -557,6 +557,10 @@ void DepsgraphNodeBuilder::build_id(ID *id, const bool force_be_visible)
case ID_AC:
build_action((bAction *)id);
break;
case ID_AN:
/* TODO: actually handle this ID type properly, will be done in a followup commit. */
build_generic_id(id);
break;
case ID_AR:
build_armature((bArmature *)id);
break;

View File

@@ -525,6 +525,10 @@ void DepsgraphRelationBuilder::build_id(ID *id)
case ID_AC:
build_action((bAction *)id);
break;
case ID_AN:
/* TODO: actually handle this ID type properly, will be done in a followup commit. */
build_generic_id(id);
break;
case ID_AR:
build_armature((bArmature *)id);
break;

View File

@@ -2392,6 +2392,8 @@ int UI_icon_from_idcode(const int idcode)
switch ((ID_Type)idcode) {
case ID_AC:
return ICON_ACTION;
case ID_AN:
return ICON_ACTION; /* TODO: give Animation its own icon. */
case ID_AR:
return ICON_ARMATURE_DATA;
case ID_BR:

View File

@@ -1118,6 +1118,8 @@ static const char *template_id_browse_tip(const StructRNA *type)
return N_("Browse Armature data to be linked");
case ID_AC:
return N_("Browse Action to be linked");
case ID_AN:
return N_("Browse Animation to be linked");
case ID_NT:
return N_("Browse Node Tree to be linked");
case ID_BR:

View File

@@ -616,6 +616,7 @@ static int gather_frames_to_render_for_id(LibraryIDLinkCallbackData *cb_data)
case ID_SCR: /* Screen */
case ID_GR: /* Group */
case ID_AC: /* bAction */
case ID_AN: /* Animation */
case ID_BR: /* Brush */
case ID_WM: /* WindowManager */
case ID_LS: /* FreestyleLineStyle */

View File

@@ -2433,6 +2433,8 @@ static BIFIconID tree_element_get_icon_from_id(const ID *id)
return ICON_WORLD_DATA;
case ID_AC:
return ICON_ACTION;
case ID_AN:
return ICON_ACTION; /* TODO: give Animation its own icon. */
case ID_NLA:
return ICON_NLA;
case ID_TXT: {

View File

@@ -139,6 +139,7 @@ struct TreeElementIcon {
ID_GR, \
ID_AR, \
ID_AC, \
ID_AN, \
ID_BR, \
ID_PA, \
ID_GD_LEGACY, \

View File

@@ -147,6 +147,7 @@ static void get_element_operation_type(
case ID_KE:
case ID_WO:
case ID_AC:
case ID_AN:
case ID_TXT:
case ID_GR:
case ID_LS:

View File

@@ -88,6 +88,7 @@ std::unique_ptr<TreeElementID> TreeElementID::create_from_id(TreeElement &legacy
case ID_TXT:
case ID_SO:
case ID_AC:
case ID_AN:
case ID_PAL:
case ID_PC:
case ID_CF:

View File

@@ -1210,6 +1210,7 @@ typedef enum IDRecalcFlag {
#define FILTER_ID_LI (1ULL << 39)
#define FILTER_ID_GP (1ULL << 40)
#define FILTER_ID_IP (1ULL << 41)
#define FILTER_ID_AN (1ULL << 42)
#define FILTER_ID_ALL \
(FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU_LEGACY | \
@@ -1219,7 +1220,7 @@ typedef enum IDRecalcFlag {
FILTER_ID_SPK | FILTER_ID_SO | FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | \
FILTER_ID_CF | FILTER_ID_WS | FILTER_ID_LP | FILTER_ID_CV | FILTER_ID_PT | FILTER_ID_VO | \
FILTER_ID_SIM | FILTER_ID_KE | FILTER_ID_SCR | FILTER_ID_WM | FILTER_ID_LI | FILTER_ID_GP | \
FILTER_ID_IP)
FILTER_ID_IP | FILTER_ID_AN)
/**
* This enum defines the index assigned to each type of IDs in the array returned by
@@ -1258,6 +1259,7 @@ typedef enum eID_Index {
/* Animation types, might be used by almost all other types. */
INDEX_ID_IP, /* Deprecated. */
INDEX_ID_AC,
INDEX_ID_AN,
/* Grease Pencil, special case, should be with the other obdata, but it can also be used by many
* other ID types, including node trees e.g.

View File

@@ -84,6 +84,7 @@ typedef enum ID_Type {
ID_PT = MAKE_ID2('P', 'T'), /* PointCloud */
ID_VO = MAKE_ID2('V', 'O'), /* Volume */
ID_GP = MAKE_ID2('G', 'P'), /* Grease Pencil */
ID_AN = MAKE_ID2('A', 'N'), /* Animation */
} ID_Type;
/* Only used as 'placeholder' in .blend files for directly linked data-blocks. */

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup DNA
*/
#pragma once
#include <math.h>
/* Struct members on own line. */
/* clang-format off */
/* -------------------------------------------------------------------- */
/** \name AnimationLayer Struct
* \{ */
#define _DNA_DEFAULT_AnimationLayer \
{ \
.influence = 1.0f, \
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name AnimationStrip Struct
* \{ */
#define _DNA_DEFAULT_AnimationStrip \
{ \
.frame_start = -INFINITY, \
.frame_end = INFINITY, \
}
/** \} */
/* clang-format on */

View File

@@ -15,6 +15,19 @@
#include "DNA_curve_types.h"
#include "DNA_listBase.h"
#ifdef __cplusplus
# include "BLI_span.hh"
# include <type_traits>
#endif
/* Forward declarations so the actual declarations can happen top-down. */
struct Animation;
struct AnimationLayer;
struct AnimationBinding;
struct AnimationStrip;
struct AnimationChannelBag;
/* ************************************************ */
/* F-Curve DataTypes */
@@ -1146,10 +1159,32 @@ typedef struct AnimData {
/** Runtime data, for depsgraph evaluation. */
FCurve **driver_array;
/**
* Active Animation data-block. If this is set, `action` and NLA-related
* properties should be ignored. Note that there is plenty of code in Blender
* that doesn't check this pointer yet.
*/
struct Animation *animation;
/**
* Identifier for which AnimationBinding of the above Animation is actually animating this
* data-block.
*
* Do not set this directly, use one of the assignment functions in ANIM_animation.hh instead.
*/
int32_t binding_handle;
/**
* Binding name, primarily used for mapping to the right binding when assigning
* another Animation data-block. Should be the same type as #AnimationBinding::name.
*
* \see AnimationBinding::name
*/
char binding_name[66];
uint8_t _pad0[6];
/* settings for animation evaluation */
/** User-defined settings. */
int flag;
char _pad[4];
/* settings for active action evaluation (based on NLA strip settings) */
/** Accumulation mode for active action. */
@@ -1208,3 +1243,183 @@ typedef struct IdAdtTemplate {
#define SELECT 1
/* ************************************************ */
/* Layered Animation data-types. */
/* Declarations of C++ wrappers. See ANIM_animation.hh for the actual classes. */
#ifdef __cplusplus
namespace blender::animrig {
class Animation;
class ChannelBag;
class KeyframeStrip;
class Layer;
class Binding;
class Strip;
} // namespace blender::animrig
#endif
/**
* Container of layered animation data.
*
* \see blender::animrig::Animation
*/
typedef struct Animation {
ID id;
struct AnimationLayer **layer_array; /* Array of 'layer_array_num' layers. */
int layer_array_num;
int layer_active_index; /* Index into layer_array, -1 means 'no active'. */
struct AnimationBinding **binding_array; /* Array of 'binding_array_num` bindings. */
int binding_array_num;
int32_t last_binding_handle;
#ifdef __cplusplus
blender::animrig::Animation &wrap();
const blender::animrig::Animation &wrap() const;
#endif
} Animation;
/**
* \see blender::animrig::Layer
*/
typedef struct AnimationLayer {
/** User-Visible identifier, unique within the Animation. */
char name[64]; /* MAX_NAME. */
float influence; /* [0-1] */
/** \see blender::animrig::Layer::flags() */
uint8_t layer_flags;
/** \see blender::animrig::Layer::mixmode() */
int8_t layer_mix_mode;
uint8_t _pad0[2];
/**
* There is always at least one strip.
* If there is only one, it can be infinite. This is the default for new layers.
*/
struct AnimationStrip **strip_array; /* Array of 'strip_array_num' strips. */
int strip_array_num;
uint8_t _pad1[4];
#ifdef __cplusplus
blender::animrig::Layer &wrap();
const blender::animrig::Layer &wrap() const;
#endif
} AnimationLayer;
/**
* \see blender::animrig::Binding
*/
typedef struct AnimationBinding {
/**
* Typically the ID name this binding was created for, including the two
* letters indicating the ID type.
*
* \see AnimData::binding_name
*/
char name[66]; /* MAX_ID_NAME */
uint8_t _pad0[2];
/**
* Type of ID-blocks that this binding can be assigned to.
* If 0, will be set to whatever ID is first assigned.
*/
int idtype;
/**
* Identifier of this Binding within the Animation data-block.
*
* This number allows reorganisation of the Animation::bindings_array without
* invalidating references. Also these remain valid when copy-on-evaluate
* copies are made.
*
* Only valid within the Animation data-block that owns this Binding.
*
* \see blender::animrig::Animation::binding_for_handle()
*/
int32_t handle;
#ifdef __cplusplus
blender::animrig::Binding &wrap();
const blender::animrig::Binding &wrap() const;
#endif
} AnimationBinding;
/**
* \see blender::animrig::Strip
*/
typedef struct AnimationStrip {
/**
* \see blender::animrig::Strip::type()
*/
int8_t strip_type;
uint8_t _pad0[3];
float frame_start; /** Start frame of the strip, in Animation time. */
float frame_end; /** End frame of the strip, in Animation time. */
/**
* Offset applied to the contents of the strip, in frames.
*
* This offset determines the difference between "Animation time" (which would
* typically be the same as the scene time, until the animation system
* supports strips referencing other Animation data-blocks).
*/
float frame_offset;
#ifdef __cplusplus
blender::animrig::Strip &wrap();
const blender::animrig::Strip &wrap() const;
#endif
} AnimationStrip;
/**
* AnimationStrip::type = Strip::Type::Keyframe.
*
* \see blender::animrig::KeyframeStrip
*/
typedef struct KeyframeAnimationStrip {
AnimationStrip strip;
struct AnimationChannelBag **channelbags_array;
int channelbags_array_num;
uint8_t _pad[4];
#ifdef __cplusplus
blender::animrig::KeyframeStrip &wrap();
const blender::animrig::KeyframeStrip &wrap() const;
#endif
} KeyframeAnimationStrip;
/**
* \see blender::animrig::ChannelBag
*/
typedef struct AnimationChannelBag {
int32_t binding_handle;
int fcurve_array_num;
FCurve **fcurve_array; /* Array of 'fcurve_array_num' FCurves. */
/* TODO: Design & implement a way to integrate other channel types as well,
* and still have them map to a certain binding */
#ifdef __cplusplus
blender::animrig::ChannelBag &wrap();
const blender::animrig::ChannelBag &wrap() const;
#endif
} ChannelBag;
#ifdef __cplusplus
/* Some static assertions that things that should have the same type actually do. */
static_assert(
std::is_same_v<decltype(AnimationBinding::handle), decltype(AnimData::binding_handle)>);
static_assert(
std::is_same_v<decltype(AnimationBinding::handle), decltype(Animation::last_binding_handle)>);
static_assert(std::is_same_v<decltype(AnimationBinding::handle),
decltype(AnimationChannelBag::binding_handle)>);
static_assert(std::is_same_v<decltype(AnimationBinding::name), decltype(AnimData::binding_name)>);
#endif

View File

@@ -75,6 +75,7 @@
#include "DNA_defaults.h"
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_asset_types.h"
#include "DNA_brush_types.h"
@@ -108,6 +109,7 @@
#include "DNA_volume_types.h"
#include "DNA_world_types.h"
#include "DNA_anim_defaults.h"
#include "DNA_armature_defaults.h"
#include "DNA_asset_defaults.h"
#include "DNA_brush_defaults.h"
@@ -142,6 +144,10 @@
#define SDNA_DEFAULT_DECL_STRUCT(struct_name) \
static const struct_name DNA_DEFAULT_##struct_name = _DNA_DEFAULT_##struct_name
/* DNA_anim_defaults.h */
SDNA_DEFAULT_DECL_STRUCT(AnimationLayer);
SDNA_DEFAULT_DECL_STRUCT(AnimationStrip);
/* DNA_asset_defaults.h */
SDNA_DEFAULT_DECL_STRUCT(AssetMetaData);
SDNA_DEFAULT_DECL_STRUCT(AssetLibraryReference);
@@ -381,6 +387,10 @@ extern const bTheme U_theme_default;
const void *DNA_default_table[SDNA_TYPE_MAX] = {
/* DNA_anim_defaults.h */
SDNA_DEFAULT_DECL(AnimationLayer),
SDNA_DEFAULT_DECL(AnimationStrip),
/* DNA_asset_defaults.h */
SDNA_DEFAULT_DECL(AssetMetaData),
SDNA_DEFAULT_DECL(AssetLibraryReference),

View File

@@ -34,6 +34,7 @@
*/
const EnumPropertyItem rna_enum_id_type_items[] = {
{ID_AC, "ACTION", ICON_ACTION, "Action", ""},
{ID_AN, "ANIMATION", ICON_ACTION, "Animation", ""}, /* TODO: give Animation its own icon. */
{ID_AR, "ARMATURE", ICON_ARMATURE_DATA, "Armature", ""},
{ID_BR, "BRUSH", ICON_BRUSH_DATA, "Brush", ""},
{ID_CF, "CACHEFILE", ICON_FILE, "Cache File", ""},
@@ -498,6 +499,8 @@ StructRNA *ID_code_to_RNA_type(short idcode)
switch ((ID_Type)idcode) {
case ID_AC:
return &RNA_Action;
case ID_AN:
break;
case ID_AR:
return &RNA_Armature;
case ID_BR:
@@ -1057,7 +1060,8 @@ static void rna_ID_update_tag(ID *id, Main *bmain, ReportList *reports, int flag
allow_flag = OB_RECALC_ALL | PSYS_RECALC;
break;
# endif
case ID_AC:
case ID_AC: /* Fallthrough. */
case ID_AN:
allow_flag = ID_RECALC_ANIMATION;
break;
default: