Anim: DNA for Animation data-block #119077

Merged
Sybren A. Stüvel merged 1 commits from dr.sybren/blender:pr/baklava-data-model-1-dna into main 2024-03-07 16:41:34 +01:00
27 changed files with 1136 additions and 3 deletions

View File

@ -0,0 +1,297 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
dr.sybren marked this conversation as resolved Outdated

I assume the year should be 2024

I assume the year should be 2024
*
* 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;
dr.sybren marked this conversation as resolved Outdated

Only used by the ID datablock management

Does it need to be exposed in the class then? That combination seems a bit misleading. Just putting the code in animation_copy_data is fine maybe.

> Only used by the ID datablock management Does it need to be exposed in the class then? That combination seems a bit misleading. Just putting the code in `animation_copy_data` is fine maybe.
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");
dr.sybren marked this conversation as resolved Outdated

Functional style cast is preferred for enum types as well.

Functional style cast is preferred for enum types as well.
/**
* 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 {
dr.sybren marked this conversation as resolved Outdated

why can layers be copied using the copy constructor but not Animation or Strip

why can layers be copied using the copy constructor but not `Animation` or `Strip`

Animation: it is an ID, and so Blender has a bunch of other logic to duplicate those already.

Strip: because of the whacky nature of the inheritance:

  • C-style inheritance: KeyframeAnimationStrip "inherits" AnimationStrip" by embedding the latter. This means that any KeyframeAnimationStrip*can be reinterpreted asAnimationStrip*`.
  • C++-style inheritance: the C++ wrappers inherit the DNA structs, so animrig::Strip inherits ::AnimationStrip, and animrig::KeyframeStrip inherits ::KeyframeAnimationStrip.

Because of this, MEM_new<Strip>(__func__, someKeyframeAnimationStrip) is going to allocate just enough space fo a Strip, and then you have something that the rest of Blender simply won't expect. It'll crash once you set that strip's strip_type property to Strip::Type::Keyframe as it simply won't have enough memory allocated to hold a KeyframeStrip.

`Animation`: it is an `ID`, and so Blender has a bunch of other logic to duplicate those already. `Strip`: because of the whacky nature of the inheritance: - 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`. Because of this, `MEM_new<Strip>(__func__, someKeyframeAnimationStrip)` is going to allocate just enough space fo a `Strip`, and then you have something that the rest of Blender simply won't expect. It'll crash once you set that strip's `strip_type` property to `Strip::Type::Keyframe` as it simply won't have enough memory allocated to hold a `KeyframeStrip`.

Ah ok so ideally we allow the copy constructor but due to weird interactions between C and C++ its not always possible.
make sense thanks :)

Ah ok so ideally we allow the copy constructor but due to weird interactions between C and C++ its not always possible. make sense thanks :)
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);
}
dr.sybren marked this conversation as resolved Outdated

what does Offset do. Is that a time offset?
Or is that what Combine does in the NLA currently

what does `Offset` do. Is that a time offset? Or is that what `Combine` does in the NLA currently

I've added some documentation. Yeah, it does what 'combine' does in the NLA. @nathanvegdahl pointed out that basically all options are some form of "combining" in a way (including "Replace" with a <100% influence), and we figured "Offset" would be clearer (as in, a rotational offset, translational offset, etc.). I hope the comments I added clarify this.

I've added some documentation. Yeah, it does what 'combine' does in the NLA. @nathanvegdahl pointed out that basically all options are some form of "combining" in a way (including "Replace" with a <100% influence), and we figured "Offset" would be clearer (as in, a rotational offset, translational offset, etc.). I hope the comments I added clarify this.
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");
dr.sybren marked this conversation as resolved Outdated

I know you've changed the name based on feedback, but reading through the code it wasn't entirely clear to me what a bag is.
Just to clarify: A bag holds the FCurves for a specific output, right.
In that case mentioning output in the name would communicate the purpose better. e.g. OutputStream (hehe because animation channels combine into a stream...)
But given that we are going to change the term output anyway, I am not considering this a showstopper. Just something to keep in mind for later

I know you've changed the name based on feedback, but reading through the code it wasn't entirely clear to me what a bag is. Just to clarify: A bag holds the FCurves for a specific output, right. In that case mentioning output in the name would communicate the purpose better. e.g. `OutputStream` (hehe because animation channels combine into a stream...) But given that we are going to change the term output anyway, I am not considering this a showstopper. Just something to keep in mind for later

We intentionally removed the "for output" part of the name, as that caused more confusion when it came to naming related functions (like KeyframeStrip::channelbag_for_output, which returns the ChannelBag for a specific output). I'll at least rephrase the docstring so that it doesn't repeat the word "bag".

We intentionally removed the "for output" part of the name, as that caused more confusion when it came to naming related functions (like `KeyframeStrip::channelbag_for_output`, which returns the `ChannelBag` for a specific output). I'll at least rephrase the docstring so that it doesn't repeat the word "bag".

I think my gripe with ChannelBag is that it implies that it just holds the data, while in reality it knows which output it is for

I think my gripe with `ChannelBag` is that it implies that it just holds the data, while in reality it knows which output it is for

I agree. It has to be DNA-compatible, though, so that means either we store the key (output handle) with the value (channelbag), or we construct a different way to store the mapping. Ideally we'd use a std::map<output_handle_t, ChannelBag *>, but that's just not an option now.

I agree. It has to be DNA-compatible, though, so that means either we store the key (output handle) with the value (channelbag), or we construct a different way to store the mapping. Ideally we'd use a `std::map<output_handle_t, ChannelBag *>`, but that's just not an option now.
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()
{
dr.sybren marked this conversation as resolved Outdated

type() -> this->type()

`type()` -> `this->type()`
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"
dr.sybren marked this conversation as resolved Outdated

Unused includes here I think

Unused includes here I think
#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;
dr.sybren marked this conversation as resolved Outdated

Instead of using namespace blender, you could put all the code except for BKE_animation_add and IDType_ID_AN in the blender::bke namespace.

Instead of `using namespace blender`, you could put all the code except for `BKE_animation_add` and `IDType_ID_AN` in the `blender::bke` namespace.
namespace blender::bke {
static void animation_copy_data(Main * /*bmain*/,
dr.sybren marked this conversation as resolved Outdated

