Files
test2/source/blender/animrig/intern/animdata.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

381 lines
12 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
Anim: merge Animation data-block into bAction The new/experimental, layered `Animation` data-block is merged with the existing `bAction` data-block. The `Animation` data-block is considerably newer than `bAction`, so the supporting code that was written for it is also more modern. When moving that code into `bAction`, I chose to keep the modernity where possible, and thus some of the old code has been updated as well. Things like preferring references over pointers. The `Animation` data-block is now gone from DNA, the main database, etc. As this was still an experimental feature, there is no versioning code to convert any of that to Actions. The DNA struct `bAction` now has a C++ wrapper `animrig::Action`, that can be obtained via `some_action->wrap()`. `animrig::Action` has functions `is_empty()`, `is_action_legacy()`, and `is_action_layered()`. They **all** return `true` when the Action is empty, as in that case none of the data that makes an action either 'legacy' or 'layered' is there. The 'animation filtering' code (for showing things in the dope sheet, graph editor, etc) that I wrote for `Animation` is intentionally kept around. These types now target 'layered actions' and the already-existing ones 'legacy actions'. A future PR may merge these two together, but given how much work it was to add something new there, I'd rather wait until the dust has settled on this commit. There are plenty of variables (and some comments) named `anim` or `animation` that now are of type `animrig::Action`. I haven't renamed them all, to keep the noise level low in this commit (it's already big enough). This can be done in a followup, non-functional PR. Related task: #121355 Pull Request: https://projects.blender.org/blender/blender/pulls/121357
2024-05-13 15:51:26 +02:00
#include "ANIM_action.hh"
#include "ANIM_animdata.hh"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_fcurve.hh"
#include "BKE_key.hh"
2024-01-15 12:44:04 -05:00
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_node.hh"
#include "BLT_translation.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DNA_anim_types.h"
#include "DNA_key_types.h"
#include "DNA_material_types.h"
#include "DNA_particle_types.h"
#include "RNA_access.hh"
namespace blender::animrig {
/* -------------------------------------------------------------------- */
/** \name Public F-Curves API
* \{ */
/* Find the users of the given ID within the objects of `bmain` and add non-duplicates to the end
* of `related_ids`. */
static void add_object_data_users(const Main &bmain, const ID &id, Vector<ID *> &related_ids)
{
if (ID_REAL_USERS(&id) != 1) {
/* Only find objects if this ID is only used once. */
return;
}
Object *ob;
ID *object_id;
FOREACH_MAIN_LISTBASE_ID_BEGIN (&bmain.objects, object_id) {
ob = reinterpret_cast<Object *>(object_id);
if (ob->data != &id) {
continue;
}
related_ids.append_non_duplicates(&ob->id);
}
FOREACH_MAIN_LISTBASE_ID_END;
}
Vector<ID *> find_related_ids(Main &bmain, ID &id)
{
Vector<ID *> related_ids({&id});
/* `related_ids` can grow during an iteration if the ID of the current iteration has associated
* code that defines relationships. */
for (int i = 0; i < related_ids.size(); i++) {
ID *related_id = related_ids[i];
if (related_id->flag & ID_FLAG_EMBEDDED_DATA) {
/* No matter the type of embedded ID, their owner can always be added to the related IDs. */
/* User counting is irrelevant for the logic here, because embedded IDs cannot be shared.
* Embedded IDs do exist (sometimes) with a non-zero user count, hence the assertion that the
* user count is not greater than 1. */
BLI_assert(ID_REAL_USERS(related_id) <= 1);
ID *owner_id = BKE_id_owner_get(related_id);
/* Embedded IDs should always have an owner. */
BLI_assert(owner_id != nullptr);
related_ids.append_non_duplicates(owner_id);
}
/* No action found on current ID, add related IDs to the ID Vector. */
switch (GS(related_id->name)) {
case ID_OB: {
Object *ob = reinterpret_cast<Object *>(related_id);
if (!ob->data) {
break;
}
ID *data = static_cast<ID *>(ob->data);
if (ID_REAL_USERS(data) == 1) {
related_ids.append_non_duplicates(data);
}
LISTBASE_FOREACH (ParticleSystem *, particle_system, &ob->particlesystem) {
if (!particle_system->part) {
continue;
}
if (ID_REAL_USERS(&particle_system->part->id) != 1) {
continue;
}
related_ids.append_non_duplicates(&particle_system->part->id);
}
break;
}
case ID_KE: {
2024-09-17 17:28:02 +10:00
/* Shape-keys. */
Key *key = reinterpret_cast<Key *>(related_id);
2024-09-17 17:28:02 +10:00
/* Shape-keys are not embedded but there is currently no way to reuse them. */
BLI_assert(ID_REAL_USERS(related_id) == 1);
related_ids.append_non_duplicates(key->from);
break;
}
case ID_MA: {
/* Explicitly not relating materials and material users. */
Material *mat = reinterpret_cast<Material *>(related_id);
if (mat->nodetree && ID_REAL_USERS(&mat->nodetree->id) == 1) {
related_ids.append_non_duplicates(&mat->nodetree->id);
}
break;
}
case ID_PA: {
if (ID_REAL_USERS(related_id) != 1) {
continue;
}
Object *ob;
ID *object_id;
/* Find users of this particle setting. */
FOREACH_MAIN_LISTBASE_ID_BEGIN (&bmain.objects, object_id) {
ob = reinterpret_cast<Object *>(object_id);
bool object_uses_particle_settings = false;
LISTBASE_FOREACH (ParticleSystem *, particle_system, &ob->particlesystem) {
if (!particle_system->part) {
continue;
}
if (&particle_system->part->id != related_id) {
continue;
}
object_uses_particle_settings = true;
break;
}
if (object_uses_particle_settings) {
related_ids.append_non_duplicates(&ob->id);
break;
}
}
FOREACH_MAIN_LISTBASE_ID_END;
break;
}
default: {
/* Just check if the ID is used as object data somewhere. */
add_object_data_users(bmain, *related_id, related_ids);
bNodeTree *node_tree = bke::node_tree_from_id(related_id);
if (node_tree && ID_REAL_USERS(&node_tree->id) == 1) {
related_ids.append_non_duplicates(&node_tree->id);
}
Key *key = BKE_key_from_id(related_id);
if (key) {
2024-09-17 17:28:02 +10:00
/* No check for multi user because the shape-key cannot be shared. */
BLI_assert(ID_REAL_USERS(&key->id) == 1);
related_ids.append_non_duplicates(&key->id);
}
break;
}
}
}
return related_ids;
}
/* Find an action on an ID that is related to the given ID. Related things are e.g. Object<->Data,
* Mesh<->Material and so on. */
static bAction *find_related_action(Main &bmain, ID &id)
{
Vector<ID *> related_ids = find_related_ids(bmain, id);
for (ID *related_id : related_ids) {
Action *action = get_action(*related_id);
if (action && action->is_action_layered()) {
/* Returning the first action found means highest priority has the action closest in the
* relationship graph. */
return action;
}
}
return nullptr;
}
bAction *id_action_ensure(Main *bmain, ID *id)
{
AnimData *adt = BKE_animdata_ensure_id(id);
if (adt == nullptr) {
printf("ERROR: data-block type is not animatable (ID = %s)\n", (id) ? (id->name) : "<None>");
return nullptr;
}
/* init action if none available yet */
/* TODO: need some wizardry to handle NLA stuff correct */
if (adt->action == nullptr) {
Anim: Remove 'Slotted Actions' experimental flag This commit takes the 'Slotted Actions' out of the experimental phase. As a result: - All newly created Actions will be slotted Actions. - Legacy Actions loaded from disk will be versioned to slotted Actions. - The new Python API for slots, layers, strips, and channel bags is available. - The legacy Python API for accessing F-Curves and Action Groups is still available, and will operate on the F-Curves/Groups for the first slot only. - Creating an Action by keying (via the UI, operators, or the `rna_struct.keyframe_insert` function) will try and share Actions between related data-blocks. See !126655 for more info about this. - Assigning an Action to a data-block will auto-assign a suitable Action Slot. The logic for this is described below. However, There are cases where this does _not_ automatically assign a slot, and thus the Action will effectively _not_ animate the data-block. Effort has been spent to make Action selection work both reliably for Blender users as well as keep the behaviour the same for Python scripts. Where these two goals did not converge, reliability and understandability for users was prioritised. Auto-selection of the Action Slot upon assigning the Action works as follows. The first rule to find a slot wins. 1. The data-block remembers the slot name that was last assigned. If the newly assigned Action has a slot with that name, it is chosen. 2. If the Action has a slot with the same name as the data-block, it is chosen. 3. If the Action has only one slot, and it has never been assigned to anything, it is chosen. 4. If the Action is assigned to an NLA strip or an Action constraint, and the Action has a single slot, and that slot has a suitable ID type, it is chosen. This last step is what I was referring to with "Where these two goals did not converge, reliability and understandability for users was prioritised." For regular Action assignments (like via the Action selectors in the Properties editor) this rule doesn't apply, even though with legacy Actions the final state ("it is animated by this Action") differs from the final state with slotted Actions ("it has no slot so is not animated"). This is done to support the following workflow: - Create an Action by animating Cube. - In order to animate Suzanne with that same Action, assign the Action to Suzanne. - Start keying Suzanne. This auto-creates and auto-assigns a new slot for Suzanne. If rule 4. above would apply in this case, the 2nd step would automatically select the Cube slot for Suzanne as well, which would immediately overwrite Suzanne's properties with the Cube animation. Technically, this commit: - removes the `WITH_ANIM_BAKLAVA` build flag, - removes the `use_animation_baklava` experimental flag in preferences, - updates the code to properly deal with the fact that empty Actions are now always considered slotted/layered Actions (instead of that relying on the user preference). Note that 'slotted Actions' and 'layered Actions' are the exact same thing, just focusing on different aspects (slot & layers) of the new data model. The "Baklava phase 1" assumptions are still asserted. This means that: - an Action can have zero or one layer, - that layer can have zero or one strip, - that strip must be of type 'keyframe' and be infinite with zero offset. The code to handle legacy Actions is NOT removed in this commit. It will be removed later. For now it's likely better to keep it around as reference to the old behaviour in order to aid in some inevitable bugfixing. Ref: #120406
2024-10-15 16:29:53 +02:00
bAction *action = find_related_action(*bmain, *id);
if (action == nullptr) {
/* init action name from name of ID block */
char actname[sizeof(id->name) - 2];
Anim: Remove 'Slotted Actions' experimental flag This commit takes the 'Slotted Actions' out of the experimental phase. As a result: - All newly created Actions will be slotted Actions. - Legacy Actions loaded from disk will be versioned to slotted Actions. - The new Python API for slots, layers, strips, and channel bags is available. - The legacy Python API for accessing F-Curves and Action Groups is still available, and will operate on the F-Curves/Groups for the first slot only. - Creating an Action by keying (via the UI, operators, or the `rna_struct.keyframe_insert` function) will try and share Actions between related data-blocks. See !126655 for more info about this. - Assigning an Action to a data-block will auto-assign a suitable Action Slot. The logic for this is described below. However, There are cases where this does _not_ automatically assign a slot, and thus the Action will effectively _not_ animate the data-block. Effort has been spent to make Action selection work both reliably for Blender users as well as keep the behaviour the same for Python scripts. Where these two goals did not converge, reliability and understandability for users was prioritised. Auto-selection of the Action Slot upon assigning the Action works as follows. The first rule to find a slot wins. 1. The data-block remembers the slot name that was last assigned. If the newly assigned Action has a slot with that name, it is chosen. 2. If the Action has a slot with the same name as the data-block, it is chosen. 3. If the Action has only one slot, and it has never been assigned to anything, it is chosen. 4. If the Action is assigned to an NLA strip or an Action constraint, and the Action has a single slot, and that slot has a suitable ID type, it is chosen. This last step is what I was referring to with "Where these two goals did not converge, reliability and understandability for users was prioritised." For regular Action assignments (like via the Action selectors in the Properties editor) this rule doesn't apply, even though with legacy Actions the final state ("it is animated by this Action") differs from the final state with slotted Actions ("it has no slot so is not animated"). This is done to support the following workflow: - Create an Action by animating Cube. - In order to animate Suzanne with that same Action, assign the Action to Suzanne. - Start keying Suzanne. This auto-creates and auto-assigns a new slot for Suzanne. If rule 4. above would apply in this case, the 2nd step would automatically select the Cube slot for Suzanne as well, which would immediately overwrite Suzanne's properties with the Cube animation. Technically, this commit: - removes the `WITH_ANIM_BAKLAVA` build flag, - removes the `use_animation_baklava` experimental flag in preferences, - updates the code to properly deal with the fact that empty Actions are now always considered slotted/layered Actions (instead of that relying on the user preference). Note that 'slotted Actions' and 'layered Actions' are the exact same thing, just focusing on different aspects (slot & layers) of the new data model. The "Baklava phase 1" assumptions are still asserted. This means that: - an Action can have zero or one layer, - that layer can have zero or one strip, - that strip must be of type 'keyframe' and be infinite with zero offset. The code to handle legacy Actions is NOT removed in this commit. It will be removed later. For now it's likely better to keep it around as reference to the old behaviour in order to aid in some inevitable bugfixing. Ref: #120406
2024-10-15 16:29:53 +02:00
if (id->flag & ID_FLAG_EMBEDDED_DATA) {
/* When the ID is embedded, use the name of the owner ID for clarity. */
ID *owner_id = BKE_id_owner_get(id);
/* If the ID is embedded it should have an owner. */
BLI_assert(owner_id != nullptr);
SNPRINTF(actname, DATA_("%sAction"), owner_id->name + 2);
}
Anim: Remove 'Slotted Actions' experimental flag This commit takes the 'Slotted Actions' out of the experimental phase. As a result: - All newly created Actions will be slotted Actions. - Legacy Actions loaded from disk will be versioned to slotted Actions. - The new Python API for slots, layers, strips, and channel bags is available. - The legacy Python API for accessing F-Curves and Action Groups is still available, and will operate on the F-Curves/Groups for the first slot only. - Creating an Action by keying (via the UI, operators, or the `rna_struct.keyframe_insert` function) will try and share Actions between related data-blocks. See !126655 for more info about this. - Assigning an Action to a data-block will auto-assign a suitable Action Slot. The logic for this is described below. However, There are cases where this does _not_ automatically assign a slot, and thus the Action will effectively _not_ animate the data-block. Effort has been spent to make Action selection work both reliably for Blender users as well as keep the behaviour the same for Python scripts. Where these two goals did not converge, reliability and understandability for users was prioritised. Auto-selection of the Action Slot upon assigning the Action works as follows. The first rule to find a slot wins. 1. The data-block remembers the slot name that was last assigned. If the newly assigned Action has a slot with that name, it is chosen. 2. If the Action has a slot with the same name as the data-block, it is chosen. 3. If the Action has only one slot, and it has never been assigned to anything, it is chosen. 4. If the Action is assigned to an NLA strip or an Action constraint, and the Action has a single slot, and that slot has a suitable ID type, it is chosen. This last step is what I was referring to with "Where these two goals did not converge, reliability and understandability for users was prioritised." For regular Action assignments (like via the Action selectors in the Properties editor) this rule doesn't apply, even though with legacy Actions the final state ("it is animated by this Action") differs from the final state with slotted Actions ("it has no slot so is not animated"). This is done to support the following workflow: - Create an Action by animating Cube. - In order to animate Suzanne with that same Action, assign the Action to Suzanne. - Start keying Suzanne. This auto-creates and auto-assigns a new slot for Suzanne. If rule 4. above would apply in this case, the 2nd step would automatically select the Cube slot for Suzanne as well, which would immediately overwrite Suzanne's properties with the Cube animation. Technically, this commit: - removes the `WITH_ANIM_BAKLAVA` build flag, - removes the `use_animation_baklava` experimental flag in preferences, - updates the code to properly deal with the fact that empty Actions are now always considered slotted/layered Actions (instead of that relying on the user preference). Note that 'slotted Actions' and 'layered Actions' are the exact same thing, just focusing on different aspects (slot & layers) of the new data model. The "Baklava phase 1" assumptions are still asserted. This means that: - an Action can have zero or one layer, - that layer can have zero or one strip, - that strip must be of type 'keyframe' and be infinite with zero offset. The code to handle legacy Actions is NOT removed in this commit. It will be removed later. For now it's likely better to keep it around as reference to the old behaviour in order to aid in some inevitable bugfixing. Ref: #120406
2024-10-15 16:29:53 +02:00
else if (GS(id->name) == ID_KE) {
Key *key = reinterpret_cast<Key *>(id);
SNPRINTF(actname, DATA_("%sAction"), key->from->name + 2);
}
else {
SNPRINTF(actname, DATA_("%sAction"), id->name + 2);
}
/* create action */
action = BKE_action_add(bmain, actname);
/* Decrement the default-1 user count, as assigning it will increase it again. */
BLI_assert(action->id.us == 1);
id_us_min(&action->id);
}
/* Assigning the Action should always work here. The only reason it wouldn't, is when a legacy
* Action of the wrong ID type is assigned, but since in this branch of the code we're only
* dealing with either new or layered Actions, this will never fail. */
const bool ok = animrig::assign_action(action, {*id, *adt});
BLI_assert_msg(ok, "Expecting Action assignment to work here");
UNUSED_VARS_NDEBUG(ok);
/* Tag depsgraph to be rebuilt to include time dependency. */
DEG_relations_tag_update(bmain);
}
DEG_id_tag_update(&adt->action->id, ID_RECALC_ANIMATION_NO_FLUSH);
/* return the action */
return adt->action;
}
void animdata_fcurve_delete(AnimData *adt, FCurve *fcu)
{
/* - If no AnimData, we've got nowhere to remove the F-Curve from
* (this doesn't guarantee that the F-Curve is in there, but at least we tried
* - If no F-Curve, there is nothing to remove
*/
if (ELEM(nullptr, adt, fcu)) {
return;
}
const bool is_driver = fcu->driver != nullptr;
if (is_driver) {
BLI_remlink(&adt->drivers, fcu);
}
else if (adt->action) {
Anim: merge Animation data-block into bAction The new/experimental, layered `Animation` data-block is merged with the existing `bAction` data-block. The `Animation` data-block is considerably newer than `bAction`, so the supporting code that was written for it is also more modern. When moving that code into `bAction`, I chose to keep the modernity where possible, and thus some of the old code has been updated as well. Things like preferring references over pointers. The `Animation` data-block is now gone from DNA, the main database, etc. As this was still an experimental feature, there is no versioning code to convert any of that to Actions. The DNA struct `bAction` now has a C++ wrapper `animrig::Action`, that can be obtained via `some_action->wrap()`. `animrig::Action` has functions `is_empty()`, `is_action_legacy()`, and `is_action_layered()`. They **all** return `true` when the Action is empty, as in that case none of the data that makes an action either 'legacy' or 'layered' is there. The 'animation filtering' code (for showing things in the dope sheet, graph editor, etc) that I wrote for `Animation` is intentionally kept around. These types now target 'layered actions' and the already-existing ones 'legacy actions'. A future PR may merge these two together, but given how much work it was to add something new there, I'd rather wait until the dust has settled on this commit. There are plenty of variables (and some comments) named `anim` or `animation` that now are of type `animrig::Action`. I haven't renamed them all, to keep the noise level low in this commit (it's already big enough). This can be done in a followup, non-functional PR. Related task: #121355 Pull Request: https://projects.blender.org/blender/blender/pulls/121357
2024-05-13 15:51:26 +02:00
Action &action = adt->action->wrap();
if (action.is_action_legacy()) {
/* Remove from group or action, whichever one "owns" the F-Curve. */
if (fcu->grp) {
bActionGroup *agrp = fcu->grp;
/* Remove F-Curve from group+action. */
action_groups_remove_channel(&action, fcu);
/* If group has no more channels, remove it too,
* otherwise can have many dangling groups #33541.
*/
if (BLI_listbase_is_empty(&agrp->channels)) {
BLI_freelinkN(&action.groups, agrp);
}
}
else {
BLI_remlink(&action.curves, fcu);
}
Anim: merge Animation data-block into bAction The new/experimental, layered `Animation` data-block is merged with the existing `bAction` data-block. The `Animation` data-block is considerably newer than `bAction`, so the supporting code that was written for it is also more modern. When moving that code into `bAction`, I chose to keep the modernity where possible, and thus some of the old code has been updated as well. Things like preferring references over pointers. The `Animation` data-block is now gone from DNA, the main database, etc. As this was still an experimental feature, there is no versioning code to convert any of that to Actions. The DNA struct `bAction` now has a C++ wrapper `animrig::Action`, that can be obtained via `some_action->wrap()`. `animrig::Action` has functions `is_empty()`, `is_action_legacy()`, and `is_action_layered()`. They **all** return `true` when the Action is empty, as in that case none of the data that makes an action either 'legacy' or 'layered' is there. The 'animation filtering' code (for showing things in the dope sheet, graph editor, etc) that I wrote for `Animation` is intentionally kept around. These types now target 'layered actions' and the already-existing ones 'legacy actions'. A future PR may merge these two together, but given how much work it was to add something new there, I'd rather wait until the dust has settled on this commit. There are plenty of variables (and some comments) named `anim` or `animation` that now are of type `animrig::Action`. I haven't renamed them all, to keep the noise level low in this commit (it's already big enough). This can be done in a followup, non-functional PR. Related task: #121355 Pull Request: https://projects.blender.org/blender/blender/pulls/121357
2024-05-13 15:51:26 +02:00
/* If action has no more F-Curves as a result of this, unlink it from
* AnimData if it did not come from a NLA Strip being tweaked.
*
* This is done so that we don't have dangling Object+Action entries in
* channel list that are empty, and linger around long after the data they
* are for has disappeared (and probably won't come back).
*/
Anim: merge Animation data-block into bAction The new/experimental, layered `Animation` data-block is merged with the existing `bAction` data-block. The `Animation` data-block is considerably newer than `bAction`, so the supporting code that was written for it is also more modern. When moving that code into `bAction`, I chose to keep the modernity where possible, and thus some of the old code has been updated as well. Things like preferring references over pointers. The `Animation` data-block is now gone from DNA, the main database, etc. As this was still an experimental feature, there is no versioning code to convert any of that to Actions. The DNA struct `bAction` now has a C++ wrapper `animrig::Action`, that can be obtained via `some_action->wrap()`. `animrig::Action` has functions `is_empty()`, `is_action_legacy()`, and `is_action_layered()`. They **all** return `true` when the Action is empty, as in that case none of the data that makes an action either 'legacy' or 'layered' is there. The 'animation filtering' code (for showing things in the dope sheet, graph editor, etc) that I wrote for `Animation` is intentionally kept around. These types now target 'layered actions' and the already-existing ones 'legacy actions'. A future PR may merge these two together, but given how much work it was to add something new there, I'd rather wait until the dust has settled on this commit. There are plenty of variables (and some comments) named `anim` or `animation` that now are of type `animrig::Action`. I haven't renamed them all, to keep the noise level low in this commit (it's already big enough). This can be done in a followup, non-functional PR. Related task: #121355 Pull Request: https://projects.blender.org/blender/blender/pulls/121357
2024-05-13 15:51:26 +02:00
animdata_remove_empty_action(adt);
}
else {
action_fcurve_remove(action, *fcu);
/* Return early to avoid the call to BKE_fcurve_free because the fcu has already been freed
* by action_fcurve_remove. */
Anim: merge Animation data-block into bAction The new/experimental, layered `Animation` data-block is merged with the existing `bAction` data-block. The `Animation` data-block is considerably newer than `bAction`, so the supporting code that was written for it is also more modern. When moving that code into `bAction`, I chose to keep the modernity where possible, and thus some of the old code has been updated as well. Things like preferring references over pointers. The `Animation` data-block is now gone from DNA, the main database, etc. As this was still an experimental feature, there is no versioning code to convert any of that to Actions. The DNA struct `bAction` now has a C++ wrapper `animrig::Action`, that can be obtained via `some_action->wrap()`. `animrig::Action` has functions `is_empty()`, `is_action_legacy()`, and `is_action_layered()`. They **all** return `true` when the Action is empty, as in that case none of the data that makes an action either 'legacy' or 'layered' is there. The 'animation filtering' code (for showing things in the dope sheet, graph editor, etc) that I wrote for `Animation` is intentionally kept around. These types now target 'layered actions' and the already-existing ones 'legacy actions'. A future PR may merge these two together, but given how much work it was to add something new there, I'd rather wait until the dust has settled on this commit. There are plenty of variables (and some comments) named `anim` or `animation` that now are of type `animrig::Action`. I haven't renamed them all, to keep the noise level low in this commit (it's already big enough). This can be done in a followup, non-functional PR. Related task: #121355 Pull Request: https://projects.blender.org/blender/blender/pulls/121357
2024-05-13 15:51:26 +02:00
return;
}
}
else {
BLI_assert_unreachable();
}
BKE_fcurve_free(fcu);
}
bool animdata_remove_empty_action(AnimData *adt)
{
if (adt->action != nullptr) {
bAction *act = adt->action;
DEG_id_tag_update(&act->id, ID_RECALC_ANIMATION_NO_FLUSH);
if (BLI_listbase_is_empty(&act->curves) && (adt->flag & ADT_NLA_EDIT_ON) == 0) {
id_us_min(&act->id);
adt->action = nullptr;
return true;
}
}
return false;
}
/** \} */
const FCurve *fcurve_find_by_rna_path(const AnimData &adt,
const StringRefNull rna_path,
const int array_index)
{
BLI_assert(adt.action);
if (!adt.action) {
return nullptr;
}
const Action &action = adt.action->wrap();
BLI_assert(action.is_action_layered());
const Slot *slot = action.slot_for_handle(adt.slot_handle);
if (!slot) {
/* No need to inspect anything if this ID does not have an Action Slot. */
return nullptr;
}
/* No check for the slot's ID type. Not only do we not have the actual ID
* to do this check, but also, since the Action and the slot have been
* assigned, just trust that it's valid. */
/* Iterate the layers top-down, as higher-up animation overrides (or at least can override)
* lower-down animation. */
for (int layer_idx = action.layer_array_num - 1; layer_idx >= 0; layer_idx--) {
const Layer *layer = action.layer(layer_idx);
/* TODO: refactor this into something nicer once we have different strip types. */
for (const Strip *strip : layer->strips()) {
switch (strip->type()) {
case Strip::Type::Keyframe: {
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 StripKeyframeData &strip_data = strip->data<StripKeyframeData>(action);
const Channelbag *channelbag_for_slot = strip_data.channelbag_for_slot(*slot);
if (!channelbag_for_slot) {
continue;
}
const FCurve *fcu = channelbag_for_slot->fcurve_find({rna_path, array_index});
if (!fcu) {
continue;
}
/* This code assumes that there is only one strip, and that it's infinite. When that
* changes, this code needs to be expanded to check for strip boundaries. */
return fcu;
}
}
/* Explicit lack of 'default' clause, to get compiler warnings when strip types are added. */
}
}
return nullptr;
}
2023-11-09 09:34:49 +11:00
} // namespace blender::animrig