#!/usr/bin/env python3 import hashlib import json import pathlib import sys import bpy def render_state(render_path): scene = bpy.context.scene cube = bpy.data.objects.get("WBV2P5001RenderCube") camera = bpy.data.objects.get("WBV2P5001RenderCamera") receipt_text = bpy.data.texts.get("WBV2-P5-001-RenderRoutingReceipt") if cube is None or camera is None or receipt_text is None: raise RuntimeError("render routing fixture is missing cube, camera, or receipt") receipt = json.loads(receipt_text.as_string()) scene.render.filepath = str(render_path) bpy.ops.render.render(write_still=True) image = bpy.data.images.load(str(render_path), check_existing=False) if tuple(image.size) != (64, 64): raise RuntimeError("render routing fixture did not produce a 64x64 image") pixels = image.pixels[:] rgba = bytes(max(0, min(255, round(float(value) * 255))) for value in pixels) visible = sum(1 for index in range(0, len(rgba), 4) if sum(rgba[index:index + 3]) > 12) result = { "sceneId": "scene:" + scene.name, "cubeId": "object:" + cube.name, "cameraId": "object:" + camera.name, "renderEngine": scene.render.engine, "resolution": [scene.render.resolution_x, scene.render.resolution_y, scene.render.resolution_percentage], "receipt": receipt, "pixelSha256": hashlib.sha256(rgba).hexdigest(), "pixelBytes": len(rgba), "visiblePixels": visible, } bpy.data.images.remove(image) return result def main(): arguments = sys.argv[sys.argv.index("--") + 1 :] if len(arguments) != 2: raise SystemExit("usage: blender -b --factory-startup --python check-wbv2-p5-001-desktop.py -- FIXTURE REPORT") fixture, output = (pathlib.Path(value).resolve() for value in arguments) bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False) render_path = fixture.with_name(f"{fixture.stem}-render.png") before = render_state(render_path) temporary = fixture.with_name(f"{fixture.stem}-reopen.blend") try: bpy.ops.wm.save_as_mainfile(filepath=str(temporary), check_existing=False, compress=True) bpy.ops.wm.open_mainfile(filepath=str(temporary), load_ui=False) after = render_state(render_path) finally: temporary.unlink(missing_ok=True) render_path.unlink(missing_ok=True) if before != after: raise RuntimeError(f"render routing save/reopen drift: {before} != {after}") report = { "schemaVersion": 1, "task": "WBV2-P5-001", "operation": "RENDER_ROUTING_DESKTOP", "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "state": after, "saveReopen": "EXACT", "blenderVersion": bpy.app.version_string, } output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8") print("wbv2-p5-001-desktop-ok engine=%s pixels=%s visible=%d routes=%d saveReopen=exact" % (after["renderEngine"], after["pixelSha256"], after["visiblePixels"], len(after["receipt"]["routes"]))) if __name__ == "__main__": main()