app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
main
John Lauer
Publish 0.2.15
bb17f84
#!/usr/bin/env python3
"""Collision-checked visual associations for a selected silkscreen layout (mm).
Try an enclosure when requested, otherwise a curved pointer to the body's rim.
Never draw across text, pads, holes or another fitted body. Report failures.
"""
import json,argparse,math
from pathlib import Path
from shapely.geometry import shape,box,LineString,Point
from shapely.ops import unary_union,nearest_points
from shapely.prepared import prep
def associate(data):
children=data['placements'];graphics=[];unresolved=[]
for p in children:
ref=p.get('reference');side=p['layer']
if p.get('contactBorder'):
for points in p['associationSegments']:graphics.append(dict(index=p['index'],reference=ref,layer=side,kind='outline',samples=points,contactBorder=True))
continue
if not ref or p.get('role')=='value':continue
target=shape(data.get('targets',{})[str(p['index'])] if str(p['index']) in data.get('targets',{}) else data['bodies'].get(side,{}).get(ref,{'type':'GeometryCollection','geometries':[]}))
if target.is_empty:continue
target=target.buffer(data.get('targetMargin',.02))
own=unary_union([box(*q['box']) for q in children if q['layer']==side and q.get('reference')==ref and (not p.get('pin') or q['index']==p['index'])]);distance=own.distance(target)
blockers=[shape(o['polygon']) for o in data['obstacles'] if o['layer']==side]
blockers += [box(*q['box']).buffer(.025) for q in children if q['layer']==side]
blockers += [LineString(g['samples']).buffer(.06) for g in graphics if g['layer']==side]
blocked=prep(unary_union(blockers))
if data.get('style')=='enclosure' and not p.get('pin'):
members=[box(*q['box']) for q in children if q['layer']==side and q.get('reference')==ref]
enclosure=box(*unary_union([target]+members).bounds).buffer(.15,join_style=2).exterior
if not blocked.intersects(enclosure.buffer(.025)):
samples=list(enclosure.coords);p['associationOutline']=samples;graphics.append(dict(index=p['index'],reference=ref,layer=side,kind='outline',samples=samples));continue
if distance<.20:continue
a0,b0=nearest_points(own,target);a0=list(a0.coords)[0];b0=list(b0.coords)[0];found=None
# Route from the label edge to just outside the target, never across its mask.
a=tuple(a0[j]+(b0[j]-a0[j])*.10/distance for j in [0,1]);b=tuple(b0[j]-(b0[j]-a0[j])*.10/distance for j in [0,1]);dx,dy=b[0]-a[0],b[1]-a[1];length=math.hypot(dx,dy)
if length<=.08:continue
for bend in [.12,-.12,.3,-.3,.6,-.6,1,-1,0]:
c1=(a[0]+dx/3-dy/length*bend,a[1]+dy/3+dx/length*bend);c2=(a[0]+dx*2/3-dy/length*bend,a[1]+dy*2/3+dx/length*bend)
points=[a,c1,c2,b];samples=[tuple((1-t)**3*a[j]+3*(1-t)**2*t*c1[j]+3*(1-t)*t*t*c2[j]+t**3*b[j] for j in [0,1]) for t in [i/40 for i in range(41)]]
if blocked.intersects(LineString(samples).buffer(.035)):continue
found=dict(index=p['index'],reference=ref,layer=side,kind='curve',points=points,samples=samples);p['leaderCurve']=points;graphics.append(found);break
if found is None:unresolved.append(dict(index=p['index'],reference=ref,layer=side,distance=distance,reason='No collision-free direct curved pointer; review enclosure or reroute'))
return dict(placements=children,graphics=graphics,unresolvedAssociations=unresolved)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--input',required=True);p.add_argument('--out',required=True);a=p.parse_args();r=associate(json.loads(Path(a.input).read_text()));Path(a.out).write_text(json.dumps(r,indent=2));print(len(r['graphics']),'pointers;',len(r['unresolvedAssociations']),'association reviews')