Files
gig-line/Tools/unreal/ue_vehicle_probe_map.py
zwitschi 4d27109668 refactor(tools): group the sixty scripts by what they talk to
One directory with sixty scripts stopped being navigable, and the grouping is also the sandbox boundary:

  Tools/unreal/    commandlets and the two map builders, with ue_common.py and ue_placement.py as the
                   shared imports (they need the engine)
  Tools/blender/   builders, exporters, the FBX preset, the MPFB character pipeline, blender_screenshots
                   (they need the Blender Flatpak)
  Tools/review/    the visual loop: render_screenshots.sh, judge_screenshot.py, hero_compare.py,
                   build_comparison_md.py, e2e_asset_check.py, hero_iterate.sh, visual_loop.py,
                   visual_targets.json
  Tools/gen/       image and texture generation: the ComfyUI driver, deterministic texture writers,
                   reference fetching
  Tools/input/     OS-level input injection
  root             _common.py, check-lfs.sh, addon_compat_probe.py, sibling_asset_audit.py, tests/

Every moved script now resolves the repository root as ../.. and puts the tools root plus its siblings on
sys.path, so 'import ue_common' still works from unreal/. The test suite bootstrap does the same for the five
folders. render_screenshots.sh takes the folder as a third argument to run_cmdlet, and the e2e driver,
hero_iterate.sh and the comparison generator were repointed.

51 README rows now link to their new location, the README gains a Layout section naming each folder's
sandbox, and the architecture notes and Blender README follow the moves.

Verified: 108 tests pass, prettier is clean, judge_screenshot.py and hero_compare.py run from their new
homes, and the e2e run was aborted mid-way (it had been rebuilding an empty DemoMap for 15 minutes) and is
being rerun against the new layout.
2026-11-10 12:40:30 +01:00

112 lines
4.2 KiB
Python

"""Add one piece of demo content to ReviewMap and save it as a vehicle probe level.
GIGLINE_PROBE_ASSET=Terrain_DEM [GIGLINE_PROBE_NOCOLLISION=1] <engine>/UnrealEditor-Cmd <uproject> \
-run=pythonscript -script=<repo>/Tools/unreal/ue_vehicle_probe_map.py -nullrhi -unattended -nopause
Why: the Chaos pawn falls correctly on ReviewMap's empty floor and is thrown upward everywhere in
DemoMap. Rather than guess which of DemoMap's 593 staged meshes is responsible, add them one group
at a time to the map that works and watch for the moment the pawn stops falling. With no
GIGLINE_PROBE_ASSET set, the level is a plain copy of ReviewMap - the control.
Assets are looked up as /Game/Hollywood/Site/<name> first, then /Game/Hollywood/Buildings/<name>.
Result: /tmp/gigline/vehicle-probe.txt
"""
import os
import unreal
RESULT = "/tmp/gigline/vehicle-probe.txt"
TARGET = "/Game/Test/VehicleProbe"
SOURCE = "/Game/Test/ReviewMap"
SEARCH = ("/Game/Hollywood/Site", "/Game/Hollywood/Buildings",
"/Game/Hollywood/Streets", "/Game/Hollywood/Props")
lines = []
def log(message):
lines.append(str(message))
unreal.log(str(message))
def find_asset(name):
for folder in SEARCH:
path = f"{folder}/{name}"
if unreal.EditorAssetLibrary.does_asset_exist(path):
return path
return None
def main():
wanted = os.environ.get("GIGLINE_PROBE_ASSET", "").strip()
if not unreal.EditorAssetLibrary.does_asset_exist(SOURCE):
log(f"missing source level {SOURCE}")
lines.append("VERDICT: FAIL")
return
target = TARGET
# A level that the previous run left behind cannot always be deleted while the editor holds it,
# so fall back to a suffixed name rather than failing the probe.
for suffix in ("", "_b", "_c", "_d"):
target = TARGET + suffix
if not unreal.EditorAssetLibrary.does_asset_exist(target):
break
unreal.EditorAssetLibrary.delete_asset(target)
if not unreal.EditorAssetLibrary.does_asset_exist(target):
break
if not unreal.EditorAssetLibrary.duplicate_asset(SOURCE, target):
log(f"could not duplicate {SOURCE} -> {TARGET}")
lines.append("VERDICT: FAIL")
return
log(f"duplicated {SOURCE} -> {target}")
unreal.EditorLevelLibrary.load_level(target)
if wanted:
path = find_asset(wanted)
if path is None:
log(f"MISSING asset {wanted} in {SEARCH}")
lines.append("VERDICT: FAIL")
return
mesh = unreal.EditorAssetLibrary.load_asset(path)
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(
unreal.StaticMeshActor, unreal.Vector(0.0, 0.0, 0.0))
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
comp.set_static_mesh(mesh)
actor.set_actor_label(f"Probe_{wanted}")
log(f"staged probe mesh {path} at the origin")
# GIGLINE_PROBE_NOCOLLISION=1 stages the same mesh with no collision at all, which separates
# "this mesh's collision ejects physics bodies" from "this mesh does something else".
if os.environ.get("GIGLINE_PROBE_NOCOLLISION", "").strip() == "1":
comp.set_collision_enabled(unreal.CollisionEnabled.NO_COLLISION)
log("probe mesh collision disabled")
else:
log("control run: no extra content")
vehicle_class = unreal.load_class(None, "/Script/GigLine.GigLineVehicle")
if vehicle_class:
pawn = unreal.EditorLevelLibrary.spawn_actor_from_class(
vehicle_class, unreal.Vector(600.0, 0.0, 300.0))
pawn.set_actor_label("Probe_Vehicle")
log("staged the Chaos pawn at (600, 0, 300)")
else:
log("vehicle class not found")
if unreal.EditorLevelLibrary.save_current_level():
log(f"saved {target}")
lines.append(f"LEVEL: {target}")
lines.append("VERDICT: PASS")
else:
log(f"save failed for {target}")
lines.append("VERDICT: FAIL")
try:
main()
except Exception as exc: # noqa: BLE001
import traceback
log(f"FAILED: {type(exc).__name__}: {exc}")
log(traceback.format_exc())
lines.append("VERDICT: FAIL")
with open(RESULT, "w") as f:
f.write("\n".join(lines) + "\n")