#!/usr/bin/env python3
"""Stretch windows: X and Y with 0.1mm transition at center, Z (thickness) PRESERVED."""
import struct, numpy as np, os

ORIG = "/app/projects/esp32-box/original"
OUT = "/app/projects/esp32-box/exports"
os.makedirs(OUT, exist_ok=True)

def load_stl(path):
    with open(path, "rb") as f:
        f.read(80)
        n = struct.unpack("<I", f.read(4))[0]
        verts = np.zeros((n*3, 3), dtype=np.float64)
        for i in range(n):
            f.read(12)
            for j in range(3):
                verts[i*3+j] = np.frombuffer(f.read(12), dtype=np.float32)
            f.read(2)
    return verts, n

def save_stl(verts, path):
    n = len(verts)//3
    with open(path, "wb") as f:
        f.write(b"\x00"*80)
        f.write(struct.pack("<I", n))
        for i in range(n):
            f.write(struct.pack("<fff", 0, 0, 0))
            for j in range(3):
                v = verts[i*3+j]
                f.write(struct.pack("<fff", float(v[0]), float(v[1]), float(v[2])))
            f.write(b"\x00\x00")

def info(p):
    with open(p, "rb") as f:
        f.read(80)
        n = struct.unpack("<I", f.read(4))[0]
        x1=y1=z1=9e9; x2=y2=z2=-9e9
        for i in range(n):
            f.read(12)
            for j in range(3):
                x,y,z = struct.unpack("<fff", f.read(12))
                x1,x2 = min(x1,x), max(x2,x)
                y1,y2 = min(y1,y), max(y2,y)
                z1,z2 = min(z1,z), max(z2,z)
            f.read(2)
    return n, x2-x1, y2-y1, z2-z1

def stretch_xy_preserve_z(v, ow, oh, nw, nh, trans):
    """Stretch X and Y with 0.1mm transition at center of each. Z (thickness) PRESERVED."""
    x0, x1 = v[:,0].min(), v[:,0].max()
    y0, y1 = v[:,1].min(), v[:,1].max()
    n = len(v)//3
    x_mid = (x0 + x1) / 2
    y_mid = (y0 + y1) / 2
    x_lo = x_mid - trans/2
    x_hi = x_mid + trans/2
    y_lo = y_mid - trans/2
    y_hi = y_mid + trans/2
    x_scale = (nw - ow + trans) / trans if trans > 0 else 1
    y_scale = (nh - oh + trans) / trans if trans > 0 else 1
    print("  X: %.1f -> %.1f (scale=%.1f), Y: %.1f -> %.1f (scale=%.1f), Z PRESERVED" % (ow, nw, x_scale, oh, nh, y_scale))
    r = v.copy()
    for idx in range(n*3):
        x, y, z = v[idx]
        # X stretch
        if x > x_lo and x < x_hi:
            xn = x_lo + (x - x_lo) * x_scale
        elif x >= x_hi:
            xn = x + nw - ow
        else:
            xn = x
        # Y stretch
        if y > y_lo and y < y_hi:
            yn = y_lo + (y - y_lo) * y_scale
        elif y >= y_hi:
            yn = y + nh - oh
        else:
            yn = y
        # Z preserved
        r[idx] = [xn, yn, z]
    return r

# Window definitions: (source, dest, target_X, target_Y)
# Z (thickness) is PRESERVED from original
windows = [
    ("Window 20x60mm - liso.stl", "window_smooth.stl", 36, 23),
    ("Window 20x60mm.stl", "window_keystone_dc.stl", 36, 23),
    ("Window 20x60mm - liso.stl", "window_smooth_y.stl", 26, 23),
    ("Window - Flex Gland.stl", "window_usb.stl", 26, 23),
]

for src, dst, nw, nh in windows:
    v, n = load_stl(os.path.join(ORIG, src))
    ow = v[:,0].max() - v[:,0].min()
    oh = v[:,1].max() - v[:,1].min()
    oz = v[:,2].max() - v[:,2].min()
    print("%s -> %s: X=%.1f->%.1f, Y=%.1f->%.1f, Z=%.1f (preserved)" % (src, dst, ow, nw, oh, nh, oz))
    r = stretch_xy_preserve_z(v, ow, oh, nw, nh, 0.1)
    save_stl(r, os.path.join(OUT, dst))
    nt, xd, yd, zd = info(os.path.join(OUT, dst))
    print("  result: X=%.1f Y=%.1f Z=%.1f (%d tri)" % (xd, yd, zd, nt))

print("\nDONE")
