128 lines
5.7 KiB
Python
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)
|