Files
gig-line/Tools/ue_audit_scene.py
T
zwitschi 73aca9db80 feat(facades): 0.1.5 — hero detail, scene audit, material orphaning fixed (#32)
Merged PR #32: 0.1.5 — hero detail, scene audit, material orphaning fixed

Co-authored-by: Zwitschi <zwitschi82@gmail.com>
2026-11-08 22:56:08 +01:00

115 lines
3.7 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/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/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
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
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")