Blender V5.0
pose_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_listbase.h"
6#include "BLI_string.h"
7
8#include "BKE_action.hh"
9#include "BKE_anim_data.hh"
10#include "BKE_animsys.h"
11#include "BKE_armature.hh"
12#include "BKE_idtype.hh"
13#include "BKE_lib_id.hh"
14#include "BKE_main.hh"
15#include "BKE_object.hh"
16
17#include "DEG_depsgraph.hh"
18
19#include "DNA_object_types.h"
20
21#include "ANIM_action.hh"
22#include "ANIM_pose.hh"
23
24#include "CLG_log.h"
25#include "testing/testing.h"
26
28 "Properties not stored in the pose are expected to not be modified.";
29
31
32class PoseTest : public testing::Test {
33 public:
42
43 static void SetUpTestSuite()
44 {
45 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
46 CLG_init();
47
48 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
50 }
51
52 static void TearDownTestSuite()
53 {
54 CLG_exit();
55 }
56
57 void SetUp() override
58 {
60 pose_action = BKE_id_new<Action>(bmain, "pose_data");
61 Layer &layer = pose_action->layer_add("first_layer");
62 Strip &strip = layer.strip_add(*pose_action, Strip::Type::Keyframe);
63 keyframe_data = &strip.data<StripKeyframeData>(*pose_action);
64
68
69 bArmature *armature = BKE_armature_add(bmain, "ArmatureA");
70 obj_armature_a->data = armature;
71
72 Bone *bone = MEM_callocN<Bone>("BONE");
73 STRNCPY(bone->name, "BoneA");
74 BLI_addtail(&armature->bonebase, bone);
75
76 bone = MEM_callocN<Bone>("BONE");
77 STRNCPY(bone->name, "BoneB");
78 BLI_addtail(&armature->bonebase, bone);
79
80 BKE_pose_ensure(bmain, obj_armature_a, armature, false);
81
82 armature = BKE_armature_add(bmain, "ArmatureB");
83 obj_armature_b->data = armature;
84
85 bone = MEM_callocN<Bone>("BONE");
86 STRNCPY(bone->name, "BoneA");
87 BLI_addtail(&armature->bonebase, bone);
88
89 bone = MEM_callocN<Bone>("BONE");
90 STRNCPY(bone->name, "BoneB");
91 BLI_addtail(&armature->bonebase, bone);
92
93 BKE_pose_ensure(bmain, obj_armature_b, armature, false);
94 }
95
96 void TearDown() override
97 {
99 }
100};
101
102TEST_F(PoseTest, get_best_slot)
103{
104 Slot &first_slot = pose_action->slot_add();
105 Slot &second_slot = pose_action->slot_add_for_id(obj_empty->id);
106
107 EXPECT_EQ(&get_best_pose_slot_for_id(obj_empty->id, *pose_action), &second_slot);
108 EXPECT_EQ(&get_best_pose_slot_for_id(obj_armature_a->id, *pose_action), &first_slot);
109}
110
111TEST_F(PoseTest, apply_action_object)
112{
113 /* Since pose bones live on the object, the code is already set up to handle objects
114 * transforms, even though the name suggests it only applies to bones. */
115 Slot &first_slot = pose_action->slot_add();
116 EXPECT_EQ(obj_empty->loc[0], 0.0f);
117 keyframe_data->keyframe_insert(bmain, first_slot, {"location", 0}, {1, 10}, key_settings);
118 AnimationEvalContext eval_context = {nullptr, 1.0f};
120 obj_empty, pose_action, first_slot.handle, &eval_context);
121 EXPECT_EQ(obj_empty->loc[0], 10.0f);
122}
123
124TEST_F(PoseTest, apply_action_all_bones_single_slot)
125{
126 Slot &first_slot = pose_action->slot_add();
127
128 keyframe_data->keyframe_insert(
129 bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
130 keyframe_data->keyframe_insert(
131 bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
132
133 bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
134 bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
135
136 bone_a->loc[1] = 1.0;
137 bone_a->loc[2] = 2.0;
138
139 AnimationEvalContext eval_context = {nullptr, 1.0f};
141 obj_armature_a, pose_action, first_slot.handle, &eval_context);
142 EXPECT_EQ(bone_a->loc[0], 10.0);
143 EXPECT_EQ(bone_b->loc[1], 5.0);
144
145 EXPECT_EQ(bone_a->loc[1], 1.0) << msg_unexpected_modification;
146 EXPECT_EQ(bone_a->loc[2], 2.0);
147}
148
149TEST_F(PoseTest, apply_action_all_bones_multiple_slots)
150{
151 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
152 Slot &slot_b = pose_action->slot_add_for_id(obj_armature_b->id);
153
154 keyframe_data->keyframe_insert(
155 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
156 keyframe_data->keyframe_insert(
157 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
158
159 keyframe_data->keyframe_insert(
160 bmain, slot_b, {"pose.bones[\"BoneA\"].location", 1}, {1, 10}, key_settings);
161 keyframe_data->keyframe_insert(
162 bmain, slot_b, {"pose.bones[\"BoneB\"].location", 1}, {1, 10}, key_settings);
163
164 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
165 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
166
167 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
168 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
169
170 AnimationEvalContext eval_context = {nullptr, 1.0f};
172 obj_armature_a, pose_action, slot_a.handle, &eval_context);
173
174 EXPECT_EQ(arm_a_bone_a->loc[0], 5.0);
175 EXPECT_EQ(arm_a_bone_a->loc[1], 0.0) << msg_unexpected_modification;
176 EXPECT_EQ(arm_a_bone_a->loc[2], 0.0) << msg_unexpected_modification;
177
178 EXPECT_EQ(arm_a_bone_b->loc[0], 5.0);
179
180 EXPECT_EQ(arm_b_bone_a->loc[1], 0.0) << "Other armature should not be affected yet.";
181
183 obj_armature_b, pose_action, slot_b.handle, &eval_context);
184
185 EXPECT_EQ(arm_b_bone_b->loc[0], 0.0) << msg_unexpected_modification;
186 EXPECT_EQ(arm_b_bone_b->loc[1], 10.0);
187 EXPECT_EQ(arm_b_bone_b->loc[2], 0.0) << msg_unexpected_modification;
188
189 EXPECT_EQ(arm_a_bone_a->loc[0], 5.0) << "Other armature should not be affected.";
190
191 /* Any slot can be applied, even if it hasn't been added for the ID. */
193 obj_armature_a, pose_action, slot_b.handle, &eval_context);
194
195 EXPECT_EQ(arm_b_bone_b->loc[1], arm_b_bone_a->loc[1])
196 << "Applying the same pose should result in the same values.";
197}
198
199TEST_F(PoseTest, apply_action_blend_single_slot)
200{
201 Slot &first_slot = pose_action->slot_add();
202 keyframe_data->keyframe_insert(
203 bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
204 keyframe_data->keyframe_insert(
205 bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
206
207 bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
208 bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
209
210 bone_a->loc[0] = 0.0;
211 bone_b->loc[1] = 0.0;
212
213 AnimationEvalContext eval_context = {nullptr, 1.0f};
215 obj_armature_a, pose_action, first_slot.handle, &eval_context, 1.0);
216
217 EXPECT_NEAR(bone_a->loc[0], 10.0, 0.001);
218 EXPECT_NEAR(bone_b->loc[1], 5.0, 0.001);
219
220 bone_a->loc[0] = 0.0;
221 bone_b->loc[1] = 0.0;
222
224 obj_armature_a, pose_action, first_slot.handle, &eval_context, 0.5);
225
226 EXPECT_NEAR(bone_a->loc[0], 5.0, 0.001);
227 EXPECT_NEAR(bone_b->loc[1], 2.5, 0.001);
228
229 bone_a->loc[0] = 0.0;
230 bone_b->loc[1] = 0.0;
231
232 bone_a->flag |= POSE_SELECTED;
233 bone_b->flag &= ~POSE_SELECTED;
234
235 /* This should only affect the selected bone. */
237 obj_armature_a, pose_action, first_slot.handle, &eval_context, 0.5);
238
239 EXPECT_NEAR(bone_a->loc[0], 5.0, 0.001);
240 EXPECT_NEAR(bone_b->loc[1], 0.0, 0.001);
241}
242
243TEST_F(PoseTest, apply_action_multiple_objects)
244{
245 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
246 Slot &slot_b = pose_action->slot_add_for_id(obj_armature_b->id);
247
248 keyframe_data->keyframe_insert(
249 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
250 keyframe_data->keyframe_insert(
251 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
252
253 keyframe_data->keyframe_insert(
254 bmain, slot_b, {"pose.bones[\"BoneA\"].location", 1}, {1, 10}, key_settings);
255 keyframe_data->keyframe_insert(
256 bmain, slot_b, {"pose.bones[\"BoneB\"].location", 1}, {1, 10}, key_settings);
257
258 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
259 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
260
261 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
262 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
263
265 arm_a_bone_a, arm_a_bone_b, arm_b_bone_a, arm_b_bone_b};
266
267 for (bPoseChannel *pose_bone : all_bones) {
268 pose_bone->flag &= ~POSE_SELECTED;
269 pose_bone->loc[0] = 0.0;
270 pose_bone->loc[1] = 0.0;
271 }
272
273 AnimationEvalContext eval_context = {nullptr, 1.0f};
275 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
276
277 /* No bones are selected, this should affect all bones. */
278 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
279 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
280 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
281 EXPECT_NEAR(arm_b_bone_b->loc[1], 10, 0.001);
282
283 for (bPoseChannel *pose_bone : all_bones) {
284 pose_bone->loc[0] = 0.0;
285 pose_bone->loc[1] = 0.0;
286 }
287
288 arm_a_bone_a->flag |= POSE_SELECTED;
289
291 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
292
293 /* Only the one selected bone should be affected. */
294 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
295 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
296 EXPECT_NEAR(arm_b_bone_a->loc[1], 0, 0.001);
297 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
298
299 for (bPoseChannel *pose_bone : all_bones) {
300 pose_bone->loc[0] = 0.0;
301 pose_bone->loc[1] = 0.0;
302 }
303
304 arm_a_bone_a->flag |= POSE_SELECTED;
305 arm_b_bone_a->flag |= POSE_SELECTED;
306
308 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
309
310 /* Only the two selected bones from different armatures should be affected. */
311 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
312 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
313 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
314 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
315
316 for (bPoseChannel *pose_bone : all_bones) {
317 pose_bone->loc[0] = 0.0;
318 pose_bone->loc[1] = 0.0;
319 }
320
322 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 0.5);
323
324 /* Blending half way. */
325 EXPECT_NEAR(arm_a_bone_a->loc[0], 2.5, 0.001);
326 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
327 EXPECT_NEAR(arm_b_bone_a->loc[1], 5, 0.001);
328 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
329
330 for (bPoseChannel *pose_bone : all_bones) {
331 pose_bone->loc[0] = 0.0;
332 pose_bone->loc[1] = 0.0;
333 }
334
335 arm_a_bone_a->flag |= POSE_SELECTED;
336 arm_a_bone_b->flag |= POSE_SELECTED;
337 arm_b_bone_a->flag |= POSE_SELECTED;
338
340 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
341
342 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
343 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
344 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
345 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
346}
347
348TEST_F(PoseTest, apply_action_multiple_objects_single_slot)
349{
350 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
351
352 keyframe_data->keyframe_insert(
353 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
354 keyframe_data->keyframe_insert(
355 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
356
357 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
358 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
359
360 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
361 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
362
364 arm_a_bone_a, arm_a_bone_b, arm_b_bone_a, arm_b_bone_b};
365
366 for (bPoseChannel *pose_bone : all_bones) {
367 pose_bone->flag &= ~POSE_SELECTED;
368 pose_bone->loc[0] = 0.0;
369 pose_bone->loc[1] = 0.0;
370 }
371
372 AnimationEvalContext eval_context = {nullptr, 1.0f};
374 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
375
376 /* No bones are selected, this should affect all bones. Armature B has no slot, it should fall
377 * back to slot 0. */
378 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
379 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
380 EXPECT_NEAR(arm_b_bone_a->loc[0], 5, 0.001);
381 EXPECT_NEAR(arm_b_bone_b->loc[0], 5, 0.001);
382}
383
384} // namespace blender::animrig::tests
Functions and classes to work with Actions.
Functions to work with animation poses.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
void BKE_pose_ensure(Main *bmain, Object *ob, bArmature *arm, bool do_id_user)
bArmature * BKE_armature_add(Main *bmain, const char *name)
void BKE_idtype_init()
Definition idtype.cc:121
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1514
Main * BKE_main_new()
Definition main.cc:89
void BKE_main_free(Main *bmain)
Definition main.cc:192
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
void CLG_exit()
Definition clog.cc:880
void CLG_init()
Definition clog.cc:873
@ POSE_SELECTED
@ HD_AUTO
@ BEZT_IPO_BEZ
@ BEZT_KEYTYPE_KEYFRAME
Object is a sort of wrapper for general info.
@ OB_EMPTY
@ OB_ARMATURE
Strip & strip_add(Action &owning_action, Strip::Type strip_type)
const T & data(const Action &owning_action) const
const blender::animrig::KeyframeSettings key_settings
Definition pose_test.cc:40
StripKeyframeData * keyframe_data
Definition pose_test.cc:39
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
void pose_apply_action_all_bones(Object *ob, bAction *action, slot_handle_t slot_handle, const AnimationEvalContext *anim_eval_context)
void pose_apply_action(blender::Span< Object * > objects, Action &pose_action, const AnimationEvalContext *anim_eval_context, float blend_factor)
void pose_apply_action_blend_all_bones(Object *ob, bAction *action, slot_handle_t slot_handle, const AnimationEvalContext *anim_eval_context, float blend_factor)
Definition pose.cc:113
Slot & get_best_pose_slot_for_id(const ID &id, Action &pose_data)
Definition pose.cc:160
void pose_apply_action_blend(Object *ob, bAction *action, slot_handle_t slot_handle, const AnimationEvalContext *anim_eval_context, float blend_factor)
constexpr char msg_unexpected_modification[]
Definition pose_test.cc:27
char name[64]