The review floor was a 5 cm slab centred 50 cm down, so the character floated 1.8 m above it. The floor is now 90 m x 90 m x 10 cm with its top face at Z 0 and the character measures Z 0..83 cm - feet on the plane. Two cubes only: A on the ground as the contact/scale reference, B held at 2 m so its cast shadow separates from the body for comparison. The scale-cubes camera was still aimed at the old three-cube row and now looks at both from the south. Screenshots are committed per the owner's instruction, and .gitignore no longer excludes them.
185 lines
8.7 KiB
Python
185 lines
8.7 KiB
Python
# Builds /Game/Test/ReviewMap — the visual-review scene — and saves it as a
|
|
# map asset. Run before the screenshot pass (Tools/render_screenshots.sh does
|
|
# this automatically).
|
|
#
|
|
# Scene: metric floor, key light, fill sky, sky atmosphere, MPFB character,
|
|
# the three 1 m scale cubes, and an AGigLineShotActor with the camera list.
|
|
#
|
|
# UnrealEditor-Cmd <uproject> -run=pythonscript -script=<repo>/Tools/ue_build_review_map.py \
|
|
# -nullrhi -unattended -nopause -stdout
|
|
#
|
|
# Do NOT load_map here: after a map load the commandlet's spawn context dies
|
|
# (every spawn_actor call returns None). Everything is spawned into the
|
|
# default world and saved as a new map asset.
|
|
|
|
import os
|
|
|
|
import unreal
|
|
|
|
RESULT = "/tmp/ue-review-map-result.txt"
|
|
MAP_PATH = "/Game/Test/ReviewMap"
|
|
lines = []
|
|
|
|
|
|
def log(msg):
|
|
lines.append(str(msg))
|
|
|
|
|
|
def spawn(cls, loc, label=None, scale=None, rot=None):
|
|
a = unreal.EditorLevelLibrary.spawn_actor_from_class(cls, loc)
|
|
if a is None:
|
|
log(f"spawn {cls.__name__} returned None")
|
|
return None
|
|
if rot is not None:
|
|
a.set_actor_rotation(rot, False)
|
|
if scale is not None:
|
|
a.set_actor_scale3d(scale)
|
|
if label:
|
|
a.set_actor_label(label)
|
|
return a
|
|
|
|
|
|
# --- scene -------------------------------------------------------------------
|
|
|
|
try:
|
|
# Neutral gray floor material (the scale-test mesh ships red)
|
|
floor_mat = None
|
|
if not unreal.EditorAssetLibrary.does_asset_exist("/Game/Test/M_ReviewFloor"):
|
|
floor_mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
|
|
"M_ReviewFloor", "/Game/Test", unreal.Material, unreal.MaterialFactoryNew())
|
|
n = unreal.MaterialEditingLibrary.create_material_expression(
|
|
floor_mat, unreal.MaterialExpressionConstant3Vector)
|
|
n.set_editor_property("constant", unreal.LinearColor(0.35, 0.35, 0.35, 1.0))
|
|
unreal.MaterialEditingLibrary.connect_material_property(
|
|
n, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
|
unreal.MaterialEditingLibrary.recompile_material(floor_mat)
|
|
unreal.EditorAssetLibrary.save_loaded_asset(floor_mat)
|
|
else:
|
|
floor_mat = unreal.EditorAssetLibrary.load_asset("/Game/Test/M_ReviewFloor")
|
|
|
|
floor_mesh = unreal.EditorAssetLibrary.load_asset("/Game/Test/ScaleTest/ScaleTest_A")
|
|
if floor_mesh and floor_mat:
|
|
# 1 m reference cube scaled to 90 m x 90 m x 0.1 m, top face at Z 0. The
|
|
# scale is applied on the actor, so Z is -5 cm (+/- 5 cm around centre).
|
|
floor = spawn(unreal.StaticMeshActor, unreal.Vector(0, 450, -5.0), "ReviewFloor",
|
|
scale=(90.0, 90.0, 0.1))
|
|
if floor:
|
|
floor.static_mesh_component.set_static_mesh(floor_mesh)
|
|
floor.static_mesh_component.set_material(0, floor_mat)
|
|
log("staged: floor (45 m plane, gray)")
|
|
else:
|
|
log("floor spawn failed")
|
|
|
|
if spawn(unreal.DirectionalLight, unreal.Vector(0, 0, 900), "RenderKeyLight",
|
|
rot=unreal.Rotator(pitch=-38.0, yaw=35.0, roll=0.0)):
|
|
log("staged: DirectionalLight")
|
|
if spawn(unreal.SkyLight, unreal.Vector(0, 0, 1200), "RenderFillSky"):
|
|
log("staged: SkyLight")
|
|
if spawn(unreal.SkyAtmosphere, unreal.Vector(0, 0, 0), "RenderSky"):
|
|
log("staged: SkyAtmosphere")
|
|
|
|
char_mesh = unreal.EditorAssetLibrary.load_asset("/Game/Test/Character/character")
|
|
if char_mesh:
|
|
ch = spawn(unreal.SkeletalMeshActor, unreal.Vector(0, 0, 0), "ReviewCharacter",
|
|
rot=unreal.Rotator(pitch=0.0, yaw=180.0, roll=0.0))
|
|
if ch:
|
|
comp = ch.skeletal_mesh_component
|
|
comp.set_skinned_asset_and_update(char_mesh)
|
|
# Root at the soles, not the world origin: the composes put feet on the
|
|
# floor's top face (Z = 0). If the skeletal root is not at the soles the
|
|
# character would float or sink, so report the bounds for the review.
|
|
if hasattr(ch, "set_actor_location"):
|
|
ch.set_actor_location(unreal.Vector(0, 0, 0), False, False)
|
|
mn, mx = (None, None)
|
|
try:
|
|
_origin, _box = ch.get_actor_bounds(False)
|
|
log(f"staged: MPFB character at origin, bounds z {_box.z:.1f} cm")
|
|
except Exception as exc: # noqa: BLE001
|
|
log(f"character bounds unavailable: {exc!r}")
|
|
log("staged: MPFB character at origin, facing -Y")
|
|
else:
|
|
log("MISSING: /Game/Test/Character/character")
|
|
|
|
# Two cubes only, on purpose: one resting on the floor gives the ground-contact
|
|
# and scale reference, one held at 2 m gives a clean cast shadow to compare
|
|
# against. Their positions are chosen so both are in the overview frame and
|
|
# neither overlaps the character.
|
|
cube_specs = (
|
|
("ScaleTest_A", -250.0, 600.0, 50.0, "on the ground: base at Z 0"),
|
|
("ScaleTest_B", 250.0, 600.0, 250.0, "floating at 2 m: casts a separation shadow"),
|
|
)
|
|
for name, x, y, z, why in cube_specs:
|
|
m = unreal.EditorAssetLibrary.load_asset(f"/Game/Test/ScaleTest/{name}")
|
|
if m:
|
|
c = spawn(unreal.StaticMeshActor, unreal.Vector(x, y, z), f"Cube_{name}")
|
|
if c:
|
|
c.static_mesh_component.set_static_mesh(m)
|
|
log(f"staged: {name} at ({x}, {y}, {z}) - {why}")
|
|
|
|
# Hero facade, if the import has run: two views a reviewer needs — straight on
|
|
# from the north sidewalk, and the oblique street view from further west.
|
|
hero = unreal.EditorAssetLibrary.load_asset("/Game/Hollywood/ElCapitan/SM_HeroFacade")
|
|
# The imported mesh spans Y -7..+1250 cm in local space (front plane at -7, the
|
|
# body behind it) and is centred on X. To put the front plane on the block's
|
|
# building line the actor goes at Y = building line + 7 cm.
|
|
hero_x = -12024.5 + 1302.0
|
|
hero_y = -1450.0 + 7.0
|
|
if hero:
|
|
actor = spawn(unreal.StaticMeshActor, unreal.Vector(hero_x, hero_y, 0.0), "Hero_Facade")
|
|
if actor:
|
|
actor.static_mesh_component.set_static_mesh(hero)
|
|
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.STATIC)
|
|
log("staged: hero facade at the measured building line")
|
|
else:
|
|
log("MISSING: /Game/Hollywood/ElCapitan/SM_HeroFacade (run Tools/ue_hero_facade_import.py)")
|
|
|
|
shots = spawn(unreal.GigLineShotActor, unreal.Vector(0, 0, 300), "ShotSequencer")
|
|
if shots:
|
|
shots.set_editor_property("shot_locations", [
|
|
unreal.Vector(700, -900, 620),
|
|
unreal.Vector(200, -300, 160),
|
|
unreal.Vector(0, -900, 200),
|
|
# first-person eye height, just south-east of the character: this is the
|
|
# view a player has at spawn, capsule and camera stack in frame
|
|
unreal.Vector(120, -260, 165),
|
|
# straight on from the north sidewalk at eye height. No camera may sit
|
|
# inside the facade body (Y -1443..+250 cm), so both stand north of the
|
|
# front plane and look south into the colonnade.
|
|
unreal.Vector(hero_x + 200, -3200, 420),
|
|
# oblique from the north-west, reading the colonnade in depth
|
|
unreal.Vector(hero_x - 2200, -3400, 620),
|
|
])
|
|
shots.set_editor_property("shot_rotations", [
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(700, -900, 620), unreal.Vector(0, 300, 80)),
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(200, -300, 160), unreal.Vector(0, 0, 85)),
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(0, -900, 200), unreal.Vector(0, 600, 80)),
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(120, -260, 165), unreal.Vector(-200, 600, 90)),
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(hero_x + 200, -3200, 420), unreal.Vector(hero_x, hero_y + 400, 760)),
|
|
unreal.MathLibrary.find_look_at_rotation(
|
|
unreal.Vector(hero_x - 2200, -3400, 620), unreal.Vector(hero_x + 300, hero_y, 700)),
|
|
])
|
|
shots.set_editor_property("shot_names",
|
|
["overview", "character", "scale-cubes", "first-person",
|
|
"hero-facade-front", "hero-facade-oblique"])
|
|
shots.set_editor_property("quit_when_done", True)
|
|
log("staged: ShotSequencer with 6 cameras")
|
|
except Exception as exc: # noqa
|
|
log(f"staging failed: {exc!r}")
|
|
|
|
# --- save --------------------------------------------------------------------
|
|
|
|
try:
|
|
world = unreal.EditorLevelLibrary.get_editor_world()
|
|
saved = unreal.EditorLoadingAndSavingUtils.save_map(world, MAP_PATH)
|
|
log(f"save_map {MAP_PATH}: {saved}")
|
|
except Exception as exc: # noqa
|
|
log(f"save_map failed: {exc!r}")
|
|
|
|
with open(RESULT, "w") as f:
|
|
f.write("\n".join(lines) + "\n")
|