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:
committed by
Christoph Lendenfeld
parent
ddbb3d673c
commit
c3e5d15c2e
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
257
source/blender/animrig/intern/pose_test.cc
Normal file
257
source/blender/animrig/intern/pose_test.cc
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/* ---------------------------- */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user