# Electronics enclosure, exploded: a shelled case with rounded corners, screw bosses and a USB-C
# cut-out, a lid with vent slots, the board inside with its parts, the four screws above.
from math import *
units("mm")

W, D, HB, HL = 160, 100, 38, 9       # outside: width, depth, the case's height, the lid's
R, T = 10, 2.4                       # corner radius, wall
LIFT = 46                            # how far the lid is lifted (the exploded view)
plastic, dark, pcb_c, metal = "#e6e3dc", "#2f3033", "#2f7a4b", "#c9ccd2"
BX, BY = 9, 9                        # the bosses' centres, in from each corner

# ---------- the case ----------
case = box(W, D, HB, at=(0, 0, 4), name="case", color=plastic)
case.fillet(R, edges="vertical")
case.fillet(3, edges="bottom")
case.shell(T, open="top")
bosses = []
for x in (BX, W - BX):
    for y in (BY, D - BY):
        b = cylinder(5, HB - T - 3.5, at=(x, y, 4 + T), sides=32)      # (just below the lid's tongue)
        b = b - cylinder(1.6, HB, at=(x, y, 4 + T + 2), sides=16)
        bosses.append(b)
case = union(case, *bosses)
# the openings in the front wall, lined up with the board's parts: USB-C, a barrel jack, two LEDs
top = 4 + T + 6 + 1.6                  # the board's top face (it sits on its standoffs, below)
slot = box(9.6, T + 2, 3.8, at=(W / 2 - 4.8, -1, top - 0.3))
slot.fillet(1.8, edges="y")
openings = [slot, cylinder(4.3, T + 2, at=(W / 2 + 26, -1, top + 4), axis="y", sides=32)]
openings += [cylinder(1.8, T + 2, at=(x, -1, top + 5.4), axis="y", sides=20) for x in (W / 2 - 34, W / 2 - 26)]
case = subtract(case, *openings)
case.paint(plastic)
feet = [cylinder(5, 4, at=(x, y, 0), name="foot", color=dark, sides=24) for x in (16, W - 16) for y in (16, D - 16)]
for f in feet:
    f.fillet(1, edges="bottom")

# ---------- the lid, lifted ----------
lz = 4 + HB + LIFT
lid = box(W, D, HL, at=(0, 0, lz), name="lid", color=plastic)
lid.fillet(R, edges="vertical")
lid.fillet(3, edges="top")
lid.shell(2, open="bottom")
for i in range(9):
    lid.top.rect(44 + i * 8, 26, 3, 48).push(2)
for x in (BX, W - BX):
    for y in (BY, D - BY):
        lid.top.circle(x - 3, y - 3, 1.8).push(2)
# a tongue round the inside that drops into the case
ring = box(W - 2 * T - 0.4, D - 2 * T - 0.4, 10, at=(T + 0.2, T + 0.2, lz - 3))
ring.fillet(R - T, edges="vertical")
hole = box(W - 2 * T - 3.2, D - 2 * T - 3.2, 10, at=(T + 1.6, T + 1.6, lz - 3))
hole.fillet(R - T - 1.4, edges="vertical")
ring = ring - hole
lid = lid + ring
lid.paint(plastic)
lid_obj = group([lid], name="lid")

screws = []
for x in (BX, W - BX):
    for y in (BY, D - BY):
        head = cylinder(3, 2, at=(x, y, lz + HL + 14), color=dark, sides=24)
        head.fillet(0.8, edges="top")
        head = head - ngon(x, y, 1.3, sides=6, offset=lz + HL + 15).pull(1)
        shank = cylinder(1.5, 12, at=(x, y, lz + HL + 2), color=dark, sides=16)
        s = head + shank
        s.paint(dark)
        s.name = "screw"
        screws.append(s)
group(screws, name="screws")

# ---------- the board ----------
bz = 4 + T + 6
pcb = box(W - 2 * BX - 14, D - 2 * T - 6, 1.6, at=(BX + 7, T + 3, bz), name="board", color=pcb_c)
pcb.fillet(1.5, edges="vertical")
parts = [pcb]
top = bz + 1.6
def part(w, d, h, x, y, name, color, r=0.4):
    p = box(w, d, h, at=(x, y, top), name=name, color=color)
    if r:
        p.fillet(r, edges="top")
    parts.append(p)
    return p
part(14, 14, 1.4, 58, 42, "microcontroller", dark)
for i in range(10):
    part(0.4, 1.6, 0.3, 59 + i * 1.3, 40.4, "pin", metal, 0)
    part(0.4, 1.6, 0.3, 59 + i * 1.3, 56, "pin", metal, 0)
usb = part(9, 7.5, 3.2, W / 2 - 4.5, 1.2, "USB-C socket", metal, 1.2)
jack = cylinder(4, 12, at=(W / 2 + 26, 1, top + 4), axis="y", name="DC jack", color=dark, sides=24)
jack = jack - cylinder(1.2, 6, at=(W / 2 + 26, 1, top + 4), axis="y", sides=16)
jack.paint(dark)
parts.append(jack)
for x in (W / 2 - 34, W / 2 - 26):
    parts.append(cylinder(1.5, 4.5, at=(x, 0.8, top + 5.4), axis="y", name="LED", color="#d94a4a" if x < W / 2 - 30 else "#3fbf6a", sides=16))
    parts.append(box(1.2, 1.2, 4.6, at=(x - 0.6, 4.6, top), name="LED leg", color=metal))
for i, (x, y) in enumerate([(30, 60), (40, 60)]):
    cap = cylinder(4, 11, at=(x, y, top), name="capacitor", color="#27407e", sides=24)
    cap.fillet(0.6, edges="top")
    parts.append(cap)
relay = part(19, 15, 15, 104, 56, "relay", "#3a62d6", 1)
part(5, 3.2, 1.2, 80, 64, "crystal", metal, 0.6)
for i in range(4):
    part(3.2, 1.6, 0.6, 30 + i * 5, 36, "resistor", "#2f3033", 0)
header = part(25.4, 2.5, 2.5, 26, 74, "header", dark, 0)
for i in range(10):
    parts.append(box(0.64, 0.64, 8.5, at=(27 + i * 2.54, 74.9, top), name="header pin", color="#d4a73c"))
group(parts, name="board")

group([case] + feet, name="case")

dim((0, -14, 4), (W, -14, 4), offset=(0, -16, 0))
dim((W + 14, 0, 4), (W + 14, D, 4), offset=(16, 0, 0))
label("USB-C", at=(W / 2 - 45, -55, 60), to=(W / 2, 0, top + 1.6))
label("vent slots", at=(W - 10, D + 30, lz + 55), to=(W / 2 + 28, D / 2, lz + HL))
