WIP: Anim: Implement keyframing functionality for the Animation data-block #119669
@ -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,
|
||||
|
@ -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")),
|
||||
),
|
||||
)
|
||||
|
||||
|
120
scripts/startup/bl_ui/temp_anim_layers.py
Normal file
120
scripts/startup/bl_ui/temp_anim_layers.py
Normal 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()
|
@ -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. */
|
||||
|
@ -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
|
||||
|
35
source/blender/animrig/ANIM_evaluation.hh
Normal file
35
source/blender/animrig/ANIM_evaluation.hh
Normal 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
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
403
source/blender/animrig/intern/animation_test.cc
Normal file
403
source/blender/animrig/intern/animation_test.cc
Normal 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
|
@ -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
|
||||
|
303
source/blender/animrig/intern/evaluation.cc
Normal file
303
source/blender/animrig/intern/evaluation.cc
Normal 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 ¤t_result,
|
||||
const Layer ¤t_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 ¤t_result,
|
||||
const Layer ¤t_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
|
122
source/blender/animrig/intern/evaluation_internal.hh
Normal file
122
source/blender/animrig/intern/evaluation_internal.hh
Normal 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
|
313
source/blender/animrig/intern/evaluation_test.cc
Normal file
313
source/blender/animrig/intern/evaluation_test.cc
Normal 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
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
||||
layer.strip_add(Strip::Type::Keyframe);
|
||||
}
|
||||
Strip *strip = layer.strip(0);
|
||||
Sybren A. Stüvel
commented
This code assumes that the strip is a 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);
|
||||
Sybren A. Stüvel
commented
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 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.
|
||||
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) {
|
||||
Sybren A. Stüvel
commented
I think it's fine to write more "boolean" style checks for these, like 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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -12,6 +12,7 @@ set(INC
|
||||
../modifiers
|
||||
../sequencer
|
||||
../windowmanager
|
||||
../animrig
|
||||
# RNA_prototypes.h
|
||||
${CMAKE_BINARY_DIR}/source/blender/makesrna
|
||||
)
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/* ------------------- */
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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. */
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
783
source/blender/makesrna/intern/rna_animation_id.cc
Normal file
783
source/blender/makesrna/intern/rna_animation_id.cc
Normal 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
|
@ -139,6 +139,9 @@ extern BlenderRNA BLENDER_RNA;
|
||||
void RNA_def_ID(BlenderRNA *brna);
|
||||
void RNA_def_action(BlenderRNA *brna);
|
||||
void RNA_def_animation(BlenderRNA *brna);
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_animation_id(BlenderRNA *brna);
|
||||
#endif
|
||||
void RNA_def_animviz(BlenderRNA *brna);
|
||||
void RNA_def_armature(BlenderRNA *brna);
|
||||
void RNA_def_attribute(BlenderRNA *brna);
|
||||
@ -486,6 +489,9 @@ void RNA_def_main_speakers(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_sounds(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_armatures(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
#ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_main_animations(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
#endif
|
||||
void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_palettes(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
void RNA_def_main_gpencil_legacy(BlenderRNA *brna, PropertyRNA *cprop);
|
||||
|
@ -90,6 +90,9 @@ static void rna_Main_filepath_set(PointerRNA *ptr, const char *value)
|
||||
}
|
||||
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(actions)
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(animations)
|
||||
# endif
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(armatures)
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(brushes)
|
||||
RNA_MAIN_LISTBASE_FUNCS_DEF(cachefiles)
|
||||
@ -319,6 +322,14 @@ void RNA_def_main(BlenderRNA *brna)
|
||||
"Actions",
|
||||
"Action data-blocks",
|
||||
RNA_def_main_actions},
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
{"animations",
|
||||
"Animation",
|
||||
"rna_Main_animations_begin",
|
||||
"animations",
|
||||
"Animation data-blocks",
|
||||
RNA_def_main_animations},
|
||||
# endif
|
||||
{"particles",
|
||||
"ParticleSettings",
|
||||
"rna_Main_particles_begin",
|
||||
|
@ -26,6 +26,7 @@
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
# include "BKE_action.h"
|
||||
# include "BKE_animation.hh"
|
||||
# include "BKE_armature.hh"
|
||||
# include "BKE_brush.hh"
|
||||
# include "BKE_camera.h"
|
||||
@ -65,6 +66,7 @@
|
||||
# include "DEG_depsgraph_build.hh"
|
||||
# include "DEG_depsgraph_query.hh"
|
||||
|
||||
# include "DNA_anim_types.h"
|
||||
# include "DNA_armature_types.h"
|
||||
# include "DNA_brush_types.h"
|
||||
# include "DNA_camera_types.h"
|
||||
@ -635,6 +637,22 @@ static bAction *rna_Main_actions_new(Main *bmain, const char *name)
|
||||
return act;
|
||||
}
|
||||
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
static Animation *rna_Main_animations_new(Main *bmain, const char *name)
|
||||
{
|
||||
char safe_name[MAX_ID_NAME - 2];
|
||||
rna_idname_validate(name, safe_name);
|
||||
|
||||
Animation *anim = BKE_animation_add(bmain, safe_name);
|
||||
id_fake_user_clear(&anim->id);
|
||||
id_us_min(&anim->id);
|
||||
|
||||
WM_main_add_notifier(NC_ID | NA_ADDED, nullptr);
|
||||
|
||||
return anim;
|
||||
}
|
||||
# endif
|
||||
|
||||
static ParticleSettings *rna_Main_particles_new(Main *bmain, const char *name)
|
||||
{
|
||||
char safe_name[MAX_ID_NAME - 2];
|
||||
@ -821,6 +839,9 @@ RNA_MAIN_ID_TAG_FUNCS_DEF(speakers, speakers, ID_SPK)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(sounds, sounds, ID_SO)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(armatures, armatures, ID_AR)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(actions, actions, ID_AC)
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(animations, animations, ID_AN)
|
||||
# endif
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(particles, particles, ID_PA)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(palettes, palettes, ID_PAL)
|
||||
RNA_MAIN_ID_TAG_FUNCS_DEF(gpencils, gpencils, ID_GD_LEGACY)
|
||||
@ -1881,6 +1902,49 @@ void RNA_def_main_actions(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
parm = RNA_def_boolean(func, "value", false, "Value", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
# ifdef WITH_ANIM_BAKLAVA
|
||||
void RNA_def_main_animations(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "BlendDataAnimations");
|
||||
srna = RNA_def_struct(brna, "BlendDataAnimations", nullptr);
|
||||
RNA_def_struct_sdna(srna, "Main");
|
||||
RNA_def_struct_ui_text(srna, "Main Animations", "Collection of animation data-blocks");
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_Main_animations_new");
|
||||
RNA_def_function_ui_description(func, "Add a new animation data-block to the main database");
|
||||
parm = RNA_def_string(func, "name", "Animation", 0, "", "Name for the new data-block");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
/* return type */
|
||||
parm = RNA_def_pointer(func, "animation", "Animation", "", "New animation data-block");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
func = RNA_def_function(srna, "remove", "rna_Main_ID_remove");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
RNA_def_function_ui_description(func,
|
||||
"Remove an animation data-block from the current blendfile");
|
||||
parm = RNA_def_pointer(func, "animation", "Animation", "", "Animation to remove");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
|
||||
RNA_def_boolean(
|
||||
func, "do_unlink", true, "", "Unlink all usages of this animation before deleting it");
|
||||
RNA_def_boolean(func,
|
||||
"do_id_user",
|
||||
true,
|
||||
"",
|
||||
"Decrement user counter of all datablocks used by this animation");
|
||||
RNA_def_boolean(
|
||||
func, "do_ui_user", true, "", "Make sure interface does not reference this animation");
|
||||
|
||||
/* Defined via RNA_MAIN_LISTBASE_FUNCS_DEF. */
|
||||
func = RNA_def_function(srna, "tag", "rna_Main_animations_tag");
|
||||
parm = RNA_def_boolean(func, "value", false, "Value", "");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
}
|
||||
# endif
|
||||
void RNA_def_main_particles(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
121
tests/python/bl_animation_id.py
Normal file
121
tests/python/bl_animation_id.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user
Here you could have
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