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/straighten-native-warning-runs.mjs

import fs from 'node:fs'
import {analyzePcbStyle} from '../tools/pcb-style/analyzer.mjs'
import {cache,base,spatial,candidates,boundarySafe,traceShapes,anchors,len} from './style-candidates.mjs'
import {ptSeg,gap} from './copper-geometry.mjs'
const original=structuredClone(cache),ids=new Set(base.filter(e=>e.type==='pcb_trace_style_warning').map(e=>e.pcb_trace_id)),changes=[],rejected=[]
const angleWarnings=t=>analyzePcbStyle([t]).issues.filter(e=>e.lineItemType==='PcbTraceSegmentOddAngle')
for(const t of cache.traces.filter(t=>ids.has(t.pcb_trace_id))){
 const before=structuredClone(t),oldShapes=traceShapes(t);for(const s of oldShapes)spatial.remove(s)
 for(const issue of angleWarnings(t).sort((a,b)=>b.startRouteIndex-a.startRouteIndex)){
  const lo=issue.startRouteIndex,hi=issue.endRouteIndex,old=t.route.slice(lo,hi+1)
  if(old.length<2||old.some(p=>p.route_type!=='wire'||p.layer!==old[0].layer||p.width!==old[0].width)){rejected.push({trace:t.pcb_trace_id,lo,hi,reason:'Mixed layers, widths or vias'});continue}
  const fixed=anchors(before).filter(i=>i>lo&&i<hi)
  const options=candidates(old).filter(r=>r.every((p,i)=>!i||Math.abs(Math.sin(Math.atan2(p.y-r[i-1].y,p.x-r[i-1].x)*4))<1e-6))
  let selected
  for(const path of options){
   const ss=traceShapes({...t,route:path})
   if(ss.some(s=>!boundarySafe(s)||spatial.violations(s).length))continue
   // Retain every real same-net junction/land contact that anchored this run.
   if(fixed.some(i=>!ss.some(s=>ptSeg(before.route[i],s.a,s.b)<=1e-7)))continue
   let drift=0;for(const s of ss)for(let j=0;j<=12;j++){const p={x:s.a.x+(s.b.x-s.a.x)*j/12,y:s.a.y+(s.b.y-s.a.y)*j/12};drift=Math.max(drift,Math.min(...old.slice(1).map((q,i)=>ptSeg(p,old[i],q))))}
   if(drift>.75)continue
   const route=[...t.route.slice(0,lo),...path,...t.route.slice(hi+1)]
   if(angleWarnings({...t,route}).length>=angleWarnings(t).length)continue
   selected={route,drift};break
  }
  if(selected){changes.push({trace:t.pcb_trace_id,lo,hi,oldPoints:old.length,newPoints:selected.route.length-(t.route.length-old.length),maxDriftMM:selected.drift});t.route=selected.route}else rejected.push({trace:t.pcb_trace_id,lo,hi,reason:'No45degree candidate preserved clearance and actual junction anchors'})
 }
 for(const s of traceShapes(t))spatial.add(s)
}
for(let i=0;i<cache.traces.length;i++){
 const a=original.traces[i],b=cache.traces[i];for(const key of ['pcb_trace_id','connection_name'])if(a[key]!==b[key])throw Error('Changed trace identity')
 if(JSON.stringify(a.route.filter(p=>p.route_type==='via'))!==JSON.stringify(b.route.filter(p=>p.route_type==='via')))throw Error('Moved via')
 if(JSON.stringify(a.route[0])!==JSON.stringify(b.route[0])||JSON.stringify(a.route.at(-1))!==JSON.stringify(b.route.at(-1)))throw Error('Moved endpoint')
}
const report={changes,rejected,remainingOddAngleWarnings:cache.traces.flatMap(angleWarnings).length,changedTraces:cache.traces.filter((t,i)=>JSON.stringify(t)!==JSON.stringify(original.traces[i])).map(t=>t.pcb_trace_id)}
fs.writeFileSync('reports/warning-cleanup/style-changes.json',JSON.stringify(report,null,2)+'\n');fs.writeFileSync('routing/final/cache.json',JSON.stringify(cache,null,2)+'\n');console.log(JSON.stringify(report,null,2))