render-near-building.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. """Offline evidence preview using the actual authored generator (not browser acceptance)."""
  2. import ast
  3. import json
  4. import math
  5. import sys
  6. from pathlib import Path
  7. import bpy
  8. import bmesh
  9. from mathutils import Vector
  10. from mathutils.geometry import tessellate_polygon
  11. root = Path(__file__).resolve().parents[2]
  12. source = Path(__file__).with_name('assemble-cad-bim.py')
  13. module = ast.parse(source.read_text(encoding='utf-8'))
  14. functions = {'ring_area', 'as_ccw', 'prism_triangles', 'facade_window_count', 'poly_prism',
  15. 'prism_volume_sign', 'sourced_roof', 'apply_roof_uv'}
  16. textures = json.loads((root / 'tools/city-massing/city/cad-bim/near-estate-pilot-input/roof-textures.json').read_text())
  17. namespace = {'bpy': bpy, 'bmesh': bmesh, 'math': math, 'Vector': Vector,
  18. 'tessellate_polygon': tessellate_polygon, 'ARCHITECTURE': True, 'ROOF_TEXTURES': textures}
  19. exec(compile(ast.Module(body=[node for node in module.body if isinstance(node, ast.FunctionDef)
  20. and node.name in functions], type_ignores=[]), str(source), 'exec'), namespace)
  21. bpy.ops.object.select_all(action='SELECT')
  22. bpy.ops.object.delete(use_global=False)
  23. def material(name, color, roughness=0.7):
  24. m = bpy.data.materials.new(name)
  25. m.diffuse_color = (*color, 1)
  26. m.use_nodes = True
  27. shader = m.node_tree.nodes.get('Principled BSDF')
  28. shader.inputs['Base Color'].default_value = (*color, 1)
  29. shader.inputs['Roughness'].default_value = roughness
  30. return m
  31. wall = material('Preview_cream', (0.68, 0.65, 0.55))
  32. window = material('Preview_glass', (0.06, 0.17, 0.21), 0.25)
  33. door = material('Preview_storey', (0.20, 0.21, 0.22))
  34. roof = namespace['sourced_roof']('cad-20556', wall)
  35. plan = json.loads((root / 'tools/city-massing/city/cad-bim/near-estate-pilot-input/seated-plan.json').read_text())
  36. b = next(b for b in plan['buildings'] if b['id'] == 'cad-20556')
  37. obj = namespace['poly_prism'](namespace['as_ccw'](b['footprintENU']), b['foundationBottom'], b['positionENU'][2] + b['heightM'],
  38. wall, roof, b['positionENU'], b['floors'], 'lod0', window, door)
  39. bpy.context.collection.objects.link(obj)
  40. namespace['apply_roof_uv'](obj, b['id'])
  41. # UVs baked using original ENU coordinates before recentering only for this offline preview.
  42. obj.location -= Vector(b['positionENU'])
  43. scene = bpy.context.scene
  44. scene.render.engine = 'CYCLES'
  45. scene.cycles.samples = 32
  46. scene.render.resolution_x = 1280
  47. scene.render.resolution_y = 800
  48. scene.render.resolution_percentage = 100
  49. scene.world.color = (0.22, 0.22, 0.22)
  50. bpy.ops.object.camera_add(location=(78, -85, 60))
  51. camera = bpy.context.object
  52. camera.rotation_euler = (Vector((0, 0, 9)) - camera.location).to_track_quat('-Z', 'Y').to_euler()
  53. camera.data.type = 'ORTHO'
  54. camera.data.ortho_scale = 95
  55. scene.camera = camera
  56. bpy.ops.object.light_add(type='AREA', location=(0, -25, 65))
  57. bpy.context.object.data.energy = 90000
  58. bpy.context.object.data.size = 60
  59. scene.render.image_settings.file_format = 'PNG'
  60. scene.render.filepath = str(root / '.scratch/cockpit-shell-panel-layout/verify/artifacts/t35/near-building-offline.png')
  61. bpy.ops.render.render(write_still=True)
  62. print('OFFLINE_MODEL_PREVIEW', scene.render.filepath)