112 lines
3.4 KiB
Python
112 lines
3.4 KiB
Python
"""
|
|
nuke_runner.py — Launch Nuke in terminal mode and stream its output.
|
|
|
|
Nuke is started ONCE with:
|
|
Nuke.exe -t process.py jobs.json
|
|
|
|
stdout/stderr are merged and forwarded line-by-line via Qt signals so the
|
|
GUI log window updates in real time. Progress is inferred from the
|
|
structured output lines that process.py prints.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import subprocess
|
|
|
|
from PySide6.QtCore import QThread, Signal
|
|
|
|
# Lines emitted by process.py that carry progress information:
|
|
# [3/10] Rendering: UNG_101_012_010_BG01_TT_v001
|
|
# [3/10] Done: UNG_101_012_010_BG01_TT_v001
|
|
_RENDERING_RE = re.compile(r"^\[(\d+)/(\d+)\]\s+Rendering:\s+(.+)$")
|
|
_DONE_RE = re.compile(r"^\[(\d+)/(\d+)\]\s+Done:\s+(.+)$")
|
|
|
|
|
|
class NukeRunner(QThread):
|
|
"""Runs a single Nuke process and emits its output as Qt signals.
|
|
|
|
Signals
|
|
-------
|
|
log_message(str)
|
|
Each non-empty output line from Nuke.
|
|
progress_update(int, int)
|
|
(jobs_done, jobs_total) whenever a job completes.
|
|
job_started(str)
|
|
basename of the job Nuke has just started rendering.
|
|
job_done(str)
|
|
basename of the job Nuke has just finished.
|
|
finished_signal(bool)
|
|
True if Nuke exited with code 0, False otherwise.
|
|
"""
|
|
|
|
log_message: Signal = Signal(str)
|
|
progress_update: Signal = Signal(int, int)
|
|
job_started: Signal = Signal(str)
|
|
job_done: Signal = Signal(str)
|
|
finished_signal: Signal = Signal(bool)
|
|
|
|
def __init__(
|
|
self,
|
|
nuke_exe: str,
|
|
process_script: str,
|
|
jobs_file: str,
|
|
total: int = 0,
|
|
parent=None,
|
|
) -> None:
|
|
super().__init__(parent)
|
|
self.nuke_exe = nuke_exe
|
|
self.process_script = process_script
|
|
self.jobs_file = jobs_file
|
|
self.total = total
|
|
|
|
def set_total(self, total: int) -> None:
|
|
self.total = total
|
|
|
|
# ── Thread entry point ────────────────────────────────────────────
|
|
|
|
def run(self) -> None:
|
|
cmd = [self.nuke_exe, "--indie", "-t", self.process_script, self.jobs_file]
|
|
self.log_message.emit("Launching: " + " ".join(cmd))
|
|
|
|
try:
|
|
proc = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
except FileNotFoundError:
|
|
self.log_message.emit(
|
|
f"ERROR: Nuke executable not found: {self.nuke_exe}"
|
|
)
|
|
self.finished_signal.emit(False)
|
|
return
|
|
except OSError as exc:
|
|
self.log_message.emit(f"ERROR: Could not launch Nuke: {exc}")
|
|
self.finished_signal.emit(False)
|
|
return
|
|
|
|
for raw_line in proc.stdout:
|
|
line = raw_line.rstrip()
|
|
if not line:
|
|
continue
|
|
|
|
self.log_message.emit(line)
|
|
|
|
m = _DONE_RE.match(line)
|
|
if m:
|
|
current, total, basename = int(m.group(1)), int(m.group(2)), m.group(3)
|
|
self.total = total
|
|
self.progress_update.emit(current, total)
|
|
self.job_done.emit(basename.strip())
|
|
continue
|
|
|
|
m = _RENDERING_RE.match(line)
|
|
if m:
|
|
basename = m.group(3)
|
|
self.job_started.emit(basename.strip())
|
|
|
|
proc.wait()
|
|
self.finished_signal.emit(proc.returncode == 0)
|