The e2e asset check now completes: PASS on all six steps - empty maps 9 s, demo build 10 s, review build 9 s, captures 80 s, judge 388 s, review file. Four breakages from the folder move had to be fixed first: - ue_placement and ue_common are siblings of the scripts that import them, but the rewrite put only the tools root on sys.path, so every Unreal commandlet died with ModuleNotFoundError. Both paths are inserted now. - render_screenshots.sh resolved the repository root one level up, so commandlet paths became Tools/Tools/... and the capture step failed in 0 s. - the driver treated a result file left by an earlier run as success, which is how a ten-second 'demo build' passed without building. Result files are unlinked before each step and child output streams to a per-step log file rather than a pipe. - two editors on one project hang instead of failing, so the driver refuses to start while one is running. Also corrects ue_recreate_maps.py: LevelEditorSubsystem.new_level() refuses a path whose package is still in memory and delete_asset() leaves the file on disk, measured create->delete->create returning False. The step now loads each map, destroys every actor, saves, and reports the count it left (0), which is the property the builders actually need. Open question recorded in TODO.md: the rebuilt map holds 409 actors and 613 KB against 600 and 886 KB before, and the ~190 missing ones look like the facade-kit and theatre-trim placements that run as separate steps.
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Audit the DemoMap for staged actors that would render wrong.
|
|
|
|
Checks that are cheap and catch the failures this project actually had:
|
|
|
|
1. **No unmaterialed actors.** 604 actors once rendered with the default material
|
|
because the demo builder deleted and recreated the site materials, orphaning
|
|
every reference; the map looked merely "a bit flat".
|
|
2. **Placement spans.** Each verified frontage building is probed against
|
|
`Tools/unreal/ue_placement.py`; a building 13 m off its footprint still renders fine,
|
|
which is exactly why it needs a number, not an eye.
|
|
3. **No duplicate actor labels.** Two buildings' kit pieces sharing a label made
|
|
the outliner ambiguous and hid one of them.
|
|
|
|
UnrealEditor-Cmd <uproject> -run=pythonscript \
|
|
-script=Tools/unreal/ue_audit_scene.py -nullrhi -unattended -nopause -stdout
|
|
|
|
Result file: `/tmp/gigline/scene-audit.txt`. Exit code from the commandlet is
|
|
always 0, so **read the file** — `visual_loop.py --verify` does not depend on it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import collections
|
|
import os
|
|
import sys
|
|
|
|
import unreal
|
|
|
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.dirname(_HERE)) # tools root, for _common
|
|
sys.path.insert(0, _HERE) # siblings in this folder (ue_common, ue_placement)
|
|
import ue_placement # noqa: E402
|
|
|
|
MAP = "/Game/Hollywood/DemoMap"
|
|
RESULT = "/tmp/gigline/scene-audit.txt"
|
|
|
|
lines: list[str] = []
|
|
failures = 0
|
|
|
|
|
|
def log(message: str) -> None:
|
|
lines.append(message)
|
|
print(message)
|
|
|
|
|
|
def fail(message: str) -> None:
|
|
global failures
|
|
failures += 1
|
|
log("FAIL " + message)
|
|
|
|
|
|
def main() -> None:
|
|
if not unreal.EditorLevelLibrary.load_level(MAP):
|
|
log(f"could not load {MAP}")
|
|
return
|
|
|
|
actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
|
|
|
# 1. materials
|
|
unmaterialed = []
|
|
for actor in actors:
|
|
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
|
|
if not comp or not comp.static_mesh:
|
|
continue
|
|
if comp.get_material(0) is None:
|
|
unmaterialed.append(actor.get_actor_label())
|
|
log(f"mesh actors: {sum(1 for a in actors if a.get_component_by_class(unreal.StaticMeshComponent))}")
|
|
if unmaterialed:
|
|
fail(f"{len(unmaterialed)} actor(s) with no material, e.g. {unmaterialed[:6]}")
|
|
else:
|
|
log("OK every staged mesh has a material")
|
|
|
|
# 2. placement spans
|
|
for actor in actors:
|
|
label = actor.get_actor_label()
|
|
key = None
|
|
if label.startswith("Frontage_SM_"):
|
|
name = label[len("Frontage_SM_"):]
|
|
key = {"Hollywood_6922": "6922", "Hollywood_6902": "6902",
|
|
"ElCapitanTheatre": "6838", "Budget_6822": "6822",
|
|
"Hollywood_6806": "6806", "LeeDrug_6800": "6800"}.get(name)
|
|
elif label == ue_placement.HERO_ACTOR:
|
|
key = "hero"
|
|
if not key:
|
|
continue
|
|
origin, extent = actor.get_actor_bounds(False)
|
|
lo, hi = origin.x - extent.x, origin.x + extent.x
|
|
problem = ue_placement.describe_span_failure(key, lo, hi)
|
|
if problem:
|
|
fail(problem)
|
|
else:
|
|
log(f"OK {key}: X {lo:.0f}..{hi:.0f}")
|
|
|
|
# 3. duplicate labels
|
|
counts = collections.Counter(a.get_actor_label() for a in actors)
|
|
duplicates = {k: v for k, v in counts.items() if v > 1}
|
|
if duplicates:
|
|
sample = list(duplicates.items())[:6]
|
|
fail(f"{len(duplicates)} duplicated actor label(s), e.g. {sample}")
|
|
else:
|
|
log("OK no duplicate actor labels")
|
|
|
|
log(f"== {failures} failure(s) ==")
|
|
|
|
|
|
try:
|
|
main()
|
|
except Exception as exc: # noqa: BLE001 - surface in the result file
|
|
import traceback
|
|
log(f"FAILED: {type(exc).__name__}: {exc}")
|
|
log(traceback.format_exc())
|
|
|
|
os.makedirs(os.path.dirname(RESULT), exist_ok=True)
|
|
with open(RESULT, "w", encoding="utf-8") as handle:
|
|
handle.write("\n".join(lines) + "\n")
|