diff --git a/blenderrestarteraftercrashing.py b/blenderrestarteraftercrashing.py new file mode 100644 index 0000000..40de3f1 --- /dev/null +++ b/blenderrestarteraftercrashing.py @@ -0,0 +1,93 @@ +#!/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()