/* 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 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 layers() const; Span 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 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 slots() const; Span 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 strip_keyframe_data() const; Span 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(action); * \endcode */ template const T &data(const Action &owning_action) const; template 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(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(this->layer_mix_mode); } /* Strip array access. */ Span strips() const; Span 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 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 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 channelbags() const; Span 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 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 fcurves() const; Span 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 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_create_many(Main *bmain, Span 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 channel_groups() const; Span 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 fcurves(); Span 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` 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> 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 fcurves_for_action_slot(Action &action, slot_handle_t slot_handle); Span 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 fcurves_in_action_slot_filtered( bAction *act, slot_handle_t slot_handle, FunctionRef predicate); /** * Return the F-Curves in the given span for which `predicate` returns true. */ Vector fcurves_in_span_filtered(Span fcurves, FunctionRef predicate); /** * Return the F-Curves in the given listbase for which `predicate` returns * true. */ Vector fcurves_in_listbase_filtered(ListBaseT fcurves, FunctionRef 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 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 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(this); } inline const animrig::ChannelGroup &bActionGroup::wrap() const { return *reinterpret_cast(this); } inline animrig::Action &bAction::wrap() { return *reinterpret_cast(this); } inline const animrig::Action &bAction::wrap() const { return *reinterpret_cast(this); } inline animrig::Layer &ActionLayer::wrap() { return *reinterpret_cast(this); } inline const animrig::Layer &ActionLayer::wrap() const { return *reinterpret_cast(this); } inline animrig::Slot &ActionSlot::wrap() { return *reinterpret_cast(this); } inline const animrig::Slot &ActionSlot::wrap() const { return *reinterpret_cast(this); } inline animrig::Strip &ActionStrip::wrap() { return *reinterpret_cast(this); } inline const animrig::Strip &ActionStrip::wrap() const { return *reinterpret_cast(this); } inline animrig::StripKeyframeData &ActionStripKeyframeData::wrap() { return *reinterpret_cast(this); } inline const animrig::StripKeyframeData &ActionStripKeyframeData::wrap() const { return *reinterpret_cast(this); } inline animrig::Channelbag &ActionChannelbag::wrap() { return *reinterpret_cast(this); } inline const animrig::Channelbag &ActionChannelbag::wrap() const { return *reinterpret_cast(this); } } // namespace blender