"""Blender-free contract for an estimated shared compact window-frame image.""" import hashlib import json import math from pathlib import Path import struct def load_window_texture(manifest_path): manifest_path = Path(manifest_path).resolve() record = json.loads(manifest_path.read_text(encoding='utf-8')) if record.get('kind') != 'estimated-window-frame' or record.get('version') not in (1, 2): raise ValueError('unsupported compact window texture manifest') path = (manifest_path.parent / record['image']).resolve() data = path.read_bytes() if len(data) > 8192 or len(data) < 33 or data[:8] != b'\x89PNG\r\n\x1a\n': raise ValueError('window texture must be a bounded PNG') width, height = struct.unpack('>II', data[16:24]) size_valid = (width, height) == (64, 64) if record['version'] == 1 else ( width == height and width in (32, 64) and record.get('dimensions') == [width, height]) if not size_valid or data[24:26] != bytes((8, 2)): raise ValueError('window texture must be bounded opaque RGB8; v1=64px, v2=declared 32/64px') if hashlib.sha256(data).hexdigest() != record['sha256']: raise ValueError('window texture source hash mismatch') mip_bytes = sum(max(1, width >> level) * max(1, height >> level) * 4 for level in range(width.bit_length())) return {**record, 'path': str(path), 'rasterBytes': len(data), 'width': width, 'height': height, 'rgba8FullMipEstimate': mip_bytes} def window_quad_uv(points): """Map a vertical window quad to one tile; never reuse geographic roof UVs.""" if len(points) != 4: raise ValueError('window face is not a quad') pairs = [(b[0]-a[0], b[1]-a[1]) for a in points for b in points] dx, dy = max(pairs, key=lambda p: math.hypot(*p)) length = math.hypot(dx, dy) low_z, high_z = min(p[2] for p in points), max(p[2] for p in points) if length < 1e-6 or high_z-low_z < 1e-6: raise ValueError('window quad has no width or height') along = [(p[0]-points[0][0])*dx/length + (p[1]-points[0][1])*dy/length for p in points] low, high = min(along), max(along) return [((u-low)/(high-low), (p[2]-low_z)/(high_z-low_z)) for u,p in zip(along,points)]