Files
PythonInUnreal/tools/run_workshop.py
T
2026-10-31 17:03:41 +01:00

128 lines
5.7 KiB
Python

"""Plain Python launcher. Does not import unreal; works on Windows and Linux."""
import argparse
from contextlib import nullcontext
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import uuid
from progress_display import ProgressDisplay
from console_links import file_link
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "Content/Python"))
from ue_workshop.paths import executable
def editor_path(explicit=None):
value = explicit or os.environ.get("WORKSHOP_UNREAL") or shutil.which("UnrealEditor")
if not value and os.name == "nt":
value = r"C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe"
if value and Path(value).is_file():
return str(Path(value).resolve())
raise FileNotFoundError("Ustaw WORKSHOP_UNREAL na plik UnrealEditor-Cmd.exe / UnrealEditor (UE 5.7).")
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("script", nargs="?", default="exercises/my_shot.py")
parser.add_argument("--editor", help="Pelna sciezka do UE 5.7")
parser.add_argument("--check", action="store_true", help="Sprawdz srodowisko bez otwierania UE")
parser.add_argument("--blender", action="store_true", help="Przy --check wymagaj tez Blendera")
parser.add_argument("--timeout", type=int, default=900)
parser.add_argument("--render", action="store_true", help="Renderuj zmienna shot ze skryptu, nawet gdy RENDER=False")
parser.add_argument("--single-frame", type=int, help="Renderuj tylko wskazana klatke (bez MP4)")
parser.add_argument("--verbose", action="store_true", help="Pokazuj pelne logi UE w konsoli")
parser.add_argument("--links", choices=("auto", "always", "never"), default="auto",
help="Klikalne sciezki w terminalu (domyslnie: auto)")
args = parser.parse_args(argv)
def link(path, stream=None):
return file_link(path, args.links, stream)
editor = editor_path(args.editor)
if args.check:
print("UE:", editor)
print("FFmpeg:", executable("ffmpeg"))
if args.blender:
print("Blender:", executable("blender"))
print("Projekt:", ROOT / "ExampleProject.uproject")
return 0
script = (ROOT / args.script).resolve()
if not script.is_file():
raise FileNotFoundError(script)
status = ROOT / "Saved/Workshop" / (uuid.uuid4().hex + ".json")
log_path = status.with_suffix(".log")
progress_path = status.with_suffix(".progress.jsonl")
status.parent.mkdir(parents=True, exist_ok=True)
env = dict(os.environ, WORKSHOP_SCRIPT=str(script), WORKSHOP_STATUS=str(status), WORKSHOP_BATCH="1")
env["WORKSHOP_PROGRESS"] = str(progress_path)
env["WORKSHOP_RENDER"] = "1" if args.render else "0"
env["WORKSHOP_SINGLE_FRAME"] = "" if args.single_frame is None else str(args.single_frame)
command = [editor, str(ROOT / "ExampleProject.uproject"),
"-ExecutePythonScript=" + (ROOT / "tools/ue_bootstrap.py").as_posix(),
"-RenderOffscreen", "-unattended", "-nosplash", "-nosound", "-stdout",
"-FullStdOutLogOutput", "-NoLoadingScreen"]
if args.verbose:
# UE writes its own log when console output is streamed directly.
command.append("-abslog=" + log_path.as_posix())
print("Uruchamiam:", script, flush=True)
progress = ProgressDisplay(progress_path)
try:
# Keep native stderr (including third-party plugins) out of the terminal too.
with nullcontext(None) if args.verbose else log_path.open("wb") as output:
with subprocess.Popen(command, env=env, cwd=ROOT, stdout=output,
stderr=subprocess.STDOUT) as process:
deadline = time.monotonic() + args.timeout
try:
while True:
progress.update()
remaining = deadline - time.monotonic()
if remaining <= 0:
raise subprocess.TimeoutExpired(command, args.timeout)
try:
exit_code = process.wait(timeout=min(0.5, remaining))
except subprocess.TimeoutExpired:
continue
progress.update()
break
finally:
if process.poll() is None:
process.kill()
process.wait()
except subprocess.TimeoutExpired:
print(f"Przekroczono limit czasu UE. Log: {link(log_path, sys.stderr)}", file=sys.stderr)
return 1
if not status.is_file():
print(f"UE nie zapisalo wyniku (kod {exit_code}). Log: {link(log_path, sys.stderr)}", file=sys.stderr)
return 1
try:
result = json.loads(status.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
print(f"Nie mozna odczytac wyniku: {exc}. Log: {link(log_path, sys.stderr)}", file=sys.stderr)
return 1
result["editor_exit_code"] = exit_code
result["log"] = str(log_path)
status.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
success = exit_code == 0 and result.get("success")
if success:
print("Gotowe:", link(result.get("video") or result.get("output") or script))
else:
print("Blad:", result.get("error") or f"UE zakonczyl prace z kodem {exit_code}.", file=sys.stderr)
print("Log:", link(log_path))
if args.verbose:
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0 if success else 1
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, ValueError) as exc:
print(str(exc), file=sys.stderr)
raise SystemExit(1)