hardware
Alex — Adom Molecule LED Nameplate
Public Made by Adomby adom
Alex's name etched out of the soldermask over the top-layer ground plane, lit by side-view LEDs firing inward across the board. A real Adom molecule: large machine pins on the 32 mm base-scaffold grid, USB-C powered. Every LED returns its current through the letters, so there is no second ground path.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
/**
* TYPE-C-31-M-12 USB-C receptacle (JLCPCB C165948).
*
* ── Why the geometry below is what it is ──────────────────────────────────
*
* Every constant here was measured off the actual model
* (modelcdn easyeda C165948, 7872 verts) rather than assumed, because the
* assumed version renders wrong in a way that still passes lint.
*
* Raw model bounding box, in model units (mm):
* x -4.470 .. +4.470 8.94 mm wide (across the connector)
* y -5.300 .. +2.600 7.90 mm deep (mouth to back)
* z -0.850 .. +3.250 legs below board, shell 3.25 mm proud
*
* The `rotationOffset.z: 180` that the registry packages ship is NOT
* decoration — it is what aligns the model with this footprint. Reconstructing
* the registry placement shows the model's raw −5.3 face is the BACK (the end
* the SMT pads protrude from) and the raw +2.6 face is the MOUTH; the 180°
* flip puts the mouth at −Y in the footprint's frame. The footprint agrees:
* its mechanical holes sit at y = −1.04, −1.27 and −5.22 relative to the pad
* row, i.e. on the −Y side, which is only physically possible if the shell —
* and therefore the mouth — is also on the −Y side. Anchor legs live inside
* the shell, never behind it.
*
* Drop the 180° and keep the native hole positions and you get a board where
* the body floats forward of its own pads while the anchors trail out the
* back. `adom-tsci lint` does not catch this: it derives the mouth vector from
* `cad.rotation.z` using the same +Y assumption, so a 180° error cancels out
* of the check and reports a healthy `body_fwd`. Look at the 3D view.
*
* ── Placement ─────────────────────────────────────────────────────────────
*
* Relative to the pad row, the shell spans +0.06 (back, overlapping the pads
* slightly) to −7.84 (mouth), and the forward anchor pair sits at −5.22, i.e.
* 2.6 mm behind the mouth. That last number is the real constraint on how far
* the connector can hang off the board: overhang the mouth by the ~2.5 mm
* quoted for a pads-only footprint and the forward anchors land ON the board
* edge. With the anchors included the honest maximum is well under 1 mm —
* flush-to-slightly-proud, which is all the mechanical spec asks for anyway.
*/
const USB_C_PN = "C165948"
const USB_C_UUID = "2a4bc2358b36497d9ab2a66ab6419ba3"
/** Model origin -> pad row, along the mouth axis. See the derivation above. */
const BODY_FORWARD_MAG = 2.5034
/** Shell front face, measured forward from the pad row. */
export const USB_C_MOUTH_FROM_PADS = 7.84
/** Forward anchor centre, measured forward from the pad row. */
export const USB_C_FRONT_ANCHOR_FROM_PADS = 5.2232
/** Half-length of the forward anchor pill along the mouth axis. */
export const USB_C_FRONT_ANCHOR_HALF = 0.9
/**
* Where to put `pcbX`/`pcbY` (the pad row) so the mouth ends up `proud` mm
* past a board edge that sits `edge` mm from the origin.
*/
export const usbCPadRowForEdge = (edge: number, proud = 0.35) =>
edge + proud - USB_C_MOUTH_FROM_PADS
const pinLabels = {
pin1: ["GND1", "A1"],
pin2: ["GND2", "B12"],
pin3: ["VBUS1", "A4"],
pin4: ["VBUS2", "B9"],
pin5: ["SBU2", "B8"],
pin6: ["CC1", "A5"],
pin7: ["DM2", "B7"],
pin8: ["DP1", "A6"],
pin9: ["DM1", "A7"],
pin10: ["DP2", "B6"],
pin11: ["SBU1", "A8"],
pin12: ["CC2", "B5"],
pin13: ["VBUS1", "A9"],
pin14: ["VBUS2", "B4"],
pin15: ["GND1", "A12"],
pin16: ["GND2", "B1"],
} as const
// 16 SMT pads, 0.5 mm pitch, pad row pinned to y = 0.
const PADS: Array<{ port: string; x: number }> = [
{ port: "A1", x: -3.35 },
{ port: "B12", x: -3.05 },
{ port: "A4", x: -2.55 },
{ port: "B9", x: -2.25 },
{ port: "B8", x: -1.75 },
{ port: "A5", x: -1.25 },
{ port: "B7", x: -0.75 },
{ port: "A6", x: -0.25 },
{ port: "A7", x: 0.25 },
{ port: "B6", x: 0.75 },
{ port: "A8", x: 1.25 },
{ port: "B5", x: 1.75 },
{ port: "B4", x: 2.25 },
{ port: "A9", x: 2.55 },
{ port: "B1", x: 3.05 },
{ port: "A12", x: 3.35 },
]
// Mechanical holes. y is measured from the pad row, negative = toward the
// mouth, so all of these sit under the shell.
const PLATED_ANCHORS = [
{ port: "alt_0", x: -4.3251, y: -1.0434, outerH: 2.0, innerH: 1.6 },
{ port: "alt_1", x: 4.3251, y: -1.0434, outerH: 2.0, innerH: 1.6 },
{ port: "alt_3", x: -4.3251, y: -5.2232, outerH: 1.8, innerH: 1.4 },
{ port: "alt_2", x: 4.3251, y: -5.2232, outerH: 1.8, innerH: 1.4 },
]
const SUPPORT_HOLES = [
{ x: -2.8999, y: -1.2685 },
{ x: 2.8999, y: -1.2685 },
]
export const UsbCReceptacle = ({
name,
pcbX,
pcbY,
pcbRotation = 0,
schX,
schY,
schRotation,
}: {
name: string
/** Position of the PAD ROW, not the shell. Use `usbCPadRowForEdge`. */
pcbX: number
pcbY: number
/** 0 fires the mouth south, 90 east, 180 north, 270 west. */
pcbRotation?: 0 | 90 | 180 | 270
schX?: number
schY?: number
schRotation?: number
}) => {
// Mouth is local −Y. Rotating (0,−1) by pcbRotation CCW gives the world
// mouth vector; positionOffset is expressed along that same vector so the
// body tracks the footprint through every rotation instead of drifting
// (tscircuit applies positionOffset in world coords, not local).
const rad = (pcbRotation * Math.PI) / 180
const mouthX = Math.sin(rad)
const mouthY = -Math.cos(rad)
return (
<chip
name={name}
pcbX={pcbX}
pcbY={pcbY}
pcbRotation={pcbRotation}
schX={schX}
schY={schY}
schRotation={schRotation}
supplierPartNumbers={{ jlcpcb: [USB_C_PN] }}
manufacturerPartNumber="TYPE-C-31-M-12"
pinLabels={pinLabels as any}
cadModel={{
objUrl: `https://modelcdn.tscircuit.com/easyeda_models/download?uuid=${USB_C_UUID}&pn=${USB_C_PN}`,
// 180° is load-bearing: it aligns the model's mouth with the −Y mouth
// this footprint's hole positions imply. Do not "simplify" it away.
rotationOffset: { x: 0, y: 0, z: 180 },
positionOffset: {
x: BODY_FORWARD_MAG * mouthX,
y: BODY_FORWARD_MAG * mouthY,
z: 0,
},
}}
footprint={
<footprint>
{PADS.map((p) => (
<smtpad
key={p.port}
portHints={[p.port]}
shape="rect"
width="0.3mm"
height="1.3mm"
pcbX={`${p.x}mm`}
pcbY="0mm"
/>
))}
{PLATED_ANCHORS.map((h) => (
<platedhole
key={h.port}
portHints={[h.port]}
shape="pill"
pcbX={`${h.x}mm`}
pcbY={`${h.y}mm`}
outerWidth="1.2mm"
outerHeight={`${h.outerH}mm`}
innerWidth="0.8mm"
innerHeight={`${h.innerH}mm`}
/>
))}
{SUPPORT_HOLES.map((h, i) => (
<hole key={i} pcbX={`${h.x}mm`} pcbY={`${h.y}mm`} diameter="0.75mm" />
))}
</footprint>
}
/>
)
}