Files
test/source/blender/blenkernel/intern/pose_backup.cc
Bastien Montagne 7aced80eec Cleanup: blenkernel: Replace 'void' MEM_[cm]allocN with templated, type-safe MEM_[cm]allocN<T>.
The main issue of 'type-less' standard C allocations is that there is no check on
allocated type possible.

This is a serious source of annoyance (and crashes) when making some
low-level structs non-trivial, as tracking down all usages of these
structs in higher-level other structs and their allocation is... really
painful.

MEM_[cm]allocN<T> templates on the other hand do check that the
given type is trivial, at build time (static assert), which makes such issue...
trivial to catch.

NOTE: New code should strive to use MEM_new (i.e. allocation and
construction) as much as possible, even for trivial PoD types.

Pull Request: https://projects.blender.org/blender/blender/pulls/136134
2025-03-20 11:25:19 +01:00

212 lines
6.2 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* 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.hh"
#include "BKE_armature.hh"
#include "BKE_idprop.hh"
#include "BKE_object_types.hh"
#include "ANIM_action.hh"
#include "ANIM_pose.hh"
using namespace blender::bke;
/* simple struct for storing backup info for one pose channel */
struct PoseChannelBackup {
PoseChannelBackup *next, *prev;
bPoseChannel *pchan; /* Pose channel this backup is for. */
bPoseChannel olddata; /* Backup of pose channel. */
IDProperty *oldprops; /* Backup copy (needs freeing) of pose channel's ID properties. */
const Object *owner; /* The object to which this pose channel belongs. */
};
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 void pose_backup_create(const Object *ob,
bAction *action,
const BoneNameSet &selected_bone_names,
PoseBackup &pose_backup)
{
BoneNameSet backed_up_bone_names;
const bool is_bone_selection_relevant = pose_backup.is_bone_selection_relevant;
/* Make a backup of the given pose channel. */
auto store_animated_pchans = [&](const 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 = MEM_callocN<PoseChannelBackup>("PoseChannelBackup");
chan_bak->pchan = pchan;
chan_bak->olddata = blender::dna::shallow_copy(*chan_bak->pchan);
chan_bak->owner = ob;
if (pchan->prop) {
chan_bak->oldprops = IDP_CopyProperty(pchan->prop);
}
BLI_addtail(&pose_backup.backups, chan_bak);
backed_up_bone_names.add_new(bone_name);
};
blender::animrig::Slot &slot = blender::animrig::get_best_pose_slot_for_id(ob->id,
action->wrap());
/* Call `store_animated_pchans()` for each FCurve that targets a bone. */
BKE_action_find_fcurves_with_bones(action, slot.handle, store_animated_pchans);
}
static blender::Set<bPoseChannel *> armature_find_selected_pose_bones(
blender::Span<Object *> objects)
{
blender::Set<bPoseChannel *> selected_bones;
bool all_bones_selected = true;
for (Object *obj : objects) {
/* Iterate over the selected bones to fill the set of bone names. */
LISTBASE_FOREACH (bPoseChannel *, pose_bone, &obj->pose->chanbase) {
if (pose_bone->bone->flag & BONE_SELECTED) {
selected_bones.add(pose_bone);
}
else {
all_bones_selected = false;
}
}
}
/* If all bones are selected, act as if none are. */
if (all_bones_selected) {
return {};
}
return selected_bones;
}
PoseBackup *BKE_pose_backup_create_all_bones(blender::Span<Object *> objects,
const bAction *action)
{
PoseBackup *pose_backup = MEM_callocN<PoseBackup>(__func__);
pose_backup->backups = {nullptr, nullptr};
pose_backup->is_bone_selection_relevant = false;
for (Object *ob : objects) {
pose_backup_create(ob, const_cast<bAction *>(action), BoneNameSet(), *pose_backup);
}
return pose_backup;
}
PoseBackup *BKE_pose_backup_create_selected_bones(blender::Span<Object *> objects,
const bAction *action)
{
PoseBackup *pose_backup = MEM_callocN<PoseBackup>(__func__);
pose_backup->backups = {nullptr, nullptr};
blender::Set<bPoseChannel *> selected_bones = armature_find_selected_pose_bones(objects);
pose_backup->is_bone_selection_relevant = !selected_bones.is_empty();
for (Object *ob : objects) {
const bArmature *armature = static_cast<const bArmature *>(ob->data);
const BoneNameSet selected_bone_names = BKE_armature_find_selected_bone_names(armature);
pose_backup_create(ob, const_cast<bAction *>(action), selected_bone_names, *pose_backup);
}
return pose_backup;
}
bool BKE_pose_backup_is_selection_relevant(const 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) {
*chan_bak->pchan = blender::dna::shallow_copy(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)
{
if (!pbd) {
/* Can happen if initialization was aborted. */
return;
}
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(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;
}