"""Export the district kit to the runtime GLB (positions + indices only). blender -b assets/blender/districts/nightdrive-districts.blend --python assets/blender/districts/export-districts.py [-- --preview out.png] Normals are derived per fragment at runtime (flat shading from screen-space derivatives), so the GLB carries no normals, UVs or materials and shared box corners stay welded. Writes provenance next to this script. """ import bpy, json, hashlib, sys, math from pathlib import Path here = Path(__file__).resolve().parent root = here.parents[2] scene = bpy.data.scenes['Roadside Districts'] output = root / 'src/assets/nightdrive-districts.glb' for obj in scene.objects: obj.select_set(True) bpy.ops.export_scene.gltf( filepath=str(output), export_format='GLB', use_selection=True, use_active_scene=True, export_apply=True, export_normals=False, export_texcoords=False, export_materials='NONE', export_animations=False, export_cameras=False, export_lights=False, export_yup=True, ) triangles = 0 for obj in scene.objects: obj.data.calc_loop_triangles() triangles += len(obj.data.loop_triangles) provenance = { 'asset': 'Nightdrive roadside districts', 'source': 'nightdrive-districts.blend', 'authorship': 'Original procedural modelling in Blender for this portfolio (author-districts.py); no external assets.', 'editable_scene': 'Roadside Districts', 'pieces': sorted({o.name.rsplit('_', 1)[0] for o in scene.objects}), 'triangles': triangles, 'attributes': 'POSITION + indices; flat normals derived at runtime', 'export': '../../../src/assets/nightdrive-districts.glb', 'sha256': hashlib.sha256(output.read_bytes()).hexdigest(), } (here / 'nightdrive-districts.asset.json').write_text(json.dumps(provenance, indent=2) + '\n') print(json.dumps(provenance)) if '--preview' in sys.argv: target = sys.argv[sys.argv.index('--preview') + 1] names = provenance['pieces'] for i, name in enumerate(names): for suffix in ('body', 'light'): obj = scene.objects[f'{name}_{suffix}'] obj.location = ((i % 4) * 34 - 50, (i // 4) * 40, 0) mat = bpy.data.materials.new(f'preview-{suffix}') mat.diffuse_color = (0.05, 0.05, 0.08, 1) if suffix == 'body' else (0.1, 0.9, 1.0, 1) obj.data.materials.append(mat) cam = bpy.data.objects.new('preview-cam', bpy.data.cameras.new('preview-cam')) scene.collection.objects.link(cam) cam.location = (95, -95, 70) cam.rotation_euler = (math.radians(58), 0, math.radians(45)) cam.data.lens = 30 scene.camera = cam try: scene.render.engine = 'BLENDER_WORKBENCH' except TypeError as e: print(e) scene.display.shading.color_type = 'MATERIAL' scene.display.shading.light = 'STUDIO' scene.display.shading.show_cavity = True scene.render.resolution_x, scene.render.resolution_y = 1600, 1000 scene.render.filepath = target bpy.ops.render.render(write_still=True)