imrishabh18/rp2040-motor-controller

A library of PCB component definitions covering MOSFETs, motor drivers, USB-PD control, sensors, current monitors, transistors, diodes, resistors, speakers, connectors, and mirrored bottom-side logos, with pin mappings, footprints, and 3D models.

Version
1.0.55
License
unset
Stars
5

scripts/find-top-pad-conflicts.py

"""Identify retained routes that conflict with the revised placement."""
import json
from pathlib import Path
from shapely.geometry import Point, Polygon, LineString, box
from shapely.affinity import rotate, translate
from shapely.strtree import STRtree
from copper_geometry import trace_segments

folder = Path('dist/top-assembly')
j = json.loads(Path('dist/pd-control/unrouted.json').read_text())
plan = json.loads((folder/'route-plan.json').read_text())
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])
ports = {e['pcb_port_id']: root(e['source_port_id']) for e in j if e['type']=='pcb_port' and e.get('source_port_id')}
sources = {e['source_trace_id']: e for e in j if e['type']=='source_trace'}
def net(t):
    if t.get('source_net_id'): return root(t['source_net_id'])
    s = sources.get(t.get('source_trace_id'), {})
    ids = s.get('connected_source_port_ids', []) + s.get('connected_source_net_ids', [])
    if ids: return root(ids[0])
    return ports[t['connectsTo'][0]]
def pad(e, prefix=''):
    shape=e.get('shape',e.get('hole_shape','circle'))
    if 'polygon' in shape: return Polygon([(p['x'],p['y']) for p in e['points']])
    if shape=='circle': g=Point(0,0).buffer(e.get(prefix+'diameter',e.get('radius',0)*2)/2)
    else:
        w,h=e[prefix+'width'],e[prefix+'height'];r=min(w,h)/2 if 'pill' in shape else e.get('corner_radius',0)
        if r: g=LineString([(-w/2+r,-h/2+r),(w/2-r,h/2-r)]).buffer(r)
        else: g=box(-w/2,-h/2,w/2,h/2)
    return translate(rotate(g,e.get('ccw_rotation',0),origin=(0,0)),e.get('x',0),e.get('y',0))
shapes=[];info=[]
for e in j:
    if e['type']=='pcb_smtpad':
        shapes.append(pad(e));info.append((e['layer'], ports.get(e.get('pcb_port_id')), e['pcb_smtpad_id']))
    elif e['type']=='pcb_plated_hole':
        for layer in e['layers']:
            shapes.append(pad(e,'outer_'));info.append((layer, ports.get(e.get('pcb_port_id')),e['pcb_plated_hole_id']))
tree=STRtree(shapes); conflicts={}; bad_indices={}
for t in plan['traces']:
    n=net(t); hits=set()
    for route_index in range(len(t['route'])-1):
        for a,b,layer,width in trace_segments(t['route'][route_index:route_index+2]):
            segment=LineString([a,b])
            for index in tree.query(segment.buffer(width/2+.151)):
                l,other,identifier=info[index]
                if l==layer and other!=n and segment.distance(shapes[index]) < width/2+.1-1e-6:
                    hits.add(identifier)
                    bad_indices.setdefault(t['pcb_trace_id'],set()).update([route_index,route_index+1])
    for route_index,p in enumerate(t['route']):
        if p['route_type']!='via': continue
        point=Point(p['x'],p['y']);radius=p.get('via_diameter',.5)/2
        for index in tree.query(point.buffer(radius+.126)):
            _,other,identifier=info[index]
            if other!=n and point.distance(shapes[index])<radius+.125-1e-6:
                hits.add(identifier);bad_indices.setdefault(t['pcb_trace_id'],set()).add(route_index)
    if hits: conflicts[t['pcb_trace_id']]=sorted(hits)
Path('reports/top-pad-conflicts.json').write_text(json.dumps(conflicts,indent=2))
Path('reports/top-pad-conflict-indices.json').write_text(json.dumps({k:sorted(v) for k,v in bad_indices.items()},indent=2))
print(json.dumps(conflicts,indent=2))