This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/pose_backup.cc
Sybren A. Stüvel 94155fb6ff Anim: expose pose blending & backup system to RNA
Expose `BKE_pose_apply_action_blend` and a simplified pose backup system
to RNA. This will make it possible to easily create some interactive
tools in Python for pose blending.

When creating a backup via this API, it is stored on the
`Object::runtime` struct. Any backup that was there before is freed
first. This way the Python code doesn't need access to the actual
`PoseBackup *`, simplifying memory management.

The limitation of having only a single backup shouldn't be too
problematic, as it is meant for things like interactive manipulation of
the current pose. Typical use looks like:

- Interactive operator starts, and creates a backup of the current pose.
- While the operator is running:
    - The pose backup is restored, so that the next steps always use the
      same reference pose.
    - Depending on user input, determine a blend factor.
    - Blend some pose from the pose library into the current pose.
- On confirmation, leave the pose as-is.
- On cancellation, restore the backup.
- Free the backup.

`BKE_pose_apply_action_blend` is exposed to RNA to make the above
possible.

An alternative approach would be to rely on the operator redo system.
However, since for poses this would use the global undo, it can get
prohibitively slow. This change is to make it easier to prototype
things; further into the future the undo system for poses should be
improved, but that's an entire project on its own.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D16900
2023-01-02 16:39:51 +01:00

164 lines
4.7 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BKE_pose_backup.h"
#include <cstring>
#include "BLI_listbase.h"
#include "MEM_guardedalloc.h"
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "BKE_action.h"
#include "BKE_action.hh"
#include "BKE_armature.hh"
#include "BKE_idprop.h"
using namespace blender::bke;
/* simple struct for storing backup info for one pose channel */
struct PoseChannelBackup {
struct PoseChannelBackup *next, *prev;
struct bPoseChannel *pchan; /* Pose channel this backup is for. */
struct bPoseChannel olddata; /* Backup of pose channel. */
struct IDProperty *oldprops; /* Backup copy (needs freeing) of pose channel's ID properties. */
};
struct PoseBackup {
bool is_bone_selection_relevant;
ListBase /* PoseChannelBackup* */ backups;
};
/**
* Create a backup of the pose, for only those bones that are animated in the
* given Action. If `selected_bone_names` is not empty, the set of bones to back
* up is intersected with these bone names such that only the selected subset is
* backed up.
*
* The returned pointer is owned by the caller.
*/
static PoseBackup *pose_backup_create(const Object *ob,
const bAction *action,
const BoneNameSet &selected_bone_names)
{
ListBase backups = {nullptr, nullptr};
const bool is_bone_selection_relevant = !selected_bone_names.is_empty();
BoneNameSet backed_up_bone_names;
/* Make a backup of the given pose channel. */
auto store_animated_pchans = [&](FCurve * /* unused */, const char *bone_name) {
if (backed_up_bone_names.contains(bone_name)) {
/* Only backup each bone once. */
return;
}
bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
if (pchan == nullptr) {
/* FCurve targets non-existent bone. */
return;
}
if (is_bone_selection_relevant && !selected_bone_names.contains(bone_name)) {
return;
}
PoseChannelBackup *chan_bak = static_cast<PoseChannelBackup *>(
MEM_callocN(sizeof(*chan_bak), "PoseChannelBackup"));
chan_bak->pchan = pchan;
memcpy(&chan_bak->olddata, chan_bak->pchan, sizeof(chan_bak->olddata));
if (pchan->prop) {
chan_bak->oldprops = IDP_CopyProperty(pchan->prop);
}
BLI_addtail(&backups, chan_bak);
backed_up_bone_names.add_new(bone_name);
};
/* Call `store_animated_pchans()` for each FCurve that targets a bone. */
BKE_action_find_fcurves_with_bones(action, store_animated_pchans);
/* PoseBackup is constructed late, so that the above loop can use stack variables. */
PoseBackup *pose_backup = static_cast<PoseBackup *>(MEM_callocN(sizeof(*pose_backup), __func__));
pose_backup->is_bone_selection_relevant = is_bone_selection_relevant;
pose_backup->backups = backups;
return pose_backup;
}
PoseBackup *BKE_pose_backup_create_all_bones(const Object *ob, const bAction *action)
{
return pose_backup_create(ob, action, BoneNameSet());
}
PoseBackup *BKE_pose_backup_create_selected_bones(const Object *ob, const bAction *action)
{
const bArmature *armature = static_cast<const bArmature *>(ob->data);
const BoneNameSet selected_bone_names = BKE_armature_find_selected_bone_names(armature);
return pose_backup_create(ob, action, selected_bone_names);
}
bool BKE_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup)
{
return pose_backup->is_bone_selection_relevant;
}
void BKE_pose_backup_restore(const PoseBackup *pbd)
{
LISTBASE_FOREACH (PoseChannelBackup *, chan_bak, &pbd->backups) {
memcpy(chan_bak->pchan, &chan_bak->olddata, sizeof(chan_bak->olddata));
if (chan_bak->oldprops) {
IDP_SyncGroupValues(chan_bak->pchan->prop, chan_bak->oldprops);
}
/* TODO: constraints settings aren't restored yet,
* even though these could change (though not that likely) */
}
}
void BKE_pose_backup_free(PoseBackup *pbd)
{
LISTBASE_FOREACH_MUTABLE (PoseChannelBackup *, chan_bak, &pbd->backups) {
if (chan_bak->oldprops) {
IDP_FreeProperty(chan_bak->oldprops);
}
BLI_freelinkN(&pbd->backups, chan_bak);
}
MEM_freeN(pbd);
}
void BKE_pose_backup_create_on_object(Object *ob, const bAction *action)
{
BKE_pose_backup_clear(ob);
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(ob, action);
ob->runtime.pose_backup = pose_backup;
}
bool BKE_pose_backup_restore_on_object(struct Object *ob)
{
if (ob->runtime.pose_backup == nullptr) {
return false;
}
BKE_pose_backup_restore(ob->runtime.pose_backup);
return true;
}
void BKE_pose_backup_clear(Object *ob)
{
if (ob->runtime.pose_backup == nullptr) {
return;
}
BKE_pose_backup_free(ob->runtime.pose_backup);
ob->runtime.pose_backup = nullptr;
}