"""
Метео-кутия за екран на Стивънсън
Компоненти: ESP32-C3 Super Mini + контролер за дъжд (LM393) в основната кутия,
AHT10 и BMP280 в отделни вентилирани "капсули" от двете къси страни.
Генерира: kutia_osnova.stl, kutia_kapak.stl, sensor_pod.stl (печата се 2x)
"""
import numpy as np
import trimesh
from trimesh.creation import box as tbox, cylinder as tcyl
from trimesh.transformations import translation_matrix as T, rotation_matrix as R

ENG = "manifold"

def B(x0, y0, z0, x1, y1, z1):
    ext = [x1 - x0, y1 - y0, z1 - z0]
    c = [(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2]
    return tbox(extents=ext, transform=T(c))

def Cz(cx, cy, z0, z1, d):
    m = tcyl(radius=d / 2, height=z1 - z0, sections=48)
    m.apply_transform(T([cx, cy, (z0 + z1) / 2]))
    return m

def Cx(x0, x1, cy, cz, d):  # цилиндър по оста X
    m = tcyl(radius=d / 2, height=x1 - x0, sections=48)
    m.apply_transform(R(np.pi / 2, [0, 1, 0]))
    m.apply_transform(T([(x0 + x1) / 2, cy, cz]))
    return m

def union(ms):  return trimesh.boolean.union(ms, engine=ENG)
def diff(a, b): return trimesh.boolean.difference([a] + b, engine=ENG)

# ---------------- параметри ----------------
L, W, H = 90.0, 55.0, 28.0     # външни размери на основата
t = 2.0                        # стена/дъно
CLR = 0.3

# ================= ОСНОВА =================
# черупка: външен обем минус кухина
shell = diff(B(0, 0, 0, L, W, H), [B(t, t, t, L - t, W - t, H + 1)])

adds = [shell]
# ъглови бобини за капака — с гнезда за месингови вложки M3x3 (Ø4.0)
boss_xy = [(6, 6), (L - 6, 6), (6, W - 6), (L - 6, W - 6)]
for bx, by in boss_xy:
    adds.append(Cz(bx, by, t, H, 8.0))

# удебеления на двете къси стени за вложките на сензорните капсули
# (блок от пода нагоре — печата се без поддръжки)
POD_Z, POD_Y = 14.0, W / 2
EAR = 18.5                # разстояние на ушите/вложките от центъра
adds.append(B(0, 5.0, t, 6, 50.0, 19))
adds.append(B(L - 6, 5.0, t, L, 50.0, 19))

# ---------- легла за платките ----------
SEAT = 5.0       # опорите вдигат платката 5 мм над пода (място за споените крачета)
FEN = 12.0       # височина на страничните ограничители (от пода)
FW_ = 1.5        # дебелина на ограничител

# --- ESP32-C3 Super Mini: гнездо 18.6 x 23.0, USB към предната стена (y=0)
ex0, ey0, eix, eiy = 11.0, 4.0, 18.6, 23.0
# опорни греди под предния и задния КЪС ръб (там няма пинове)
adds.append(B(ex0, ey0, t, ex0 + eix, ey0 + 2.5, t + SEAT))
adds.append(B(ex0, ey0 + eiy - 2.5, t, ex0 + eix, ey0 + eiy, t + SEAT))
# странични ограничители (L-рамене от ъглите, среден отвор за зип-връзка)
for sx in [ex0 - FW_, ex0 + eix]:
    adds.append(B(sx, ey0, t, sx + FW_, ey0 + 6, t + FEN))
    adds.append(B(sx, ey0 + eiy - 6, t, sx + FW_, ey0 + eiy, t + FEN))
# къси рамене на предния ръб (2.5 мм — оставят 13.6 мм за USB-C щекера)
adds.append(B(ex0, ey0 - FW_, t, ex0 + 2.5, ey0, t + FEN))
adds.append(B(ex0 + eix - 2.5, ey0 - FW_, t, ex0 + eix, ey0, t + FEN))
# рамене на задния ръб
adds.append(B(ex0, ey0 + eiy, t, ex0 + 6, ey0 + eiy + FW_, t + FEN))
adds.append(B(ex0 + eix - 6, ey0 + eiy, t, ex0 + eix, ey0 + eiy + FW_, t + FEN))

# --- LM393 контролер за дъжд: гнездо 33.5 x 15.5, пиновете на късите ръбове
rx0, ry0, rix, riy = 42.0, 6.0, 33.5, 15.5
# ъглови опори 3x3 (само под ъглите — отдолу всичко е свободно за споените изводи)
for cx in [rx0, rx0 + rix - 3]:
    for cy in [ry0, ry0 + riy - 3]:
        adds.append(B(cx, cy, t, cx + 3, cy + 3, t + SEAT))
# ограничители: дълги рамене 6 по дългите ръбове, къси 2 по късите (пиновете са в средата)
for sy, d in [(ry0 - FW_, 0), (ry0 + riy, 0)]:
    adds.append(B(rx0, sy, t, rx0 + 6, sy + FW_, t + FEN))
    adds.append(B(rx0 + rix - 6, sy, t, rx0 + rix, sy + FW_, t + FEN))
for sx in [rx0 - FW_, rx0 + rix]:
    adds.append(B(sx, ry0, t, sx + FW_, ry0 + 2, t + FEN))
    adds.append(B(sx, ry0 + riy - 2, t, sx + FW_, ry0 + riy, t + FEN))

base = union(adds)

cuts = []
# гнезда за вложки M3x3 в бобините (Ø4.0, дълбочина 6)
for bx, by in boss_xy:
    cuts.append(Cz(bx, by, H - 6, H + 1, 4.0))
# USB-C отвор в предната стена (платката е на 5 мм над пода → конектор z 8..11.5)
cuts.append(B(13.8, -1, 6.0, 26.8, 3, 15.0))
# прорези за кабелни превръзки през пода (застопоряват платките)
cuts.append(B(ex0 - 3.7, ey0 + 8.5, -1, ex0 - 1.7, ey0 + 14.5, t + 1))       # ESP32 ляво
cuts.append(B(ex0 + eix + 1.7, ey0 + 8.5, -1, ex0 + eix + 3.7, ey0 + 14.5, t + 1))  # ESP32 дясно
cuts.append(B(rx0 + 13.5, ry0 - 3.7, -1, rx0 + 19.5, ry0 - 1.7, t + 1))      # LM393 отпред
cuts.append(B(rx0 + 13.5, ry0 + riy + 1.7, -1, rx0 + 19.5, ry0 + riy + 3.7, t + 1)) # LM393 отзад
# отвор за кабела към панела за дъжд (задна стена)
rain_hole = tcyl(radius=3.0, height=6, sections=48)
rain_hole.apply_transform(R(np.pi / 2, [1, 0, 0]))
rain_hole.apply_transform(T([80, W - 1, 10]))
cuts.append(rain_hole)

# вентилационни прорези – дълги стени (високо, за да не пречат на USB)
for wall_y0, wall_y1 in [(-1, t + 1), (W - t - 1, W + 1)]:
    x = 8.0
    while x + 2 <= 72:
        cuts.append(B(x, wall_y0, 15, x + 2, wall_y1, 25))
        x += 7
# вентилация в дъното (задната половина, извън джобовете)
for yy in [30, 34, 38, 42, 46]:
    cuts.append(B(15, yy, -1, 75, yy + 2, t + 1))

# отвори в късите стени: кабелен Ø8 в центъра + 2 гнезда Ø4.0 за вложки M3x3
# (вложките се пресоват от външната страна, болтовете идват през ушите на фланеца)
for x0, x1 in [(-1, 7), (L - 7, L + 1)]:
    cuts.append(Cx(x0, x1, POD_Y, POD_Z, 8.0))
    cuts.append(Cx(x0, x1, POD_Y - EAR, POD_Z, 4.0))
    cuts.append(Cx(x0, x1, POD_Y + EAR, POD_Z, 4.0))

base = diff(base, cuts)
base.export("/home/claude/kutia_osnova.stl")

# ================= КАПАК =================
lid = [B(0, 0, 0, L, W, 2.0)]
# вътрешен борд (влиза в кутията)
lip_o = B(t + CLR, t + CLR, -3, L - t - CLR, W - t - CLR, 0)
lip_i = B(t + CLR + 1.6, t + CLR + 1.6, -4, L - t - CLR - 1.6, W - t - CLR - 1.6, 1)
lip = diff(lip_o, [lip_i])
# изрези в борда около бобините
lip = diff(lip, [Cz(bx, by, -5, 1, 10.5) for bx, by in boss_xy])
lid = union(lid + [lip])

lcuts = [Cz(bx, by, -5, 3, 3.4) for bx, by in boss_xy]          # отвори за винтове
for yy in [14, 20, 26, 32, 38]:                                  # вентилация
    lcuts.append(B(25, yy, -1, 65, yy + 2, 3))
lid = diff(lid, lcuts)
lid.export("/home/claude/kutia_kapak.stl")

# ============ СЕНЗОРНА КАПСУЛА (x2) ============
# Печата се с фланеца надолу, БЕЗ поддръжки: вместо плосък таван (мост)
# горната част е покрив под 45° — и отвън, и отвътре. Сензорът се вкарва
# през правоъгълния отвор във фланеца, после капсулата се затяга с ДВА
# болта M3x8 през ушите във вложките в стената. Ушите са извън тялото.
def hull(pts):
    return trimesh.convex.convex_hull(np.array(pts, dtype=float))

FL, FW, FT = 44.0, 26.0, 3.0
BH = 16.0                                  # височина на правите стени над фланеца
pod_parts = [B(-FL / 2, -FW / 2, 0, FL / 2, FW / 2, FT)]     # фланец
pod_parts.append(B(-15, -13, FT, 15, 13, FT + BH))           # право тяло 30 x 26
# външен покрив: 45° скатове до било (издига се 13 при полуширина 13)
pod_parts.append(hull([(-15, -13, FT + BH), (15, -13, FT + BH),
                       (-15, 13, FT + BH), (15, 13, FT + BH),
                       (-2, 0, FT + BH + 13), (2, 0, FT + BH + 13)]))
pod = union(pod_parts)

pcuts = [B(-13, -11, FT, 13, 11, FT + BH)]                   # кухина (права част)
# вътрешен покрив на кухината — също 45° (полуширина 11 -> било +11)
pcuts.append(hull([(-13, -11, FT + BH), (13, -11, FT + BH),
                   (-13, 11, FT + BH), (13, 11, FT + BH),
                   (-2, 0, FT + BH + 11), (2, 0, FT + BH + 11)]))
pcuts.append(B(-8, -10, -1, 8, 10, FT + 1))                  # отвор за вкарване на сензора (16 x 20)
pcuts.append(Cz(+18.5, 0, -1, FT + 1, 3.2))                  # уши за болтовете M3
pcuts.append(Cz(-18.5, 0, -1, FT + 1, 3.2))
pcuts.append(Cz(-10.5, 0, -1, FT + 1, 2.6))                      # за кабелна превръзка
pcuts.append(Cz(+10.5, 0, -1, FT + 1, 2.6))
# прорези по 4-те стени на тялото (само в правата част, горен ръб = мост 2 мм)
for xx in [-11, -6, -1, 4, 9]:
    pcuts.append(B(xx, -16, FT + 3, xx + 2, 16, FT + 14))         # предна+задна
for yy in [-9, -4, 1, 6]:
    pcuts.append(B(-17, yy, FT + 3, 17, yy + 2, FT + 14))         # двете къси
pod = diff(union([pod]), pcuts)
pod.export("/home/claude/sensor_pod.stl")

for n, m in [("osnova", base), ("kapak", lid), ("pod", pod)]:
    print(n, "watertight:", m.is_watertight, "faces:", len(m.faces))
