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;
}