diff --git a/source/blender/animrig/ANIM_pose.hh b/source/blender/animrig/ANIM_pose.hh index b69cab68ccc..c749338f600 100644 --- a/source/blender/animrig/ANIM_pose.hh +++ b/source/blender/animrig/ANIM_pose.hh @@ -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 diff --git a/source/blender/animrig/CMakeLists.txt b/source/blender/animrig/CMakeLists.txt index d0cc39e45f6..7e9e51dee18 100644 --- a/source/blender/animrig/CMakeLists.txt +++ b/source/blender/animrig/CMakeLists.txt @@ -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 diff --git a/source/blender/animrig/intern/pose.cc b/source/blender/animrig/intern/pose.cc index ccc50a8ffd2..c490e09ad4f 100644 --- a/source/blender/animrig/intern/pose.cc +++ b/source/blender/animrig/intern/pose.cc @@ -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 diff --git a/source/blender/animrig/intern/pose_test.cc b/source/blender/animrig/intern/pose_test.cc new file mode 100644 index 00000000000..1ec26533e92 --- /dev/null +++ b/source/blender/animrig/intern/pose_test.cc @@ -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(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(*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(MEM_callocN(sizeof(Bone), "BONE")); + STRNCPY(bone->name, "BoneA"); + BLI_addtail(&armature->bonebase, bone); + + bone = static_cast(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(MEM_callocN(sizeof(Bone), "BONE")); + STRNCPY(bone->name, "BoneA"); + BLI_addtail(&armature->bonebase, bone); + + bone = static_cast(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 diff --git a/source/blender/blenkernel/BKE_action.hh b/source/blender/blenkernel/BKE_action.hh index 6bd24a4195f..a50002db650 100644 --- a/source/blender/blenkernel/BKE_action.hh +++ b/source/blender/blenkernel/BKE_action.hh @@ -376,14 +376,15 @@ using FoundFCurveCallbackConst = blender::FunctionRef; /** - * 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 diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h index 7a668aab09e..0825dd5c839 100644 --- a/source/blender/blenkernel/BKE_animsys.h +++ b/source/blender/blenkernel/BKE_animsys.h @@ -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, diff --git a/source/blender/blenkernel/BKE_pose_backup.h b/source/blender/blenkernel/BKE_pose_backup.h index b46d57cb878..000f15b2d91 100644 --- a/source/blender/blenkernel/BKE_pose_backup.h +++ b/source/blender/blenkernel/BKE_pose_backup.h @@ -14,6 +14,7 @@ #include +#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. diff --git a/source/blender/blenkernel/intern/action_bones.cc b/source/blender/blenkernel/intern/action_bones.cc index f30aa18e1d5..cc6cfd6e590 100644 --- a/source/blender/blenkernel/intern/action_bones.cc +++ b/source/blender/blenkernel/intern/action_bones.cc @@ -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), bone_name); }; - BKE_action_find_fcurves_with_bones(const_cast(action), const_callback); + BKE_action_find_fcurves_with_bones( + const_cast(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))) { diff --git a/source/blender/blenkernel/intern/pose_backup.cc b/source/blender/blenkernel/intern/pose_backup.cc index 920adacb03d..02d587d701c 100644 --- a/source/blender/blenkernel/intern/pose_backup.cc +++ b/source/blender/blenkernel/intern/pose_backup.cc @@ -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(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(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; } diff --git a/source/blender/editors/armature/pose_lib_2.cc b/source/blender/editors/armature/pose_lib_2.cc index 1965b520844..2bbf16e45c5 100644 --- a/source/blender/editors/armature/pose_lib_2.cc +++ b/source/blender/editors/armature/pose_lib_2.cc @@ -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); } /* ---------------------------- */ diff --git a/source/blender/editors/render/render_preview.cc b/source/blender/editors/render/render_preview.cc index 724776604ba..68b06c7e3fd 100644 --- a/source/blender/editors/render/render_preview.cc +++ b/source/blender/editors/render/render_preview.cc @@ -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(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); diff --git a/source/blender/makesrna/intern/rna_pose_api.cc b/source/blender/makesrna/intern/rna_pose_api.cc index 382fec7db23..5328072e4d7 100644 --- a/source/blender/makesrna/intern/rna_pose_api.cc +++ b/source/blender/makesrna/intern/rna_pose_api.cc @@ -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)