Anim: Unit tests for "Insert Needed" #116419

Merged
Christoph Lendenfeld merged 4 commits from ChrisLend/blender:unit_tests_insert_needed into main 2024-01-05 09:52:31 +01:00
1 changed files with 69 additions and 0 deletions

View File

@ -395,6 +395,75 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
bpy.data.objects.remove(keyed_object, do_unlink=True)
class InsertNeededTest(AbstractKeyframingTest, unittest.TestCase):
def setUp(self):
super().setUp()
bpy.context.scene.tool_settings.use_keyframe_insert_auto = True
bpy.context.preferences.edit.use_keyframe_insert_needed = True
bpy.context.preferences.edit.use_keyframe_insert_available = False
def tearDown(self):
bpy.context.scene.tool_settings.use_keyframe_insert_auto = False
bpy.context.preferences.edit.use_keyframe_insert_needed = False
def test_insert_needed_object(self):
keyed_object = _create_animation_object()
with bpy.context.temp_override(**_get_view3d_context()):
bpy.context.scene.frame_set(1)
bpy.ops.transform.translate(value=(-1, 0, 0))
bpy.context.scene.frame_set(5)
bpy.ops.transform.translate(value=(1, 0, 0))
action = keyed_object.animation_data.action
self.assertTrue(_fcurve_paths_match(action.fcurves, ["location"]))
# With "Insert Needed" enabled it has to key all location channels first,
# before it can add keys only to the channels where values have actually

This should check that len(expected_keys) == len(action.fcurves). Otherwise the test will succeed when there are no FCurves at all. In other words, the current test doesn't detect missing FCurves, it just detects unexpected ones.

This should check that `len(expected_keys) == len(action.fcurves)`. Otherwise the test will succeed when there are no FCurves at all. In other words, the current test doesn't detect missing FCurves, it just detects unexpected ones.

explicitly checking for 3 F-Curves self.assertEqual(len(action.fcurves), 3)

explicitly checking for 3 F-Curves `self.assertEqual(len(action.fcurves), 3)`

Yeah, my code wouldn't work well ;-) But you got the point of it!

Yeah, my code wouldn't work well ;-) But you got the point of it!
# changed.
expected_keys = {
"location": (2, 1, 1)
}

This seems to be a teardown, basically to prevent this test from influencing the next one. This should be done in a teardown function (docs), so that it's ensured to be run even when the test itself fails.

This seems to be a teardown, basically to prevent this test from influencing the next one. This should be done in a teardown function ([docs](https://docs.python.org/3/library/unittest.html#unittest.TestCase.tearDown)), so that it's ensured to be run even when the test itself fails.

thanks, did not know that was a thing. Also made an override to the setUp function so it is a bit clearer which user prefs are set

I will check the other tests to see if they can be refactored with setUp and tearDown. Different PR though

thanks, did not know that was a thing. Also made an override to the `setUp` function so it is a bit clearer which user prefs are set I will check the other tests to see if they can be refactored with `setUp` and `tearDown`. Different PR though

The bpy.data.objects.remove(keyed_object, do_unlink=True) call also should be in the teardown though, otherwise the object will stay there when the test fails.

The `bpy.data.objects.remove(keyed_object, do_unlink=True)` call also should be in the teardown though, otherwise the object will stay there when the test fails.

I don't think this is an issue because the base class AbstractKeyframingTest has the setUp function:

def setUp(self):
    super().setUp()
    bpy.ops.wm.read_homefile(use_factory_startup=True)
I don't think this is an issue because the base class `AbstractKeyframingTest` has the `setUp` function: ``` def setUp(self): super().setUp() bpy.ops.wm.read_homefile(use_factory_startup=True) ```

In that case the object removal code can be removed altogether -- either it's worth calling (and thus ensuring it gets called), or it's worth removing. When in doubt, throw it into the canal!

In that case the object removal code can be removed altogether -- either it's worth calling (and thus ensuring it gets called), or it's worth removing. When in doubt, throw it into the canal!

threw it into the canal. Will do the same with the other tests in an upcoming refactor PR

threw it into the canal. Will do the same with the other tests in an upcoming refactor PR
self.assertEqual(len(action.fcurves), 3)
for fcurve in action.fcurves:
if fcurve.data_path not in expected_keys:
raise AssertionError(f"Did not expect a key on {fcurve.data_path}")
self.assertEqual(expected_keys[fcurve.data_path][fcurve.array_index], len(fcurve.keyframe_points))
def test_insert_needed_bone(self):
armature_obj = _create_armature()
bpy.ops.object.mode_set(mode='POSE')
with bpy.context.temp_override(**_get_view3d_context()):
bpy.context.scene.frame_set(1)
bpy.ops.transform.translate(value=(-1, 0, 0), orient_type='LOCAL')
bpy.context.scene.frame_set(5)
bpy.ops.transform.translate(value=(1, 0, 0), orient_type='LOCAL')
bpy.ops.object.mode_set(mode='OBJECT')
action = armature_obj.animation_data.action
bone_path = f"pose.bones[\"{_BONE_NAME}\"]"
self.assertTrue(_fcurve_paths_match(action.fcurves, [f"{bone_path}.location"]))
# With "Insert Needed" enabled it has to key all location channels first,
# before it can add keys only to the channels where values have actually
# changed.
expected_keys = {
f"{bone_path}.location": (2, 1, 1)
}
self.assertEqual(len(action.fcurves), 3)
for fcurve in action.fcurves:
if fcurve.data_path not in expected_keys:
raise AssertionError(f"Did not expect a key on {fcurve.data_path}")
self.assertEqual(expected_keys[fcurve.data_path][fcurve.array_index], len(fcurve.keyframe_points))
def main():
global args
import argparse