"""
GLB -> ikili STL donusturucu (bagimsiz, kutuphane gerektirmez).

Tarayicidaki FikoSTLDisaAktar.ts ile ayni isi yapar; tarayici acmadan
mevcut ComfyUI ciktilarini STL'e cevirmek / dogrulamak icin.

Kullanim:
  python tools/glb-stl-cevir.py girdi.glb cikti.stl [--mm 100]

--mm verilirse model en buyuk kenari o mm olacak sekilde olceklenir,
XY'de merkezlenir ve Z=0 tabanina oturtulur (uygulamadaki davranisin aynisi).
"""
import json
import struct
import sys

BILESEN = {5120: ('b', 1), 5121: ('B', 1), 5122: ('h', 2),
           5123: ('H', 2), 5125: ('I', 4), 5126: ('f', 4)}
ELEMAN = {'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, 'MAT4': 16}


def glb_oku(yol):
    b = open(yol, 'rb').read()
    if b[:4] != b'glTF':
        raise SystemExit('Bu bir GLB dosyasi degil.')
    off, js, ikili = 12, None, b''
    while off + 8 <= len(b):
        ln, ty = struct.unpack('<I4s', b[off:off + 8])
        govde = b[off + 8:off + 8 + ln]
        if ty == b'JSON':
            js = json.loads(govde.decode('utf-8'))
        elif ty[:3] == b'BIN':
            ikili = govde
        off += 8 + ln
    if js is None:
        raise SystemExit('GLB icinde JSON chunk yok.')
    return js, ikili


def accessor_oku(js, ikili, idx):
    a = js['accessors'][idx]
    bv = js['bufferViews'][a['bufferView']]
    fmt, bayt = BILESEN[a['componentType']]
    adet = ELEMAN[a['type']]
    temel = bv.get('byteOffset', 0) + a.get('byteOffset', 0)
    adim = bv.get('byteStride') or (bayt * adet)
    cik = []
    for i in range(a['count']):
        p = temel + i * adim
        cik.append(struct.unpack_from('<' + fmt * adet, ikili, p))
    return cik


def dugum_matrisi(n):
    if 'matrix' in n:
        m = n['matrix']          # glTF sutun-oncelikli
        return [[m[0], m[4], m[8], m[12]], [m[1], m[5], m[9], m[13]],
                [m[2], m[6], m[10], m[14]], [m[3], m[7], m[11], m[15]]]
    import math
    t = n.get('translation', [0, 0, 0])
    r = n.get('rotation', [0, 0, 0, 1])
    s = n.get('scale', [1, 1, 1])
    x, y, z, w = r
    rot = [[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
           [2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
           [2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)]]
    m = [[rot[i][j] * s[j] for j in range(3)] + [t[i]] for i in range(3)]
    m.append([0, 0, 0, 1])
    return m


def carp(m, v):
    return tuple(m[i][0] * v[0] + m[i][1] * v[1] + m[i][2] * v[2] + m[i][3]
                 for i in range(3))


def matris_carp(a, b):
    return [[sum(a[i][k] * b[k][j] for k in range(4)) for j in range(4)]
            for i in range(4)]


def ucgenleri_topla(js, ikili):
    ucgenler = []
    birim = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]

    def gez(dugum_idx, ust):
        n = js['nodes'][dugum_idx]
        m = matris_carp(ust, dugum_matrisi(n))
        if 'mesh' in n:
            for pr in js['meshes'][n['mesh']].get('primitives', []):
                if pr.get('mode', 4) != 4:
                    continue
                poz = [carp(m, v) for v in
                       accessor_oku(js, ikili, pr['attributes']['POSITION'])]
                if pr.get('indices') is not None:
                    ix = [i[0] for i in accessor_oku(js, ikili, pr['indices'])]
                else:
                    ix = list(range(len(poz)))
                for i in range(0, len(ix) - 2, 3):
                    ucgenler.append((poz[ix[i]], poz[ix[i + 1]], poz[ix[i + 2]]))
        for c in n.get('children', []):
            gez(c, m)

    sahne = js.get('scenes', [{}])[js.get('scene', 0)]
    for k in sahne.get('nodes', range(len(js.get('nodes', [])))):
        gez(k, birim)
    return ucgenler


def olcekle_ve_oturt(ucgenler, hedef_mm):
    dz = [[min(v[e] for t in ucgenler for v in t),
           max(v[e] for t in ucgenler for v in t)] for e in range(3)]
    en_buyuk = max(d[1] - d[0] for d in dz)
    k = (hedef_mm / en_buyuk) if en_buyuk > 0 else 1.0
    ox = (dz[0][0] + dz[0][1]) / 2 * k
    oy = (dz[1][0] + dz[1][1]) / 2 * k
    oz = dz[2][0] * k
    return [tuple((v[0] * k - ox, v[1] * k - oy, v[2] * k - oz) for v in t)
            for t in ucgenler]


def normal(t):
    (ax, ay, az), (bx, by, bz), (cx, cy, cz) = t
    ux, uy, uz = bx - ax, by - ay, bz - az
    vx, vy, vz = cx - ax, cy - ay, cz - az
    nx, ny, nz = uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx
    u = (nx * nx + ny * ny + nz * nz) ** 0.5
    return (nx / u, ny / u, nz / u) if u > 0 else (0.0, 0.0, 0.0)


def stl_yaz(yol, ucgenler):
    with open(yol, 'wb') as f:
        f.write(b'FikoYT resimden-3D STL'.ljust(80, b' '))
        f.write(struct.pack('<I', len(ucgenler)))
        for t in ucgenler:
            f.write(struct.pack('<3f', *normal(t)))
            for v in t:
                f.write(struct.pack('<3f', *v))
            f.write(b'\0\0')


def main():
    if len(sys.argv) < 3:
        raise SystemExit(__doc__)
    girdi, cikti = sys.argv[1], sys.argv[2]
    mm = 0.0
    if '--mm' in sys.argv:
        mm = float(sys.argv[sys.argv.index('--mm') + 1])

    js, ikili = glb_oku(girdi)
    ucgenler = ucgenleri_topla(js, ikili)
    if not ucgenler:
        raise SystemExit('GLB icinde ucgen bulunamadi.')
    if mm > 0:
        ucgenler = olcekle_ve_oturt(ucgenler, mm)
    stl_yaz(cikti, ucgenler)

    dz = [[min(v[e] for t in ucgenler for v in t),
           max(v[e] for t in ucgenler for v in t)] for e in range(3)]
    print('ucgen   :', len(ucgenler))
    print('olcu    : %.2f x %.2f x %.2f' % tuple(d[1] - d[0] for d in dz))
    print('yazildi :', cikti)


if __name__ == '__main__':
    main()
