sync
This commit is contained in:
1 parent
5284a0f5df
commit
e523a5e1fe
2 files changed
+52
-12
No files matched your search
Binary file not shown.
@@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
Run this from Unreal's Output Log with::
|
Run this from Unreal's Output Log with::
|
||||||
|
|
||||||
exec(open(r".../Content/Python/generate_blueprint_turntable.py").read())
|
py ".../Content/Python/generate_blueprint_turntable.py" /Game/Blueprints/BP_MyAsset
|
||||||
generate_turntables("/Game/Blueprints/BP_MyAsset")
|
|
||||||
|
Append ``--render-now`` to start Movie Render Queue and
|
||||||
|
``--all-animations`` to render every AnimSequence using the Blueprint's
|
||||||
|
skeleton.
|
||||||
|
|
||||||
The script creates/saves a small presentation map, so the bindings in the
|
The script creates/saves a small presentation map, so the bindings in the
|
||||||
sequences and MRQ jobs are reproducible. Open any generated sequence in
|
sequences and MRQ jobs are reproducible. Open any generated sequence in
|
||||||
@@ -19,6 +22,7 @@ DEFAULT_STAGE_MAP = "/Game/Maps/TurntableStage"
|
|||||||
DEFAULT_RENDER_DIRECTORY = "{project_dir}/Saved/TurntableRenders"
|
DEFAULT_RENDER_DIRECTORY = "{project_dir}/Saved/TurntableRenders"
|
||||||
FPS = 30
|
FPS = 30
|
||||||
TURN_DURATION_SECONDS = 5.0
|
TURN_DURATION_SECONDS = 5.0
|
||||||
|
CAMERA_Z = 40.0
|
||||||
QUEUE_RENDER_IMMEDIATELY = False # True starts MRQ immediately after queueing.
|
QUEUE_RENDER_IMMEDIATELY = False # True starts MRQ immediately after queueing.
|
||||||
RENDER_ALL_SKELETON_ANIMATIONS = False
|
RENDER_ALL_SKELETON_ANIMATIONS = False
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ def _create_or_open_stage(stage_map):
|
|||||||
|
|
||||||
def _find_skeletal_components(actor):
|
def _find_skeletal_components(actor):
|
||||||
components = actor.get_components_by_class(unreal.SkeletalMeshComponent)
|
components = actor.get_components_by_class(unreal.SkeletalMeshComponent)
|
||||||
return [component for component in components if component.get_editor_property("skeletal_mesh")]
|
return [component for component in components if component.get_skeletal_mesh_asset()]
|
||||||
|
|
||||||
|
|
||||||
def _animations_set_on_blueprint(actor):
|
def _animations_set_on_blueprint(actor):
|
||||||
@@ -74,14 +78,15 @@ def _all_skeleton_animations(actor):
|
|||||||
"""Optional broad mode: render every AnimSequence sharing the actor skeleton."""
|
"""Optional broad mode: render every AnimSequence sharing the actor skeleton."""
|
||||||
skeletons = set()
|
skeletons = set()
|
||||||
for component in _find_skeletal_components(actor):
|
for component in _find_skeletal_components(actor):
|
||||||
mesh = component.get_editor_property("skeletal_mesh")
|
mesh = component.get_skeletal_mesh_asset()
|
||||||
skeleton = mesh.get_editor_property("skeleton") if mesh else None
|
skeleton = mesh.get_editor_property("skeleton") if mesh else None
|
||||||
if skeleton:
|
if skeleton:
|
||||||
skeletons.add(skeleton.get_path_name())
|
skeletons.add(skeleton.get_path_name())
|
||||||
|
|
||||||
animations = []
|
animations = []
|
||||||
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
||||||
for data in registry.get_assets_by_class("AnimSequence", True):
|
anim_sequence_class = unreal.TopLevelAssetPath("/Script/Engine", "AnimSequence")
|
||||||
|
for data in registry.get_assets_by_class(anim_sequence_class, True):
|
||||||
animation = data.get_asset()
|
animation = data.get_asset()
|
||||||
if animation and animation.get_editor_property("skeleton").get_path_name() in skeletons:
|
if animation and animation.get_editor_property("skeleton").get_path_name() in skeletons:
|
||||||
animations.append(animation)
|
animations.append(animation)
|
||||||
@@ -110,7 +115,8 @@ def _set_transform_keys(track, frames, start_location, start_rotation):
|
|||||||
|
|
||||||
def _add_camera(sequence, camera, frames):
|
def _add_camera(sequence, camera, frames):
|
||||||
camera_binding = sequence.add_possessable(camera)
|
camera_binding = sequence.add_possessable(camera)
|
||||||
cut_track = sequence.add_master_track(unreal.MovieSceneCameraCutTrack)
|
# UE 5.8 creates master tracks through MovieSceneSequence.add_track.
|
||||||
|
cut_track = sequence.add_track(unreal.MovieSceneCameraCutTrack)
|
||||||
cut = cut_track.add_section()
|
cut = cut_track.add_section()
|
||||||
cut.set_range(0, frames)
|
cut.set_range(0, frames)
|
||||||
# Camera cuts require a MovieSceneObjectBindingID, not the binding proxy.
|
# Camera cuts require a MovieSceneObjectBindingID, not the binding proxy.
|
||||||
@@ -120,7 +126,7 @@ def _add_camera(sequence, camera, frames):
|
|||||||
def _add_animation_track(sequence, actor, animation, frames):
|
def _add_animation_track(sequence, actor, animation, frames):
|
||||||
"""Bind the matching skeletal component and play its animation clip."""
|
"""Bind the matching skeletal component and play its animation clip."""
|
||||||
for component in _find_skeletal_components(actor):
|
for component in _find_skeletal_components(actor):
|
||||||
mesh = component.get_editor_property("skeletal_mesh")
|
mesh = component.get_skeletal_mesh_asset()
|
||||||
skeleton = mesh.get_editor_property("skeleton") if mesh else None
|
skeleton = mesh.get_editor_property("skeleton") if mesh else None
|
||||||
if skeleton != animation.get_editor_property("skeleton"):
|
if skeleton != animation.get_editor_property("skeleton"):
|
||||||
continue
|
continue
|
||||||
@@ -160,6 +166,10 @@ def _make_sequence(sequence_name, output_folder, actor, camera, animation=None):
|
|||||||
|
|
||||||
|
|
||||||
def _queue_render(sequence, stage_map, directory):
|
def _queue_render(sequence, stage_map, directory):
|
||||||
|
# Use a concrete directory rather than an MRQ format token, so its location
|
||||||
|
# is deterministic both in the queue UI and during PIE execution.
|
||||||
|
if directory == DEFAULT_RENDER_DIRECTORY:
|
||||||
|
directory = unreal.Paths.project_saved_dir().rstrip("/\\") + "/TurntableRenders"
|
||||||
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
|
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
|
||||||
queue = subsystem.get_queue()
|
queue = subsystem.get_queue()
|
||||||
job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
|
job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
|
||||||
@@ -171,10 +181,24 @@ def _queue_render(sequence, stage_map, directory):
|
|||||||
output.output_directory = unreal.DirectoryPath(directory)
|
output.output_directory = unreal.DirectoryPath(directory)
|
||||||
output.file_name_format = "{job_name}/{frame_number}"
|
output.file_name_format = "{job_name}/{frame_number}"
|
||||||
output.output_resolution = unreal.IntPoint(1920, 1080)
|
output.output_resolution = unreal.IntPoint(1920, 1080)
|
||||||
|
output.override_existing_output = True
|
||||||
|
# A queue constructed in Python has no implicit render pass. Without a
|
||||||
|
# Deferred Pass MRQ has no frames to submit or write.
|
||||||
|
config.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPassBase)
|
||||||
config.find_or_add_setting_by_class(unreal.MoviePipelineImageSequenceOutput_PNG)
|
config.find_or_add_setting_by_class(unreal.MoviePipelineImageSequenceOutput_PNG)
|
||||||
return subsystem
|
return subsystem
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_previous_turntable_jobs(bp_name):
|
||||||
|
"""Replace prior generated jobs without touching the user's unrelated MRQ jobs."""
|
||||||
|
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
|
||||||
|
queue = subsystem.get_queue()
|
||||||
|
prefix = "LS_TT_" + bp_name
|
||||||
|
for job in queue.get_jobs():
|
||||||
|
if job.job_name.startswith(prefix):
|
||||||
|
queue.delete_job(job)
|
||||||
|
|
||||||
|
|
||||||
def generate_turntables(blueprint_path, output_root=DEFAULT_OUTPUT_ROOT,
|
def generate_turntables(blueprint_path, output_root=DEFAULT_OUTPUT_ROOT,
|
||||||
stage_map=DEFAULT_STAGE_MAP, render_directory=DEFAULT_RENDER_DIRECTORY,
|
stage_map=DEFAULT_STAGE_MAP, render_directory=DEFAULT_RENDER_DIRECTORY,
|
||||||
render_immediately=QUEUE_RENDER_IMMEDIATELY,
|
render_immediately=QUEUE_RENDER_IMMEDIATELY,
|
||||||
@@ -208,12 +232,21 @@ def generate_turntables(blueprint_path, output_root=DEFAULT_OUTPUT_ROOT,
|
|||||||
actor.set_actor_label("TT_" + bp_name)
|
actor.set_actor_label("TT_" + bp_name)
|
||||||
origin, extent = actor.get_actor_bounds(False)
|
origin, extent = actor.get_actor_bounds(False)
|
||||||
distance = max(extent.x, extent.y, extent.z) * 3.0 + 150.0
|
distance = max(extent.x, extent.y, extent.z) * 3.0 + 150.0
|
||||||
|
camera_location = unreal.Vector(origin.x - distance, origin.y, CAMERA_Z)
|
||||||
|
camera_target = unreal.Vector(origin.x, origin.y, CAMERA_Z)
|
||||||
camera = unreal.EditorLevelLibrary.spawn_actor_from_class(
|
camera = unreal.EditorLevelLibrary.spawn_actor_from_class(
|
||||||
unreal.CineCameraActor, unreal.Vector(origin.x - distance, origin.y, origin.z), unreal.Rotator(0, 0, 0))
|
unreal.CineCameraActor, camera_location, unreal.Rotator(0, 0, 0))
|
||||||
camera.set_actor_label("TT_Camera_" + bp_name)
|
camera.set_actor_label("TT_Camera_" + bp_name)
|
||||||
camera.set_actor_rotation(
|
camera.set_actor_rotation(
|
||||||
unreal.MathLibrary.find_look_at_rotation(camera.get_actor_location(), origin), False)
|
unreal.MathLibrary.find_look_at_rotation(camera.get_actor_location(), camera_target), False)
|
||||||
camera.get_cine_camera_component().set_editor_property("current_focal_length", 50.0)
|
cine_camera = camera.get_cine_camera_component()
|
||||||
|
cine_camera.set_editor_property("current_focal_length", 50.0)
|
||||||
|
# A library turntable must stay sharp across the whole asset, not only on a
|
||||||
|
# small cinematic focal plane.
|
||||||
|
cine_camera.set_editor_property(
|
||||||
|
"focus_settings",
|
||||||
|
unreal.CameraFocusSettings(focus_method=unreal.CameraFocusMethod.DISABLE),
|
||||||
|
)
|
||||||
|
|
||||||
sequences = [_make_sequence("LS_TT_" + bp_name, folder, actor, camera)]
|
sequences = [_make_sequence("LS_TT_" + bp_name, folder, actor, camera)]
|
||||||
animations = _all_skeleton_animations(actor) if render_all_skeleton_animations else _animations_set_on_blueprint(actor)
|
animations = _all_skeleton_animations(actor) if render_all_skeleton_animations else _animations_set_on_blueprint(actor)
|
||||||
@@ -222,6 +255,7 @@ def generate_turntables(blueprint_path, output_root=DEFAULT_OUTPUT_ROOT,
|
|||||||
sequences.append(_make_sequence(name, folder, actor, camera, animation))
|
sequences.append(_make_sequence(name, folder, actor, camera, animation))
|
||||||
|
|
||||||
unreal.EditorLevelLibrary.save_current_level()
|
unreal.EditorLevelLibrary.save_current_level()
|
||||||
|
_remove_previous_turntable_jobs(bp_name)
|
||||||
subsystem = None
|
subsystem = None
|
||||||
for sequence in sequences:
|
for sequence in sequences:
|
||||||
subsystem = _queue_render(sequence, stage_map, render_directory)
|
subsystem = _queue_render(sequence, stage_map, render_directory)
|
||||||
@@ -233,7 +267,9 @@ def generate_turntables(blueprint_path, output_root=DEFAULT_OUTPUT_ROOT,
|
|||||||
|
|
||||||
|
|
||||||
def _run_from_console_arguments():
|
def _run_from_console_arguments():
|
||||||
"""Support `py "script.py" /Game/Path/BP_Name` in the Unreal console."""
|
"""Support a concise `py "script.py" /Game/Path/BP_Name [flags]` command."""
|
||||||
|
render_now = "--render-now" in sys.argv
|
||||||
|
all_animations = "--all-animations" in sys.argv
|
||||||
for argument in sys.argv[1:]:
|
for argument in sys.argv[1:]:
|
||||||
# Dragging an asset into the Output Log can produce
|
# Dragging an asset into the Output Log can produce
|
||||||
# /Script/Engine.Blueprint'/Game/Foo/BP_X.BP_X'; accept that form too.
|
# /Script/Engine.Blueprint'/Game/Foo/BP_X.BP_X'; accept that form too.
|
||||||
@@ -241,7 +277,11 @@ def _run_from_console_arguments():
|
|||||||
if game_index < 0:
|
if game_index < 0:
|
||||||
continue
|
continue
|
||||||
asset_path = argument[game_index:].split("'", 1)[0].split(".", 1)[0]
|
asset_path = argument[game_index:].split("'", 1)[0].split(".", 1)[0]
|
||||||
generate_turntables(asset_path)
|
generate_turntables(
|
||||||
|
asset_path,
|
||||||
|
render_immediately=render_now,
|
||||||
|
render_all_skeleton_animations=all_animations,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user