Refactor: adjust unit tests to no longer use the legacy Action API
Adjust various unit tests so that they no longer use the legacy Action API (which was deprecated in Blender 4.4 and will be removed in 5.0). No functional changes. This is part of #146586 Pull Request: https://projects.blender.org/blender/blender/pulls/147060
This commit is contained in:
@@ -377,203 +377,6 @@ class LimitationsTest(unittest.TestCase):
|
||||
self.assertFalse(hasattr(strip, 'frame_offset'))
|
||||
|
||||
|
||||
class LegacyAPIOnLayeredActionTest(unittest.TestCase):
|
||||
"""Test that the legacy Action API works on layered Actions.
|
||||
|
||||
It should give access to the keyframes for the first slot.
|
||||
|
||||
- curve_frame_range
|
||||
- fcurves
|
||||
- groups
|
||||
- id_root
|
||||
- flip_with_pose(object)
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
bpy.ops.wm.read_homefile(use_factory_startup=True)
|
||||
|
||||
self.action = bpy.data.actions.new('LayeredAction')
|
||||
|
||||
def test_fcurves_on_layered_action(self) -> None:
|
||||
slot = self.action.slots.new(bpy.data.objects['Cube'].id_type, bpy.data.objects['Cube'].name)
|
||||
|
||||
layer = self.action.layers.new(name="Layer")
|
||||
strip = layer.strips.new(type='KEYFRAME')
|
||||
channelbag = strip.channelbags.new(slot=slot)
|
||||
|
||||
# Create new F-Curves via legacy API, they should be stored on the Channelbag.
|
||||
fcurve1 = self.action.fcurves.new("scale", index=1)
|
||||
fcurve2 = self.action.fcurves.new("scale", index=2)
|
||||
self.assertEqual([fcurve1, fcurve2], channelbag.fcurves[:], "Expected two F-Curves after creating them")
|
||||
self.assertEqual([fcurve1, fcurve2], self.action.fcurves[:],
|
||||
"Expected the same F-Curves on the legacy API")
|
||||
|
||||
# Find an F-Curve.
|
||||
self.assertEqual(fcurve2, self.action.fcurves.find("scale", index=2))
|
||||
|
||||
# Create an already-existing F-Curve.
|
||||
try:
|
||||
self.action.fcurves.new("scale", index=2)
|
||||
except RuntimeError as ex:
|
||||
self.assertIn("F-Curve 'scale[2]' already exists in action 'LayeredAction'", str(ex))
|
||||
else:
|
||||
self.fail("expected RuntimeError not thrown")
|
||||
self.assertEqual([fcurve1, fcurve2], channelbag.fcurves[:],
|
||||
"Expected two F-Curves after failing to create a third")
|
||||
self.assertEqual([fcurve1, fcurve2], self.action.fcurves[:])
|
||||
|
||||
# Remove a single F-Curve.
|
||||
self.action.fcurves.remove(fcurve1)
|
||||
self.assertEqual([fcurve2], channelbag.fcurves[:], "Expected single F-Curve after removing one")
|
||||
self.assertEqual([fcurve2], self.action.fcurves[:])
|
||||
|
||||
# Clear all F-Curves (with multiple F-Curves to avoid the trivial case).
|
||||
self.action.fcurves.new("scale", index=3)
|
||||
self.action.fcurves.clear()
|
||||
self.assertEqual([], channelbag.fcurves[:], "Expected empty fcurves list after clearing")
|
||||
self.assertEqual([], self.action.fcurves[:])
|
||||
|
||||
def test_fcurves_clear_should_not_create_layers(self):
|
||||
self.action.fcurves.clear()
|
||||
self.assertEqual([], self.action.slots[:])
|
||||
self.assertEqual([], self.action.layers[:])
|
||||
|
||||
def test_fcurves_new_on_empty_action(self) -> None:
|
||||
# Create new F-Curves via legacy API, this should create a layer+strip+Channelbag.
|
||||
fcurve1 = self.action.fcurves.new("scale", index=1)
|
||||
fcurve2 = self.action.fcurves.new("scale", index=2)
|
||||
|
||||
self.assertEqual(1, len(self.action.slots))
|
||||
self.assertEqual(1, len(self.action.layers))
|
||||
|
||||
slot = self.action.slots[0]
|
||||
layer = self.action.layers[0]
|
||||
|
||||
self.assertEqual("Legacy Slot", slot.name_display)
|
||||
self.assertEqual("Legacy Layer", layer.name)
|
||||
|
||||
self.assertEqual(1, len(layer.strips))
|
||||
strip = layer.strips[0]
|
||||
self.assertEqual('KEYFRAME', strip.type)
|
||||
self.assertEqual(1, len(strip.channelbags))
|
||||
channelbag = strip.channelbags[0]
|
||||
self.assertEqual(channelbag.slot_handle, slot.handle)
|
||||
|
||||
self.assertEqual([fcurve1, fcurve2], channelbag.fcurves[:])
|
||||
|
||||
# After this, there is no need to test the rest of the functions, as the
|
||||
# Action will be in the same state as in test_fcurves_on_layered_action().
|
||||
|
||||
def test_groups(self) -> None:
|
||||
# Create a group by using the legacy API to create an F-Curve with group name.
|
||||
group_name = "Object Transfoibles"
|
||||
self.action.fcurves.new("scale", index=1, action_group=group_name)
|
||||
|
||||
layer = self.action.layers[0]
|
||||
strip = layer.strips[0]
|
||||
channelbag = strip.channelbags[0]
|
||||
|
||||
self.assertEqual(1, len(channelbag.groups), "The new group should be available on the channelbag")
|
||||
self.assertEqual(group_name, channelbag.groups[0].name)
|
||||
self.assertEqual(1, len(self.action.groups), "The new group should be available with the legacy group API")
|
||||
self.assertEqual(group_name, self.action.groups[0].name)
|
||||
|
||||
# Create a group via the legacy API.
|
||||
group = self.action.groups.new(group_name)
|
||||
self.assertEqual("{}.001".format(group_name), group.name, "The group should have a unique name")
|
||||
self.assertEqual(group, self.action.groups[1], "The group should be accessible via the legacy API")
|
||||
self.assertEqual(group, channelbag.groups[1], "The group should be accessible via the channelbag")
|
||||
|
||||
# Remove a group via the legacy API.
|
||||
self.action.groups.remove(group)
|
||||
self.assertNotIn(group, self.action.groups[:], "A group should be removable via the legacy API")
|
||||
self.assertNotIn(group, channelbag.groups[:], "A group should be removable via the legacy API")
|
||||
|
||||
def test_groups_new_on_empty_action(self) -> None:
|
||||
# Create new group via legacy API, this should create a layer+strip+Channelbag.
|
||||
group = self.action.groups.new("foo")
|
||||
|
||||
self.assertEqual(1, len(self.action.slots))
|
||||
self.assertEqual(1, len(self.action.layers))
|
||||
|
||||
slot = self.action.slots[0]
|
||||
layer = self.action.layers[0]
|
||||
|
||||
self.assertEqual("Legacy Slot", slot.name_display)
|
||||
self.assertEqual("Legacy Layer", layer.name)
|
||||
|
||||
self.assertEqual(1, len(layer.strips))
|
||||
strip = layer.strips[0]
|
||||
self.assertEqual('KEYFRAME', strip.type)
|
||||
self.assertEqual(1, len(strip.channelbags))
|
||||
channelbag = strip.channelbags[0]
|
||||
self.assertEqual(channelbag.slot_handle, slot.handle)
|
||||
|
||||
self.assertEqual([group], channelbag.groups[:])
|
||||
|
||||
def test_id_root_on_layered_action(self) -> None:
|
||||
# When there's at least one slot, action.id_root should simply act as a
|
||||
# proxy for the first slot's target_id_type. This should work for both
|
||||
# reading and writing.
|
||||
|
||||
slot_1 = self.action.slots.new('OBJECT', "Slot 1")
|
||||
slot_2 = self.action.slots.new('CAMERA', "Slot 2")
|
||||
bpy.data.objects['Cube'].animation_data_create()
|
||||
bpy.data.objects['Cube'].animation_data.action = self.action
|
||||
bpy.data.objects['Cube'].animation_data.action_slot = slot_1
|
||||
|
||||
self.assertEqual(self.action.id_root, 'OBJECT')
|
||||
self.assertEqual(self.action.slots[0].target_id_type, 'OBJECT')
|
||||
self.assertEqual(self.action.slots[0].identifier, 'OBSlot 1')
|
||||
self.assertEqual(self.action.slots[1].target_id_type, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[1].identifier, 'CASlot 2')
|
||||
self.assertEqual(bpy.data.objects['Cube'].animation_data.last_slot_identifier, 'OBSlot 1')
|
||||
|
||||
self.action.id_root = 'MATERIAL'
|
||||
|
||||
self.assertEqual(self.action.id_root, 'MATERIAL')
|
||||
self.assertEqual(self.action.slots[0].target_id_type, 'MATERIAL')
|
||||
self.assertEqual(self.action.slots[0].identifier, 'MASlot 1')
|
||||
self.assertEqual(self.action.slots[1].target_id_type, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[1].identifier, 'CASlot 2')
|
||||
self.assertEqual(bpy.data.objects['Cube'].animation_data.last_slot_identifier, 'MASlot 1')
|
||||
|
||||
def test_id_root_on_layered_action_for_identifier_uniqueness(self) -> None:
|
||||
# When setting id_root such that the first slot's identifier would
|
||||
# become a duplicate, the name portion of the identifier should be
|
||||
# automatically renamed to be unique.
|
||||
|
||||
slot_1 = self.action.slots.new('OBJECT', "Foo")
|
||||
slot_2 = self.action.slots.new('CAMERA', "Foo")
|
||||
|
||||
self.assertEqual(self.action.id_root, 'OBJECT')
|
||||
self.assertEqual(self.action.slots[0].target_id_type, 'OBJECT')
|
||||
self.assertEqual(self.action.slots[0].identifier, 'OBFoo')
|
||||
self.assertEqual(self.action.slots[1].target_id_type, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[1].identifier, 'CAFoo')
|
||||
|
||||
self.action.id_root = 'CAMERA'
|
||||
|
||||
self.assertEqual(self.action.id_root, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[0].target_id_type, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[0].identifier, 'CAFoo.001')
|
||||
self.assertEqual(self.action.slots[1].target_id_type, 'CAMERA')
|
||||
self.assertEqual(self.action.slots[1].identifier, 'CAFoo')
|
||||
|
||||
def test_id_root_on_empty_action(self) -> None:
|
||||
# When there are no slots, setting action.id_root should create a legacy
|
||||
# slot and set its target_id_type.
|
||||
|
||||
self.assertEqual(self.action.id_root, 'UNSPECIFIED')
|
||||
self.assertEqual(len(self.action.slots), 0)
|
||||
|
||||
self.action.id_root = 'OBJECT'
|
||||
|
||||
self.assertEqual(self.action.id_root, 'OBJECT')
|
||||
self.assertEqual(len(self.action.slots), 1)
|
||||
self.assertEqual(self.action.slots[0].target_id_type, 'OBJECT')
|
||||
|
||||
|
||||
class ChannelbagsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
anims = bpy.data.actions
|
||||
|
||||
@@ -20,16 +20,32 @@ class AbstractAnimationTest:
|
||||
cls.testdir = args.testdir
|
||||
|
||||
def setUp(self):
|
||||
assert isinstance(self, unittest.TestCase)
|
||||
self.assertTrue(self.testdir.exists(),
|
||||
'Test dir %s should exist' % self.testdir)
|
||||
|
||||
|
||||
def _channelbag(animated_id: bpy.types.ID) -> bpy.types.ActionChannelbag:
|
||||
"""Return the first layer's Channelbag of the animated ID's Action."""
|
||||
action = animated_id.animation_data.action
|
||||
action_slot = animated_id.animation_data.action_slot
|
||||
channelbag = action.layers[0].strips[0].channelbag(action_slot)
|
||||
assert channelbag is not None
|
||||
return channelbag
|
||||
|
||||
|
||||
def _first_fcurve(animated_id: bpy.types.ID) -> bpy.types.FCurve:
|
||||
"""Return the first F-Curve of the animated ID's Action."""
|
||||
return _channelbag(animated_id).fcurves[0]
|
||||
|
||||
|
||||
class FCurveEvaluationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
def test_fcurve_versioning_291(self):
|
||||
# See D8752.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "fcurve-versioning-291.blend"))
|
||||
cube = bpy.data.objects['Cube']
|
||||
fcurve = cube.animation_data.action.fcurves.find('location', index=0)
|
||||
channelbag = cube.animation_data.action.layers[0].strips[0].channelbags[0]
|
||||
fcurve = channelbag.fcurves.find('location', index=0)
|
||||
|
||||
self.assertAlmostEqual(0.0, fcurve.evaluate(1))
|
||||
self.assertAlmostEqual(0.019638698548078537, fcurve.evaluate(2))
|
||||
@@ -46,7 +62,8 @@ class FCurveEvaluationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
# See D8752.
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "fcurve-extreme-handles.blend"))
|
||||
cube = bpy.data.objects['Cube']
|
||||
fcurve = cube.animation_data.action.fcurves.find('location', index=0)
|
||||
channelbag = cube.animation_data.action.layers[0].strips[0].channelbags[0]
|
||||
fcurve = channelbag.fcurves.find('location', index=0)
|
||||
|
||||
self.assertAlmostEqual(0.0, fcurve.evaluate(1))
|
||||
self.assertAlmostEqual(0.004713400732725859, fcurve.evaluate(2))
|
||||
@@ -66,11 +83,6 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
This tests both the evaluation of the RNA property and the F-Curve
|
||||
interpolation itself (the not-exposed-to-RNA flags `FCURVE_INT_VALUES` and
|
||||
`FCURVE_DISCRETE_VALUES` have an impact on the latter as well).
|
||||
|
||||
NOTE: This test uses the backward-compatible API in 4.4 (action.fcurves)
|
||||
because it only uses a single slot anyway. This way, the test is
|
||||
backward-compatible with older versions of Blender, and can be used to track
|
||||
down regression issues.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -87,7 +99,7 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
camera.keyframe_insert('lens', frame=64)
|
||||
|
||||
self._make_all_keys_linear()
|
||||
fcurve = camera.animation_data.action.fcurves[0]
|
||||
fcurve = _first_fcurve(camera)
|
||||
|
||||
scene.frame_set(0)
|
||||
self.assertAlmostEqual(16, camera.lens)
|
||||
@@ -115,7 +127,7 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
render.keyframe_insert('simplify_subdivision', frame=64)
|
||||
|
||||
self._make_all_keys_linear()
|
||||
fcurve = scene.animation_data.action.fcurves[0]
|
||||
fcurve = _first_fcurve(scene)
|
||||
|
||||
scene.frame_set(0)
|
||||
self.assertAlmostEqual(16, render.simplify_subdivision)
|
||||
@@ -143,7 +155,7 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
render.keyframe_insert('use_simplify', frame=64)
|
||||
|
||||
self._make_all_keys_linear()
|
||||
fcurve = scene.animation_data.action.fcurves[0]
|
||||
fcurve = _first_fcurve(scene)
|
||||
|
||||
scene.frame_set(0)
|
||||
self.assertEqual(False, render.use_simplify)
|
||||
@@ -170,7 +182,7 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
cube.keyframe_insert('rotation_mode', frame=64)
|
||||
|
||||
self._make_all_keys_linear()
|
||||
fcurve = cube.animation_data.action.fcurves[0]
|
||||
fcurve = _first_fcurve(cube)
|
||||
|
||||
scene.frame_set(0)
|
||||
self.assertEqual('QUATERNION', cube.rotation_mode)
|
||||
@@ -192,13 +204,13 @@ class PropertyInterpolationTest(AbstractAnimationTest, unittest.TestCase):
|
||||
"""
|
||||
|
||||
for action in bpy.data.actions:
|
||||
# Make this test backward compatible with older versions of Blender,
|
||||
# to make it easier to test regressions.
|
||||
self.assertEqual(1, len(action.slots), f"{action} should have exactly one slot")
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for key in fcurve.keyframe_points:
|
||||
key.interpolation = 'LINEAR'
|
||||
for layer in action.layers:
|
||||
for strip in layer.strips:
|
||||
self.assertEqual(strip.type, 'KEYFRAME')
|
||||
for channelbag in strip.channelbags:
|
||||
for fcurve in channelbag.fcurves:
|
||||
for key in fcurve.keyframe_points:
|
||||
key.interpolation = 'LINEAR'
|
||||
|
||||
|
||||
class EulerFilterTest(AbstractAnimationTest, unittest.TestCase):
|
||||
@@ -290,8 +302,8 @@ class EulerFilterTest(AbstractAnimationTest, unittest.TestCase):
|
||||
@staticmethod
|
||||
def active_object_rotation_channels() -> list[bpy.types.FCurve]:
|
||||
ob = bpy.context.view_layer.objects.active
|
||||
action = ob.animation_data.action
|
||||
return [action.fcurves.find('rotation_euler', index=idx) for idx in range(3)]
|
||||
channelbag = _channelbag(ob)
|
||||
return [channelbag.fcurves.find('rotation_euler', index=idx) for idx in range(3)]
|
||||
|
||||
|
||||
def get_view3d_context():
|
||||
@@ -322,8 +334,9 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
fcurve = _first_fcurve(key_object)
|
||||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
key = fcurve.keyframe_points[key_index]
|
||||
self.assertEqual(key.co.x, key_index)
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
@@ -339,7 +352,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
||||
key_object.keyframe_insert("rotation_euler", keytype='UNSUPPORTED')
|
||||
|
||||
# Only a single key should have been inserted.
|
||||
keys = key_object.animation_data.action.fcurves[0].keyframe_points
|
||||
keys = _first_fcurve(key_object).keyframe_points
|
||||
self.assertEqual(len(keys), 1)
|
||||
self.assertEqual(keys[0].type, 'GENERATED')
|
||||
|
||||
@@ -354,7 +367,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
key = _first_fcurve(key_object).keyframe_points[key_index]
|
||||
self.assertEqual(key.co.x, key_index + frame_offset)
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
@@ -369,7 +382,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
for key_index in range(key_count):
|
||||
key = key_object.animation_data.action.fcurves[0].keyframe_points[key_index]
|
||||
key = _first_fcurve(key_object).keyframe_points[key_index]
|
||||
self.assertAlmostEqual(key.co.x, key_index / key_count)
|
||||
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
@@ -405,7 +418,7 @@ class KeyframeInsertTest(AbstractAnimationTest, unittest.TestCase):
|
||||
# Even though range() is exclusive, the floating point limitations mean keys end up on that position.
|
||||
1000001.0
|
||||
]
|
||||
keyframe_points = key_object.animation_data.action.fcurves[0].keyframe_points
|
||||
keyframe_points = _first_fcurve(key_object).keyframe_points
|
||||
for i, value in enumerate(floating_point_steps):
|
||||
key = keyframe_points[i]
|
||||
self.assertAlmostEqual(key.co.x, value)
|
||||
@@ -429,7 +442,7 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
fcu = _first_fcurve(key_object)
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i, value=0)
|
||||
|
||||
@@ -452,7 +465,7 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
fcu = _first_fcurve(key_object)
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i + frame_offset, value=0)
|
||||
|
||||
@@ -474,7 +487,7 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
fcu = _first_fcurve(key_object)
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i / key_count, value=0)
|
||||
|
||||
@@ -497,7 +510,7 @@ class KeyframeDeleteTest(AbstractAnimationTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
key_object = bpy.context.active_object
|
||||
fcu = key_object.animation_data.action.fcurves[0]
|
||||
fcu = _first_fcurve(key_object)
|
||||
for i in range(key_count):
|
||||
fcu.keyframe_points.insert(frame=i / key_count + frame_offset, value=0)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ blender -b --factory-startup --python tests/python/bl_animation_keyframing.py --
|
||||
"""
|
||||
|
||||
|
||||
def _fcurve_paths_match(fcurves: list, expected_paths: list) -> bool:
|
||||
def _fcurve_paths_match(fcurves: list[bpy.types.FCurve], expected_paths: list[str]) -> None:
|
||||
data_paths = list(set([fcurve.data_path for fcurve in fcurves]))
|
||||
data_paths.sort()
|
||||
expected_paths.sort()
|
||||
@@ -22,6 +22,21 @@ def _fcurve_paths_match(fcurves: list, expected_paths: list) -> bool:
|
||||
f"Expected paths do not match F-Curve paths. Expected: {expected_paths}. F-Curve: {data_paths}")
|
||||
|
||||
|
||||
def _first_channelbag(action: bpy.types.Action) -> bpy.types.ActionChannelbag:
|
||||
"""Return the first Channelbag of the Action."""
|
||||
assert isinstance(action, bpy.types.Action), f"Expected Action, got {action!r}"
|
||||
return action.layers[0].strips[0].channelbags[0]
|
||||
|
||||
|
||||
def _channelbag(animated_id: bpy.types.ID) -> bpy.types.ActionChannelbag:
|
||||
"""Return the first layer's Channelbag of the animated ID's Action."""
|
||||
action = animated_id.animation_data.action
|
||||
action_slot = animated_id.animation_data.action_slot
|
||||
channelbag = action.layers[0].strips[0].channelbag(action_slot)
|
||||
assert channelbag is not None
|
||||
return channelbag
|
||||
|
||||
|
||||
def _get_view3d_context():
|
||||
ctx = bpy.context.copy()
|
||||
|
||||
@@ -86,7 +101,7 @@ def _insert_by_name_test(insert_key: str, expected_paths: list):
|
||||
keyed_object = _create_animation_object()
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type=insert_key)
|
||||
_fcurve_paths_match(keyed_object.animation_data.action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(_channelbag(keyed_object).fcurves, expected_paths)
|
||||
bpy.data.objects.remove(keyed_object, do_unlink=True)
|
||||
|
||||
|
||||
@@ -95,7 +110,7 @@ def _insert_from_user_preference_test(enabled_user_pref_fields: set, expected_pa
|
||||
bpy.context.preferences.edit.key_insert_channels = enabled_user_pref_fields
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
_fcurve_paths_match(keyed_object.animation_data.action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(_channelbag(keyed_object).fcurves, expected_paths)
|
||||
bpy.data.objects.remove(keyed_object, do_unlink=True)
|
||||
|
||||
|
||||
@@ -110,7 +125,7 @@ def _insert_with_keying_set_test(keying_set_name: str, expected_paths: list):
|
||||
keyed_object = _create_animation_object()
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
_fcurve_paths_match(keyed_object.animation_data.action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(_channelbag(keyed_object).fcurves, expected_paths)
|
||||
bpy.data.objects.remove(keyed_object, do_unlink=True)
|
||||
|
||||
|
||||
@@ -148,18 +163,20 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
channelbag = _channelbag(keyed_object)
|
||||
|
||||
# Check the F-Curves paths.
|
||||
expect_paths = ["location", "location", "location"]
|
||||
actual_paths = [fcurve.data_path for fcurve in keyed_object.animation_data.action.fcurves]
|
||||
actual_paths = [fcurve.data_path for fcurve in channelbag.fcurves]
|
||||
self.assertEqual(actual_paths, expect_paths)
|
||||
|
||||
# The actual reason for this test: check that these curves have the right group.
|
||||
expect_groups = ["Object Transforms"]
|
||||
actual_groups = [group.name for group in keyed_object.animation_data.action.groups]
|
||||
actual_groups = [group.name for group in channelbag.groups]
|
||||
self.assertEqual(actual_groups, expect_groups)
|
||||
|
||||
expect_groups = 3 * [keyed_object.animation_data.action.groups[0]]
|
||||
actual_groups = [fcurve.group for fcurve in keyed_object.animation_data.action.fcurves]
|
||||
expect_groups = 3 * [channelbag.groups[0]]
|
||||
actual_groups = [fcurve.group for fcurve in channelbag.fcurves]
|
||||
self.assertEqual(actual_groups, expect_groups)
|
||||
|
||||
def test_insert_custom_properties(self):
|
||||
@@ -196,7 +213,7 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
keyed_rna_paths = [f"[\"{bpy.utils.escape_identifier(path)}\"]" for path in keyed_properties.keys()]
|
||||
_fcurve_paths_match(keyed_object.animation_data.action.fcurves, keyed_rna_paths)
|
||||
_fcurve_paths_match(_channelbag(keyed_object).fcurves, keyed_rna_paths)
|
||||
bpy.data.objects.remove(keyed_object, do_unlink=True)
|
||||
|
||||
def test_key_selection_state(self):
|
||||
@@ -207,7 +224,7 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.context.scene.frame_set(5)
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
for fcurve in keyed_object.animation_data.action.fcurves:
|
||||
for fcurve in _channelbag(keyed_object).fcurves:
|
||||
self.assertEqual(len(fcurve.keyframe_points), 2)
|
||||
self.assertFalse(fcurve.keyframe_points[0].select_control_point)
|
||||
self.assertTrue(fcurve.keyframe_points[1].select_control_point)
|
||||
@@ -218,7 +235,7 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
# Test on location, which is a 3-item array, without explicitly passing an array index.
|
||||
self.assertTrue(curve_object.keyframe_insert('location'))
|
||||
|
||||
ob_fcurves = curve_object.animation_data.action.fcurves
|
||||
ob_fcurves = _channelbag(curve_object).fcurves
|
||||
|
||||
self.assertEqual(len(ob_fcurves), 3,
|
||||
"Keying 'location' without any array index should have created 3 F-Curves")
|
||||
@@ -249,8 +266,9 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
# Test with property for which Blender knows a group name too ('Object Transforms').
|
||||
self.assertTrue(curve_object.keyframe_insert('location', group="Téšt"))
|
||||
|
||||
fcurves = curve_object.animation_data.action.fcurves
|
||||
fgroups = curve_object.animation_data.action.groups
|
||||
channelbag = _channelbag(curve_object)
|
||||
fcurves = channelbag.fcurves
|
||||
fgroups = channelbag.groups
|
||||
|
||||
self.assertEqual(3 * ['location'], [fcurve.data_path for fcurve in fcurves])
|
||||
self.assertEqual([0, 1, 2], [fcurve.array_index for fcurve in fcurves])
|
||||
@@ -271,7 +289,7 @@ class InsertKeyTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
obj = bpy.context.object
|
||||
obj.data.attributes.new("test", "FLOAT", "POINT")
|
||||
self.assertTrue(obj.data.keyframe_insert('attributes["test"].data[0].value'))
|
||||
fcurves = obj.data.animation_data.action.fcurves
|
||||
fcurves = _channelbag(obj.data).fcurves
|
||||
self.assertEqual(len(fcurves), 1)
|
||||
self.assertEqual(fcurves[0].data_path, 'attributes["test"].data[0].value')
|
||||
|
||||
@@ -294,7 +312,7 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="BUILTIN_KSI_VisualLoc")
|
||||
|
||||
for fcurve in constrained.animation_data.action.fcurves:
|
||||
for fcurve in _channelbag(constrained).fcurves:
|
||||
self.assertEqual(fcurve.keyframe_points[0].co.y, t_value)
|
||||
|
||||
def test_visual_rotation_keying_set(self):
|
||||
@@ -310,7 +328,7 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="BUILTIN_KSI_VisualRot")
|
||||
|
||||
for fcurve in constrained.animation_data.action.fcurves:
|
||||
for fcurve in _channelbag(constrained).fcurves:
|
||||
self.assertAlmostEqual(fcurve.keyframe_points[0].co.y, rot_value_rads, places=4)
|
||||
|
||||
def test_visual_location_user_pref_override(self):
|
||||
@@ -327,7 +345,7 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
for fcurve in constrained.animation_data.action.fcurves:
|
||||
for fcurve in _channelbag(constrained).fcurves:
|
||||
self.assertEqual(fcurve.keyframe_points[0].co.y, t_value)
|
||||
|
||||
def test_visual_location_user_pref(self):
|
||||
@@ -344,7 +362,7 @@ class VisualKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
for fcurve in constrained.animation_data.action.fcurves:
|
||||
for fcurve in _channelbag(constrained).fcurves:
|
||||
self.assertEqual(fcurve.keyframe_points[0].co.y, t_value)
|
||||
|
||||
|
||||
@@ -391,11 +409,12 @@ class CycleAwareKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
|
||||
# Check that only location keys have been created.
|
||||
_fcurve_paths_match(action.fcurves, ["location"])
|
||||
channelbag = action.layers[0].strips[0].channelbags[0]
|
||||
_fcurve_paths_match(channelbag.fcurves, ["location"])
|
||||
|
||||
expected_keys = [1.0, 3.0, 5.0, 9.0, 20.0]
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.fcurves:
|
||||
actual_keys = [key.co.x for key in fcurve.keyframe_points]
|
||||
self.assertEqual(expected_keys, actual_keys)
|
||||
|
||||
@@ -425,7 +444,8 @@ class CycleAwareKeyingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
|
||||
expected_keys = [1.0, 3.0, 5.0, 20.0]
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
channelbag = action.layers[0].strips[0].channelbags[0]
|
||||
for fcurve in channelbag.fcurves:
|
||||
actual_keys = [key.co.x for key in fcurve.keyframe_points]
|
||||
self.assertEqual(expected_keys, actual_keys)
|
||||
|
||||
@@ -451,8 +471,8 @@ class AutoKeyframingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
action = keyed_object.animation_data.action
|
||||
_fcurve_paths_match(action.fcurves, ["location", "rotation_euler", "scale"])
|
||||
channelbag = _channelbag(keyed_object)
|
||||
_fcurve_paths_match(channelbag.fcurves, ["location", "rotation_euler", "scale"])
|
||||
|
||||
def test_autokey_bone(self):
|
||||
armature_obj = _create_armature()
|
||||
@@ -463,10 +483,10 @@ class AutoKeyframingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
action = armature_obj.animation_data.action
|
||||
channelbag = _channelbag(armature_obj)
|
||||
bone_path = f"pose.bones[\"{_BONE_NAME}\"]"
|
||||
expected_paths = [f"{bone_path}.location", f"{bone_path}.rotation_euler", f"{bone_path}.scale"]
|
||||
_fcurve_paths_match(action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(channelbag.fcurves, expected_paths)
|
||||
|
||||
def test_key_selection_state(self):
|
||||
armature_obj = _create_armature()
|
||||
@@ -476,8 +496,8 @@ class AutoKeyframingTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.transform.translate(value=(0, 1, 0))
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
action = armature_obj.animation_data.action
|
||||
for fcurve in action.fcurves:
|
||||
channelbag = _channelbag(armature_obj)
|
||||
for fcurve in channelbag.fcurves:
|
||||
self.assertEqual(len(fcurve.keyframe_points), 2)
|
||||
self.assertFalse(fcurve.keyframe_points[0].select_control_point)
|
||||
self.assertTrue(fcurve.keyframe_points[1].select_control_point)
|
||||
@@ -507,8 +527,8 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
# Test that no new keyframes have been added.
|
||||
action = keyed_object.animation_data.action
|
||||
_fcurve_paths_match(action.fcurves, ["rotation_euler"])
|
||||
channelbag = _channelbag(keyed_object)
|
||||
_fcurve_paths_match(channelbag.fcurves, ["rotation_euler"])
|
||||
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.context.scene.frame_set(1)
|
||||
@@ -516,10 +536,10 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.context.scene.frame_set(5)
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
action = keyed_object.animation_data.action
|
||||
_fcurve_paths_match(action.fcurves, ["location", "rotation_euler"])
|
||||
channelbag = _channelbag(keyed_object)
|
||||
_fcurve_paths_match(channelbag.fcurves, ["location", "rotation_euler"])
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.fcurves:
|
||||
# Translating the bone would also add rotation keys as long as "Only Insert Needed" is off.
|
||||
if "location" in fcurve.data_path or "rotation" in fcurve.data_path:
|
||||
self.assertEqual(len(fcurve.keyframe_points), 2)
|
||||
@@ -537,10 +557,10 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
# Test that no new keyframes have been added.
|
||||
action = armature_obj.animation_data.action
|
||||
channelbag = _channelbag(armature_obj)
|
||||
bone_path = f"pose.bones[\"{_BONE_NAME}\"]"
|
||||
expected_paths = [f"{bone_path}.rotation_euler"]
|
||||
_fcurve_paths_match(action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(channelbag.fcurves, expected_paths)
|
||||
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.context.scene.frame_set(1)
|
||||
@@ -549,9 +569,9 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
expected_paths = [f"{bone_path}.location", f"{bone_path}.rotation_euler"]
|
||||
_fcurve_paths_match(action.fcurves, expected_paths)
|
||||
_fcurve_paths_match(channelbag.fcurves, expected_paths)
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.fcurves:
|
||||
# Translating the bone would also add rotation keys as long as "Only Insert Needed" is off.
|
||||
if "location" in fcurve.data_path or "rotation" in fcurve.data_path:
|
||||
self.assertEqual(len(fcurve.keyframe_points), 2)
|
||||
@@ -572,10 +592,10 @@ class InsertAvailableTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.context.scene.frame_set(5)
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Available")
|
||||
|
||||
action = keyed_object.animation_data.action
|
||||
_fcurve_paths_match(action.fcurves, ["location"])
|
||||
channelbag = _channelbag(keyed_object)
|
||||
_fcurve_paths_match(channelbag.fcurves, ["location"])
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.fcurves:
|
||||
self.assertEqual(len(fcurve.keyframe_points), 2)
|
||||
|
||||
def test_insert_available(self):
|
||||
@@ -610,8 +630,8 @@ class InsertNeededTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.context.scene.frame_set(5)
|
||||
bpy.ops.transform.translate(value=(1, 0, 0))
|
||||
|
||||
action = keyed_object.animation_data.action
|
||||
_fcurve_paths_match(action.fcurves, ["location"])
|
||||
channelbag = _channelbag(keyed_object)
|
||||
_fcurve_paths_match(channelbag.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
|
||||
@@ -620,9 +640,9 @@ class InsertNeededTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
"location": (2, 1, 1)
|
||||
}
|
||||
|
||||
self.assertEqual(len(action.fcurves), 3)
|
||||
self.assertEqual(len(channelbag.fcurves), 3)
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.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))
|
||||
@@ -639,9 +659,9 @@ class InsertNeededTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
action = armature_obj.animation_data.action
|
||||
channelbag = _channelbag(armature_obj)
|
||||
bone_path = f"pose.bones[\"{_BONE_NAME}\"]"
|
||||
_fcurve_paths_match(action.fcurves, [f"{bone_path}.location"])
|
||||
_fcurve_paths_match(channelbag.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
|
||||
@@ -650,9 +670,9 @@ class InsertNeededTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
f"{bone_path}.location": (2, 1, 1)
|
||||
}
|
||||
|
||||
self.assertEqual(len(action.fcurves), 3)
|
||||
self.assertEqual(len(channelbag.fcurves), 3)
|
||||
|
||||
for fcurve in action.fcurves:
|
||||
for fcurve in channelbag.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))
|
||||
@@ -668,7 +688,19 @@ def _create_nla_anim_object():
|
||||
add: 0, 1
|
||||
base: 0, 1
|
||||
"""
|
||||
|
||||
anim_object = bpy.data.objects.new("anim_object", None)
|
||||
|
||||
def _ensure_fcurve(action: bpy.types.Action, *, data_path: str, index: int) -> bpy.types.FCurve:
|
||||
# Briefly directly assign the Action so that Blender knows what to do.
|
||||
anim_object.animation_data_create().action = action
|
||||
try:
|
||||
fcurve = action.fcurve_ensure_for_datablock(anim_object, data_path=data_path, index=index)
|
||||
finally:
|
||||
anim_object.animation_data.action = None
|
||||
|
||||
return fcurve
|
||||
|
||||
bpy.context.scene.collection.objects.link(anim_object)
|
||||
bpy.context.view_layer.objects.active = anim_object
|
||||
anim_object.select_set(True)
|
||||
@@ -677,7 +709,7 @@ def _create_nla_anim_object():
|
||||
track = anim_object.animation_data.nla_tracks.new()
|
||||
track.name = "base"
|
||||
action_base = bpy.data.actions.new(name="action_base")
|
||||
fcu = action_base.fcurves.new(data_path="location", index=0)
|
||||
fcu = _ensure_fcurve(action_base, data_path="location", index=0)
|
||||
fcu.keyframe_points.insert(0, value=0).interpolation = 'LINEAR'
|
||||
fcu.keyframe_points.insert(10, value=1).interpolation = 'LINEAR'
|
||||
track.strips.new("base_strip", 0, action_base)
|
||||
@@ -686,7 +718,7 @@ def _create_nla_anim_object():
|
||||
track = anim_object.animation_data.nla_tracks.new()
|
||||
track.name = "add"
|
||||
action_add = bpy.data.actions.new(name="action_add")
|
||||
fcu = action_add.fcurves.new(data_path="location", index=0)
|
||||
fcu = _ensure_fcurve(action_add, data_path="location", index=0)
|
||||
fcu.keyframe_points.insert(0, value=0).interpolation = 'LINEAR'
|
||||
fcu.keyframe_points.insert(10, value=1).interpolation = 'LINEAR'
|
||||
strip = track.strips.new("add_strip", 0, action_add)
|
||||
@@ -696,7 +728,7 @@ def _create_nla_anim_object():
|
||||
track = anim_object.animation_data.nla_tracks.new()
|
||||
track.name = "top"
|
||||
action_top = bpy.data.actions.new(name="action_top")
|
||||
fcu = action_top.fcurves.new(data_path="location", index=0)
|
||||
fcu = _ensure_fcurve(action_top, data_path="location", index=0)
|
||||
fcu.keyframe_points.insert(0, value=0).interpolation = 'LINEAR'
|
||||
fcu.keyframe_points.insert(10, value=0).interpolation = 'LINEAR'
|
||||
track.strips.new("top_strip", 0, action_top)
|
||||
@@ -744,12 +776,13 @@ class NlaInsertTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
base_action = bpy.data.actions["action_base"]
|
||||
channelbag = _first_channelbag(base_action)
|
||||
# Location X should not have been able to insert a keyframe because the top strip is overriding the result completely,
|
||||
# making it impossible to calculate which value should be inserted.
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=0).keyframe_points), 2)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=0).keyframe_points), 2)
|
||||
# Location Y and Z will go through since they have not been defined in the action of the top strip.
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=1).keyframe_points), 1)
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=2).keyframe_points), 1)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=1).keyframe_points), 1)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=2).keyframe_points), 1)
|
||||
|
||||
def test_insert_additive(self):
|
||||
nla_anim_object = _create_nla_anim_object()
|
||||
@@ -778,7 +811,8 @@ class NlaInsertTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert()
|
||||
|
||||
# Check that the expected F-Curves exist.
|
||||
fcurves_actual = {(f.data_path, f.array_index) for f in base_action.fcurves}
|
||||
channelbag = _first_channelbag(base_action)
|
||||
fcurves_actual = {(f.data_path, f.array_index) for f in channelbag.fcurves}
|
||||
fcurves_expect = {
|
||||
("location", 0),
|
||||
("location", 1),
|
||||
@@ -788,14 +822,14 @@ class NlaInsertTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
|
||||
# This should have added keys to Y and Z but not X.
|
||||
# X already had two keys from the file setup.
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=0).keyframe_points), 2)
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=1).keyframe_points), 1)
|
||||
self.assertEqual(len(base_action.fcurves.find("location", index=2).keyframe_points), 1)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=0).keyframe_points), 2)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=1).keyframe_points), 1)
|
||||
self.assertEqual(len(channelbag.fcurves.find("location", index=2).keyframe_points), 1)
|
||||
|
||||
# The keyframe value should not be changed even though the position of the
|
||||
# object is modified by the additive layer.
|
||||
self.assertAlmostEqual(nla_anim_object.location.x, 2.0, 8)
|
||||
fcurve_loc_x = base_action.fcurves.find("location", index=0)
|
||||
fcurve_loc_x = channelbag.fcurves.find("location", index=0)
|
||||
self.assertAlmostEqual(fcurve_loc_x.keyframe_points[-1].co[1], 1.0, 8)
|
||||
|
||||
|
||||
@@ -808,8 +842,8 @@ class KeyframeDeleteTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
self.assertTrue(armature.animation_data is not None)
|
||||
self.assertTrue(armature.animation_data.action is not None)
|
||||
action = armature.animation_data.action
|
||||
self.assertEqual(len(action.fcurves), 3)
|
||||
channelbag = _channelbag(armature)
|
||||
self.assertEqual(len(channelbag.fcurves), 3)
|
||||
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
@@ -817,22 +851,22 @@ class KeyframeDeleteTest(AbstractKeyframingTest, unittest.TestCase):
|
||||
bpy.context.scene.frame_set(5)
|
||||
bpy.ops.anim.keyframe_insert_by_name(type="Location")
|
||||
# This should have added new FCurves for the pose bone.
|
||||
self.assertEqual(len(action.fcurves), 6)
|
||||
self.assertEqual(len(channelbag.fcurves), 6)
|
||||
|
||||
bpy.ops.anim.keyframe_delete_v3d()
|
||||
# No Fcurves should yet be deleted.
|
||||
self.assertEqual(len(action.fcurves), 6)
|
||||
self.assertEqual(len(action.fcurves[0].keyframe_points), 1)
|
||||
self.assertEqual(len(channelbag.fcurves), 6)
|
||||
self.assertEqual(len(channelbag.fcurves[0].keyframe_points), 1)
|
||||
bpy.context.scene.frame_set(1)
|
||||
bpy.ops.anim.keyframe_delete_v3d()
|
||||
# This should leave the object level keyframes of the armature
|
||||
self.assertEqual(len(action.fcurves), 3)
|
||||
self.assertEqual(len(channelbag.fcurves), 3)
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
with bpy.context.temp_override(**_get_view3d_context()):
|
||||
bpy.ops.anim.keyframe_delete_v3d()
|
||||
# The last FCurves should be deleted from the object now.
|
||||
self.assertEqual(len(action.fcurves), 0)
|
||||
self.assertEqual(len(channelbag.fcurves), 0)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -35,7 +35,12 @@ class AbstractNlaStripTest(unittest.TestCase):
|
||||
self.nla_tracks = self.test_object.animation_data.nla_tracks
|
||||
|
||||
self.action = bpy.data.actions.new(name="ObjectAction")
|
||||
x_location_fcurve = self.action.fcurves.new(data_path="location", index=0, action_group="Object Transforms")
|
||||
slot = self.action.slots.new(self.test_object.id_type, self.test_object.name)
|
||||
layer = self.action.layers.new("Layer")
|
||||
strip = layer.strips.new(type="KEYFRAME")
|
||||
channelbag = strip.channelbags.new(slot)
|
||||
|
||||
x_location_fcurve = channelbag.fcurves.new(data_path="location", index=0, group_name="Object Transforms")
|
||||
for frame in range(1, 5):
|
||||
x_location_fcurve.keyframe_points.insert(frame, value=frame).interpolation = "CONSTANT"
|
||||
|
||||
|
||||
@@ -52,6 +52,12 @@ def _create_armature():
|
||||
return armature_obj
|
||||
|
||||
|
||||
def _first_channelbag(action: bpy.types.Action) -> bpy.types.ActionChannelbag:
|
||||
"""Return the first Channelbag of the Action."""
|
||||
assert isinstance(action, bpy.types.Action)
|
||||
return action.layers[0].strips[0].channelbags[0]
|
||||
|
||||
|
||||
class CreateAssetTest(unittest.TestCase):
|
||||
|
||||
_library_folder = None
|
||||
@@ -109,8 +115,9 @@ class CreateAssetTest(unittest.TestCase):
|
||||
# string_test is not here because it should not be keyed.
|
||||
}
|
||||
expected_pose_values.update(_BBONE_VALUES)
|
||||
self.assertEqual(len(pose_action.fcurves), 26)
|
||||
for fcurve in pose_action.fcurves:
|
||||
pose_channelbag = _first_channelbag(pose_action)
|
||||
self.assertEqual(len(pose_channelbag.fcurves), 26)
|
||||
for fcurve in pose_channelbag.fcurves:
|
||||
self.assertTrue(
|
||||
fcurve.data_path in expected_pose_values,
|
||||
"Only the selected bone should be in the pose asset")
|
||||
@@ -158,8 +165,9 @@ class CreateAssetTest(unittest.TestCase):
|
||||
# string_test is not here because it should not be keyed.
|
||||
}
|
||||
expected_pose_values.update(_BBONE_VALUES)
|
||||
self.assertEqual(len(pose_action.fcurves), 26)
|
||||
for fcurve in pose_action.fcurves:
|
||||
pose_channelbag = _first_channelbag(pose_action)
|
||||
self.assertEqual(len(pose_channelbag.fcurves), 26)
|
||||
for fcurve in pose_channelbag.fcurves:
|
||||
self.assertTrue(
|
||||
fcurve.data_path in expected_pose_values,
|
||||
"Only the selected bone should be in the pose asset")
|
||||
@@ -193,8 +201,9 @@ class CreateAssetTest(unittest.TestCase):
|
||||
# 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:
|
||||
pose_channelbag = _first_channelbag(pose_action)
|
||||
self.assertEqual(len(pose_channelbag.fcurves), 24)
|
||||
for fcurve in pose_channelbag.fcurves:
|
||||
self.assertTrue(
|
||||
fcurve.data_path in expected_pose_values,
|
||||
"Only the selected bone should be in the pose asset")
|
||||
|
||||
@@ -874,30 +874,31 @@ class USDImportTest(AbstractUSDTest):
|
||||
8 + i), 1.0, 2, "Unexpected weight for Elbow deform vert")
|
||||
|
||||
action = bpy.data.actions['Anim1']
|
||||
channelbag = action.layers[0].strips[0].channelbags[0]
|
||||
|
||||
# Verify the Elbow joint rotation animation.
|
||||
curve_path = 'pose.bones["Elbow"].rotation_quaternion'
|
||||
|
||||
# Quat W
|
||||
f = action.fcurves.find(curve_path, index=0)
|
||||
f = channelbag.fcurves.find(curve_path, index=0)
|
||||
self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion W curve")
|
||||
self.assertAlmostEqual(f.evaluate(0), 1.0, 2, "Unexpected value for rotation quaternion W curve at frame 0")
|
||||
self.assertAlmostEqual(f.evaluate(10), 0.707, 2, "Unexpected value for rotation quaternion W curve at frame 10")
|
||||
|
||||
# Quat X
|
||||
f = action.fcurves.find(curve_path, index=1)
|
||||
f = channelbag.fcurves.find(curve_path, index=1)
|
||||
self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion X curve")
|
||||
self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion X curve at frame 0")
|
||||
self.assertAlmostEqual(f.evaluate(10), 0.707, 2, "Unexpected value for rotation quaternion X curve at frame 10")
|
||||
|
||||
# Quat Y
|
||||
f = action.fcurves.find(curve_path, index=2)
|
||||
f = channelbag.fcurves.find(curve_path, index=2)
|
||||
self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion Y curve")
|
||||
self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion Y curve at frame 0")
|
||||
self.assertAlmostEqual(f.evaluate(10), 0.0, 2, "Unexpected value for rotation quaternion Y curve at frame 10")
|
||||
|
||||
# Quat Z
|
||||
f = action.fcurves.find(curve_path, index=3)
|
||||
f = channelbag.fcurves.find(curve_path, index=3)
|
||||
self.assertIsNotNone(f, "Couldn't find Elbow rotation quaternion Z curve")
|
||||
self.assertAlmostEqual(f.evaluate(0), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 0")
|
||||
self.assertAlmostEqual(f.evaluate(10), 0.0, 2, "Unexpected value for rotation quaternion Z curve at frame 10")
|
||||
|
||||
Reference in New Issue
Block a user