basic advanced example
This commit is contained in:
1 parent
6fe57915e8
commit
bd8edf5afb
29 files changed
+1501
-38
No files matched your search
@@ -0,0 +1,34 @@
|
||||
"""Validate offline catalog timings against UE and play enum clips with automatic ends."""
|
||||
import math
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting
|
||||
from ue_workshop.animations import FighterAnimations, CreatureAnimations
|
||||
from ue_workshop.characters import FIGHTER_MODEL, CREATURE_MODEL
|
||||
from ue_workshop.skeletal import import_model
|
||||
|
||||
for model, name, catalog in ((FIGHTER_MODEL, "Fighter", FighterAnimations),
|
||||
(CREATURE_MODEL, "Creature", CreatureAnimations)):
|
||||
imported = import_model(model, name=name, reimport=False)
|
||||
for clip in catalog:
|
||||
assert math.isclose(clip.seconds, imported.animations[clip.key].get_play_length(),
|
||||
rel_tol=0, abs_tol=0.000001), clip.key
|
||||
|
||||
scene = Scene("CatalogSmoke")
|
||||
fighter = scene.skeletal_model(FIGHTER_MODEL, name="Fighter", rotation=(0, 0, 180))
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene, location=(450, -550, 300), fov=45)
|
||||
shot = Sequence(scene, fps=24, frames=120)
|
||||
shot.camera(cam)
|
||||
draw = FighterAnimations.DRAW_A_GREAT_SWORD_1
|
||||
attack = FighterAnimations.GREAT_SWORD_ATTACK
|
||||
first = shot.animation(fighter, draw, start=0)
|
||||
second_start = draw.frames(shot.fps) - 6
|
||||
second = shot.animation(fighter, attack, start=second_start)
|
||||
fast = shot.animation(fighter, attack, start=60, speed=2)
|
||||
assert first.get_end_frame() == draw.frames(shot.fps)
|
||||
assert second.get_end_frame() == second_start + attack.frames(shot.fps)
|
||||
assert fast.get_end_frame() == 60 + attack.frames(shot.fps, speed=2)
|
||||
assert second.get_editor_property("params").animation.get_name() == attack.key
|
||||
shot.save(open_editor=False)
|
||||
renderer = Renderer(width=640, height=360, samples=1, warmup=0, mp4=False)
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Import a model under a student name and verify its enum is generated automatically."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from ue_workshop import Scene, Sequence
|
||||
from ue_workshop import animation_catalog, skeletal
|
||||
from ue_workshop.characters import FIGHTER_MODEL, _clips
|
||||
from ue_workshop.paths import PROJECT
|
||||
|
||||
scene = Scene("AutomaticCatalogSmoke")
|
||||
clips = _clips(FIGHTER_MODEL)
|
||||
output = PROJECT / "build/AutomaticCatalogSmoke/animations.py"
|
||||
update = animation_catalog.update_catalog
|
||||
with patch.object(skeletal, "update_catalog", side_effect=lambda name, data, **options:
|
||||
update(name, data, output=output, **options)):
|
||||
actor = scene.skeletal_model(
|
||||
FIGHTER_MODEL, name="StudentCatalogSmoke", reimport=False,
|
||||
animations={key: clips[key] for key in ("great_sword_idle", "great_sword_attack")})
|
||||
|
||||
namespace = {"__name__": "ue_workshop._generated_smoke", "__package__": "ue_workshop"}
|
||||
exec(compile(output.read_text(encoding="utf-8"), str(output), "exec"), namespace)
|
||||
catalog = namespace["StudentCatalogSmokeAnimations"]
|
||||
assert len(catalog) == 2
|
||||
attack = catalog.GREAT_SWORD_ATTACK
|
||||
assert attack.seconds > 0
|
||||
shot = Sequence(scene, frames=120)
|
||||
section = shot.animation(actor, attack, start=0)
|
||||
assert section.get_end_frame() == attack.frames(shot.fps)
|
||||
shot.save(open_editor=False)
|
||||
|
||||
# Check the normal hook updates the real existing catalog and preserves models.
|
||||
skeletal.import_model(FIGHTER_MODEL, name="Fighter", reimport=False)
|
||||
skeletal.import_model(PROJECT / "source_assets/character/creature/Maw J Laygo.fbx", name="Creature", reimport=False)
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Check native pose retention and render a frame after the dying clip."""
|
||||
|
||||
import unreal
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting
|
||||
from ue_workshop.animations import CreatureAnimations as C
|
||||
from ue_workshop.characters import CREATURE_MODEL
|
||||
|
||||
scene = Scene("KeepPoseSmoke")
|
||||
actor = scene.skeletal_model(CREATURE_MODEL, name="Creature", height=300)
|
||||
child, imported = scene._skeletal_models[actor.get_path_name()]
|
||||
component = child.skeletal_mesh_component
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene, location=(500, -650, 400), target=(0, 0, 80), fov=60)
|
||||
shot = Sequence(scene, fps=30, frames=300)
|
||||
shot.camera(cam)
|
||||
section = shot.animation(actor, C.MUTANT_DYING, start=0, keep_end_pose=True)
|
||||
assert section.get_completion_mode() == unreal.MovieSceneCompletionMode.KEEP_STATE
|
||||
shot.save(open_editor=False)
|
||||
clip = imported.animations[C.MUTANT_DYING.key]
|
||||
changed = False
|
||||
for bone in unreal.AnimationLibrary.get_animation_track_names(clip):
|
||||
first = unreal.AnimationLibrary.get_bone_pose_for_time(clip, bone, 0, False)
|
||||
last = unreal.AnimationLibrary.get_bone_pose_for_time(clip, bone, clip.get_play_length(), False)
|
||||
if not first.rotation.equals(last.rotation, 0.001):
|
||||
changed = True
|
||||
break
|
||||
assert changed, "Dying source has no pose change"
|
||||
assert section.get_end_frame() < 299
|
||||
renderer = Renderer(width=640, height=360, samples=1, warmup=0, mp4=False)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Verify moving slash root travel becomes persistent parent Transform keys."""
|
||||
import json
|
||||
import math
|
||||
|
||||
import unreal
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting
|
||||
from ue_workshop.animations import FighterAnimations as F
|
||||
from ue_workshop.characters import FIGHTER_MODEL
|
||||
from ue_workshop.paths import PROJECT
|
||||
|
||||
scene = Scene("KeepEndSmoke")
|
||||
actor = scene.skeletal_model(FIGHTER_MODEL, name="Fighter", height=200)
|
||||
child, imported = scene._skeletal_models[actor.get_path_name()]
|
||||
original = imported.animations[F.GREAT_SWORD_SLASH_2.key]
|
||||
bone = child.skeletal_mesh_component.get_bone_name(0)
|
||||
before = unreal.AnimationLibrary.get_bone_pose_for_time(original, bone, original.get_play_length(), False)
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene, location=(650, -650, 450), target=(0, 150, 100), fov=55)
|
||||
shot = Sequence(scene, fps=30, frames=300)
|
||||
shot.camera(cam)
|
||||
section = shot.animation(actor, F.GREAT_SWORD_SLASH_2, start=57, keep_end_position=True)
|
||||
shot.animation(actor, F.GREAT_SWORD_SLASH_3, start=161)
|
||||
copy = section.get_editor_property("params").animation
|
||||
assert copy != original
|
||||
end = unreal.AnimationLibrary.get_bone_pose_for_time(copy, bone, copy.get_play_length(), False)
|
||||
start = unreal.AnimationLibrary.get_bone_pose_for_time(copy, bone, 0, False)
|
||||
assert abs(end.translation.y - start.translation.y) < 0.001
|
||||
after = unreal.AnimationLibrary.get_bone_pose_for_time(original, bone, original.get_play_length(), False)
|
||||
assert abs(after.translation.y - before.translation.y) < 0.001
|
||||
channels = shot._transforms[actor.get_path_name()]
|
||||
last = [channel.get_keys()[-1].get_value() for channel in channels[:3]]
|
||||
assert math.hypot(last[0], last[1]) > 100, last
|
||||
assert channels[0].get_keys()[-1].get_time().frame_number.value == section.get_end_frame()
|
||||
shot.save(open_editor=False)
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(shot.asset)
|
||||
positions = {}
|
||||
hips_positions = {}
|
||||
for frame in (57, 160, 161, 162, 163, 164, 190):
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.set_current_time(frame)
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.refresh_current_level_sequence()
|
||||
positions[frame] = actor.get_actor_location().to_tuple()
|
||||
hips_positions[frame] = child.skeletal_mesh_component.get_socket_location(bone).to_tuple()
|
||||
assert math.dist(positions[163], positions[190]) < 0.01, positions
|
||||
assert math.dist(positions[162], positions[163]) < 20, positions
|
||||
assert math.dist(hips_positions[162], hips_positions[163]) < 30, hips_positions
|
||||
assert abs((hips_positions[160][1] - hips_positions[57][1]) -
|
||||
(positions[160][1] - positions[57][1])) < 1, hips_positions
|
||||
(PROJECT / "Saved/Workshop/keep_end_position.json").write_text(json.dumps(positions, indent=2), encoding="utf-8")
|
||||
renderer = Renderer(width=640, height=360, samples=1, warmup=0, mp4=False)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Check every light type and render keyed color and intensity (frames 0 and 47)."""
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting, materials
|
||||
|
||||
scene = Scene("LightsSmoke")
|
||||
floor = materials.color("LightsFloor", rgb=(0.5, 0.5, 0.5))
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2), material=floor)
|
||||
scene.primitive("Sphere", name="Ball", location=(0, 0, 50), material=floor)
|
||||
sun = lighting.sun(scene, color=(1, 0.9, 0.8))
|
||||
point = lighting.point(scene, location=(-300, 200, 200), color=(0, 1, 0))
|
||||
spot = lighting.spot(scene, location=(0, -300, 400), target=(0, 0, 0))
|
||||
cam = camera.create(scene, location=(450, -550, 300), target=(0, 0, 50))
|
||||
shot = Sequence(scene, fps=24, frames=48)
|
||||
shot.camera(cam)
|
||||
shot.light_key(spot, 0, color=(1, 0, 0), intensity=5000)
|
||||
shot.light_key(spot, 47, color=(0, 0, 1), intensity=40000, interpolation="auto")
|
||||
shot.light_key(point, 0, intensity=0)
|
||||
shot.light_key(point, 47, intensity=20000)
|
||||
shot.light_key(sun, 0, intensity=0)
|
||||
shot.light_key(sun, 24, intensity=500)
|
||||
shot.light_key(sun, 25, intensity=0, interpolation="constant")
|
||||
shot.save(open_editor=False)
|
||||
assert len(shot.asset.get_bindings()) >= 2
|
||||
renderer = Renderer(width=640, height=360, samples=1, warmup=8, mp4=False)
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Verify position queries and relative movement after kept Mixamo root travel."""
|
||||
import math
|
||||
|
||||
from ue_workshop import Scene, Sequence
|
||||
from ue_workshop.animations import CreatureAnimations as C
|
||||
from ue_workshop.characters import CREATURE_MODEL
|
||||
|
||||
scene = Scene("RelativeMoveSmoke")
|
||||
creature = scene.skeletal_model(CREATURE_MODEL, name="Creature", height=300, location=(0, -850, 0))
|
||||
shot = Sequence(scene, fps=30, frames=180)
|
||||
shot.animation(creature, C.MUTANT_RUN, start=0, end=60, keep_end_position=True)
|
||||
shot.animation(creature, C.MUTANT_SWIPING, start=20)
|
||||
shot.animation(creature, C.MUTANT_WALKING, start=80, end=120)
|
||||
before = shot.position(creature, 80)
|
||||
assert math.dist(before, (0, -850, 0)) > 100, before
|
||||
target = shot.move(creature, start=80, end=120, offset=(0, 50, 0), interpolation="auto")
|
||||
assert math.dist(shot.position(creature, 80), before) < 0.001
|
||||
assert math.dist(shot.position(creature, 120), target) < 0.001
|
||||
assert abs(target[1] - before[1] - 50) < 0.001
|
||||
assert math.dist(shot.position(creature, 179), target) < 0.001
|
||||
middle = shot.position(creature, 100)
|
||||
assert before[1] < middle[1] < target[1]
|
||||
shot.save(open_editor=False)
|
||||
print("Relative movement from", before, "to", target)
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Reverse a walking clip and replace baked travel with an explicit retreat."""
|
||||
import math
|
||||
|
||||
from ue_workshop import Scene, Sequence
|
||||
from ue_workshop.animations import CreatureAnimations as C
|
||||
from ue_workshop.characters import CREATURE_MODEL
|
||||
|
||||
scene = Scene("ReverseWalkingSmoke")
|
||||
creature = scene.skeletal_model(CREATURE_MODEL, name="Creature", height=300, location=(0, -850, 0))
|
||||
shot = Sequence(scene, fps=30, frames=180)
|
||||
shot.animation(creature, C.MUTANT_RUN, start=0, end=60, keep_end_position=True)
|
||||
shot.animation(creature, C.MUTANT_SWIPING, start=20)
|
||||
walk = shot.animation(creature, C.MUTANT_WALKING, start=80, end=120,
|
||||
keep_end_position=True, reverse=True)
|
||||
assert walk.get_editor_property("params").get_editor_property("reverse")
|
||||
before = shot.position(creature, 80)
|
||||
natural_end = shot.position(creature, 120)
|
||||
assert natural_end[1] < before[1] - 10, (before, natural_end)
|
||||
# A later authored key should retain its relative movement after replacement.
|
||||
shot.transform_key(creature, 150, location=(natural_end[0], natural_end[1] + 30, natural_end[2]))
|
||||
target = shot.move(creature, start=80, end=120, offset=(0, -200, 0))
|
||||
assert math.dist(shot.position(creature, 80), before) < 0.001
|
||||
assert math.dist(shot.position(creature, 120), target) < 0.001
|
||||
assert abs(target[1] - before[1] + 200) < 0.001
|
||||
middle = shot.position(creature, 100)
|
||||
assert target[1] < middle[1] < before[1], middle
|
||||
assert abs(shot.position(creature, 150)[1] - target[1] - 30) < 0.001
|
||||
channels = shot._transforms[creature.get_path_name()]
|
||||
assert all(not 80 < key.get_time().frame_number.value < 120 for key in channels[1].get_keys())
|
||||
shot.save(open_editor=False)
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Optional refresh of bundled enums; other models are preserved. Imports update them automatically."""
|
||||
from ue_workshop.characters import FIGHTER_MODEL, CREATURE_MODEL
|
||||
from ue_workshop.skeletal import import_model
|
||||
|
||||
counts = {}
|
||||
for model, name in (
|
||||
(FIGHTER_MODEL, "Fighter"),
|
||||
(CREATURE_MODEL, "Creature"),
|
||||
):
|
||||
imported = import_model(model, name=name, reimport=False)
|
||||
counts[name + "Animations"] = len(imported.animations)
|
||||
print("Exported animation catalog:", counts)
|
||||
Reference in new issue
Block a user