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/copper-geometry.mjs
const eps=1e-6,cross=(a,b,c)=>(b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x)
const dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y)
function ptSeg(p,a,b){const dx=b.x-a.x,dy=b.y-a.y,u=Math.max(0,Math.min(1,((p.x-a.x)*dx+(p.y-a.y)*dy)/(dx*dx+dy*dy||1)));return Math.hypot(p.x-a.x-u*dx,p.y-a.y-u*dy)}
function lineTouch(a,b,c,d){const u=cross(a,b,c),v=cross(a,b,d),w=cross(c,d,a),z=cross(c,d,b);if(((u>eps&&v<-eps)||(u<-eps&&v>eps))&&((w>eps&&z<-eps)||(w<-eps&&z>eps)))return true;return Math.min(ptSeg(a,c,d),ptSeg(b,c,d),ptSeg(c,a,b),ptSeg(d,a,b))<eps}
function segDist(a,b,c,d){return lineTouch(a,b,c,d)?0:Math.min(ptSeg(a,c,d),ptSeg(b,c,d),ptSeg(c,a,b),ptSeg(d,a,b))}
function inside(p,poly){let b=false;for(let i=0,j=poly.length-1;i<poly.length;j=i++){const a=poly[i],c=poly[j];if(ptSeg(p,a,c)<eps)return true;if((a.y>p.y)!==(c.y>p.y)&&p.x<(c.x-a.x)*(p.y-a.y)/(c.y-a.y)+a.x)b=!b}return b}
const edges=p=>p.map((v,i)=>[v,p[(i+1)%p.length]])
function gap(a,b){
if(b.kind==='pour'&&a.kind!=='pour')return gap(b,a)
if(a.kind==='pour'){
if(b.kind==='circle')return pourInside(b,a)?-b.r:Math.min(...pourEdges(a).map(([u,v])=>ptSeg(b,u,v)))-b.r
if(b.kind==='segment')return pourInside(b.a,a)||pourInside(b.b,a)?-b.r:Math.min(...pourEdges(a).map(([u,v])=>segDist(b.a,b.b,u,v)))-b.r
if(b.kind==='polygon'){
if(b.points.some(p=>pourInside(p,a))||a.outer.some(p=>inside(p,b.points)))return -1
return edgeGap(pourEdges(a),edges(b.points))
}
if(b.kind==='pour'){
if(a.outer.some(p=>pourInside(p,b))||b.outer.some(p=>pourInside(p,a)))return -1
return edgeGap(edges(a.outer),edges(b.outer))
}
}
if(a.kind==='polygon'&&b.kind!=='polygon')return gap(b,a)
if(a.kind==='circle'&&b.kind==='circle')return dist(a,b)-a.r-b.r
if(a.kind==='segment'&&b.kind==='circle')return ptSeg(b,a.a,a.b)-a.r-b.r
if(a.kind==='circle'&&b.kind==='segment')return gap(b,a)
if(a.kind==='segment'&&b.kind==='segment')return segDist(a.a,a.b,b.a,b.b)-a.r-b.r
if(a.kind==='circle'&&b.kind==='polygon')return inside(a,b.points)?-a.r:Math.min(...edges(b.points).map(([u,v])=>ptSeg(a,u,v)))-a.r
if(a.kind==='segment'&&b.kind==='polygon')return inside(a.a,b.points)||inside(a.b,b.points)?-a.r:Math.min(...edges(b.points).map(([u,v])=>segDist(a.a,a.b,u,v)))-a.r
if(a.kind==='polygon'&&b.kind==='polygon'){if(inside(a.points[0],b.points)||inside(b.points[0],a.points))return -1;return Math.min(...edges(a.points).flatMap(([u,v])=>edges(b.points).map(([s,t])=>segDist(u,v,s,t))))}
throw Error('Unhandled geometry')
}
export {gap,dist,ptSeg,segDist,inside}