WIP: Anim: Implement keyframing functionality for the Animation data-block #119669

Closed
Christoph Lendenfeld wants to merge 19 commits from ChrisLend/blender:baklava_keyframing into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
55 changed files with 4039 additions and 239 deletions

View File

@ -61,6 +61,7 @@ _modules = [
"properties_texture",
"properties_world",
"properties_collection",
"temp_anim_layers",
"generic_ui_list",
# Generic Space Modules
@ -121,6 +122,7 @@ def register():
register_class(cls)
space_filebrowser.register_props()
temp_anim_layers.register_props()
from bpy.props import (
EnumProperty,

View File

@ -2696,6 +2696,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_new_matrix_socket"}, ("blender/blender/issues/116067", "Matrix Socket")),
({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")),
({"property": "use_extension_repos"}, ("/blender/blender/issues/117286", "#117286")),
({"property": "use_animation_baklava"}, ("/blender/blender/pulls/114098", "#114098")),
),
)

View File

@ -0,0 +1,120 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""NOTE: this is temporary UI code to show animation layers.
It is not meant for any particular use, just to have *something* in the UI.
"""
import threading
import bpy
from bpy.types import Context, Panel, WindowManager
from bpy.props import PointerProperty
class VIEW3D_PT_animation_layers(Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Animation"
bl_label = "Baklava"
@classmethod
def poll(cls, context: Context) -> bool:
return context.preferences.experimental.use_animation_baklava and context.object
def draw(self, context: Context) -> None:
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
# FIXME: this should be done in response to a messagebus callback, notifier, whatnot.
adt = context.object.animation_data
with _wm_selected_animation_lock:
if adt:
context.window_manager.selected_animation = adt.animation
else:
context.window_manager.selected_animation = None
col = layout.column()
# This has to go via an auxillary property, as assigning an Animation
# data-block should be possible even when `context.object.animation_data`
# is `None`, and thus its `animation` property does not exist.
col.template_ID(context.window_manager, 'selected_animation')
col = layout.column(align=True)
anim = adt and adt.animation
if anim:
col.prop(adt, 'animation_binding_handle', text="Binding")
binding = [o for o in anim.bindings if o.handle == adt.animation_binding_handle]
if binding:
col.prop(binding[0], 'name', text="Anim Binding Name")
else:
col.label(text="AN Binding Name: -")
if adt:
col.prop(adt, 'animation_binding_name', text="ADT Binding Name")
else:
col.label(text="ADT Binding Name: -")
layout.separator()
if not anim:
layout.label(text="No layers")
return
for layer_idx, layer in reversed(list(enumerate(anim.layers))):
layerbox = layout.box()
col = layerbox.column(align=True)
col.prop(layer, "name", text=f"Layer {layer_idx+1}:")
col.prop(layer, "influence")
col.prop(layer, "mix_mode")
classes = (
VIEW3D_PT_animation_layers,
)
_wm_selected_animation_lock = threading.Lock()
def _wm_selected_animation_update(self: WindowManager, context: Context) -> None:
# Avoid responding to changes written by the panel above.
lock_ok = _wm_selected_animation_lock.acquire(blocking=False)
if not lock_ok:
return
try:
if self.selected_animation is None and context.object.animation_data is None:
return
adt = context.object.animation_data_create()
if adt.animation == self.selected_animation:
# Avoid writing to the property when the new value hasn't changed.
return
adt.animation = self.selected_animation
finally:
_wm_selected_animation_lock.release()
def register_props() -> None:
# Put behind a `try` because it won't exist when Blender is built without
# experimental features.
try:
from bpy.types import Animation
except ImportError:
return
# Due to this hackyness, the WindowManager will increase the user count of
# the pointed-to Animation data-block.
WindowManager.selected_animation = PointerProperty(
type=Animation,
name="Animation",
description="Animation assigned to the active Object",
update=_wm_selected_animation_update,
)
if __name__ == "__main__": # only for live edit.
register_, _ = bpy.utils.register_classes_factory(classes)
register_()
register_props()

View File

@ -62,14 +62,92 @@ 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_propagate
*/
void binding_name_set(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_set(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();
/** Assign this animation to the ID.
*
* \param binding The binding this ID should be animated by, may be nullptr if it is to be
* assigned later. In that case, the ID will not actually receive any animation. \param
* animated_id The ID that should be animated by this Animation data-block.
*/
bool assign_id(Binding *binding, ID &animated_id);
void unassign_id(ID &animated_id);
/**
* Find the binding that best matches the animated ID.
*
* If the ID is already animated by this Animation, by matching this
* Animation's bindings with (in order):
*
* - `animated_id.adt->binding_handle`,
* - `animated_id.adt->binding_name`,
* - `animated_id.name`.
*
* Note that this different from #binding_for_id, which does not use the
* binding name, and only works when this Animation is already assigned. */
Binding *find_suitable_binding_for(const ID &animated_id);
/** Free all data in the `Animation`. Doesn't delete the `Animation` itself. */
void free_data();
/* Flags access. */
enum class Flag : uint8_t { Expanded = (1 << 0) };
bool is_expanded()
{
return this->flag & uint8_t(Flag::Expanded);
}
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");
@ -82,7 +160,17 @@ static_assert(sizeof(Animation) == sizeof(::Animation),
*/
class Strip : public ::AnimationStrip {
public:
Strip() = default;
/**
* Strip instances should not be created via this constructor. Create a sub-class like
* #KeyframeStrip instead.
*
* The reason is that various functions will assume that the `Strip` is actually a down-cast
* instance of another strip class, and that `Strip::type()` will say which type. To avoid having
* to explcitly deal with an 'invalid' type everywhere, creating a `Strip` directly is simply not
* allowed.
*/
Strip() = delete;
/**
* Strip cannot be duplicated via the copy constructor. Either use a concrete
* strip type's copy constructor, or use Strip::duplicate().
@ -117,6 +205,18 @@ class Strip : public ::AnimationStrip {
template<typename T> bool is() const;
template<typename T> T &as();
template<typename T> const T &as() const;
bool contains_frame(float frame_time) const;
bool is_last_frame(float frame_time) const;
/**
* Set the start and end frame.
*
* Note that this does not do anything else. There is no check whether the
* frame numbers are valid (i.e. frame_start <= frame_end). Infinite values
* (negative for frame_start, positive for frame_end) are supported.
*/
void resize(float frame_start, float frame_end);
};
static_assert(sizeof(Strip) == sizeof(::AnimationStrip),
"DNA struct and its C++ wrapper must have the same size");
@ -172,6 +272,20 @@ 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 +309,27 @@ class Binding : public ::AnimationBinding {
Binding() = default;
Binding(const Binding &other) = default;
~Binding() = default;
/**
* Let the given ID receive animation from this binding.
*
* This is a low-level function; for most purposes you want
* #Animation::assign_id instead.
*
* \note This does _not_ set animated_id->adt->animation to the owner of this
* Binding. It's the caller's responsibility to do that.
*
* \return Whether this was possible. If the Binding was already bound to a
* specific ID type, and `animated_id` is of a different type, it will be
* refused. If the ID type cannot be animated at all, false is also returned.
*
* \see assign_animation
* \see Animation::assign_id
*/
bool connect_id(ID &animated_id);
/** 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 +348,42 @@ 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);
/**
* Find an FCurve for this binding + RNA path + array index combination.
*
* If it cannot be found, `nullptr` is returned.
*/
FCurve *fcurve_find(const Binding &binding, StringRefNull rna_path, int array_index);
/**
* Find an FCurve for this binding + RNA path + array index combination.
*
* If it cannot be found, a new one is created.
*/
FCurve &fcurve_find_or_create(const Binding &binding, StringRefNull rna_path, int array_index);
FCurve *keyframe_insert(const Binding &binding,
StringRefNull rna_path,
int array_index,
float2 time_value,
const KeyframeSettings &settings);
};
static_assert(sizeof(KeyframeStrip) == sizeof(::KeyframeAnimationStrip),
"DNA struct and its C++ wrapper must have the same size");
@ -234,10 +405,54 @@ class ChannelBag : public ::AnimationChannelBag {
blender::MutableSpan<FCurve *> fcurves();
const FCurve *fcurve(int64_t index) const;
FCurve *fcurve(int64_t index);
const FCurve *fcurve_find(const StringRefNull rna_path, const int array_index) const;
};
static_assert(sizeof(ChannelBag) == sizeof(::AnimationChannelBag),
"DNA struct and its C++ wrapper must have the same size");
/**
* Assign the animation to the ID.
*
* This will will make a best-effort guess as to which binding to use, in this
* order;
*
* - By stable index.
* - By fallback string.
* - By the ID's name (matching agains the binding name).
* - If the above do not find a suitable binding, the animated ID will not
* receive any animation and the calller is responsible for creating an binding
* and assigning it.
*
* \return `false` if the assignment was not possible (for example the ID is of a type that cannot
* be animated). If the above fall-through case of "no binding found" is reached, this function
* will still return `true` as the Animation was succesfully assigned.
*/
bool assign_animation(Animation &anim, ID &animated_id);
/**
* Ensure that this ID is no longer animated.
*/
void unassign_animation(ID &animated_id);
/**
* Return the Animation of this ID, or nullptr if it has none.
*/
Animation *get_animation(ID &animated_id);
/**
* Return the F-Curves for this specific binding handle.
*
* This is just a utility function, that's intended to become obsolete when multi-layer animation
* is introduced. However, since Blender currently only supports a single layer with a single
* strip, of a single type, this function can be used.
*
* The use of this function is also an indicator for code that will have to be altered when
* multi-layered animation is getting implemented.
*/
Span<FCurve *> fcurves_for_animation(Animation &anim, binding_handle_t binding_handle);
Span<const FCurve *> fcurves_for_animation(const Animation &anim, binding_handle_t binding_handle);
} // namespace blender::animrig
/* Wrap functions for the DNA structs. */

View File

@ -10,6 +10,8 @@
#pragma once
#include "BLI_string_ref.hh"
struct ID;
struct Main;
@ -20,12 +22,19 @@ struct bAction;
namespace blender::animrig {
class Animation;
/**
* Get (or add relevant data to be able to do so) the Active Action for the given
* Animation Data block, given an ID block where the Animation Data should reside.
*/
bAction *id_action_ensure(Main *bmain, ID *id);
/**
* Get or create the animation datablock for the given ID. Doesn NOT create a binding.
*/
Animation *id_animation_ensure(Main *bmain, ID *id);
/**
* Delete the F-Curve from the given AnimData block (if possible),
* as appropriate according to animation context.
@ -44,4 +53,27 @@ void reevaluate_fcurve_errors(bAnimContext *ac);
*/
bool animdata_remove_empty_action(AnimData *adt);
/**
* Compatibility helper function for `BKE_animadata_fcurve_find_by_rna_path()`.
*
* Searches each layer (top to bottom) to find an FCurve that matches the given
* RNA path & index.
*
* \see BKE_animadata_fcurve_find_by_rna_path
*
* \note The returned FCurve should NOT be used for keyframe manipulation. Its
* existence is an indicator for "this property is animated".
*
* This function should probably be limited to the active layer (for the given
* property, once pinning to layers is there), so that the "this is keyed" color
* is more accurate.
*
* Again, this is just to hook up the new Animation data-block to the old
* Blender UI code.
*/
const FCurve *fcurve_find_by_rna_path(const Animation &anim,
const ID &animated_id,
StringRefNull rna_path,
int array_index);
} // namespace blender::animrig

View File

@ -0,0 +1,35 @@
/* SPDX-FileCopyrightText: 2023 Blender Developers
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief Animation data-block evaluation.
*/
#pragma once
#include "DNA_anim_types.h"
#include "ANIM_animation.hh"
struct AnimationEvalContext;
struct PointerRNA;
namespace blender::animrig {
/**
* Top level animation evaluation function.
*
* Animate the given ID, using the animation data-block and the given binding.
*
* \param flush_to_original when true, look up the original data-block (assuming
* the given one is an evaluated copy) and update that too.
*/
void evaluate_and_apply_animation(PointerRNA &animated_id_ptr,
Animation &animation,
binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context,
bool flush_to_original);
} // namespace blender::animrig

View File

@ -10,7 +10,10 @@
#pragma once
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "DNA_anim_types.h"
struct AnimData;
struct FCurve;
@ -31,6 +34,11 @@ struct KeyframeSettings {
*/
KeyframeSettings get_keyframe_settings(bool from_userprefs);
/**
* Create an fcurve for a specific channel, pre-set-up with default flags and interpolation mode.
*/
FCurve *create_fcurve_for_channel(StringRef rna_path, int array_index);
/** Initialize the given BezTriple with default values. */
void initialize_bezt(BezTriple *beztr,
float2 position,

View File

@ -27,6 +27,7 @@ set(SRC
intern/bone_collections.cc
intern/bonecolor.cc
intern/driver.cc
intern/evaluation.cc
intern/fcurve.cc
intern/keyframing.cc
intern/keyframing_auto.cc
@ -39,11 +40,13 @@ set(SRC
ANIM_bone_collections.hh
ANIM_bonecolor.hh
ANIM_driver.hh
ANIM_evaluation.hh
ANIM_fcurve.hh
ANIM_keyframing.hh
ANIM_rna.hh
ANIM_visualkey.hh
intern/bone_collections_internal.hh
intern/evaluation_internal.hh
)
set(LIB
@ -54,6 +57,7 @@ set(LIB
PRIVATE bf_editor_interface
PRIVATE bf::intern::guardedalloc
PRIVATE bf::intern::atomic
PRIVATE bf::intern::clog
)
@ -64,7 +68,9 @@ if(WITH_GTESTS)
set(TEST_INC
)
set(TEST_SRC
intern/animation_test.cc
intern/bone_collections_test.cc
intern/evaluation_test.cc
)
set(TEST_LIB
PRIVATE bf::animrig

View File

@ -7,6 +7,7 @@
*/
#include "ANIM_action.hh"
#include "ANIM_fcurve.hh"
#include "BKE_action.h"
#include "BKE_fcurve.hh"
#include "BLI_listbase.h"
@ -49,17 +50,12 @@ FCurve *action_fcurve_ensure(Main *bmain,
return fcu;
}
fcu = BKE_fcurve_create();
fcu = create_fcurve_for_channel(rna_path, array_index);
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->auto_smoothing = U.auto_smoothing_new;
if (BLI_listbase_is_empty(&act->curves)) {
fcu->flag |= FCURVE_ACTIVE;
}
fcu->rna_path = BLI_strdup(rna_path);
fcu->array_index = array_index;
if (U.keying_flag & KEYING_FLAG_XYZ2RGB && ptr != nullptr) {
/* For Loc/Rot/Scale and also Color F-Curves, the color of the F-Curve in the Graph Editor,
* is determined by the array index for the F-Curve.

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,67 @@
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;
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. It also has a shrink_array()
* function, if we ever need one (we will).
* 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);
if (*array != nullptr) {
MEM_freeN(*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 +116,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 +181,169 @@ 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(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 32-bit 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;
}
Binding *Animation::find_suitable_binding_for(const ID &animated_id)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
/* The stable index is only valid when this animation has already been
* assigned. Otherwise it's meaningless. */
if (adt && adt->animation == this) {
Binding *binding = this->binding_for_handle(adt->binding_handle);
if (binding && binding->is_suitable_for(animated_id)) {
return binding;
}
}
/* Try the binding name from the AnimData, if it is set,*/
if (adt && adt->binding_name[0]) {
Binding *binding = this->binding_find_by_name(adt->binding_name);
if (binding && binding->is_suitable_for(animated_id)) {
return binding;
}
}
/* As a last resort, search for the ID name. */
Binding *binding = this->binding_find_by_name(animated_id.name);
if (binding && binding->is_suitable_for(animated_id)) {
return binding;
}
return nullptr;
}
void Animation::free_data()
{
/* Free layers. */
@ -90,6 +361,62 @@ void Animation::free_data()
this->binding_array_num = 0;
}
bool Animation::assign_id(Binding *binding, ID &animated_id)
{
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
if (!adt) {
return false;
}
if (adt->animation) {
/* Unassign the ID from its existing animation first, or use the top-level
* function `assign_animation(anim, ID)`. */
return false;
}
if (binding) {
if (!binding->connect_id(animated_id)) {
return false;
}
/* If the binding is not yet named, use the ID name. */
if (binding->name[0] == '\0') {
this->binding_name_set(*binding, animated_id.name);
}
/* Always make sure the ID's binding name matches the assigned binding. */
STRNCPY_UTF8(adt->binding_name, binding->name);
}
else {
adt->binding_handle = 0;
/* Keep adt->binding_name untouched, as A) it's not necessary to erase it
* because `adt->binding_handle = 0` already indicates "no binding yet",
* and B) it would erase information that can later be used when trying to
* identify which binding this was once attached to. */
}
adt->animation = this;
id_us_plus(&this->id);
return true;
}
void Animation::unassign_id(ID &animated_id)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
BLI_assert_msg(adt->animation == this, "ID is not assigned to this Animation");
/* Before unassigning, make sure that the stored Binding name is up to date.
* If Blender would be bug-free, and we could assume that `Animation::binding_name_propagate()`
* would always be called when appropriate, this code could be removed. */
const Binding *binding = this->binding_for_handle(adt->binding_handle);
if (binding) {
STRNCPY_UTF8(adt->binding_name, binding->name);
}
id_us_min(&this->id);
adt->animation = nullptr;
}
/* ----- AnimationLayer implementation ----------- */
Layer::Layer(const Layer &other)
@ -131,7 +458,101 @@ 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::connect_id(ID &animated_id)
{
if (!this->is_suitable_for(animated_id)) {
return false;
}
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
if (!adt) {
return false;
}
if (this->idtype == 0) {
this->idtype = GS(animated_id.name);
}
adt->binding_handle = this->handle;
return true;
}
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;
}
bool assign_animation(Animation &anim, ID &animated_id)
{
unassign_animation(animated_id);
Binding *binding = anim.find_suitable_binding_for(animated_id);
return anim.assign_id(binding, animated_id);
}
void unassign_animation(ID &animated_id)
{
Animation *anim = get_animation(animated_id);
if (!anim) {
return;
}
anim->unassign_id(animated_id);
}
Animation *get_animation(ID &animated_id)
{
AnimData *adt = BKE_animdata_from_id(&animated_id);
if (!adt) {
return nullptr;
}
if (!adt->animation) {
return nullptr;
}
return &adt->animation->wrap();
}
/* ----- AnimationStrip implementation ----------- */
@ -158,6 +579,29 @@ Strip::~Strip()
BLI_assert_unreachable();
}
bool Strip::contains_frame(const float frame_time) const
{
return this->frame_start <= frame_time && frame_time <= this->frame_end;
}
bool Strip::is_last_frame(const float frame_time) const
{
/* Maybe this needs a more advanced equality check. Implement that when
* we have an actual example case that breaks. */
return this->frame_end == frame_time;
}
void Strip::resize(const float frame_start, const float frame_end)
{
BLI_assert(frame_start <= frame_end);
BLI_assert_msg(frame_start < std::numeric_limits<float>::infinity(),
"only the end frame can be at positive infinity");
BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
"only the start frame can be at negative infinity");
this->frame_start = frame_start;
this->frame_end = frame_end;
}
/* ----- KeyframeAnimationStrip implementation ----------- */
KeyframeStrip::KeyframeStrip(const KeyframeStrip &other)
@ -216,6 +660,123 @@ 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;
}
FCurve *KeyframeStrip::fcurve_find(const Binding &binding,
const StringRefNull rna_path,
const int array_index)
{
ChannelBag *channels = this->channelbag_for_binding(binding);
if (channels == nullptr) {
return nullptr;
}
/* Copy of the logic in BKE_fcurve_find(), but then compatible with our array-of-FCurves
* instead of ListBase. */
for (FCurve *fcu : channels->fcurves()) {
/* Check indices first, much cheaper than a string comparison. */
/* Simple string-compare (this assumes that they have the same root...) */
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
return fcu;
}
}
return nullptr;
}
FCurve &KeyframeStrip::fcurve_find_or_create(const Binding &binding,
const StringRefNull rna_path,
const int array_index)
{
if (FCurve *existing_fcurve = this->fcurve_find(binding, rna_path, array_index)) {
return *existing_fcurve;
}
FCurve *new_fcurve = create_fcurve_for_channel(rna_path.c_str(), array_index);
ChannelBag *channels = this->channelbag_for_binding(binding);
if (channels == nullptr) {
channels = &this->channelbag_for_binding_add(binding);
}
if (channels->fcurve_array_num == 0) {
new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
}
grow_array_and_append(&channels->fcurve_array, &channels->fcurve_array_num, new_fcurve);
return *new_fcurve;
}
FCurve *KeyframeStrip::keyframe_insert(const Binding &binding,
const StringRefNull rna_path,
const int array_index,
const float2 time_value,
const KeyframeSettings &settings)
{
FCurve &fcurve = this->fcurve_find_or_create(binding, rna_path, array_index);
if (!BKE_fcurve_is_keyframable(&fcurve)) {
/* TODO: handle this properly, in a way that can be communicated to the user. */
std::fprintf(stderr,
"FCurve %s[%d] for binding %s doesn't allow inserting keys.\n",
rna_path.c_str(),
array_index,
binding.name);
return nullptr;
}
/* TODO: Handle the eInsertKeyFlags. */
const int index = insert_vert_fcurve(&fcurve, time_value, settings, eInsertKeyFlags(0));
if (index < 0) {
std::fprintf(stderr,
"Could not insert key into FCurve %s[%d] for binding %s.\n",
rna_path.c_str(),
array_index,
binding.name);
return nullptr;
}
return &fcurve;
}
/* AnimationChannelBag implementation. */
@ -257,4 +818,49 @@ FCurve *ChannelBag::fcurve(const int64_t index)
return this->fcurve_array[index];
}
const FCurve *ChannelBag::fcurve_find(const StringRefNull rna_path, const int array_index) const
{
for (const FCurve *fcu : this->fcurves()) {
/* Check indices first, much cheaper than a string comparison. */
if (fcu->array_index == array_index && fcu->rna_path && StringRef(fcu->rna_path) == rna_path) {
return fcu;
}
}
return nullptr;
}
Span<FCurve *> fcurves_for_animation(Animation &anim, const binding_handle_t binding_handle)
{
const Span<const FCurve *> fcurves = fcurves_for_animation(const_cast<const Animation &>(anim),
binding_handle);
FCurve **first = const_cast<FCurve **>(fcurves.data());
return Span<FCurve *>(first, fcurves.size());
}
Span<const FCurve *> fcurves_for_animation(const Animation &anim,
const binding_handle_t binding_handle)
{
if (binding_handle == 0) {
return {};
}
for (const animrig::Layer *layer : anim.layers()) {
for (const animrig::Strip *strip : layer->strips()) {
switch (strip->type()) {
case animrig::Strip::Type::Keyframe: {
const animrig::KeyframeStrip &key_strip = strip->as<animrig::KeyframeStrip>();
const animrig::ChannelBag *bag = key_strip.channelbag_for_binding(binding_handle);
if (!bag) {
continue;
}
return bag->fcurves();
}
}
}
}
return {};
}
} // namespace blender::animrig

View File

@ -0,0 +1,403 @@
/* 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_id_free(bmain, &cube->id);
BKE_id_free(bmain, &suzanne->id);
BKE_id_free(bmain, &anim->id);
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.";
/* Add some keys to check that also the strip data is freed correctly. */
const KeyframeSettings settings = get_keyframe_settings(false);
Binding &binding = anim->binding_add();
strip.as<KeyframeStrip>().keyframe_insert(binding, "location", 0, {1.0f, 47.0f}, settings);
another_strip.as<KeyframeStrip>().keyframe_insert(
binding, "location", 0, {1.0f, 47.0f}, settings);
}
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);
/* Add some keys to check that also the strip data is freed correctly. */
const KeyframeSettings settings = get_keyframe_settings(false);
Binding &binding = anim->binding_add();
strip0.as<KeyframeStrip>().keyframe_insert(binding, "location", 0, {1.0f, 47.0f}, settings);
strip1.as<KeyframeStrip>().keyframe_insert(binding, "location", 0, {1.0f, 47.0f}, settings);
strip2.as<KeyframeStrip>().keyframe_insert(binding, "location", 0, {1.0f, 47.0f}, settings);
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);
EXPECT_TRUE(binding.connect_id(cube->id));
EXPECT_STREQ("", binding.name)
<< "This low-level assignment function should not manipulate the Binding name";
EXPECT_EQ(GS(cube->id.name), binding.idtype);
}
TEST_F(AnimationLayersTest, add_binding_multiple)
{
Binding &out_cube = anim->binding_add();
Binding &out_suzanne = anim->binding_add();
EXPECT_TRUE(out_cube.connect_id(cube->id));
EXPECT_TRUE(out_suzanne.connect_id(suzanne->id));
EXPECT_EQ(2, anim->last_binding_handle);
EXPECT_EQ(1, out_cube.handle);
EXPECT_EQ(2, out_suzanne.handle);
}
TEST_F(AnimationLayersTest, anim_assign_id)
{
/* Assign to the only, 'virgin' Binding, should always work. */
Binding &out_cube = anim->binding_add();
ASSERT_TRUE(anim->assign_id(&out_cube, cube->id));
EXPECT_EQ(out_cube.handle, cube->adt->binding_handle);
EXPECT_STREQ(out_cube.name, cube->id.name)
<< "The binding should be named after the assigned ID";
EXPECT_STREQ(out_cube.name, cube->adt->binding_name)
<< "The binding name should be copied to the adt";
/* Assign another ID to the same Binding. */
ASSERT_TRUE(anim->assign_id(&out_cube, suzanne->id));
EXPECT_STREQ(out_cube.name, cube->id.name)
<< "The binding should not be renamed on assignment once it has a name";
EXPECT_STREQ(out_cube.name, cube->adt->binding_name)
<< "The binding name should be copied to the adt";
/* Assign Cube to another binding without unassigning first. */
Binding &another_out_cube = anim->binding_add();
ASSERT_FALSE(anim->assign_id(&another_out_cube, cube->id))
<< "Assigning animation (with this function) when already assigned should fail.";
/* Assign Cube to another 'virgin' binding. This should not cause a name
* collision between the Bindings. */
anim->unassign_id(cube->id);
ASSERT_TRUE(anim->assign_id(&another_out_cube, cube->id));
EXPECT_EQ(another_out_cube.handle, cube->adt->binding_handle);
EXPECT_STREQ("OBKüüübus.001", another_out_cube.name) << "The binding should be uniquely named";
EXPECT_STREQ("OBKüüübus.001", cube->adt->binding_name)
<< "The binding name should be copied to the adt";
/* Create an ID of another type. This should not be assignable to this binding. */
ID *mesh = static_cast<ID *>(BKE_id_new_nomain(ID_ME, "Mesh"));
EXPECT_FALSE(anim->assign_id(&out_cube, *mesh))
<< "Mesh should not be animatable by an Object binding";
BKE_id_free(nullptr, mesh);
}
TEST_F(AnimationLayersTest, rename_binding)
{
Binding &out_cube = anim->binding_add();
ASSERT_TRUE(anim->assign_id(&out_cube, cube->id));
EXPECT_EQ(out_cube.handle, cube->adt->binding_handle);
EXPECT_STREQ(out_cube.name, cube->id.name)
<< "The binding should be named after the assigned ID";
EXPECT_STREQ(out_cube.name, cube->adt->binding_name)
<< "The binding name should be copied to the adt";
anim->binding_name_set(out_cube, "New Binding Name");
EXPECT_STREQ("New Binding Name", out_cube.name);
/* At this point the binding name will not have been copied to the cube
* AnimData. However, I don't want to test for that here, as it's not exactly
* desirable behaviour, but more of a side-effect of the current
* implementation. */
anim->binding_name_propagate(*bmain, out_cube);
EXPECT_STREQ("New Binding Name", cube->adt->binding_name);
/* Finally, do another rename, do NOT call the propagate function, then
* unassign. This should still result in the correct binding name being stored
* on the ADT. */
anim->binding_name_set(out_cube, "Even Newer Name");
anim->unassign_id(cube->id);
EXPECT_STREQ("Even Newer Name", cube->adt->binding_name);
}
TEST_F(AnimationLayersTest, rename_binding_name_collision)
{
Binding &binding1 = anim->binding_add();
Binding &binding2 = anim->binding_add();
anim->binding_name_set(binding1, "New Binding Name");
anim->binding_name_set(binding2, "New Binding Name");
EXPECT_STREQ("New Binding Name", binding1.name);
EXPECT_STREQ("New Binding Name.001", binding2.name);
}
TEST_F(AnimationLayersTest, find_suitable_binding)
{
/* ===
* Empty case, no bindings exist yet and the ID doesn't even have an AnimData. */
EXPECT_EQ(nullptr, anim->find_suitable_binding_for(cube->id));
/* ===
* Binding exists with the same name & type as the ID, but the ID doesn't have any AnimData yet.
* These should nevertheless be matched up. */
Binding &binding = anim->binding_add();
binding.handle = 327;
STRNCPY_UTF8(binding.name, "OBKüüübus");
binding.idtype = GS(cube->id.name);
EXPECT_EQ(&binding, anim->find_suitable_binding_for(cube->id));
/* ===
* Binding exists with the same name & type as the ID, and the ID has an AnimData with the same
* binding name, but a different binding_handle. Since the Animation has not yet been
* assigned to this ID, the binding_handle should be ignored, and the binding name used for
* matching. */
/* Create an binding with a handle that should be ignored.*/
Binding &other_out = anim->binding_add();
other_out.handle = 47;
AnimData *adt = BKE_animdata_ensure_id(&cube->id);
adt->animation = nullptr;
/* Configure adt to use the handle of one binding, and the name of the other. */
adt->binding_handle = other_out.handle;
STRNCPY_UTF8(adt->binding_name, binding.name);
EXPECT_EQ(&binding, anim->find_suitable_binding_for(cube->id));
/* ===
* Same situation as above (AnimData has name of one binding, but the handle of another),
* except that the animation data-block has already been assigned. In this case the handle
* should take precedence. */
adt->animation = anim;
id_us_plus(&anim->id);
EXPECT_EQ(&other_out, anim->find_suitable_binding_for(cube->id));
/* ===
* An binding exists, but doesn't match anything in the anim data of the cube. This should fall
* back to using the ID name. */
adt->binding_handle = 161;
STRNCPY_UTF8(adt->binding_name, "¿¿What's this??");
EXPECT_EQ(&binding, anim->find_suitable_binding_for(cube->id));
}
TEST_F(AnimationLayersTest, strip)
{
constexpr float inf = std::numeric_limits<float>::infinity();
Layer &layer0 = anim->layer_add("Test Læür nul");
Strip &strip = layer0.strip_add(Strip::Type::Keyframe);
strip.resize(-inf, inf);
EXPECT_TRUE(strip.contains_frame(0.0f));
EXPECT_TRUE(strip.contains_frame(-100000.0f));
EXPECT_TRUE(strip.contains_frame(100000.0f));
EXPECT_TRUE(strip.is_last_frame(inf));
strip.resize(1.0f, 2.0f);
EXPECT_FALSE(strip.contains_frame(0.0f))
<< "Strip should not contain frames before its first frame";
EXPECT_TRUE(strip.contains_frame(1.0f)) << "Strip should contain its first frame.";
EXPECT_TRUE(strip.contains_frame(2.0f)) << "Strip should contain its last frame.";
EXPECT_FALSE(strip.contains_frame(2.0001f))
<< "Strip should not contain frames after its last frame";
EXPECT_FALSE(strip.is_last_frame(1.0f));
EXPECT_FALSE(strip.is_last_frame(1.5f));
EXPECT_FALSE(strip.is_last_frame(1.9999f));
EXPECT_TRUE(strip.is_last_frame(2.0f));
EXPECT_FALSE(strip.is_last_frame(2.0001f));
/* Same test as above, but with much larger end frame number. This is 2 hours at 24 FPS. */
strip.resize(1.0f, 172800.0f);
EXPECT_TRUE(strip.contains_frame(172800.0f)) << "Strip should contain its last frame.";
EXPECT_FALSE(strip.contains_frame(172800.1f))
<< "Strip should not contain frames after its last frame";
/* You can't get much closer to the end frame before it's considered equal. */
EXPECT_FALSE(strip.is_last_frame(172799.925f));
EXPECT_TRUE(strip.is_last_frame(172800.0f));
EXPECT_FALSE(strip.is_last_frame(172800.075f));
}
TEST_F(AnimationLayersTest, KeyframeStrip__keyframe_insert)
{
Binding &binding = anim->binding_add();
EXPECT_TRUE(binding.connect_id(cube->id));
Layer &layer = anim->layer_add("Kübus layer");
Strip &strip = layer.strip_add(Strip::Type::Keyframe);
KeyframeStrip &key_strip = strip.as<KeyframeStrip>();
const KeyframeSettings settings = get_keyframe_settings(false);
FCurve *fcurve_loc_a = key_strip.keyframe_insert(
binding, "location", 0, {1.0f, 47.0f}, settings);
ASSERT_NE(nullptr, fcurve_loc_a)
<< "Expect all the necessary data structures to be created on insertion of a key";
/* Check the strip was created correctly, with the channels for the binding. */
ASSERT_EQ(1, key_strip.channelbags().size());
ChannelBag *channels = key_strip.channelbag(0);
EXPECT_EQ(binding.handle, channels->binding_handle);
/* Insert a second key, should insert into the same FCurve as before. */
FCurve *fcurve_loc_b = key_strip.keyframe_insert(
binding, "location", 0, {5.0f, 47.1f}, settings);
ASSERT_EQ(fcurve_loc_a, fcurve_loc_b)
<< "Expect same (binding/rna path/array index) tuple to return the same FCurve.";
EXPECT_EQ(2, fcurve_loc_b->totvert);
EXPECT_EQ(47.0f, evaluate_fcurve(fcurve_loc_a, 1.0f));
EXPECT_EQ(47.1f, evaluate_fcurve(fcurve_loc_a, 5.0f));
/* Insert another key for another property, should create another FCurve. */
FCurve *fcurve_rot = key_strip.keyframe_insert(
binding, "rotation_quaternion", 0, {1.0f, 0.25f}, settings);
EXPECT_NE(fcurve_loc_b, fcurve_rot)
<< "Expected rotation and location curves to be different FCurves.";
EXPECT_EQ(2, channels->fcurves().size()) << "Expected a second FCurve to be created.";
}
} // namespace blender::animrig::tests

View File

@ -6,10 +6,12 @@
* \ingroup animrig
*/
#include "ANIM_animation.hh"
#include "ANIM_animdata.hh"
#include "BKE_action.h"
#include "BKE_anim_data.hh"
#include "BKE_animation.hh"
#include "BKE_fcurve.hh"
#include "BKE_lib_id.hh"
@ -26,6 +28,9 @@
#include "RNA_access.hh"
#include "RNA_path.hh"
#include <algorithm>
#include <cmath>
namespace blender::animrig {
/* -------------------------------------------------------------------- */
@ -73,6 +78,32 @@ bAction *id_action_ensure(Main *bmain, ID *id)
return adt->action;
}
Animation *id_animation_ensure(Main *bmain, ID *id)
{
BLI_assert(id != nullptr);
AnimData *adt = BKE_animdata_from_id(id);
if (adt == nullptr) {
adt = BKE_animdata_ensure_id(id);
}
if (adt == nullptr) {
/* If still none (as not allowed to add, or ID doesn't have animdata for some reason) */
printf("ERROR: Couldn't add AnimData (ID = %s)\n", (id) ? (id->name) : "<None>");
return nullptr;
}
if (adt->animation != nullptr) {
return &adt->animation->wrap();
}
::Animation *anim = BKE_animation_add(bmain, "Animation");
DEG_relations_tag_update(bmain);
DEG_id_tag_update(&anim->id, ID_RECALC_ANIMATION_NO_FLUSH);
return &anim->wrap();
}
void animdata_fcurve_delete(bAnimContext *ac, AnimData *adt, FCurve *fcu)
{
/* - If no AnimData, we've got nowhere to remove the F-Curve from
@ -174,4 +205,46 @@ void reevaluate_fcurve_errors(bAnimContext *ac)
}
}
const FCurve *fcurve_find_by_rna_path(const Animation &anim,
const ID &animated_id,
const StringRefNull rna_path,
const int array_index)
{
const Binding *binding = anim.binding_for_id(animated_id);
if (!binding) {
/* No need to inspect anything if this ID does not have an animation Binding. */
return nullptr;
}
/* Iterate the layers top-down, as higher-up animation overrides (or at least can override)
* lower-down animation. */
for (int layer_idx = anim.layer_array_num - 1; layer_idx >= 0; layer_idx--) {
const Layer *layer = anim.layer(layer_idx);
/* TODO: refactor this into something nicer once we have different strip types. */
for (const Strip *strip : layer->strips()) {
switch (strip->type()) {
case Strip::Type::Keyframe: {
const KeyframeStrip &key_strip = strip->as<KeyframeStrip>();
const ChannelBag *channelbag_for_binding = key_strip.channelbag_for_binding(*binding);
if (!channelbag_for_binding) {
continue;
}
const FCurve *fcu = channelbag_for_binding->fcurve_find(rna_path, array_index);
if (!fcu) {
continue;
}
/* This code assumes that there is only one strip, and that it's infinite. When that
* changes, this code needs to be expanded to check for strip boundaries. */
return fcu;
}
}
/* Explicit lack of 'default' clause, to get compiler warnings when strip types are added. */
}
}
return nullptr;
}
} // namespace blender::animrig

View File

@ -0,0 +1,303 @@
/* SPDX-FileCopyrightText: 2023 Blender Developers
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_evaluation.hh"
#include "RNA_access.hh"
#include "BKE_animsys.h"
#include "BKE_fcurve.hh"
#include "BLI_map.hh"
#include "evaluation_internal.hh"
namespace blender::animrig {
using namespace internal;
/**
* Blend the 'current layer' with the 'last evaluation result', returning the
* blended result.
*/
EvaluationResult blend_layer_results(const EvaluationResult &last_result,
const EvaluationResult &current_result,
const Layer &current_layer);
/**
* Apply the result of the animation evaluation to the given data-block.
*
* \param flush_to_original when true, look up the original data-block (assuming the given one is
* an evaluated copy) and update that too.
*/
void apply_evaluation_result(const EvaluationResult &evaluation_result,
PointerRNA &animated_id_ptr,
bool flush_to_original);
static EvaluationResult evaluate_animation(PointerRNA &animated_id_ptr,
Animation &animation,
const binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context)
{
EvaluationResult last_result;
/* Evaluate each layer in order. */
for (Layer *layer : animation.layers()) {
if (layer->influence <= 0.0f) {
/* Don't bother evaluating layers without influence. */
continue;
}
auto layer_result = evaluate_layer(animated_id_ptr, *layer, binding_handle, anim_eval_context);
if (!layer_result) {
continue;
}
if (!last_result) {
/* Simple case: no results so far, so just use this layer as-is. There is
* nothing to blend/combine with, so ignore the influence and combination
* options. */
last_result = layer_result;
continue;
}
/* Complex case: blend this layer's result into the previous layer's result. */
last_result = blend_layer_results(last_result, layer_result, *layer);
}
return last_result;
}
void evaluate_and_apply_animation(PointerRNA &animated_id_ptr,
Animation &animation,
const binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context,
const bool flush_to_original)
{
EvaluationResult evaluation_result = evaluate_animation(
animated_id_ptr, animation, binding_handle, anim_eval_context);
if (!evaluation_result) {
return;
}
apply_evaluation_result(evaluation_result, animated_id_ptr, flush_to_original);
}
/* Copy of the same-named function in anim_sys.cc, with the check on action groups removed. */
static bool is_fcurve_evaluatable(const FCurve *fcu)
{
if (fcu->flag & (FCURVE_MUTED | FCURVE_DISABLED)) {
return false;
}
if (BKE_fcurve_is_empty(fcu)) {
return false;
}
return true;
}
/* Copy of the same-named function in anim_sys.cc, but with the special handling for NLA strips
* removed. */
static void animsys_construct_orig_pointer_rna(const PointerRNA *ptr, PointerRNA *ptr_orig)
{
*ptr_orig = *ptr;
/* Original note from anim_sys.cc:
* -----------
* NOTE: nlastrip_evaluate_controls() creates PointerRNA with ID of nullptr. Technically, this is
* not a valid pointer, but there are exceptions in various places of this file which handles
* such pointers.
* We do special trickery here as well, to quickly go from evaluated to original NlaStrip.
* -----------
* And this is all not ported to the new layered animation system. */
BLI_assert_msg(ptr->owner_id, "NLA support was not ported to the layered animation system");
ptr_orig->owner_id = ptr_orig->owner_id->orig_id;
ptr_orig->data = ptr_orig->owner_id;
}
/* Copy of the same-named function in anim_sys.cc. */
static void animsys_write_orig_anim_rna(PointerRNA *ptr,
const char *rna_path,
const int array_index,
const float value)
{
PointerRNA ptr_orig;
animsys_construct_orig_pointer_rna(ptr, &ptr_orig);
PathResolvedRNA orig_anim_rna;
/* TODO(sergey): Should be possible to cache resolved path in dependency graph somehow. */
if (BKE_animsys_rna_path_resolve(&ptr_orig, rna_path, array_index, &orig_anim_rna)) {
BKE_animsys_write_to_rna_path(&orig_anim_rna, value);
}
}
static EvaluationResult evaluate_keyframe_strip(PointerRNA &animated_id_ptr,
KeyframeStrip &key_strip,
const binding_handle_t binding_handle,
const AnimationEvalContext &offset_eval_context)
{
ChannelBag *channelbag_for_binding = key_strip.channelbag_for_binding(binding_handle);
if (!channelbag_for_binding) {
return {};
}
EvaluationResult evaluation_result;
for (FCurve *fcu : channelbag_for_binding->fcurves()) {
/* Blatant copy of animsys_evaluate_fcurves(). */
if (!is_fcurve_evaluatable(fcu)) {
continue;
}
PathResolvedRNA anim_rna;
if (!BKE_animsys_rna_path_resolve(
&animated_id_ptr, fcu->rna_path, fcu->array_index, &anim_rna))
{
printf("Cannot resolve RNA path %s[%d] on ID %s\n",
fcu->rna_path,
fcu->array_index,
animated_id_ptr.owner_id->name);
continue;
}
const float curval = calculate_fcurve(&anim_rna, fcu, &offset_eval_context);
evaluation_result.store(fcu->rna_path, fcu->array_index, curval, anim_rna);
}
return evaluation_result;
}
void apply_evaluation_result(const EvaluationResult &evaluation_result,
PointerRNA &animated_id_ptr,
const bool flush_to_original)
{
for (auto channel_result : evaluation_result.items()) {
const PropIdentifier &prop_ident = channel_result.key;
const AnimatedProperty &anim_prop = channel_result.value;
const float animated_value = anim_prop.value;
PathResolvedRNA anim_rna = anim_prop.prop_rna;
BKE_animsys_write_to_rna_path(&anim_rna, animated_value);
if (flush_to_original) {
/* Convert the StringRef to a `const char *`, as the rest of the RNA path handling code in
* BKE still uses `char *` instead of `StringRef`. */
animsys_write_orig_anim_rna(&animated_id_ptr,
StringRefNull(prop_ident.rna_path).c_str(),
prop_ident.array_index,
animated_value);
}
}
}
static EvaluationResult evaluate_strip(PointerRNA &animated_id_ptr,
Strip &strip,
const binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context)
{
AnimationEvalContext offset_eval_context = anim_eval_context;
/* Positive offset means the entire strip is pushed "to the right", so
* evaluation needs to happen further "to the left". */
offset_eval_context.eval_time -= strip.frame_offset;
switch (strip.type()) {
case Strip::Type::Keyframe: {
KeyframeStrip &key_strip = strip.as<KeyframeStrip>();
return evaluate_keyframe_strip(
animated_id_ptr, key_strip, binding_handle, offset_eval_context);
}
}
return {};
}
EvaluationResult blend_layer_results(const EvaluationResult &last_result,
const EvaluationResult &current_result,
const Layer &current_layer)
{
/* TODO?: store the layer results sequentially, so that we can step through
* them in parallel, instead of iterating over one and doing map lookups on
* the other. */
EvaluationResult blend = last_result;
for (auto channel_result : current_result.items()) {
const PropIdentifier &prop_ident = channel_result.key;
AnimatedProperty *last_prop = blend.lookup_ptr(prop_ident);
const AnimatedProperty &anim_prop = channel_result.value;
if (!last_prop) {
/* Nothing to blend with, so just take (influence * value). */
blend.store(prop_ident.rna_path,
prop_ident.array_index,
anim_prop.value * current_layer.influence,
anim_prop.prop_rna);
continue;
}
/* TODO: move this to a separate function. And write more smartness for rotations. */
switch (current_layer.mix_mode()) {
case Layer::MixMode::Replace:
last_prop->value = anim_prop.value * current_layer.influence;
break;
case Layer::MixMode::Offset:
last_prop->value = math::interpolate(
current_layer.influence, last_prop->value, anim_prop.value);
break;
case Layer::MixMode::Add:
last_prop->value += anim_prop.value * current_layer.influence;
break;
case Layer::MixMode::Subtract:
last_prop->value -= anim_prop.value * current_layer.influence;
break;
case Layer::MixMode::Multiply:
last_prop->value *= anim_prop.value * current_layer.influence;
break;
};
}
return blend;
}
namespace internal {
EvaluationResult evaluate_layer(PointerRNA &animated_id_ptr,
Layer &layer,
const binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context)
{
/* TODO: implement cross-blending between overlapping strips. For now, this is not supported.
* Instead, the first strong result is taken (see below), and if that is not available, the last
* weak result will be used.
*
* Weak result: obtained from evaluating the final frame of the strip.
* Strong result: any result that is not a weak result. */
EvaluationResult last_weak_result;
for (Strip *strip : layer.strips()) {
if (!strip->contains_frame(anim_eval_context.eval_time)) {
continue;
}
const EvaluationResult strip_result = evaluate_strip(
animated_id_ptr, *strip, binding_handle, anim_eval_context);
if (!strip_result) {
continue;
}
const bool is_weak_result = strip->is_last_frame(anim_eval_context.eval_time);
if (is_weak_result) {
/* Keep going until a strong result is found. */
last_weak_result = strip_result;
continue;
}
/* Found a strong result, just return it. */
return strip_result;
}
return last_weak_result;
}
} // namespace internal
} // namespace blender::animrig

View File

@ -0,0 +1,122 @@
/* SPDX-FileCopyrightText: 2024 Blender Developers
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_map.hh"
#include "BLI_string_ref.hh"
#include "RNA_access.hh"
namespace blender::animrig::internal {
class PropIdentifier {
public:
StringRefNull rna_path;
int array_index;
PropIdentifier() = default;
PropIdentifier(const StringRefNull rna_path, const int array_index)
: rna_path(rna_path), array_index(array_index)
{
}
bool operator==(const PropIdentifier &other) const
{
return rna_path == other.rna_path && array_index == other.array_index;
}
bool operator!=(const PropIdentifier &other) const
{
return !(*this == other);
}
uint64_t hash() const
{
return get_default_hash(rna_path, array_index);
}
};
class AnimatedProperty {
public:
float value;
PathResolvedRNA prop_rna;
AnimatedProperty(const float value, const PathResolvedRNA &prop_rna)
: value(value), prop_rna(prop_rna)
{
}
};
/* Evaluated FCurves for some animation binding.
* Mapping from property identifier to its float value.
*
* Can be fed to the evaluation of the next layer, mixed with another strip, or
* used to modify actual RNA properties.
*
* TODO: see if this is efficient, and contains enough info, for mixing. For now
* this just captures the FCurve evaluation result, but doesn't have any info
* about how to do the mixing (LERP, quaternion SLERP, etc.).
*/
class EvaluationResult {
protected:
using EvaluationMap = Map<PropIdentifier, AnimatedProperty>;
EvaluationMap result_;
public:
EvaluationResult() = default;
EvaluationResult(const EvaluationResult &other) = default;
~EvaluationResult() = default;
public:
operator bool() const
{
return !this->is_empty();
}
bool is_empty() const
{
return result_.is_empty();
}
void store(const StringRefNull rna_path,
const int array_index,
const float value,
const PathResolvedRNA &prop_rna)
{
PropIdentifier key(rna_path, array_index);
AnimatedProperty anim_prop(value, prop_rna);
result_.add_overwrite(key, anim_prop);
}
AnimatedProperty value(const StringRefNull rna_path, const int array_index) const
{
PropIdentifier key(rna_path, array_index);
return result_.lookup(key);
}
const AnimatedProperty *lookup_ptr(const PropIdentifier &key) const
{
return result_.lookup_ptr(key);
}
AnimatedProperty *lookup_ptr(const PropIdentifier &key)
{
return result_.lookup_ptr(key);
}
EvaluationMap::ItemIterator items() const
{
return result_.items();
}
};
/**
* Evaluate the animation data on the given layer, for the given binding. This
* just returns the evaluation result, without taking any other layers,
* blending, influence, etc. into account. */
EvaluationResult evaluate_layer(PointerRNA &animated_id_ptr,
Layer &layer,
binding_handle_t binding_handle,
const AnimationEvalContext &anim_eval_context);
} // namespace blender::animrig::internal

View File

@ -0,0 +1,313 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_animation.hh"
#include "ANIM_evaluation.hh"
#include "evaluation_internal.hh"
#include "BKE_animation.hh"
#include "BKE_animsys.h"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_object.hh"
#include "DNA_object_types.h"
#include "RNA_access.hh"
#include "RNA_prototypes.h"
#include "BLI_math_base.h"
#include "BLI_string_utf8.h"
#include <optional>
#include "testing/testing.h"
namespace blender::animrig::tests {
using namespace blender::animrig::internal;
class AnimationEvaluationTest : public testing::Test {
protected:
Animation anim = {};
Object *cube;
Binding *binding;
Layer *layer;
KeyframeSettings settings = get_keyframe_settings(false);
AnimationEvalContext anim_eval_context = {};
PointerRNA cube_rna_ptr;
public:
static void SetUpTestSuite()
{
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
void SetUp() override
{
anim = {};
STRNCPY_UTF8(anim.id.name, "ANÄnimåtië");
cube = BKE_object_add_only_object(nullptr, OB_EMPTY, "Küüübus");
binding = &anim.binding_add();
anim.assign_id(binding, cube->id);
layer = &anim.layer_add("Kübus layer");
/* Make it easier to predict test values. */
settings.interpolation = BEZT_IPO_LIN;
cube_rna_ptr = RNA_pointer_create(&cube->id, &RNA_Object, &cube->id);
}
void TearDown() override
{
BKE_id_free(nullptr, &cube->id);
anim.wrap().free_data();
}
/** Evaluate the layer, and return result for the given property. */
std::optional<float> evaluate_single_property(const StringRefNull rna_path,
const int array_index,
const float eval_time)
{
anim_eval_context.eval_time = eval_time;
EvaluationResult result = evaluate_layer(
cube_rna_ptr, *layer, binding->handle, anim_eval_context);
const AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier(rna_path, array_index));
if (!loc0_result) {
return {};
}
return loc0_result->value;
}
/** Evaluate the layer, and test that the given property evaluates to the expected value. */
testing::AssertionResult test_evaluate_layer(const StringRefNull rna_path,
const int array_index,
const float2 eval_time__expect_value)
{
const float eval_time = eval_time__expect_value[0];
const float expect_value = eval_time__expect_value[1];
const std::optional<float> opt_eval_value = evaluate_single_property(
rna_path, array_index, eval_time);
if (!opt_eval_value) {
return testing::AssertionFailure()
<< rna_path << "[" << array_index << "] should have been animated";
}
const float eval_value = *opt_eval_value;
const uint diff_ulps = ulp_diff_ff(expect_value, eval_value);
if (diff_ulps >= 4) {
return testing::AssertionFailure()
<< std::endl
<< " " << rna_path << "[" << array_index
<< "] evaluation did not produce the expected result:" << std::endl
<< " evaluted to: " << testing::PrintToString(eval_value) << std::endl
<< " expected : " << testing::PrintToString(expect_value) << std::endl;
}
return testing::AssertionSuccess();
};
/** Evaluate the layer, and test that the given property is not part of the result. */
testing::AssertionResult test_evaluate_layer_no_result(const StringRefNull rna_path,
const int array_index,
const float eval_time)
{
const std::optional<float> eval_value = evaluate_single_property(
rna_path, array_index, eval_time);
if (eval_value) {
return testing::AssertionFailure()
<< std::endl
<< " " << rna_path << "[" << array_index
<< "] evaluation should NOT produce a value:" << std::endl
<< " evaluted to: " << testing::PrintToString(*eval_value) << std::endl;
}
return testing::AssertionSuccess();
}
};
TEST_F(AnimationEvaluationTest, evaluate_layer__keyframes)
{
Strip &strip = layer->strip_add(Strip::Type::Keyframe);
KeyframeStrip &key_strip = strip.as<KeyframeStrip>();
/* Set some keys. */
key_strip.keyframe_insert(*binding, "location", 0, {1.0f, 47.1f}, settings);
key_strip.keyframe_insert(*binding, "location", 0, {5.0f, 47.5f}, settings);
key_strip.keyframe_insert(*binding, "rotation_euler", 1, {1.0f, 0.0f}, settings);
key_strip.keyframe_insert(*binding, "rotation_euler", 1, {5.0f, 3.14f}, settings);
/* Set the animated properties to some values. These should not be overwritten
* by the evaluation itself. */
cube->loc[0] = 3.0f;
cube->loc[1] = 2.0f;
cube->loc[2] = 7.0f;
cube->rot[0] = 3.0f;
cube->rot[1] = 2.0f;
cube->rot[2] = 7.0f;
/* Evaluate. */
anim_eval_context.eval_time = 3.0f;
EvaluationResult result = evaluate_layer(
cube_rna_ptr, *layer, binding->handle, anim_eval_context);
/* Check the result. */
ASSERT_FALSE(result.is_empty());
AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier("location", 0));
ASSERT_NE(nullptr, loc0_result) << "location[0] should have been animated";
EXPECT_EQ(47.3f, loc0_result->value);
EXPECT_EQ(3.0f, cube->loc[0]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(2.0f, cube->loc[1]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(7.0f, cube->loc[2]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(3.0f, cube->rot[0]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(2.0f, cube->rot[1]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(7.0f, cube->rot[2]) << "Evaluation should not modify the animated ID";
}
TEST_F(AnimationEvaluationTest, strip_boundaries__single_strip)
{
/* Single finite strip, check first, middle, and last frame. */
Strip &strip = layer->strip_add(Strip::Type::Keyframe);
strip.resize(1.0f, 10.0f);
/* Set some keys. */
KeyframeStrip &key_strip = strip.as<KeyframeStrip>();
key_strip.keyframe_insert(*binding, "location", 0, {1.0f, 47.0f}, settings);
key_strip.keyframe_insert(*binding, "location", 0, {5.0f, 327.0f}, settings);
key_strip.keyframe_insert(*binding, "location", 0, {10.0f, 48.0f}, settings);
/* Evaluate the layer to see how it handles the boundaries + something in between. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
}
TEST_F(AnimationEvaluationTest, strip_boundaries__nonoverlapping)
{
/* Two finite strips that are strictly distinct. */
Strip &strip1 = layer->strip_add(Strip::Type::Keyframe);
Strip &strip2 = layer->strip_add(Strip::Type::Keyframe);
strip1.resize(1.0f, 10.0f);
strip2.resize(11.0f, 20.0f);
strip2.frame_offset = 10;
/* Set some keys. */
{
KeyframeStrip &key_strip1 = strip1.as<KeyframeStrip>();
key_strip1.keyframe_insert(*binding, "location", 0, {1.0f, 47.0f}, settings);
key_strip1.keyframe_insert(*binding, "location", 0, {5.0f, 327.0f}, settings);
key_strip1.keyframe_insert(*binding, "location", 0, {10.0f, 48.0f}, settings);
}
{
KeyframeStrip &key_strip2 = strip2.as<KeyframeStrip>();
key_strip2.keyframe_insert(*binding, "location", 0, {1.0f, 47.0f}, settings);
key_strip2.keyframe_insert(*binding, "location", 0, {5.0f, 327.0f}, settings);
key_strip2.keyframe_insert(*binding, "location", 0, {10.0f, 48.0f}, settings);
}
/* Check Strip 1. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
/* Check Strip 2. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {11.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {13.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {20.0f, 48.0f}));
/* Check outside the range of the strips. */
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 20.001f));
}
TEST_F(AnimationEvaluationTest, strip_boundaries__overlapping_edge)
{
/* Two finite strips that are overlapping on their edge. */
Strip &strip1 = layer->strip_add(Strip::Type::Keyframe);
Strip &strip2 = layer->strip_add(Strip::Type::Keyframe);
strip1.resize(1.0f, 10.0f);
strip2.resize(10.0f, 19.0f);
strip2.frame_offset = 9;
/* Set some keys. */
{
KeyframeStrip &key_strip1 = strip1.as<KeyframeStrip>();
key_strip1.keyframe_insert(*binding, "location", 0, {1.0f, 47.0f}, settings);
key_strip1.keyframe_insert(*binding, "location", 0, {5.0f, 327.0f}, settings);
key_strip1.keyframe_insert(*binding, "location", 0, {10.0f, 48.0f}, settings);
}
{
KeyframeStrip &key_strip2 = strip2.as<KeyframeStrip>();
key_strip2.keyframe_insert(*binding, "location", 0, {1.0f, 47.0f}, settings);
key_strip2.keyframe_insert(*binding, "location", 0, {5.0f, 327.0f}, settings);
key_strip2.keyframe_insert(*binding, "location", 0, {10.0f, 48.0f}, settings);
}
/* Check Strip 1. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
/* Check overlapping frame. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 47.0f}))
<< "On the overlapping frame, only Strip 2 should be evaluated.";
/* Check Strip 2. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {12.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {19.0f, 48.0f}));
/* Check outside the range of the strips. */
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 19.001f));
}
class AccessibleEvaluationResult : public EvaluationResult {
public:
EvaluationMap &get_map()
{
return result_;
}
};
TEST(AnimationEvaluationResultTest, prop_identifier_hashing)
{
AccessibleEvaluationResult result;
/* Test storing the same result twice, with different memory locations of the RNA paths. This
* tests that the mapping uses the actual string, and not just pointer comparison. */
const char *rna_path_1 = "pose.bones['Root'].location";
const std::string rna_path_2(rna_path_1);
ASSERT_NE(rna_path_1, rna_path_2.c_str())
<< "This test requires different addresses for the RNA path strings";
PathResolvedRNA fake_resolved_rna;
result.store(rna_path_1, 0, 1.0f, fake_resolved_rna);
result.store(rna_path_2, 0, 2.0f, fake_resolved_rna);
EXPECT_EQ(1, result.get_map().size())
<< "Storing a result for the same property twice should just overwrite the previous value";
{
PropIdentifier key(rna_path_1, 0);
AnimatedProperty *anim_prop = result.lookup_ptr(key);
EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
}
{
PropIdentifier key(rna_path_2, 0);
AnimatedProperty *anim_prop = result.lookup_ptr(key);
EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
}
}
} // namespace blender::animrig::tests

View File

@ -13,6 +13,7 @@
#include "ANIM_fcurve.hh"
#include "BKE_fcurve.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "DNA_anim_types.h"
#include "MEM_guardedalloc.h"
@ -32,6 +33,17 @@ KeyframeSettings get_keyframe_settings(const bool from_userprefs)
return settings;
}
FCurve *create_fcurve_for_channel(const StringRef rna_path, const int array_index)
{
FCurve *fcu = BKE_fcurve_create();
fcu->rna_path = BLI_strdupn(rna_path.data(), rna_path.size());
fcu->array_index = array_index;
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->auto_smoothing = U.auto_smoothing_new;
return fcu;
}
bool delete_keyframe_fcurve(AnimData *adt, FCurve *fcu, float cfra)
{
bool found;

View File

@ -13,6 +13,7 @@
#include <fmt/format.h>
#include "ANIM_action.hh"
#include "ANIM_animation.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "ANIM_keyframing.hh"
@ -994,6 +995,97 @@ static blender::Vector<float> get_keyframe_values(PointerRNA *ptr,
return values;
}
struct KeyInsertData {
float2 position;
int array_index;
};
/* `key_data` is expected to be in Strip space. */
static void insert_key_strip(KeyframeStrip &strip,
Binding &binding,
const std::string &rna_path,
const KeyInsertData &key_data,
const KeyframeSettings &key_settings)
{
strip.keyframe_insert(binding, rna_path, key_data.array_index, key_data.position, key_settings);
}
static void insert_key_layer(Layer &layer,
Binding &binding,
const std::string &rna_path,
const KeyInsertData &key_data,
const KeyframeSettings &key_settings)
{
if (layer.strips().size() == 0) {

Here you could have

for (int property_array_index : rna_values.index_range()) {
  const float value = rna_values[property_array_index];
  ...
}

This will remove the need to keep track of the array index explicitly, and I think the code will be a bit simpler for it.

Here you could have ```cpp for (int property_array_index : rna_values.index_range()) { const float value = rna_values[property_array_index]; ... } ``` This will remove the need to keep track of the array index explicitly, and I think the code will be a bit simpler for it.

the code is now in a different place, but I still used that logic. Thanks

the code is now in a different place, but I still used that logic. Thanks
layer.strip_add(Strip::Type::Keyframe);
}
Strip *strip = layer.strip(0);

This code assumes that the strip is a KeyframeStrip. Better to just pass the strip parameter as such, and leave the wrapping / assumptions / error handling to the caller.

This code assumes that the strip is a `KeyframeStrip`. Better to just pass the `strip` parameter as such, and leave the wrapping / assumptions / error handling to the caller.
if (strip == nullptr) {
/* TODO: report error. */
printf("ERROR: Strip is a null pointer");
return;
}
/* TODO morph key data based on Layer position in stack and Strip offset. */
insert_key_strip(strip->as<KeyframeStrip>(), binding, rna_path, key_data, key_settings);
}
static void insert_key_anim(Animation &anim,
PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
const float scene_frame,
const eInsertKeyFlags insert_key_flags,
const KeyframeSettings &key_settings)
{
ID *id = rna_pointer->owner_id;
Binding *binding = anim.binding_for_id(*id);
if (binding == nullptr) {
binding = &anim.binding_add();
const bool success = anim.assign_id(binding, *id);
if (!success) {
printf("ERROR: Failed to assign the ID to the binding");
/* TODO: Report error. */
return;
}
}
Layer *layer;
if (anim.layers().size() == 0) {
layer = &anim.layer_add("Layer 0");
}
else {
layer = anim.layer(0);
}
if (layer == nullptr) {
printf("ERROR: Cannot get or create a layer");
/* TODO: Report error. */
return;
}
for (const std::string &rna_path : rna_paths) {
const bool visual_keyframing = insert_key_flags & INSERTKEY_MATRIX;
PointerRNA ptr;
PropertyRNA *prop = nullptr;
const bool path_resolved = RNA_path_resolve_property(
rna_pointer, rna_path.c_str(), &ptr, &prop);

There's now double code to ensure a layer exists, which means duplication of the default layer name logic as well. Better to move this into some KeyframeStrip &keystrip = animrig::simplifications::ensure_layer_with_keyframe_strip(anim) function.

There's now double code to ensure a layer exists, which means duplication of the default layer name logic as well. Better to move this into some `KeyframeStrip &keystrip = animrig::simplifications::ensure_layer_with_keyframe_strip(anim)` function.

where is the duplication of that code? Is that somewhere in your code?

where is the duplication of that code? Is that somewhere in your code?
if (!path_resolved) {
return;
}
const std::optional<std::string> rna_path_id_to_prop = RNA_path_from_ID_to_property(&ptr,
prop);
Vector<float> rna_values = get_keyframe_values(&ptr, prop, visual_keyframing);
for (int property_index : rna_values.index_range()) {
KeyInsertData key_data;
key_data.array_index = property_index;
key_data.position = {scene_frame, rna_values[property_index]};
insert_key_layer(*layer, *binding, rna_path_id_to_prop.value(), key_data, key_settings);
}
}
}
void insert_key_rna(PointerRNA *rna_pointer,
const blender::Span<std::string> rna_paths,
const float scene_frame,
@ -1004,6 +1096,34 @@ void insert_key_rna(PointerRNA *rna_pointer,
const AnimationEvalContext &anim_eval_context)
{
ID *id = rna_pointer->owner_id;
AnimData *adt;
/* init animdata if none available yet */
adt = BKE_animdata_ensure_id(id);
if (adt == nullptr) {
return;
}
if (adt->animation || !adt->action) {

I think it's fine to write more "boolean" style checks for these, like if (adt->animation || !adt->action).

I used to be of "the other camp", but thinking about things like smart pointers have swayed me in the other direction.

I think it's fine to write more "boolean" style checks for these, like `if (adt->animation || !adt->action)`. I used to be of "the other camp", but thinking about things like smart pointers have swayed me in the other direction.
/* TODO: This should only execute with an experimental flag enabled. */
/* TODO: Don't hardcode key settings. */
Animation *anim = id_animation_ensure(bmain, id);
if (anim == nullptr) {
BKE_reportf(reports,
RPT_ERROR,
"Could not insert keyframe, as this type does not support animation data (ID = "
"%s)",
id->name);
return;
}
KeyframeSettings key_settings;
key_settings.keyframe_type = key_type;
key_settings.handle = HD_AUTO_ANIM;
key_settings.interpolation = BEZT_IPO_BEZ;
insert_key_anim(*anim, rna_pointer, rna_paths, scene_frame, insert_key_flags, key_settings);
return;
}
bAction *action = id_action_ensure(bmain, id);
if (action == nullptr) {
BKE_reportf(reports,
@ -1014,8 +1134,6 @@ void insert_key_rna(PointerRNA *rna_pointer,
return;
}
AnimData *adt = BKE_animdata_from_id(id);
/* Keyframing functions can deal with the nla_context being a nullptr. */
ListBase nla_cache = {nullptr, nullptr};
NlaKeyframingContext *nla_context = nullptr;

View File

@ -292,8 +292,10 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con
/**
* Find an F-Curve from its rna path and index.
*
* If there is an action assigned to the `animdata`, it will be searched for a matching F-curve
* first. Drivers are searched only if no valid action F-curve could be found.
* The search order is as follows. The first match will be returned:
* - Animation
* - Action
* - Drivers
*
* \note Typically, indices in RNA arrays are stored separately in F-curves, so the rna_path
* should not include them (e.g. `rna_path='location[0]'` will not match any F-Curve on an Object,
@ -301,8 +303,13 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con
*
* \note Return pointer parameters (`r_action`, `r_driven` and `r_special`) are all optional and
* may be NULL.
*
* \note since Animation data-blocks may have multiple layers all containing an F-Curve for this
* property, what is returned is a best-effort guess. The topmost layer has priority, and it is
* assumed that when it has a strip, it's infinite.
*/
FCurve *BKE_animadata_fcurve_find_by_rna_path(AnimData *animdata,
FCurve *BKE_animadata_fcurve_find_by_rna_path(const ID *id,
AnimData *animdata,
const char *rna_path,
const int rna_index,
bAction **r_action,

View File

@ -10,6 +10,8 @@
#include <cstring>
#include <optional>
#include "ANIM_animation.hh"
#include "BKE_action.h"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
@ -278,6 +280,11 @@ bool BKE_animdata_id_is_animated(const ID *id)
return false;
}
if (adt->animation) {
blender::animrig::Animation &anim = adt->animation->wrap();
return anim.binding_for_id(*id) != nullptr;
}
if (adt->action != nullptr && !BLI_listbase_is_empty(&adt->action->curves)) {
return true;
}

View File

@ -51,6 +51,8 @@
#include "BKE_report.hh"
#include "BKE_texture.h"
#include "ANIM_evaluation.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_query.hh"
@ -3917,16 +3919,26 @@ void BKE_animsys_evaluate_animdata(ID *id,
*/
/* TODO: need to double check that this all works correctly */
if (recalc & ADT_RECALC_ANIM) {
/* evaluate NLA data */
if ((adt->nla_tracks.first) && !(adt->flag & ADT_NLA_EVAL_OFF)) {
/* evaluate NLA-stack
* - active action is evaluated as part of the NLA stack as the last item
*/
animsys_calculate_nla(&id_ptr, adt, anim_eval_context, flush_to_original);
if (adt->animation && adt->binding_handle) {
/* Animation data-blocks take precedence over the old Action + NLA system. */
blender::animrig::evaluate_and_apply_animation(id_ptr,
adt->animation->wrap(),
adt->binding_handle,
*anim_eval_context,
flush_to_original);
}
/* evaluate Active Action only */
else if (adt->action) {
animsys_evaluate_action(&id_ptr, adt->action, anim_eval_context, flush_to_original);
else {
/* evaluate NLA data */
if ((adt->nla_tracks.first) && !(adt->flag & ADT_NLA_EVAL_OFF)) {
/* evaluate NLA-stack
* - active action is evaluated as part of the NLA stack as the last item
*/
animsys_calculate_nla(&id_ptr, adt, anim_eval_context, flush_to_original);
}
/* evaluate Active Action only */
else if (adt->action) {
animsys_evaluate_action(&id_ptr, adt->action, anim_eval_context, flush_to_original);
}
}
}

View File

@ -14,6 +14,9 @@
#include "MEM_guardedalloc.h"
#include "ANIM_animation.hh"
#include "ANIM_animdata.hh"
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "DNA_text_types.h"
@ -38,6 +41,7 @@
#include "BKE_idprop.h"
#include "BKE_lib_query.hh"
#include "BKE_nla.h"
#include "BKE_scene.hh"
#include "BLO_read_write.hh"
@ -249,7 +253,7 @@ FCurve *id_data_find_fcurve(
* needs to be re-checked I think?. */
bool is_driven = false;
FCurve *fcu = BKE_animadata_fcurve_find_by_rna_path(
adt, path->c_str(), index, nullptr, &is_driven);
id, adt, path->c_str(), index, nullptr, &is_driven);
if (is_driven) {
if (r_driven != nullptr) {
*r_driven = is_driven;
@ -346,8 +350,12 @@ int BKE_fcurves_filter(ListBase *dst, ListBase *src, const char *dataPrefix, con
return matches;
}
FCurve *BKE_animadata_fcurve_find_by_rna_path(
AnimData *animdata, const char *rna_path, int rna_index, bAction **r_action, bool *r_driven)
FCurve *BKE_animadata_fcurve_find_by_rna_path(const ID *id,
AnimData *animdata,
const char *rna_path,
int rna_index,
bAction **r_action,
bool *r_driven)
{
if (r_driven != nullptr) {
*r_driven = false;
@ -356,11 +364,24 @@ FCurve *BKE_animadata_fcurve_find_by_rna_path(
*r_action = nullptr;
}
/* Animation data-block takes priority over Action data-block. */
if (animdata->animation) {
/* TODO: this branch probably also needs a `Animation *r_anim` parameter for full
* compatibility with the Action-based uses. Even better: change to return a
* result struct with all the relevant information/data. */
BLI_assert(id);
const FCurve *fcu = blender::animrig::fcurve_find_by_rna_path(
animdata->animation->wrap(), *id, rna_path, rna_index);
if (fcu) {
/* The new Animation data-block is stricter with const-ness than older code, hence the
* const_cast. */
return const_cast<FCurve *>(fcu);
}
}
/* Action takes priority over drivers. */
const bool has_action_fcurves = animdata->action != nullptr &&
!BLI_listbase_is_empty(&animdata->action->curves);
const bool has_drivers = !BLI_listbase_is_empty(&animdata->drivers);
/* Animation takes priority over drivers. */
if (has_action_fcurves) {
FCurve *fcu = BKE_fcurve_find(&animdata->action->curves, rna_path, rna_index);
@ -373,6 +394,7 @@ FCurve *BKE_animadata_fcurve_find_by_rna_path(
}
/* If not animated, check if driven. */
const bool has_drivers = !BLI_listbase_is_empty(&animdata->drivers);
if (has_drivers) {
FCurve *fcu = BKE_fcurve_find(&animdata->drivers, rna_path, rna_index);
@ -462,7 +484,7 @@ FCurve *BKE_fcurve_find_by_rna_context_ui(bContext * /*C*/,
/* Standard F-Curve from animdata - Animation (Action) or Drivers. */
FCurve *fcu = BKE_animadata_fcurve_find_by_rna_path(
adt, rna_path->c_str(), rnaindex, r_action, r_driven);
ptr->owner_id, adt, rna_path->c_str(), rnaindex, r_action, r_driven);
if (fcu != nullptr && r_animdata != nullptr) {
*r_animdata = adt;

View File

@ -360,12 +360,12 @@ bool BKE_lib_override_library_property_is_animated(
const char index_token_start_backup = *index_token_start;
*index_token_start = '\0';
fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, liboverride_prop->rna_path, rnaprop_index, nullptr, nullptr);
id, anim_data, liboverride_prop->rna_path, rnaprop_index, nullptr, nullptr);
*index_token_start = index_token_start_backup;
}
else {
fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, liboverride_prop->rna_path, 0, nullptr, nullptr);
id, anim_data, liboverride_prop->rna_path, 0, nullptr, nullptr);
}
if (fcurve != nullptr) {
return true;

View File

@ -12,6 +12,7 @@ set(INC
../modifiers
../sequencer
../windowmanager
../animrig
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
)

View File

@ -558,8 +558,7 @@ void DepsgraphNodeBuilder::build_id(ID *id, const bool force_be_visible)
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);
build_animation((Animation *)id);
break;
case ID_AR:
build_armature((bArmature *)id);
@ -1222,10 +1221,15 @@ void DepsgraphNodeBuilder::build_animdata(ID *id)
if (adt->action != nullptr) {
build_action(adt->action);
}
if (adt->animation != nullptr) {
build_animation(adt->animation);
}
/* Make sure ID node exists. */
(void)add_id_node(id);
ID *id_cow = get_cow_id(id);
if (adt->action != nullptr || !BLI_listbase_is_empty(&adt->nla_tracks)) {
if (adt->action != nullptr || adt->animation != nullptr ||
!BLI_listbase_is_empty(&adt->nla_tracks))
{
OperationNode *operation_node;
/* Explicit entry operation. */
operation_node = add_operation_node(id, NodeType::ANIMATION, OperationCode::ANIMATION_ENTRY);
@ -1295,6 +1299,15 @@ void DepsgraphNodeBuilder::build_action(bAction *action)
add_operation_node(&action->id, NodeType::ANIMATION, OperationCode::ANIMATION_EVAL);
}
void DepsgraphNodeBuilder::build_animation(Animation *animation)
{
if (built_map_.checkIsBuiltAndTag(animation)) {
return;
}
build_idproperties(animation->id.properties);
add_operation_node(&animation->id, NodeType::ANIMATION, OperationCode::ANIMATION_EVAL);
}
void DepsgraphNodeBuilder::build_driver(ID *id, FCurve *fcurve, int driver_index)
{
/* Create data node for this driver */

View File

@ -19,6 +19,7 @@
#include "DEG_depsgraph.hh"
struct Animation;
struct CacheFile;
struct Camera;
struct Collection;
@ -218,6 +219,7 @@ class DepsgraphNodeBuilder : public DepsgraphBuilder {
*/
virtual void build_animation_images(ID *id);
virtual void build_action(bAction *action);
virtual void build_animation(Animation *animation);
/**
* Build graph node(s) for Driver

View File

@ -90,6 +90,7 @@
#include "RNA_prototypes.h"
#include "RNA_types.hh"
#include "ANIM_animation.hh"
#include "SEQ_iterator.hh"
#include "DEG_depsgraph.hh"
@ -526,8 +527,7 @@ void DepsgraphRelationBuilder::build_id(ID *id)
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);
build_animation((Animation *)id);
break;
case ID_AR:
build_armature((bArmature *)id);
@ -1551,7 +1551,7 @@ void DepsgraphRelationBuilder::build_animdata(ID *id)
{
/* Images. */
build_animation_images(id);
/* Animation curves and NLA. */
/* Animation curves, NLA, and Animation datablock. */
build_animdata_curves(id);
/* Drivers. */
build_animdata_drivers(id);
@ -1573,7 +1573,12 @@ void DepsgraphRelationBuilder::build_animdata_curves(ID *id)
if (adt->action != nullptr) {
build_action(adt->action);
}
if (adt->action == nullptr && BLI_listbase_is_empty(&adt->nla_tracks)) {
if (adt->animation != nullptr) {
build_animation(adt->animation);
}
if (adt->action == nullptr && adt->animation == nullptr &&
BLI_listbase_is_empty(&adt->nla_tracks))
{
return;
}
/* Ensure evaluation order from entry to exit. */
@ -1582,13 +1587,18 @@ void DepsgraphRelationBuilder::build_animdata_curves(ID *id)
OperationKey animation_exit_key(id, NodeType::ANIMATION, OperationCode::ANIMATION_EXIT);
add_relation(animation_entry_key, animation_eval_key, "Init -> Eval");
add_relation(animation_eval_key, animation_exit_key, "Eval -> Exit");
/* Wire up dependency from action. */
/* Wire up dependency from action and Animation datablock. */
ComponentKey adt_key(id, NodeType::ANIMATION);
/* Relation from action itself. */
if (adt->action != nullptr) {
ComponentKey action_key(&adt->action->id, NodeType::ANIMATION);
add_relation(action_key, adt_key, "Action -> Animation");
}
/* Relation from Animation datablock itself. */
if (adt->animation != nullptr) {
ComponentKey animation_key(&adt->animation->id, NodeType::ANIMATION);
add_relation(animation_key, adt_key, "Animation ID -> Animation");
}
/* Get source operations. */
Node *node_from = get_node(adt_key);
BLI_assert(node_from != nullptr);
@ -1601,11 +1611,52 @@ void DepsgraphRelationBuilder::build_animdata_curves(ID *id)
if (adt->action != nullptr) {
build_animdata_curves_targets(id, adt_key, operation_from, &adt->action->curves);
}
if (adt->animation != nullptr) {
build_animdata_animation_targets(
id, adt->binding_handle, adt_key, operation_from, adt->animation);
}
LISTBASE_FOREACH (NlaTrack *, nlt, &adt->nla_tracks) {
build_animdata_nlastrip_targets(id, adt_key, operation_from, &nlt->strips);
}
}
void DepsgraphRelationBuilder::build_animdata_fcurve_target(
ID *id, PointerRNA id_ptr, ComponentKey &adt_key, OperationNode *operation_from, FCurve *fcu)
{
PointerRNA ptr;
PropertyRNA *prop;
int index;
if (!RNA_path_resolve_full(&id_ptr, fcu->rna_path, &ptr, &prop, &index)) {
return;
}
Node *node_to = rna_node_query_.find_node(&ptr, prop, RNAPointerSource::ENTRY);
if (node_to == nullptr) {
return;
}
OperationNode *operation_to = node_to->get_entry_operation();
/* NOTE: Special case for bones, avoid relation from animation to
* each of the bones. Bone evaluation could only start from pose
* init anyway. */
if (operation_to->opcode == OperationCode::BONE_LOCAL) {
OperationKey pose_init_key(id, NodeType::EVAL_POSE, OperationCode::POSE_INIT);
add_relation(adt_key, pose_init_key, "Animation -> Prop", RELATION_CHECK_BEFORE_ADD);
return;
}
graph_->add_new_relation(
operation_from, operation_to, "Animation -> Prop", RELATION_CHECK_BEFORE_ADD);
/* It is possible that animation is writing to a nested ID data-block,
* need to make sure animation is evaluated after target ID is copied. */
const IDNode *id_node_from = operation_from->owner->owner;
const IDNode *id_node_to = operation_to->owner->owner;
if (id_node_from != id_node_to) {
ComponentKey cow_key(id_node_to->id_orig, NodeType::COPY_ON_EVAL);
add_relation(cow_key,
adt_key,
"Animated Copy-on-Eval -> Animation",
RELATION_CHECK_BEFORE_ADD | RELATION_FLAG_NO_FLUSH);
}
}
void DepsgraphRelationBuilder::build_animdata_curves_targets(ID *id,
ComponentKey &adt_key,
OperationNode *operation_from,
@ -1614,37 +1665,45 @@ void DepsgraphRelationBuilder::build_animdata_curves_targets(ID *id,
/* Iterate over all curves and build relations. */
PointerRNA id_ptr = RNA_id_pointer_create(id);
LISTBASE_FOREACH (FCurve *, fcu, curves) {
PointerRNA ptr;
PropertyRNA *prop;
int index;
if (!RNA_path_resolve_full(&id_ptr, fcu->rna_path, &ptr, &prop, &index)) {
continue;
}
Node *node_to = rna_node_query_.find_node(&ptr, prop, RNAPointerSource::ENTRY);
if (node_to == nullptr) {
continue;
}
OperationNode *operation_to = node_to->get_entry_operation();
/* NOTE: Special case for bones, avoid relation from animation to
* each of the bones. Bone evaluation could only start from pose
* init anyway. */
if (operation_to->opcode == OperationCode::BONE_LOCAL) {
OperationKey pose_init_key(id, NodeType::EVAL_POSE, OperationCode::POSE_INIT);
add_relation(adt_key, pose_init_key, "Animation -> Prop", RELATION_CHECK_BEFORE_ADD);
continue;
}
graph_->add_new_relation(
operation_from, operation_to, "Animation -> Prop", RELATION_CHECK_BEFORE_ADD);
/* It is possible that animation is writing to a nested ID data-block,
* need to make sure animation is evaluated after target ID is copied. */
const IDNode *id_node_from = operation_from->owner->owner;
const IDNode *id_node_to = operation_to->owner->owner;
if (id_node_from != id_node_to) {
ComponentKey cow_key(id_node_to->id_orig, NodeType::COPY_ON_EVAL);
add_relation(cow_key,
adt_key,
"Animated Copy-on-Eval -> Animation",
RELATION_CHECK_BEFORE_ADD | RELATION_FLAG_NO_FLUSH);
build_animdata_fcurve_target(id, id_ptr, adt_key, operation_from, fcu);
}
}
void DepsgraphRelationBuilder::build_animdata_animation_targets(ID *id,
const int32_t binding_handle,
ComponentKey &adt_key,
OperationNode *operation_from,
Animation *dna_animation)
{
BLI_assert(id != nullptr);
BLI_assert(operation_from != nullptr);
BLI_assert(dna_animation != nullptr);
PointerRNA id_ptr = RNA_id_pointer_create(id);
animrig::Animation &animation = dna_animation->wrap();
const animrig::Binding *binding = animation.binding_for_handle(binding_handle);
if (binding == nullptr) {
/* If there's no matching binding, there's no animation dependency. */
return;
}
for (animrig::Layer *layer : animation.layers()) {
for (animrig::Strip *strip : layer->strips()) {
switch (strip->type()) {
case animrig::Strip::Type::Keyframe: {
animrig::KeyframeStrip &keyframe_strip = strip->as<animrig::KeyframeStrip>();
animrig::ChannelBag *channels = keyframe_strip.channelbag_for_binding(*binding);
if (channels == nullptr) {
/* Go to next strip. */
break;
}
for (FCurve *fcu : channels->fcurves()) {
build_animdata_fcurve_target(id, id_ptr, adt_key, operation_from, fcu);
}
break;
}
}
}
}
}
@ -1762,6 +1821,21 @@ void DepsgraphRelationBuilder::build_action(bAction *action)
}
}
void DepsgraphRelationBuilder::build_animation(Animation *animation)
{
if (built_map_.checkIsBuiltAndTag(animation)) {
return;
}
const BuilderStack::ScopedEntry stack_entry = stack_.trace(animation->id);
build_idproperties(animation->id.properties);
TimeSourceKey time_src_key;
ComponentKey animation_key(&animation->id, NodeType::ANIMATION);
add_relation(time_src_key, animation_key, "TimeSrc -> Animation");
}
void DepsgraphRelationBuilder::build_driver(ID *id, FCurve *fcu)
{
ChannelDriver *driver = fcu->driver;

View File

@ -32,6 +32,7 @@
#include "intern/node/deg_node_id.hh"
#include "intern/node/deg_node_operation.hh"
struct Animation;
struct CacheFile;
struct Camera;
struct Collection;
@ -165,10 +166,20 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder {
RootPChanMap *root_map);
virtual void build_animdata(ID *id);
virtual void build_animdata_curves(ID *id);
virtual void build_animdata_fcurve_target(ID *id,
PointerRNA id_ptr,
ComponentKey &adt_key,
OperationNode *operation_from,
FCurve *fcu);
virtual void build_animdata_curves_targets(ID *id,
ComponentKey &adt_key,
OperationNode *operation_from,
ListBase *curves);
virtual void build_animdata_animation_targets(ID *id,
int32_t binding_handle,
ComponentKey &adt_key,
OperationNode *operation_from,
Animation *animation);
virtual void build_animdata_nlastrip_targets(ID *id,
ComponentKey &adt_key,
OperationNode *operation_from,
@ -177,6 +188,7 @@ class DepsgraphRelationBuilder : public DepsgraphBuilder {
virtual void build_animdata_force(ID *id);
virtual void build_animation_images(ID *id);
virtual void build_action(bAction *action);
virtual void build_animation(Animation *animation);
virtual void build_driver(ID *id, FCurve *fcurve);
virtual void build_driver_data(ID *id, FCurve *fcurve);
virtual void build_driver_variables(ID *id, FCurve *fcurve);

View File

@ -9,6 +9,7 @@
#include <cstdio>
#include "ANIM_action.hh"
#include "ANIM_animation.hh"
#include "ANIM_animdata.hh"
#include "ANIM_keyframing.hh"
@ -76,6 +77,8 @@
#include "WM_api.hh"
#include "WM_types.hh"
using namespace blender;
/* *********************************************** */
/* XXX constant defines to be moved elsewhere? */
@ -1246,6 +1249,92 @@ static bAnimChannelType ACF_NLACURVE = {
/*setting_ptr*/ acf_fcurve_setting_ptr,
};
/* Object Animation Expander ------------------------------------------- */
/* TODO: just get this from RNA? */
static int acf_fillanim_icon(bAnimListElem * /*ale*/)
{
return ICON_ACTION; /* TODO: give Animation its own icon? */
}
/* check if some setting exists for this channel */
static bool acf_fillanim_setting_valid(bAnimContext * /*ac*/,
bAnimListElem * /*ale*/,
eAnimChannel_Settings setting)
{
switch (setting) {
case ACHANNEL_SETTING_SELECT:
case ACHANNEL_SETTING_EXPAND:
return true;
default:
return false;
}
}
/* Get the appropriate flag(s) for the setting when it is valid. */
static int acf_fillanim_setting_flag(bAnimContext * /*ac*/,
eAnimChannel_Settings setting,
bool *r_neg)
{
*r_neg = false;
switch (setting) {
case ACHANNEL_SETTING_SELECT:
return ADT_UI_SELECTED;
case ACHANNEL_SETTING_EXPAND:
return int(animrig::Animation::Flag::Expanded);
default:
return 0;
}
}
/* get pointer to the setting */
static void *acf_fillanim_setting_ptr(bAnimListElem *ale,
eAnimChannel_Settings setting,
short *r_type)
{
Animation *anim = (Animation *)ale->data;
AnimData *adt = ale->adt;
BLI_assert(anim);
BLI_assert(adt);
*r_type = 0;
switch (setting) {
case ACHANNEL_SETTING_SELECT:
return GET_ACF_FLAG_PTR(adt->flag, r_type);
case ACHANNEL_SETTING_EXPAND:
return GET_ACF_FLAG_PTR(anim->flag, r_type);
default:
return nullptr;
}
}
/** Object Animation expander type define. */
static bAnimChannelType ACF_FILLANIM = {
/*channel_type_name*/ "Ob-Animation Filler",
/*channel_role*/ ACHANNEL_ROLE_EXPANDER,
/*get_backdrop_color*/ acf_generic_dataexpand_color,
/*get_channel_color*/ nullptr,
/*draw_backdrop*/ acf_generic_dataexpand_backdrop,
/*get_indent_level*/ acf_generic_indentation_1,
/*get_offset*/ acf_generic_basic_offset,
/*name*/ acf_generic_idblock_name,
/*name_prop*/ acf_generic_idfill_name_prop,
/*icon*/ acf_fillanim_icon,
/*has_setting*/ acf_fillanim_setting_valid,
/*setting_flag*/ acf_fillanim_setting_flag,
/*setting_ptr*/ acf_fillanim_setting_ptr,
};
/* Object Action Expander ------------------------------------------- */
/* TODO: just get this from RNA? */
@ -4280,6 +4369,7 @@ static void ANIM_init_channel_typeinfo_data()
animchannelTypeInfo[type++] = &ACF_NLACONTROLS; /* NLA Control FCurve Expander */
animchannelTypeInfo[type++] = &ACF_NLACURVE; /* NLA Control FCurve Channel */
animchannelTypeInfo[type++] = &ACF_FILLANIM; /* Object Animation Expander */
animchannelTypeInfo[type++] = &ACF_FILLACTD; /* Object Action Expander */
animchannelTypeInfo[type++] = &ACF_FILLDRIVERS; /* Drivers Expander */

View File

@ -39,6 +39,7 @@
#include "BKE_lib_id.hh"
#include "BKE_mask.h"
#include "BKE_nla.h"
#include "BKE_scene.hh"
#include "BKE_screen.hh"
#include "BKE_workspace.h"
@ -255,6 +256,7 @@ void ANIM_set_active_channel(bAnimContext *ac,
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -311,6 +313,7 @@ void ANIM_set_active_channel(bAnimContext *ac,
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -364,6 +367,7 @@ bool ANIM_is_active_channel(bAnimListElem *ale)
{
switch (ale->type) {
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -500,6 +504,7 @@ static eAnimChannels_SetFlag anim_channels_selection_flag_for_toggle(const ListB
break;
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -614,6 +619,7 @@ static void anim_channels_select_set(bAnimContext *ac,
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -3830,6 +3836,7 @@ static int mouse_anim_channels(bContext *C,
notifierFlags |= click_select_channel_object(C, ac, ale, selectmode);
break;
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:
@ -4575,7 +4582,7 @@ static blender::Vector<FCurve *> get_fcurves_of_property(
const int length = RNA_property_array_length(ptr, prop);
for (int i = 0; i < length; i++) {
FCurve *fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, path->c_str(), i, nullptr, nullptr);
id, anim_data, path->c_str(), i, nullptr, nullptr);
if (fcurve != nullptr) {
fcurves.append(fcurve);
}
@ -4583,7 +4590,7 @@ static blender::Vector<FCurve *> get_fcurves_of_property(
}
else {
FCurve *fcurve = BKE_animadata_fcurve_find_by_rna_path(
anim_data, path->c_str(), index, nullptr, nullptr);
id, anim_data, path->c_str(), index, nullptr, nullptr);
if (fcurve != nullptr) {
fcurves.append(fcurve);
}

View File

@ -55,7 +55,10 @@ void ANIM_list_elem_update(Main *bmain, Scene *scene, bAnimListElem *ale)
adt = BKE_animdata_from_id(id);
if (adt) {
DEG_id_tag_update(id, ID_RECALC_ANIMATION);
if (adt->action != nullptr) {
if (adt->animation != nullptr) {
DEG_id_tag_update(&adt->animation->id, ID_RECALC_ANIMATION);
}
else if (adt->action != nullptr) {
DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION);
}
}

View File

@ -221,27 +221,30 @@ AnimData *ANIM_nla_mapping_get(bAnimContext *ac, bAnimListElem *ale)
/* apart from strictly keyframe-related contexts, this shouldn't even happen */
/* XXX: nla and channel here may not be necessary... */
if (ELEM(ac->datatype,
ANIMCONT_ACTION,
ANIMCONT_SHAPEKEY,
ANIMCONT_DOPESHEET,
ANIMCONT_FCURVES,
ANIMCONT_NLA,
ANIMCONT_CHANNEL,
ANIMCONT_TIMELINE))
if (!ELEM(ac->datatype,
ANIMCONT_ACTION,
ANIMCONT_SHAPEKEY,
ANIMCONT_DOPESHEET,
ANIMCONT_FCURVES,
ANIMCONT_NLA,
ANIMCONT_CHANNEL,
ANIMCONT_TIMELINE))
{
/* handling depends on the type of animation-context we've got */
if (ale) {
/* NLA Control Curves occur on NLA strips,
* and shouldn't be subjected to this kind of mapping. */
if (ale->type != ANIMTYPE_NLACURVE) {
return ale->adt;
}
}
return nullptr;
}
/* cannot handle... */
return nullptr;
/* handling depends on the type of animation-context we've got */
if (!ale) {
return nullptr;
}
/* NLA Control Curves occur on NLA strips,
* and shouldn't be subjected to this kind of mapping. */
if (ale->type == ANIMTYPE_NLACURVE) {
return nullptr;
}
return ale->adt;
}
/* ------------------- */

View File

@ -88,8 +88,11 @@
#include "SEQ_sequencer.hh"
#include "SEQ_utils.hh"
#include "ANIM_animation.hh"
#include "ANIM_bone_collections.hh"
using namespace blender;
/* ************************************************************ */
/* Blender Context <-> Animation Context mapping */
@ -446,14 +449,14 @@ bool ANIM_animdata_can_have_greasepencil(const eAnimCont_Types type)
/* ............................... */
/* quick macro to test if AnimData is usable */
#define ANIMDATA_HAS_KEYS(id) ((id)->adt && (id)->adt->action)
/* quick macro to test if AnimData has usable Action */
#define ANIMDATA_HAS_KEYS(id) ((id)->adt && !(id)->adt->animation && (id)->adt->action)
/* quick macro to test if AnimData is usable for drivers */
#define ANIMDATA_HAS_DRIVERS(id) ((id)->adt && (id)->adt->drivers.first)
/* quick macro to test if AnimData is usable for NLA */
#define ANIMDATA_HAS_NLA(id) ((id)->adt && (id)->adt->nla_tracks.first)
#define ANIMDATA_HAS_NLA(id) ((id)->adt && !(id)->adt->animation && (id)->adt->nla_tracks.first)
/**
* Quick macro to test for all three above usability tests, performing the appropriate provided
@ -478,6 +481,7 @@ bool ANIM_animdata_can_have_greasepencil(const eAnimCont_Types type)
* - driversOk: line or block of code to execute for Drivers case
* - nlaKeysOk: line or block of code for NLA Strip Keyframes case
* - keysOk: line or block of code for Keyframes case
* - animOk: line or block of code for Keyframes from Animation data blocks case
*
* The checks for the various cases are as follows:
* 0) top level: checks for animdata and also that all the F-Curves for the block will be visible
@ -489,8 +493,9 @@ bool ANIM_animdata_can_have_greasepencil(const eAnimCont_Types type)
* 3) drivers: include drivers from animdata block (for Drivers mode in Graph Editor)
* 4A) nla strip keyframes: these are the per-strip controls for time and influence
* 4B) normal keyframes: only when there is an active action
* 4C) normal keyframes: only when there is an Animation assigned
*/
#define ANIMDATA_FILTER_CASES(id, adtOk, nlaOk, driversOk, nlaKeysOk, keysOk) \
#define ANIMDATA_FILTER_CASES(id, adtOk, nlaOk, driversOk, nlaKeysOk, keysOk, animOk) \
{ \
if ((id)->adt) { \
if (!(filter_mode & ANIMFILTER_CURVE_VISIBLE) || \
@ -511,6 +516,9 @@ bool ANIM_animdata_can_have_greasepencil(const eAnimCont_Types type)
driversOk \
} \
} \
else if ((id)->adt->animation) { \
animOk \
} \
else { \
if (ANIMDATA_HAS_NLA(id)) { \
nlaKeysOk \
@ -580,6 +588,36 @@ bool ANIM_animdata_can_have_greasepencil(const eAnimCont_Types type)
/* ----------- 'Private' Stuff --------------- */
/**
* Set `ale` so that it points to the top-most 'summary' channel of the given `adt`.
* So this is either the Animation or the Action, or empty.
*/
static void key_data_from_adt(bAnimListElem &ale, AnimData *adt)
{
ale.adt = adt;
if (!adt) {
ale.key_data = nullptr;
ale.datatype = ALE_NONE;
return;
}
if (adt->animation) {
ale.key_data = adt->animation;
ale.datatype = ALE_ANIM;
return;
}
if (adt->action) {
ale.key_data = adt->action;
ale.datatype = ALE_ACT;
return;
}
ale.key_data = nullptr;
ale.datatype = ALE_NONE;
}
/* this function allocates memory for a new bAnimListElem struct for the
* provided animation channel-data.
*/
@ -634,6 +672,15 @@ static bAnimListElem *make_new_animlistelem(void *data,
ale->adt = BKE_animdata_from_id(&ob->id);
break;
}
case ANIMTYPE_FILLANIM: {
Animation *anim = (Animation *)data;
ale->flag = anim->flag;
ale->key_data = anim;
ale->datatype = ALE_ANIM;
break;
}
case ANIMTYPE_FILLACTD: {
bAction *act = (bAction *)data;
@ -655,244 +702,125 @@ static bAnimListElem *make_new_animlistelem(void *data,
}
case ANIMTYPE_DSMAT: {
Material *ma = (Material *)data;
AnimData *adt = ma->adt;
ale->flag = FILTER_MAT_OBJD(ma);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, ma->adt);
break;
}
case ANIMTYPE_DSLAM: {
Light *la = (Light *)data;
AnimData *adt = la->adt;
ale->flag = FILTER_LAM_OBJD(la);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, la->adt);
break;
}
case ANIMTYPE_DSCAM: {
Camera *ca = (Camera *)data;
AnimData *adt = ca->adt;
ale->flag = FILTER_CAM_OBJD(ca);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, ca->adt);
break;
}
case ANIMTYPE_DSCACHEFILE: {
CacheFile *cache_file = (CacheFile *)data;
AnimData *adt = cache_file->adt;
ale->flag = FILTER_CACHEFILE_OBJD(cache_file);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, cache_file->adt);
break;
}
case ANIMTYPE_DSCUR: {
Curve *cu = (Curve *)data;
AnimData *adt = cu->adt;
ale->flag = FILTER_CUR_OBJD(cu);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, cu->adt);
break;
}
case ANIMTYPE_DSARM: {
bArmature *arm = (bArmature *)data;
AnimData *adt = arm->adt;
ale->flag = FILTER_ARM_OBJD(arm);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, arm->adt);
break;
}
case ANIMTYPE_DSMESH: {
Mesh *mesh = (Mesh *)data;
AnimData *adt = mesh->adt;
ale->flag = FILTER_MESH_OBJD(mesh);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, mesh->adt);
break;
}
case ANIMTYPE_DSLAT: {
Lattice *lt = (Lattice *)data;
AnimData *adt = lt->adt;
ale->flag = FILTER_LATTICE_OBJD(lt);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, lt->adt);
break;
}
case ANIMTYPE_DSSPK: {
Speaker *spk = (Speaker *)data;
AnimData *adt = spk->adt;
ale->flag = FILTER_SPK_OBJD(spk);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, spk->adt);
break;
}
case ANIMTYPE_DSHAIR: {
Curves *curves = (Curves *)data;
AnimData *adt = curves->adt;
ale->flag = FILTER_CURVES_OBJD(curves);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, curves->adt);
break;
}
case ANIMTYPE_DSPOINTCLOUD: {
PointCloud *pointcloud = (PointCloud *)data;
AnimData *adt = pointcloud->adt;
ale->flag = FILTER_POINTS_OBJD(pointcloud);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, pointcloud->adt);
break;
}
case ANIMTYPE_DSVOLUME: {
Volume *volume = (Volume *)data;
AnimData *adt = volume->adt;
ale->flag = FILTER_VOLUME_OBJD(volume);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, volume->adt);
break;
}
case ANIMTYPE_DSSKEY: {
Key *key = (Key *)data;
AnimData *adt = key->adt;
ale->flag = FILTER_SKE_OBJD(key);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, key->adt);
break;
}
case ANIMTYPE_DSWOR: {
World *wo = (World *)data;
AnimData *adt = wo->adt;
ale->flag = FILTER_WOR_SCED(wo);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, wo->adt);
break;
}
case ANIMTYPE_DSNTREE: {
bNodeTree *ntree = (bNodeTree *)data;
AnimData *adt = ntree->adt;
ale->flag = FILTER_NTREE_DATA(ntree);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, ntree->adt);
break;
}
case ANIMTYPE_DSLINESTYLE: {
FreestyleLineStyle *linestyle = (FreestyleLineStyle *)data;
AnimData *adt = linestyle->adt;
ale->flag = FILTER_LS_SCED(linestyle);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, linestyle->adt);
break;
}
case ANIMTYPE_DSPART: {
ParticleSettings *part = (ParticleSettings *)ale->data;
AnimData *adt = part->adt;
ale->flag = FILTER_PART_OBJD(part);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, part->adt);
break;
}
case ANIMTYPE_DSTEX: {
Tex *tex = (Tex *)data;
AnimData *adt = tex->adt;
ale->flag = FILTER_TEX_DATA(tex);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, tex->adt);
break;
}
case ANIMTYPE_DSGPENCIL: {
bGPdata *gpd = (bGPdata *)data;
AnimData *adt = gpd->adt;
/* NOTE: we just reuse the same expand filter for this case */
ale->flag = EXPANDED_GPD(gpd);
/* XXX: currently, this is only used for access to its animation data */
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, gpd->adt);
break;
}
case ANIMTYPE_DSMCLIP: {
MovieClip *clip = (MovieClip *)data;
AnimData *adt = clip->adt;
ale->flag = EXPANDED_MCLIP(clip);
ale->key_data = (adt) ? adt->action : nullptr;
ale->datatype = ALE_ACT;
ale->adt = BKE_animdata_from_id(static_cast<ID *>(data));
key_data_from_adt(*ale, clip->adt);
break;
}
case ANIMTYPE_NLACONTROLS: {
@ -1366,6 +1294,37 @@ static size_t animfilter_fcurves(ListBase *anim_data,
return items;
}
static size_t animfilter_fcurves_span(ListBase * /*bAnimListElem*/ anim_data,
bDopeSheet * /*ads*/,
Span<FCurve *> fcurves,
const int filter_mode,
ID *owner_id,
ID *fcurve_owner_id)
{
size_t num_items = 0;
BLI_assert(owner_id);
for (FCurve *fcu : fcurves) {
/* make_new_animlistelem will return nullptr when fcu == nullptr, and that's
* going to cause problems. */
BLI_assert(fcu);
/* TODO: deal with `filter_mode` and `ads->filterflag`.
* See `animfilter_fcurve_next()`. */
if (filter_mode & ANIMFILTER_TMP_PEEK) {
/* Found an animation channel, which is good enough for the 'TMP_PEEK' mode. */
return 1;
}
bAnimListElem *ale = make_new_animlistelem(fcu, ANIMTYPE_FCURVE, owner_id, fcurve_owner_id);
BLI_addtail(anim_data, ale);
num_items++;
}
return num_items;
}
static size_t animfilter_act_group(bAnimContext *ac,
ListBase *anim_data,
bDopeSheet *ads,
@ -1501,6 +1460,32 @@ static size_t animfilter_action(bAnimContext *ac,
return items;
}
/**
* Add animation list elements for FCurves in an Animation data-block.
*/
static size_t animfilter_animation_fcurves(
ListBase *anim_data, bDopeSheet *ads, AnimData *adt, const int filter_mode, ID *owner_id)
{
/* If this ID is not bound, there is nothing to show. */
if (adt->binding_handle == 0) {
return 0;
}
BLI_assert(adt->animation); /* Otherwise this function wouldn't be called. */
animrig::Animation &anim = adt->animation->wrap();
/* Don't include anything from this animation if it is linked in from another
* file, and we're getting stuff for editing... */
if ((filter_mode & ANIMFILTER_FOREDIT) && (ID_IS_LINKED(&anim) || ID_IS_OVERRIDE_LIBRARY(&anim)))
{
return 0;
}
/* For now we don't show layers anywhere, just the contained F-Curves. */
Span<FCurve *> fcurves = animrig::fcurves_for_animation(anim, adt->binding_handle);
return animfilter_fcurves_span(anim_data, ads, fcurves, filter_mode, owner_id, &anim.id);
}
/* Include NLA-Data for NLA-Editor:
* - When ANIMFILTER_LIST_CHANNELS is used, that means we should be filtering the list for display
* Although the evaluation order is from the first track to the last and then apply the
@ -1692,8 +1677,11 @@ static size_t animfilter_block_data(
{ /* NLA Control Keyframes */
items += animfilter_nla_controls(anim_data, ads, adt, filter_mode, id);
},
{ /* Keyframes */
{ /* Keyframes from Action. */
items += animfilter_action(ac, anim_data, ads, adt->action, filter_mode, id);
},
{ /* Keyframes from Animation. */
items += animfilter_animation_fcurves(anim_data, ads, adt, filter_mode, id);
});
}
@ -2968,10 +2956,15 @@ static size_t animdata_filter_ds_obanim(
expanded = EXPANDED_DRVD(adt);
},
{/* NLA Strip Controls - no dedicated channel for now (XXX) */},
{ /* Keyframes */
{ /* Keyframes from Action. */
type = ANIMTYPE_FILLACTD;
cdata = adt->action;
expanded = EXPANDED_ACTC(adt->action);
},
{ /* Keyframes from Animation. */
type = ANIMTYPE_FILLANIM;
cdata = adt->animation;
expanded = adt->animation->wrap().is_expanded();
});
/* add object-level animation channels */
@ -3147,10 +3140,15 @@ static size_t animdata_filter_ds_scene(
expanded = EXPANDED_DRVD(adt);
},
{/* NLA Strip Controls - no dedicated channel for now (XXX) */},
{ /* Keyframes */
{ /* Keyframes from Action. */
type = ANIMTYPE_FILLACTD;
cdata = adt->action;
expanded = EXPANDED_ACTC(adt->action);
},
{ /* Keyframes from Animation. */
type = ANIMTYPE_FILLANIM;
cdata = adt->animation;
expanded = adt->animation->wrap().is_expanded();
});
/* add scene-level animation channels */

View File

@ -379,6 +379,7 @@ enum class ChannelType {
SCENE,
OBJECT,
FCURVE,
ANIMATION,
ACTION,
ACTION_GROUP,
GREASE_PENCIL_CELS,
@ -404,6 +405,7 @@ struct ChannelListElement {
Object *ob;
AnimData *adt;
FCurve *fcu;
Animation *anim;
bAction *act;
bActionGroup *agrp;
bGPDlayer *gpl;
@ -432,6 +434,10 @@ static void build_channel_keylist(ChannelListElement *elem, blender::float2 rang
fcurve_to_keylist(elem->adt, elem->fcu, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::ANIMATION: {
animation_to_keylist(elem->adt, elem->anim, elem->keylist, elem->saction_flag, range);
break;
}
case ChannelType::ACTION: {
action_to_keylist(elem->adt, elem->act, elem->keylist, elem->saction_flag, range);
break;
@ -700,6 +706,25 @@ void ED_add_action_group_channel(ChannelDrawList *channel_list,
draw_elem->channel_locked = locked;
}
void ED_add_animation_channel(ChannelDrawList *channel_list,
AnimData *adt,
Animation *anim,
const float ypos,
const float yscale_fac,
int saction_flag)
{
BLI_assert(anim);
const bool locked = (anim && (ID_IS_LINKED(anim) || ID_IS_OVERRIDE_LIBRARY(anim)));
saction_flag &= ~SACTION_SHOW_EXTREMES;
ChannelListElement *draw_elem = channel_list_add_element(
channel_list, ChannelType::ANIMATION, ypos, yscale_fac, eSAction_Flag(saction_flag));
draw_elem->adt = adt;
draw_elem->anim = anim;
draw_elem->channel_locked = locked;
}
void ED_add_action_channel(ChannelDrawList *channel_list,
AnimData *adt,
bAction *act,

View File

@ -387,6 +387,9 @@ short ANIM_animchannel_keyframes_loop(KeyframeEditData *ked,
return agrp_keyframes_loop(ked, (bActionGroup *)ale->data, key_ok, key_cb, fcu_cb);
case ALE_ACT: /* action */
return act_keyframes_loop(ked, (bAction *)ale->key_data, key_ok, key_cb, fcu_cb);
case ALE_ANIM: /* Animation data-block. */
printf("ANIM_animchannel_keyframes_loop: not yet implemented for Animation data-blocks\n");
return 0;
case ALE_OB: /* object */
return ob_keyframes_loop(ked, ads, (Object *)ale->key_data, key_ok, key_cb, fcu_cb);
@ -425,6 +428,10 @@ short ANIM_animchanneldata_keyframes_loop(KeyframeEditData *ked,
return agrp_keyframes_loop(ked, (bActionGroup *)data, key_ok, key_cb, fcu_cb);
case ALE_ACT: /* action */
return act_keyframes_loop(ked, (bAction *)data, key_ok, key_cb, fcu_cb);
case ALE_ANIM: /* Animation data-block. */
printf(
"ANIM_animchanneldata_keyframes_loop: not yet implemented for Animation data-blocks\n");
return 0;
case ALE_OB: /* object */
return ob_keyframes_loop(ked, ads, (Object *)data, key_ok, key_cb, fcu_cb);

View File

@ -36,6 +36,8 @@
#include "ED_anim_api.hh"
#include "ED_keyframes_keylist.hh"
#include "ANIM_animation.hh"
/* *************************** Keyframe Processing *************************** */
/* ActKeyColumns (Keyframe Columns) ------------------------------------------ */
@ -1165,6 +1167,25 @@ void action_group_to_keylist(AnimData *adt,
}
}
/**
* Assumption: the animation is bound to adt->binding_handle. This assumption will break when we
* have things like reference strips, where the strip can reference another binding handle.
*/
void animation_to_keylist(AnimData *adt,
Animation *anim,
AnimKeylist *keylist,
const int saction_flag,
blender::float2 range)
{
BLI_assert(adt);
BLI_assert(anim);
BLI_assert(GS(anim->id.name) == ID_AN);
for (FCurve *fcurve : fcurves_for_animation(anim->wrap(), adt->binding_handle)) {
fcurve_to_keylist(adt, fcurve, keylist, saction_flag, range);
}
}
void action_to_keylist(AnimData *adt,
bAction *act,
AnimKeylist *keylist,

View File

@ -158,7 +158,7 @@ struct bAnimListElem {
*/
/** ID block that channel is attached to */
ID *id;
/** source of the animation data attached to ID block (for convenience) */
/** source of the animation data attached to ID block */
AnimData *adt;
/**
@ -201,6 +201,7 @@ enum eAnim_ChannelType {
ANIMTYPE_NLACONTROLS,
ANIMTYPE_NLACURVE,
ANIMTYPE_FILLANIM,
ANIMTYPE_FILLACTD,
ANIMTYPE_FILLDRIVERS,
@ -260,6 +261,7 @@ enum eAnim_KeyType {
ALE_OB, /* Object summary */
ALE_ACT, /* Action summary */
ALE_GROUP, /* Action Group summary */
ALE_ANIM, /* Animation data-block summary. */
ALE_GREASE_PENCIL_CEL, /* Grease Pencil Cels. */
ALE_GREASE_PENCIL_DATA, /* Grease Pencil Cels summary. */

View File

@ -10,6 +10,7 @@
#include "BLI_sys_types.h"
struct Animation;
struct AnimData;
struct ChannelDrawList;
struct FCurve;
@ -65,6 +66,13 @@ void ED_add_action_group_channel(ChannelDrawList *draw_list,
float ypos,
float yscale_fac,
int saction_flag);
/* Animation Summary.*/
void ED_add_animation_channel(ChannelDrawList *channel_list,
AnimData *adt,
Animation *anim,
float ypos,
float yscale_fac,
int saction_flag);
/* Action Summary */
void ED_add_action_channel(ChannelDrawList *draw_list,
AnimData *adt,

View File

@ -11,6 +11,7 @@
#include "BLI_math_vector_types.hh"
#include "BLI_range.h"
struct Animation;
struct AnimData;
struct CacheFile;
struct FCurve;
@ -159,6 +160,9 @@ void action_group_to_keylist(AnimData *adt,
AnimKeylist *keylist,
int saction_flag,
blender::float2 range);
/* Animation */
void animation_to_keylist(
AnimData *adt, Animation *anim, AnimKeylist *keylist, int saction_flag, blender::float2 range);
/* Action */
void action_to_keylist(
AnimData *adt, bAction *act, AnimKeylist *keylist, int saction_flag, blender::float2 range);

View File

@ -18,6 +18,7 @@
/* Types --------------------------------------------------------------- */
#include "DNA_anim_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
@ -221,6 +222,7 @@ static void draw_backdrops(bAnimContext *ac, ListBase &anim_data, View2D *v2d, u
break;
}
case ANIMTYPE_FILLACTD:
case ANIMTYPE_FILLANIM:
case ANIMTYPE_DSSKEY:
case ANIMTYPE_DSWOR: {
immUniformColor3ubvAlpha(col2b, sel ? col1[3] : col2b[3]);
@ -366,6 +368,14 @@ static void draw_keyframes(bAnimContext *ac,
scale_factor,
action_flag);
break;
case ALE_ANIM:
ED_add_animation_channel(draw_list,
adt,
static_cast<Animation *>(ale->key_data),
ycenter,
scale_factor,
action_flag);
break;
case ALE_ACT:
ED_add_action_channel(draw_list,
adt,

View File

@ -112,6 +112,11 @@ static void actkeys_list_element_to_keylist(bAnimContext *ac,
ob_to_keylist(ads, ob, keylist, 0, range);
break;
}
case ALE_ANIM: {
Animation *anim = (Animation *)ale->key_data;
animation_to_keylist(adt, anim, keylist, 0, range);
break;
}
case ALE_ACT: {
bAction *act = (bAction *)ale->key_data;
action_to_keylist(adt, act, keylist, 0, range);

View File

@ -151,6 +151,7 @@ static int mouse_nla_tracks(bContext *C, bAnimContext *ac, int track_index, shor
break;
}
case ANIMTYPE_FILLACTD: /* Action Expander */
case ANIMTYPE_FILLANIM: /* Animation Expander */
case ANIMTYPE_DSMAT: /* Datablock AnimData Expanders */
case ANIMTYPE_DSLAM:
case ANIMTYPE_DSCAM:

View File

@ -1272,6 +1272,9 @@ typedef struct Animation {
int binding_array_num;
int32_t last_binding_handle;
uint8_t flag;
uint8_t _pad0[7];
#ifdef __cplusplus
blender::animrig::Animation &wrap();
const blender::animrig::Animation &wrap() const;

View File

@ -721,8 +721,9 @@ typedef struct UserDef_Experimental {
char use_new_volume_nodes;
char use_shader_node_previews;
char use_extension_repos;
char use_animation_baklava;
char _pad[3];
char _pad[2];
/** `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

@ -4751,6 +4751,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,
@ -377,6 +380,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;
}
@ -500,6 +508,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

@ -24,6 +24,7 @@
#include "rna_internal.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_keyframing.hh"
@ -96,6 +97,8 @@ const EnumPropertyItem rna_enum_keying_flag_api_items[] = {
# include "BKE_fcurve.hh"
# include "BKE_nla.h"
# include "ANIM_animation.hh"
# include "DEG_depsgraph.hh"
# include "DEG_depsgraph_build.hh"
@ -180,6 +183,62 @@ bool rna_AnimData_tweakmode_override_apply(Main * /*bmain*/,
return true;
}
# ifdef WITH_ANIM_BAKLAVA
static void rna_AnimData_animation_set(PointerRNA *ptr, PointerRNA value, ReportList * /*reports*/)
{
BLI_assert(ptr->owner_id);
ID &animated_id = *ptr->owner_id;
Animation *anim = static_cast<Animation *>(value.data);
if (!anim) {
blender::animrig::unassign_animation(animated_id);
return;
}
blender::animrig::assign_animation(anim->wrap(), animated_id);
}
static void rna_AnimData_animation_binding_handle_set(
PointerRNA *ptr, const blender::animrig::binding_handle_t new_binding_handle)
{
BLI_assert(ptr->owner_id);
ID &animated_id = *ptr->owner_id;
/* 'adt' is guaranteed to exist, or otherwise this function could not be called. */
AnimData *adt = BKE_animdata_from_id(&animated_id);
BLI_assert_msg(adt, "ID.animation_data is unexpectedly empty");
if (!adt) {
WM_reportf(RPT_ERROR,
"Data-block '%s' does not have any animation data, how did you set this property?",
animated_id.name + 2);
return;
}
if (new_binding_handle == 0) {
/* No need to check with the Animation, as 'no binding' is always valid. */
adt->binding_handle = 0;
return;
}
blender::animrig::Animation *anim = blender::animrig::get_animation(animated_id);
if (!anim) {
/* No animation to verify the stable index is valid. Just set it, it'll be ignored anyway. */
adt->binding_handle = new_binding_handle;
return;
}
blender::animrig::Binding *binding = anim->binding_for_handle(new_binding_handle);
if (!binding) {
WM_reportf(RPT_ERROR,
"Animation '%s' has no binding with handle %d",
anim->id.name + 2,
new_binding_handle);
return;
}
binding->connect_id(animated_id);
}
# endif
/* ****************************** */
/* wrapper for poll callback */
@ -1440,6 +1499,24 @@ static void rna_def_animdata(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Pin in Graph Editor", "");
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, nullptr);
# ifdef WITH_ANIM_BAKLAVA
/* Animation data-block */
prop = RNA_def_property(srna, "animation", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "Animation");
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_pointer_funcs(prop, nullptr, "rna_AnimData_animation_set", nullptr, nullptr);
RNA_def_property_ui_text(prop, "Animation", "Active Animation for this data-block");
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_AnimData_dependency_update");
prop = RNA_def_property(srna, "animation_binding_handle", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "binding_handle");
RNA_def_property_int_funcs(prop, nullptr, "rna_AnimData_animation_binding_handle_set", nullptr);
RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN, "rna_AnimData_dependency_update");
prop = RNA_def_property(srna, "animation_binding_name", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "binding_name");
# endif
RNA_define_lib_overridable(false);
/* Animation Data API */

View File

@ -0,0 +1,783 @@
/* 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,
"An 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();
binding.connect_id(*animated_id);
anim.binding_name_set(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 binding 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::Animation &anim = rna_animation(ptr);
animrig::Binding &binding_to_find = rna_data_binding(ptr);
Span<animrig::Binding *> bindings = anim.bindings();
for (int i = 0; i < bindings.size(); ++i) {
animrig::Binding &binding = *bindings[i];
if (&binding != &binding_to_find) {
continue;
}
return fmt::format("bindings[{}]", i);
}
return std::nullopt;
}
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_set(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 binding 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();
for (int i = 0; i < strips.size(); ++i) {
animrig::Strip &strip = *strips[i];
if (&strip != &strip_to_find) {
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, i);
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 FCurve *rna_KeyframeAnimationStrip_key_insert(KeyframeAnimationStrip *dna_strip,
ReportList *reports,
AnimationBinding *dna_binding,
const char *rna_path,
const int array_index,
const float value,
const float time)
{
if (dna_binding == nullptr) {
BKE_report(reports, RPT_ERROR, "binding cannot be None");
return nullptr;
}
animrig::KeyframeStrip &key_strip = dna_strip->wrap();
const animrig::Binding &binding = dna_binding->wrap();
const animrig::KeyframeSettings settings = animrig::get_keyframe_settings(true);
FCurve *fcurve = key_strip.keyframe_insert(
binding, rna_path, array_index, {time, value}, settings);
return fcurve;
}
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 an 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");
parm = RNA_def_string(func,
"name",
nullptr,
sizeof(AnimationLayer::name) - 1,
"Name",
"Name of the layer, 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 data-blocks animated by this Animation");
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",
"Reference to a data-block that will be animated by this Animation");
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");
prop = RNA_def_property(srna, "handle", PROP_INT, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
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 infinite strip to the layer");
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 binding of 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 binding of 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");
}
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 FCurves 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");
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);
/* KeyframeStrip.key_insert(...). */
func = RNA_def_function(srna, "key_insert", "rna_KeyframeAnimationStrip_key_insert");
RNA_def_function_flag(func, FUNC_USE_REPORTS);
parm = RNA_def_pointer(func,
"binding",
"AnimationBinding",
"Binding",
"The binding that identifies which 'thing' should be keyed");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_string(func, "data_path", nullptr, 0, "Data Path", "F-Curve data path");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_int(
func,
"array_index",
-1,
-INT_MAX,
INT_MAX,
"Array Index",
"Index of the animated array element, or -1 if the property is not an array",
-1,
4);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_float(func,
"value",
0.0,
-FLT_MAX,
FLT_MAX,
"Value to key",
"Value of the animated property",
-FLT_MAX,
FLT_MAX);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_float(func,
"time",
0.0,
-FLT_MAX,
FLT_MAX,
"Time of the key",
"Time, in frames, of the key",
-FLT_MAX,
FLT_MAX);
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "The FCurve this key was inserted on");
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 FCurves 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_channelbags(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_channelbags(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

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

@ -7189,6 +7189,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
"section of the preferences");
RNA_def_property_boolean_funcs(
prop, nullptr, "rna_PreferencesExperimental_use_extension_repos_set");
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-id, 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,121 @@
# 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 AnimationIDAssignmentTest(unittest.TestCase):
"""Test assigning animations & check reference counts."""
def test_animation_id_assignment(self):
# Create new animation datablock.
anim = bpy.data.animations.new('TestAnim')
self.assertEqual(0, anim.users)
# Assign the animation to the cube,
cube = bpy.data.objects['Cube']
cube_adt = cube.animation_data_create()
cube_adt.animation = anim
self.assertEqual(1, anim.users)
# Assign the animation to the camera as well.
camera = bpy.data.objects['Camera']
camera_adt = camera.animation_data_create()
camera_adt.animation = anim
self.assertEqual(2, anim.users)
# Unassigning should decrement the user count.
cube_adt.animation = None
self.assertEqual(1, anim.users)
# Deleting the camera should also decrement the user count.
bpy.data.objects.remove(camera)
self.assertEqual(0, anim.users)
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()