The versioning code that upgrades legacy actions to new slotted actions also needs to properly assign slots to the IDs that use those upgraded actions. It was doing this correctly except for not traversing into and assigning slots to embedded IDs. This commit adds the code to handle embedded IDs as well. Additionally, this changes how mismatched `id_type`s are handled when upgrading actions. Rather than refusing to assign the slot created during the upgrade if the `id_type` doesn't match the ID, we assign it anyway with a warning. The rationale is that this represents a case where the Action `idroot` was already mismatched, and it turns out that has always been possible. So we now opt to simply preserve that state of affairs rather than attempt to "fix" it. Pull Request: https://projects.blender.org/blender/blender/pulls/129002
172 lines
4.9 KiB
C++
172 lines
4.9 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup animrig
|
|
*/
|
|
|
|
#include "ANIM_action.hh"
|
|
#include "ANIM_action_iterators.hh"
|
|
|
|
#include "BLI_assert.h"
|
|
|
|
#include "BKE_anim_data.hh"
|
|
#include "BKE_nla.hh"
|
|
|
|
#include "DNA_constraint_types.h"
|
|
|
|
namespace blender::animrig {
|
|
|
|
void foreach_fcurve_in_action(Action &action, FunctionRef<void(FCurve &fcurve)> callback)
|
|
{
|
|
if (action.is_action_legacy()) {
|
|
LISTBASE_FOREACH (FCurve *, fcurve, &action.curves) {
|
|
callback(*fcurve);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (Layer *layer : action.layers()) {
|
|
for (Strip *strip : layer->strips()) {
|
|
if (strip->type() != Strip::Type::Keyframe) {
|
|
continue;
|
|
}
|
|
for (ChannelBag *bag : strip->data<StripKeyframeData>(action).channelbags()) {
|
|
for (FCurve *fcu : bag->fcurves()) {
|
|
callback(*fcu);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void foreach_fcurve_in_action_slot(Action &action,
|
|
slot_handle_t handle,
|
|
FunctionRef<void(FCurve &fcurve)> callback)
|
|
{
|
|
if (action.is_action_legacy()) {
|
|
LISTBASE_FOREACH (FCurve *, fcurve, &action.curves) {
|
|
callback(*fcurve);
|
|
}
|
|
}
|
|
else if (action.is_action_layered()) {
|
|
for (Layer *layer : action.layers()) {
|
|
for (Strip *strip : layer->strips()) {
|
|
if (strip->type() != Strip::Type::Keyframe) {
|
|
continue;
|
|
}
|
|
for (ChannelBag *bag : strip->data<StripKeyframeData>(action).channelbags()) {
|
|
if (bag->slot_handle != handle) {
|
|
continue;
|
|
}
|
|
for (FCurve *fcu : bag->fcurves()) {
|
|
BLI_assert(fcu != nullptr);
|
|
callback(*fcu);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool foreach_action_slot_use(
|
|
const ID &animated_id,
|
|
FunctionRef<bool(const Action &action, slot_handle_t slot_handle)> callback)
|
|
{
|
|
|
|
const auto forward_to_callback = [&](ID & /* animated_id */,
|
|
bAction *&action_ptr_ref,
|
|
const slot_handle_t &slot_handle_ref,
|
|
char * /*slot_name*/) -> bool {
|
|
if (!action_ptr_ref) {
|
|
return true;
|
|
}
|
|
return callback(const_cast<const Action &>(action_ptr_ref->wrap()), slot_handle_ref);
|
|
};
|
|
|
|
return foreach_action_slot_use_with_references(const_cast<ID &>(animated_id),
|
|
forward_to_callback);
|
|
}
|
|
|
|
bool foreach_action_slot_use_with_references(ID &animated_id,
|
|
FunctionRef<bool(ID &animated_id,
|
|
bAction *&action_ptr_ref,
|
|
slot_handle_t &slot_handle_ref,
|
|
char *slot_name)> callback)
|
|
{
|
|
AnimData *adt = BKE_animdata_from_id(&animated_id);
|
|
|
|
if (adt) {
|
|
if (adt->action) {
|
|
/* Direct assignment. */
|
|
if (!callback(animated_id, adt->action, adt->slot_handle, adt->slot_name)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* NLA strips. */
|
|
const bool looped_until_last_strip = bke::nla::foreach_strip_adt(*adt, [&](NlaStrip *strip) {
|
|
if (strip->act) {
|
|
if (!callback(animated_id, strip->act, strip->action_slot_handle, strip->action_slot_name))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
if (!looped_until_last_strip) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* The rest of the code deals with constraints, so only relevant when this is an Object. */
|
|
if (GS(animated_id.name) != ID_OB) {
|
|
return true;
|
|
}
|
|
|
|
const Object &object = reinterpret_cast<const Object &>(animated_id);
|
|
|
|
/**
|
|
* Visit a constraint, and call the callback if it's an Action constraint.
|
|
*
|
|
* \returns whether to continue looping over possible uses of Actions, i.e.
|
|
* the return value of the callback.
|
|
*/
|
|
auto visit_constraint = [&](const bConstraint &constraint) -> bool {
|
|
if (constraint.type != CONSTRAINT_TYPE_ACTION) {
|
|
return true;
|
|
}
|
|
bActionConstraint *constraint_data = static_cast<bActionConstraint *>(constraint.data);
|
|
if (!constraint_data->act) {
|
|
return true;
|
|
}
|
|
return callback(animated_id,
|
|
constraint_data->act,
|
|
constraint_data->action_slot_handle,
|
|
constraint_data->action_slot_name);
|
|
};
|
|
|
|
/* Visit Object constraints. */
|
|
LISTBASE_FOREACH (bConstraint *, con, &object.constraints) {
|
|
if (!visit_constraint(*con)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Visit Pose Bone constraints. */
|
|
if (object.type == OB_ARMATURE) {
|
|
LISTBASE_FOREACH (bPoseChannel *, pchan, &object.pose->chanbase) {
|
|
LISTBASE_FOREACH (bConstraint *, con, &pchan->constraints) {
|
|
if (!visit_constraint(*con)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace blender::animrig
|