seveibar/pd-dual-stepper-controller

Bottom-side PCB silkscreen artwork consisting of embedded mirrored TS Circuit SVG logos, with via-clearance-aware lower-board placement.

Version
0.1.0
License
unset
Stars
0

scripts/freerouting_geometry.py

"""Export actual placed copper geometry to a local Specctra routing job.
The result must be imported and independently checked before use.
"""
import json,sys,math,re
from pathlib import Path
from shapely.geometry import Point,LineString,box,Polygon
from shapely.affinity import translate,rotate
from copper_geometry import trace_segments
j=json.load(open('work/freerouting/placement.json'));plan=json.load(open('dist/revision/route-plan.json'))
for e in j:
 if e.get('shape')=='polygon' and 'points' in e and 'x' not in e:
  center=Polygon([(p['x'],p['y']) for p in e['points']]).centroid;e['x'],e['y']=center.x,center.y
parent={}
def root(x):
 parent.setdefault(x,x)
 if parent[x]!=x:parent[x]=root(parent[x])
 return parent[x]
for e in j:
 ids=e.get('connected_source_port_ids',[])+e.get('connected_source_net_ids',[]) if e['type']=='source_trace' else e.get('source_port_ids',[]) if e['type']=='source_component_internal_connection' else []
 for x in ids[1:]:parent[root(x)]=root(ids[0])
pp={e['pcb_port_id']:e for e in j if e['type']=='pcb_port'}
sc={e['source_component_id']:e for e in j if e['type']=='source_component'}
sp={e['source_port_id']:e for e in j if e['type']=='source_port'}
st={e['source_trace_id']:e for e in j if e['type']=='source_trace'}
pt={e['pcb_trace_id']:e for e in j if e['type']=='pcb_trace'}
def enet(e):
 if e.get('pcb_port_id') in pp:return root(pp[e['pcb_port_id']]['source_port_id'])
 if e.get('source_trace_id') in st:
  t=st[e['source_trace_id']];return root((t.get('connected_source_port_ids',[])+t.get('connected_source_net_ids',[]))[0])
 if e.get('source_net_id'):return root(e['source_net_id'])
 if e.get('pcb_trace_id') in pt:return enet(pt[e['pcb_trace_id']])
 return 'UNCONNECTED_'+e.get(e['type']+'_id','unknown')
layers=['top','inner1','inner2','bottom'];u=lambda n: str(round(n*1000,4));xy=lambda x,y:u(x)+' '+u(y)

def geom(e,prefix=''):
 if e.get('shape')=='polygon':return Polygon([(p['x'],p['y']) for p in e['points']])
 shape=e.get('shape',e.get('hole_shape','circle'));x,y=e['x'],e['y']
 if shape=='circle':g=Point(0,0).buffer(e.get(prefix+'diameter',e.get('radius',0)*2)/2,quad_segs=12)
 else:
  w,h=e[prefix+'width'],e[prefix+'height'];r=min(w,h)/2 if 'pill' in shape or shape=='oval' else e.get('corner_radius',0)
  g=box(-w/2,-h/2,w/2,h/2) if not r else (LineString([(-w/2+r,-h/2+r),(w/2-r,h/2-r)]).buffer(r,quad_segs=12) if min(w,h)<=2*r+1e-8 else box(-w/2+r,-h/2+r,w/2-r,h/2-r).buffer(r,quad_segs=12))
 return translate(rotate(g,e.get('ccw_rotation',0),origin=(0,0)),x,y)
def polygon(g,l):return f'(polygon {l} 0 '+' '.join(xy(x,y) for x,y in list(g.exterior.coords)[:-1])+')'