main
John Lauer Three revisions with per-experiment write-ups, inline 3D viewers, sources, fab files, open-science scorecard 091a7c9 12d ago
import { Molecule, ValidateContacts } from "@tsci/adom-inc.molecule"
import { MachineContactMedium } from "@tsci/adom-inc.library"
import { UsbCReceptacle } from "./UsbCReceptacle"

// 3×5 pixel font — [col, row] pairs for each lit LED (row 0 = top)
const FONT: Record<string, [number, number][]> = {
  S: [[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[2,2],[2,3],[0,4],[1,4],[2,4]],
  T: [[0,0],[1,0],[2,0],[1,1],[1,2],[1,3],[1,4]],
  E: [[0,0],[1,0],[2,0],[0,1],[0,2],[1,2],[0,3],[0,4],[1,4],[2,4]],
  V: [[0,0],[2,0],[0,1],[2,1],[0,2],[2,2],[0,3],[2,3],[1,4]],
  N: [[0,0],[2,0],[0,1],[1,1],[2,1],[0,2],[2,2],[0,3],[2,3],[0,4],[2,4]],
}

const WORD = "STEVEN"
const GRID = 3.5        // mm between LED centers
const LETTER_GAP = 4    // mm gap between letters
const LETTER_W = 2 * GRID

type Led = { x: number; y: number }
const leds: Led[] = []
let cursor = 0
for (const ch of WORD) {
  for (const [col, row] of FONT[ch]) {
    leds.push({ x: cursor + col * GRID, y: -row * GRID })
  }
  cursor += LETTER_W + LETTER_GAP
}

const textWidth = cursor - LETTER_GAP   // 62 mm for STEVEN
const xOff = -(textWidth / 2)           // centred on a 96-wide board → −31 … +31
const yOff = 7                          // center vertically: rows at y = 7, 3.5, 0, -3.5, -7
const N = leds.length                   // 58

// Nominal board width. USB-C sits at W/2 − 4 per the adom-usbc guide.
const W = 96

export default () => (
  <Molecule
    type="4pin"
    size="96x24"
    pinType="MachinePinMediumStandard"
    wing="nominal"
    roundEdges={true}
  >
    {/* ── Silkscreen ── */}
    <silkscreentext text="STEVEN" fontSize={1.3}
      pcbX={xOff + textWidth / 2} pcbY={-10} layer="top" />
    <silkscreentext text="NAME IN LIGHTS" fontSize={0.5}
      pcbX={xOff + textWidth / 2} pcbY={-11.3} layer="top" />

    <silkscreentext layer="bottom" text="STEVEN'S NAMEPLATE" fontSize={1.0} pcbX={0} pcbY={3} />
    <silkscreentext layer="bottom" text={`USB-C powered · ${N} white LEDs`} fontSize={0.5} pcbX={0} pcbY={1.5} />
    <silkscreentext layer="bottom" text="v1.0 · 2026" fontSize={0.5} pcbX={0} pcbY={0} />

    {/* ── 4 corner machine contacts for workcell scaffold (2 mm grid) ── */}
    <ValidateContacts>
      <MachineContactMedium name="MC_VCC1" pcbX={-40} pcbY={8} />
      <MachineContactMedium name="MC_GND1" pcbX={-40} pcbY={-8} />
      <MachineContactMedium name="MC_VCC2" pcbX={36}  pcbY={8} />
      <MachineContactMedium name="MC_GND2" pcbX={36}  pcbY={-8} />
    </ValidateContacts>
    <silkscreentext text="VCC" fontSize={0.4} pcbX={-40} pcbY={6.5} layer="top" />
    <silkscreentext text="GND" fontSize={0.4} pcbX={-40} pcbY={-6.5} layer="top" />
    <silkscreentext text="VCC" fontSize={0.4} pcbX={36}  pcbY={6.5} layer="top" />
    <silkscreentext text="GND" fontSize={0.4} pcbX={36}  pcbY={-6.5} layer="top" />

    {/* ── USB-C power input (east edge) — pcbX = nominal/2 − 4 = 44 ── */}
    <UsbCReceptacle name="J1" pcbX={W / 2 - 4} pcbY={0} mount="east" />
    {/* CC pulldowns. Spec is Rd = 5.1k ±10% (4.59k–5.61k). Adom stocks no
        5.1k in any package (see adom-basic-parts), so use the stocked 4.7k
        0402 — comfortably inside the window, unlike 5.6k which sits on the
        upper limit. */}
    <resistor name="R_CC1" resistance="4.7k" footprint="0402" pcbX={37} pcbY={2} />
    <resistor name="R_CC2" resistance="4.7k" footprint="0402" pcbX={37} pcbY={-2} />
    <capacitor name="C_BYP" capacitance="100nF" footprint="0402" pcbX={40} pcbY={5} />

    {/* ── 58 white 0603 LEDs spelling STEVEN (3×5 pixel grid, 3 mm pitch) ── */}
    {leds.map((pos, i) => (
      <led key={`D${i + 1}`} name={`D${i + 1}`} color="white" footprint="0603"
        pcbX={xOff + pos.x} pcbY={yOff + pos.y} />
    ))}

    {/* ── 58 × 220 Ω current-limiting resistors (one per LED), 1.6 mm below each
        LED and rotated 180° so pin2 sits directly under the anode. 1.6 mm (not
        1.5/1.75/1.9) is the offset the autorouter fully connects — the others
        each leave one bottom-layer trace ending under a top pad with no via;
        see scripts/check-connectivity.py. ── */}
    {leds.map((pos, i) => (
      <resistor key={`R${i + 1}`} name={`R${i + 1}`} resistance="220" footprint="0402"
        pcbX={xOff + pos.x} pcbY={yOff + pos.y - 1.6} pcbRotation={180} />
    ))}

    {/* ── Nets ── */}
    <net name="VBUS" />
    <net name="GND" />

    {/* Machine contacts → power rails (scaffold can power the board too) */}
    <trace from="MC_VCC1.pin1" to="net.VBUS" />
    <trace from="MC_VCC2.pin1" to="net.VBUS" />
    <trace from="MC_GND1.pin1" to="net.GND" />
    <trace from="MC_GND2.pin1" to="net.GND" />

    {/* USB-C → power rails */}
    <trace from="J1.VBUS1" to="net.VBUS" />
    <trace from="J1.VBUS2" to="net.VBUS" />
    <trace from="J1.GND1" to="net.GND" />
    <trace from="J1.GND2" to="net.GND" />

    {/* CC pulldowns — required for a charger to supply power */}
    <trace from="R_CC1.pin1" to="J1.CC1" />
    <trace from="R_CC1.pin2" to="net.GND" />
    <trace from="R_CC2.pin1" to="J1.CC2" />
    <trace from="R_CC2.pin2" to="net.GND" />

    {/* Bypass cap */}
    <trace from="C_BYP.pin1" to="net.VBUS" />
    <trace from="C_BYP.pin2" to="net.GND" />

    {/* LED wiring: VBUS → resistor → LED anode, cathode → GND */}
    {leds.map((_, i) => (
      <trace key={`vr${i}`} from={`R${i + 1}.pin1`} to="net.VBUS" />
    ))}
    {leds.map((_, i) => (
      <trace key={`rd${i}`} from={`R${i + 1}.pin2`} to={`D${i + 1}.anode`} />
    ))}
    {leds.map((_, i) => (
      <trace key={`dg${i}`} from={`D${i + 1}.cathode`} to="net.GND" />
    ))}
  </Molecule>
)