"""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", "-nocef"] 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)