"""Složí tiskové projekty z STL ve složce stl/ (standardní 3MF, jednotky mm):
  supl_sada.3mf       jeden šuplík + pomůcky (2 plochy)
  supl_prihradky.3mf  startovní sada vložných přihrádek (1 plocha), plní celý šuplík

Rozložení pro Bambu Lab P2S (stůl 256 x 256 mm):
  supl_sada plocha 1: šuplík + 2 lišty + 2 zarážky           (bez podpor)
  supl_sada plocha 2: montážní šablona, 2 příčné + 1 podélná přepážka (ukázka, další dle potřeby), madlo, zkušební kousky
Souřadnice odpovídají Bambu Studiu (počátek v rohu stolu, 2. plocha je vpravo s mezerou 20 % šířky stolu).
Spuštění:  blender -b -P make_3mf.py   (nebo libovolný python 3)
"""
import os, struct, zipfile, math

HERE = os.path.dirname(os.path.abspath(__file__)) if "__file__" in globals() else r"C:\AI\3d modely\krabička"
STL = os.path.join(HERE, "stl")
BED = 256.0
P2 = BED * 1.2                      # posun 2. plochy ve Bambu Studiu


def read_stl(path):
    d = open(path, "rb").read()
    n = struct.unpack_from("<I", d, 80)[0]
    tris = []
    for i in range(n):
        v = struct.unpack_from("<12fH", d, 84 + 50 * i)
        tris.append([(v[3], v[4], v[5]), (v[6], v[7], v[8]), (v[9], v[10], v[11])])
    return tris


def place(tris, cx, cy, rot=0):
    """Otočí kolem Z o rot stupňů, vycentruje bbox na (cx, cy), spodek na z=0 a vrátí (vertexy, trojúhelníky, bbox)."""
    c, s = math.cos(math.radians(rot)), math.sin(math.radians(rot))
    t = [[(x * c - y * s, x * s + y * c, z) for x, y, z in tri] for tri in tris]
    xs = [p[0] for tri in t for p in tri]; ys = [p[1] for tri in t for p in tri]; zs = [p[2] for tri in t for p in tri]
    ox, oy, oz = cx - (min(xs) + max(xs)) / 2, cy - (min(ys) + max(ys)) / 2, -min(zs)
    idx, verts, faces = {}, [], []
    for tri in t:
        f = []
        for x, y, z in tri:
            k = (round(x + ox, 4), round(y + oy, 4), round(z + oz, 4))
            if k not in idx:
                idx[k] = len(verts); verts.append(k)
            f.append(idx[k])
        if len(set(f)) == 3:
            faces.append(f)
    return verts, faces, (min(xs) + ox, max(xs) + ox, min(ys) + oy, max(ys) + oy, max(zs) + oz)


