another go at circuit boards
Log in to post a comment.
// Generative PCB Artwork — rev‑3 (27 Apr 2025) // Fixes: // • Components & pads now ALWAYS stay inside the board outline (margin). // • Pads remember their parent component, so we can exit traces cleanly. // • Traces leave each pad at least one GRID cell before routing, so no // lines cross component bodies. // • BFS router uses those exit points and respects component clearance. // • Added pad‑to‑edge clearance to placement, so nothing pokes outside. // ----------------------------------------------------------- const BOARD_W = 180; const BOARD_H = 120; const GRID = 4; const MARGIN = GRID * 3; // board edge clearance for bodies & pads // component counts const IC_COUNT = 4; const R_COUNT = 6; const C_COUNT = 4; // net params const NETS_PER_COMP = 2; // pad size const PAD_R_MIN = 0.8; const PAD_R_MAX = 1.2; // turtle helpers ------------------------------------------------------------- const turtle = new Turtle(); Turtle.prototype.jump = function(x,y){this.penup();this.goto(x,y);this.pendown();}; // board coords & grid -------------------------------------------------------- const X0 = -BOARD_W/2 + MARGIN; const Y0 = -BOARD_H/2 + MARGIN; const X1 = BOARD_W/2 - MARGIN; const Y1 = BOARD_H/2 - MARGIN; function snap(v){return Math.round(v/GRID)*GRID;} function inBoard(x,y){return x>=X0&&x<=X1&&y>=Y0&&y<=Y1;} // occupancy grid ------------------------------------------------------------- const NX = Math.floor((X1-X0)/GRID)+1; const NY = Math.floor((Y1-Y0)/GRID)+1; const occ = Array.from({length:NX},()=>new Uint8Array(NY)); function gi(x){return Math.round((x-X0)/GRID);} // 0..NX‑1 function gj(y){return Math.round((y-Y0)/GRID);} // 0..NY‑1 function mark(x,y){const i=gi(x), j=gj(y); if(i>=0&&i<NX&&j>=0&&j<NY) occ[i][j]=1; } function isOcc(x,y){const i=gi(x), j=gj(y); return (i<0||i>=NX||j<0||j>=NY) || occ[i][j]; } // data ----------------------------------------------------------------------- const comps=[]; // {id,type,x,y,w,h,pads:[{x,y,comp}],bbox:{}} const nets=[]; // {a:{x,y,comp}, b:{x,y,comp}} let compId=0; // utilities ------------------------------------------------------------------ function drawRect(cx,cy,w,h){ turtle.jump(cx-w/2, cy-h/2); turtle.goto(cx+w/2, cy-h/2); turtle.goto(cx+w/2, cy+h/2); turtle.goto(cx-w/2, cy+h/2); turtle.goto(cx-w/2, cy-h/2); } function shuffle(arr){for(let i=arr.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[arr[i],arr[j]]=[arr[j],arr[i]];}return arr;} // component placement -------------------------------------------------------- function canPlace(gx,gy,wg,hg){ for(let x=gx-GRID;x<gx+(wg+1)*GRID;x+=GRID){ for(let y=gy-GRID;y<gy+(hg+1)*GRID;y+=GRID){ if(isOcc(x,y)) return false; } } return true; } function reserveArea(gx,gy,wg,hg){ // reserve body + 1‑cell clearance for(let x=gx-GRID;x<gx+(wg+1)*GRID;x+=GRID){ for(let y=gy-GRID;y<gy+(hg+1)*GRID;y+=GRID){ mark(x,y); } } } function addPad(cx,cy,comp){ const r = PAD_R_MIN + Math.random()*(PAD_R_MAX-PAD_R_MIN); turtle.jump(cx,cy-r); turtle.circle(r); mark(cx,cy); return {x:cx,y:cy,comp}; } function addComponent(type,wg,hg){ for(let t=0;t<400;t++){ const gx = snap(X0+GRID) + Math.floor(Math.random()*(NX-wg-2))*GRID; const gy = snap(Y0+GRID) + Math.floor(Math.random()*(NY-hg-2))*GRID; if(!canPlace(gx,gy,wg,hg)) continue; reserveArea(gx,gy,wg,hg); const c = {id:compId++, type, x:gx, y:gy, w:wg*GRID, h:hg*GRID, pads:[]}; comps.push(c); drawComponent(c); return c; } return null; } function drawComponent(c){ drawRect(c.x+c.w/2, c.y+c.h/2, c.w, c.h); if(c.type==='IC'){ // pads on each side every GRID cell const perim=[]; for(let x=c.x;x<=c.x+c.w;x+=GRID) perim.push({x,y:c.y-GRID}); // top for(let y=c.y;y<=c.y+c.h;y+=GRID) perim.push({x:c.x+c.w+GRID,y}); // right for(let x=c.x+c.w;x>=c.x;x-=GRID) perim.push({x,y:c.y+c.h+GRID}); // bottom for(let y=c.y+c.h;y>=c.y;y-=GRID) perim.push({x:c.x-GRID,y}); // left shuffle(perim).slice(0,Math.max(4,perim.length/3)).forEach(p=>{ if(inBoard(p.x,p.y)) c.pads.push(addPad(p.x,p.y,c)); }); }else{ // R/C two pads opposite const p1={x:c.x-GRID,y:c.y+c.h/2}; const p2={x:c.x+c.w+GRID,y:c.y+c.h/2}; if(inBoard(p1.x,p1.y)) c.pads.push(addPad(p1.x,p1.y,c)); if(inBoard(p2.x,p2.y)) c.pads.push(addPad(p2.x,p2.y,c)); } } // netlist -------------------------------------------------------------------- function buildNets(){ for(const c of comps){ for(let k=0;k<NETS_PER_COMP;k++){ const other = comps[Math.floor(Math.random()*comps.length)]; if(other===c) continue; const padA = c.pads[Math.floor(Math.random()*c.pads.length)]; const padB = other.pads[Math.floor(Math.random()*other.pads.length)]; nets.push({a:padA,b:padB}); } } } // routing helpers ------------------------------------------------------------ const DIRS = [[GRID,0],[-GRID,0],[0,GRID],[0,-GRID]]; function exitPoint(pad){ const comp = pad.comp; let ex=pad.x, ey=pad.y; if(pad.x < comp.x) ex = pad.x - GRID; else if(pad.x > comp.x+comp.w) ex = pad.x + GRID; else if(pad.y < comp.y) ey = pad.y - GRID; else ey = pad.y + GRID; return {x:ex,y:ey}; } function bfs(ax,ay,bx,by){ const q=[[ax,ay]]; const seen=new Set([`${ax},${ay}`]); const prev={}; while(q.length){ const [x,y]=q.shift(); if(x===bx&&y===by) break; for(const[dx,dy] of DIRS){ const nx=x+dx, ny=y+dy; const key=`${nx},${ny}`; if(!inBoard(nx,ny)||isOcc(nx,ny)||seen.has(key)) continue; seen.add(key); prev[key]=`${x},${y}`; q.push([nx,ny]); } } const path=[]; let key=`${bx},${by}`; if(!prev[key]) return []; while(key!==`${ax},${ay}`){ const [px,py]=key.split(',').map(Number); path.unshift([px,py]); key=prev[key]; } return path; } function drawTrace(pts){ turtle.jump(pts[0][0],pts[0][1]); for(let i=1;i<pts.length;i++){ turtle.goto(pts[i][0],pts[i][1]); mark(pts[i][0],pts[i][1]); } } function routeNets(){ nets.forEach(net=>{ const e1 = exitPoint(net.a); const e2 = exitPoint(net.b); const path = bfs(e1.x,e1.y,e2.x,e2.y); if(path.length){ drawTrace([[net.a.x,net.a.y], [e1.x,e1.y], ...path, [e2.x,e2.y], [net.b.x,net.b.y]]); } }); } // outline -------------------------------------------------------------------- function drawOutline(){ const bx0=-BOARD_W/2, by0=-BOARD_H/2, bx1=BOARD_W/2, by1=BOARD_H/2; turtle.jump(bx0,by0); turtle.goto(bx1,by0); turtle.goto(bx1,by1); turtle.goto(bx0,by1); turtle.goto(bx0,by0); } // main ----------------------------------------------------------------------- function walk(i){ if(i===0){ drawOutline(); for(let k=0;k<IC_COUNT;k++) addComponent('IC', 3+Math.floor(Math.random()*3), 3+Math.floor(Math.random()*2)); for(let k=0;k<R_COUNT;k++) addComponent('R', 2,1); for(let k=0;k<C_COUNT;k++) addComponent('C', 1,1); buildNets(); routeNets(); } return false; }