Files
test2/source/blender/animrig/intern/animdata.cc
Christoph Lendenfeld 1dd06e6623 Fix #143818: Clear Keyframes operator unassigns action
The `ANIM_OT_keyframe_clear_v3d` had a few issues

The reason for the action disappearing was the call to `animdata_remove_empty_action`.
This checked the curves on the action directly, thus failed with the layered actions.

However with the change to the layered action, the convention is that actions are not
unassigned when it is empty. This is why I also added a check for legacy actions before the call.
I am doing this even though legacy actions will be removed in 5.0 because this
needs to be backported.

The third issue was that `changed` was never set to true with layered actions,
which meant the notifier was never sent to update the UI.

Pull Request: https://projects.blender.org/blender/blender/pulls/144001
2025-08-07 10:41:46 +02:00

382 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#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"
#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_utf8.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: {
/* Shape-keys. */
Key *key = reinterpret_cast<Key *>(related_id);
/* 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) {
/* 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) {
bAction *action = find_related_action(*bmain, *id);
if (action == nullptr) {
/* init action name from name of ID block */
char actname[sizeof(id->name) - 2];
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_UTF8(actname, DATA_("%sAction"), owner_id->name + 2);
}
else if (GS(id->name) == ID_KE) {
Key *key = reinterpret_cast<Key *>(id);
SNPRINTF_UTF8(actname, DATA_("%sAction"), key->from->name + 2);
}
else {
SNPRINTF_UTF8(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) {
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);
}
/* 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).
*/
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. */
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);
Action &action = act->wrap();
if (action.is_empty() && (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: {
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;
}
} // namespace blender::animrig