Fix #141909: Creating a pose assets captures unkeyed custom properties

This was an oversight caused by 358a0479e8

Before this, only keyed custom properties were capture into the pose asset.
This behavior is now restored.

Pull Request: https://projects.blender.org/blender/blender/pulls/141937
This commit is contained in:
Christoph Lendenfeld
2025-07-17 11:05:31 +02:00
committed by Christoph Lendenfeld
parent 23d02ec57b
commit 0191848671
2 changed files with 67 additions and 5 deletions

View File

@@ -122,6 +122,19 @@ static blender::animrig::Action &extract_pose(Main &bmain,
BLI_assert(pose_object->pose);
Slot &slot = action.slot_add_for_id(pose_object->id);
const bArmature *armature = static_cast<bArmature *>(pose_object->data);
Set<RNAPath> existing_paths;
if (pose_object->adt && pose_object->adt->action &&
pose_object->adt->slot_handle != Slot::unassigned)
{
Action &pose_object_action = pose_object->adt->action->wrap();
const slot_handle_t pose_object_slot = pose_object->adt->slot_handle;
foreach_fcurve_in_action_slot(pose_object_action, pose_object_slot, [&](FCurve &fcurve) {
RNAPath existing_path = {fcurve.rna_path, std::nullopt, fcurve.array_index};
existing_paths.add(existing_path);
});
}
LISTBASE_FOREACH (bPoseChannel *, pose_bone, &pose_object->pose->chanbase) {
if (!(pose_bone->bone->flag & BONE_SELECTED) ||
!blender::animrig::bone_is_visible(armature, pose_bone->bone))
@@ -147,6 +160,12 @@ static blender::animrig::Action &extract_pose(Main &bmain,
continue;
}
for (const int i : values.index_range()) {
if (RNA_property_is_idprop(resolved_property) &&
!existing_paths.contains({rna_path_id_to_prop.value(), std::nullopt, i}))
{
/* Skipping custom properties without animation. */
continue;
}
strip_data.keyframe_insert(
&bmain, slot, {rna_path_id_to_prop.value(), i}, {1, values[i]}, key_settings);
}

View File

@@ -85,14 +85,18 @@ class CreateAssetTest(unittest.TestCase):
self._armature_object.pose.bones[_BONE_NAME_1].bone.select = True
self._armature_object.pose.bones[_BONE_NAME_2].bone.select = False
self.assertEqual(len(bpy.data.actions), 0)
self._armature_object.pose.bones[_BONE_NAME_1].keyframe_insert('["bool_test"]')
self._armature_object.pose.bones[_BONE_NAME_1].keyframe_insert('["float_test"]')
# There is an action for the custom properties.
self.assertEqual(len(bpy.data.actions), 1)
bpy.ops.poselib.create_pose_asset(
pose_name="local_asset",
asset_library_reference='LOCAL',
catalog_path="unit_test")
self.assertEqual(len(bpy.data.actions), 1, "Local poses should be stored as actions")
pose_action = bpy.data.actions[0]
self.assertEqual(len(bpy.data.actions), 2, "Local poses should be stored as actions")
pose_action = bpy.data.actions[1]
self.assertTrue(pose_action.asset_data is not None, "The created action should be marked as an asset")
expected_pose_values = {
@@ -122,13 +126,17 @@ class CreateAssetTest(unittest.TestCase):
self._armature_object.pose.bones[_BONE_NAME_1].bone.select = True
self._armature_object.pose.bones[_BONE_NAME_2].bone.select = False
self.assertEqual(len(bpy.data.actions), 0)
self._armature_object.pose.bones[_BONE_NAME_1].keyframe_insert('["bool_test"]')
self._armature_object.pose.bones[_BONE_NAME_1].keyframe_insert('["float_test"]')
# There is an action for the custom properties.
self.assertEqual(len(bpy.data.actions), 1)
bpy.ops.poselib.create_pose_asset(
pose_name="local_asset",
asset_library_reference=_LIB_NAME,
catalog_path="unit_test")
self.assertEqual(len(bpy.data.actions), 0, "The asset should not have been created in this file")
self.assertEqual(len(bpy.data.actions), 1, "The asset should not have been created in this file")
actions_folder = os.path.join(self._library.path, "Saved", "Actions")
asset_files = os.listdir(actions_folder)
self.assertEqual(len(asset_files),
@@ -160,6 +168,41 @@ class CreateAssetTest(unittest.TestCase):
self.assertAlmostEqual(fcurve.keyframe_points[0].co.y,
expected_pose_values[fcurve.data_path][fcurve.array_index], 4)
def test_custom_properties_without_keys(self):
# Custom properties without keys should not be added to the pose asset.
self._armature_object.pose.bones[_BONE_NAME_1].location = (1, 1, 2)
self._armature_object.pose.bones[_BONE_NAME_2].location = (-1, 0, 0)
self._armature_object.pose.bones[_BONE_NAME_1].bone.select = True
self._armature_object.pose.bones[_BONE_NAME_2].bone.select = False
self.assertEqual(len(bpy.data.actions), 0)
bpy.ops.poselib.create_pose_asset(
pose_name="local_asset",
asset_library_reference='LOCAL',
catalog_path="unit_test")
pose_action = bpy.data.actions[0]
self.assertTrue(pose_action.asset_data is not None, "The created action should be marked as an asset")
expected_pose_values = {
f'pose.bones["{_BONE_NAME_1}"].location': (1, 1, 2),
f'pose.bones["{_BONE_NAME_1}"].rotation_quaternion': (1, 0, 0, 0),
f'pose.bones["{_BONE_NAME_1}"].scale': (1, 1, 1),
# The custom properties are not keyed, thus they should not be in the pose asset.
}
expected_pose_values.update(_BBONE_VALUES)
self.assertEqual(len(pose_action.fcurves), 24)
for fcurve in pose_action.fcurves:
self.assertTrue(
fcurve.data_path in expected_pose_values,
"Only the selected bone should be in the pose asset")
self.assertEqual(len(fcurve.keyframe_points), 1, "Only one key should have been created")
self.assertEqual(fcurve.keyframe_points[0].co.x, 1, "Poses should be on the first frame")
self.assertAlmostEqual(fcurve.keyframe_points[0].co.y,
expected_pose_values[fcurve.data_path][fcurve.array_index], 4)
def main():
global args