#!/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)