import math from functools import lru_cache import os from pathlib import Path import shutil import subprocess from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[1] def blender(): path = os.environ.get("BLENDER_BIN") or shutil.which("blender") local = Path("/Applications/Blender.app/Contents/MacOS/Blender") if not path and local.exists(): path = str(local) if not path: raise RuntimeError("Install Blender or set BLENDER_BIN to render refinement evidence.") return path @lru_cache(maxsize=1) def check_renderer(): subprocess.run([blender(), "--background", "--factory-startup", "--python-exit-code", "1", "--python-expr", "import numpy; import _cycles; import io_scene_gltf2.blender.imp.mesh; " "print('GLTF renderer ready; CPU denoising:', _cycles.with_openimagedenoise)"], check=True, timeout=60, capture_output=True, text=True) def render(model, folder, components=False): folder.mkdir(parents=True, exist_ok=True) command = [blender(), "--background", "--factory-startup", "--python-exit-code", "1", "--python", str(ROOT / "scripts/render_views.py"), "--", "--input", str(model), "--output", str(folder)] if components: command.append("--components") with (folder / "render.log").open("w") as log: subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, check=True, timeout=300) paths = sorted(folder.glob("*.png")) if not paths: raise RuntimeError("Blender produced no review images.") sheet = Image.new("RGB", (384 * 3, 410 * math.ceil(len(paths) / 3)), "#20252b") draw = ImageDraw.Draw(sheet) for index, path in enumerate(paths): x, y = index % 3 * 384, index // 3 * 410 with Image.open(path) as image: sheet.paste(image.convert("RGB"), (x, y + 26)) draw.text((x + 10, y + 7), path.stem, fill="white") sheet.save(folder / "contact.png") return folder / "contact.png"