# -*- coding: utf-8 -*-
# ============================================================
#  EXPORT KE AR (GLB)  —  Media Pembelajaran FreeCAD
#  Ubah model 3D yang sedang terbuka menjadi file .glb (format AR)
#  hanya dengan 1 klik. Tanpa software tambahan, gratis & legal.
#
#  CARA PAKAI:
#   1. Buka file FreeCAD-mu (model 3D, mis. hasil Pad/Extrude).
#   2. Menu: Macro -> Macros... -> pilih "ExportKeAR" -> Execute.
#   3. Beri nama file (mis. Nama_Kelas) -> Simpan.
#   4. Upload file .glb itu ke website -> lihat di AR dari HP.
# ============================================================
import os, struct, json
import FreeCAD


def _info(title, text):
    """Tampilkan pesan ke siswa (kotak dialog); fallback ke Report view."""
    try:
        from PySide import QtGui
        try:
            from PySide import QtWidgets
        except Exception:
            QtWidgets = QtGui
        box = QtWidgets.QMessageBox()
        box.setWindowTitle(title)
        box.setText(text)
        box.exec_()
    except Exception:
        FreeCAD.Console.PrintMessage("\n[%s] %s\n" % (title, text))


def _has_solid(o):
    try:
        return hasattr(o, "Shape") and o.Shape and (not o.Shape.isNull()) and len(o.Shape.Solids) > 0
    except Exception:
        return False


def collect_tris(doc, tol=0.1):
    """Kumpulkan titik + segitiga dari bentuk 3D pada dokumen."""
    cands = [o for o in doc.Objects if _has_solid(o)]
    if not cands:
        cands = [o for o in doc.Objects
                 if hasattr(o, "Shape") and o.Shape and not o.Shape.isNull()
                 and len(o.Shape.Faces) > 0]
    vis = [o for o in cands if getattr(o, "Visibility", False)]
    use = vis if vis else cands
    if (not vis) and len(use) > 1:
        try:
            use = [max(use, key=lambda o: o.Shape.Volume)]
        except Exception:
            pass
    P, FAC = [], []
    for o in use:
        pts, facets = o.Shape.tessellate(tol)
        base = len(P)
        for v in pts:
            P.append((v.x, v.y, v.z))
        for f in facets:
            FAC.append((base + f[0], base + f[1], base + f[2]))
    return use, P, FAC


