"""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)