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