2023-11-07 13:17:43 +01:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
|
|
|
|
/** \file
|
|
|
|
|
* \ingroup animrig
|
|
|
|
|
*/
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
#include "DNA_action_types.h"
|
|
|
|
|
#include "DNA_anim_types.h"
|
|
|
|
|
#include "DNA_array_utils.hh"
|
|
|
|
|
#include "DNA_defaults.h"
|
2024-09-13 15:04:47 +02:00
|
|
|
#include "DNA_scene_types.h"
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2023-11-07 13:17:43 +01:00
|
|
|
#include "BLI_listbase.h"
|
2024-09-06 17:24:13 +02:00
|
|
|
#include "BLI_map.hh"
|
2024-05-30 12:23:58 +02:00
|
|
|
#include "BLI_math_base.h"
|
2023-11-07 13:17:43 +01:00
|
|
|
#include "BLI_string.h"
|
2024-05-30 12:23:58 +02:00
|
|
|
#include "BLI_string_utf8.h"
|
|
|
|
|
#include "BLI_string_utils.hh"
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
#include "BLI_utildefines.h"
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
#include "BKE_action.hh"
|
|
|
|
|
#include "BKE_anim_data.hh"
|
|
|
|
|
#include "BKE_fcurve.hh"
|
|
|
|
|
#include "BKE_lib_id.hh"
|
|
|
|
|
#include "BKE_main.hh"
|
2024-09-10 11:08:10 +02:00
|
|
|
#include "BKE_nla.hh"
|
2024-09-26 17:29:01 +02:00
|
|
|
#include "BKE_report.hh"
|
2023-11-24 13:05:10 +01:00
|
|
|
|
|
|
|
|
#include "RNA_access.hh"
|
|
|
|
|
#include "RNA_path.hh"
|
2024-07-10 18:30:02 +02:00
|
|
|
#include "RNA_prototypes.hh"
|
2023-11-07 13:17:43 +01:00
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
|
|
|
|
|
|
#include "DEG_depsgraph_build.hh"
|
|
|
|
|
|
|
|
|
|
#include "ANIM_action.hh"
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
#include "ANIM_action_iterators.hh"
|
2024-09-19 14:46:25 +02:00
|
|
|
#include "ANIM_action_legacy.hh"
|
Fix: inserting keys in Action editor for non-active ID doesn't work
After #122672 landed, slots other than the the slot for the currently active
ID became accessible from the Action Editor. However, the code for inserting
keys directly in the Action Editor assumed that the ID being keyed was
always the active one. This made inserting keys on slots other than the slot
for the active ID fail because the RNA paths wouldn't resolve for that ID.
Additionally, even if they did resolve (e.g. if the ID type was the same), the
keying code would use the wrong ID anyway, which would result in unexpected
behavior regardless.
This commit fixes this by looking up an ID that actually uses the given slot,
and using that ID to perform the keying.
Note that there are various corner cases that have to be handled, such as when
there is more than one user of a slot (and none of them are the active ID),
which makes the choice ambiguous, or when there are no users of a slot. We
handle these corner cases by simply keying the fcurves directly, rather than
going through the normal keyframing code paths. This means that in those corner
cases the value of the property in the scene is ignored, and the fcurve is
simply keyed with whatever value the fcurve evaluates to on that frame. We may
revisit this in the future if this fallback behavior turns out to not feel good
to users in practice, but for now it seems like a reasonable solution.
Pull Request: https://projects.blender.org/blender/blender/pulls/124227
2024-07-09 11:25:45 +02:00
|
|
|
#include "ANIM_animdata.hh"
|
2024-05-30 12:23:58 +02:00
|
|
|
#include "ANIM_fcurve.hh"
|
2024-09-10 11:08:10 +02:00
|
|
|
|
2024-06-13 14:27:36 +02:00
|
|
|
#include "action_runtime.hh"
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
2023-11-07 13:17:43 +01:00
|
|
|
namespace blender::animrig {
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
namespace {
|
|
|
|
|
/**
|
2024-11-26 12:11:06 +01:00
|
|
|
* Default identifier for action slots. The first two characters in the identifier indicate the ID
|
|
|
|
|
* type of whatever is animated by it.
|
2024-05-30 12:23:58 +02:00
|
|
|
*
|
2025-01-27 15:15:33 +01:00
|
|
|
* Since the ID type might not be determined when the slot is created, the prefix starts out at
|
|
|
|
|
* XX (see below). Note that no code should use this XX value; use Slot::has_idtype() instead.
|
2024-05-30 12:23:58 +02:00
|
|
|
*/
|
2024-07-05 16:59:34 +02:00
|
|
|
constexpr const char *slot_default_name = "Slot";
|
2025-01-27 15:15:33 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Slot identifier prefix for untyped slots (i.e. where `Slot::has_idtype()` returns `false`).
|
|
|
|
|
*/
|
|
|
|
|
constexpr const char *slot_untyped_prefix = "XX";
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
constexpr const char *layer_default_name = "Layer";
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
static animrig::Layer &ActionLayer_alloc()
|
|
|
|
|
{
|
|
|
|
|
ActionLayer *layer = DNA_struct_default_alloc(ActionLayer);
|
|
|
|
|
return layer->wrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Copied from source/blender/blenkernel/intern/grease_pencil.cc.
|
|
|
|
|
* Keep an eye on DNA_array_utils.hh; we may want to move these functions in there. */
|
|
|
|
|
template<typename T> static void grow_array(T **array, int *num, const int add_num)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(add_num > 0);
|
|
|
|
|
const int new_array_num = *num + add_num;
|
2024-08-23 10:53:36 +02:00
|
|
|
T *new_array = MEM_cnew_array<T>(new_array_num, "animrig::action/grow_array");
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
blender::uninitialized_relocate_n(*array, *num, new_array);
|
|
|
|
|
MEM_SAFE_FREE(*array);
|
|
|
|
|
|
|
|
|
|
*array = new_array;
|
|
|
|
|
*num = new_array_num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename T> static void grow_array_and_append(T **array, int *num, T item)
|
|
|
|
|
{
|
|
|
|
|
grow_array(array, num, 1);
|
|
|
|
|
(*array)[*num - 1] = item;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
template<typename T>
|
|
|
|
|
static void grow_array_and_insert(T **array, int *num, const int index, T item)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(index >= 0 && index <= *num);
|
|
|
|
|
const int new_array_num = *num + 1;
|
|
|
|
|
T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
|
|
|
|
|
|
|
|
|
|
blender::uninitialized_relocate_n(*array, index, new_array);
|
|
|
|
|
new_array[index] = item;
|
|
|
|
|
blender::uninitialized_relocate_n(*array + index, *num - index, new_array + index + 1);
|
|
|
|
|
|
|
|
|
|
MEM_SAFE_FREE(*array);
|
|
|
|
|
|
|
|
|
|
*array = new_array;
|
|
|
|
|
*num = new_array_num;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
template<typename T> static void shrink_array(T **array, int *num, const int shrink_num)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(shrink_num > 0);
|
|
|
|
|
const int new_array_num = *num - shrink_num;
|
2024-08-23 10:53:36 +02:00
|
|
|
T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
blender::uninitialized_move_n(*array, new_array_num, new_array);
|
|
|
|
|
MEM_freeN(*array);
|
|
|
|
|
|
|
|
|
|
*array = new_array;
|
|
|
|
|
*num = new_array_num;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
template<typename T> static void shrink_array_and_remove(T **array, int *num, const int index)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(index >= 0 && index < *num);
|
|
|
|
|
const int new_array_num = *num - 1;
|
|
|
|
|
T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
|
|
|
|
|
|
|
|
|
|
blender::uninitialized_move_n(*array, index, new_array);
|
|
|
|
|
blender::uninitialized_move_n(*array + index + 1, *num - index - 1, new_array + index);
|
|
|
|
|
MEM_freeN(*array);
|
|
|
|
|
|
|
|
|
|
*array = new_array;
|
|
|
|
|
*num = new_array_num;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 13:07:15 +02:00
|
|
|
/**
|
|
|
|
|
* Same as `shrink_array_and_remove()` above, except instead of shifting all the
|
|
|
|
|
* elements after the removed item over to fill the gap, it just swaps in the last
|
|
|
|
|
* element to where the removed element was.
|
|
|
|
|
*/
|
|
|
|
|
template<typename T> static void shrink_array_and_swap_remove(T **array, int *num, const int index)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(index >= 0 && index < *num);
|
|
|
|
|
const int new_array_num = *num - 1;
|
|
|
|
|
T *new_array = MEM_cnew_array<T>(new_array_num, __func__);
|
|
|
|
|
|
|
|
|
|
blender::uninitialized_move_n(*array, index, new_array);
|
|
|
|
|
if (index < new_array_num) {
|
|
|
|
|
new_array[index] = (*array)[new_array_num];
|
|
|
|
|
blender::uninitialized_move_n(*array + index + 1, *num - index - 2, new_array + index + 1);
|
|
|
|
|
}
|
|
|
|
|
MEM_freeN(*array);
|
|
|
|
|
|
|
|
|
|
*array = new_array;
|
|
|
|
|
*num = new_array_num;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
/**
|
|
|
|
|
* Moves the given (end exclusive) range to index `to`, shifting other items
|
|
|
|
|
* before/after to make room.
|
|
|
|
|
*
|
|
|
|
|
* The range is moved such that the *start* ends up at `to`.
|
|
|
|
|
*
|
|
|
|
|
* `to` *must* be far away enough from the end of the array for the entire range
|
|
|
|
|
* to be moved there without spilling over the end of the array.
|
|
|
|
|
*/
|
|
|
|
|
template<typename T>
|
|
|
|
|
static void array_shift_range(
|
|
|
|
|
T *array, const int num, const int range_start, const int range_end, const int to)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(range_start <= range_end);
|
|
|
|
|
BLI_assert(range_end <= num);
|
|
|
|
|
BLI_assert(to <= num + range_start - range_end);
|
2024-08-23 13:10:36 +10:00
|
|
|
UNUSED_VARS_NDEBUG(num);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
2024-08-26 11:34:42 +10:00
|
|
|
if (ELEM(range_start, range_end, to)) {
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (to < range_start) {
|
|
|
|
|
T *start = array + to;
|
|
|
|
|
T *mid = array + range_start;
|
|
|
|
|
T *end = array + range_end;
|
|
|
|
|
std::rotate(start, mid, end);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
T *start = array + range_start;
|
|
|
|
|
T *mid = array + range_end;
|
|
|
|
|
T *end = array + to + range_end - range_start;
|
|
|
|
|
std::rotate(start, mid, end);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
/* ----- Action implementation ----------- */
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
bool Action::is_empty() const
|
|
|
|
|
{
|
2024-07-06 14:19:04 +10:00
|
|
|
/* The check for emptiness has to include the check for an empty `groups` ListBase because of the
|
2024-07-05 17:56:45 +02:00
|
|
|
* animation filtering code. With the functions `rearrange_action_channels` and
|
|
|
|
|
* `join_groups_action_temp` the ownership of FCurves is temporarily transferred to the `groups`
|
|
|
|
|
* ListBase leaving `curves` potentially empty. */
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->layer_array_num == 0 && this->slot_array_num == 0 &&
|
2024-07-05 17:56:45 +02:00
|
|
|
BLI_listbase_is_empty(&this->curves) && BLI_listbase_is_empty(&this->groups);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
bool Action::is_action_legacy() const
|
|
|
|
|
{
|
|
|
|
|
/* This is a valid legacy Action only if there is no layered info. */
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->layer_array_num == 0 && this->slot_array_num == 0;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
bool Action::is_action_layered() const
|
|
|
|
|
{
|
|
|
|
|
/* This is a valid layered Action if there is ANY layered info (because that
|
|
|
|
|
* takes precedence) or when there is no legacy info. */
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->layer_array_num > 0 || this->slot_array_num > 0 ||
|
2024-07-05 17:56:45 +02:00
|
|
|
(BLI_listbase_is_empty(&this->curves) && BLI_listbase_is_empty(&this->groups));
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blender::Span<const Layer *> Action::layers() const
|
|
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<const Layer *>{reinterpret_cast<Layer **>(this->layer_array),
|
|
|
|
|
this->layer_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-09-30 11:51:14 +02:00
|
|
|
blender::Span<Layer *> Action::layers()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<Layer *>{reinterpret_cast<Layer **>(this->layer_array),
|
|
|
|
|
this->layer_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
const Layer *Action::layer(const int64_t index) const
|
|
|
|
|
{
|
|
|
|
|
return &this->layer_array[index]->wrap();
|
|
|
|
|
}
|
|
|
|
|
Layer *Action::layer(const int64_t index)
|
|
|
|
|
{
|
|
|
|
|
return &this->layer_array[index]->wrap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-27 14:34:24 +02:00
|
|
|
Layer &Action::layer_add(const std::optional<StringRefNull> name)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
Layer &new_layer = ActionLayer_alloc();
|
2024-09-27 14:34:24 +02:00
|
|
|
if (name.has_value()) {
|
|
|
|
|
STRNCPY_UTF8(new_layer.name, name.value().c_str());
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-01-13 17:40:22 +01:00
|
|
|
STRNCPY_UTF8(new_layer.name, DATA_(layer_default_name));
|
2024-09-27 14:34:24 +02:00
|
|
|
}
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
grow_array_and_append<::ActionLayer *>(&this->layer_array, &this->layer_array_num, &new_layer);
|
|
|
|
|
this->layer_active_index = this->layer_array_num - 1;
|
|
|
|
|
|
2024-06-10 15:57:47 +02:00
|
|
|
/* If this is the first layer in this Action, it means that it could have been
|
|
|
|
|
* used as a legacy Action before. As a result, this->idroot may be non-zero
|
|
|
|
|
* while it should be zero for layered Actions.
|
|
|
|
|
*
|
|
|
|
|
* And since setting this to 0 when it is already supposed to be 0 is fine,
|
|
|
|
|
* there is no check for whether this is actually the first layer. */
|
|
|
|
|
this->idroot = 0;
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
return new_layer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void layer_ptr_destructor(ActionLayer **dna_layer_ptr)
|
|
|
|
|
{
|
|
|
|
|
Layer &layer = (*dna_layer_ptr)->wrap();
|
|
|
|
|
MEM_delete(&layer);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool Action::layer_remove(Layer &layer_to_remove)
|
|
|
|
|
{
|
|
|
|
|
const int64_t layer_index = this->find_layer_index(layer_to_remove);
|
|
|
|
|
if (layer_index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dna::array::remove_index(&this->layer_array,
|
|
|
|
|
&this->layer_array_num,
|
|
|
|
|
&this->layer_active_index,
|
|
|
|
|
layer_index,
|
|
|
|
|
layer_ptr_destructor);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 12:06:23 +02:00
|
|
|
void Action::layer_keystrip_ensure()
|
2024-06-07 13:26:18 +02:00
|
|
|
{
|
2024-07-25 12:06:23 +02:00
|
|
|
/* Ensure a layer. */
|
|
|
|
|
Layer *layer;
|
|
|
|
|
if (this->layers().is_empty()) {
|
|
|
|
|
layer = &this->layer_add(DATA_(layer_default_name));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
layer = this->layer(0);
|
2024-06-07 13:26:18 +02:00
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
/* Ensure a keyframe Strip. */
|
2024-07-25 12:06:23 +02:00
|
|
|
if (layer->strips().is_empty()) {
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
layer->strip_add(*this, Strip::Type::Keyframe);
|
2024-07-25 12:06:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Within the limits of Baklava Phase 1, the above code should not have
|
|
|
|
|
* created more than one layer, or more than one strip on the layer. And if a
|
|
|
|
|
* layer + strip already existed, that must have been a keyframe strip. */
|
|
|
|
|
assert_baklava_phase_1_invariants(*this);
|
2024-06-07 13:26:18 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
int64_t Action::find_layer_index(const Layer &layer) const
|
|
|
|
|
{
|
|
|
|
|
for (const int64_t layer_index : this->layers().index_range()) {
|
|
|
|
|
const Layer *visit_layer = this->layer(layer_index);
|
|
|
|
|
if (visit_layer == &layer) {
|
|
|
|
|
return layer_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-03 15:48:00 +02:00
|
|
|
int64_t Action::find_slot_index(const Slot &slot) const
|
|
|
|
|
{
|
|
|
|
|
for (const int64_t slot_index : this->slots().index_range()) {
|
|
|
|
|
const Slot *visit_slot = this->slot(slot_index);
|
|
|
|
|
if (visit_slot == &slot) {
|
|
|
|
|
return slot_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
blender::Span<const Slot *> Action::slots() const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-09-30 11:51:14 +02:00
|
|
|
blender::Span<Slot *> Action::slots()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<Slot *>{reinterpret_cast<Slot **>(this->slot_array), this->slot_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
const Slot *Action::slot(const int64_t index) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return &this->slot_array[index]->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot *Action::slot(const int64_t index)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return &this->slot_array[index]->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot *Action::slot_for_handle(const slot_handle_t handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
const Slot *slot = const_cast<const Action *>(this)->slot_for_handle(handle);
|
|
|
|
|
return const_cast<Slot *>(slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
const Slot *Action::slot_for_handle(const slot_handle_t handle) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
if (handle == Slot::unassigned) {
|
2024-07-04 14:44:19 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
/* TODO: implement hash-map lookup. */
|
2024-07-05 16:59:34 +02:00
|
|
|
for (const Slot *slot : slots()) {
|
|
|
|
|
if (slot->handle == handle) {
|
|
|
|
|
return slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
static void slot_identifier_ensure_unique(Action &action, Slot &slot)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
/* Cannot capture parameters by reference in the lambda, as that would change its signature
|
|
|
|
|
* and no longer be compatible with BLI_uniquename_cb(). That's why this struct is necessary. */
|
|
|
|
|
struct DupNameCheckData {
|
2024-07-05 17:01:50 +02:00
|
|
|
Action &action;
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
};
|
2024-07-05 17:01:50 +02:00
|
|
|
DupNameCheckData check_data = {action, slot};
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
auto check_name_is_used = [](void *arg, const char *name) -> bool {
|
|
|
|
|
DupNameCheckData *data = static_cast<DupNameCheckData *>(arg);
|
2024-07-05 17:01:50 +02:00
|
|
|
for (const Slot *slot : data->action.slots()) {
|
2024-07-05 16:59:34 +02:00
|
|
|
if (slot == &data->slot) {
|
|
|
|
|
/* Don't compare against the slot that's being renamed. */
|
2024-05-30 12:23:58 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-11-26 12:11:06 +01:00
|
|
|
if (STREQ(slot->identifier, name)) {
|
2024-05-30 12:23:58 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_uniquename_cb(
|
|
|
|
|
check_name_is_used, &check_data, "", '.', slot.identifier, sizeof(slot.identifier));
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:04:37 +01:00
|
|
|
void Action::slot_display_name_set(Main &bmain, Slot &slot, StringRefNull new_display_name)
|
2025-02-03 14:48:39 +01:00
|
|
|
{
|
|
|
|
|
this->slot_display_name_define(slot, new_display_name);
|
|
|
|
|
this->slot_identifier_propagate(bmain, slot);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Action::slot_display_name_define(Slot &slot, StringRefNull new_display_name)
|
2024-12-02 17:04:37 +01:00
|
|
|
{
|
|
|
|
|
BLI_assert_msg(StringRef(new_display_name).size() >= 1,
|
|
|
|
|
"Action Slot display names must not be empty");
|
|
|
|
|
BLI_assert_msg(StringRef(slot.identifier).size() >= 2,
|
|
|
|
|
"Action Slot's existing identifier lacks the two-character type prefix, which "
|
|
|
|
|
"would make the display name copy meaningless due to early null termination.");
|
|
|
|
|
|
|
|
|
|
BLI_strncpy_utf8(slot.identifier + 2, new_display_name.c_str(), ARRAY_SIZE(slot.identifier) - 2);
|
|
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 13:01:47 +01:00
|
|
|
void Action::slot_idtype_define(Slot &slot, ID_Type idtype)
|
|
|
|
|
{
|
|
|
|
|
slot.idtype = idtype;
|
|
|
|
|
slot.identifier_ensure_prefix();
|
|
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
void Action::slot_identifier_set(Main &bmain, Slot &slot, const StringRefNull new_identifier)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
/* TODO: maybe this function should only set the 'identifier without prefix' aka the 'display
|
|
|
|
|
* name'. That way only `this->id_type` is responsible for the prefix. I (Sybren) think that's
|
|
|
|
|
* easier to determine when the code is a bit more mature, and we can see what the majority of
|
|
|
|
|
* the calls to this function actually do/need. */
|
2024-11-02 17:27:09 +11:00
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
this->slot_identifier_define(slot, new_identifier);
|
|
|
|
|
this->slot_identifier_propagate(bmain, slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
void Action::slot_identifier_define(Slot &slot, const StringRefNull new_identifier)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_assert_msg(
|
|
|
|
|
StringRef(new_identifier).size() >= Slot::identifier_length_min,
|
|
|
|
|
"Action Slot identifiers must be large enough for a 2-letter ID code + the display name");
|
|
|
|
|
STRNCPY_UTF8(slot.identifier, new_identifier.c_str());
|
|
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
void Action::slot_identifier_propagate(Main &bmain, const Slot &slot)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
/* Just loop over all animatable IDs in the main database. */
|
|
|
|
|
ListBase *lb;
|
|
|
|
|
ID *id;
|
|
|
|
|
FOREACH_MAIN_LISTBASE_BEGIN (&bmain, lb) {
|
|
|
|
|
FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
|
|
|
|
|
if (!id_can_have_animdata(id)) {
|
|
|
|
|
/* This ID type cannot have any animation, so ignore all and continue to
|
|
|
|
|
* the next ID type. */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AnimData *adt = BKE_animdata_from_id(id);
|
|
|
|
|
if (!adt || adt->action != this) {
|
2024-07-05 17:01:50 +02:00
|
|
|
/* Not animated by this Action. */
|
2024-05-30 12:23:58 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
if (adt->slot_handle != slot.handle) {
|
|
|
|
|
/* Not animated by this Slot. */
|
2024-05-30 12:23:58 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
/* Ensure the Slot identifier on the AnimData is correct. */
|
Anim: rename `action_slot_name` to `last_slot_identifier`
`AnimData`, NLA strips, and action constraints all have an `action_slot_name`
field in RNA. The purpose of this field is to store the identifier of the most
recently assigned slot, so that it can be used for auto-assignment when later
assigning different actions.
However, this field name is misleading in two ways:
1. In accordance with #130740, it's actually the slot *identifier*, not name.
2. It could be mistaken as a way to rename the currently assigned slot, which it
is not.
To resolve both of those issues, we're renaming the field to
`last_slot_identifier`, which better communicates its actual nature.
As a bonus, this also ends up decluttering Python autocomplete when looking
for things related to action_slot.
Ref: #130892
Pull Request: https://projects.blender.org/blender/blender/pulls/130911
2024-11-26 16:05:40 +01:00
|
|
|
STRNCPY_UTF8(adt->last_slot_identifier, slot.identifier);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
FOREACH_MAIN_LISTBASE_ID_END;
|
|
|
|
|
}
|
|
|
|
|
FOREACH_MAIN_LISTBASE_END;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
Slot *Action::slot_find_by_identifier(const StringRefNull slot_identifier)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
for (Slot *slot : slots()) {
|
2024-11-26 12:11:06 +01:00
|
|
|
if (STREQ(slot->identifier, slot_identifier.c_str())) {
|
2024-07-05 16:59:34 +02:00
|
|
|
return slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &Action::slot_allocate()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &slot = *MEM_new<Slot>(__func__);
|
|
|
|
|
this->last_slot_handle++;
|
2024-07-05 17:01:50 +02:00
|
|
|
BLI_assert_msg(this->last_slot_handle > 0, "Action Slot handle overflow");
|
2024-07-05 16:59:34 +02:00
|
|
|
slot.handle = this->last_slot_handle;
|
2024-07-04 14:44:19 +02:00
|
|
|
|
|
|
|
|
/* Set the default flags. These cannot be set via the 'DNA defaults' system,
|
|
|
|
|
* as that would require knowing which bit corresponds with which flag. That's
|
|
|
|
|
* only known to the C++ wrapper code. */
|
2024-07-05 16:59:34 +02:00
|
|
|
slot.set_expanded(true);
|
|
|
|
|
return slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &Action::slot_add()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &slot = this->slot_allocate();
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2025-01-27 15:15:33 +01:00
|
|
|
/* Assign the default name and the 'untyped' identifier prefix. */
|
|
|
|
|
STRNCPY_UTF8(slot.identifier, slot_untyped_prefix);
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
/* Append the Slot to the Action. */
|
|
|
|
|
grow_array_and_append<::ActionSlot *>(&this->slot_array, &this->slot_array_num, &slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
2024-06-10 15:57:47 +02:00
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
/* If this is the first slot in this Action, it means that it could have
|
2024-06-10 15:57:47 +02:00
|
|
|
* been used as a legacy Action before. As a result, this->idroot may be
|
|
|
|
|
* non-zero while it should be zero for layered Actions.
|
|
|
|
|
*
|
|
|
|
|
* And since setting this to 0 when it is already supposed to be 0 is fine,
|
|
|
|
|
* there is no check for whether this is actually the first layer. */
|
|
|
|
|
this->idroot = 0;
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
return slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:04:37 +01:00
|
|
|
Slot &Action::slot_add_for_id_type(const ID_Type idtype)
|
|
|
|
|
{
|
|
|
|
|
Slot &slot = this->slot_add();
|
|
|
|
|
|
|
|
|
|
slot.idtype = idtype;
|
|
|
|
|
slot.identifier_ensure_prefix();
|
|
|
|
|
BLI_strncpy_utf8(slot.identifier + 2, DATA_(slot_default_name), ARRAY_SIZE(slot.identifier) - 2);
|
|
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
|
|
|
|
|
|
|
|
|
/* No need to call anim.slot_identifier_propagate() as nothing will be using
|
|
|
|
|
* this brand new Slot yet. */
|
|
|
|
|
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &Action::slot_add_for_id(const ID &animated_id)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot &slot = this->slot_add();
|
|
|
|
|
slot.idtype = GS(animated_id.name);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-11 11:20:00 +11:00
|
|
|
/* Determine the identifier for this slot, prioritizing transparent
|
2024-12-09 12:57:06 +01:00
|
|
|
* auto-selection when toggling between Actions. That's why the last-used slot
|
|
|
|
|
* identifier is used here, and the ID name only as fallback. */
|
|
|
|
|
const AnimData *adt = BKE_animdata_from_id(&animated_id);
|
|
|
|
|
const StringRefNull last_slot_identifier = adt ? adt->last_slot_identifier : "";
|
|
|
|
|
|
|
|
|
|
StringRefNull slot_identifier = last_slot_identifier;
|
|
|
|
|
if (slot_identifier.is_empty()) {
|
|
|
|
|
slot_identifier = animated_id.name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->slot_identifier_define(slot, slot_identifier);
|
2024-11-26 12:11:06 +01:00
|
|
|
/* No need to call anim.slot_identifier_propagate() as nothing will be using
|
2024-07-05 16:59:34 +02:00
|
|
|
* this brand new Slot yet. */
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-09 12:57:06 +01:00
|
|
|
/* The last-used slot might have had a different ID type through some quirk (changes to linked
|
|
|
|
|
* data, for example). So better ensure that the identifier prefix is correct on this new slot,
|
|
|
|
|
* instead of relying for 100% on the old one. */
|
|
|
|
|
slot.identifier_ensure_prefix();
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
return slot;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-03 15:48:00 +02:00
|
|
|
static void slot_ptr_destructor(ActionSlot **dna_slot_ptr)
|
|
|
|
|
{
|
|
|
|
|
Slot &slot = (*dna_slot_ptr)->wrap();
|
|
|
|
|
MEM_delete(&slot);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool Action::slot_remove(Slot &slot_to_remove)
|
|
|
|
|
{
|
|
|
|
|
/* Check that this slot belongs to this Action. */
|
|
|
|
|
const int64_t slot_index = this->find_slot_index(slot_to_remove);
|
|
|
|
|
if (slot_index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remove the slot's data from each layer. */
|
|
|
|
|
for (Layer *layer : this->layers()) {
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
layer->slot_data_remove(*this, slot_to_remove.handle);
|
2024-09-03 15:48:00 +02:00
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
/* Don't bother un-assigning this slot from its users. The slot handle will
|
|
|
|
|
* not be reused by a new slot anyway. */
|
2024-09-03 15:48:00 +02:00
|
|
|
|
|
|
|
|
/* Remove the actual slot. */
|
|
|
|
|
dna::array::remove_index(
|
|
|
|
|
&this->slot_array, &this->slot_array_num, nullptr, slot_index, slot_ptr_destructor);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 10:24:47 +01:00
|
|
|
void Action::slot_move_to_index(Slot &slot, const int to_slot_index)
|
2025-01-28 17:05:58 +01:00
|
|
|
{
|
|
|
|
|
BLI_assert(this->slots().index_range().contains(to_slot_index));
|
|
|
|
|
|
|
|
|
|
const int from_slot_index = this->slots().first_index_try(&slot);
|
|
|
|
|
BLI_assert_msg(from_slot_index >= 0, "Slot not in this action.");
|
|
|
|
|
|
|
|
|
|
array_shift_range(
|
|
|
|
|
this->slot_array, this->slot_array_num, from_slot_index, from_slot_index + 1, to_slot_index);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-12 11:59:04 +02:00
|
|
|
void Action::slot_active_set(const slot_handle_t slot_handle)
|
|
|
|
|
{
|
|
|
|
|
for (Slot *slot : slots()) {
|
|
|
|
|
slot->set_active(slot->handle == slot_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Slot *Action::slot_active_get()
|
|
|
|
|
{
|
|
|
|
|
for (Slot *slot : slots()) {
|
|
|
|
|
if (slot->is_active()) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
bool Action::is_slot_animated(const slot_handle_t slot_handle) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
if (slot_handle == Slot::unassigned) {
|
2024-05-30 12:23:58 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
Span<const FCurve *> fcurves = fcurves_for_action_slot(*this, slot_handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
return !fcurves.is_empty();
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
int Action::strip_keyframe_data_append(StripKeyframeData *strip_data)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(strip_data != nullptr);
|
|
|
|
|
|
|
|
|
|
grow_array_and_append<ActionStripKeyframeData *>(
|
|
|
|
|
&this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, strip_data);
|
|
|
|
|
|
|
|
|
|
return this->strip_keyframe_data_array_num - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 13:07:15 +02:00
|
|
|
void Action::strip_keyframe_data_remove_if_unused(const int index)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(index >= 0 && index < this->strip_keyframe_data_array_num);
|
|
|
|
|
|
|
|
|
|
/* Make sure the data isn't being used anywhere. */
|
2024-09-21 22:46:00 +10:00
|
|
|
for (const Layer *layer : this->layers()) {
|
|
|
|
|
for (const Strip *strip : layer->strips()) {
|
2024-09-20 13:07:15 +02:00
|
|
|
if (strip->type() == Strip::Type::Keyframe && strip->data_index == index) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Free the item to be removed. */
|
|
|
|
|
MEM_delete<StripKeyframeData>(
|
|
|
|
|
static_cast<StripKeyframeData *>(this->strip_keyframe_data_array[index]));
|
|
|
|
|
|
|
|
|
|
/* Remove the item, swapping in the item at the end of the array. */
|
|
|
|
|
shrink_array_and_swap_remove<ActionStripKeyframeData *>(
|
|
|
|
|
&this->strip_keyframe_data_array, &this->strip_keyframe_data_array_num, index);
|
|
|
|
|
|
|
|
|
|
/* Update strips that pointed at the swapped-in item.
|
|
|
|
|
*
|
|
|
|
|
* Note that we don't special-case the corner-case where the removed data was
|
|
|
|
|
* at the end of the array, but it ends up not mattering because then
|
|
|
|
|
* `old_index == index`. */
|
|
|
|
|
const int old_index = this->strip_keyframe_data_array_num;
|
|
|
|
|
for (Layer *layer : this->layers()) {
|
|
|
|
|
for (Strip *strip : layer->strips()) {
|
|
|
|
|
if (strip->type() == Strip::Type::Keyframe && strip->data_index == old_index) {
|
|
|
|
|
strip->data_index = index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
Span<const StripKeyframeData *> Action::strip_keyframe_data() const
|
|
|
|
|
{
|
|
|
|
|
/* The reinterpret cast is needed because `strip_keyframe_data_array` is for
|
|
|
|
|
* pointers to the C type `ActionStripKeyframeData`, but we want the C++
|
|
|
|
|
* wrapper type `StripKeyframeData`. */
|
|
|
|
|
return Span<StripKeyframeData *>{
|
|
|
|
|
reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
|
|
|
|
|
this->strip_keyframe_data_array_num};
|
|
|
|
|
}
|
2024-09-30 11:51:14 +02:00
|
|
|
Span<StripKeyframeData *> Action::strip_keyframe_data()
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
{
|
|
|
|
|
/* The reinterpret cast is needed because `strip_keyframe_data_array` is for
|
|
|
|
|
* pointers to the C type `ActionStripKeyframeData`, but we want the C++
|
|
|
|
|
* wrapper type `StripKeyframeData`. */
|
2024-09-30 11:51:14 +02:00
|
|
|
return Span<StripKeyframeData *>{
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
reinterpret_cast<StripKeyframeData **>(this->strip_keyframe_data_array),
|
|
|
|
|
this->strip_keyframe_data_array_num};
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
Layer *Action::get_layer_for_keyframing()
|
|
|
|
|
{
|
2024-06-28 11:06:34 +02:00
|
|
|
assert_baklava_phase_1_invariants(*this);
|
|
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
if (this->layers().is_empty()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this->layer(0);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
void Action::slot_identifier_ensure_prefix(Slot &slot)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
slot.identifier_ensure_prefix();
|
|
|
|
|
slot_identifier_ensure_unique(*this, slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
void Action::slot_setup_for_id(Slot &slot, const ID &animated_id)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2025-01-27 16:29:18 +01:00
|
|
|
if (!ID_IS_EDITABLE(this) || ID_IS_OVERRIDE_LIBRARY(this)) {
|
|
|
|
|
/* Do not write to linked data. For now, also avoid changing the slot identifier on an
|
|
|
|
|
* override. Actions cannot have library overrides at the moment, and when they do, this should
|
|
|
|
|
* actually get designed. For now, it's better to avoid editing data than editing too much. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
if (slot.has_idtype()) {
|
|
|
|
|
BLI_assert(slot.idtype == GS(animated_id.name));
|
2024-05-30 12:23:58 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
slot.idtype = GS(animated_id.name);
|
2024-11-26 12:11:06 +01:00
|
|
|
this->slot_identifier_ensure_prefix(slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 15:04:47 +02:00
|
|
|
bool Action::has_keyframes(const slot_handle_t action_slot_handle) const
|
|
|
|
|
{
|
|
|
|
|
if (this->is_action_legacy()) {
|
|
|
|
|
/* Old BKE_action_has_motion(const bAction *act) implementation. */
|
|
|
|
|
LISTBASE_FOREACH (const FCurve *, fcu, &this->curves) {
|
|
|
|
|
if (fcu->totvert) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const FCurve *fcu : fcurves_for_action_slot(*this, action_slot_handle)) {
|
|
|
|
|
if (fcu->totvert) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Action::has_single_frame() const
|
|
|
|
|
{
|
|
|
|
|
bool found_key = false;
|
|
|
|
|
float found_key_frame = 0.0f;
|
|
|
|
|
|
2024-09-19 14:46:25 +02:00
|
|
|
for (const FCurve *fcu : legacy::fcurves_all(this)) {
|
2024-09-13 15:04:47 +02:00
|
|
|
switch (fcu->totvert) {
|
|
|
|
|
case 0:
|
|
|
|
|
/* No keys, so impossible to come to a conclusion on this curve alone. */
|
|
|
|
|
continue;
|
|
|
|
|
case 1:
|
|
|
|
|
/* Single key, which is the complex case, so handle below. */
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
/* Multiple keys, so there is animation. */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float this_key_frame = fcu->bezt != nullptr ? fcu->bezt[0].vec[1][0] :
|
|
|
|
|
fcu->fpt[0].vec[0];
|
|
|
|
|
if (!found_key) {
|
|
|
|
|
found_key = true;
|
|
|
|
|
found_key_frame = this_key_frame;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise
|
|
|
|
|
* with these comparisons. */
|
|
|
|
|
if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) {
|
|
|
|
|
/* This key differs from the already-found key, so this Action represents animation. */
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* There is only a single frame if we found at least one key. */
|
|
|
|
|
return found_key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Action::is_cyclic() const
|
|
|
|
|
{
|
|
|
|
|
return (this->flag & ACT_FRAME_RANGE) && (this->flag & ACT_CYCLIC);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 15:10:46 +02:00
|
|
|
/** Return the frame range of the span of keys. */
|
|
|
|
|
static float2 get_frame_range_of_fcurves(Span<const FCurve *> fcurves, bool include_modifiers);
|
|
|
|
|
|
2024-09-13 15:04:47 +02:00
|
|
|
float2 Action::get_frame_range() const
|
|
|
|
|
{
|
|
|
|
|
if (this->flag & ACT_FRAME_RANGE) {
|
|
|
|
|
return {this->frame_start, this->frame_end};
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 14:46:25 +02:00
|
|
|
Vector<const FCurve *> all_fcurves = legacy::fcurves_all(this);
|
2024-09-13 15:10:46 +02:00
|
|
|
return get_frame_range_of_fcurves(all_fcurves, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 Action::get_frame_range_of_slot(const slot_handle_t slot_handle) const
|
|
|
|
|
{
|
|
|
|
|
if (this->flag & ACT_FRAME_RANGE) {
|
|
|
|
|
return {this->frame_start, this->frame_end};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<const FCurve *> legacy_fcurves;
|
|
|
|
|
Span<const FCurve *> fcurves_to_consider;
|
|
|
|
|
|
|
|
|
|
if (this->is_action_layered()) {
|
|
|
|
|
fcurves_to_consider = fcurves_for_action_slot(*this, slot_handle);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2024-09-19 14:46:25 +02:00
|
|
|
legacy_fcurves = legacy::fcurves_all(this);
|
2024-09-13 15:10:46 +02:00
|
|
|
fcurves_to_consider = legacy_fcurves;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return get_frame_range_of_fcurves(fcurves_to_consider, false);
|
2024-09-13 15:04:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float2 Action::get_frame_range_of_keys(const bool include_modifiers) const
|
2024-09-13 15:10:46 +02:00
|
|
|
{
|
2024-09-19 14:46:25 +02:00
|
|
|
return get_frame_range_of_fcurves(legacy::fcurves_all(this), include_modifiers);
|
2024-09-13 15:10:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static float2 get_frame_range_of_fcurves(Span<const FCurve *> fcurves,
|
|
|
|
|
const bool include_modifiers)
|
2024-09-13 15:04:47 +02:00
|
|
|
{
|
|
|
|
|
float min = 999999999.0f, max = -999999999.0f;
|
|
|
|
|
bool foundvert = false, foundmod = false;
|
|
|
|
|
|
2024-09-13 15:10:46 +02:00
|
|
|
for (const FCurve *fcu : fcurves) {
|
2024-09-13 15:04:47 +02:00
|
|
|
/* if curve has keyframes, consider them first */
|
|
|
|
|
if (fcu->totvert) {
|
|
|
|
|
float nmin, nmax;
|
|
|
|
|
|
|
|
|
|
/* get extents for this curve
|
|
|
|
|
* - no "selected only", since this is often used in the backend
|
|
|
|
|
* - no "minimum length" (we will apply this later), otherwise
|
|
|
|
|
* single-keyframe curves will increase the overall length by
|
|
|
|
|
* a phantom frame (#50354)
|
|
|
|
|
*/
|
|
|
|
|
BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
|
|
|
|
|
|
|
|
|
|
/* compare to the running tally */
|
|
|
|
|
min = min_ff(min, nmin);
|
|
|
|
|
max = max_ff(max, nmax);
|
|
|
|
|
|
|
|
|
|
foundvert = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* if include_modifiers is enabled, need to consider modifiers too
|
|
|
|
|
* - only really care about the last modifier
|
|
|
|
|
*/
|
|
|
|
|
if ((include_modifiers) && (fcu->modifiers.last)) {
|
|
|
|
|
FModifier *fcm = static_cast<FModifier *>(fcu->modifiers.last);
|
|
|
|
|
|
|
|
|
|
/* only use the maximum sensible limits of the modifiers if they are more extreme */
|
|
|
|
|
switch (fcm->type) {
|
|
|
|
|
case FMODIFIER_TYPE_LIMITS: /* Limits F-Modifier */
|
|
|
|
|
{
|
|
|
|
|
FMod_Limits *fmd = (FMod_Limits *)fcm->data;
|
|
|
|
|
|
|
|
|
|
if (fmd->flag & FCM_LIMIT_XMIN) {
|
|
|
|
|
min = min_ff(min, fmd->rect.xmin);
|
|
|
|
|
}
|
|
|
|
|
if (fmd->flag & FCM_LIMIT_XMAX) {
|
|
|
|
|
max = max_ff(max, fmd->rect.xmax);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case FMODIFIER_TYPE_CYCLES: /* Cycles F-Modifier */
|
|
|
|
|
{
|
|
|
|
|
FMod_Cycles *fmd = (FMod_Cycles *)fcm->data;
|
|
|
|
|
|
|
|
|
|
if (fmd->before_mode != FCM_EXTRAPOLATE_NONE) {
|
|
|
|
|
min = MINAFRAMEF;
|
|
|
|
|
}
|
|
|
|
|
if (fmd->after_mode != FCM_EXTRAPOLATE_NONE) {
|
|
|
|
|
max = MAXFRAMEF;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
/* TODO: function modifier may need some special limits */
|
|
|
|
|
|
|
|
|
|
default: /* all other standard modifiers are on the infinite range... */
|
|
|
|
|
min = MINAFRAMEF;
|
|
|
|
|
max = MAXFRAMEF;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foundmod = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (foundvert || foundmod) {
|
|
|
|
|
return float2{max_ff(min, MINAFRAMEF), min_ff(max, MAXFRAMEF)};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return float2{0.0f, 0.0f};
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
/* ----- ActionLayer implementation ----------- */
|
|
|
|
|
|
2024-09-17 18:55:30 +02:00
|
|
|
Layer *Layer::duplicate_with_shallow_strip_copies(const StringRefNull allocation_name) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-17 18:55:30 +02:00
|
|
|
ActionLayer *copy = MEM_cnew<ActionLayer>(allocation_name.c_str());
|
|
|
|
|
*copy = *reinterpret_cast<const ActionLayer *>(this);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
/* Make a shallow copy of the Strips, without copying their data. */
|
|
|
|
|
copy->strip_array = MEM_cnew_array<ActionStrip *>(this->strip_array_num,
|
|
|
|
|
allocation_name.c_str());
|
|
|
|
|
for (int i : this->strips().index_range()) {
|
|
|
|
|
Strip *strip_copy = MEM_new<Strip>(allocation_name.c_str(), *this->strip(i));
|
|
|
|
|
copy->strip_array[i] = strip_copy;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
|
2024-09-17 18:55:30 +02:00
|
|
|
return ©->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Layer::~Layer()
|
|
|
|
|
{
|
|
|
|
|
for (Strip *strip : this->strips()) {
|
|
|
|
|
MEM_delete(strip);
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(this->strip_array);
|
|
|
|
|
this->strip_array_num = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blender::Span<const Strip *> Layer::strips() const
|
|
|
|
|
{
|
|
|
|
|
return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
|
|
|
|
|
this->strip_array_num};
|
|
|
|
|
}
|
2024-09-30 11:51:14 +02:00
|
|
|
blender::Span<Strip *> Layer::strips()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<Strip *>{reinterpret_cast<Strip **>(this->strip_array),
|
|
|
|
|
this->strip_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
const Strip *Layer::strip(const int64_t index) const
|
|
|
|
|
{
|
|
|
|
|
return &this->strip_array[index]->wrap();
|
|
|
|
|
}
|
|
|
|
|
Strip *Layer::strip(const int64_t index)
|
|
|
|
|
{
|
|
|
|
|
return &this->strip_array[index]->wrap();
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
Strip &Layer::strip_add(Action &owning_action, const Strip::Type strip_type)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
Strip &strip = Strip::create(owning_action, strip_type);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
/* Add the new strip to the strip array. */
|
|
|
|
|
grow_array_and_append<::ActionStrip *>(&this->strip_array, &this->strip_array_num, &strip);
|
|
|
|
|
|
|
|
|
|
return strip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void strip_ptr_destructor(ActionStrip **dna_strip_ptr)
|
|
|
|
|
{
|
|
|
|
|
Strip &strip = (*dna_strip_ptr)->wrap();
|
|
|
|
|
MEM_delete(&strip);
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-20 13:07:15 +02:00
|
|
|
bool Layer::strip_remove(Action &owning_action, Strip &strip)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-27 13:20:43 +10:00
|
|
|
const int64_t strip_index = this->find_strip_index(strip);
|
2024-05-30 12:23:58 +02:00
|
|
|
if (strip_index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 13:07:15 +02:00
|
|
|
const Strip::Type strip_type = strip.type();
|
|
|
|
|
const int data_index = strip.data_index;
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
dna::array::remove_index(
|
|
|
|
|
&this->strip_array, &this->strip_array_num, nullptr, strip_index, strip_ptr_destructor);
|
|
|
|
|
|
2024-09-20 13:07:15 +02:00
|
|
|
/* It's important that we do this *after* removing the strip itself
|
|
|
|
|
* (immediately above), because otherwise the strip will be found as a
|
|
|
|
|
* still-existing user of the strip data and thus the strip data won't be
|
|
|
|
|
* removed even if this strip was the last user. */
|
|
|
|
|
switch (strip_type) {
|
|
|
|
|
case Strip::Type::Keyframe:
|
|
|
|
|
owning_action.strip_keyframe_data_remove_if_unused(data_index);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int64_t Layer::find_strip_index(const Strip &strip) const
|
|
|
|
|
{
|
|
|
|
|
for (const int64_t strip_index : this->strips().index_range()) {
|
|
|
|
|
const Strip *visit_strip = this->strip(strip_index);
|
|
|
|
|
if (visit_strip == &strip) {
|
|
|
|
|
return strip_index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
void Layer::slot_data_remove(Action &owning_action, const slot_handle_t slot_handle)
|
2024-09-03 15:48:00 +02:00
|
|
|
{
|
|
|
|
|
for (Strip *strip : this->strips()) {
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
strip->slot_data_remove(owning_action, slot_handle);
|
2024-09-03 15:48:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
/* ----- ActionSlot implementation ----------- */
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot::Slot()
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
memset(this, 0, sizeof(*this));
|
2024-07-05 16:59:34 +02:00
|
|
|
this->runtime = MEM_new<SlotRuntime>(__func__);
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot::Slot(const Slot &other)
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
2024-09-13 12:05:28 +02:00
|
|
|
memcpy(this, &other, sizeof(*this));
|
2024-07-05 16:59:34 +02:00
|
|
|
this->runtime = MEM_new<SlotRuntime>(__func__);
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot::~Slot()
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
MEM_delete(this->runtime);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::blend_read_post()
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(!this->runtime);
|
2024-07-05 16:59:34 +02:00
|
|
|
this->runtime = MEM_new<SlotRuntime>(__func__);
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
bool Slot::is_suitable_for(const ID &animated_id) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
if (!this->has_idtype()) {
|
2024-07-05 16:59:34 +02:00
|
|
|
/* Without specific ID type set, this Slot can animate any ID. */
|
2024-05-30 12:23:58 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
/* Check that the ID type is compatible with this slot. */
|
2024-05-30 12:23:58 +02:00
|
|
|
const int animated_idtype = GS(animated_id.name);
|
|
|
|
|
return this->idtype == animated_idtype;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
bool Slot::has_idtype() const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
return this->idtype != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot::Flags Slot::flags() const
|
2024-07-04 14:44:19 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return static_cast<Slot::Flags>(this->slot_flags);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
bool Slot::is_expanded() const
|
2024-07-04 14:44:19 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->slot_flags & uint8_t(Flags::Expanded);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::set_expanded(const bool expanded)
|
2024-07-04 14:44:19 +02:00
|
|
|
{
|
|
|
|
|
if (expanded) {
|
2024-07-05 16:59:34 +02:00
|
|
|
this->slot_flags |= uint8_t(Flags::Expanded);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2024-10-02 15:42:49 +10:00
|
|
|
this->slot_flags &= ~uint8_t(Flags::Expanded);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
bool Slot::is_selected() const
|
2024-07-04 14:44:19 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->slot_flags & uint8_t(Flags::Selected);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::set_selected(const bool selected)
|
2024-07-04 14:44:19 +02:00
|
|
|
{
|
|
|
|
|
if (selected) {
|
2024-07-05 16:59:34 +02:00
|
|
|
this->slot_flags |= uint8_t(Flags::Selected);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2024-10-02 15:42:49 +10:00
|
|
|
this->slot_flags &= ~uint8_t(Flags::Selected);
|
2024-07-04 14:44:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-12 11:59:04 +02:00
|
|
|
bool Slot::is_active() const
|
|
|
|
|
{
|
|
|
|
|
return this->slot_flags & uint8_t(Flags::Active);
|
|
|
|
|
}
|
|
|
|
|
void Slot::set_active(const bool active)
|
|
|
|
|
{
|
|
|
|
|
if (active) {
|
|
|
|
|
this->slot_flags |= uint8_t(Flags::Active);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2024-10-02 15:42:49 +10:00
|
|
|
this->slot_flags &= ~uint8_t(Flags::Active);
|
2024-07-12 11:59:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Span<ID *> Slot::users(Main &bmain) const
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
if (bmain.is_action_slot_to_id_map_dirty) {
|
|
|
|
|
internal::rebuild_slot_user_cache(bmain);
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
BLI_assert(this->runtime);
|
|
|
|
|
return this->runtime->users.as_span();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
Vector<ID *> Slot::runtime_users()
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
BLI_assert_msg(this->runtime, "Slot::runtime should always be allocated");
|
2024-06-13 14:27:36 +02:00
|
|
|
return this->runtime->users;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::users_add(ID &animated_id)
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(this->runtime);
|
|
|
|
|
this->runtime->users.append_non_duplicates(&animated_id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::users_remove(ID &animated_id)
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(this->runtime);
|
|
|
|
|
Vector<ID *> &users = this->runtime->users;
|
|
|
|
|
|
2024-12-13 10:42:54 +01:00
|
|
|
/* Even though users_add() ensures that there are no duplicates, there's still things like
|
|
|
|
|
* pointer swapping etc. that can happen via the foreach-id looping code. That means that the
|
|
|
|
|
* entries in the user map are not 100% under control of the user_add() and user_remove()
|
|
|
|
|
* function, and thus we cannot assume that there are no duplicates. */
|
|
|
|
|
users.remove_if([&](const ID *user) { return user == &animated_id; });
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
void Slot::users_invalidate(Main &bmain)
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
bmain.is_action_slot_to_id_map_dirty = true;
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2025-02-04 16:55:22 +01:00
|
|
|
std::string Slot::idtype_string() const
|
2024-07-09 15:57:48 +02:00
|
|
|
{
|
|
|
|
|
if (!this->has_idtype()) {
|
2025-01-27 15:15:33 +01:00
|
|
|
return slot_untyped_prefix;
|
2024-07-09 15:57:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char name[3] = {0};
|
|
|
|
|
*reinterpret_cast<short *>(name) = this->idtype;
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-04 15:46:22 +01:00
|
|
|
StringRef Slot::identifier_prefix() const
|
|
|
|
|
{
|
|
|
|
|
StringRef identifier(this->identifier);
|
|
|
|
|
BLI_assert(identifier.size() >= 2);
|
|
|
|
|
|
|
|
|
|
return identifier.substr(0, 2);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
StringRefNull Slot::identifier_without_prefix() const
|
2024-07-09 15:57:48 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_assert(StringRef(this->identifier).size() >= identifier_length_min);
|
2024-07-09 15:57:48 +02:00
|
|
|
|
|
|
|
|
/* Avoid accessing an uninitialized part of the string accidentally. */
|
2024-11-26 12:11:06 +01:00
|
|
|
if (this->identifier[0] == '\0' || this->identifier[1] == '\0') {
|
2024-07-09 15:57:48 +02:00
|
|
|
return "";
|
|
|
|
|
}
|
2024-11-26 12:11:06 +01:00
|
|
|
return this->identifier + 2;
|
2024-07-09 15:57:48 +02:00
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
void Slot::identifier_ensure_prefix()
|
2024-07-09 15:57:48 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_assert(StringRef(this->identifier).size() >= identifier_length_min);
|
2024-07-09 15:57:48 +02:00
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
if (StringRef(this->identifier).size() < 2) {
|
2024-07-09 15:57:48 +02:00
|
|
|
/* The code below would overwrite the trailing 0-byte. */
|
2024-11-26 12:11:06 +01:00
|
|
|
this->identifier[2] = '\0';
|
2024-07-09 15:57:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!this->has_idtype()) {
|
|
|
|
|
/* A zero idtype is not going to convert to a two-character string, so we
|
|
|
|
|
* need to explicitly assign the default prefix. */
|
2025-01-27 15:15:33 +01:00
|
|
|
this->identifier[0] = slot_untyped_prefix[0];
|
|
|
|
|
this->identifier[1] = slot_untyped_prefix[1];
|
2024-07-09 15:57:48 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 12:11:06 +01:00
|
|
|
*reinterpret_cast<short *>(this->identifier) = this->idtype;
|
2024-07-09 15:57:48 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-13 14:27:36 +02:00
|
|
|
/* ----- Functions ----------- */
|
|
|
|
|
|
2024-09-24 17:28:35 +02:00
|
|
|
Action &action_add(Main &bmain, const StringRefNull name)
|
|
|
|
|
{
|
|
|
|
|
bAction *dna_action = BKE_action_add(&bmain, name.c_str());
|
|
|
|
|
id_us_clear_real(&dna_action->id);
|
|
|
|
|
return dna_action->wrap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 17:47:36 +02:00
|
|
|
bool assign_action(bAction *action, ID &animated_id)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
AnimData *adt = BKE_animdata_ensure_id(&animated_id);
|
|
|
|
|
if (!adt) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-09-26 17:29:01 +02:00
|
|
|
return assign_action(action, {animated_id, *adt});
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
bool assign_action(bAction *action, const OwnedAnimData owned_adt)
|
2024-09-20 17:47:36 +02:00
|
|
|
{
|
2024-09-26 17:29:01 +02:00
|
|
|
if (!BKE_animdata_action_editable(&owned_adt.adt)) {
|
|
|
|
|
/* Cannot remove, otherwise things turn to custard. */
|
|
|
|
|
BKE_report(nullptr, RPT_ERROR, "Cannot change action, as it is still being edited in NLA");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return generic_assign_action(owned_adt.owner_id,
|
|
|
|
|
action,
|
|
|
|
|
owned_adt.adt.action,
|
|
|
|
|
owned_adt.adt.slot_handle,
|
Anim: rename `action_slot_name` to `last_slot_identifier`
`AnimData`, NLA strips, and action constraints all have an `action_slot_name`
field in RNA. The purpose of this field is to store the identifier of the most
recently assigned slot, so that it can be used for auto-assignment when later
assigning different actions.
However, this field name is misleading in two ways:
1. In accordance with #130740, it's actually the slot *identifier*, not name.
2. It could be mistaken as a way to rename the currently assigned slot, which it
is not.
To resolve both of those issues, we're renaming the field to
`last_slot_identifier`, which better communicates its actual nature.
As a bonus, this also ends up decluttering Python autocomplete when looking
for things related to action_slot.
Ref: #130892
Pull Request: https://projects.blender.org/blender/blender/pulls/130911
2024-11-26 16:05:40 +01:00
|
|
|
owned_adt.adt.last_slot_identifier);
|
2024-09-20 17:47:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
bool assign_tmpaction(bAction *action, const OwnedAnimData owned_adt)
|
2024-09-20 17:24:20 +02:00
|
|
|
{
|
2024-09-26 17:29:01 +02:00
|
|
|
return generic_assign_action(owned_adt.owner_id,
|
|
|
|
|
action,
|
|
|
|
|
owned_adt.adt.tmpact,
|
|
|
|
|
owned_adt.adt.tmp_slot_handle,
|
Anim: rename `action_slot_name` to `last_slot_identifier`
`AnimData`, NLA strips, and action constraints all have an `action_slot_name`
field in RNA. The purpose of this field is to store the identifier of the most
recently assigned slot, so that it can be used for auto-assignment when later
assigning different actions.
However, this field name is misleading in two ways:
1. In accordance with #130740, it's actually the slot *identifier*, not name.
2. It could be mistaken as a way to rename the currently assigned slot, which it
is not.
To resolve both of those issues, we're renaming the field to
`last_slot_identifier`, which better communicates its actual nature.
As a bonus, this also ends up decluttering Python autocomplete when looking
for things related to action_slot.
Ref: #130892
Pull Request: https://projects.blender.org/blender/blender/pulls/130911
2024-11-26 16:05:40 +01:00
|
|
|
owned_adt.adt.tmp_last_slot_identifier);
|
2024-09-20 17:24:20 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
bool unassign_action(ID &animated_id)
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
{
|
2024-09-26 17:29:01 +02:00
|
|
|
return assign_action(nullptr, animated_id);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
bool unassign_action(OwnedAnimData owned_adt)
|
2024-09-23 15:53:32 +02:00
|
|
|
{
|
2024-09-26 17:29:01 +02:00
|
|
|
return assign_action(nullptr, owned_adt);
|
2024-09-23 15:53:32 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 12:07:58 +02:00
|
|
|
Slot *assign_action_ensure_slot_for_keying(Action &action, ID &animated_id)
|
|
|
|
|
{
|
2024-10-11 13:02:25 +02:00
|
|
|
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
2024-08-29 12:07:58 +02:00
|
|
|
Slot *slot;
|
|
|
|
|
|
2024-12-09 14:48:14 +01:00
|
|
|
/* Find a suitable slot, but be stricter about when to allow searching by name
|
|
|
|
|
* than generic_slot_for_autoassign(...). */
|
2025-01-27 16:16:02 +01:00
|
|
|
if (adt && adt->action == &action) {
|
|
|
|
|
/* The slot handle is only valid when this action is already assigned.
|
|
|
|
|
* Otherwise it's meaningless. */
|
|
|
|
|
slot = action.slot_for_handle(adt->slot_handle);
|
|
|
|
|
|
|
|
|
|
/* If this Action is already assigned, a search by name is inappropriate, as it might
|
|
|
|
|
* re-assign an intentionally-unassigned slot. */
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* In this case a by-name search is ok, so defer to generic_slot_for_autoassign(). */
|
|
|
|
|
slot = generic_slot_for_autoassign(animated_id, action, adt ? adt->last_slot_identifier : "");
|
2024-08-29 12:07:58 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-27 16:16:02 +01:00
|
|
|
/* As a last resort, if there is only one slot and it has no ID type yet, use that. This is what
|
|
|
|
|
* gets created for the backwards compatibility RNA API, for example to allow
|
|
|
|
|
* `action.fcurves.new()`. Key insertion should use that slot as well. */
|
2024-10-11 13:02:25 +02:00
|
|
|
if (!slot && action.slots().size() == 1) {
|
|
|
|
|
Slot *first_slot = action.slot(0);
|
|
|
|
|
if (!first_slot->has_idtype()) {
|
|
|
|
|
slot = first_slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If no suitable slot was found, create a new one. */
|
2024-08-29 12:07:58 +02:00
|
|
|
if (!slot || !slot->is_suitable_for(animated_id)) {
|
|
|
|
|
slot = &action.slot_add_for_id(animated_id);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 09:54:15 +02:00
|
|
|
/* Only try to assign the Action to the ID if it is not already assigned.
|
|
|
|
|
* Assignment can fail when the ID is in NLA Tweak mode. */
|
2024-10-11 13:02:25 +02:00
|
|
|
const bool is_correct_action = adt && adt->action == &action;
|
2024-10-03 09:54:15 +02:00
|
|
|
if (!is_correct_action && !assign_action(&action, animated_id)) {
|
2024-09-26 17:29:01 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 13:02:25 +02:00
|
|
|
const bool is_correct_slot = adt && adt->slot_handle == slot->handle;
|
2024-10-03 09:54:15 +02:00
|
|
|
if (!is_correct_slot && assign_action_slot(slot, animated_id) != ActionSlotAssignmentResult::OK)
|
|
|
|
|
{
|
2024-09-26 17:29:01 +02:00
|
|
|
/* This should never happen, as a few lines above a new slot is created for
|
|
|
|
|
* this ID if the found one wasn't deemed suitable. */
|
|
|
|
|
BLI_assert_unreachable();
|
2024-08-29 12:07:58 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
static bool is_id_using_action_slot(const ID &animated_id,
|
|
|
|
|
const Action &action,
|
|
|
|
|
const slot_handle_t slot_handle)
|
|
|
|
|
{
|
|
|
|
|
auto visit_action_use = [&](const Action &used_action, slot_handle_t used_slot_handle) -> bool {
|
|
|
|
|
const bool is_used = (&used_action == &action && used_slot_handle == slot_handle);
|
|
|
|
|
return !is_used; /* Stop searching when we found a use of this Action+Slot. */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const bool looped_until_end = foreach_action_slot_use(animated_id, visit_action_use);
|
|
|
|
|
return !looped_until_end;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
bool generic_assign_action(ID &animated_id,
|
2024-09-20 17:47:36 +02:00
|
|
|
bAction *action_to_assign,
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
bAction *&action_ptr_ref,
|
|
|
|
|
slot_handle_t &slot_handle_ref,
|
2024-11-26 12:11:06 +01:00
|
|
|
char *slot_identifier)
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_assert(slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
|
2024-09-26 17:29:01 +02:00
|
|
|
if (action_to_assign && legacy::action_treat_as_legacy(*action_to_assign)) {
|
|
|
|
|
/* Check that the Action is suitable for this ID type.
|
|
|
|
|
* This is only necessary for legacy Actions. */
|
|
|
|
|
if (!BKE_animdata_action_ensure_idroot(&animated_id, action_to_assign)) {
|
|
|
|
|
BKE_reportf(
|
|
|
|
|
nullptr,
|
|
|
|
|
RPT_ERROR,
|
|
|
|
|
"Could not set action '%s' to animate ID '%s', as it does not have suitably rooted "
|
|
|
|
|
"paths for this purpose",
|
|
|
|
|
action_to_assign->id.name + 2,
|
|
|
|
|
animated_id.name);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
/* Un-assign any previously-assigned Action first. */
|
|
|
|
|
if (action_ptr_ref) {
|
|
|
|
|
/* Un-assign the slot. This will always succeed, so no need to check the result. */
|
|
|
|
|
if (slot_handle_ref != Slot::unassigned) {
|
|
|
|
|
const ActionSlotAssignmentResult result = generic_assign_action_slot(
|
2024-11-26 12:11:06 +01:00
|
|
|
nullptr, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
BLI_assert(result == ActionSlotAssignmentResult::OK);
|
|
|
|
|
UNUSED_VARS_NDEBUG(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Un-assign the Action itself. */
|
|
|
|
|
id_us_min(&action_ptr_ref->id);
|
|
|
|
|
action_ptr_ref = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!action_to_assign) {
|
|
|
|
|
/* Un-assigning was the point, so the work is done. */
|
2024-09-26 17:29:01 +02:00
|
|
|
return true;
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Assign the new Action. */
|
|
|
|
|
action_ptr_ref = action_to_assign;
|
|
|
|
|
id_us_plus(&action_ptr_ref->id);
|
|
|
|
|
|
2024-12-09 14:48:14 +01:00
|
|
|
/* Auto-assign a slot. */
|
|
|
|
|
Slot *slot = generic_slot_for_autoassign(animated_id, action_ptr_ref->wrap(), slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
const ActionSlotAssignmentResult result = generic_assign_action_slot(
|
2024-11-26 12:11:06 +01:00
|
|
|
slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
BLI_assert(result == ActionSlotAssignmentResult::OK);
|
|
|
|
|
UNUSED_VARS_NDEBUG(result);
|
2024-09-26 17:29:01 +02:00
|
|
|
|
|
|
|
|
return true;
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-09 14:48:14 +01:00
|
|
|
Slot *generic_slot_for_autoassign(const ID &animated_id,
|
|
|
|
|
Action &action,
|
|
|
|
|
const StringRefNull last_slot_identifier)
|
|
|
|
|
{
|
|
|
|
|
/* The slot-finding code in assign_action_ensure_slot_for_keying() is very
|
|
|
|
|
* similar to the code here (differences are documented there). It is very
|
|
|
|
|
* likely that changes in the logic here should be applied there as well. */
|
|
|
|
|
|
|
|
|
|
/* Try the slot identifier, if it is set. */
|
|
|
|
|
if (!last_slot_identifier.is_empty()) {
|
Anim: treat untyped slot identifiers ("XXSlot") as wildcard
When assigning an Action to an ID, a slot can be automatically
assigned as well. This behaviour is now extended by making untyped
slot identifiers (like `XXSlot`) act as wildcards.
If the last-used slot identifier was 'untyped' (like `XXSlot`), and a
slot with the same name that is specific to the animated ID's type
exists, that slot will be chosen.
Similarly, if the last-used slot identifier was 'typed' (like
`OBSlot`), a slot `OBSlot` does NOT exist, but an untyped slot with
the same name exists (like `XXSlot`), that one will be chosen.
If there is any ambiguity in the matter, the more specific slot is
chosen. In other words, in this case:
- last_slot_identifier = `XXSlot`
- both `XXSlot` and `OBSlot` exist on the Action (where `OB`
represents the ID type of `animated_id`).
the `OBSlot` should be chosen. This means that `XXSlot` NOT being
auto-assigned if there is an alternative. Since untyped slots are
bound on assignment, this design keeps the Action as-is, which means
that the `XXSlot` remains untyped and thus the user is free to assign
this to another ID type if desired.
Pull Request: https://projects.blender.org/blender/blender/pulls/133653
2025-01-27 18:03:20 +01:00
|
|
|
/* If the last-used slot identifier was 'untyped', i.e. started with XX, see if something more
|
|
|
|
|
* specific to this ID type exists.
|
|
|
|
|
*
|
|
|
|
|
* If there is any choice in the matter, the more specific slot is chosen. In other words, in
|
|
|
|
|
* this case:
|
|
|
|
|
*
|
|
|
|
|
* - last_slot_identifier = `XXSlot`
|
|
|
|
|
* - both `XXSlot` and `OBSlot` exist on the Action (where `OB` represents the ID type of
|
|
|
|
|
* `animated_id`).
|
|
|
|
|
*
|
|
|
|
|
* the `OBSlot` should be chosen. This means that `XXSlot` NOT being auto-assigned if there is
|
|
|
|
|
* an alternative. Since untyped slots are bound on assignment, this design keeps the Action
|
|
|
|
|
* as-is, which means that the `XXSlot` remains untyped and thus the user is free to assign
|
|
|
|
|
* this to another ID type if desired. */
|
|
|
|
|
|
|
|
|
|
const bool last_used_identifier_is_typed = last_slot_identifier.substr(0, 2) !=
|
|
|
|
|
slot_untyped_prefix;
|
|
|
|
|
if (!last_used_identifier_is_typed) {
|
|
|
|
|
const std::string with_idtype_prefix = StringRef(animated_id.name, 2) +
|
|
|
|
|
last_slot_identifier.substr(2);
|
|
|
|
|
Slot *slot = action.slot_find_by_identifier(with_idtype_prefix);
|
|
|
|
|
if (slot && slot->is_suitable_for(animated_id)) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* See if the actual last-used slot identifier can be matched. */
|
2024-12-09 14:48:14 +01:00
|
|
|
Slot *slot = action.slot_find_by_identifier(last_slot_identifier);
|
|
|
|
|
if (slot && slot->is_suitable_for(animated_id)) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
Anim: treat untyped slot identifiers ("XXSlot") as wildcard
When assigning an Action to an ID, a slot can be automatically
assigned as well. This behaviour is now extended by making untyped
slot identifiers (like `XXSlot`) act as wildcards.
If the last-used slot identifier was 'untyped' (like `XXSlot`), and a
slot with the same name that is specific to the animated ID's type
exists, that slot will be chosen.
Similarly, if the last-used slot identifier was 'typed' (like
`OBSlot`), a slot `OBSlot` does NOT exist, but an untyped slot with
the same name exists (like `XXSlot`), that one will be chosen.
If there is any ambiguity in the matter, the more specific slot is
chosen. In other words, in this case:
- last_slot_identifier = `XXSlot`
- both `XXSlot` and `OBSlot` exist on the Action (where `OB`
represents the ID type of `animated_id`).
the `OBSlot` should be chosen. This means that `XXSlot` NOT being
auto-assigned if there is an alternative. Since untyped slots are
bound on assignment, this design keeps the Action as-is, which means
that the `XXSlot` remains untyped and thus the user is free to assign
this to another ID type if desired.
Pull Request: https://projects.blender.org/blender/blender/pulls/133653
2025-01-27 18:03:20 +01:00
|
|
|
|
|
|
|
|
/* If the last-used slot identifier was IDSomething, and XXSomething exists (where ID = the
|
|
|
|
|
* ID code of the animated ID), fall back to the XX. If slot `IDSomething` existed, the code
|
|
|
|
|
* above would have already returned it. */
|
|
|
|
|
if (last_used_identifier_is_typed) {
|
|
|
|
|
const std::string with_untyped_prefix = StringRef(slot_untyped_prefix) +
|
|
|
|
|
last_slot_identifier.substr(2);
|
|
|
|
|
Slot *slot = action.slot_find_by_identifier(with_untyped_prefix);
|
|
|
|
|
if (slot && slot->is_suitable_for(animated_id)) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-09 14:48:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Search for the ID name (which includes the ID type). */
|
|
|
|
|
{
|
|
|
|
|
Slot *slot = action.slot_find_by_identifier(animated_id.name);
|
|
|
|
|
if (slot && slot->is_suitable_for(animated_id)) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If there is only one slot, and it is not specific to any ID type, use that.
|
|
|
|
|
*
|
|
|
|
|
* This should only trigger in some special cases, like legacy Actions that
|
|
|
|
|
* were converted to slotted Actions by the versioning code, where the legacy
|
|
|
|
|
* Action was never assigned to anything (and thus had idroot = 0). */
|
|
|
|
|
if (action.slots().size() == 1) {
|
|
|
|
|
Slot *slot = action.slot(0);
|
|
|
|
|
if (!slot->has_idtype()) {
|
|
|
|
|
return slot;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
ActionSlotAssignmentResult generic_assign_action_slot(Slot *slot_to_assign,
|
|
|
|
|
ID &animated_id,
|
|
|
|
|
bAction *&action_ptr_ref,
|
|
|
|
|
slot_handle_t &slot_handle_ref,
|
2024-11-26 12:11:06 +01:00
|
|
|
char *slot_identifier)
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
{
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_assert(slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
if (!action_ptr_ref) {
|
|
|
|
|
/* No action assigned yet, so no way to assign a slot. */
|
|
|
|
|
return ActionSlotAssignmentResult::MissingAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Action &action = action_ptr_ref->wrap();
|
|
|
|
|
|
|
|
|
|
/* Check that the slot can actually be assigned. */
|
|
|
|
|
if (slot_to_assign) {
|
2024-09-30 11:51:14 +02:00
|
|
|
if (!action.slots().contains(slot_to_assign)) {
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
return ActionSlotAssignmentResult::SlotNotFromAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!slot_to_assign->is_suitable_for(animated_id)) {
|
|
|
|
|
return ActionSlotAssignmentResult::SlotNotSuitable;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Slot *slot_to_unassign = action.slot_for_handle(slot_handle_ref);
|
|
|
|
|
|
|
|
|
|
/* If there was a previously-assigned slot, unassign it first. */
|
|
|
|
|
slot_handle_ref = Slot::unassigned;
|
|
|
|
|
if (slot_to_unassign) {
|
2024-11-26 12:11:06 +01:00
|
|
|
/* Make sure that the stored Slot identifier is up to date. The slot identifier might have
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
* changed in a way that wasn't copied into the ADT yet (for example when the
|
2024-11-26 12:11:06 +01:00
|
|
|
* Action is linked from another file), so better copy the identifier to be sure
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
* that it can be transparently reassigned later.
|
|
|
|
|
*
|
2024-11-26 12:11:06 +01:00
|
|
|
* TODO: Replace this with a BLI_assert() that the identifier is as expected, and "simply"
|
|
|
|
|
* ensure this identifier is always correct. */
|
|
|
|
|
BLI_strncpy_utf8(slot_identifier, slot_to_unassign->identifier, Slot::identifier_length_max);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
|
|
|
|
|
/* If this was the last use of this slot, remove this ID from its users. */
|
|
|
|
|
if (!is_id_using_action_slot(animated_id, action, slot_to_unassign->handle)) {
|
|
|
|
|
slot_to_unassign->users_remove(animated_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!slot_to_assign) {
|
|
|
|
|
return ActionSlotAssignmentResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
action.slot_setup_for_id(*slot_to_assign, animated_id);
|
|
|
|
|
slot_handle_ref = slot_to_assign->handle;
|
2024-11-26 12:11:06 +01:00
|
|
|
BLI_strncpy_utf8(slot_identifier, slot_to_assign->identifier, Slot::identifier_length_max);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
slot_to_assign->users_add(animated_id);
|
|
|
|
|
|
|
|
|
|
return ActionSlotAssignmentResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-16 18:54:49 +02:00
|
|
|
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,
|
2024-11-26 12:11:06 +01:00
|
|
|
char *slot_identifier)
|
2024-09-16 18:54:49 +02:00
|
|
|
{
|
|
|
|
|
if (slot_handle_to_assign == Slot::unassigned && !action_ptr_ref) {
|
|
|
|
|
/* No Action assigned, so no slot was used anyway. Just blindly assign the
|
|
|
|
|
* 'unassigned' handle. */
|
|
|
|
|
slot_handle_ref = Slot::unassigned;
|
|
|
|
|
return ActionSlotAssignmentResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!action_ptr_ref) {
|
|
|
|
|
/* No Action to verify the slot handle is valid. As the slot handle will be
|
|
|
|
|
* completely ignored when re-assigning an Action, better to refuse setting
|
|
|
|
|
* it altogether. This will make bugs more obvious. */
|
|
|
|
|
return ActionSlotAssignmentResult::MissingAction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Slot *slot = action_ptr_ref->wrap().slot_for_handle(slot_handle_to_assign);
|
2024-11-26 12:11:06 +01:00
|
|
|
return generic_assign_action_slot(
|
|
|
|
|
slot, animated_id, action_ptr_ref, slot_handle_ref, slot_identifier);
|
2024-09-16 18:54:49 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-11 11:21:19 +02:00
|
|
|
bool is_action_assignable_to(const bAction *dna_action, const ID_Type id_code)
|
|
|
|
|
{
|
|
|
|
|
if (!dna_action) {
|
|
|
|
|
/* Clearing the Action is always possible. */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dna_action->idroot == 0) {
|
|
|
|
|
/* This is either a never-assigned legacy action, or a layered action. In
|
|
|
|
|
* any case, it can be assigned to any ID. */
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const animrig::Action &action = dna_action->wrap();
|
2024-09-26 15:16:56 +02:00
|
|
|
if (legacy::action_treat_as_legacy(action)) {
|
2024-06-11 11:21:19 +02:00
|
|
|
/* Legacy Actions can only be assigned if their idroot matches. Empty
|
|
|
|
|
* Actions are considered both 'layered' and 'legacy' at the same time,
|
|
|
|
|
* hence this condition checks for 'not layered' rather than 'legacy'. */
|
|
|
|
|
return action.idroot == id_code;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
ActionSlotAssignmentResult assign_action_slot(Slot *slot_to_assign, ID &animated_id)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-06-13 15:12:25 +02:00
|
|
|
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
|
|
|
|
if (!adt) {
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
return ActionSlotAssignmentResult::MissingAction;
|
2024-06-13 15:12:25 +02:00
|
|
|
}
|
|
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
return generic_assign_action_slot(
|
Anim: rename `action_slot_name` to `last_slot_identifier`
`AnimData`, NLA strips, and action constraints all have an `action_slot_name`
field in RNA. The purpose of this field is to store the identifier of the most
recently assigned slot, so that it can be used for auto-assignment when later
assigning different actions.
However, this field name is misleading in two ways:
1. In accordance with #130740, it's actually the slot *identifier*, not name.
2. It could be mistaken as a way to rename the currently assigned slot, which it
is not.
To resolve both of those issues, we're renaming the field to
`last_slot_identifier`, which better communicates its actual nature.
As a bonus, this also ends up decluttering Python autocomplete when looking
for things related to action_slot.
Ref: #130892
Pull Request: https://projects.blender.org/blender/blender/pulls/130911
2024-11-26 16:05:40 +01:00
|
|
|
slot_to_assign, animated_id, adt->action, adt->slot_handle, adt->last_slot_identifier);
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
}
|
2024-05-30 12:23:58 +02:00
|
|
|
|
Anim: make action/slot assignment code more generic
In the `blender::animrig` namespace, add two new functions
`generic_assign_action()` and `generic_assign_action_slot()`. These are
now used as basis for (un)assigning Actions and Action slots, both
directly on the ID and for NLA strips.
The method `Action::assign_id()` has been removed, in favour of free
functions for (un)assigning Actions and slots.
This is almost a non-functional change. The only thing that's different
is when both an Action and a slot should get assigned in one go.
Old behaviour: the slot would be inspected, and if not suitable for the
animated ID, the entire operation would be aborted. In other words, any
previously-assigned Action would still be assigned, making this an
atomic operation.
New behaviour: assigning an Action + a slot is no longer atomic. First
the new Action is assigned (which uses the automatic slot selection
based on the name). If after this the given slot cannot be assigned, the
new Action + the auto-selected slot are still kept.
This makes the Action & slot assignment more uniform in behaviour, where
"assign this Action + this slot" as one operation behaves the same as
two sequential operations "assign this Action" + "assign this slot". If
it turns out to be confusing, we can always improve things.
The return code for slot assignment is now more explicit (enum instead
of boolean), so that the caller can decide what to do in case of which
error (like unassigning the slot if the automatic behaviour is
unwanted).
Pull Request: https://projects.blender.org/blender/blender/pulls/127712
2024-09-16 17:23:56 +02:00
|
|
|
ActionSlotAssignmentResult assign_action_and_slot(Action *action,
|
|
|
|
|
Slot *slot_to_assign,
|
|
|
|
|
ID &animated_id)
|
|
|
|
|
{
|
|
|
|
|
if (!assign_action(action, animated_id)) {
|
|
|
|
|
return ActionSlotAssignmentResult::MissingAction;
|
|
|
|
|
}
|
|
|
|
|
return assign_action_slot(slot_to_assign, animated_id);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-04 13:34:12 +02:00
|
|
|
ActionSlotAssignmentResult assign_tmpaction_and_slot_handle(bAction *action,
|
|
|
|
|
const slot_handle_t slot_handle,
|
|
|
|
|
const OwnedAnimData owned_adt)
|
|
|
|
|
{
|
|
|
|
|
if (!assign_tmpaction(action, owned_adt)) {
|
|
|
|
|
return ActionSlotAssignmentResult::MissingAction;
|
|
|
|
|
}
|
|
|
|
|
return generic_assign_action_slot_handle(slot_handle,
|
|
|
|
|
owned_adt.owner_id,
|
|
|
|
|
owned_adt.adt.tmpact,
|
|
|
|
|
owned_adt.adt.tmp_slot_handle,
|
Anim: rename `action_slot_name` to `last_slot_identifier`
`AnimData`, NLA strips, and action constraints all have an `action_slot_name`
field in RNA. The purpose of this field is to store the identifier of the most
recently assigned slot, so that it can be used for auto-assignment when later
assigning different actions.
However, this field name is misleading in two ways:
1. In accordance with #130740, it's actually the slot *identifier*, not name.
2. It could be mistaken as a way to rename the currently assigned slot, which it
is not.
To resolve both of those issues, we're renaming the field to
`last_slot_identifier`, which better communicates its actual nature.
As a bonus, this also ends up decluttering Python autocomplete when looking
for things related to action_slot.
Ref: #130892
Pull Request: https://projects.blender.org/blender/blender/pulls/130911
2024-11-26 16:05:40 +01:00
|
|
|
owned_adt.adt.tmp_last_slot_identifier);
|
2024-10-04 13:34:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
Action *get_action(ID &animated_id)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
|
|
|
|
if (!adt) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (!adt->action) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return &adt->action->wrap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
std::optional<std::pair<Action *, Slot *>> get_action_slot_pair(ID &animated_id)
|
2024-06-13 14:27:36 +02:00
|
|
|
{
|
|
|
|
|
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
|
|
|
|
if (!adt || !adt->action) {
|
|
|
|
|
/* Not animated by any Action. */
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Action &action = adt->action->wrap();
|
2024-07-05 16:59:34 +02:00
|
|
|
Slot *slot = action.slot_for_handle(adt->slot_handle);
|
|
|
|
|
if (!slot) {
|
2024-06-13 14:27:36 +02:00
|
|
|
/* Will not receive any animation from this Action. */
|
|
|
|
|
return std::nullopt;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
return std::make_pair(&action, slot);
|
2024-06-13 14:27:36 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
/* ----- ActionStrip implementation ----------- */
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
Strip &Strip::create(Action &owning_action, const Strip::Type type)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
/* Create the strip. */
|
|
|
|
|
ActionStrip *strip = MEM_cnew<ActionStrip>(__func__);
|
|
|
|
|
memcpy(strip, DNA_struct_default_get(ActionStrip), sizeof(*strip));
|
|
|
|
|
strip->strip_type = int8_t(type);
|
|
|
|
|
|
|
|
|
|
/* Create the strip's data on the owning Action. */
|
|
|
|
|
switch (type) {
|
|
|
|
|
case Strip::Type::Keyframe: {
|
|
|
|
|
StripKeyframeData *strip_data = MEM_new<StripKeyframeData>(__func__);
|
|
|
|
|
strip->data_index = owning_action.strip_keyframe_data_append(strip_data);
|
|
|
|
|
break;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
/* This can happen if someone forgets to add a strip type in the `switch`
|
|
|
|
|
* above, or if someone is evil and passes an invalid strip type to this
|
|
|
|
|
* function. */
|
|
|
|
|
BLI_assert_msg(strip->data_index != -1, "Newly created strip has no data.");
|
|
|
|
|
|
|
|
|
|
return strip->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
bool Strip::is_infinite() const
|
|
|
|
|
{
|
|
|
|
|
return this->frame_start == -std::numeric_limits<float>::infinity() &&
|
|
|
|
|
this->frame_end == std::numeric_limits<float>::infinity();
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
bool Strip::contains_frame(const float frame_time) const
|
|
|
|
|
{
|
|
|
|
|
return this->frame_start <= frame_time && frame_time <= this->frame_end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Strip::is_last_frame(const float frame_time) const
|
|
|
|
|
{
|
|
|
|
|
/* Maybe this needs a more advanced equality check. Implement that when
|
|
|
|
|
* we have an actual example case that breaks. */
|
|
|
|
|
return this->frame_end == frame_time;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Strip::resize(const float frame_start, const float frame_end)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(frame_start <= frame_end);
|
|
|
|
|
BLI_assert_msg(frame_start < std::numeric_limits<float>::infinity(),
|
|
|
|
|
"only the end frame can be at positive infinity");
|
|
|
|
|
BLI_assert_msg(frame_end > -std::numeric_limits<float>::infinity(),
|
|
|
|
|
"only the start frame can be at negative infinity");
|
|
|
|
|
this->frame_start = frame_start;
|
|
|
|
|
this->frame_end = frame_end;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
template<>
|
|
|
|
|
const StripKeyframeData &Strip::data<StripKeyframeData>(const Action &owning_action) const
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(this->type() == StripKeyframeData::TYPE);
|
|
|
|
|
|
|
|
|
|
return *owning_action.strip_keyframe_data()[this->data_index];
|
|
|
|
|
}
|
|
|
|
|
template<> StripKeyframeData &Strip::data<StripKeyframeData>(Action &owning_action)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(this->type() == StripKeyframeData::TYPE);
|
|
|
|
|
|
|
|
|
|
return *owning_action.strip_keyframe_data()[this->data_index];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Strip::slot_data_remove(Action &owning_action, const slot_handle_t slot_handle)
|
|
|
|
|
{
|
|
|
|
|
switch (this->type()) {
|
|
|
|
|
case Type::Keyframe:
|
|
|
|
|
this->data<StripKeyframeData>(owning_action).slot_data_remove(slot_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ----- ActionStripKeyframeData implementation ----------- */
|
2024-05-30 12:23:58 +02:00
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
StripKeyframeData::StripKeyframeData(const StripKeyframeData &other)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
memcpy(this, &other, sizeof(*this));
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
this->channelbag_array = MEM_cnew_array<ActionChannelbag *>(other.channelbag_array_num,
|
2024-07-16 13:24:23 +02:00
|
|
|
__func__);
|
2024-12-02 17:55:59 +01:00
|
|
|
Span<const Channelbag *> channelbags_src = other.channelbags();
|
2024-05-30 12:23:58 +02:00
|
|
|
for (int i : channelbags_src.index_range()) {
|
2024-12-02 17:55:59 +01:00
|
|
|
this->channelbag_array[i] = MEM_new<animrig::Channelbag>(__func__, *other.channelbag(i));
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
StripKeyframeData::~StripKeyframeData()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
for (Channelbag *channelbag_for_slot : this->channelbags()) {
|
2024-07-05 16:59:34 +02:00
|
|
|
MEM_delete(channelbag_for_slot);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-07-16 13:24:23 +02:00
|
|
|
MEM_SAFE_FREE(this->channelbag_array);
|
|
|
|
|
this->channelbag_array_num = 0;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<const Channelbag *> StripKeyframeData::channelbags() const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
return blender::Span<Channelbag *>{reinterpret_cast<Channelbag **>(this->channelbag_array),
|
2024-07-16 13:24:23 +02:00
|
|
|
this->channelbag_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<Channelbag *> StripKeyframeData::channelbags()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
return blender::Span<Channelbag *>{reinterpret_cast<Channelbag **>(this->channelbag_array),
|
2024-09-30 11:51:14 +02:00
|
|
|
this->channelbag_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
const Channelbag *StripKeyframeData::channelbag(const int64_t index) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-16 13:24:23 +02:00
|
|
|
return &this->channelbag_array[index]->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *StripKeyframeData::channelbag(const int64_t index)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-16 13:24:23 +02:00
|
|
|
return &this->channelbag_array[index]->wrap();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
const Channelbag *StripKeyframeData::channelbag_for_slot(const slot_handle_t slot_handle) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
for (const Channelbag *channels : this->channelbags()) {
|
2024-07-05 16:59:34 +02:00
|
|
|
if (channels->slot_handle == slot_handle) {
|
2024-05-30 12:23:58 +02:00
|
|
|
return channels;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
int64_t StripKeyframeData::find_channelbag_index(const Channelbag &channelbag) const
|
2024-07-18 11:14:15 +02:00
|
|
|
{
|
|
|
|
|
for (int64_t index = 0; index < this->channelbag_array_num; index++) {
|
2024-07-27 13:20:43 +10:00
|
|
|
if (this->channelbag(index) == &channelbag) {
|
2024-07-18 11:14:15 +02:00
|
|
|
return index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *StripKeyframeData::channelbag_for_slot(const slot_handle_t slot_handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
const auto *const_this = const_cast<const StripKeyframeData *>(this);
|
2024-07-05 16:59:34 +02:00
|
|
|
const auto *const_channels = const_this->channelbag_for_slot(slot_handle);
|
2024-12-02 17:55:59 +01:00
|
|
|
return const_cast<Channelbag *>(const_channels);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
const Channelbag *StripKeyframeData::channelbag_for_slot(const Slot &slot) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->channelbag_for_slot(slot.handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *StripKeyframeData::channelbag_for_slot(const Slot &slot)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
return this->channelbag_for_slot(slot.handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag &StripKeyframeData::channelbag_for_slot_add(const Slot &slot)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2025-01-30 10:28:45 +01:00
|
|
|
return this->channelbag_for_slot_add(slot.handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Channelbag &StripKeyframeData::channelbag_for_slot_add(const slot_handle_t slot_handle)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert_msg(channelbag_for_slot(slot_handle) == nullptr,
|
|
|
|
|
"Cannot add channelbag for already-registered slot");
|
|
|
|
|
BLI_assert_msg(slot_handle != Slot::unassigned, "Cannot add channelbag for 'unassigned' slot");
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag &channels = MEM_new<ActionChannelbag>(__func__)->wrap();
|
2025-01-30 10:28:45 +01:00
|
|
|
channels.slot_handle = slot_handle;
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
grow_array_and_append<ActionChannelbag *>(
|
2024-07-16 13:24:23 +02:00
|
|
|
&this->channelbag_array, &this->channelbag_array_num, &channels);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
return channels;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag &StripKeyframeData::channelbag_for_slot_ensure(const Slot &slot)
|
2024-07-18 15:07:16 +02:00
|
|
|
{
|
2025-01-31 15:16:10 +01:00
|
|
|
return this->channelbag_for_slot_ensure(slot.handle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Channelbag &StripKeyframeData::channelbag_for_slot_ensure(const slot_handle_t slot_handle)
|
|
|
|
|
{
|
|
|
|
|
Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
|
2024-12-02 17:55:59 +01:00
|
|
|
if (channelbag != nullptr) {
|
|
|
|
|
return *channelbag;
|
2024-07-18 15:07:16 +02:00
|
|
|
}
|
2025-01-31 15:16:10 +01:00
|
|
|
return this->channelbag_for_slot_add(slot_handle);
|
2024-07-18 15:07:16 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
static void channelbag_ptr_destructor(ActionChannelbag **dna_channelbag_ptr)
|
2024-07-18 11:14:15 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag &channelbag = (*dna_channelbag_ptr)->wrap();
|
2024-07-18 11:14:15 +02:00
|
|
|
MEM_delete(&channelbag);
|
|
|
|
|
};
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool StripKeyframeData::channelbag_remove(Channelbag &channelbag_to_remove)
|
2024-07-18 11:14:15 +02:00
|
|
|
{
|
|
|
|
|
const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove);
|
|
|
|
|
if (channelbag_index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dna::array::remove_index(&this->channelbag_array,
|
|
|
|
|
&this->channelbag_array_num,
|
|
|
|
|
nullptr,
|
|
|
|
|
channelbag_index,
|
|
|
|
|
channelbag_ptr_destructor);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
void StripKeyframeData::slot_data_remove(const slot_handle_t slot_handle)
|
2024-09-03 15:48:00 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *channelbag = this->channelbag_for_slot(slot_handle);
|
2024-09-03 15:48:00 +02:00
|
|
|
if (!channelbag) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this->channelbag_remove(*channelbag);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
const FCurve *Channelbag::fcurve_find(const FCurveDescriptor fcurve_descriptor) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-18 15:07:16 +02:00
|
|
|
return animrig::fcurve_find(this->fcurves(), fcurve_descriptor);
|
|
|
|
|
}
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
FCurve *Channelbag::fcurve_find(const FCurveDescriptor fcurve_descriptor)
|
2024-07-18 15:07:16 +02:00
|
|
|
{
|
|
|
|
|
/* Intermediate variable needed to disambiguate const/non-const overloads. */
|
|
|
|
|
Span<FCurve *> fcurves = this->fcurves();
|
|
|
|
|
return animrig::fcurve_find(fcurves, fcurve_descriptor);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
FCurve &Channelbag::fcurve_ensure(Main *bmain, const FCurveDescriptor fcurve_descriptor)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-18 15:07:16 +02:00
|
|
|
if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) {
|
2024-05-30 12:23:58 +02:00
|
|
|
return *existing_fcurve;
|
|
|
|
|
}
|
2024-07-23 08:10:19 +02:00
|
|
|
return this->fcurve_create(bmain, fcurve_descriptor);
|
2024-07-18 17:06:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
FCurve *Channelbag::fcurve_create_unique(Main *bmain, FCurveDescriptor fcurve_descriptor)
|
2024-07-18 17:06:12 +02:00
|
|
|
{
|
|
|
|
|
if (this->fcurve_find(fcurve_descriptor)) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-07-23 08:10:19 +02:00
|
|
|
return &this->fcurve_create(bmain, fcurve_descriptor);
|
2024-07-18 17:06:12 +02:00
|
|
|
}
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
FCurve &Channelbag::fcurve_create(Main *bmain, FCurveDescriptor fcurve_descriptor)
|
2024-07-18 17:06:12 +02:00
|
|
|
{
|
2024-06-28 09:39:51 +02:00
|
|
|
FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-07-18 15:07:16 +02:00
|
|
|
if (this->fcurve_array_num == 0) {
|
2024-05-30 12:23:58 +02:00
|
|
|
new_fcurve->flag |= FCURVE_ACTIVE; /* First curve is added active. */
|
|
|
|
|
}
|
|
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
bActionGroup *group = fcurve_descriptor.channel_group.has_value() ?
|
|
|
|
|
&this->channel_group_ensure(*fcurve_descriptor.channel_group) :
|
|
|
|
|
nullptr;
|
2024-08-29 19:12:45 +02:00
|
|
|
const int insert_index = group ? group->fcurve_range_start + group->fcurve_range_length :
|
|
|
|
|
this->fcurve_array_num;
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
BLI_assert(insert_index <= this->fcurve_array_num);
|
|
|
|
|
|
|
|
|
|
grow_array_and_insert(&this->fcurve_array, &this->fcurve_array_num, insert_index, new_fcurve);
|
|
|
|
|
if (group) {
|
|
|
|
|
group->fcurve_range_length += 1;
|
2024-08-29 19:12:45 +02:00
|
|
|
this->restore_channel_group_invariants();
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
2024-07-23 08:10:19 +02:00
|
|
|
|
|
|
|
|
if (bmain) {
|
|
|
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
return *new_fcurve;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::fcurve_append(FCurve &fcurve)
|
2024-09-13 15:04:47 +02:00
|
|
|
{
|
|
|
|
|
/* Appended F-Curves don't belong to any group yet, so better make sure their
|
|
|
|
|
* group pointer reflects that. */
|
|
|
|
|
fcurve.grp = nullptr;
|
|
|
|
|
|
|
|
|
|
grow_array_and_append(&this->fcurve_array, &this->fcurve_array_num, &fcurve);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-18 17:06:12 +02:00
|
|
|
static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
|
|
|
|
|
{
|
|
|
|
|
BKE_fcurve_free(*fcurve_ptr);
|
|
|
|
|
};
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool Channelbag::fcurve_remove(FCurve &fcurve_to_remove)
|
2024-07-18 17:06:12 +02:00
|
|
|
{
|
2024-09-27 15:18:18 +02:00
|
|
|
if (!this->fcurve_detach(fcurve_to_remove)) {
|
2024-09-24 17:28:35 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
BKE_fcurve_free(&fcurve_to_remove);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::fcurve_remove_by_index(const int64_t fcurve_index)
|
2024-09-27 15:18:18 +02:00
|
|
|
{
|
|
|
|
|
/* Grab the pointer before it's detached, so we can free it after. */
|
|
|
|
|
FCurve *fcurve_to_remove = this->fcurve(fcurve_index);
|
|
|
|
|
|
|
|
|
|
this->fcurve_detach_by_index(fcurve_index);
|
|
|
|
|
|
|
|
|
|
BKE_fcurve_free(fcurve_to_remove);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-24 17:28:35 +02:00
|
|
|
static void fcurve_ptr_noop_destructor(FCurve ** /*fcurve_ptr*/) {}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool Channelbag::fcurve_detach(FCurve &fcurve_to_detach)
|
2024-09-24 17:28:35 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
const int64_t fcurve_index = this->fcurves().first_index_try(&fcurve_to_detach);
|
2024-07-18 17:06:12 +02:00
|
|
|
if (fcurve_index < 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2024-09-27 15:18:18 +02:00
|
|
|
this->fcurve_detach_by_index(fcurve_index);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::fcurve_detach_by_index(const int64_t fcurve_index)
|
2024-09-27 15:18:18 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(fcurve_index >= 0);
|
|
|
|
|
BLI_assert(fcurve_index < this->fcurve_array_num);
|
2024-07-18 17:06:12 +02:00
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
const int group_index = this->channel_group_containing_index(fcurve_index);
|
|
|
|
|
if (group_index != -1) {
|
2024-08-29 19:12:45 +02:00
|
|
|
bActionGroup *group = this->channel_group(group_index);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
group->fcurve_range_length -= 1;
|
|
|
|
|
if (group->fcurve_range_length <= 0) {
|
2024-09-30 11:51:14 +02:00
|
|
|
const int group_index = this->channel_groups().first_index_try(group);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
this->channel_group_remove_raw(group_index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-24 17:28:35 +02:00
|
|
|
dna::array::remove_index(&this->fcurve_array,
|
|
|
|
|
&this->fcurve_array_num,
|
|
|
|
|
nullptr,
|
|
|
|
|
fcurve_index,
|
|
|
|
|
fcurve_ptr_noop_destructor);
|
2024-07-18 17:06:12 +02:00
|
|
|
|
2024-09-02 15:38:34 +02:00
|
|
|
this->restore_channel_group_invariants();
|
|
|
|
|
|
2024-07-25 10:17:42 +10:00
|
|
|
/* As an optimization, this function could call `DEG_relations_tag_update(bmain)` to prune any
|
2024-07-23 08:10:19 +02:00
|
|
|
* relationships that are now no longer necessary. This is not needed for correctness of the
|
|
|
|
|
* depsgraph evaluation results though. */
|
2024-07-18 17:06:12 +02:00
|
|
|
}
|
|
|
|
|
|
2025-01-30 10:24:47 +01:00
|
|
|
void Channelbag::fcurve_move_to_index(FCurve &fcurve, int to_fcurve_index)
|
2024-09-06 16:12:05 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(to_fcurve_index >= 0 && to_fcurve_index < this->fcurves().size());
|
|
|
|
|
|
2024-09-30 11:51:14 +02:00
|
|
|
const int fcurve_index = this->fcurves().first_index_try(&fcurve);
|
2024-09-06 16:12:05 +02:00
|
|
|
BLI_assert_msg(fcurve_index >= 0, "FCurve not in this channel bag.");
|
|
|
|
|
|
|
|
|
|
array_shift_range(
|
|
|
|
|
this->fcurve_array, this->fcurve_array_num, fcurve_index, fcurve_index + 1, to_fcurve_index);
|
|
|
|
|
|
|
|
|
|
this->restore_channel_group_invariants();
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::fcurves_clear()
|
2024-07-18 17:06:12 +02:00
|
|
|
{
|
|
|
|
|
dna::array::clear(&this->fcurve_array, &this->fcurve_array_num, nullptr, fcurve_ptr_destructor);
|
2024-09-30 15:03:46 +02:00
|
|
|
|
|
|
|
|
/* Since all F-Curves are gone, the groups are all empty. */
|
|
|
|
|
for (bActionGroup *group : channel_groups()) {
|
|
|
|
|
group->fcurve_range_start = 0;
|
|
|
|
|
group->fcurve_range_length = 0;
|
|
|
|
|
}
|
2024-07-18 17:06:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-11 12:16:28 +02:00
|
|
|
static void cyclic_keying_ensure_modifier(FCurve &fcurve)
|
|
|
|
|
{
|
|
|
|
|
/* BKE_fcurve_get_cycle_type() only looks at the first modifier to see if it's a Cycle modifier,
|
|
|
|
|
* so if we're going to add one, better make sure it's the first one.
|
|
|
|
|
|
|
|
|
|
* BUT: add_fmodifier() only allows adding a Cycle modifier when there are none yet, so that's
|
|
|
|
|
* all that we need to check for here.
|
|
|
|
|
*/
|
|
|
|
|
if (!BLI_listbase_is_empty(&fcurve.modifiers)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_fmodifier(&fcurve.modifiers, FMODIFIER_TYPE_CYCLES, &fcurve);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ensure there are at least two keys in this F-Curve, in order to define A cycle range.
|
|
|
|
|
*
|
|
|
|
|
* That range does NOT have to be the same as `cycle_range` -- that parameter is only used to
|
|
|
|
|
* insert a 2nd key when there is only one.
|
|
|
|
|
*
|
|
|
|
|
* \note This function ONLY does something when there is a single keyframe. If there are more, the
|
|
|
|
|
* first and last keys already define the cycle range. If that range is not the same as the
|
|
|
|
|
* `cycle_range` parameter, it's seen as an animator's choice and won't be adjusted.
|
|
|
|
|
*/
|
|
|
|
|
static void cyclic_keying_ensure_cycle_range_exists(FCurve &fcurve, const float2 cycle_range)
|
|
|
|
|
{
|
|
|
|
|
/* This is basically a copy of the legacy function `make_new_fcurve_cyclic()`
|
|
|
|
|
* in `keyframing.cc`, except that it's limited to only one thing (ensuring
|
|
|
|
|
* two keys exist to make cycling possible). Creating the F-Curve modifier is
|
|
|
|
|
* the responsibility of another function. */
|
|
|
|
|
|
|
|
|
|
if (fcurve.totvert != 1 || fcurve.bezt == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float period = cycle_range[1] - cycle_range[0];
|
|
|
|
|
if (period < 0.1f) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Move the one existing keyframe into the cycle range. */
|
|
|
|
|
const float frame_offset = fcurve.bezt[0].vec[1][0] - cycle_range[0];
|
|
|
|
|
const float fix = floorf(frame_offset / period) * period;
|
|
|
|
|
|
|
|
|
|
fcurve.bezt[0].vec[0][0] -= fix;
|
|
|
|
|
fcurve.bezt[0].vec[1][0] -= fix;
|
|
|
|
|
fcurve.bezt[0].vec[2][0] -= fix;
|
|
|
|
|
|
|
|
|
|
/* Reallocate the array to make space for the 2nd point. */
|
|
|
|
|
fcurve.totvert++;
|
|
|
|
|
fcurve.bezt = static_cast<BezTriple *>(
|
|
|
|
|
MEM_reallocN(fcurve.bezt, sizeof(BezTriple) * fcurve.totvert));
|
|
|
|
|
|
|
|
|
|
/* Duplicate and offset the keyframe. */
|
|
|
|
|
fcurve.bezt[1] = fcurve.bezt[0];
|
|
|
|
|
fcurve.bezt[1].vec[0][0] += period;
|
|
|
|
|
fcurve.bezt[1].vec[1][0] += period;
|
|
|
|
|
fcurve.bezt[1].vec[2][0] += period;
|
|
|
|
|
}
|
|
|
|
|
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
SingleKeyingResult StripKeyframeData::keyframe_insert(Main *bmain,
|
|
|
|
|
const Slot &slot,
|
|
|
|
|
const FCurveDescriptor fcurve_descriptor,
|
|
|
|
|
const float2 time_value,
|
|
|
|
|
const KeyframeSettings &settings,
|
2024-10-11 12:16:28 +02:00
|
|
|
const eInsertKeyFlags insert_key_flags,
|
|
|
|
|
const std::optional<float2> cycle_range)
|
2024-06-07 13:26:18 +02:00
|
|
|
{
|
|
|
|
|
/* Get the fcurve, or create one if it doesn't exist and the keying flags
|
|
|
|
|
* allow. */
|
2024-07-18 15:07:16 +02:00
|
|
|
FCurve *fcurve = nullptr;
|
|
|
|
|
if (key_insertion_may_create_fcurve(insert_key_flags)) {
|
2024-07-23 08:10:19 +02:00
|
|
|
fcurve = &this->channelbag_for_slot_ensure(slot).fcurve_ensure(bmain, fcurve_descriptor);
|
2024-07-18 15:07:16 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *channels = this->channelbag_for_slot(slot);
|
2024-07-18 15:07:16 +02:00
|
|
|
if (channels != nullptr) {
|
|
|
|
|
fcurve = channels->fcurve_find(fcurve_descriptor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
if (!fcurve) {
|
|
|
|
|
std::fprintf(stderr,
|
2024-07-05 16:59:34 +02:00
|
|
|
"FCurve %s[%d] for slot %s was not created due to either the Only Insert "
|
2024-06-07 13:26:18 +02:00
|
|
|
"Available setting or Replace keyframing mode.\n",
|
2024-06-28 09:39:51 +02:00
|
|
|
fcurve_descriptor.rna_path.c_str(),
|
|
|
|
|
fcurve_descriptor.array_index,
|
2024-11-26 12:11:06 +01:00
|
|
|
slot.identifier);
|
2024-06-07 13:26:18 +02:00
|
|
|
return SingleKeyingResult::CANNOT_CREATE_FCURVE;
|
|
|
|
|
}
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-06-07 13:26:18 +02:00
|
|
|
if (!BKE_fcurve_is_keyframable(fcurve)) {
|
2024-05-30 12:23:58 +02:00
|
|
|
/* TODO: handle this properly, in a way that can be communicated to the user. */
|
|
|
|
|
std::fprintf(stderr,
|
2024-07-05 16:59:34 +02:00
|
|
|
"FCurve %s[%d] for slot %s doesn't allow inserting keys.\n",
|
2024-06-28 09:39:51 +02:00
|
|
|
fcurve_descriptor.rna_path.c_str(),
|
|
|
|
|
fcurve_descriptor.array_index,
|
2024-11-26 12:11:06 +01:00
|
|
|
slot.identifier);
|
2024-05-30 12:23:58 +02:00
|
|
|
return SingleKeyingResult::FCURVE_NOT_KEYFRAMEABLE;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-11 12:16:28 +02:00
|
|
|
if (cycle_range && (*cycle_range)[0] < (*cycle_range)[1]) {
|
|
|
|
|
/* Cyclic keying consists of three things:
|
|
|
|
|
* - Ensure there is a Cycle modifier on the F-Curve.
|
|
|
|
|
* - Ensure the start and end of the cycle have explicit keys, so that the
|
|
|
|
|
* cycle modifier knows how to cycle (it doesn't look at the Action, and
|
|
|
|
|
* as long as the period is correct, the first/last keys don't have to
|
|
|
|
|
* align with the Action start/end).
|
|
|
|
|
* - Offset the key to insert so that it falls within the cycle range.
|
|
|
|
|
*/
|
|
|
|
|
cyclic_keying_ensure_modifier(*fcurve);
|
|
|
|
|
cyclic_keying_ensure_cycle_range_exists(*fcurve, *cycle_range);
|
|
|
|
|
/* Offsetting the key doesn't have to happen here, as insert_vert_fcurve()
|
|
|
|
|
* takes care of that. */
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
const SingleKeyingResult insert_vert_result = insert_vert_fcurve(
|
2024-06-07 13:26:18 +02:00
|
|
|
fcurve, time_value, settings, insert_key_flags);
|
2024-05-30 12:23:58 +02:00
|
|
|
|
|
|
|
|
if (insert_vert_result != SingleKeyingResult::SUCCESS) {
|
|
|
|
|
std::fprintf(stderr,
|
2024-07-05 16:59:34 +02:00
|
|
|
"Could not insert key into FCurve %s[%d] for slot %s.\n",
|
2024-06-28 09:39:51 +02:00
|
|
|
fcurve_descriptor.rna_path.c_str(),
|
|
|
|
|
fcurve_descriptor.array_index,
|
2024-11-26 12:11:06 +01:00
|
|
|
slot.identifier);
|
2024-05-30 12:23:58 +02:00
|
|
|
return insert_vert_result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SingleKeyingResult::SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
/* ActionChannelbag implementation. */
|
2024-05-30 12:23:58 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag::Channelbag(const Channelbag &other)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-07-05 16:59:34 +02:00
|
|
|
this->slot_handle = other.slot_handle;
|
2024-05-30 12:23:58 +02:00
|
|
|
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
this->fcurve_array_num = other.fcurve_array_num;
|
2024-05-30 12:23:58 +02:00
|
|
|
this->fcurve_array = MEM_cnew_array<FCurve *>(other.fcurve_array_num, __func__);
|
|
|
|
|
for (int i = 0; i < other.fcurve_array_num; i++) {
|
|
|
|
|
const FCurve *fcu_src = other.fcurve_array[i];
|
|
|
|
|
this->fcurve_array[i] = BKE_fcurve_copy(fcu_src);
|
|
|
|
|
}
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
this->group_array_num = other.group_array_num;
|
|
|
|
|
this->group_array = MEM_cnew_array<bActionGroup *>(other.group_array_num, __func__);
|
|
|
|
|
for (int i = 0; i < other.group_array_num; i++) {
|
|
|
|
|
const bActionGroup *group_src = other.group_array[i];
|
|
|
|
|
this->group_array[i] = static_cast<bActionGroup *>(MEM_dupallocN(group_src));
|
2024-12-02 17:55:59 +01:00
|
|
|
this->group_array[i]->channelbag = this;
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
2024-09-13 12:05:28 +02:00
|
|
|
|
|
|
|
|
/* BKE_fcurve_copy() resets the FCurve's group pointer. Which is good, because the groups are
|
|
|
|
|
* duplicated too. This sets the group pointers to the correct values. */
|
|
|
|
|
this->restore_channel_group_invariants();
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag::~Channelbag()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
for (FCurve *fcu : this->fcurves()) {
|
|
|
|
|
BKE_fcurve_free(fcu);
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(this->fcurve_array);
|
|
|
|
|
this->fcurve_array_num = 0;
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
for (bActionGroup *group : this->channel_groups()) {
|
|
|
|
|
MEM_SAFE_FREE(group);
|
|
|
|
|
}
|
|
|
|
|
MEM_SAFE_FREE(this->group_array);
|
|
|
|
|
this->group_array_num = 0;
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<const FCurve *> Channelbag::fcurves() const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
return blender::Span<FCurve *>{this->fcurve_array, this->fcurve_array_num};
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<FCurve *> Channelbag::fcurves()
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<FCurve *>{this->fcurve_array, this->fcurve_array_num};
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
const FCurve *Channelbag::fcurve(const int64_t index) const
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
return this->fcurve_array[index];
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
FCurve *Channelbag::fcurve(const int64_t index)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
|
|
|
|
return this->fcurve_array[index];
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<const bActionGroup *> Channelbag::channel_groups() const
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
return blender::Span<bActionGroup *>{this->group_array, this->group_array_num};
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
blender::Span<bActionGroup *> Channelbag::channel_groups()
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
return blender::Span<bActionGroup *>{this->group_array, this->group_array_num};
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
const bActionGroup *Channelbag::channel_group(const int64_t index) const
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(index < this->group_array_num);
|
|
|
|
|
return this->group_array[index];
|
|
|
|
|
}
|
2024-12-02 17:55:59 +01:00
|
|
|
bActionGroup *Channelbag::channel_group(const int64_t index)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(index < this->group_array_num);
|
|
|
|
|
return this->group_array[index];
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
const bActionGroup *Channelbag::channel_group_find(const StringRef name) const
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
for (const bActionGroup *group : this->channel_groups()) {
|
|
|
|
|
if (name == StringRef{group->name}) {
|
|
|
|
|
return group;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bActionGroup *Channelbag::channel_group_find(const StringRef name)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
/* Intermediate variable needed to disambiguate const/non-const overloads. */
|
|
|
|
|
Span<bActionGroup *> groups = this->channel_groups();
|
|
|
|
|
for (bActionGroup *group : groups) {
|
|
|
|
|
if (name == StringRef{group->name}) {
|
|
|
|
|
return group;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
int Channelbag::channel_group_containing_index(const int fcurve_array_index)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
int i = 0;
|
2024-09-15 23:14:09 +10:00
|
|
|
for (const bActionGroup *group : this->channel_groups()) {
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
if (fcurve_array_index >= group->fcurve_range_start &&
|
|
|
|
|
fcurve_array_index < (group->fcurve_range_start + group->fcurve_range_length))
|
|
|
|
|
{
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bActionGroup &Channelbag::channel_group_create(StringRefNull name)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
bActionGroup *new_group = static_cast<bActionGroup *>(
|
|
|
|
|
MEM_callocN(sizeof(bActionGroup), __func__));
|
|
|
|
|
|
|
|
|
|
/* Find the end fcurve index of the current channel groups, to be used as the
|
|
|
|
|
* start of the new channel group. */
|
|
|
|
|
int fcurve_index = 0;
|
|
|
|
|
const int length = this->channel_groups().size();
|
|
|
|
|
if (length > 0) {
|
2024-09-15 23:14:09 +10:00
|
|
|
const bActionGroup *last = this->channel_group(length - 1);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
fcurve_index = last->fcurve_range_start + last->fcurve_range_length;
|
|
|
|
|
}
|
|
|
|
|
new_group->fcurve_range_start = fcurve_index;
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
new_group->channelbag = this;
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
/* Make it selected. */
|
|
|
|
|
new_group->flag = AGRP_SELECTED;
|
|
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
/* Ensure it has a unique name.
|
|
|
|
|
*
|
|
|
|
|
* Note that this only happens here (upon creation). The user can later rename
|
|
|
|
|
* groups to have duplicate names. This is stupid, but it's how the legacy
|
|
|
|
|
* system worked, and at the time of writing this code we're just trying to
|
2024-11-27 19:01:00 +11:00
|
|
|
* match that system's behavior, even when it's goofy. */
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
std::string unique_name = BLI_uniquename_cb(
|
|
|
|
|
[&](const StringRef name) {
|
2024-09-15 23:14:09 +10:00
|
|
|
for (const bActionGroup *group : this->channel_groups()) {
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
if (STREQ(group->name, name.data())) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
'.',
|
|
|
|
|
name[0] == '\0' ? DATA_("Group") : name);
|
|
|
|
|
|
|
|
|
|
STRNCPY_UTF8(new_group->name, unique_name.c_str());
|
|
|
|
|
|
|
|
|
|
grow_array_and_append(&this->group_array, &this->group_array_num, new_group);
|
|
|
|
|
|
|
|
|
|
return *new_group;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bActionGroup &Channelbag::channel_group_ensure(StringRefNull name)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
bActionGroup *group = this->channel_group_find(name);
|
|
|
|
|
if (group) {
|
|
|
|
|
return *group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this->channel_group_create(name);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool Channelbag::channel_group_remove(bActionGroup &group)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
const int group_index = this->channel_groups().first_index_try(&group);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
if (group_index == -1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Move the group's fcurves to just past the end of where the grouped
|
|
|
|
|
* fcurves will be after this group is removed. */
|
2024-09-15 23:14:09 +10:00
|
|
|
const bActionGroup *last_group = this->channel_groups().last();
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
BLI_assert(last_group != nullptr);
|
|
|
|
|
const int to_index = last_group->fcurve_range_start + last_group->fcurve_range_length -
|
|
|
|
|
group.fcurve_range_length;
|
|
|
|
|
array_shift_range(this->fcurve_array,
|
|
|
|
|
this->fcurve_array_num,
|
|
|
|
|
group.fcurve_range_start,
|
|
|
|
|
group.fcurve_range_start + group.fcurve_range_length,
|
|
|
|
|
to_index);
|
|
|
|
|
|
|
|
|
|
this->channel_group_remove_raw(group_index);
|
2024-08-29 19:12:45 +02:00
|
|
|
this->restore_channel_group_invariants();
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 10:24:47 +01:00
|
|
|
void Channelbag::channel_group_move_to_index(bActionGroup &group, const int to_group_index)
|
2024-09-06 16:12:05 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(to_group_index >= 0 && to_group_index < this->channel_groups().size());
|
|
|
|
|
|
2024-09-30 11:51:14 +02:00
|
|
|
const int group_index = this->channel_groups().first_index_try(&group);
|
2024-09-06 16:12:05 +02:00
|
|
|
BLI_assert_msg(group_index >= 0, "Group not in this channel bag.");
|
|
|
|
|
|
|
|
|
|
/* Shallow copy, to track which fcurves should be moved in the second step. */
|
|
|
|
|
const bActionGroup pre_move_group = group;
|
|
|
|
|
|
|
|
|
|
/* First we move the group to its new position. The call to
|
|
|
|
|
* `restore_channel_group_invariants()` is necessary to update the group's
|
|
|
|
|
* fcurve range (as well as the ranges of the other groups) to match its new
|
|
|
|
|
* position in the group array. */
|
|
|
|
|
array_shift_range(
|
|
|
|
|
this->group_array, this->group_array_num, group_index, group_index + 1, to_group_index);
|
|
|
|
|
this->restore_channel_group_invariants();
|
|
|
|
|
|
|
|
|
|
/* Move the fcurves that were part of `group` (as recorded in
|
|
|
|
|
*`pre_move_group`) to their new positions (now in `group`) so that they're
|
|
|
|
|
* part of `group` again. */
|
|
|
|
|
array_shift_range(this->fcurve_array,
|
|
|
|
|
this->fcurve_array_num,
|
|
|
|
|
pre_move_group.fcurve_range_start,
|
|
|
|
|
pre_move_group.fcurve_range_start + pre_move_group.fcurve_range_length,
|
|
|
|
|
group.fcurve_range_start);
|
|
|
|
|
this->restore_channel_group_invariants();
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::channel_group_remove_raw(const int group_index)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(group_index >= 0 && group_index < this->channel_groups().size());
|
|
|
|
|
|
|
|
|
|
MEM_SAFE_FREE(this->group_array[group_index]);
|
|
|
|
|
shrink_array_and_remove(&this->group_array, &this->group_array_num, group_index);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void Channelbag::restore_channel_group_invariants()
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
2024-08-29 19:12:45 +02:00
|
|
|
/* Shift channel groups. */
|
|
|
|
|
{
|
|
|
|
|
int start_index = 0;
|
|
|
|
|
for (bActionGroup *group : this->channel_groups()) {
|
|
|
|
|
group->fcurve_range_start = start_index;
|
|
|
|
|
start_index += group->fcurve_range_length;
|
|
|
|
|
}
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
/* Double-check that this didn't push any of the groups off the end of the
|
|
|
|
|
* fcurve array. */
|
|
|
|
|
BLI_assert(start_index <= this->fcurve_array_num);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
/* Recompute fcurves' group pointers. */
|
|
|
|
|
{
|
|
|
|
|
for (FCurve *fcurve : this->fcurves()) {
|
|
|
|
|
fcurve->grp = nullptr;
|
|
|
|
|
}
|
|
|
|
|
for (bActionGroup *group : this->channel_groups()) {
|
|
|
|
|
for (FCurve *fcurve : group->wrap().fcurves()) {
|
|
|
|
|
fcurve->grp = group;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
bool ChannelGroup::is_legacy() const
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
return this->channelbag == nullptr;
|
2024-08-29 19:12:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Span<FCurve *> ChannelGroup::fcurves()
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(!this->is_legacy());
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
if (this->fcurve_range_length == 0) {
|
|
|
|
|
return {};
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
|
|
|
|
|
this->fcurve_range_length);
|
2024-08-29 19:12:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Span<const FCurve *> ChannelGroup::fcurves() const
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(!this->is_legacy());
|
|
|
|
|
|
|
|
|
|
if (this->fcurve_range_length == 0) {
|
|
|
|
|
return {};
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
2024-08-29 19:12:45 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
return this->channelbag->wrap().fcurves().slice(this->fcurve_range_start,
|
|
|
|
|
this->fcurve_range_length);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 12:23:58 +02:00
|
|
|
/* Utility function implementations. */
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
const animrig::Channelbag *channelbag_for_action_slot(const Action &action,
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
const slot_handle_t slot_handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
assert_baklava_phase_1_invariants(action);
|
|
|
|
|
|
2024-07-05 16:59:34 +02:00
|
|
|
if (slot_handle == Slot::unassigned) {
|
2024-05-30 12:23:58 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
for (const animrig::Layer *layer : action.layers()) {
|
2024-05-30 12:23:58 +02:00
|
|
|
for (const animrig::Strip *strip : layer->strips()) {
|
|
|
|
|
switch (strip->type()) {
|
|
|
|
|
case animrig::Strip::Type::Keyframe: {
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
const animrig::StripKeyframeData &strip_data = strip->data<animrig::StripKeyframeData>(
|
|
|
|
|
action);
|
2024-12-02 17:55:59 +01:00
|
|
|
const animrig::Channelbag *bag = strip_data.channelbag_for_slot(slot_handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
if (bag) {
|
|
|
|
|
return bag;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
animrig::Channelbag *channelbag_for_action_slot(Action &action, const slot_handle_t slot_handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-12-02 17:55:59 +01:00
|
|
|
const animrig::Channelbag *const_bag = channelbag_for_action_slot(
|
2024-07-05 17:01:50 +02:00
|
|
|
const_cast<const Action &>(action), slot_handle);
|
2024-12-02 17:55:59 +01:00
|
|
|
return const_cast<animrig::Channelbag *>(const_bag);
|
2024-05-30 12:23:58 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
Span<FCurve *> fcurves_for_action_slot(Action &action, const slot_handle_t slot_handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-19 14:46:25 +02:00
|
|
|
BLI_assert(action.is_action_layered());
|
2024-07-15 10:26:41 +02:00
|
|
|
assert_baklava_phase_1_invariants(action);
|
2024-12-02 17:55:59 +01:00
|
|
|
animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
if (!bag) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return bag->fcurves();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 17:01:50 +02:00
|
|
|
Span<const FCurve *> fcurves_for_action_slot(const Action &action, const slot_handle_t slot_handle)
|
2024-05-30 12:23:58 +02:00
|
|
|
{
|
2024-09-19 14:46:25 +02:00
|
|
|
BLI_assert(action.is_action_layered());
|
2024-07-15 10:26:41 +02:00
|
|
|
assert_baklava_phase_1_invariants(action);
|
2024-12-02 17:55:59 +01:00
|
|
|
const animrig::Channelbag *bag = channelbag_for_action_slot(action, slot_handle);
|
2024-05-30 12:23:58 +02:00
|
|
|
if (!bag) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
return bag->fcurves();
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 15:21:58 +02:00
|
|
|
FCurve *fcurve_find_in_action(bAction *act, FCurveDescriptor fcurve_descriptor)
|
2023-11-07 13:17:43 +01:00
|
|
|
{
|
2024-06-28 09:39:51 +02:00
|
|
|
if (act == nullptr) {
|
2023-11-07 13:17:43 +01:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-09-23 15:27:57 +02:00
|
|
|
|
|
|
|
|
Action &action = act->wrap();
|
|
|
|
|
if (action.is_action_legacy()) {
|
|
|
|
|
return BKE_fcurve_find(
|
|
|
|
|
&act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert_baklava_phase_1_invariants(action);
|
|
|
|
|
Layer *layer = action.layer(0);
|
|
|
|
|
if (!layer) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
Strip *strip = layer->strip(0);
|
|
|
|
|
if (!strip) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
for (Channelbag *channelbag : strip_data.channelbags()) {
|
2024-09-23 15:27:57 +02:00
|
|
|
FCurve *fcu = channelbag->fcurve_find(fcurve_descriptor);
|
|
|
|
|
if (fcu) {
|
|
|
|
|
return fcu;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCurve *fcurve_find_in_assigned_slot(AnimData &adt, FCurveDescriptor fcurve_descriptor)
|
|
|
|
|
{
|
|
|
|
|
return fcurve_find_in_action_slot(adt.action, adt.slot_handle, fcurve_descriptor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCurve *fcurve_find_in_action_slot(bAction *act,
|
|
|
|
|
const slot_handle_t slot_handle,
|
|
|
|
|
FCurveDescriptor fcurve_descriptor)
|
|
|
|
|
{
|
|
|
|
|
if (act == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Action &action = act->wrap();
|
|
|
|
|
if (action.is_action_legacy()) {
|
|
|
|
|
return BKE_fcurve_find(
|
|
|
|
|
&act->curves, fcurve_descriptor.rna_path.c_str(), fcurve_descriptor.array_index);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *cbag = channelbag_for_action_slot(action, slot_handle);
|
2024-09-23 15:27:57 +02:00
|
|
|
if (!cbag) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return cbag->fcurve_find(fcurve_descriptor);
|
2023-11-07 13:17:43 +01:00
|
|
|
}
|
|
|
|
|
|
2024-10-01 14:48:52 +02:00
|
|
|
bool fcurve_matches_collection_path(const FCurve &fcurve,
|
|
|
|
|
const StringRefNull collection_rna_path,
|
|
|
|
|
const StringRefNull data_name)
|
2024-09-23 16:58:40 +02:00
|
|
|
{
|
|
|
|
|
BLI_assert(!collection_rna_path.is_empty());
|
|
|
|
|
|
|
|
|
|
const size_t quoted_name_size = data_name.size() + 1;
|
|
|
|
|
char *quoted_name = static_cast<char *>(alloca(quoted_name_size));
|
|
|
|
|
|
2024-10-01 14:48:52 +02:00
|
|
|
if (!fcurve.rna_path) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
/* Skipping names longer than `quoted_name_size` is OK since we're after an exact match. */
|
|
|
|
|
if (!BLI_str_quoted_substr(
|
|
|
|
|
fcurve.rna_path, collection_rna_path.c_str(), quoted_name, quoted_name_size))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (quoted_name != data_name) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector<FCurve *> fcurves_in_action_slot_filtered(bAction *act,
|
|
|
|
|
const slot_handle_t slot_handle,
|
|
|
|
|
FunctionRef<bool(const FCurve &fcurve)> predicate)
|
|
|
|
|
{
|
|
|
|
|
BLI_assert(act);
|
|
|
|
|
|
|
|
|
|
Vector<FCurve *> found;
|
|
|
|
|
|
2024-09-24 13:44:27 +02:00
|
|
|
foreach_fcurve_in_action_slot(act->wrap(), slot_handle, [&](FCurve &fcurve) {
|
2024-10-01 14:48:52 +02:00
|
|
|
if (predicate(fcurve)) {
|
|
|
|
|
found.append(&fcurve);
|
2024-09-23 16:58:40 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return found;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 13:31:14 +02:00
|
|
|
Vector<FCurve *> fcurves_in_span_filtered(Span<FCurve *> fcurves,
|
|
|
|
|
FunctionRef<bool(const FCurve &fcurve)> predicate)
|
|
|
|
|
{
|
|
|
|
|
Vector<FCurve *> found;
|
|
|
|
|
|
|
|
|
|
for (FCurve *fcurve : fcurves) {
|
|
|
|
|
if (predicate(*fcurve)) {
|
|
|
|
|
found.append(fcurve);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return found;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-03 15:01:53 +02:00
|
|
|
Vector<FCurve *> fcurves_in_listbase_filtered(ListBase /* FCurve * */ fcurves,
|
|
|
|
|
FunctionRef<bool(const FCurve &fcurve)> predicate)
|
|
|
|
|
{
|
|
|
|
|
Vector<FCurve *> found;
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (FCurve *, fcurve, &fcurves) {
|
|
|
|
|
if (predicate(*fcurve)) {
|
|
|
|
|
found.append(fcurve);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return found;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-07 13:17:43 +01:00
|
|
|
FCurve *action_fcurve_ensure(Main *bmain,
|
|
|
|
|
bAction *act,
|
|
|
|
|
const char group[],
|
|
|
|
|
PointerRNA *ptr,
|
2024-06-28 09:39:51 +02:00
|
|
|
FCurveDescriptor fcurve_descriptor)
|
2023-11-07 13:17:43 +01:00
|
|
|
{
|
2024-06-28 09:39:51 +02:00
|
|
|
if (act == nullptr) {
|
2023-11-07 13:17:43 +01:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-07-12 10:47:32 +02:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
if (animrig::legacy::action_treat_as_legacy(*act)) {
|
|
|
|
|
return action_fcurve_ensure_legacy(bmain, act, group, ptr, fcurve_descriptor);
|
|
|
|
|
}
|
2024-07-12 10:47:32 +02:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
/* NOTE: for layered actions we require the following:
|
|
|
|
|
*
|
|
|
|
|
* - `ptr` is non-null.
|
|
|
|
|
* - `ptr` has an `owner_id` that already uses `act`.
|
|
|
|
|
*
|
|
|
|
|
* This isn't for any principled reason, but rather is because adding
|
|
|
|
|
* support for layered actions to this function was a fix to make Follow
|
|
|
|
|
* Path animation work properly with layered actions (see PR #124353), and
|
|
|
|
|
* those are the requirements the Follow Path code conveniently met.
|
|
|
|
|
* Moreover those requirements were also already met by the other call sites
|
|
|
|
|
* that potentially call this function with layered actions.
|
|
|
|
|
*
|
|
|
|
|
* Trying to puzzle out what "should" happen when these requirements don't
|
|
|
|
|
* hold, or if this is even the best place to handle the layered action
|
|
|
|
|
* cases at all, was leading to discussion of larger changes than made sense
|
|
|
|
|
* to tackle at that point. */
|
|
|
|
|
Action &action = act->wrap();
|
|
|
|
|
|
|
|
|
|
BLI_assert(ptr != nullptr);
|
|
|
|
|
if (ptr == nullptr || ptr->owner_id == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
ID &animated_id = *ptr->owner_id;
|
|
|
|
|
BLI_assert(get_action(animated_id) == &action);
|
|
|
|
|
if (get_action(animated_id) != &action) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-07-12 10:47:32 +02:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
/* Ensure the id has an assigned slot. */
|
|
|
|
|
Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
|
|
|
|
|
if (!slot) {
|
|
|
|
|
/* This means the ID type is not animatable. */
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2024-07-12 10:47:32 +02:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
action.layer_keystrip_ensure();
|
2024-07-12 10:47:32 +02:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
assert_baklava_phase_1_invariants(action);
|
|
|
|
|
StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
|
|
|
|
|
|
|
|
|
|
return &strip_data.channelbag_for_slot_ensure(*slot).fcurve_ensure(bmain, fcurve_descriptor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FCurve *action_fcurve_ensure_legacy(Main *bmain,
|
|
|
|
|
bAction *act,
|
|
|
|
|
const char group[],
|
|
|
|
|
PointerRNA *ptr,
|
|
|
|
|
FCurveDescriptor fcurve_descriptor)
|
|
|
|
|
{
|
|
|
|
|
if (!act) {
|
|
|
|
|
return nullptr;
|
2024-07-12 10:47:32 +02:00
|
|
|
}
|
2023-11-07 13:17:43 +01:00
|
|
|
|
2024-10-11 13:07:20 +02:00
|
|
|
BLI_assert(act->wrap().is_empty() || act->wrap().is_action_legacy());
|
|
|
|
|
|
2023-11-07 17:24:36 +01:00
|
|
|
/* Try to find f-curve matching for this setting.
|
2023-11-07 13:17:43 +01:00
|
|
|
* - add if not found and allowed to add one
|
|
|
|
|
* TODO: add auto-grouping support? how this works will need to be resolved
|
|
|
|
|
*/
|
2024-09-23 15:27:57 +02:00
|
|
|
FCurve *fcu = animrig::fcurve_find_in_action(act, fcurve_descriptor);
|
2023-11-07 13:17:43 +01:00
|
|
|
|
|
|
|
|
if (fcu != nullptr) {
|
|
|
|
|
return fcu;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-20 14:05:09 +02:00
|
|
|
/* Determine the property subtype if we can. */
|
|
|
|
|
std::optional<PropertySubType> prop_subtype = std::nullopt;
|
|
|
|
|
if (ptr != nullptr) {
|
2023-11-28 13:29:46 +01:00
|
|
|
PropertyRNA *resolved_prop;
|
|
|
|
|
PointerRNA resolved_ptr;
|
|
|
|
|
PointerRNA id_ptr = RNA_id_pointer_create(ptr->owner_id);
|
|
|
|
|
const bool resolved = RNA_path_resolve_property(
|
2024-06-28 09:39:51 +02:00
|
|
|
&id_ptr, fcurve_descriptor.rna_path.c_str(), &resolved_ptr, &resolved_prop);
|
2023-11-24 15:28:15 +01:00
|
|
|
if (resolved) {
|
2024-06-20 14:05:09 +02:00
|
|
|
prop_subtype = RNA_property_subtype(resolved_prop);
|
2023-11-24 13:05:10 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 09:39:51 +02:00
|
|
|
BLI_assert_msg(!fcurve_descriptor.prop_subtype.has_value(),
|
|
|
|
|
"Did not expect a prop_subtype to be passed in. This is fine, but does need some "
|
2024-10-11 13:07:20 +02:00
|
|
|
"changes to action_fcurve_ensure_legacy() to deal with it");
|
2024-06-28 09:39:51 +02:00
|
|
|
fcu = create_fcurve_for_channel(
|
|
|
|
|
{fcurve_descriptor.rna_path, fcurve_descriptor.array_index, prop_subtype});
|
2024-06-20 14:05:09 +02:00
|
|
|
|
|
|
|
|
if (BLI_listbase_is_empty(&act->curves)) {
|
|
|
|
|
fcu->flag |= FCURVE_ACTIVE;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-07 13:17:43 +01:00
|
|
|
if (group) {
|
|
|
|
|
bActionGroup *agrp = BKE_action_group_find_name(act, group);
|
|
|
|
|
|
|
|
|
|
if (agrp == nullptr) {
|
|
|
|
|
agrp = action_groups_add_new(act, group);
|
|
|
|
|
|
|
|
|
|
/* Sync bone group colors if applicable. */
|
2024-02-26 17:40:57 +01:00
|
|
|
if (ptr && (ptr->type == &RNA_PoseBone) && ptr->data) {
|
2023-11-30 16:42:40 +11:00
|
|
|
const bPoseChannel *pchan = static_cast<const bPoseChannel *>(ptr->data);
|
2023-11-07 13:17:43 +01:00
|
|
|
action_group_colors_set_from_posebone(agrp, pchan);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
action_groups_add_channel(act, agrp, fcu);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_addtail(&act->curves, fcu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* New f-curve was added, meaning it's possible that it affects
|
|
|
|
|
* dependency graph component which wasn't previously animated.
|
|
|
|
|
*/
|
|
|
|
|
DEG_relations_tag_update(bmain);
|
|
|
|
|
|
|
|
|
|
return fcu;
|
|
|
|
|
}
|
2024-06-28 11:06:34 +02:00
|
|
|
|
2024-08-08 11:06:49 +02:00
|
|
|
bool action_fcurve_remove(Action &action, FCurve &fcu)
|
|
|
|
|
{
|
2024-10-07 13:31:14 +02:00
|
|
|
if (action_fcurve_detach(action, fcu)) {
|
|
|
|
|
BKE_fcurve_free(&fcu);
|
|
|
|
|
return true;
|
2024-08-08 11:06:49 +02:00
|
|
|
}
|
2024-10-07 13:31:14 +02:00
|
|
|
|
2024-08-08 11:06:49 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2024-09-24 17:28:35 +02:00
|
|
|
|
|
|
|
|
bool action_fcurve_detach(Action &action, FCurve &fcurve_to_detach)
|
|
|
|
|
{
|
|
|
|
|
if (action.is_action_legacy()) {
|
|
|
|
|
return BLI_remlink_safe(&action.curves, &fcurve_to_detach);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (Layer *layer : action.layers()) {
|
|
|
|
|
for (Strip *strip : layer->strips()) {
|
|
|
|
|
if (!(strip->type() == Strip::Type::Keyframe)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
|
2024-12-02 17:55:59 +01:00
|
|
|
for (Channelbag *bag : strip_data.channelbags()) {
|
2024-09-24 17:28:35 +02:00
|
|
|
const bool is_detached = bag->fcurve_detach(fcurve_to_detach);
|
|
|
|
|
if (is_detached) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void action_fcurve_attach(Action &action,
|
|
|
|
|
const slot_handle_t action_slot,
|
|
|
|
|
FCurve &fcurve_to_attach,
|
|
|
|
|
std::optional<StringRefNull> group_name)
|
|
|
|
|
{
|
2024-09-26 15:16:56 +02:00
|
|
|
if (animrig::legacy::action_treat_as_legacy(action)) {
|
2024-09-24 17:28:35 +02:00
|
|
|
BLI_addtail(&action.curves, &fcurve_to_attach);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Slot *slot = action.slot_for_handle(action_slot);
|
|
|
|
|
BLI_assert(slot);
|
|
|
|
|
if (!slot) {
|
|
|
|
|
printf("Cannot find slot handle %d on Action %s, unable to attach F-Curve %s[%d] to it!\n",
|
|
|
|
|
action_slot,
|
|
|
|
|
action.id.name + 2,
|
|
|
|
|
fcurve_to_attach.rna_path,
|
|
|
|
|
fcurve_to_attach.array_index);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
action.layer_keystrip_ensure();
|
|
|
|
|
StripKeyframeData &strip_data = action.layer(0)->strip(0)->data<StripKeyframeData>(action);
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag &cbag = strip_data.channelbag_for_slot_ensure(*slot);
|
2024-09-24 17:28:35 +02:00
|
|
|
cbag.fcurve_append(fcurve_to_attach);
|
|
|
|
|
|
|
|
|
|
if (group_name) {
|
|
|
|
|
bActionGroup &group = cbag.channel_group_ensure(*group_name);
|
|
|
|
|
cbag.fcurve_assign_to_channel_group(fcurve_to_attach, group);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void action_fcurve_move(Action &action_dst,
|
|
|
|
|
const slot_handle_t action_slot_dst,
|
|
|
|
|
Action &action_src,
|
|
|
|
|
FCurve &fcurve)
|
|
|
|
|
{
|
|
|
|
|
/* Store the group name locally, as the group will be removed if this was its
|
|
|
|
|
* last F-Curve. */
|
|
|
|
|
std::optional<std::string> group_name;
|
|
|
|
|
if (fcurve.grp) {
|
|
|
|
|
group_name = fcurve.grp->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool is_detached = action_fcurve_detach(action_src, fcurve);
|
|
|
|
|
BLI_assert(is_detached);
|
|
|
|
|
UNUSED_VARS_NDEBUG(is_detached);
|
|
|
|
|
|
|
|
|
|
action_fcurve_attach(action_dst, action_slot_dst, fcurve, group_name);
|
|
|
|
|
}
|
2024-08-08 11:06:49 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
void channelbag_fcurves_move(Channelbag &channelbag_dst, Channelbag &channelbag_src)
|
2024-10-03 15:01:53 +02:00
|
|
|
{
|
|
|
|
|
while (!channelbag_src.fcurves().is_empty()) {
|
|
|
|
|
FCurve &fcurve = *channelbag_src.fcurve(0);
|
|
|
|
|
|
|
|
|
|
/* Store the group name locally, as the group will be removed if this was its
|
|
|
|
|
* last F-Curve. */
|
|
|
|
|
std::optional<std::string> group_name;
|
|
|
|
|
if (fcurve.grp) {
|
|
|
|
|
group_name = fcurve.grp->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool is_detached = channelbag_src.fcurve_detach(fcurve);
|
|
|
|
|
BLI_assert(is_detached);
|
|
|
|
|
UNUSED_VARS_NDEBUG(is_detached);
|
|
|
|
|
|
|
|
|
|
channelbag_dst.fcurve_append(fcurve);
|
|
|
|
|
|
|
|
|
|
if (group_name) {
|
|
|
|
|
bActionGroup &group = channelbag_dst.channel_group_ensure(*group_name);
|
|
|
|
|
channelbag_dst.fcurve_assign_to_channel_group(fcurve, group);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool Channelbag::fcurve_assign_to_channel_group(FCurve &fcurve, bActionGroup &to_group)
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
if (this->channel_groups().first_index_try(&to_group) == -1) {
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 11:51:14 +02:00
|
|
|
const int fcurve_index = this->fcurves().first_index_try(&fcurve);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
if (fcurve_index == -1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
if (fcurve.grp == &to_group) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Remove fcurve from old group, if it belongs to one. */
|
|
|
|
|
if (fcurve.grp != nullptr) {
|
2024-09-06 16:12:05 +02:00
|
|
|
fcurve.grp->fcurve_range_length--;
|
|
|
|
|
if (fcurve.grp->fcurve_range_length == 0) {
|
2024-09-30 11:51:14 +02:00
|
|
|
const int group_index = this->channel_groups().first_index_try(fcurve.grp);
|
2024-09-06 16:12:05 +02:00
|
|
|
this->channel_group_remove_raw(group_index);
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
2024-09-06 16:12:05 +02:00
|
|
|
this->restore_channel_group_invariants();
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
array_shift_range(this->fcurve_array,
|
|
|
|
|
this->fcurve_array_num,
|
|
|
|
|
fcurve_index,
|
|
|
|
|
fcurve_index + 1,
|
|
|
|
|
to_group.fcurve_range_start + to_group.fcurve_range_length);
|
|
|
|
|
to_group.fcurve_range_length++;
|
|
|
|
|
|
2024-08-29 19:12:45 +02:00
|
|
|
this->restore_channel_group_invariants();
|
Anim: add channel groups to layered actions
This PR adds channel groups (also known as fcurve groups or action groups) to
layered actions. For layered actions, these groups belong to the `ChannelBag`s
and can vary by bag.
From a user perspective, the goal is for these to function just like channel
groups from legacy actions. However, internally they are implemented a little
differently: legacy actions store both channel groups and fcurves in a listbase,
and groups indicate what fcurves are in them with a listbase that points
directly into the larger fcurve listbase. Layered actions, on the other hand,
store both fcurves and channel groups in an array, and groups indicate what
fcurves are in them by indexing into the fcurve array.
Despite taking this different approach, we still reuse the `bActionGroup` struct
for the new channel groups, just adding the necessary fields for index-based
fcurve membership as described above.
This PR does not implement all of the functionality needed to reach feature
parity with legacy action channel groups, but implements the main core and gets
them basically working.
It's easier to list the things that *haven't* been implemented yet:
- Operators for letting the user manually create/remove/move channel groups.
- Keyframe selection in the action/dopesheet editor on channel group rows
themselves are not yet working correctly.
- Handling channel groups in legacy/layered action conversion operators.
- Making the legacy `action.groups` property work on single-layer-single-strip
layered actions.
Those are left for future PRs. Other than that, in theory everything should be
working now.
Pull Request: https://projects.blender.org/blender/blender/pulls/125774
2024-08-22 17:13:12 +02:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
bool Channelbag::fcurve_ungroup(FCurve &fcurve)
|
2024-09-06 16:12:05 +02:00
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
const int fcurve_index = this->fcurves().first_index_try(&fcurve);
|
2024-09-06 16:12:05 +02:00
|
|
|
if (fcurve_index == -1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fcurve.grp == nullptr) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bActionGroup *old_group = fcurve.grp;
|
|
|
|
|
|
|
|
|
|
array_shift_range(this->fcurve_array,
|
|
|
|
|
this->fcurve_array_num,
|
|
|
|
|
fcurve_index,
|
|
|
|
|
fcurve_index + 1,
|
|
|
|
|
this->fcurve_array_num - 1);
|
|
|
|
|
|
|
|
|
|
old_group->fcurve_range_length--;
|
|
|
|
|
if (old_group->fcurve_range_length == 0) {
|
2024-09-30 11:51:14 +02:00
|
|
|
const int old_group_index = this->channel_groups().first_index_try(old_group);
|
2024-09-06 16:12:05 +02:00
|
|
|
this->channel_group_remove_raw(old_group_index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this->restore_channel_group_invariants();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
Fix: inserting keys in Action editor for non-active ID doesn't work
After #122672 landed, slots other than the the slot for the currently active
ID became accessible from the Action Editor. However, the code for inserting
keys directly in the Action Editor assumed that the ID being keyed was
always the active one. This made inserting keys on slots other than the slot
for the active ID fail because the RNA paths wouldn't resolve for that ID.
Additionally, even if they did resolve (e.g. if the ID type was the same), the
keying code would use the wrong ID anyway, which would result in unexpected
behavior regardless.
This commit fixes this by looking up an ID that actually uses the given slot,
and using that ID to perform the keying.
Note that there are various corner cases that have to be handled, such as when
there is more than one user of a slot (and none of them are the active ID),
which makes the choice ambiguous, or when there are no users of a slot. We
handle these corner cases by simply keying the fcurves directly, rather than
going through the normal keyframing code paths. This means that in those corner
cases the value of the property in the scene is ignored, and the fcurve is
simply keyed with whatever value the fcurve evaluates to on that frame. We may
revisit this in the future if this fallback behavior turns out to not feel good
to users in practice, but for now it seems like a reasonable solution.
Pull Request: https://projects.blender.org/blender/blender/pulls/124227
2024-07-09 11:25:45 +02:00
|
|
|
ID *action_slot_get_id_for_keying(Main &bmain,
|
|
|
|
|
Action &action,
|
|
|
|
|
const slot_handle_t slot_handle,
|
|
|
|
|
ID *primary_id)
|
|
|
|
|
{
|
2024-09-26 15:16:56 +02:00
|
|
|
if (animrig::legacy::action_treat_as_legacy(action)) {
|
Fix: inserting keys in Action editor for non-active ID doesn't work
After #122672 landed, slots other than the the slot for the currently active
ID became accessible from the Action Editor. However, the code for inserting
keys directly in the Action Editor assumed that the ID being keyed was
always the active one. This made inserting keys on slots other than the slot
for the active ID fail because the RNA paths wouldn't resolve for that ID.
Additionally, even if they did resolve (e.g. if the ID type was the same), the
keying code would use the wrong ID anyway, which would result in unexpected
behavior regardless.
This commit fixes this by looking up an ID that actually uses the given slot,
and using that ID to perform the keying.
Note that there are various corner cases that have to be handled, such as when
there is more than one user of a slot (and none of them are the active ID),
which makes the choice ambiguous, or when there are no users of a slot. We
handle these corner cases by simply keying the fcurves directly, rather than
going through the normal keyframing code paths. This means that in those corner
cases the value of the property in the scene is ignored, and the fcurve is
simply keyed with whatever value the fcurve evaluates to on that frame. We may
revisit this in the future if this fallback behavior turns out to not feel good
to users in practice, but for now it seems like a reasonable solution.
Pull Request: https://projects.blender.org/blender/blender/pulls/124227
2024-07-09 11:25:45 +02:00
|
|
|
if (primary_id && get_action(*primary_id) == &action) {
|
|
|
|
|
return primary_id;
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Slot *slot = action.slot_for_handle(slot_handle);
|
|
|
|
|
if (slot == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
blender::Span<ID *> users = slot->users(bmain);
|
|
|
|
|
if (users.size() == 1) {
|
|
|
|
|
/* We only do this for `users.size() == 1` and not `users.size() >= 1`
|
|
|
|
|
* because when there's more than one user it's ambiguous which user we
|
|
|
|
|
* should return, and that would be unpredictable for end users of Blender.
|
|
|
|
|
* We also expect that to be a corner case anyway. So instead we let that
|
|
|
|
|
* case either get disambiguated by the primary ID in the case below, or
|
|
|
|
|
* return null. */
|
|
|
|
|
return users[0];
|
|
|
|
|
}
|
|
|
|
|
if (users.contains(primary_id)) {
|
|
|
|
|
return primary_id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 16:39:00 +02:00
|
|
|
ID *action_slot_get_id_best_guess(Main &bmain, Slot &slot, ID *primary_id)
|
|
|
|
|
{
|
|
|
|
|
blender::Span<ID *> users = slot.users(bmain);
|
|
|
|
|
if (users.is_empty()) {
|
2024-08-26 11:34:42 +10:00
|
|
|
return nullptr;
|
2024-07-11 16:39:00 +02:00
|
|
|
}
|
|
|
|
|
if (users.contains(primary_id)) {
|
|
|
|
|
return primary_id;
|
|
|
|
|
}
|
|
|
|
|
return users[0];
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 15:07:01 +02:00
|
|
|
slot_handle_t first_slot_handle(const ::bAction &dna_action)
|
|
|
|
|
{
|
|
|
|
|
const Action &action = dna_action.wrap();
|
|
|
|
|
if (action.slot_array_num == 0) {
|
|
|
|
|
return Slot::unassigned;
|
|
|
|
|
}
|
|
|
|
|
return action.slot_array[0]->handle;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 11:06:34 +02:00
|
|
|
void assert_baklava_phase_1_invariants(const Action &action)
|
|
|
|
|
{
|
|
|
|
|
if (action.is_action_legacy()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (action.layers().is_empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(action.layers().size() == 1);
|
|
|
|
|
|
|
|
|
|
assert_baklava_phase_1_invariants(*action.layer(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assert_baklava_phase_1_invariants(const Layer &layer)
|
|
|
|
|
{
|
|
|
|
|
if (layer.strips().is_empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
BLI_assert(layer.strips().size() == 1);
|
|
|
|
|
|
|
|
|
|
assert_baklava_phase_1_invariants(*layer.strip(0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assert_baklava_phase_1_invariants(const Strip &strip)
|
|
|
|
|
{
|
2024-06-28 17:42:53 +02:00
|
|
|
UNUSED_VARS_NDEBUG(strip);
|
2024-06-28 11:06:34 +02:00
|
|
|
BLI_assert(strip.type() == Strip::Type::Keyframe);
|
|
|
|
|
BLI_assert(strip.is_infinite());
|
|
|
|
|
BLI_assert(strip.frame_offset == 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 11:57:26 +02:00
|
|
|
Action *convert_to_layered_action(Main &bmain, const Action &legacy_action)
|
|
|
|
|
{
|
|
|
|
|
if (!legacy_action.is_action_legacy()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string suffix = "_layered";
|
|
|
|
|
/* In case the legacy action has a long name it is shortened to make space for the suffix. */
|
|
|
|
|
char legacy_name[MAX_ID_NAME - 10];
|
|
|
|
|
/* Offsetting the id.name to remove the ID prefix (AC) which gets added back later. */
|
|
|
|
|
STRNCPY_UTF8(legacy_name, legacy_action.id.name + 2);
|
|
|
|
|
|
|
|
|
|
const std::string layered_action_name = std::string(legacy_name) + suffix;
|
|
|
|
|
bAction *dna_action = BKE_action_add(&bmain, layered_action_name.c_str());
|
|
|
|
|
|
|
|
|
|
Action &converted_action = dna_action->wrap();
|
|
|
|
|
Slot &slot = converted_action.slot_add();
|
|
|
|
|
Layer &layer = converted_action.layer_add(legacy_action.id.name);
|
Anim: change how action strip data is stored
This updates the layered action data model to store strip data differently. Specifically:
- `Strip` is now just a single, POD type that only stores the data common to all
strips, such as start/end frames.
- The data that might be of a completely different nature between strips (e.g.
keyframe data vs modifier data) is now stored in arrays on the action itself.
- `Strip`s indicate their type with an enum, and specify their data with an
index into the array on the action that stores data for that type.
This approach requires a little more data juggling, but has the advantage of
making `Strip`s themselves super simple POD types, and also opening the door to
trivial strip instancing later on: instances are just strips that point at the
same data.
The intention is that the RNA API remains the same: from RNA's perspective there
is no data storage separate from the strips, and a strip's data is presented as
fields and methods directly on the strip itself. Different strip types will be
presented as different subtypes of `ActionStrip`, each with their own fields and
methods specific to their underlying data's type. However, this PR doesn't
implement that sub-typing, leaving it for a future PR. It does, however, put the
fields and methods of the one strip type we have so far directly on the strip,
which avoids changing the APIs we have so far.
This PR implements the bulk of this new approach, and everything should be
functional and working correctly. However, there are two TODO items left over
that will be implemented in forthcoming PRs:
- Type refinement in the RNA api. This PR actually removes the existing type
refinement code that was implemented in terms of the inheritance tree of the
actual C++ types, and this will need to be reimplemented in terms of the new
data model. The RNA API still works without the type refinement since there
are only keyframe strips right now, but it will be needed in preparation for
more strip types down the road.
- Strip data deletion. This PR only deletes data from the strip data arrays when
the whole action is deleted, and otherwise just accumulates strip data as more
and more strips are added, never removing the data when the corresponding
strips get removed. That's fine in the short term, especially since we only
support single strips right now. But it does need to be implemented in
preparation for proper layered actions.
Pull Request: https://projects.blender.org/blender/blender/pulls/126559
2024-09-17 17:31:09 +02:00
|
|
|
Strip &strip = layer.strip_add(converted_action, Strip::Type::Keyframe);
|
|
|
|
|
BLI_assert(strip.data<StripKeyframeData>(converted_action).channelbag_array_num == 0);
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *bag = &strip.data<StripKeyframeData>(converted_action).channelbag_for_slot_add(slot);
|
2024-07-11 11:57:26 +02:00
|
|
|
|
|
|
|
|
const int fcu_count = BLI_listbase_count(&legacy_action.curves);
|
|
|
|
|
bag->fcurve_array = MEM_cnew_array<FCurve *>(fcu_count, "Convert to layered action");
|
|
|
|
|
bag->fcurve_array_num = fcu_count;
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
2024-09-06 17:24:13 +02:00
|
|
|
blender::Map<FCurve *, FCurve *> old_new_fcurve_map;
|
2024-07-11 11:57:26 +02:00
|
|
|
LISTBASE_FOREACH_INDEX (FCurve *, fcu, &legacy_action.curves, i) {
|
|
|
|
|
bag->fcurve_array[i] = BKE_fcurve_copy(fcu);
|
2024-09-06 17:24:13 +02:00
|
|
|
bag->fcurve_array[i]->grp = nullptr;
|
|
|
|
|
old_new_fcurve_map.add(fcu, bag->fcurve_array[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LISTBASE_FOREACH (bActionGroup *, group, &legacy_action.groups) {
|
|
|
|
|
/* The resulting group might not have the same name, because the legacy system allowed
|
|
|
|
|
* duplicate names while the new system ensures uniqueness. */
|
|
|
|
|
bActionGroup &converted_group = bag->channel_group_create(group->name);
|
|
|
|
|
LISTBASE_FOREACH (FCurve *, fcu, &group->channels) {
|
|
|
|
|
if (fcu->grp != group) {
|
|
|
|
|
/* Since the group listbase points to the action listbase, it won't stop iterating when
|
|
|
|
|
* reaching the end of the group but iterate to the end of the action FCurves. */
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
FCurve *new_fcurve = old_new_fcurve_map.lookup(fcu);
|
|
|
|
|
bag->fcurve_assign_to_channel_group(*new_fcurve, converted_group);
|
|
|
|
|
}
|
2024-07-11 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &converted_action;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 15:08:16 +02:00
|
|
|
/**
|
|
|
|
|
* Clone information from the given slot into this slot while retaining important info like the
|
2024-11-26 12:11:06 +01:00
|
|
|
* slot handle and runtime data. This copies the identifier which might clash with other
|
2025-01-20 15:19:25 +01:00
|
|
|
* identifiers on the action. Call `slot_identifier_ensure_unique` after.
|
2024-09-20 15:08:16 +02:00
|
|
|
*/
|
|
|
|
|
static void clone_slot(Slot &from, Slot &to)
|
|
|
|
|
{
|
|
|
|
|
ActionSlotRuntimeHandle *runtime = to.runtime;
|
|
|
|
|
slot_handle_t handle = to.handle;
|
|
|
|
|
*reinterpret_cast<ActionSlot *>(&to) = *reinterpret_cast<ActionSlot *>(&from);
|
|
|
|
|
to.runtime = runtime;
|
|
|
|
|
to.handle = handle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void move_slot(Main &bmain, Slot &source_slot, Action &from_action, Action &to_action)
|
|
|
|
|
{
|
2024-09-30 11:51:14 +02:00
|
|
|
BLI_assert(from_action.slots().contains(&source_slot));
|
2024-09-20 15:08:16 +02:00
|
|
|
BLI_assert(&from_action != &to_action);
|
|
|
|
|
|
|
|
|
|
/* No merging of strips or layers is handled. All data is put into the assumed single strip. */
|
|
|
|
|
assert_baklava_phase_1_invariants(from_action);
|
|
|
|
|
assert_baklava_phase_1_invariants(to_action);
|
|
|
|
|
|
|
|
|
|
StripKeyframeData &from_strip_data = from_action.layer(0)->strip(0)->data<StripKeyframeData>(
|
|
|
|
|
from_action);
|
|
|
|
|
StripKeyframeData &to_strip_data = to_action.layer(0)->strip(0)->data<StripKeyframeData>(
|
|
|
|
|
to_action);
|
|
|
|
|
|
|
|
|
|
Slot &target_slot = to_action.slot_add();
|
|
|
|
|
clone_slot(source_slot, target_slot);
|
2024-11-26 12:11:06 +01:00
|
|
|
slot_identifier_ensure_unique(to_action, target_slot);
|
2024-09-20 15:08:16 +02:00
|
|
|
|
2024-12-02 17:55:59 +01:00
|
|
|
Channelbag *channelbag = from_strip_data.channelbag_for_slot(source_slot.handle);
|
|
|
|
|
BLI_assert(channelbag != nullptr);
|
|
|
|
|
channelbag->slot_handle = target_slot.handle;
|
|
|
|
|
grow_array_and_append<ActionChannelbag *>(
|
|
|
|
|
&to_strip_data.channelbag_array, &to_strip_data.channelbag_array_num, channelbag);
|
|
|
|
|
int index = from_strip_data.find_channelbag_index(*channelbag);
|
|
|
|
|
shrink_array_and_remove<ActionChannelbag *>(
|
2024-09-20 15:08:16 +02:00
|
|
|
&from_strip_data.channelbag_array, &from_strip_data.channelbag_array_num, index);
|
|
|
|
|
|
|
|
|
|
/* Reassign all users of `source_slot` to the action `to_action` and the slot `target_slot`. */
|
|
|
|
|
for (ID *user : source_slot.users(bmain)) {
|
2024-10-15 11:04:04 +02:00
|
|
|
const auto assign_other_action = [&](ID & /* animated_id */,
|
|
|
|
|
bAction *&action_ptr_ref,
|
|
|
|
|
slot_handle_t &slot_handle_ref,
|
2024-11-26 12:11:06 +01:00
|
|
|
char *slot_identifier) -> bool {
|
2024-09-20 15:08:16 +02:00
|
|
|
/* Only reassign if the reference is actually from the same action. Could be from a different
|
|
|
|
|
* action when using the NLA or action constraints. */
|
|
|
|
|
if (action_ptr_ref != &from_action) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-09-26 17:29:01 +02:00
|
|
|
|
|
|
|
|
{ /* Assign the Action. */
|
|
|
|
|
const bool assign_ok = generic_assign_action(
|
2024-11-26 12:11:06 +01:00
|
|
|
*user, &to_action, action_ptr_ref, slot_handle_ref, slot_identifier);
|
2024-09-26 17:29:01 +02:00
|
|
|
BLI_assert_msg(assign_ok, "Expecting slotted Actions to always be assignable");
|
|
|
|
|
UNUSED_VARS_NDEBUG(assign_ok);
|
|
|
|
|
}
|
|
|
|
|
{ /* Assign the Slot. */
|
|
|
|
|
const ActionSlotAssignmentResult result = generic_assign_action_slot(
|
2024-11-26 12:11:06 +01:00
|
|
|
&target_slot, *user, action_ptr_ref, slot_handle_ref, slot_identifier);
|
2024-09-26 17:29:01 +02:00
|
|
|
BLI_assert(result == ActionSlotAssignmentResult::OK);
|
|
|
|
|
UNUSED_VARS_NDEBUG(result);
|
|
|
|
|
}
|
2024-09-20 15:08:16 +02:00
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
foreach_action_slot_use_with_references(*user, assign_other_action);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
from_action.slot_remove(source_slot);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-09 09:34:49 +11:00
|
|
|
} // namespace blender::animrig
|