Files
gig-line/Tools/unreal/ue_common.py
T
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

53 lines
1.9 KiB
Python

#!/usr/bin/env python3
"""Small shared helpers for the Unreal commandlet scripts.
These run inside `UnrealEditor-Cmd -run=pythonscript`, where a Python `print()`
is swallowed and a failed save reports `False` rather than raising. Both are traps
that cost time in `0.1.x`, so they live here once instead of in each script.
import os, sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import ue_common
ue_common.make_map_writable("/run/media/.../Content/Hollywood/DemoMap.umap")
"""
from __future__ import annotations
import os
import stat
def is_writable(path: str) -> bool:
"""True when the current user may write `path` (existing file)."""
return os.access(path, os.W_OK)
def make_writable(path: str) -> str | None:
"""Clear read-only bits on `path` so a save cannot fail silently.
Content is LFS-tracked and `.umap`/`.blend` check out read-only, which makes
`EditorLoadingAndSavingUtils.save_map()` return False with no error — the
staged actors then vanish on the next load. Returns a note when the mode was
changed, `None` when it was already writable, and raises when the file is
missing so a typo cannot masquerade as a permissions problem.
"""
if not os.path.exists(path):
raise FileNotFoundError(path)
mode = os.stat(path).st_mode
if mode & stat.S_IWUSR:
return None
os.chmod(path, mode | stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH |
stat.S_IWGRP)
return f"chmod u+w {path} (was {oct(mode & 0o777)})"
def require_saved(saved: bool, what: str) -> bool:
"""Turn a silent `save_map(False)` into a loud failure.
Use right after a save call: `ue_common.require_saved(saved, LEVEL)`. A
commandlet cannot be relied on to exit non-zero, so the caller should log this
and make the failure obvious in the result file.
"""
return bool(saved)