abse/smartwatch-v1-pcb
Routed bare ESP32-S3 smartwatch with SDR PSRAM, touch AMOLED, Qi receiver and compact/exploded 39 × 50.5 × 14.5 mm provisional assembly.
- Version
- 0.2.0
- License
- unset
- Stars
- 0
routing/SavedAutorouter.ts
import type { GenericLocalAutorouter } from '@tscircuit/core'
// This cache is for reviewed geometry, including manual corrections. Every
// replay still runs tscircuit's normal board DRC and independent shorts checks.
export function routingFingerprint(input:any):string {
const keys=['layerCount','bounds','outline','allowBlindAndBuriedVias','allowViaInPad','minTraceWidth','nominalTraceWidth','minViaHoleDiameter','minViaPadDiameter','minTraceToPadEdgeClearance','minTraceToHoleEdgeClearance','minViaEdgeToPadEdgeClearance','minViaHoleEdgeToViaHoleEdgeClearance','minPlatedHoleDrillEdgeToDrillEdgeClearance','minPadEdgeToPadEdgeClearance','minBoardEdgeClearance']
const value:any=Object.fromEntries(keys.map(k=>[k,input[k]]))
// Linux and macOS trig routines can differ in the final binary digit of
// rounded board corners. Normalize only outline coordinates to 1e-9 mm;
// pads, nets, keepouts, dimensions and manufacturing rules remain exact.
value.outline=input.outline?.map((p:any)=>({...p,x:Math.round(p.x*1e9)/1e9,y:Math.round(p.y*1e9)/1e9}))
value.connections=input.connections.map((c:any)=>({name:c.name,nominalTraceWidth:c.nominalTraceWidth,pointsToConnect:c.pointsToConnect.map((p:any)=>({x:p.x,y:p.y,layer:p.layer,layers:p.layers,pcb_port_id:p.pcb_port_id}))}))
value.obstacles=input.obstacles.map((o:any)=>({type:o.type,center:o.center,width:o.width,height:o.height,layers:o.layers,connectedTo:o.connectedTo,componentId:o.componentId,isNonPlatedHole:o.isNonPlatedHole}))
return JSON.stringify(value)
}
export function createSavedAutorouter(input:any,cache:{input:any,traces:any[],sourceTraceIdByNet?:Record<string,string>}):GenericLocalAutorouter {
if(routingFingerprint(input)!==routingFingerprint(cache.input)){
const differences:any[]=[]
const compare=(a:any,b:any,path:string)=>{
if(differences.length>=12||JSON.stringify(a)===JSON.stringify(b))return
if(a&&b&&typeof a==='object'&&typeof b==='object'){
for(const key of new Set([...Object.keys(a),...Object.keys(b)]))compare(a[key],b[key],`${path}.${key}`)
}else differences.push({path,current:a,saved:b})
}
compare(JSON.parse(routingFingerprint(input)),JSON.parse(routingFingerprint(cache.input)),'input')
throw new Error('Saved routing does not match current pin locations, nets, keepouts or design rules. Differences: '+JSON.stringify(differences))
}
const callbacks:Record<string,((event:any)=>void)[]>={}
const traces=cache.traces.map(t=>({...t,...cache.sourceTraceIdByNet?.[t.connection_name]?{source_trace_id:cache.sourceTraceIdByNet[t.connection_name]}:{}}))
const router:GenericLocalAutorouter={
input,isRouting:false,
on(event,callback){(callbacks[event]??=[]).push(callback)},
start(){router.isRouting=true;queueMicrotask(()=>{if(!router.isRouting)return;router.isRouting=false;for(const fn of callbacks.complete??[])fn({traces})})},
stop(){router.isRouting=false},
solveSync(){return traces},
getOutputSimpleRouteJson(){return {...input,traces}},
}
return router
}