def build(out, items, title):
    objs, info = [], []
    for i, (name, f, cx, cy, rot) in enumerate(items, 1):
        v, fc, bb = place(read_stl(os.path.join(STL, f)), cx, cy, rot)
        objs.append((i, name, v, fc)); info.append((name, bb, len(fc)))

    xml = ['<?xml version="1.0" encoding="UTF-8"?>',
           '<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02">',
           '<metadata name="Title">%s</metadata>' % title,
           '<metadata name="Designer">Claude + Blender</metadata>', '<resources>']
    for i, name, v, fc in objs:
        xml.append('<object id="%d" name="%s" type="model"><mesh><vertices>' % (i, name))
        xml += ['<vertex x="%g" y="%g" z="%g"/>' % p for p in v]
        xml.append('</vertices><triangles>')
        xml += ['<triangle v1="%d" v2="%d" v3="%d"/>' % tuple(t) for t in fc]
        xml.append('</triangles></mesh></object>')
    xml.append('</resources><build>')
    xml += ['<item objectid="%d" printable="1"/>' % i for i, *_ in objs]
    xml.append('</build></model>')

    ct = ('<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
          '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
          '<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/></Types>')
    rels = ('<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
            '<Relationship Target="/3D/3dmodel.model" Id="rel0" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/></Relationships>')
    with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
        z.writestr("[Content_Types].xml", ct)
        z.writestr("_rels/.rels", rels)
        z.writestr("3D/3dmodel.model", "\n".join(xml))

    # ---- kontrola: uzavřenost, rozměry, kolize obrysů, vejde se na stůl ----
    print("3MF:", out, round(os.path.getsize(out) / 1e6, 2), "MB")
    for (i, name, v, fc), (_, bb, nt) in zip(objs, info):
        edges = {}
        for a, b, c in fc:
            for e in ((a, b), (b, c), (c, a)):
                edges[e] = edges.get(e, 0) + 1
        bad = sum(1 for (a, b), n in edges.items() if n != 1 or edges.get((b, a), 0) != 1)
        plate = 1 if bb[1] <= BED else 2
        ox = 0 if plate == 1 else P2
        ok = bb[0] >= ox and bb[1] <= ox + BED and bb[2] >= 0 and bb[3] <= BED
        print("  %-19s plocha %d  x %.0f..%.0f  y %.0f..%.0f  v %.1f  trojuh. %d  neuzavrene hrany %d  %s"
              % (name, plate, bb[0] - ox, bb[1] - ox, bb[2], bb[3], bb[4], nt, bad, "OK" if ok else "MIMO STUL"))
    boxes = [(n, b) for n, b, _ in info]
    for a in range(len(boxes)):
        for b in range(a + 1, len(boxes)):
            (n1, p), (n2, q) = boxes[a], boxes[b]
            if p[0] < q[1] + 2 and q[0] < p[1] + 2 and p[2] < q[3] + 2 and q[2] < p[3] + 2:
                print("  POZOR: obrysy se blizi < 2 mm:", n1, n2)


build(os.path.join(HERE, "supl_sada.3mf"), [
    # plocha 1: hlavní díly jednoho šuplíku
    ("supl",               "supl.stl",             103.0, 103.0, 0),
    ("lista_P",            "lista_P.stl",          227.0, 102.0, 0),
    ("lista_L",            "lista_L.stl",          102.0, 229.0, 90),
    ("zarazka_1",          "zarazka_2x.stl",       214.0, 236.0, 0),
    ("zarazka_2",          "zarazka_2x.stl",       236.0, 236.0, 0),
    # plocha 2: šablona, přepážky, madlo, zkouška vůle
    ("sablona_listy",      "sablona_listy.stl",    P2 + 98.5, 28.5, 0),
    ("predelka_prycna_1",  "predelka_prycna.stl",  P2 + 100.0, 83.5, 0),
    ("predelka_prycna_2",  "predelka_prycna.stl",  P2 + 100.0, 139.5, 0),
    ("predelka_podelna_1", "predelka_podelna.stl", P2 + 100.0, 195.5, 0),
    ("madlo",              "madlo_1x.stl",         P2 + 219.0, 160.0, 90),
    ("TEST_vule_0.30",     "test_vule_0.30.stl",   P2 + 217.0, 18.0, 0),
    ("TEST_vule_0.40",     "test_vule_0.40.stl",   P2 + 243.0, 18.0, 0),
    ("TEST_vule_0.50",     "test_vule_0.50.stl",   P2 + 217.0, 55.0, 0),
    ("TEST_klic",          "test_klic.stl",        P2 + 230.0, 88.0, 0),
], "Zasuvka 200x200x70 na rybinovych listach")

# startovní sada přihrádek (zaplní celý šuplík, 16 modulů): 1 plocha
build(os.path.join(HERE, "supl_prihradky.3mf"), [
    ("krabicka_4x1",  "krabicka_4x1.stl",  98.0, 27.0, 0),
    ("delena_2x2",    "delena_2x2.stl",    52.0, 99.0, 0),
    ("vyrez_2x1",     "vyrez_2x1.stl",     150.0, 77.0, 0),
    ("sikma_2x1",     "sikma_2x1.stl",     150.0, 128.0, 0),
    ("krabicka_2x1_a", "krabicka_2x1.stl", 52.0, 174.0, 0),
    ("krabicka_2x1_b", "krabicka_2x1.stl", 150.0, 179.0, 0),
], "Startovni sada prihradek pro zasuvku")
