51 lines
2.8 KiB
Python
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')
|