Files
workinf_Blender_Wasm/blender-5.2.0/source/blender/animrig/ANIM_action.hh
2026-08-12 04:47:48 -04:00

1978 lines
69 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*
* \brief Functions and classes to work with Actions.
*/
#pragma once
#include "ANIM_fcurve.hh"
#include "ANIM_keyframing.hh"
#include "DNA_action_types.h"
#include "DNA_anim_types.h"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BLI_enum_flags.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "RNA_types.hh"
#include <utility>
namespace blender {
struct FCurve;
struct FCurve;
struct ID;
struct Main;
struct PointerRNA;
struct Main;
namespace animrig {
/* Forward declarations for the types defined later in this file. */
class Layer;
class Strip;
class Slot;
/**
* Container of animation data for one or more animated IDs.
*
* An Action broadly consists of four things:
*
* 1. Layers, which contain Strips.
* 2. Strips, which reference StripData.
* 3. StripData: Strip{TYPE}Data contains animation data of the given type. For
* example, StripKeyframeData (currently the only StripData type) contains
* keyframes.
* 4. Slots, which are used as identifiers for subsets of animation data within
* StripData items.
*
* StripData is not stored in the Strips themselves, but rather is stored
* separately at the top level of the Action, and each Strip *references* a
* StripData item. This allows Strip instancing by having more than one Strip
* reference the same StripData item.
*
* Each Action has a set of Slots defined at its top level. The animation data
* within a StripData item is organized into one or more subsets, each of which
* is marked as being for a different Slot.
*
* For an ID to be animated by an Action, the ID must specify both an Action and
* a Slot within that Action. The Slot that the ID uses determines which subset
* of the animation data throughout the Action it is animated by. If an Action
* but no Slot is specified, the ID is simply not animated.
*
* \note Temporary limitations: each Action can only contain one Layer, and each
* Layer can only contain one infinite Strip with no time offset. These
* limitations will be progressively lifted as we implement layered animation
* and non-linear animation functionality for Actions in the future. (See:
* `assert_baklava_phase_1_invariants()`.)
*
* \note This wrapper class for the `bAction` DNA struct only has functionality
* for the layered animation data. The legacy F-Curves (in `bAction::curves`)
* and their groups (in `bAction::groups`) are not managed here. See animrig::versioning.
*
* \see #AnimData::action
* \see #AnimData::slot_handle
* \see assert_baklava_phase_1_invariants()
* \see #animrig::versioning::action_is_layered()
*/
class Action : public bAction {
public:
Action() = default;
/**
* Copy constructor is deleted, as code should use regular ID library
* management functions to duplicate this data-block.
*/
Action(const Action &other) = delete;
/**
* Return whether this Action has any data at all.
*
* \return true when `bAction::layer_array` and `bAction::slot_array` are empty.
*/
bool is_empty() const;
/* Action Layers access. */
Span<const Layer *> layers() const;
Span<Layer *> layers();
const Layer *layer(int64_t index) const;
Layer *layer(int64_t index);
/**
* Create a new layer in this Action.
*
* The new layer is added to the end of the layer array, and will be empty (no
* strips).
*
* \note At the time of writing this comment only a single layer per Action is
* supported in Blender, but this function does NOT enforce that. Be careful!
*
* \param name: The name to give the new layer. If no name is given, a default
* name is used. The name may be altered (e.g. appending ".001") to enforce
* uniqueness within the Action.
*
* \return A reference to the newly created layer.
*
* \see assert_baklava_phase_1_invariants()
*/
Layer &layer_add(std::optional<StringRefNull> name);
/**
* Remove the layer from this Action.
*
* 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);
/**
* Ensure that there is at least one layer with the infinite keyframe strip.
*
* \note Within the limits of Project Baklava Phase 1, this means that there
* will be exactly one layer with one keyframe strip on it.
*/
void layer_keystrip_ensure();
/* Action Slot access. */
Span<const Slot *> slots() const;
Span<Slot *> slots();
const Slot *slot(int64_t index) const;
Slot *slot(int64_t index);
/**
* Return the Slot with the given handle.
*
* \param handle: can be `Slot::unassigned`, in which case `nullptr` is returned.
*
* \return `nullptr` when the slot cannot be found, so either the handle was
* `Slot::unassigned` or some value that does not match any Slot in this Action.
*/
Slot *slot_for_handle(slot_handle_t handle);
const Slot *slot_for_handle(slot_handle_t handle) const;
/**
* Set the slot display name (the part of the identifier after the two-letter
* ID prefix), ensure the resulting identifier is unique, and propagate the
* new identifier to all data-blocks that use it
*
* This has to be done on the Action level to ensure each slot has a unique
* identifier within the Action.
*
* \see #Action::slot_identifier_set
*/
void slot_display_name_set(Main &bmain, Slot &slot, StringRefNull new_display_name);
/**
* Set the slot display name (the part of the identifier after the two-letter
* ID prefix), and ensure the resulting identifier is unique.
*
* This has to be done on the Action level to ensure each slot has a unique
* identifier within the Action.
*
* \note This does NOT propagate the resulting slot identifier to the slot's
* users.
*
* \see #Action::slot_display_name_set
* \see #Action::slot_identifier_propagate
*/
void slot_display_name_define(Slot &slot, StringRefNull new_display_name);
/**
* Set the slot's target ID type, updating the identifier prefix to match and
* ensuring that the resulting identifier is unique.
*
* This has to be done on the Action level to ensure each slot has a unique
* identifier within the Action.
*
* \note This does NOT propagate the identifier to the slot's users. That is
* the caller's responsibility.
*
* \see #Action::slot_identifier_propagate
*/
void slot_idtype_define(Slot &slot, ID_Type idtype);
/**
* Set the slot identifier, ensure it is unique, and propagate the new identifier to
* all data-blocks that use it.
*
* This has to be done on the Action level to ensure each slot has a
* unique identifier within the Action.
*
* \note This does NOT ensure the first two characters match the ID type of
* this slot. This is the caller's responsibility.
*
* \see #Action::slot_identifier_define
* \see #Action::slot_identifier_propagate
*/
void slot_identifier_set(Main &bmain, Slot &slot, StringRefNull new_identifier);
/**
* Set the slot identifier, and ensure it is unique.
*
* \note This does NOT ensure the first two characters match the ID type of
* this slot. This is the caller's responsibility.
*
* \see #Action::slot_identifier_set
* \see #Action::slot_identifier_propagate
*/
void slot_identifier_define(Slot &slot, StringRefNull new_identifier);
/**
* Update the `AnimData::last_slot_identifier` field of any ID that is animated by
* this Slot.
*
* Should be called after `slot_identifier_define(slot)`. 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 slot_identifier_propagate(Main &bmain, const Slot &slot);
/**
* Return the slot in this action with the given identifier, if any.
*
* \return A pointer to the matching slot, or nullptr if no matching slot is
* found.
*/
Slot *slot_find_by_identifier(StringRefNull slot_identifier);
/**
* Create a new Slot.
*
* This method should generally not be used outside of low-level code and
* legacy action versioning code, because it creates a Slot with an
* unspecified intended ID type, which should be avoided. Prefer
* `slot_add_for_id_type()` and `slot_add_for_id()` for adding new slots.
*
* TODO: we should probably rename this method to make it clear that it
* shouldn't be used as the standard way to add a slot.
*
* The slot is given a default name and will be suitable for any ID type.
* After assigning the slot to an ID, it will be changed to only be suitable
* for that ID's type.
*
* \see slot_add_for_id_type()
* \see slot_add_for_id()
*/
Slot &slot_add();
/**
* Create a new, unused Slot for the given ID type.
*
* The returned slot will only be suitable for the specified ID type.
*/
Slot &slot_add_for_id_type(ID_Type idtype);
/**
* Create a new, unused Slot suitable for the given ID.
*
* The slot will be named after `animated_id.adt.last_slot_identifier`, defaulting to the ID's
* name when that is not set. This is done so that toggling Actions works transparently, when
* toggling between `this` and the Action last assigned to the ID.
*
* The slot will only be suitable for the ID's type.
*
* Note that this assigns neither this Action nor the new Slot to the ID. This function
* merely initializes the Slot itself to suitable values to start animating this ID.
*/
Slot &slot_add_for_id(const ID &animated_id);
/**
* Remove a slot, and ALL animation data that belongs to it.
*
* After this call, the reference is no longer valid as the slot will have been freed.
*
* Note that this does NOT unassign this slot from all its users. When the Action is linked into
* another file, that other file cannot be updated, and so missing slots are something that has
* to be handled anyway. Also any new slot on this Action will NOT reuse this slot's handle.
*
* \return true when the layer was found & removed, false if it wasn't found.
*/
bool slot_remove(Slot &slot_to_remove);
/**
* Move the given slot to position `to_slot_index` among the slots of the
* action.
*
* `slot` must belong to this action, and `to_slot_index` must be a
* valid index in the slot array.
*/
void slot_move_to_index(Slot &slot, int to_slot_index);
/**
* Set the active Slot, ensuring only one Slot is flagged as the Active one.
*
* \param slot_handle: if #Slot::unassigned, there will not be any active slot.
* Passing an unknown/invalid slot handle will result in no slot being active.
*/
void slot_active_set(slot_handle_t slot_handle);
/**
* Get the active Slot.
*
* This requires a linear scan of the slots, to find the one with the 'Active' flag set. Storing
* this on the Slot itself has the advantage that the 'active' status of a Slot can be determined
* without requiring access to the owning Action.
*
* As this already does a linear scan for the active slot, the slot is returned as a pointer;
* obtaining the pointer from a handle would require another linear scan to get the pointer,
* whereas obtaining the handle from the pointer is a constant operation.
*/
Slot *slot_active_get();
/**
* Strip data array access.
*/
Span<const StripKeyframeData *> strip_keyframe_data() const;
Span<StripKeyframeData *> strip_keyframe_data();
/**
* Return whether this Action actually has any animation data for the given slot.
*
* \see has_keyframes()
*/
bool is_slot_animated(slot_handle_t slot_handle) const;
/**
* Check if the slot with this handle has any keyframes.
*
* \see is_slot_animated()
*/
bool has_keyframes(slot_handle_t action_slot_handle) const ATTR_WARN_UNUSED_RESULT;
/**
* Return whether the action has one unique point in time keyed.
*
* This is mostly for the pose library, which will have different behavior depending on whether
* an Action corresponds to a "pose" (one keyframe) or "animation snippet" (multiple keyframes).
*
* \return `false` when there is no keyframe at all or keys on different points in time, `true`
* when exactly one point in time is keyed.
*/
bool has_single_frame() const ATTR_WARN_UNUSED_RESULT;
/**
* Returns whether this Action is configured as cyclic.
*/
bool is_cyclic() const ATTR_WARN_UNUSED_RESULT;
/**
* Get the layer that should be used for user-level keyframe insertion.
*
* \return The layer, or nullptr if no layer exists that can currently be used
* for keyframing (e.g. all layers are locked, once we've implemented
* locking).
*/
Layer *get_layer_for_keyframing();
/**
* Retrieve the intended playback frame range of the entire Action.
*
* \return a tuple (start frame, end frame). This is either the manually set range (if enabled),
* or the result of a scan of all F-Curves for their first & last frames.
*
* \see get_frame_range_of_keys()
* \see get_frame_range_of_slot()
*/
float2 get_frame_range() const ATTR_WARN_UNUSED_RESULT;
/**
* Retrieve the intended playback frame range of a slot.
*
* \return a tuple (start frame, end frame). This is either the manually set range (if enabled)
* of the Action, or the result of a scan of all F-Curves of the slot for their first & last
* frames.
*
* \see get_frame_range()
*/
float2 get_frame_range_of_slot(slot_handle_t slot_handle) const ATTR_WARN_UNUSED_RESULT;
/**
* Calculate the extents of this Action.
*
* Performs a scan of all F-Curves for their first & last key frames.
*
* \return tuple (first key frame, last key frame).
*/
float2 get_frame_range_of_keys(bool include_modifiers) const ATTR_WARN_UNUSED_RESULT;
/**
* Set the slot's ID type to that of the animated ID, ensure the identifier
* prefix is set accordingly, and that the identifier is unique within the
* Action.
*
* This is a low-level function, and shouldn't be called directly outside of
* the generic slot-assignment functions.
*
* \note This assumes that the slot has no ID type set yet. If it does, it
* is considered a bug to call this function.
*/
void slot_setup_for_id(Slot &slot, const ID &animated_id);
protected:
/* Friends for the purpose of adding/removing strip data on the action's strip
* data arrays. This is needed for the strip creation and removal code in
* `Strip` and `Layer`'s methods. */
friend Strip;
friend Layer;
/** Return the layer's index, or -1 if not found in this Action. */
int64_t find_layer_index(const Layer &layer) const;
/** Return the slot's index, or -1 if not found in this Action. */
int64_t find_slot_index(const Slot &slot) const;
/**
* Append the given `StripKeyframeData` item to the action's keyframe data
* array.
*
* Note: this takes ownership of `strip_data`.
*
* \return The index of the appended item in the array.
*/
int strip_keyframe_data_append(StripKeyframeData *strip_data);
/**
* Remove the keyframe strip data at `index` if it is no longer used anywhere
* in the action.
*
* If the strip data is unused, it is both removed from the array *and* freed.
* Otherwise no changes are made and the action remains as-is.
*
* Note: this may alter the indices of some strip data items, due to items
* shifting around to fill the gap left by the removed item. This method
* ensures that all indices stored within the action (e.g. in the strips
* themselves) are properly updated to the new values so that everything is
* still referencing the same data. However, if any indices are stored
* *outside* the action, they will no longer be valid.
*/
void strip_keyframe_data_remove_if_unused(int index);
private:
/**
* Create a new slot for this Action, but *don't* add it to the Action's list
* of slots.
*
* This *does* give the slot a slot handle, and also correspondingly updates
* the Action's `last_slot_handle` field, hence why this is a method on
* Action.
*
* This is a low-level function. Prefer `slot_add()` and friends in most
* cases.
*
* \see slot_add()
* \see slot_add_for_id()
*/
Slot &slot_allocate();
/**
* Ensure the slot identifier prefix matches its ID type.
*
* This ensures that the first two characters match the ID type of
* this slot.
*
* \see #Action::slot_identifier_propagate
*/
void slot_identifier_ensure_prefix(Slot &slot);
};
static_assert(sizeof(Action) == sizeof(bAction),
"DNA struct and its C++ wrapper must have the same size");
/**
* Strips define how the actual animation data is mapped onto the layers.
*
* Strips do not technically own their own data, but instead refer to data
* that's stored in arrays directly on the action itself, and specify how that
* data is mapped onto a layer.
*
* Different strips can refer to different types of data, although at the moment
* only one type of strip data is implemented: keyframe animation data.
*/
class Strip : public ActionStrip {
public:
/**
* The possible types of strip data.
*
* Each enum value here corresponds to one data type. It is used to record
* which type of data a strip refers to in the strip's `data_type` field (also
* returned by `Strip::type()`). Each data type also knows which enum value it
* corresponds to, stored in the type's static `TYPE` field.
*/
enum class Type : int8_t { Keyframe = 0 };
/* Strips typically shouldn't be directly constructed or copied, because their
* data is actually stored in arrays on the action, and that data also needs
* to be created and managed along with the strips. */
Strip() = delete;
/**
* Make a shallow copy, effectively creating an *instance* of a strip.
*
* Does *not* make a copy of the strip's data, which is stored in an array on
* the owning action. */
explicit Strip(const Strip &other) = default;
/**
* Creates a new strip of type `type` for `owning_action`, with the strip's
* data created on the relevant data array on `owning_action`.
*
* NOTE: strongly prefer using `Layer::strip_add()`, which creates a strip
* directly on a layer and sidesteps any ambiguities about ownership.
*
* This method does *not* add the strip to a layer. That is the responsibility
* of the caller.
*
* The strip is heap-allocated, and the caller is responsible for ensuring
* that it gets freed or is given an owner (such as a layer) that will later
* free it.
*
* The new strip is initialized to have infinite extent and zero time offset.
*
* \see `Layer::strip_add()`
*/
static Strip &create(Action &owning_action, const Strip::Type type);
/**
* Strip type.
*
* Convenience wrapper to avoid having to do the cast from `int` to
* `Strip::Type` everywhere.
*/
Type type() const
{
return Type(this->strip_type);
}
/**
* Return whether the strip's frame range extends from -infinity to +infinity.
*/
bool is_infinite() const;
/**
* Return whether the given frame is within the strip's frame range.
*
* \note Strip frame ranges are inclusive on both sides.
*/
bool contains_frame(float frame_time) const;
/**
* Return whether the end of the strip's frame range matches the given frame
* time.
*/
bool is_last_frame(float frame_time) const;
/**
* Set the start and end frame.
*
* This directly sets the start/end frames to the values given. It is up to
* the caller to ensure the invariants of the strip itself and of the layer it
* belongs to.
*
* `frame_start` must be less than or equal to `frame_end`. Infinite values
* (negative for `frame_start`, positive for `frame_end`) are supported.
*/
void resize(float frame_start, float frame_end);
/**
* Fetch the strip's data from its owning action.
*
* `T` *must* correspond to the strip's data type. In other words, this must
* hold true: `T::TYPE == strip.type()`.
*
* For example, to get a keyframe strip's data:
*
* \code{.cc}
* StripKeyframeData &strip_data = strip.data<StripKeyframeData>(action);
* \endcode
*/
template<typename T> const T &data(const Action &owning_action) const;
template<typename T> T &data(Action &owning_action);
};
static_assert(sizeof(Strip) == sizeof(ActionStrip),
"DNA struct and its C++ wrapper must have the same size");
/**
* Layers can be stacked on top of each other to define the animation. Each
* layer has a mix mode and an influence (0-1), which define how it is mixed
* with the layers below it.
*
* Layers contain one or more Strips, which in turn contain the animation data
* itself.
*
* Temporary limitation: at most one strip may exist on a layer, and it extends
* from negative to positive infinity.
*
* Note: the invariants around multiple strips (such as strip overlap, ordering
* within the strip array, etc.) have not yet been decided. These will be
* decided and documented when support for multiple strips is added.
*/
class Layer : public ActionLayer {
public:
Layer() = default;
Layer(const Layer &other) = delete;
~Layer();
/**
* Duplicate the layer and its strips, but only make shallow copies of the
* strips.
*
* Specifically, this doesn't duplicate the strip data that's stored in the
* layer's owning action, leaving the fields of the strips themselves
* exactly as-is.
*
* WARNING: this method is primarily used in the code that makes full
* duplicates of actions, where the arrays of strip data are copied separately
* for efficiency. This method's applications are narrow and you probably
* shouldn't use it unless you really know what you're doing.
*/
Layer *duplicate_with_shallow_strip_copies(StringRefNull allocation_name) const;
enum class Flags : uint8_t {
/* Set by default, cleared to mute. */
Enabled = (1 << 0),
};
Flags flags() const
{
return static_cast<Flags>(this->layer_flags);
}
enum class MixMode : int8_t {
/** Channels in this layer override the same channels from underlying layers. */
Replace = 0,
/** Channels in this layer are added to underlying layers as sequential operations. */
Offset = 1,
/** Channels in this layer are added to underlying layers on a per-channel basis. */
Add = 2,
/** Channels in this layer are subtracted to underlying layers on a per-channel basis. */
Subtract = 3,
/** Channels in this layer are multiplied with underlying layers on a per-channel basis. */
Multiply = 4,
};
MixMode mix_mode() const
{
return static_cast<MixMode>(this->layer_mix_mode);
}
/* Strip array access. */
Span<const Strip *> strips() const;
Span<Strip *> strips();
const Strip *strip(int64_t index) const;
Strip *strip(int64_t index);
/**
* Add a new Strip of the given type.
*
* This creates a new infinite strip and appends it to the end of the layer's
* strip array. It does no validation of invariants, and it is up to the
* caller to ensure that invariants hold.
*/
Strip &strip_add(Action &owning_action, 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(Action &owning_action, Strip &strip);
protected:
/**
* Return the index of `strip` in this layer's strip array, or -1 if not found
* in this layer.
*/
int64_t find_strip_index(const Strip &strip) const;
};
static_assert(sizeof(Layer) == sizeof(ActionLayer),
"DNA struct and its C++ wrapper must have the same size");
ENUM_OPERATORS(Layer::Flags);
/**
* Identifier for a sub-set of the animation data inside an Action.
*
* An animatable ID specifies both an `Action*` and an `ActionSlot::handle`
* to identify which F-Curves (and in the future other animation data) it will
* be animated by.
*
* \see #AnimData::slot_handle
*/
class Slot : public ActionSlot {
public:
Slot();
explicit Slot(const Slot &other);
~Slot();
/**
* Update the Slot after reading it from a blend file.
*
* This is a low-level function and should not typically be used. It's only here to let
* blenkernel allocate the runtime struct when reading a Slot from disk, without having to
* share the struct definition itself. */
void blend_read_post();
/**
* Slot handle value indicating that there is no slot assigned.
*/
constexpr static slot_handle_t unassigned = 0;
/**
* Slot identifiers consist of a two-character ID code, then the display name.
* This means that the minimum length of a valid identifier is 3 characters.
*/
constexpr static int identifier_length_min = 3;
constexpr static int identifier_length_max = MAX_ID_NAME;
static_assert(sizeof(AnimData::last_slot_identifier) == identifier_length_max);
static_assert(sizeof(NlaStrip::last_slot_identifier) == identifier_length_max);
/**
* Return a string that represents the Slot's 'idtype'.
*
* E.g "OB" for object, "CA" for camera, etc.
*
* This is different from `identifier_prefix()`: this constructs a
* string directly from the actual 'idtype' field of the Slot, whereas
* `identifier_prefix()` returns the first two characters of the
* identifier string.
*
* This distinction matters in some lower-level code where the two can
* momentarily be out of sync, although this should always be corrected before
* exiting such code so that it's never observable in higher-level code.
*
* \see identifier_prefix()
* \see identifier_ensure_prefix()
*/
std::string idtype_string() const;
/**
* Return the two-character type prefix of this Slot's identifier.
*
* This corresponds to the intended ID type of the slot, e.g "OB" for object,
* "CA" for camera, etc.
*
* This is subtly different from `idtype_string()`. See its documentation for
* details.
*
* \see idtype_string()
* \see identifier_ensure_prefix()
*/
StringRef identifier_prefix() const;
/**
* Return this Slot's identifier without the prefix, also known as the
* "display name".
*
* E.g. if the identifier is "OBCube", then "Cube" is returned.
*
* \see identifier_prefix()
*/
StringRefNull identifier_without_prefix() const;
/**
* Return whether this Slot is suitable to be used by the given ID.
*
* "Suitable" means that one of the following is true:
*
* - The Slot's intended ID type (`idtype`) matches the given ID's type.
* - The Slot's intended ID type is unspecified (see `has_idtype()`).
*
* If either of those hold true, the Slot is considered suitable for the ID.
* Otherwise it is considered unsuitable.
*
* Note that it is possible, but odd, for an ID to use a Slot that is not
* suitable for it. This is discouraged, and a best effort is made to prevent
* this in typical cases, but it is not possible to completely prevent due to
* library linking (e.g. an Action linked from another file may be replaced in
* that other file, causing its Slots to effectively change). Therefore this
* method returning `false` should NOT be taken as a guarantee that this Slot
* will never be used by the given ID or other IDs of the same type.
*
* \see idtype_string()
* \see has_idtype()
*/
bool is_suitable_for(const ID &animated_id) const;
/**
* Return whether this Slot has a specified intended ID type (`idtype`) set.
*
* \see idtype_string()
* \see is_suitable_for()
*/
bool has_idtype() const;
/* Flags access. */
enum class Flags : uint8_t {
/** Expanded/collapsed in animation editors. */
Expanded = (1 << 0),
/** Selected in animation editors. */
Selected = (1 << 1),
/** The active Slot for this Action. Set via a method on the Action. */
Active = (1 << 2),
};
Flags flags() const;
bool is_expanded() const;
void set_expanded(bool expanded);
bool is_selected() const;
void set_selected(bool selected);
bool is_active() const;
/** Return the set of IDs that are animated by this Slot. */
Span<ID *> users(Main &bmain) const;
/**
* Directly return the runtime users vector.
*
* This function does not refresh the users cache, so it may be out of date.
*
* This is a low-level function, and should only be used when calling `users(bmain)` is not
* appropriate.
*
* \see #Slot::users(Main &bmain)
*/
Vector<ID *> runtime_users();
/**
* Register this ID as animated by this Slot.
*
* This is a low-level function and should not typically be used.
* Use #Action::assign_id(slot, animated_id) instead.
*/
void users_add(ID &animated_id);
/**
* Register this ID as no longer animated by this Slot.
*
* This is a low-level function and should not typically be used.
* Use #Action::assign_id(nullptr, animated_id) instead.
*/
void users_remove(ID &animated_id);
/**
* Mark the users cache as 'dirty', triggering a full rebuild next time it is accessed.
*
* This is typically not necessary, and only called from low-level code.
*
* \note This static method invalidates all user caches of all Action Slots.
*
* \see #animrig::internal::rebuild_slot_user_cache()
* \see #bke::animdata::action_slots_user_cache_invalidate(), which is an alternative to
* calling this static method in case the caller only wants to depend on BKE headers.
*/
static void users_invalidate(Main &bmain);
/**
* Ensure the first two characters of this Slot's identifier match its
* intended ID type.
*
* This typically does not need to be called outside of some low-level
* functions. Aside from versioning code that upgrades legacy actions, Slots
* should always be created with a specific intended ID type and corresponding
* identifier prefix that never changes after creation, making this method
* unnecessary.
*
* In the rare cases that a Slot does not have a specified intended ID type,
* this method *still* should typically not be called directly. In those cases
* prefer assigning to an ID (e.g. via `Action::assign_action_slot()`), which
* will set the Slot's intended ID type and identifier prefix to match the
* given ID's type, as well as ensure identifier uniqueness within the Action.
*
* \note This does NOT ensure identifier uniqueness within the Action. That is the
* responsibility of the caller.
*
* \see #assign_action_slot
*/
void identifier_ensure_prefix();
protected:
friend Action;
/**
* Set the 'Active' flag. Only allowed to be called by Action.
*/
void set_active(bool active);
};
static_assert(sizeof(Slot) == sizeof(ActionSlot),
"DNA struct and its C++ wrapper must have the same size");
ENUM_OPERATORS(Slot::Flags);
/**
* Keyframe animation data for a keyframe strip.
*
* This contains a set of Channelbags, up to one for each slot in the owning
* action. Each Channelbag contains the keyframe animation data for the slot it
* corresponds to.
*
* \see ChannelBag
*/
class StripKeyframeData : public ActionStripKeyframeData {
public:
/* Value of `Strip::type()` that corresponds to this type. */
static constexpr Strip::Type TYPE = Strip::Type::Keyframe;
StripKeyframeData() = default;
explicit StripKeyframeData(const StripKeyframeData &other);
~StripKeyframeData();
/* Channelbag array access. */
Span<const Channelbag *> channelbags() const;
Span<Channelbag *> channelbags();
const Channelbag *channelbag(int64_t index) const;
Channelbag *channelbag(int64_t index);
/**
* Find the channelbag for the given slot.
*
* \return nullptr if there is none yet for the given slot.
*/
const Channelbag *channelbag_for_slot(const Slot &slot) const;
Channelbag *channelbag_for_slot(const Slot &slot);
const Channelbag *channelbag_for_slot(slot_handle_t slot_handle) const;
Channelbag *channelbag_for_slot(slot_handle_t slot_handle);
/**
* Add a channelbag for the given slot.
*
* Should only be called when there is no `Channelbag` for this slot yet.
*/
Channelbag &channelbag_for_slot_add(const Slot &slot);
Channelbag &channelbag_for_slot_add(slot_handle_t slot_handle);
/**
* Find the channelbag for the given slot, or if none exists, create it.
*/
Channelbag &channelbag_for_slot_ensure(const Slot &slot);
Channelbag &channelbag_for_slot_ensure(slot_handle_t slot_handle);
/**
* Remove the given channelbag from this strip data.
*
* After this call the reference is no longer valid, as the memory will have been freed.
*
* \return true when the channelbag was found & removed, false if it wasn't found.
*/
bool channelbag_remove(Channelbag &channelbag_to_remove);
/**
* Remove all strip data for the given slot.
*/
void slot_data_remove(slot_handle_t slot_handle);
/**
* Clone the channelbag belonging to the source slot, and assign it to the target slot.
*
* This is typically only called from #duplicate_slot().
*/
void slot_data_duplicate(slot_handle_t source_slot_handle, slot_handle_t target_slot_handle);
/**
* Return the index of `channelbag` in this strip data's channelbag array, or
* -1 if `channelbag` doesn't exist in this strip data.
*/
int64_t find_channelbag_index(const Channelbag &channelbag) const;
SingleKeyingResult keyframe_insert(Main *bmain,
const Slot &slot,
const FCurveDescriptor &fcurve_descriptor,
float2 time_value,
const KeyframeSettings &settings,
eInsertKeyFlags insert_key_flags = INSERTKEY_NOFLAGS,
std::optional<float2> cycle_range = std::nullopt);
};
static_assert(sizeof(StripKeyframeData) == sizeof(ActionStripKeyframeData),
"DNA struct and its C++ wrapper must have the same size");
/**
* Collection of F-Curves, intended for a specific Slot handle.
*
* In addition to F-Curves, Channelbags can also contain ChannelGroups, which
* are used to organize F-Curves within the Channelbag (e.g. all F-Curves for a
* given bone can be put into a ChannelGroup with that bone's name).
*
* \see ChannelGroup
*/
class Channelbag : public ActionChannelbag {
public:
Channelbag() = default;
explicit Channelbag(const Channelbag &other);
~Channelbag();
/* FCurves access. */
Span<const FCurve *> fcurves() const;
Span<FCurve *> fcurves();
const FCurve *fcurve(int64_t index) const;
FCurve *fcurve(int64_t index);
/**
* Find an FCurve matching the fcurve descriptor.
*
* If it cannot be found, `nullptr` is returned.
*/
const FCurve *fcurve_find(const FCurveDescriptor &fcurve_descriptor) const;
FCurve *fcurve_find(const FCurveDescriptor &fcurve_descriptor);
/**
* Find an FCurve matching the fcurve descriptor, or create one if it doesn't
* exist.
*
* \param bmain: Used to tag the dependency graph(s) for relationship
* rebuilding. This is necessary when adding a new F-Curve, as a
* previously-unanimated depsgraph component may become animated now. Can be
* nullptr, in which case the tagging is skipped and is left as the
* responsibility of the caller.
*/
FCurve &fcurve_ensure(Main *bmain, const FCurveDescriptor &fcurve_descriptor);
/**
* Create an F-Curve, but only if it doesn't exist yet in this Channelbag.
*
* \return the F-Curve was created, or nullptr if it already existed.
*
* \param bmain: Used to tag the dependency graph(s) for relationship
* rebuilding. This is necessary when adding a new F-Curve, as a
* previously-unanimated depsgraph component may become animated now. Can be
* nullptr, in which case the tagging is skipped and is left as the
* responsibility of the caller.
*/
FCurve *fcurve_create_unique(Main *bmain, const FCurveDescriptor &fcurve_descriptor);
/**
* Create many F-Curves at once.
*
* Conceptually the same as adding many curves in a loop:
* \code{.cc}
* Vector<FCurve*> res(fcurve_descriptors.size(), nullptr);
* for (int64_t i = 0; i < fcurve_descriptors.size(); i++) {
* const FCurveDescriptor &desc = fcurve_descriptors[i];
* res[i] = this->fcurve_create_unique(bmain, desc);
* }
* return res;
* \endcode
*
* However that is quadratic complexity due to each curve uniqueness check being
* a linear scan, plus invariants rebuilding after each curve.
*
* \return Vector of created F-Curves. Vector size is the same as input span size.
* A vector element can be nullptr if input descriptor has empty RNA path, or if
* if such curve already exists.
*
* \param bmain: Used to tag the dependency graph(s) for relationship
* rebuilding. This is necessary when adding a new F-Curve, as a
* previously-unanimated depsgraph component may become animated now. Can be
* nullptr, in which case the tagging is skipped and is left as the
* responsibility of the caller.
*/
Vector<FCurve *> fcurve_create_many(Main *bmain, Span<FCurveDescriptor> fcurve_descriptors);
/**
* Duplicates the FCurve and changes the data of the duplicate to the given `new_...` values.
* In case an FCurve with `new_path` and `new_array_index` already exists, the keys in it are
* replaced with the keys of old_fcurve and it is moved to `new_group_name`.
*/
FCurve &fcurve_clone(const FCurve &old_fcurve,
StringRefNull new_path,
int new_array_index,
StringRef new_group_name);
/**
* Append an F-Curve to this Channelbag.
*
* This transfers ownership of the F-Curve to this Channelbag, and it is up to
* the caller to ensure that this is valid (e.g. the F-Curve doesn't also
* belong to something else).
*
* The F-Curve will not be member of any group after appending.
*
* This is considered a low-level function. Things like depsgraph relations
* tagging is left to the caller.
*/
void fcurve_append(FCurve &fcurve);
/**
* Remove an F-Curve from the Channelbag.
*
* Additionally, if the F-Curve was the last F-Curve in a channel group, that
* channel group is also deleted.
*
* After this call, if the F-Curve was found, the reference will no longer be
* valid, as the curve will have been freed.
*
* \return true when the F-Curve was found & removed, false if it wasn't found.
*
* \see fcurve_detach
*/
bool fcurve_remove(FCurve &fcurve_to_remove);
/**
* Remove an F-Curve from the Channelbag, identified by its index in the array.
*
* Acts the same as fcurve_remove() except it's a bit more efficient as it
* doesn't need to find the F-Curve in the array first.
*
* \see fcurve_remove
*/
void fcurve_remove_by_index(int64_t fcurve_index);
/**
* Detach an F-Curve from the Channelbag.
*
* Additionally, if the fcurve was the last fcurve in a channel group, that
* channel group is deleted.
*
* The F-Curve is not freed. After the call returns `true`, its ownership has
* transferred to the caller.
*
* \return true when the F-Curve was found & detached, false if it wasn't found.
*
* \see fcurve_remove
*/
bool fcurve_detach(FCurve &fcurve_to_detach);
/**
* Detach an F-Curve from the Channelbag, identified by its index in the array.
*
* Acts the same as fcurve_detach() except it's a bit more efficient as it
* doesn't need to find the F-Curve in the array first.
*
* \see fcurve_detach
*/
void fcurve_detach_by_index(int64_t fcurve_index);
/**
* Move the given fcurve to position `to_fcurve_index` in the fcurve array.
*
* Note: this can indirectly alter channel group memberships, because the
* channel groups don't change what ranges in the fcurve array they cover.
*
* `fcurve` must belong to this channel bag, and `to_fcurve_index` must be a
* valid index in the fcurve array.
*/
void fcurve_move_to_index(FCurve &fcurve, int to_fcurve_index);
/**
* Remove all F-Curves from this Channelbag.
*
* Since all channel groups become empty, this also removes all channel
* groups.
*/
void fcurves_clear();
/* Channel group access. */
Span<const bActionGroup *> channel_groups() const;
Span<bActionGroup *> channel_groups();
const bActionGroup *channel_group(int64_t index) const;
bActionGroup *channel_group(int64_t index);
/**
* Find the first bActionGroup (channel group) with the given name.
*
* Note that channel groups with the same name are allowed, and this simply
* returns the first match.
*
* If no matching group is found, `nullptr` is returned.
*/
const bActionGroup *channel_group_find(StringRef name) const;
bActionGroup *channel_group_find(StringRef name);
/**
* Find the index of the channel group.
*
* \return The index of the channel group if found, or -1 if no such group is
* found.
*/
int channel_group_find_index(const bActionGroup *group) const;
/**
* Find the channel group that contains the fcurve at `fcurve_array_index` as
* a member.
*
* \return The index of the channel group if found, or -1 if no such group is
* found.
*/
int channel_group_containing_index(int fcurve_array_index);
/**
* Create a new empty channel group with the given name.
*
* The new group is added to the end of the channel group array of the
* Channelbag.
*
* This function ensures the group has a unique name, and thus the name of the
* created group may differ from the `name` parameter.
*
* \return A reference to the new channel group.
*/
bActionGroup &channel_group_create(StringRefNull name);
/**
* Find a channel group with the given name, or if none exists create one.
*
* If a new group is created, it's added to the end of the channel group array
* of the Channelbag.
*
* \return A reference to the channel group.
*/
bActionGroup &channel_group_ensure(StringRefNull name);
/**
* Remove the given channel group from the channel bag.
*
* Any fcurves that were part of this group will me moved to just after all
* grouped fcurves.
*
* \return true when the channel group was found & removed, false if it wasn't
* found.
*/
bool channel_group_remove(bActionGroup &group);
/**
* Move the given channel group's to position `to_group_index` among the
* channel groups.
*
* The fcurves in the channel group are moved with it, so that membership
* doesn't change.
*
* `group` must belong to this channel bag, and `to_group_index` must be a
* valid index in the channel group array.
*/
void channel_group_move_to_index(bActionGroup &group, int to_group_index);
/**
* Assigns the given FCurve to the given channel group.
*
* Fails if either doesn't belong to this channel bag, but otherwise always
* succeeds.
*
* \return True on success, false on failure.
*/
bool fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group);
/**
* Removes the given FCurve from the channel group it's in, if any.
*
* As part of removing `fcurve` from its group, `fcurve` is moved to the end
* of the fcurve array. However, if `fcurve` is already ungrouped then this
* method is a no-op.
*
* Fails if the fcurve doesn't belong to this channel bag, but otherwise
* always succeeds.
*
* \return True on success, false on failure.
*/
bool fcurve_ungroup(FCurve &fcurve);
protected:
/**
* Create an F-Curve.
*
* Assumes that there is no such F-Curve yet on this Channelbag. If it is
* uncertain whether this is the case, use `fcurve_create_unique()` instead.
*
* \param bmain: Used to tag the dependency graph(s) for relationship
* rebuilding. This is necessary when adding a new F-Curve, as a
* previously-unanimated depsgraph component may become animated now. Can be
* nullptr, in which case the tagging is skipped and is left as the
* responsibility of the caller.
*/
FCurve &fcurve_create(Main *bmain, const FCurveDescriptor &fcurve_descriptor);
private:
/**
* Remove the channel group at `channel_group_index` from the channel group
* array.
*
* This is a low-level function that *only* manipulates the channel group
* array in the most basic way. It literally just removes the given item from
* the array and frees it, just like `erase()` on `std::vector`.
*
* It specifically does *not* maintain any of the semantic invariants of the
* group array or its relationship to the fcurves.
*
* `restore_channel_group_invariants()` should be called at some point after
* this to restore the semantic invariants.
*
* \see `restore_channel_group_invariants()`
*/
void channel_group_remove_raw(int group_index);
/**
* Restore invariants related to channel groups.
*
* This restores critical invariants and should be called (at some point) any
* time that groups are explicitly modified or that group membership of
* fcurves might change implicitly (e.g. due to moving/adding/removing
* fcurves).
*
* The specific invariants restored by this method are:
* 1. All grouped fcurves should come before all non-grouped fcurves.
* 2. All fcurves should point back to the group they belong to (if any) via
* their `grp` pointer.
*
* This function assumes that the fcurves are already in the correct group
* order (so the first N belong to the first group, which is also of length N,
* etc.). The groups are then updated so their starting index matches this.
* Then the fcurves' `grp` pointer is updated, so that any changes in group
* membership is correctly reflected.
*
* For example, if the mapping of groups to fcurves looks like this (g* are
* the groups, dots indicate ungrouped areas, and f* are the fcurves, so e.g.
* group g0 currently contains f1 and f2, but ought to contain f0 and f1):
*
* \code{.unparsed}
* |..| g0 |..|g1|.....| g2 |..|
* |f0|f1|f2|f3|f4|f5|f6|f7|f8|f9|
* \endcode
*
* Then after calling this function they will look like this:
*
* \code{.unparsed}
* | g0 |g1| g2 |..............|
* |f0|f1|f2|f3|f4|f5|f6|f7|f8|f9|
* \endcode
*
* Note that this specifically does *not* move the fcurves, but rather moves
* the groups *over* the fcurves, changing membership.
*
* The `grp` pointers in the fcurves are then updated to reflect their new
* group membership, using the groups as the source of truth.
*/
void restore_channel_group_invariants();
};
static_assert(sizeof(Channelbag) == sizeof(ActionChannelbag),
"DNA struct and its C++ wrapper must have the same size");
/**
* A group of channels within a Channelbag.
*
* This does *not* own the fcurves--the Channelbag does. This just groups
* fcurves for organizational purposes, e.g. for use in the channel list in the
* animation editors.
*/
class ChannelGroup : public bActionGroup {
public:
/**
* Determine whether this channel group is from a legacy action or a layered action.
*
* TODO: this should be removed, as it's currently only used by code that is
* no longer relevant and should also be removed due to legacy actions no
* longer being supported at runtime.
*
* \return True if it's from a legacy action, false if it's from a layered action.
*/
bool is_legacy() const;
/**
* Get the fcurves in this channel group.
*/
Span<FCurve *> fcurves();
Span<const FCurve *> fcurves() const;
};
static_assert(sizeof(ChannelGroup) == sizeof(bActionGroup),
"DNA struct and its C++ wrapper must have the same size");
/**
* Create a new Action with zero users.
*
* This is basically the same as `BKE_action_add`, except that the Action has
* zero users and it's already wrapped with its C++ wrapper.
*
* \see #BKE_action_add
*/
Action &action_add(Main &bmain, StringRefNull name);
/* ---------- Action & Slot Assignment --------------- */
enum class ActionSlotAssignmentResult : int8_t {
OK = 0,
SlotNotFromAction = 1, /* Slot does not belong to the assigned Action. */
SlotNotSuitable = 2, /* Slot is not suitable for the given ID type. */
MissingAction = 3, /* No Action assigned yet, so cannot assign slot. */
};
/**
* Assign the Action to the ID.
*
* This will make a best-effort guess as to which slot to use, in this
* order;
*
* - By slot handle.
* - By fallback string.
* - By the ID's name (matching against the slot name).
* - If the above do not find a suitable slot, the animated ID will not
* receive any animation and the caller is responsible for creating a slot
* 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 slot found" is reached, this function
* will still return `true` as the Action was successfully assigned.
*/
[[nodiscard]] bool assign_action(bAction *action, ID &animated_id);
/**
* Same as assign_action(action, id) above.
*
* Use this function when you already have the AnimData struct of this ID.
*
* \return true when successful, false otherwise. This can fail when the NLA is in tweak mode (no
* action changes allowed).
*/
[[nodiscard]] bool assign_action(bAction *action, OwnedAnimData owned_adt);
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id);
/**
* Utility function that assigns both an Action and a slot of that Action.
*
* Returns the result of the underlying assign_action_slot() call.
*
* \see assign_action
* \see assign_action_slot
*/
ActionSlotAssignmentResult assign_action_and_slot(Action *action,
Slot *slot_to_assign,
ID &animated_id);
/**
* Assign the Action, ensuring that a Slot is also assigned.
*
* If this Action happens to already be assigned, and a Slot is assigned too, that Slot is
* returned. Otherwise a new Slot is created + assigned.
*
* \returns the assigned slot if the assignment was successful, or `nullptr` otherwise. Reasons the
* assignment can fail is when the given ID is of an animatable type, when the ID is in NLA Tweak
* mode (in which case no Action assignments can happen).
*
* \note Contrary to `assign_action()` this skips the search by slot identifier when the Action is
* already assigned. It should be possible for an animator to un-assign a slot, then create a new
* slot by inserting a new key. This shouldn't auto-assign the old slot (by identifier) and _then_
* insert the key.
*
* \see assign_action()
*/
[[nodiscard]] Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id);
/**
* Same as assign_action, except it assigns to #AnimData::tmpact and #AnimData::tmp_slot_handle.
*/
[[nodiscard]] bool assign_tmpaction(bAction *action, OwnedAnimData owned_adt);
[[nodiscard]] ActionSlotAssignmentResult assign_tmpaction_and_slot_handle(
bAction *action, slot_handle_t slot_handle, OwnedAnimData owned_adt);
/**
* Un-assign the Action assigned to this ID.
*
* Same as calling `assign_action(nullptr, animated_id)`.
*
* \see #animrig::assign_action(ID &animated_id)
*/
[[nodiscard]] bool unassign_action(ID &animated_id);
/**
* Un-assign the Action assigned to this ID.
*
* Same as calling `assign_action(nullptr, owned_adt)`.
*
* \see #animrig::assign_action(OwnedAnimData owned_adt)
*/
[[nodiscard]] bool unassign_action(OwnedAnimData owned_adt);
/**
* Generic function to build Action-assignment logic.
*
* This is a low-level function, intended as a building block for higher-level Action assignment
* functions.
*
* The function is named "generic" as it is independent of whether this is for
* direct assignment to the ID, or to an NLA strip, or an Action Constraint.
*/
[[nodiscard]] bool generic_assign_action(ID &animated_id,
bAction *action_to_assign,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_identifier);
/**
* Generic function to build Slot-assignment logic.
*
* This is a low-level function, intended as a building block for higher-level slot assignment
* functions.
*
* The function is named "generic" as it is independent of whether this is for
* direct assignment to the ID, or to an NLA strip, or an Action Constraint.
*/
[[nodiscard]] ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign,
ID &animated_id,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_identifier);
/**
* Generic function to build Slot Handle-assignment logic.
*
* This is a low-level function, intended as a building block for higher-level slot handle
* assignment functions.
*
* The function is named "generic" as it is independent of whether this is for
* direct assignment to the ID, or to an NLA strip, or an Action Constraint.
*/
[[nodiscard]] ActionSlotAssignmentResult generic_assign_action_slot_handle(
slot_handle_t slot_handle_to_assign,
ID &animated_id,
bAction *&action_ptr_ref,
slot_handle_t &slot_handle_ref,
char *slot_identifier);
/**
* Generic function for finding the slot to auto-assign when the Action is assigned.
*
* This is a low-level function, used by generic_assign_action() to pick a slot.
* It's declared here so that unit tests can reach it.
*
* The function is named "generic" as it is independent of whether this is for
* direct assignment to the ID, or to an NLA strip, or an Action Constraint.
*
* \see #generic_assign_action()
* \see #generic_assign_action_slot()
* \see #generic_assign_action_slot_handle()
*/
[[nodiscard]] Slot *generic_slot_for_autoassign(const ID &animated_id,
Action &action,
StringRefNull last_slot_identifier);
/* --------------- Accessors --------------------- */
/**
* Return the Action of this ID, or nullptr if it has none.
*/
Action *get_action(ID &animated_id);
/**
* Get the Action and the Slot that animate this ID.
*
* \return One of two options:
* - `pair<Action, Slot>` when an Action and a Slot are assigned. In other
* words, when this ID is actually animated by this Action+Slot pair.
* - `nullopt`: when this ID is not animated. This can have several causes: not
* an animatable type, no Action assigned, or no Slot assigned.
*/
std::optional<std::pair<Action *, Slot *>> get_action_slot_pair(ID &animated_id);
const animrig::Channelbag *channelbag_for_action_slot(const Action &action,
slot_handle_t slot_handle);
animrig::Channelbag *channelbag_for_action_slot(Action &action, slot_handle_t slot_handle);
/**
* Return the F-Curves for this specific slot handle.
*
* This is just a utility function, that's intended to become obsolete when multi-layer Actions
* are 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 Actions are getting implemented.
*
* \see #animrig::legacy::fcurves_for_action_slot
*/
Span<FCurve *> fcurves_for_action_slot(Action &action, slot_handle_t slot_handle);
Span<const FCurve *> fcurves_for_action_slot(const Action &action, slot_handle_t slot_handle);
/**
* Find or create a Channelbag on the given action, for the given ID.
*
* This function also ensures that there is a layer and a keyframe strip for the
* channelbag to exist on.
*
* \param dna_action: MUST already be assigned to the animated ID.
*
* \param animated_id: The ID that is animated by this Action. It is used to
* create and assign an appropriate slot if needed when creating the fcurve, and
* set the fcurve color properly
*/
Channelbag &action_channelbag_ensure(bAction &dna_action, ID &animated_id);
/**
* Find or create an F-Curve on the given action that matches the given fcurve
* descriptor.
*
* \param bmain: If not nullptr, this function also ensures that dependency
* graph relationships are rebuilt. This is necessary when adding a new F-Curve,
* as a previously-unanimated depsgraph component may become animated now.
*
* \param action: MUST already be assigned to the animated ID.
*
* \param animated_id: The ID that is animated by this Action. It is used to
* create and assign an appropriate slot if needed when creating the fcurve, and
* set the fcurve color properly
*
* \param fcurve_descriptor: description of the fcurve to lookup/create. Note
* that this is *not* relative to `ptr` (e.g. if `ptr` is not an ID). It should
* contain the exact data path of the fcurve to be looked up/created.
*/
FCurve &action_fcurve_ensure(Main *bmain,
bAction &action,
ID &animated_id,
const FCurveDescriptor &fcurve_descriptor);
/**
* Find or create an F-Curve on the given action that matches the given fcurve
* descriptor.
*
* This function was primarily intended for use with legacy actions, but for
* reasons of expedience it also works with layered actions under the
* following limited circumstances: `ptr` must be non-null and must have an
* `owner_id` that already uses `act`. See the comments in the implementation
* for more details.
*
* \note This function also ensures that dependency graph relationships are
* rebuilt. This is necessary when adding a new F-Curve, as a
* previously-unanimated depsgraph component may become animated now.
*
* \param ptr: RNA pointer for the struct the fcurve is being looked up/created
* for. It is used to create and assign an appropriate slot if needed when
* creating the fcurve, and set the fcurve color properly
*
* \param fcurve_descriptor: description of the fcurve to lookup/create. Note
* that this is *not* relative to `ptr` (e.g. if `ptr` is not an ID). It should
* contain the exact data path of the fcurve to be looked up/created.
*
* \see action_fcurve_ensure for a function that is specific to layered actions,
* and is easier to use because it does not depend on an RNA pointer.
*/
FCurve *action_fcurve_ensure_ex(Main *bmain,
bAction *act,
PointerRNA *ptr,
const FCurveDescriptor &fcurve_descriptor);
/**
* Find the F-Curve in the given Action.
*
* All the Action slots are searched for this F-Curve. To limit to a single
* slot, use fcurve_find_in_action_slot().
*
* \see #animrig::fcurve_find_in_action_slot
*/
FCurve *fcurve_find_in_action(bAction *act, const FCurveDescriptor &fcurve_descriptor);
/**
* Find the F-Curve in the given Action Slot.
*
* \see #animrig::fcurve_find_in_action
*/
FCurve *fcurve_find_in_action_slot(bAction *act,
slot_handle_t slot_handle,
const FCurveDescriptor &fcurve_descriptor);
/**
* Find the F-Curve in the Action Slot assigned to this ADT.
*
* \see #animrig::fcurve_find_in_action
*/
FCurve *fcurve_find_in_assigned_slot(AnimData &adt, const FCurveDescriptor &fcurve_descriptor);
/**
* Return whether `fcurve` targets the given collection path + data name.
*
* For example, to match F-Curves for the pose bone named `"botje"`, you'd pass
* `collection_rna_path = "pose.bones["` and `data_name="botje"`.
*
* \return True if `fcurve` matches, false if it doesn't.
*/
bool fcurve_matches_collection_path(const FCurve &fcurve,
StringRefNull collection_rna_path,
StringRefNull data_name);
/**
* Return the F-Curves in the given action+slot for which `predicate` returns
* true.
*/
Vector<FCurve *> fcurves_in_action_slot_filtered(
bAction *act, slot_handle_t slot_handle, FunctionRef<bool(const FCurve &fcurve)> predicate);
/**
* Return the F-Curves in the given span for which `predicate` returns true.
*/
Vector<FCurve *> fcurves_in_span_filtered(Span<FCurve *> fcurves,
FunctionRef<bool(const FCurve &fcurve)> predicate);
/**
* Return the F-Curves in the given listbase for which `predicate` returns
* true.
*/
Vector<FCurve *> fcurves_in_listbase_filtered(ListBaseT<FCurve> fcurves,
FunctionRef<bool(const FCurve &fcurve)> predicate);
/**
* Remove the given FCurve from the action by searching for it in all channelbags.
* This assumes that an FCurve can only exist in an action once.
*
* \returns true if the given FCurve was removed.
*
* \see action_fcurve_detach
*/
bool action_fcurve_remove(Action &action, FCurve &fcu);
/**
* Detach the F-Curve from the Action, searching for it in all channelbags.
*
* The F-Curve is not freed, and ownership is transferred to the caller.
*
* \see action_fcurve_remove
* \see action_fcurve_attach
* \see action_fcurve_move
*
* \return true when the F-Curve was found and detached, false if not found.
*/
bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach);
/**
* Attach the F-Curve to the Action Slot.
*
*
* This assumes the 'Baklava Phase 1' invariants (one layer,
* one keyframe strip).
*
* \see action_fcurve_detach
* \see action_fcurve_move
*/
void action_fcurve_attach(Action &action,
slot_handle_t action_slot,
FCurve &fcurve_to_attach,
std::optional<StringRefNull> group_name);
/**
* Move an F-Curve from one Action to the other.
*
* If the F-Curve was part of a channel group, the group membership also carries
* over to the destination Action. If no group with the same name exists, it is
* created. This only happens for layered Actions, though.
*
* The F-Curve must exist on the source Action. All channelbags for all slots
* are searched for the F-Curve.
*
* \param action_slot_dst: may not be #Slot::unassigned on layered Actions.
*
* \see #animrig::action_fcurve_detach
*/
void action_fcurve_move(Action &action_dst,
slot_handle_t action_slot_dst,
Action &action_src,
FCurve &fcurve);
/**
* Moves all F-Curves from one Channelbag to the other.
*
* The Channelbags do not need to be part of the same action, or even belong to
* an action at all.
*
* If the F-Curves belonged to channel groups, the group membership also carries
* over to the destination Channelbag. If groups with the same names don't
* exist, they are created. \see #animrig::action_fcurve_detach
*
* The order of existing channel groups in the destination Channelbag are not
* changed, and any new groups are placed after those in the order they appeared
* in the src group.
*/
void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src);
/**
* Find an appropriate user of the given Action + Slot for keyframing purposes.
*
* Usually this function shouldn't be necessary, because you'll already have an
* obvious ID that you're keying. But in some cases (such as the action editor
* where multiple slots are accessible) the active ID that would normally get
* keyed might have nothing to do with the slot that's actually getting keyed.
*
* This function handles such cases by attempting to find an actual user of the
* slot that's appropriate for keying. More specifically:
*
* - If `primary_id` is a user of the slot, `primary_id` is always returned.
* - If the slot has precisely one user, that user is returned.
* - Otherwise, nullptr is returned.
*
* In other words, the cases where a user of the slot is *not* returned are:
*
* - The slot has no users at all.
* - The slot has multiple users, none of which are `primary_id`, and therefore
* there is no single, clear user that can be appropriately used for keying.
*
* \param primary_id: whenever this is among the users of the action + slot, it
* is given priority and is returned. May be null.
*/
ID *action_slot_get_id_for_keying(Main &bmain,
Action &action,
slot_handle_t slot_handle,
ID *primary_id);
/**
* Make a best-effort guess as to which ID* is animated by the given slot.
*
* This is only used in rare cases; usually the ID* for which operations are
* performed is known.
*
* \note This function was specifically written because the 'display name' of an
* F-Curve can only be determined by resolving its RNA path, and for that an ID*
* is necessary. It would be better to cache that name on the F-Curve itself, so
* that this constant resolving (for drawing, filtering by name, etc.) isn't
* necessary any more.
*/
ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id);
/**
* Return the handle of the first slot of this Action.
*
* This is for code that needs to treat Actions as somewhat-legacy Actions, i.e. as holders of
* F-Curves for which the specific slot is not interesting.
*
* TODO: Maybe at some point this function should get extended with an ID type parameter, to return
* the first slot that is suitable for that ID type.
*
* \return The handle of the first slot, or #Slot::unassigned if there is no slot.
*/
slot_handle_t first_slot_handle(const bAction &dna_action);
/**
* Assert the invariants of Project Baklava phase 1.
*
* For an action the invariants are that it:
* - has zero layers.
* - OR has a single layer that adheres to the phase 1 invariants for layers.
*
* For a layer the invariants are that it:
* - Has zero strips.
* - OR has a single strip that adheres to the phase 1 invariants for strips.
*
* For a strip the invariants are that it:
* - Is a keyframe strip.
* - AND is infinite.
* - AND has no time offset (i.e. aligns with scene time).
*
* This simultaneously serves as a todo marker for later phases of Project
* Baklava and ensures that the phase-1 invariants hold at runtime.
*
* TODO: these functions should be changed to assert fewer and fewer assumptions
* as we progress through the phases of Project Baklava and more and more of the
* new animation system is implemented. Finally, they should be removed entirely
* when the full system is completely implemented.
*/
void assert_baklava_phase_1_invariants(const Action &action);
/** \copydoc assert_baklava_phase_1_invariants(const Action &) */
void assert_baklava_phase_1_invariants(const Layer &layer);
/** \copydoc assert_baklava_phase_1_invariants(const Action &) */
void assert_baklava_phase_1_invariants(const Strip &strip);
/**
* Move the given slot from `from_action` to `to_action`.
* The slot identifier might not be exactly the same if the identifier already exists in the slots
* of `to_action`. Also the slot handle is likely going to be different on `to_action`. All users
* of the slot will be reassigned to the moved slot on `to_action`.
*
* \note The `from_action` will not be deleted by this function. But it might leave it without
* users which means it will not be saved (unless it has a fake user).
*/
void move_slot(Main &bmain, Slot &slot, Action &from_action, Action &to_action);
/**
* Duplicate a slot, and all its animation data.
*
* Data-blocks using the slot are not updated, so the returned slot will be unused.
*
* The `action` MUST own `slot`.
*/
Slot &duplicate_slot(Action &action, const Slot &slot);
/**
* Deselect the keys of all actions in the Span. Duplicate entries are only visited once.
*/
void deselect_keys_actions(Span<bAction *> actions);
/**
* Deselect all keys within the action.
*/
void action_deselect_keys(Action &action);
} // namespace animrig
/* Wrap functions for the DNA structs. */
inline animrig::ChannelGroup &bActionGroup::wrap()
{
return *reinterpret_cast<animrig::ChannelGroup *>(this);
}
inline const animrig::ChannelGroup &bActionGroup::wrap() const
{
return *reinterpret_cast<const animrig::ChannelGroup *>(this);
}
inline animrig::Action &bAction::wrap()
{
return *reinterpret_cast<animrig::Action *>(this);
}
inline const animrig::Action &bAction::wrap() const
{
return *reinterpret_cast<const animrig::Action *>(this);
}
inline animrig::Layer &ActionLayer::wrap()
{
return *reinterpret_cast<animrig::Layer *>(this);
}
inline const animrig::Layer &ActionLayer::wrap() const
{
return *reinterpret_cast<const animrig::Layer *>(this);
}
inline animrig::Slot &ActionSlot::wrap()
{
return *reinterpret_cast<animrig::Slot *>(this);
}
inline const animrig::Slot &ActionSlot::wrap() const
{
return *reinterpret_cast<const animrig::Slot *>(this);
}
inline animrig::Strip &ActionStrip::wrap()
{
return *reinterpret_cast<animrig::Strip *>(this);
}
inline const animrig::Strip &ActionStrip::wrap() const
{
return *reinterpret_cast<const animrig::Strip *>(this);
}
inline animrig::StripKeyframeData &ActionStripKeyframeData::wrap()
{
return *reinterpret_cast<animrig::StripKeyframeData *>(this);
}
inline const animrig::StripKeyframeData &ActionStripKeyframeData::wrap() const
{
return *reinterpret_cast<const animrig::StripKeyframeData *>(this);
}
inline animrig::Channelbag &ActionChannelbag::wrap()
{
return *reinterpret_cast<animrig::Channelbag *>(this);
}
inline const animrig::Channelbag &ActionChannelbag::wrap() const
{
return *reinterpret_cast<const animrig::Channelbag *>(this);
}
} // namespace blender