Files
sunrise/scripts/verify-summit-blender.py

51 lines
2.8 KiB
Python

"""Round-trip the eight GLBs in a disposable Blender scene; preserve the authoring scene."""
import bpy
import json
from pathlib import Path
from mathutils import Vector
root = Path('D:/project/sunrise')
manifest = json.loads((root/'public/models/summit/manifest.json').read_text(encoding='utf-8'))
expected = json.loads((root/'assets/summit/verification.json').read_text(encoding='utf-8'))
expected = {a['id']:a for a in expected['assets']}
original_scene = bpy.context.window.scene
report = {'blenderVersion':bpy.app.version_string,'checks':'passed','assets':[]}
check_scene = bpy.data.scenes.new('Temporary GLB round-trip validation')
try:
bpy.context.window.scene = check_scene
with bpy.context.temp_override(scene=check_scene,view_layer=check_scene.view_layers[0]):
for asset in manifest['assets']:
bpy.ops.import_scene.gltf(filepath=str(root/'public/models/summit'/asset['file']))
objects = list(check_scene.objects)
roots = [obj for obj in objects if obj.get('assetId') == asset['id']]
assert len(roots)==1, asset['id']
assert roots[0].location.length < 1e-5
meshes = [obj for obj in objects if obj.type=='MESH']
assert len(meshes)==expected[asset['id']]['meshes']
bpy.context.view_layer.update()
corners = [obj.matrix_world @ Vector(p) for obj in meshes for p in obj.bound_box]
lower = [min(p[i] for p in corners) for i in range(3)]
upper = [max(p[i] for p in corners) for i in range(3)]
# Blender X,Y,Z -> GLB X,Y,Z = X,Z,-Y.
actual_min = [lower[0],lower[2],-upper[1]]
actual_max = [upper[0],upper[2],-lower[1]]
target = expected[asset['id']]['bounds']
assert max(abs(a-b) for a,b in zip(actual_min,target['min'])) < .001
assert max(abs(a-b) for a,b in zip(actual_max,target['max'])) < .001
assert all(obj.data.materials for obj in meshes)
report['assets'].append({'id':asset['id'],'meshes':len(meshes),'boundsMatchThreeWithinMetres':.001})
imported_materials = {mat for obj in meshes for mat in obj.data.materials if mat}
for obj in objects:
data = obj.data if obj.type=='MESH' else None
bpy.data.objects.remove(obj,do_unlink=True)
if data is not None and data.users==0:
bpy.data.meshes.remove(data)
for mat in imported_materials:
if mat.users==0:
bpy.data.materials.remove(mat)
finally:
bpy.context.window.scene = original_scene
bpy.data.scenes.remove(check_scene)
(root/'assets/summit/blender-roundtrip.json').write_text(json.dumps(report,indent=2),encoding='utf-8')
print('PASS: all 8 GLBs re-imported; bounds match Three.js within 1 mm; authoring scene restored')