def build_glb(P, FAC):
    """Bangun biner GLB (real-size: mm->m, Z-up->Y-up, didudukkan di lantai)."""
    S = 0.001
    T = [(x * S, z * S, -y * S) for (x, y, z) in P]
    xs = [p[0] for p in T]; ys = [p[1] for p in T]; zs = [p[2] for p in T]
    cx = (min(xs) + max(xs)) / 2.0
    cz = (min(zs) + max(zs)) / 2.0
    my = min(ys)
    T = [(x - cx, y - my, z - cz) for (x, y, z) in T]

    positions, normals = [], []
    for (i, j, k) in FAC:
        a = T[i]; b = T[j]; c = T[k]
        ux, uy, uz = b[0]-a[0], b[1]-a[1], b[2]-a[2]
        vx, vy, vz = c[0]-a[0], c[1]-a[1], c[2]-a[2]
        nx, ny, nz = uy*vz-uz*vy, uz*vx-ux*vz, ux*vy-uy*vx
        ln = (nx*nx+ny*ny+nz*nz) ** 0.5 or 1.0
        nx, ny, nz = nx/ln, ny/ln, nz/ln
        for p in (a, b, c):
            positions.extend(p)
            normals.extend((nx, ny, nz))

    count = len(positions) // 3
    pos_bytes = struct.pack("<%df" % len(positions), *positions)
    nrm_bytes = struct.pack("<%df" % len(normals), *normals)
    bin_blob = pos_bytes + nrm_bytes
    xs = positions[0::3]; ys = positions[1::3]; zs = positions[2::3]
    pmin = [min(xs), min(ys), min(zs)]
    pmax = [max(xs), max(ys), max(zs)]

    gltf = {
        "asset": {"version": "2.0", "generator": "FreeCAD Export ke AR (Media FreeCAD)"},
        "scene": 0, "scenes": [{"nodes": [0]}], "nodes": [{"mesh": 0}],
        "meshes": [{"primitives": [{"attributes": {"POSITION": 0, "NORMAL": 1}, "material": 0}]}],
        "materials": [{"name": "logam", "pbrMetallicRoughness": {
            "baseColorFactor": [0.55, 0.6, 0.66, 1.0], "metallicFactor": 0.85, "roughnessFactor": 0.35}}],
        "buffers": [{"byteLength": len(bin_blob)}],
        "bufferViews": [
            {"buffer": 0, "byteOffset": 0, "byteLength": len(pos_bytes), "target": 34962},
            {"buffer": 0, "byteOffset": len(pos_bytes), "byteLength": len(nrm_bytes), "target": 34962}],
        "accessors": [
            {"bufferView": 0, "componentType": 5126, "count": count, "type": "VEC3", "min": pmin, "max": pmax},
            {"bufferView": 1, "componentType": 5126, "count": count, "type": "VEC3"}],
    }
    json_blob = json.dumps(gltf).encode("utf-8")
    while len(json_blob) % 4: json_blob += b" "
    while len(bin_blob) % 4: bin_blob += b"\x00"
    total = 12 + 8 + len(json_blob) + 8 + len(bin_blob)
    out = struct.pack("<III", 0x46546C67, 2, total)
    out += struct.pack("<II", len(json_blob), 0x4E4F534A) + json_blob
    out += struct.pack("<II", len(bin_blob), 0x004E4942) + bin_blob
    return out, count, len(FAC)


def main():
    doc = FreeCAD.ActiveDocument
    if doc is None:
        _info("Export ke AR", "Belum ada dokumen terbuka.\nBuka / gambar model 3D-mu dulu ya, lalu jalankan lagi.")
        return
    use, P, FAC = collect_tris(doc)
    if not FAC:
        _info("Export ke AR", "Tidak ditemukan bentuk 3D (solid) untuk diekspor.\n\n"
              "Pastikan modelmu sudah berupa benda 3D (mis. hasil Pad/Extrude), "
              "bukan hanya sketsa 2D.")
        return

    base = doc.Label or "model"
    default_dir = os.path.expanduser("~")
    if getattr(doc, "FileName", ""):
        default_dir = os.path.dirname(doc.FileName)
        base = os.path.splitext(os.path.basename(doc.FileName))[0]
    out_path = os.path.join(default_dir, base + ".glb")

    # Dialog "Simpan sebagai" (boleh ganti nama -> mis. Nama_Kelas.glb)
    try:
        from PySide import QtGui
        try:
            from PySide import QtWidgets
        except Exception:
            QtWidgets = QtGui
        import FreeCADGui
        parent = FreeCADGui.getMainWindow()
        chosen, _f = QtWidgets.QFileDialog.getSaveFileName(
            parent, "Simpan file AR (GLB) — beri nama: Nama_Kelas", out_path, "Model AR (*.glb)")
        if not chosen:
            return  # dibatalkan
        if not chosen.lower().endswith(".glb"):
            chosen += ".glb"
        out_path = chosen
    except Exception as e:
        FreeCAD.Console.PrintWarning("\n[Export ke AR] Dialog gagal (%s) -> pakai lokasi default.\n" % e)

    glb, count, ntri = build_glb(P, FAC)
    with open(out_path, "wb") as f:
        f.write(glb)

    _info("Berhasil! ✅",
          "File AR berhasil dibuat:\n\n%s\n\n(%d segitiga, %d KB)\n\n"
          "Langkah berikut: buka website di HP -> Login -> Upload file .glb ini."
          % (out_path, ntri, max(1, len(glb)//1024)))
    FreeCAD.Console.PrintMessage("\n[Export ke AR] OK -> %s (%d byte, %d segitiga)\n" % (out_path, len(glb), ntri))


main()