I'd rather not use the term 'deep copy' for this callback.

In ID management area, 'deep copy' is for when you duplicate an ID and all of its ID dependencies.

'Copy' is when you only duplicate the ID, while keeping it linked to the same ID dependencies as the source.

Shallow copy is when you only copy the ID struct itself, but none of its owned/managed sub-data (e.g. what we do when writing a blendfile).

I'd rather not use the term 'deep copy' for this callback. In ID management area, 'deep copy' is for when you duplicate an ID _and all of its ID dependencies_. 'Copy' is when you only duplicate the ID, while keeping it linked to the same ID dependencies as the source. `Shallow copy` is when you only copy the ID struct itself, but none of its owned/managed sub-data (e.g. what we do when writing a blendfile).
std::optional<Library *> /*owner_library*/,
dr.sybren marked this conversation as resolved Outdated

This actually will have to be updated, a681f5d896 from yesterday changed the signature of these callbacks.

This actually will have to be updated, a681f5d896 from yesterday changed the signature of these callbacks.

Thanks for the warning. I'll address that when landing the PR. If I were to rebase now, the 'show changes' button (on a force-push) would include all the changes on main as well, making it very hard to follow.

Thanks for the warning. I'll address that when landing the PR. If I were to rebase now, the 'show changes' button (on a force-push) would include all the changes on `main` as well, making it very hard to follow.
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;
dr.sybren marked this conversation as resolved Outdated

Use C++ reinterpret_cast

Use C++ `reinterpret_cast`
/* 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]);
dr.sybren marked this conversation as resolved Outdated

in all other occurrences of IDTypeInfo that I checked this was written with the N_ macro, e.g. N_("animations")
If I read the macro right this is for translation purposes. Not sure if this is still required.

For context: I found this because I was looking for the reason why in the outliner the category is named "animations", lowercase, instead of "Animations" which would be consistent with the other categories. Adding N_ does NOT fix that though

in all other occurrences of `IDTypeInfo` that I checked this was written with the `N_` macro, e.g. `N_("animations")` If I read the macro right this is for translation purposes. Not sure if this is still required. For context: I found this because I was looking for the reason why in the outliner the category is named "animations", lowercase, instead of "Animations" which would be consistent with the other categories. Adding `N_` does NOT fix that though

Good catch. I copied the Action IDTypeinfo, and that didn't have the N_() either. I'll add it to Animation.

Good catch. I copied the `Action` IDTypeinfo, and that didn't have the `N_()` either. I'll add it to `Animation`.
}
}
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
*/
dr.sybren marked this conversation as resolved Outdated

Would add a comment about why 66 and not 64 here (i.e. MAX_ID_NAME instead of MAX_NAME).

Does it actually have to be same as AnimationOutput::name?

Would add a comment about why `66` and not `64` here (i.e. `MAX_ID_NAME` instead of `MAX_NAME`). Does it actually have to be same as `AnimationOutput::name`?

Yes, this has to be the same as AnimationOutput::name. This is enforced in the static assert at the bottom of the file:

static_assert(std::is_same_v<decltype(AnimationBinding::name), decltype(AnimData::binding_name)>);
Yes, this has to be the same as `AnimationOutput::name`. This is enforced in the static assert at the bottom of the file: ```cpp static_assert(std::is_same_v<decltype(AnimationBinding::name), decltype(AnimData::binding_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 {
dr.sybren marked this conversation as resolved Outdated

Just reference MAX_NAME instead of MAX_ID_NAME ?

Just reference `MAX_NAME` instead of `MAX_ID_NAME` ?
/** 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;
dr.sybren marked this conversation as resolved Outdated

I am curious why this is an array of pointers instead of just an FCurve array.
My first thought is that the bag doesn't own the data, but then who owns it. I didn't see any other new datastructure holding FCurves.

I am curious why this is an array of pointers instead of just an `FCurve` array. My first thought is that the bag doesn't own the data, but then who owns it. I didn't see any other new datastructure holding FCurves.

Good point, I think this can be changed to an array of FCurves.

Good point, I think this can be changed to an array of FCurves.

On second thought, it's better to keep these as pointers. This means that the array itself can be reallocated or reshuffled, while keeping the FCurves themselves untouched. This also means that references from Python remain valid, and that reorganising things is a bit more efficient.

On second thought, it's better to keep these as pointers. This means that the array itself can be reallocated or reshuffled, while keeping the FCurves themselves untouched. This also means that references from Python remain valid, and that reorganising things is a bit more efficient.

makes sense, thanks for clarifying

makes sense, thanks for clarifying
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: