basic advanced example
This commit is contained in:
1 parent
6fe57915e8
commit
bd8edf5afb
29 files changed
+1501
-38
No files matched your search
@@ -7,6 +7,11 @@ import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "Content/Python"))
|
||||
from ue_workshop.animation_clip import AnimationClip
|
||||
|
||||
|
||||
class KnownClip(AnimationClip):
|
||||
WALK = ("walk", 2.0)
|
||||
|
||||
|
||||
class Clip:
|
||||
@@ -17,6 +22,12 @@ class Clip:
|
||||
assert name == "skeleton"
|
||||
return self.skeleton
|
||||
|
||||
def get_path_name(self):
|
||||
return "OriginalClip"
|
||||
|
||||
def get_play_length(self):
|
||||
return 2.0
|
||||
|
||||
|
||||
class AnimationTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -35,6 +46,10 @@ class AnimationTests(unittest.TestCase):
|
||||
self.shot.asset = MagicMock()
|
||||
self.shot.asset.get_tick_resolution.return_value = SimpleNamespace(numerator=24000, denominator=1)
|
||||
self.shot._animations = {}
|
||||
self.shot._transforms = {}
|
||||
self.shot._root_motion = {}
|
||||
self.shot._root_motion_copies = {}
|
||||
self.shot._pose_holds = {}
|
||||
self.shot.bind = MagicMock()
|
||||
self.actor = MagicMock()
|
||||
self.actor.get_path_name.return_value = "Parent"
|
||||
@@ -44,7 +59,7 @@ class AnimationTests(unittest.TestCase):
|
||||
self.clip = Clip(self.skeleton)
|
||||
self.imported = SimpleNamespace(mesh=MagicMock(), animations={"walk": self.clip})
|
||||
self.imported.mesh.get_editor_property.return_value = self.skeleton
|
||||
self.shot.scene = SimpleNamespace(_skeletal_models={"Parent": (self.child, self.imported)})
|
||||
self.shot.scene = SimpleNamespace(name="TestScene", _skeletal_models={"Parent": (self.child, self.imported)})
|
||||
|
||||
def test_adjacent_clips_share_child_track_and_set_speed(self):
|
||||
self.shot.animation(self.actor, "walk", start=0, end=48)
|
||||
@@ -55,6 +70,24 @@ class AnimationTests(unittest.TestCase):
|
||||
track.add_section.return_value.set_range.assert_called_with(48, 120)
|
||||
self.unreal.MovieSceneTimeWarpVariant.return_value.set_fixed_play_rate.assert_called_with(0.8)
|
||||
|
||||
def test_enum_plays_once_when_end_is_omitted(self):
|
||||
section = self.shot.animation(self.actor, KnownClip.WALK, start=10)
|
||||
section.set_range.assert_called_with(10, 58)
|
||||
section.get_editor_property.return_value.set_editor_property.assert_any_call("animation", self.clip)
|
||||
|
||||
def test_enum_automatic_end_respects_speed(self):
|
||||
section = self.shot.animation(self.actor, KnownClip.WALK, start=10, speed=2)
|
||||
section.set_range.assert_called_with(10, 34)
|
||||
|
||||
def test_explicit_end_overrides_enum_duration(self):
|
||||
section = self.shot.animation(self.actor, KnownClip.WALK, start=0, end=120)
|
||||
section.set_range.assert_called_with(0, 120)
|
||||
|
||||
def test_enum_cannot_exceed_sequence_length(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.shot.animation(self.actor, KnownClip.WALK, start=100)
|
||||
self.shot.bind.assert_not_called()
|
||||
|
||||
def test_nested_clip_is_rejected_without_creating_section(self):
|
||||
self.shot.animation(self.actor, "walk", start=0, end=48)
|
||||
with self.assertRaises(ValueError):
|
||||
@@ -64,11 +97,62 @@ class AnimationTests(unittest.TestCase):
|
||||
def test_invalid_ranges_and_speeds_do_not_create_track(self):
|
||||
for options in ({"start": -1}, {"end": 121}, {"start": 48, "end": 48},
|
||||
{"start": True}, {"speed": 0}, {"speed": float("nan")},
|
||||
{"speed": float("inf")}):
|
||||
{"speed": float("inf")}, {"keep_end_position": 1}, {"reverse": 1},
|
||||
{"keep_end_pose": 1}):
|
||||
with self.subTest(options=options), self.assertRaises(ValueError):
|
||||
self.shot.animation(self.actor, "walk", **options)
|
||||
self.shot.bind.assert_not_called()
|
||||
|
||||
def test_reverse_is_set_on_native_animation_parameters(self):
|
||||
section = self.shot.animation(self.actor, "walk", start=0, end=60, reverse=True)
|
||||
section.get_editor_property.return_value.set_editor_property.assert_any_call("reverse", True)
|
||||
|
||||
def test_keep_end_pose_uses_native_keep_state(self):
|
||||
main, hold = MagicMock(), MagicMock()
|
||||
self.shot.bind.return_value.add_track.return_value.add_section.side_effect = [main, hold]
|
||||
section = self.shot.animation(self.actor, KnownClip.WALK, start=10, keep_end_pose=True)
|
||||
section.set_completion_mode.assert_called_once_with(self.unreal.MovieSceneCompletionMode.KEEP_STATE)
|
||||
section.set_range.assert_called_with(10, 58)
|
||||
hold.set_range.assert_called_with(58, 120)
|
||||
self.unreal.MovieSceneTimeWarpVariant.return_value.set_fixed_play_rate.assert_called_with(0.0)
|
||||
|
||||
def test_later_clip_truncates_frozen_pose(self):
|
||||
main, hold, next_section = MagicMock(), MagicMock(), MagicMock()
|
||||
self.shot.bind.return_value.add_track.return_value.add_section.side_effect = [main, hold, next_section]
|
||||
self.shot.animation(self.actor, KnownClip.WALK, start=0, keep_end_pose=True)
|
||||
self.shot.animation(self.actor, KnownClip.WALK, start=60)
|
||||
hold.set_range.assert_called_with(48, 60)
|
||||
|
||||
def test_relative_move_replaces_baked_keys_and_offsets_future_motion(self):
|
||||
axes = [MagicMock() for _ in range(3)]
|
||||
future_keys = []
|
||||
for axis in axes:
|
||||
interior, future = MagicMock(), MagicMock()
|
||||
interior.get_time.return_value.frame_number.value = 90
|
||||
future.get_time.return_value.frame_number.value = 150
|
||||
future.get_value.return_value = 100
|
||||
axis.get_keys.return_value = [interior, future]
|
||||
future_keys.append(future)
|
||||
self.shot._transforms["Parent"] = axes
|
||||
self.shot.position = MagicMock(side_effect=[(0, 300, 0), (0, 350, 0)])
|
||||
self.shot.transform_key = MagicMock()
|
||||
self.assertEqual(self.shot.move(self.actor, 80, 110, offset=(0, -200, 0)), (0, 100, 0))
|
||||
for axis in axes:
|
||||
axis.remove_key.assert_called_once_with(axis.get_keys.return_value[0])
|
||||
future_keys[1].set_value.assert_called_once_with(-150)
|
||||
|
||||
def test_keep_end_position_uses_copy_and_adds_transform_motion(self):
|
||||
copy = Clip(self.skeleton)
|
||||
prepare = MagicMock(return_value=(copy, ["poses"], 2.0))
|
||||
self.shot._keep_end_position = MagicMock()
|
||||
with patch.dict(sys.modules, {"ue_workshop.root_motion": SimpleNamespace(prepare=prepare)}):
|
||||
section = self.shot.animation(self.actor, "walk", start=0, end=48, keep_end_position=True)
|
||||
self.shot.animation(self.actor, "walk", start=60, end=108, keep_end_position=True)
|
||||
prepare.assert_called_once()
|
||||
self.assertIs(self.imported.animations["walk"], self.clip)
|
||||
section.get_editor_property.return_value.set_editor_property.assert_any_call("animation", copy)
|
||||
self.assertEqual(self.shot._keep_end_position.call_count, 2)
|
||||
|
||||
def test_crossfade_sets_complementary_weight_curves(self):
|
||||
track = self.shot.bind.return_value.add_track.return_value
|
||||
first, second = MagicMock(), MagicMock()
|
||||
@@ -143,3 +227,33 @@ class AnimationTests(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
self.shot.animation(self.actor, "walk")
|
||||
self.shot.bind.assert_not_called()
|
||||
|
||||
def test_position_uses_existing_sequencer_curves(self):
|
||||
axes = [MagicMock(), MagicMock(), MagicMock()]
|
||||
for channel, value in zip(axes, (10, 250, 3)):
|
||||
channel.evaluate_keys.return_value = [value]
|
||||
self.shot._transforms["Parent"] = axes
|
||||
self.assertEqual(self.shot.position(self.actor, 80), (10, 250, 3))
|
||||
self.shot.asset.make_range.assert_called_once_with(80, 1)
|
||||
|
||||
def test_position_without_track_uses_spawn_position(self):
|
||||
self.actor.get_actor_location.return_value.to_tuple.return_value = (30, -850, 0)
|
||||
self.assertEqual(self.shot.position(self.actor, 80), (30, -850, 0))
|
||||
|
||||
def test_relative_move_anchors_existing_position_and_adds_offset(self):
|
||||
self.shot.position = MagicMock(return_value=(10, 250, 3))
|
||||
self.shot.transform_key = MagicMock()
|
||||
target = self.shot.move(self.actor, start=80, end=110, offset=(0, 50, 0))
|
||||
self.assertEqual(target, (10, 300, 3))
|
||||
self.shot.position.assert_called_once_with(self.actor, 80)
|
||||
self.shot.transform_key.assert_any_call(self.actor, 80, location=(10, 250, 3), interpolation="auto")
|
||||
self.shot.transform_key.assert_any_call(self.actor, 110, location=(10, 300, 3), interpolation="auto")
|
||||
|
||||
def test_invalid_relative_move_does_not_write_keys(self):
|
||||
self.shot.transform_key = MagicMock()
|
||||
for options in ({"start": 80, "end": 80}, {"start": 80, "end": 120},
|
||||
{"start": 80, "end": 110, "offset": (0, float("nan"), 0)}):
|
||||
options.setdefault("offset", (0, 50, 0))
|
||||
with self.subTest(options=options), self.assertRaises(ValueError):
|
||||
self.shot.move(self.actor, **options)
|
||||
self.shot.transform_key.assert_not_called()
|
||||
Reference in new issue
Block a user