Files
Portfolio/assets/blender/districts/export-districts.py

74 lines
3.3 KiB
Python

"""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'
# Retired in round 6: the overhead gantry and the sign truss now own the road,
# and the billboards' light strips read as garbled text. They stay authored in
# the .blend but are no longer exported.
RETIRED = {'gantry', 'billboard'}
exported = [o for o in scene.objects if o.name.rsplit('_', 1)[0] not in RETIRED]
for obj in scene.objects:
obj.select_set(obj in exported)
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 exported:
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 exported}),
'retired': sorted(RETIRED),
'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)