render-negative-estate-pilot.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738
  1. """Render actual exported pilot GLBs for offline asset review, not a browser screenshot."""
  2. import bpy, json, math
  3. from pathlib import Path
  4. from mathutils import Vector
  5. BASE = Path('.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/negative-estate-pilot').resolve()
  6. OUT = BASE/'offline-renders-v3'
  7. if OUT.exists(): raise RuntimeError('Use a fresh render directory')
  8. OUT.mkdir()
  9. plan = json.loads((BASE/'input-v2/seated-plan.json').read_text())
  10. manifest = json.loads((BASE/'export-v3/manifest.json').read_text())
  11. bpy.ops.wm.read_factory_settings(use_empty=True)
  12. for cell in manifest['cells']:
  13. bpy.ops.import_scene.gltf(filepath=str(BASE/'export-v3'/cell['lod0']['uri']))
  14. scene = bpy.context.scene
  15. scene.render.engine = 'CYCLES';scene.cycles.samples=16
  16. scene.render.resolution_x=512;scene.render.resolution_y=512;scene.render.resolution_percentage=100
  17. scene.render.image_settings.file_format='PNG'
  18. scene.world = bpy.data.worlds.new('Review world');scene.world.use_nodes=True
  19. scene.world.node_tree.nodes['Background'].inputs[0].default_value=(.65,.68,.72,1)
  20. scene.world.node_tree.nodes['Background'].inputs[1].default_value=.6
  21. scene.view_settings.view_transform='AgX'
  22. sun_data=bpy.data.lights.new('Review sun','SUN');sun_data.energy=2.5
  23. sun=bpy.data.objects.new('Review sun',sun_data);scene.collection.objects.link(sun);sun.rotation_euler=(math.radians(25),math.radians(-20),math.radians(-30))
  24. cam_data=bpy.data.cameras.new('Review camera');cam=bpy.data.objects.new('Review camera',cam_data);scene.collection.objects.link(cam);scene.camera=cam
  25. cam_data.type='ORTHO';cam_data.lens=50
  26. meshes = [o for o in scene.objects if o.type=='MESH']
  27. records=[]
  28. for building in plan['buildings']:
  29. target=next(o for o in meshes if o.name==building['id'])
  30. for o in meshes:o.hide_render=(o!=target)
  31. points=[target.matrix_world@Vector(c) for c in target.bound_box]
  32. low=Vector(tuple(min(p[i] for p in points) for i in range(3)));high=Vector(tuple(max(p[i] for p in points) for i in range(3)));center=(low+high)/2
  33. size=max(high-low);cam.location=center+Vector((1,-1,.85)).normalized()*size*3
  34. cam.rotation_euler=(center-cam.location).to_track_quat('-Z','Y').to_euler();cam_data.ortho_scale=size*1.5
  35. file=OUT/(building['id']+'.png');scene.render.filepath=str(file);bpy.ops.render.render(write_still=True)
  36. records.append({'id':building['id'],'image':str(file),'from':'export-v3 actual LOD0 glTF import','estimatedFloors':building['floors'],'estimatedHeightM':building['heightM']})
  37. (OUT/'manifest.json').write_text(json.dumps({'images':records,'browserUsed':False,'runtimeVisualAcceptance':False,'note':'Isolated authored asset view; world lighting differs from cockpit grade/fog, foundation shown below grade.'},indent=2),encoding='utf8')
  38. print('PILOT_REVIEW_RENDER',json.dumps({'images':len(records),'output':str(OUT)}))