Blender V4.5
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 "ANIM_action.hh"
20#include "ANIM_pose.hh"
21
22#include "CLG_log.h"
23#include "testing/testing.h"
24
26 "Properties not stored in the pose are expected to not be modified.";
27
29
30class PoseTest : public testing::Test {
31 public:
40
41 static void SetUpTestSuite()
42 {
43 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
44 CLG_init();
45
46 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
48 }
49
50 static void TearDownTestSuite()
51 {
52 CLG_exit();
53 }
54
55 void SetUp() override
56 {
58 pose_action = BKE_id_new<Action>(bmain, "pose_data");
59 Layer &layer = pose_action->layer_add("first_layer");
60 Strip &strip = layer.strip_add(*pose_action, Strip::Type::Keyframe);
61 keyframe_data = &strip.data<StripKeyframeData>(*pose_action);
62
66
67 bArmature *armature = BKE_armature_add(bmain, "ArmatureA");
68 obj_armature_a->data = armature;
69
70 Bone *bone = MEM_callocN<Bone>("BONE");
71 STRNCPY(bone->name, "BoneA");
72 BLI_addtail(&armature->bonebase, bone);
73
74 bone = MEM_callocN<Bone>("BONE");
75 STRNCPY(bone->name, "BoneB");
76 BLI_addtail(&armature->bonebase, bone);
77
78 BKE_pose_ensure(bmain, obj_armature_a, armature, false);
79
80 armature = BKE_armature_add(bmain, "ArmatureB");
81 obj_armature_b->data = armature;
82
83 bone = MEM_callocN<Bone>("BONE");
84 STRNCPY(bone->name, "BoneA");
85 BLI_addtail(&armature->bonebase, bone);
86
87 bone = MEM_callocN<Bone>("BONE");
88 STRNCPY(bone->name, "BoneB");
89 BLI_addtail(&armature->bonebase, bone);
90
91 BKE_pose_ensure(bmain, obj_armature_b, armature, false);
92 }
93
94 void TearDown() override
95 {
97 }
98};
99
100TEST_F(PoseTest, get_best_slot)
101{
102 Slot &first_slot = pose_action->slot_add();
103 Slot &second_slot = pose_action->slot_add_for_id(obj_empty->id);
104
105 EXPECT_EQ(&get_best_pose_slot_for_id(obj_empty->id, *pose_action), &second_slot);
106 EXPECT_EQ(&get_best_pose_slot_for_id(obj_armature_a->id, *pose_action), &first_slot);
107}
108
109TEST_F(PoseTest, apply_action_object)
110{
111 /* Since pose bones live on the object, the code is already set up to handle objects
112 * transforms, even though the name suggests it only applies to bones. */
113 Slot &first_slot = pose_action->slot_add();
114 EXPECT_EQ(obj_empty->loc[0], 0.0f);
115 keyframe_data->keyframe_insert(bmain, first_slot, {"location", 0}, {1, 10}, key_settings);
116 AnimationEvalContext eval_context = {nullptr, 1.0f};
118 obj_empty, pose_action, first_slot.handle, &eval_context);
119 EXPECT_EQ(obj_empty->loc[0], 10.0f);
120}
121
122TEST_F(PoseTest, apply_action_all_bones_single_slot)
123{
124 Slot &first_slot = pose_action->slot_add();
125
126 keyframe_data->keyframe_insert(
127 bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
128 keyframe_data->keyframe_insert(
129 bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
130
131 bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
132 bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
133
134 bone_a->loc[1] = 1.0;
135 bone_a->loc[2] = 2.0;
136
137 AnimationEvalContext eval_context = {nullptr, 1.0f};
139 obj_armature_a, pose_action, first_slot.handle, &eval_context);
140 EXPECT_EQ(bone_a->loc[0], 10.0);
141 EXPECT_EQ(bone_b->loc[1], 5.0);
142
143 EXPECT_EQ(bone_a->loc[1], 1.0) << msg_unexpected_modification;
144 EXPECT_EQ(bone_a->loc[2], 2.0);
145}
146
147TEST_F(PoseTest, apply_action_all_bones_multiple_slots)
148{
149 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
150 Slot &slot_b = pose_action->slot_add_for_id(obj_armature_b->id);
151
152 keyframe_data->keyframe_insert(
153 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
154 keyframe_data->keyframe_insert(
155 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
156
157 keyframe_data->keyframe_insert(
158 bmain, slot_b, {"pose.bones[\"BoneA\"].location", 1}, {1, 10}, key_settings);
159 keyframe_data->keyframe_insert(
160 bmain, slot_b, {"pose.bones[\"BoneB\"].location", 1}, {1, 10}, key_settings);
161
162 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
163 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
164
165 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
166 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
167
168 AnimationEvalContext eval_context = {nullptr, 1.0f};
170 obj_armature_a, pose_action, slot_a.handle, &eval_context);
171
172 EXPECT_EQ(arm_a_bone_a->loc[0], 5.0);
173 EXPECT_EQ(arm_a_bone_a->loc[1], 0.0) << msg_unexpected_modification;
174 EXPECT_EQ(arm_a_bone_a->loc[2], 0.0) << msg_unexpected_modification;
175
176 EXPECT_EQ(arm_a_bone_b->loc[0], 5.0);
177
178 EXPECT_EQ(arm_b_bone_a->loc[1], 0.0) << "Other armature should not be affected yet.";
179
181 obj_armature_b, pose_action, slot_b.handle, &eval_context);
182
183 EXPECT_EQ(arm_b_bone_b->loc[0], 0.0) << msg_unexpected_modification;
184 EXPECT_EQ(arm_b_bone_b->loc[1], 10.0);
185 EXPECT_EQ(arm_b_bone_b->loc[2], 0.0) << msg_unexpected_modification;
186
187 EXPECT_EQ(arm_a_bone_a->loc[0], 5.0) << "Other armature should not be affected.";
188
189 /* Any slot can be applied, even if it hasn't been added for the ID. */
191 obj_armature_a, pose_action, slot_b.handle, &eval_context);
192
193 EXPECT_EQ(arm_b_bone_b->loc[1], arm_b_bone_a->loc[1])
194 << "Applying the same pose should result in the same values.";
195}
196
197TEST_F(PoseTest, apply_action_blend_single_slot)
198{
199 Slot &first_slot = pose_action->slot_add();
200 keyframe_data->keyframe_insert(
201 bmain, first_slot, {"pose.bones[\"BoneA\"].location", 0}, {1, 10}, key_settings);
202 keyframe_data->keyframe_insert(
203 bmain, first_slot, {"pose.bones[\"BoneB\"].location", 1}, {1, 5}, key_settings);
204
205 bPoseChannel *bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
206 bPoseChannel *bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
207
208 bone_a->loc[0] = 0.0;
209 bone_b->loc[1] = 0.0;
210
211 AnimationEvalContext eval_context = {nullptr, 1.0f};
213 obj_armature_a, pose_action, first_slot.handle, &eval_context, 1.0);
214
215 EXPECT_NEAR(bone_a->loc[0], 10.0, 0.001);
216 EXPECT_NEAR(bone_b->loc[1], 5.0, 0.001);
217
218 bone_a->loc[0] = 0.0;
219 bone_b->loc[1] = 0.0;
220
222 obj_armature_a, pose_action, first_slot.handle, &eval_context, 0.5);
223
224 EXPECT_NEAR(bone_a->loc[0], 5.0, 0.001);
225 EXPECT_NEAR(bone_b->loc[1], 2.5, 0.001);
226
227 bone_a->loc[0] = 0.0;
228 bone_b->loc[1] = 0.0;
229
230 bone_a->bone->flag |= BONE_SELECTED;
231 bone_b->bone->flag &= ~BONE_SELECTED;
232
233 /* This should only affect the selected bone. */
235 obj_armature_a, pose_action, first_slot.handle, &eval_context, 0.5);
236
237 EXPECT_NEAR(bone_a->loc[0], 5.0, 0.001);
238 EXPECT_NEAR(bone_b->loc[1], 0.0, 0.001);
239}
240
241TEST_F(PoseTest, apply_action_multiple_objects)
242{
243 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
244 Slot &slot_b = pose_action->slot_add_for_id(obj_armature_b->id);
245
246 keyframe_data->keyframe_insert(
247 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
248 keyframe_data->keyframe_insert(
249 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
250
251 keyframe_data->keyframe_insert(
252 bmain, slot_b, {"pose.bones[\"BoneA\"].location", 1}, {1, 10}, key_settings);
253 keyframe_data->keyframe_insert(
254 bmain, slot_b, {"pose.bones[\"BoneB\"].location", 1}, {1, 10}, key_settings);
255
256 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
257 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
258
259 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
260 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
261
263 arm_a_bone_a, arm_a_bone_b, arm_b_bone_a, arm_b_bone_b};
264
265 for (bPoseChannel *pose_bone : all_bones) {
266 pose_bone->bone->flag &= ~BONE_SELECTED;
267 pose_bone->loc[0] = 0.0;
268 pose_bone->loc[1] = 0.0;
269 }
270
271 AnimationEvalContext eval_context = {nullptr, 1.0f};
273 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
274
275 /* No bones are selected, this should affect all bones. */
276 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
277 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
278 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
279 EXPECT_NEAR(arm_b_bone_b->loc[1], 10, 0.001);
280
281 for (bPoseChannel *pose_bone : all_bones) {
282 pose_bone->loc[0] = 0.0;
283 pose_bone->loc[1] = 0.0;
284 }
285
286 arm_a_bone_a->bone->flag |= BONE_SELECTED;
287
289 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
290
291 /* Only the one selected bone should be affected. */
292 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
293 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
294 EXPECT_NEAR(arm_b_bone_a->loc[1], 0, 0.001);
295 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
296
297 for (bPoseChannel *pose_bone : all_bones) {
298 pose_bone->loc[0] = 0.0;
299 pose_bone->loc[1] = 0.0;
300 }
301
302 arm_a_bone_a->bone->flag |= BONE_SELECTED;
303 arm_b_bone_a->bone->flag |= BONE_SELECTED;
304
306 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
307
308 /* Only the two selected bones from different armatures should be affected. */
309 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
310 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
311 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
312 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
313
314 for (bPoseChannel *pose_bone : all_bones) {
315 pose_bone->loc[0] = 0.0;
316 pose_bone->loc[1] = 0.0;
317 }
318
320 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 0.5);
321
322 /* Blending half way. */
323 EXPECT_NEAR(arm_a_bone_a->loc[0], 2.5, 0.001);
324 EXPECT_NEAR(arm_a_bone_b->loc[0], 0, 0.001);
325 EXPECT_NEAR(arm_b_bone_a->loc[1], 5, 0.001);
326 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
327
328 for (bPoseChannel *pose_bone : all_bones) {
329 pose_bone->loc[0] = 0.0;
330 pose_bone->loc[1] = 0.0;
331 }
332
333 arm_a_bone_a->bone->flag |= BONE_SELECTED;
334 arm_a_bone_b->bone->flag |= BONE_SELECTED;
335 arm_b_bone_a->bone->flag |= BONE_SELECTED;
336
338 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
339
340 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
341 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
342 EXPECT_NEAR(arm_b_bone_a->loc[1], 10, 0.001);
343 EXPECT_NEAR(arm_b_bone_b->loc[1], 0, 0.001);
344}
345
346TEST_F(PoseTest, apply_action_multiple_objects_single_slot)
347{
348 Slot &slot_a = pose_action->slot_add_for_id(obj_armature_a->id);
349
350 keyframe_data->keyframe_insert(
351 bmain, slot_a, {"pose.bones[\"BoneA\"].location", 0}, {1, 5}, key_settings);
352 keyframe_data->keyframe_insert(
353 bmain, slot_a, {"pose.bones[\"BoneB\"].location", 0}, {1, 5}, key_settings);
354
355 bPoseChannel *arm_a_bone_a = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneA");
356 bPoseChannel *arm_a_bone_b = BKE_pose_channel_find_name(obj_armature_a->pose, "BoneB");
357
358 bPoseChannel *arm_b_bone_a = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneA");
359 bPoseChannel *arm_b_bone_b = BKE_pose_channel_find_name(obj_armature_b->pose, "BoneB");
360
362 arm_a_bone_a, arm_a_bone_b, arm_b_bone_a, arm_b_bone_b};
363
364 for (bPoseChannel *pose_bone : all_bones) {
365 pose_bone->bone->flag &= ~BONE_SELECTED;
366 pose_bone->loc[0] = 0.0;
367 pose_bone->loc[1] = 0.0;
368 }
369
370 AnimationEvalContext eval_context = {nullptr, 1.0f};
372 {obj_armature_a, obj_armature_b}, *pose_action, &eval_context, 1.0);
373
374 /* No bones are selected, this should affect all bones. Armature B has no slot, it should fall
375 * back to slot 0. */
376 EXPECT_NEAR(arm_a_bone_a->loc[0], 5, 0.001);
377 EXPECT_NEAR(arm_a_bone_b->loc[0], 5, 0.001);
378 EXPECT_NEAR(arm_b_bone_a->loc[0], 5, 0.001);
379 EXPECT_NEAR(arm_b_bone_b->loc[0], 5, 0.001);
380}
381
382} // 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)
Definition armature.cc:2932
bArmature * BKE_armature_add(Main *bmain, const char *name)
Definition armature.cc:538
void BKE_idtype_init()
Definition idtype.cc:122
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1495
Main * BKE_main_new()
Definition main.cc:48
void BKE_main_free(Main *bmain)
Definition main.cc:175
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:688
void CLG_exit(void)
Definition clog.c:704
void CLG_init(void)
Definition clog.c:697
@ BONE_SELECTED
@ HD_AUTO
@ BEZT_IPO_BEZ
@ BEZT_KEYTYPE_KEYFRAME
@ 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:38
StripKeyframeData * keyframe_data
Definition pose_test.cc:37
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:114
Slot & get_best_pose_slot_for_id(const ID &id, Action &pose_data)
Definition pose.cc:161
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:25
char name[64]
struct Bone * bone