mirror of
https://github.com/ConsistentlyInconsistentYT/Pixeltovoxelprojector.git
synced 2025-11-19 14:56:35 +00:00
93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Watch-dog that restarts Blender until every entry in metadata.json
|
||
is "rendered": true. Now hardened against path-quoting issues and
|
||
third-party add-ons crashing on start-up.
|
||
"""
|
||
|
||
import json, os, subprocess, sys, tempfile, time
|
||
from pathlib import Path
|
||
import textwrap, json as _json # for safe string literal
|
||
|
||
# ─────────────────────────── USER SETTINGS ────────────────────────────
|
||
BLENDER_EXE = r"C:\Program Files\Blender Foundation\Blender 3.6\blender.exe"
|
||
BLEND_FILE = r"C:\Users\youruser\Desktop\birdge.blend"
|
||
JOB_SCRIPT = r"C:\Users\youruser\Desktop\birdge.py"
|
||
|
||
OUTPUT_DIR = r"C:\Users\Rhoady\Desktop\pet projects\relcams40"
|
||
META_PATH = Path(OUTPUT_DIR) / "metadata.json"
|
||
|
||
CRASH_SLEEP = 5.0 # seconds between retries
|
||
# ───────────────────────────────────────────────────────────────────────
|
||
|
||
# ----------------------------------------------------------------------
|
||
def all_frames_done(meta_file: Path) -> bool:
|
||
if not meta_file.exists():
|
||
return False
|
||
with meta_file.open("r") as fh:
|
||
return all(m.get("rendered") for m in json.load(fh))
|
||
|
||
# ----------------------------------------------------------------------
|
||
def make_wrapper() -> Path:
|
||
wrapper = Path(tempfile.gettempdir()) / "run_render_job_once.py"
|
||
|
||
safe_job = _json.dumps(JOB_SCRIPT) # proper quoting
|
||
safe_json = _json.dumps(str(META_PATH)) # ← new
|
||
|
||
wrapper.write_text(textwrap.dedent(f"""
|
||
import importlib.util, types, pathlib, sys, os
|
||
|
||
# ---- import the render job script ----------------------------------
|
||
job_path = pathlib.Path({safe_job})
|
||
spec = importlib.util.spec_from_file_location("job", job_path)
|
||
if spec and spec.loader:
|
||
job = importlib.util.module_from_spec(spec)
|
||
spec.loader.exec_module(job)
|
||
else: # very old Python fallback
|
||
job = types.ModuleType("job")
|
||
exec(job_path.read_text(), job.__dict__)
|
||
sys.modules["job"] = job
|
||
|
||
# ---- pick fresh vs. resume ----------------------------------------
|
||
meta_path = pathlib.Path({safe_json})
|
||
job.NEW_PHOTOS = not meta_path.exists() # True on first run only
|
||
print(f"[WRAPPER] NEW_PHOTOS = {{job.NEW_PHOTOS}}")
|
||
|
||
job.main()
|
||
"""))
|
||
|
||
return wrapper
|
||
|
||
# ----------------------------------------------------------------------
|
||
def launch_blender(wrapper: Path) -> int:
|
||
cmd = [
|
||
BLENDER_EXE,
|
||
"--factory-startup", # <-- disables all add-ons
|
||
"-b", BLEND_FILE,
|
||
"--python", str(wrapper)
|
||
]
|
||
return subprocess.run(cmd).returncode
|
||
|
||
# ----------------------------------------------------------------------
|
||
def main():
|
||
print("▶ Blender render watchdog started.")
|
||
wrapper = make_wrapper()
|
||
|
||
while True:
|
||
if all_frames_done(META_PATH):
|
||
print("✓ All frames already rendered – nothing to do. Exiting.")
|
||
return
|
||
|
||
print("→ Launching Blender …")
|
||
rc = launch_blender(wrapper)
|
||
|
||
if rc == 0 and all_frames_done(META_PATH):
|
||
print("✓ Job completed on this pass. Exiting.")
|
||
return
|
||
|
||
print(f"⚠ Blender exited (code {rc}). Will retry in {CRASH_SLEEP}s …")
|
||
time.sleep(CRASH_SLEEP)
|
||
|
||
# ----------------------------------------------------------------------
|
||
if __name__ == "__main__":
|
||
main()
|