molecule
Evan — Adom Molecule LED Nameplate
Public Made by Adomby adom
Evan's name in 66 green 0603 LEDs on a 5x7 dot-matrix grid, USB-C powered off the east edge. A real Adom molecule: four corner machine pins, 96x44 mm absolute, every part from the Adom Basic Parts catalog.
main
John Lauer
README.html with embedded 3D walkthrough viewer, full BOM and schematic, source bundle, build outputs
23c1d5e
2mo ago
import fs from "fs"
const b = fs.readFileSync("dist/lib/index/3d.glb")
const g = JSON.parse(b.slice(20, 20 + b.readUInt32LE(12)).toString("utf8"))
const mat = n => { if (n.matrix) return n.matrix.slice()
const t=n.translation||[0,0,0], r=n.rotation||[0,0,0,1], s=n.scale||[1,1,1], [x,y,z,w]=r
return [(1-2*(y*y+z*z))*s[0],(2*(x*y+z*w))*s[0],(2*(x*z-y*w))*s[0],0,
(2*(x*y-z*w))*s[1],(1-2*(x*x+z*z))*s[1],(2*(y*z+x*w))*s[1],0,
(2*(x*z+y*w))*s[2],(2*(y*z-x*w))*s[2],(1-2*(x*x+y*y))*s[2],0,t[0],t[1],t[2],1] }
const mul=(a,b)=>{const o=new Array(16);for(let c=0;c<4;c++)for(let r=0;r<4;r++){let v=0;for(let k=0;k<4;k++)v+=a[k*4+r]*b[c*4+k];o[c*4+r]=v}return o}
const xf=(m,p)=>[m[0]*p[0]+m[4]*p[1]+m[8]*p[2]+m[12],m[1]*p[0]+m[5]*p[1]+m[9]*p[2]+m[13],m[2]*p[0]+m[6]*p[1]+m[10]*p[2]+m[14]]
const I=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
function bbox(idx,parent,acc){const n=g.nodes[idx],m=mul(parent,mat(n))
if(n.mesh!=null)for(const p of g.meshes[n.mesh].primitives){const a=g.accessors[p.attributes.POSITION];if(!a.min)continue
for(let i=0;i<8;i++){const c=[(i&1)?a.max[0]:a.min[0],(i&2)?a.max[1]:a.min[1],(i&4)?a.max[2]:a.min[2]],w=xf(m,c)
for(let k=0;k<3;k++){acc.min[k]=Math.min(acc.min[k],w[k]);acc.max[k]=Math.max(acc.max[k],w[k])}}}
for(const c of n.children||[])bbox(c,m,acc)}
const bb = name => { const i=g.nodes.findIndex(n=>n.name===name); if(i<0) return null
const acc={min:[1e9,1e9,1e9],max:[-1e9,-1e9,-1e9]}; bbox(i,I,acc); return acc }
const j1=bb("J1"), bd=bb("Box0")
// GLB X = -pcbX (verified against MP1..MP4 and D0)
const j1PcbMin=-j1.max[0], j1PcbMax=-j1.min[0]
const edge=bd.max[0]
const cj=JSON.parse(fs.readFileSync("dist/lib/index/circuit.json","utf8"))
const sc=cj.find(x=>x.type==="source_component"&&x.name==="J1")
const pc=cj.find(x=>x.type==="pcb_component"&&x.source_component_id===sc.source_component_id)
const pads=cj.filter(x=>x.type==="pcb_smtpad"&&x.pcb_component_id===pc.pcb_component_id)
const padRow=pads.reduce((s,p)=>s+p.x,0)/pads.length
console.log(`board east edge = ${edge.toFixed(3)}`)
console.log(`pad row x = ${padRow.toFixed(3)}`)
console.log(`pcb_component ctr = ${pc.center.x.toFixed(3)}`)
console.log(`shell pcb x range = ${j1PcbMin.toFixed(3)} .. ${j1PcbMax.toFixed(3)} (depth ${(j1PcbMax-j1PcbMin).toFixed(3)})`)
console.log(` back vs pad row = ${(j1PcbMin-padRow).toFixed(3)} mm (target 0)`)
console.log(` mouth vs edge = ${(j1PcbMax-edge).toFixed(3)} mm (target +0.4 proud)`)
const backErr = Math.abs(j1PcbMin - padRow)
const proud = j1PcbMax - edge
let bad = false
if (backErr > 0.3) { console.error(`FAIL: shell back is ${backErr.toFixed(2)} mm off the pad row`); bad = true }
if (proud < 0.1 || proud > 1.0) { console.error(`FAIL: mouth is ${proud.toFixed(2)} mm past the edge; want 0.1..1.0`); bad = true }
const holes = cj.filter(x => (x.type === "pcb_plated_hole" || x.type === "pcb_hole") && x.pcb_component_id === pc.pcb_component_id)
const maxCopper = Math.max(
...pads.map(p => p.x + (p.width || 0) / 2),
...holes.map(h => h.x + (h.outer_width ?? h.hole_diameter ?? 0) / 2),
)
if (maxCopper > edge) { console.error(`FAIL: copper reaches ${maxCopper.toFixed(2)} > board edge ${edge}`); bad = true }
console.log(`easternmost copper = ${maxCopper.toFixed(3)} (edge ${edge}, clearance ${(edge - maxCopper).toFixed(3)})`)
if (bad) process.exit(1)
console.log("OK: USB-C placement verified against the rendered GLB")