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
}