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