"""Render the 12 tutorial material/model combinations with desktop Blender. Usage: build_blender_native/bin/blender --background --factory-startup \ --python tools/web/generate-pbr-tutorial-renders.py -- \ web/app/public/tutorial/pbr/studio.blend \ web/app/public/assets/pbr-library/pbr-library.blend \ web/app/public/tutorial/pbr/renders """ import hashlib import json import pathlib import sys import bpy MODELS = [ ("PBR Suzanne", "suzanne"), ("PBR Cube", "cube"), ("PBR Torus", "torus"), ] MATERIALS = [ ("PBR Metal Brushed", "metal-brushed"), ("PBR Glass", "glass"), ("PBR Wood", "wood"), ("PBR Emission Cyan", "emission-cyan"), ] def sha256_file(path): digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def append_library(blend_path): with bpy.data.libraries.load(str(blend_path), link=False) as (data_from, data_to): data_to.objects = list(data_from.objects) data_to.materials = list(data_from.materials) data_to.images = list(data_from.images) return data_to.objects, data_to.materials def main(studio_path, library_path, renders_dir): studio = pathlib.Path(studio_path).resolve() library = pathlib.Path(library_path).resolve() out = pathlib.Path(renders_dir).resolve() out.mkdir(parents=True, exist_ok=True) bpy.ops.wm.open_mainfile(filepath=str(studio)) scene = bpy.context.scene objects, materials = append_library(library) material_by_name = {material.name: material for material in materials if material} model_by_name = {} for obj in objects: if obj is None: continue scene.collection.objects.link(obj) model_by_name[obj.name] = obj obj.hide_render = True scene.render.engine = "BLENDER_EEVEE" scene.render.resolution_x = 1024 scene.render.resolution_y = 1024 scene.render.resolution_percentage = 100 scene.render.image_settings.file_format = "PNG" scene.render.image_settings.color_mode = "RGBA" scene.render.image_settings.color_depth = "8" scene.render.film_transparent = False scene.render.dither_intensity = 0 scene.eevee.taa_render_samples = 64 scene.view_settings.look = "None" manifest = [] for model_name, model_slug in MODELS: model = model_by_name[model_name] for material_name, material_slug in MATERIALS: for other in model_by_name.values(): other.hide_render = other is not model material = material_by_name.get(material_name) if material is None: raise RuntimeError(f"missing material {material_name}") model.data.materials.clear() model.data.materials.append(material) png = out / f"{model_slug}-{material_slug}.png" jpg = out / f"{model_slug}-{material_slug}.jpg" scene.render.filepath = str(png) bpy.ops.render.render(write_still=True) scene.render.image_settings.file_format = "JPEG" scene.render.image_settings.color_mode = "RGB" scene.render.image_settings.quality = 90 scene.render.filepath = str(jpg) bpy.ops.render.render(write_still=True) scene.render.image_settings.file_format = "PNG" scene.render.image_settings.color_mode = "RGBA" manifest.append({ "model": model_name, "modelSlug": model_slug, "material": material_name, "materialSlug": material_slug, "png": str(png.relative_to(pathlib.Path(__file__).resolve().parents[2])).replace("\\", "/"), "jpg": str(jpg.relative_to(pathlib.Path(__file__).resolve().parents[2])).replace("\\", "/"), "width": 1024, "height": 1024, "bytes": png.stat().st_size, "sha256": sha256_file(png), }) (out / "manifest.json").write_text( json.dumps({ "schemaVersion": 1, "renderer": "DESKTOP_BLENDER_EEVEE", "blenderVersion": ".".join(str(value) for value in bpy.app.version), "renders": manifest, }, indent=2, ensure_ascii=True) + "\n", encoding="utf-8", newline="\n", ) print(json.dumps({"renders": len(manifest), "output": str(out)})) if __name__ == "__main__": arguments = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] if len(arguments) != 3: raise SystemExit("usage: blender --background --factory-startup --python generate-pbr-tutorial-renders.py -- STUDIO LIBRARY RENDERS") main(*arguments)