ShiboSoftwareDev/linux-gameboy-advance
Restores PCB assembly metadata by assigning part numbers and 3D CAD models, removing test-point bodies, and validating top-side SMT solder-paste features without altering copper or pads.
- Version
- 1.0.17
- License
- unset
- Stars
- 0
scripts/route-d10-local.mjs
import fs from "node:fs"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
const routeFile = process.argv[2] ?? "work/optimized-carrier-traces.json"
const circuit = JSON.parse(fs.readFileSync("dist/index/circuit.json", "utf8"))
const traces = JSON.parse(fs.readFileSync(routeFile, "utf8"))
const byId = new Map(traces.map((trace) => [trace.pcb_trace_id, trace]))
const finalTraceById = new Map(circuit.filter((e) => e.type === "pcb_trace")
.map((trace) => [trace.pcb_trace_id, trace]))
for (const trace of circuit.filter((e) => e.type === "pcb_trace")) {
const fixed = byId.get(trace.pcb_trace_id)
if (fixed) trace.route = fixed.route
}
const connMap = getFullConnectivityMapFromCircuitJson(circuit)
const targetId = "source_trace_108_0"
const target = byId.get(targetId)
const startIndex = 12
const endIndex = 16
const start = target.route[startIndex]
const end = target.route[endIndex]
const layer = "inner2"
const bounds = { minX: -10, maxX: 30, minY: 15, maxY: 40.5 }
const pointSegmentDistance = (p, a, b) => {
const dx = b.x-a.x, dy = b.y-a.y, den = dx*dx+dy*dy
const t = den ? Math.max(0,Math.min(1,((p.x-a.x)*dx+(p.y-a.y)*dy)/den)) : 0
return Math.hypot(p.x-(a.x+t*dx),p.y-(a.y+t*dy))
}
const segmentDistance = (a,b,c,d) => {
const cross = (p,q,r)=>(q.x-p.x)*(r.y-p.y)-(q.y-p.y)*(r.x-p.x)
const o1=cross(a,b,c),o2=cross(a,b,d),o3=cross(c,d,a),o4=cross(c,d,b)
if (((o1>0&&o2<0)||(o1<0&&o2>0))&&((o3>0&&o4<0)||(o3<0&&o4>0))) return 0
return Math.min(pointSegmentDistance(a,c,d),pointSegmentDistance(b,c,d),
pointSegmentDistance(c,a,b),pointSegmentDistance(d,a,b))
}
const allTraceRecords = [
...circuit.filter((e)=>e.type==="pcb_trace"&&!byId.has(e.pcb_trace_id)),
...traces.map((route)=>({...finalTraceById.get(route.pcb_trace_id),...route})),
]
const otherSegments=[]
const otherVias=[]
for(const trace of allTraceRecords){
if(connMap.areIdsConnected(targetId,trace.pcb_trace_id))continue
for(let i=0;i<trace.route.length-1;i++){
const a=trace.route[i],b=trace.route[i+1]
if(a.route_type==="wire"&&b.route_type==="wire"&&a.layer===layer&&
Math.max(a.x,b.x)>=bounds.minX&&Math.min(a.x,b.x)<=bounds.maxX&&
Math.max(a.y,b.y)>=bounds.minY&&Math.min(a.y,b.y)<=bounds.maxY)
otherSegments.push({a,b,id:trace.pcb_trace_id})
}
for(const point of trace.route)if(point.route_type==="via"&&
point.x>=bounds.minX&&point.x<=bounds.maxX&&point.y>=bounds.minY&&point.y<=bounds.maxY)otherVias.push({...point,id:trace.pcb_trace_id})
}
const pads=circuit.filter((pad)=>["pcb_smtpad","pcb_plated_hole"].includes(pad.type)&&
(pad.layers??[pad.layer]).includes(layer)&&pad.x>=bounds.minX&&pad.x<=bounds.maxX&&
pad.y>=bounds.minY&&pad.y<=bounds.maxY)
.filter((pad)=>!connMap.areIdsConnected(targetId,pad.pcb_smtpad_id??pad.pcb_plated_hole_id))
const padDistance=(point,pad)=>{
if(pad.type==="pcb_plated_hole"){
const w=pad.outer_diameter??pad.outer_width,h=pad.outer_diameter??pad.outer_height
return Math.hypot(Math.max(Math.abs(point.x-pad.x)-w/2,0),Math.max(Math.abs(point.y-pad.y)-h/2,0))
}
if(pad.shape==="circle")return Math.max(0,Math.hypot(point.x-pad.x,point.y-pad.y)-pad.radius)
const angle=-(pad.ccw_rotation??0)*Math.PI/180,dx=point.x-pad.x,dy=point.y-pad.y
const x=dx*Math.cos(angle)-dy*Math.sin(angle),y=dx*Math.sin(angle)+dy*Math.cos(angle)
return Math.hypot(Math.max(Math.abs(x)-pad.width/2,0),Math.max(Math.abs(y)-pad.height/2,0))
}
const segmentPadDistance=(a,b,pad)=>{
const count=Math.max(1,Math.ceil(Math.hypot(b.x-a.x,b.y-a.y)/0.04));let best=Infinity
for(let i=0;i<=count;i++){const t=i/count;best=Math.min(best,padDistance({x:a.x+(b.x-a.x)*t,y:a.y+(b.y-a.y)*t},pad))}
return best
}
const edgeClear=(a,b)=>
!otherSegments.some((s)=>segmentDistance(a,b,s.a,s.b)<0.221)&&
!otherVias.some((v)=>pointSegmentDistance(v,a,b)<0.386)&&
!pads.some((pad)=>segmentPadDistance(a,b,pad)<0.161)
const nearest = (point) => ({
segment: otherSegments.map((s)=>({d:pointSegmentDistance(point,s.a,s.b),id:s.id}))
.sort((a,b)=>a.d-b.d)[0],
via: otherVias.map((v)=>({d:Math.hypot(point.x-v.x,point.y-v.y),id:v.id,x:v.x,y:v.y}))
.sort((a,b)=>a.d-b.d)[0],
pad: pads.map((pad)=>({d:padDistance(point,pad),id:pad.pcb_smtpad_id??pad.pcb_plated_hole_id}))
.sort((a,b)=>a.d-b.d)[0],
})
console.log(JSON.stringify({obstacles:{segments:otherSegments.length,vias:otherVias.length,pads:pads.length},start:nearest(start),end:nearest(end)},null,2))
const step=.1,key=(p)=>`${Math.round(p.x/step)},${Math.round(p.y/step)}`
const point=(k)=>{const [x,y]=k.split(",").map(Number);return{x:x*step,y:y*step}}
const startKey=key(start),open=[startKey],openSet=new Set(open),closed=new Set()
const came=new Map(),g=new Map([[startKey,0]]),score=new Map([[startKey,Math.hypot(start.x-end.x,start.y-end.y)]])
const dirs=[[-1,0],[1,0],[0,-1],[0,1],[-1,-1],[-1,1],[1,-1],[1,1]]
let goalKey=null
while(open.length&&closed.size<50000){
let bi=0;for(let i=1;i<open.length;i++)if((score.get(open[i])??Infinity)<(score.get(open[bi])??Infinity))bi=i
const ck=open.splice(bi,1)[0];openSet.delete(ck)
const current=ck===startKey?start:point(ck)
if(edgeClear(current,end)){goalKey=ck;break}
closed.add(ck)
for(const [dx,dy] of dirs){
const next={x:Math.round((current.x+dx*step)*10)/10,y:Math.round((current.y+dy*step)*10)/10}
if(next.x<bounds.minX||next.x>bounds.maxX||next.y<bounds.minY||next.y>bounds.maxY)continue
const nk=key(next);if(closed.has(nk)||!edgeClear(current,next))continue
const ng=(g.get(ck)??0)+Math.hypot(dx*step,dy*step)
if(ng>=(g.get(nk)??Infinity))continue
came.set(nk,ck);g.set(nk,ng);score.set(nk,ng+Math.hypot(next.x-end.x,next.y-end.y))
if(!openSet.has(nk)){open.push(nk);openSet.add(nk)}
}
}
if(!goalKey)throw new Error(`No inner2 path after ${closed.size} nodes`)
const path=[];let cursor=goalKey
while(cursor!==startKey){path.unshift(point(cursor));cursor=came.get(cursor)}
path.push(end)
for(let i=0;i<path.length-1;) {
const previous=i===0?start:path[i-1]
if(edgeClear(previous,path[i+1]))path.splice(i,1);else i++
}
const wires=path.slice(0,-1).map((p)=>({route_type:"wire",x:p.x,y:p.y,width:.12,layer}))
target.route.splice(startIndex+1,endIndex-startIndex-1,...wires)
fs.writeFileSync(routeFile,JSON.stringify(traces,null,2)+"\n")
console.log(JSON.stringify({nodes:closed.size,segments:wires.length+1,path:[start,...path]},null,2))