Anim: Make pose library code aware of slots

This patch makes the internal functions for the pose library aware of action slots.
* Allows to apply poses with more than 1 slot.
* The slot is chosen based on a best guess, with a fallback to the first slot.

Not in this patch:

There is no straightforward way to create multi slot pose assets yet. That will come later.
It is possible to manually tag an action with more than 1 slot as an asset though.

When applying poses, only the active object is modified. Multi object editing support will come later.

Part of Design #131840

Pull Request: https://projects.blender.org/blender/blender/pulls/132161
This commit is contained in:
Christoph Lendenfeld
2025-01-02 15:27:06 +01:00
committed by Christoph Lendenfeld
parent ddbb3d673c
commit c3e5d15c2e
12 changed files with 360 additions and 43 deletions

View File

@@ -39,4 +39,12 @@ void pose_apply_action_blend(Object *ob,
slot_handle_t slot_handle,
const AnimationEvalContext *anim_eval_context,
float blend_factor);
/**
* Get the best slot to read pose data from for the given ID.
* Will always return a Slot as it falls back to the first Slot.
*
* Assumes that the Action has at least one Slot.
*/
Slot &get_best_pose_slot_for_id(const ID &id, Action &pose_data);
} // namespace blender::animrig

View File

@@ -81,6 +81,7 @@ if(WITH_GTESTS)
intern/evaluation_test.cc
intern/keyframing_test.cc
intern/nla_test.cc
intern/pose_test.cc
)
set(TEST_LIB
PRIVATE bf::animrig

View File

@@ -32,8 +32,10 @@ void pose_apply_restore_fcurves(bAction *action)
}
}
void pose_apply_disable_fcurves_for_unselected_bones(
bAction *action, const blender::bke::BoneNameSet &selected_bone_names)
static void pose_apply_disable_fcurves_for_unselected_bones(
bAction *action,
const slot_handle_t slot_handle,
const blender::bke::BoneNameSet &selected_bone_names)
{
auto disable_unselected_fcurve = [&](FCurve *fcu, const char *bone_name) {
const bool is_bone_selected = selected_bone_names.contains(bone_name);
@@ -41,7 +43,7 @@ void pose_apply_disable_fcurves_for_unselected_bones(
fcu->flag |= FCURVE_DISABLED;
}
};
blender::bke::BKE_action_find_fcurves_with_bones(action, disable_unselected_fcurve);
blender::bke::BKE_action_find_fcurves_with_bones(action, slot_handle, disable_unselected_fcurve);
}
void pose_apply(Object *ob,
@@ -55,6 +57,10 @@ void pose_apply(Object *ob,
return;
}
if (action->wrap().slot_array_num == 0) {
return;
}
const bArmature *armature = (bArmature *)ob->data;
const blender::bke::BoneNameSet selected_bone_names =
blender::bke::BKE_armature_find_selected_bone_names(armature);
@@ -63,7 +69,7 @@ void pose_apply(Object *ob,
if (limit_to_selected_bones) {
/* Mute all FCurves that are not associated with selected bones. This separates the concept of
* bone selection from the FCurve evaluation code. */
pose_apply_disable_fcurves_for_unselected_bones(action, selected_bone_names);
pose_apply_disable_fcurves_for_unselected_bones(action, slot_handle, selected_bone_names);
}
/* Apply the Action. */
@@ -118,4 +124,17 @@ void pose_apply_action_blend(Object *ob,
pose_apply(ob, action, slot_handle, anim_eval_context, evaluate_and_blend);
}
Slot &get_best_pose_slot_for_id(const ID &id, Action &pose_data)
{
BLI_assert_msg(pose_data.slot_array_num > 0,
"Actions without slots have no data. This should have been caught earlier.");
Slot *slot = generic_slot_for_autoassign(id, pose_data, "");
if (slot == nullptr) {
slot = pose_data.slot(0);
}
return *slot;
}
} // namespace blender::animrig

View File

@@ -0,0 +1,257 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_string.h"
#include "BKE_action.hh"
#include "BKE_anim_data.hh"
#include "BKE_animsys.h"
#include "BKE_armature.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "DEG_depsgraph.hh"
#include "ANIM_action.hh"
#include "ANIM_pose.hh"
#include "CLG_log.h"
#include "testing/testing.h"
constexpr char msg_unexpected_modification[] =
"Properties not stored in the pose are expected to not be modified.";
namespace blender::animrig::tests {
class PoseTest : public testing::Test {
public:
Main *bmain;
Action *pose_action;
Object *obj_empty;
Object *obj_armature_a;
Object *obj_armature_b;
StripKeyframeData *keyframe_data;
const blender::animrig::KeyframeSettings key_settings = {
BEZT_KEYTYPE_KEYFRAME, HD_AUTO, BEZT_IPO_BEZ};
static void SetUpTestSuite()
{
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
CLG_init();
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
pose_action = static_cast<Action *>(BKE_id_new(bmain, ID_AC, "pose_data"));
Layer &layer = pose_action->layer_add("first_layer");
Strip &strip = layer.strip_add(*pose_action, Strip::Type::Keyframe);
keyframe_data = &strip.data<StripKeyframeData>(*pose_action);
obj_empty = BKE_object_add_only_object(bmain, OB_EMPTY, "obj_empty");
obj_armature_a = BKE_object_add_only_object(bmain, OB_ARMATURE, "obj_armature_a");
obj_armature_b = BKE_object_add_only_object(bmain, OB_ARMATURE, "obj_armature_b");
bArmature *armature = BKE_armature_add(bmain, "ArmatureA");
obj_armature_a->data = armature;
Bone *bone = static_cast<Bone *>(MEM_callocN(sizeof(Bone), "BONE"));
STRNCPY(bone->name, "BoneA");
BLI_addtail(&armature->bonebase, bone);
bone = static_cast<Bone *>(MEM_callocN(sizeof(Bone), "BONE"));
STRNCPY(bone->name, "BoneB");
BLI_addtail(&armature->bonebase, bone);
BKE_pose_ensure(bmain, obj_armature_a, armature, false);
armature = BKE_armature_add(bmain, "ArmatureB");
obj_armature_b->data = armature;
bone = static_cast<Bone *>(MEM_callocN(sizeof(Bone), "BONE"));
STRNCPY(bone->name, "BoneA");
BLI_addtail(&armature->bonebase, bone);
bone = static_cast<Bone *>(MEM_callocN(sizeof(Bone), "BONE"));
STRNCPY(bone->name, "BoneB");
BLI_addtail(&armature->bonebase, bone);
BKE_pose_ensure(bmain, obj_armature_b, armature, false);
}
void TearDown() override
{
BKE_main_free(bmain);
}
};
TEST_F(PoseTest, get_best_slot)
{
Slot &first_slot = pose_action->slot_add();
Slot &second_slot = pose_action->slot_add_for_id(obj_empty->id);
EXPECT_EQ(&get_best_pose_slot_for_id(obj_empty->id, *pose_action), &second_slot);
EXPECT_EQ(&get_best_pose_slot_for_id(obj_armature_a->id, *pose_action), &first_slot);
}
TEST_F(PoseTest, apply_action_object)
{
/* Since pose bones live on the object, the code is already set up to handle objects
* transforms, even though the name suggests it only applies to bones. */
Slot &first_slot = pose_action->slot_add();
EXPECT_EQ(obj_empty->loc[0], 0.0f);
keyframe_data->keyframe_insert(bmain, first_slot, {"location", 0}, {1, 10}, key_settings);
AnimationEvalContext eval_context = {nullptr, 1.0f};
blender::animrig::pose_apply_action_all_bones(
obj_empty, pose_action, first_slot.handle, &eval_context);
EXPECT_EQ(obj_empty->loc[0], 10.0f);
}
TEST_F(PoseTest, apply_action_all_bones_single_slot)
{
Slot &first_slot = pose_action->slot_add();
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
bone_a->loc[1] = 1.0;
bone_a->loc[2] = 2.0;
AnimationEvalContext eval_context = {nullptr, 1.0f};
blender::animrig::pose_apply_action_all_bones(
obj_armature_a, pose_action, first_slot.handle, &eval_context);
EXPECT_EQ(bone_a->loc[0], 10.0);
EXPECT_EQ(bone_b->loc[1], 5.0);
EXPECT_EQ(bone_a->loc[1], 1.0) << msg_unexpected_modification;
EXPECT_EQ(bone_a->loc[2], 2.0);
}
TEST_F(PoseTest, apply_action_all_bones_multiple_slots)
{
Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
Slot &slot_b = pose_action->slot_add_for_id(obj_armature_b->id);
keyframe_data->keyframe_insert(
bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
keyframe_data->keyframe_insert(
bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
keyframe_data->keyframe_insert(
bmain, slot_b, {"pose.bones[\"BoneA\"].location", 1}, {1, 10}, key_settings);
keyframe_data->keyframe_insert(
bmain, slot_b, {"pose.bones[\"BoneB\"].location", 1}, {1, 10}, key_settings);
bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
AnimationEvalContext eval_context = {nullptr, 1.0f};
blender::animrig::pose_apply_action_all_bones(
obj_armature_a, pose_action, slot_a.handle, &eval_context);
EXPECT_EQ(arm_a_bone_a->loc[0], 5.0);
EXPECT_EQ(arm_a_bone_a->loc[1], 0.0) << msg_unexpected_modification;
EXPECT_EQ(arm_a_bone_a->loc[2], 0.0) << msg_unexpected_modification;
EXPECT_EQ(arm_a_bone_b->loc[0], 5.0);
EXPECT_EQ(arm_b_bone_a->loc[1], 0.0) << "Other armature should not be affected yet.";
blender::animrig::pose_apply_action_all_bones(
obj_armature_b, pose_action, slot_b.handle, &eval_context);
EXPECT_EQ(arm_b_bone_b->loc[0], 0.0) << msg_unexpected_modification;
EXPECT_EQ(arm_b_bone_b->loc[1], 10.0);
EXPECT_EQ(arm_b_bone_b->loc[2], 0.0) << msg_unexpected_modification;
EXPECT_EQ(arm_a_bone_a->loc[0], 5.0) << "Other armature should not be affected.";
/* Any slot can be applied, even if it hasn't been added for the ID. */
blender::animrig::pose_apply_action_all_bones(
obj_armature_a, pose_action, slot_b.handle, &eval_context);
EXPECT_EQ(arm_b_bone_b->loc[1], arm_b_bone_a->loc[1])
<< "Applying the same pose should result in the same values.";
}
TEST_F(PoseTest, apply_action_selected_bones_single_slot)
{
Slot &first_slot = pose_action->slot_add();
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
bone_a->loc[1] = 1.0;
bone_a->loc[2] = 2.0;
bone_b->loc[1] = 0.0;
/* The algorithm code uses the bone flag instead of the pose bone flag. */
bone_a->bone->flag |= BONE_SELECTED;
bone_b->bone->flag &= ~BONE_SELECTED;
AnimationEvalContext eval_context = {nullptr, 1.0f};
blender::animrig::pose_apply_action_selected_bones(
obj_armature_a, pose_action, first_slot.handle, &eval_context);
EXPECT_EQ(bone_a->loc[0], 10.0);
EXPECT_EQ(bone_b->loc[1], 0.0) << "Unselected bones should not be affected.";
EXPECT_EQ(bone_a->loc[1], 1.0) << msg_unexpected_modification;
EXPECT_EQ(bone_a->loc[2], 2.0) << msg_unexpected_modification;
}
TEST_F(PoseTest, apply_action_blend_single_slot)
{
Slot &first_slot = pose_action->slot_add();
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
keyframe_data->keyframe_insert(
bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
bone_a->loc[0] = 0.0;
bone_b->loc[1] = 0.0;
AnimationEvalContext eval_context = {nullptr, 1.0f};
blender::animrig::pose_apply_action_blend(
obj_armature_a, pose_action, first_slot.handle, &eval_context, 1.0);
EXPECT_NEAR(bone_a->loc[0], 10.0, 0.001);
EXPECT_NEAR(bone_b->loc[1], 5.0, 0.001);
bone_a->loc[0] = 0.0;
bone_b->loc[1] = 0.0;
blender::animrig::pose_apply_action_blend(
obj_armature_a, pose_action, first_slot.handle, &eval_context, 0.5);
EXPECT_NEAR(bone_a->loc[0], 5.0, 0.001);
EXPECT_NEAR(bone_b->loc[1], 2.5, 0.001);
}
} // namespace blender::animrig::tests

View File

@@ -376,14 +376,15 @@ using FoundFCurveCallbackConst =
blender::FunctionRef<void(const FCurve *fcurve, const char *bone_name)>;
/**
* Calls `callback` for every fcurve in `action` that targets any bone.
* Calls `callback` for every fcurve in an action slot that targets any bone.
*
* For layered actions this is currently limited to fcurves in the first slot of
* the action. This is because these functions are intended for use by pose
* library code, which currently (as of writing this) only use the first slot in
* layered actions.
* \param slot_handle only FCurves from the given action slot are visited.
*/
void BKE_action_find_fcurves_with_bones(bAction *action, FoundFCurveCallback callback);
void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallbackConst callback);
void BKE_action_find_fcurves_with_bones(bAction *action,
blender::animrig::slot_handle_t slot_handle,
FoundFCurveCallback callback);
void BKE_action_find_fcurves_with_bones(const bAction *action,
blender::animrig::slot_handle_t slot_handle,
FoundFCurveCallbackConst callback);
}; // namespace blender::bke

View File

@@ -313,8 +313,6 @@ void BKE_animsys_evaluate_all_animation(struct Main *main,
/**
* Evaluate Action (F-Curve Bag).
*
* Note that this is only used for either legacy Actions or for evaluation of the NLA.
*/
void animsys_evaluate_action(struct PointerRNA *ptr,
struct bAction *act,

View File

@@ -14,6 +14,7 @@
#include <stdbool.h>
#include "BKE_action.hh"
#include "BLI_listbase.h"
#ifdef __cplusplus
@@ -28,15 +29,20 @@ struct PoseBackup;
* The backup is owned by the caller, and should be freed with `BKE_pose_backup_free()`.
*/
struct PoseBackup *BKE_pose_backup_create_selected_bones(
const struct Object *ob, const struct bAction *action) ATTR_WARN_UNUSED_RESULT;
const struct Object *ob,
const struct bAction *action,
blender::animrig::slot_handle_t slot_handle) ATTR_WARN_UNUSED_RESULT;
/**
* Create a backup of those bones that are animated in the given action.
*
* The backup is owned by the caller, and should be freed with `BKE_pose_backup_free()`.
*/
struct PoseBackup *BKE_pose_backup_create_all_bones(
const struct Object *ob, const struct bAction *action) ATTR_WARN_UNUSED_RESULT;
struct PoseBackup *BKE_pose_backup_create_all_bones(const struct Object *ob,
const struct bAction *action,
blender::animrig::slot_handle_t slot_handle)
ATTR_WARN_UNUSED_RESULT;
bool BKE_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup);
void BKE_pose_backup_restore(const struct PoseBackup *pbd);
void BKE_pose_backup_free(struct PoseBackup *pbd);
@@ -47,7 +53,9 @@ void BKE_pose_backup_free(struct PoseBackup *pbd);
* The backup is owned by the Object, and there can be only one backup at a time.
* It should be freed with `BKE_pose_backup_clear(ob)`.
*/
void BKE_pose_backup_create_on_object(struct Object *ob, const struct bAction *action);
void BKE_pose_backup_create_on_object(struct Object *ob,
const struct bAction *action,
blender::animrig::slot_handle_t slot_handle);
/**
* Restore the pose backup owned by this OBject.

View File

@@ -21,22 +21,21 @@
namespace blender::bke {
void BKE_action_find_fcurves_with_bones(bAction *action, FoundFCurveCallback callback)
void BKE_action_find_fcurves_with_bones(bAction *action,
const blender::animrig::slot_handle_t slot_handle,
FoundFCurveCallback callback)
{
auto const_callback = [&](const FCurve *fcurve, const char *bone_name) {
callback(const_cast<FCurve *>(fcurve), bone_name);
};
BKE_action_find_fcurves_with_bones(const_cast<const bAction *>(action), const_callback);
BKE_action_find_fcurves_with_bones(
const_cast<const bAction *>(action), slot_handle, const_callback);
}
void BKE_action_find_fcurves_with_bones(const bAction *action, FoundFCurveCallbackConst callback)
void BKE_action_find_fcurves_with_bones(const bAction *action,
const blender::animrig::slot_handle_t slot_handle,
FoundFCurveCallbackConst callback)
{
/* As of writing this comment, this is only ever used in the pose library
* code, where actions are only supposed to have one slot. If either it
* starts getting used elsewhere or the pose library starts using multiple
* slots, this will need to be updated. */
const animrig::slot_handle_t slot_handle = animrig::first_slot_handle(*action);
for (const FCurve *fcu : animrig::legacy::fcurves_for_action_slot(action, slot_handle)) {
char bone_name[MAXBONENAME];
if (!BLI_str_quoted_substr(fcu->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {

View File

@@ -50,6 +50,7 @@ struct PoseBackup {
*/
static PoseBackup *pose_backup_create(const Object *ob,
const bAction *action,
const blender::animrig::slot_handle_t slot_handle,
const BoneNameSet &selected_bone_names)
{
ListBase backups = {nullptr, nullptr};
@@ -87,7 +88,7 @@ static PoseBackup *pose_backup_create(const Object *ob,
};
/* Call `store_animated_pchans()` for each FCurve that targets a bone. */
BKE_action_find_fcurves_with_bones(action, store_animated_pchans);
BKE_action_find_fcurves_with_bones(action, slot_handle, 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__));
@@ -96,16 +97,19 @@ static PoseBackup *pose_backup_create(const Object *ob,
return pose_backup;
}
PoseBackup *BKE_pose_backup_create_all_bones(const Object *ob, const bAction *action)
PoseBackup *BKE_pose_backup_create_all_bones(const Object *ob,
const bAction *action,
const blender::animrig::slot_handle_t slot_handle)
{
return pose_backup_create(ob, action, BoneNameSet());
return pose_backup_create(ob, action, slot_handle, BoneNameSet());
}
PoseBackup *BKE_pose_backup_create_selected_bones(const Object *ob, const bAction *action)
PoseBackup *BKE_pose_backup_create_selected_bones(
const Object *ob, const bAction *action, const blender::animrig::slot_handle_t slot_handle)
{
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);
return pose_backup_create(ob, action, slot_handle, selected_bone_names);
}
bool BKE_pose_backup_is_selection_relevant(const PoseBackup *pose_backup)
@@ -138,10 +142,12 @@ void BKE_pose_backup_free(PoseBackup *pbd)
MEM_freeN(pbd);
}
void BKE_pose_backup_create_on_object(Object *ob, const bAction *action)
void BKE_pose_backup_create_on_object(Object *ob,
const bAction *action,
const blender::animrig::slot_handle_t slot_handle)
{
BKE_pose_backup_clear(ob);
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(ob, action);
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(ob, action, slot_handle);
ob->runtime->pose_backup = pose_backup;
}

View File

@@ -107,8 +107,11 @@ static bAction *poselib_action_to_blend(PoseBlendData *pbd)
/* Makes a copy of the current pose for restoration purposes - doesn't do constraints currently */
static void poselib_backup_posecopy(PoseBlendData *pbd)
{
const bAction *action = poselib_action_to_blend(pbd);
pbd->pose_backup = BKE_pose_backup_create_selected_bones(pbd->ob, action);
bAction *action = poselib_action_to_blend(pbd);
blender::animrig::Action &pose_data = action->wrap();
blender::animrig::Slot &slot = blender::animrig::get_best_pose_slot_for_id(pbd->ob->id,
pose_data);
pbd->pose_backup = BKE_pose_backup_create_selected_bones(pbd->ob, action, slot.handle);
if (pbd->state == POSE_BLEND_INIT) {
/* Ready for blending now. */
@@ -196,11 +199,11 @@ static void poselib_blend_apply(bContext *C, wmOperator *op)
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, 0.0f);
bAction *to_blend = poselib_action_to_blend(pbd);
blender::animrig::slot_handle_t to_blend_slot_handle = blender::animrig::first_slot_handle(
*to_blend);
blender::animrig::Slot &to_blend_slot = blender::animrig::get_best_pose_slot_for_id(
pbd->ob->id, to_blend->wrap());
blender::animrig::pose_apply_action_blend(
pbd->ob, to_blend, to_blend_slot_handle, &anim_eval_context, pbd->blend_factor);
pbd->ob, to_blend, to_blend_slot.handle, &anim_eval_context, pbd->blend_factor);
}
/* ---------------------------- */

View File

@@ -977,14 +977,24 @@ static PoseBackup *action_preview_render_prepare(IconPreview *preview)
}
/* Create a backup of the current pose. */
bAction *action = (bAction *)preview->id;
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(object, action);
blender::animrig::Action &pose_action = reinterpret_cast<bAction *>(preview->id)->wrap();
if (pose_action.slot_array_num == 0) {
WM_report(RPT_WARNING, "Action has no data, cannot render preview");
return nullptr;
}
blender::animrig::Slot &slot = blender::animrig::get_best_pose_slot_for_id(object->id,
pose_action);
PoseBackup *pose_backup = BKE_pose_backup_create_all_bones(object, &pose_action, slot.handle);
/* Apply the Action as pose, so that it can be rendered. This assumes the Action represents a
* single pose, and that thus the evaluation time doesn't matter. */
AnimationEvalContext anim_eval_context = {preview->depsgraph, 0.0f};
const blender::animrig::slot_handle_t slot_handle = blender::animrig::first_slot_handle(*action);
blender::animrig::pose_apply_action_all_bones(object, action, slot_handle, &anim_eval_context);
const blender::animrig::slot_handle_t slot_handle = blender::animrig::first_slot_handle(
pose_action);
blender::animrig::pose_apply_action_all_bones(
object, &pose_action, slot_handle, &anim_eval_context);
/* Force evaluation of the new pose, before the preview is rendered. */
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);

View File

@@ -163,9 +163,16 @@ static void rna_Pose_blend_pose_from_action(ID *pose_owner,
static void rna_Pose_backup_create(ID *pose_owner, bAction *action)
{
BLI_assert(GS(pose_owner->name) == ID_OB);
if (!action || action->wrap().slot_array_num == 0) {
/* A pose asset without slots has no data, this usually doesn't happen but can happen by
* tagging an empty action as a pose asset. */
return;
}
Object *pose_owner_ob = (Object *)pose_owner;
blender::animrig::Slot &slot = blender::animrig::get_best_pose_slot_for_id(*pose_owner,
action->wrap());
BKE_pose_backup_create_on_object(pose_owner_ob, action);
BKE_pose_backup_create_on_object(pose_owner_ob, action, slot.handle);
}
static bool rna_Pose_backup_restore(ID *pose_owner, bContext *C)