abse/smartwatch-flex-top-only
Three-panel flex smartwatch with two parallel R3.9 bends, all components flat-top, low-profile JST connector harnesses, split heartbeat optical windows and screw-retained PCB mounting. Modeled 39 × 50.5 × 14.5mm prototype; 100% routing, zero native DRC and shorts. Hardware qualification pending.
- Version
- 0.2.20
- License
- unset
- Stars
- 0
scripts/audit-native-drills.mjs
import fs from 'node:fs'
import {isAuthorizedThermalVia} from './thermal-via-policy.mjs'
import {createHash} from 'node:crypto'
import {copperModel} from './copper-model.mjs'
import {gap} from './copper-geometry.mjs'
const [cacheFile,circuitFile,reportFile] = process.argv.slice(2)
const cache = JSON.parse(fs.readFileSync(cacheFile))
const circuit = JSON.parse(fs.readFileSync(circuitFile))
const allLayers = ['bottom','inner1','inner2','top']
const vias = circuit.filter(e=>e.type==='pcb_via')
const holes = [...vias,...circuit.filter(e=>e.type==='pcb_plated_hole')]
const pads = copperModel(cache,circuit).filter(s=>s.pad)
const failures = []
let minHoleGap = Infinity, minViaPadGap = Infinity
for (const v of vias) {
if (Math.abs(v.outer_diameter-.45)>1e-8 || Math.abs(v.hole_diameter-.3)>1e-8 || JSON.stringify([...v.layers].sort())!==JSON.stringify(allLayers))
failures.push({id:v.pcb_via_id,error:'Unexpected manufacturing via dimensions or layers'})
const shape = {kind:'circle',x:v.x,y:v.y,r:v.outer_diameter/2}
for(const k of circuit.filter(e=>e.type==='pcb_keepout')){if(k.shape!=='rect')throw Error('Unsupported native via keepout');const q={kind:'polygon',points:[{x:k.center.x-k.width/2,y:k.center.y-k.height/2},{x:k.center.x+k.width/2,y:k.center.y-k.height/2},{x:k.center.x+k.width/2,y:k.center.y+k.height/2},{x:k.center.x-k.width/2,y:k.center.y+k.height/2}]};if(gap(shape,q)<-1e-6)failures.push({id:v.pcb_via_id,keepout:k.pcb_keepout_id,error:'Via copper overlaps PCB keepout'})}
for (const p of pads) {
const d=gap(shape,p)
if(isAuthorizedThermalVia(v,p,circuit,cache))continue
minViaPadGap=Math.min(minViaPadGap,d)
if (d<.001-1e-6) failures.push({id:v.pcb_via_id,pad:p.id,gapMM:d,error:'Via annulus overlaps a component land'})
}
}
for (let i=0;i<holes.length;i++) for (let j=i+1;j<holes.length;j++) {
const a=holes[i],b=holes[j]
const d=Math.hypot(a.x-b.x,a.y-b.y)-(a.hole_diameter+b.hole_diameter)/2
minHoleGap=Math.min(minHoleGap,d)
if (d<.2-1e-6) failures.push({a:a.pcb_via_id??a.pcb_plated_hole_id,b:b.pcb_via_id??b.pcb_plated_hole_id,gapMM:d,error:'Manufacturing drill clearance'})
}
const expected = cache.traces.flatMap(t=>t.route.filter(p=>p.route_type==='via').map(p=>({trace:t.pcb_trace_id,x:p.x,y:p.y})))
for (const p of expected) if (!vias.some(v=>v.pcb_trace_id===p.trace&&Math.hypot(v.x-p.x,v.y-p.y)<1e-7))
failures.push({...p,error:'Cached transition has no native manufacturing via'})
const coilComponent=circuit.find(e=>e.type==='source_component'&&['LRX','L6'].includes(e.name))
const coilPcb=circuit.find(e=>e.type==='pcb_component'&&e.source_component_id===coilComponent?.source_component_id)
const coilTrace=circuit.find(e=>e.type==='pcb_trace'&&e.pcb_component_id===coilPcb?.pcb_component_id)
const coilVias=vias.filter(v=>v.pcb_trace_id===coilTrace?.pcb_trace_id)
if (coilVias.length!==2) failures.push({error:'Printed coil must have two native series transition vias',actual:coilVias.length})
if (vias.length!==expected.length+coilVias.length) failures.push({error:'Native/cache via counts differ',native:vias.length,cache:expected.length,coil:coilVias.length})
const report={circuitFile,sourceSha256:createHash('sha256').update(fs.readFileSync(circuitFile)).digest('hex'),cacheFile,cacheSha256:createHash('sha256').update(fs.readFileSync(cacheFile)).digest('hex'),nativeViaCount:vias.length,componentPlatedHoleCount:holes.length-vias.length,coilViaCount:coilVias.length,minHoleGapMM:minHoleGap,minViaPadGapMM:minViaPadGap,failures,status:failures.length?'FAIL':'PASS'}
fs.writeFileSync(reportFile,JSON.stringify(report,null,2)+'\n')
console.log('All native manufacturing drills:',report.status,failures.length,'failures')
if(failures.length)process.exitCode=1