Files
PythonInUnreal/tools/smoke_skeletal.py
T
2026-10-31 20:16:32 +01:00

70 lines
3.8 KiB
Python

"""Real UE check: both Mixamo skeletons, animation-only FBXs and Sequencer poses."""
import json
import unreal
from ue_workshop import Scene, Sequence, camera, lighting, Renderer
from ue_workshop.paths import PROJECT
scene = Scene("SkeletalSmoke")
base = PROJECT / "source_assets/character"
characters = []
for name, model, walk, idle, attack, position in (
("FighterSkeletalSmoke", "fighter/Maria WProp J J Ong.fbx", "fighter/great sword walk.fbx",
"fighter/great sword idle.fbx", "fighter/great sword attack.fbx", (-160, 0, 0)),
("CreatureSkeletalSmoke", "creature/Maw J Laygo.fbx", "creature/mutant walking.fbx",
"creature/mutant idle.fbx", "creature/mutant swiping.fbx", (160, 0, 0)),
):
actor = scene.skeletal_model(base / model, name=name, location=position,
animations={"walk": base / walk, "idle": base / idle, "attack": base / attack})
child, imported = scene._skeletal_models[actor.get_path_name()]
assert isinstance(child, unreal.SkeletalMeshActor)
assert isinstance(imported.mesh, unreal.SkeletalMesh)
for alias in ("walk", "idle", "attack"):
clip = imported.animations[alias]
assert clip.get_editor_property("skeleton") == imported.mesh.get_editor_property("skeleton")
assert clip.get_editor_property("sequence_length") > 0
if alias == "walk":
tracks = unreal.AnimationLibrary.get_animation_track_names(clip)
assert tracks, "Walk has no bone tracks"
poses_differ = False
for bone in tracks:
first = unreal.AnimationLibrary.get_bone_pose_for_frame(clip, bone, 0, False)
later = unreal.AnimationLibrary.get_bone_pose_for_frame(clip, bone, 10, False)
if not first.rotation.equals(later.rotation, 0.001):
poses_differ = True
break
assert poses_differ, "Walk has no changing bone rotations"
characters.append(actor)
if name == "FighterSkeletalSmoke":
# Bundled sources are FBX 6.1, too old for Blender's FBX importer.
# Export modern FBXs for the Blender round-trip fixture.
folder = PROJECT / "build/SkeletalFixture"
folder.mkdir(parents=True, exist_ok=True)
for obj, filename in ((imported.mesh, "fighter.fbx"), (imported.animations["walk"], "walk.fbx")):
task = unreal.AssetExportTask()
task.set_editor_property("object", obj)
task.set_editor_property("filename", str(folder / filename))
task.set_editor_property("automated", True)
task.set_editor_property("prompt", False)
task.set_editor_property("replace_identical", True)
task.set_editor_property("options", unreal.FbxExportOption())
assert unreal.Exporter.run_asset_export_task(task)
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
lighting.studio(scene)
cam = camera.create(scene, location=(600, -850, 400), target=(0, 0, 100), fov=55)
shot = Sequence(scene, frames=48)
shot.camera(cam)
for actor in characters:
first = shot.animation(actor, "walk", start=0, end=16)
second = shot.animation(actor, "idle", start=16, end=32, speed=0.75)
shot.animation(actor, "attack", start=32, end=48)
assert first.get_editor_property("params").animation is not None
assert abs(second.get_editor_property("params").play_rate.to_fixed_play_rate() - 0.75) < 1e-6
shot.transform_key(actor, 0, rotation=(0, 0, 0))
shot.transform_key(actor, 47, rotation=(0, 0, 90))
shot.save(open_editor=False)
summary = {"characters": [actor.get_actor_label() for actor in characters], "frames": shot.frames}
(PROJECT / "Saved/Workshop/skeletal_smoke.json").write_text(json.dumps(summary), encoding="utf-8")
renderer = Renderer(width=640, height=360, samples=1, warmup=0, mp4=False)