Files
workinf_Blender_Wasm/tools/web/generate-pbr-tutorial-renders.py
mes123456 d5dd647881
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): finish RE 5000PBR lesson video and fix non-ASCII GLB import
- Add 11:07 Chinese-narrated lesson video, VTT, poster and 13 chapter screenshots.
- Regenerate 20-render WebGL2 matrix and 15 operation captures on RTX 4060.
- Fix WASM staging for non-ASCII import filenames (猴头.glb); rebuild web_engine.
- Harden lesson video recording pipeline; fix stale lesson-link E2E selector.
- Add RE PBR derived libraries, lesson page, evidence and status report.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-12 21:49:24 +08:00

131 lines
4.7 KiB
Python

"""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)