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
"""Independent polygon preflight for text, pointer strokes and enclosure strokes.
Inputs must include native mask polygons, fitted bodies, holes and existing artwork.
Fail closed for missing required inventory or invalid association geometry.
"""
import json,argparse
from pathlib import Path
from shapely.geometry import shape,box,LineString
from shapely.strtree import STRtree
def check(data):
errors=[];placements=data['placements'];obstacles=data['obstacles'];required=set(data.get('requiredIndices',[]));present=[p['index'] for p in placements]
if not required:errors.append(dict(kind='missing-coverage-contract'))
for i in sorted(required-set(present)):errors.append(dict(kind='missing-label',index=i))
if len(present)!=len(set(present)):errors.append(dict(kind='duplicate-label'))
for side in ['F.SilkS','B.SilkS']:
obs=[o for o in obstacles if o['layer']==side];geoms=[shape(o['polygon']) for o in obs];tree=STRtree(geoms)
marks=[(p['index'],'text',box(*p['box'])) for p in placements if p['layer']==side]
for g in data.get('graphics',[]):
if g['layer']==side:marks.append((g['index'],g['kind'],LineString(g['samples']).buffer(g.get('width',.06)/2)))
for index,kind,g in marks:
if not g.is_valid:errors.append(dict(kind='invalid-mark',index=index));continue
for k in tree.query(g):
o=obs[int(k)];other=geoms[int(k)]
if g.intersection(other).area>1e-7:errors.append(dict(kind='obstacle-collision',index=index,mark=kind,obstacle=o['id'] if 'id' in o else o.get('reference'),obstacleKind=o['kind'],layer=side))
# Text rectangles may not overlap. Association lines also reserve their stroke width.
tree=STRtree([g for _,_,g in marks])
for k,(index,kind,g) in enumerate(marks):
for j in tree.query(g):
j=int(j)
if j>=k:continue
otherindex,otherkind,other=marks[j]
if kind!='text' and otherkind!='text':continue # joined segments are allowed
if g.intersection(other).area>1e-7:errors.append(dict(kind='mark-collision',index=index,otherIndex=otherindex,mark=kind,otherMark=otherkind,layer=side))
groups={}
for p in placements:
if p.get('reference') and p.get('role') in ['reference','value']:groups.setdefault((p['layer'],p['reference']),{})[p['role']]=p
if p.get('lineSizes') and any(z>=p['lineSizes'][0] for z in p['lineSizes'][1:]):errors.append(dict(kind='multiline-hierarchy',index=p['index']))
for (side,ref),g in groups.items():
if 'value' in g and ('reference' not in g or g['value']['size']>g['reference']['size']*.8):errors.append(dict(kind='pair-hierarchy',reference=ref,layer=side))
return dict(passed=not errors,errors=errors,labels=len(placements),graphics=len(data.get('graphics',[])),obstacles=len(obstacles),nativeVerified=False)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--input',required=True);p.add_argument('--out',required=True);a=p.parse_args();r=check(json.loads(Path(a.input).read_text()));Path(a.out).write_text(json.dumps(r,indent=2));print(('OK' if r['passed'] else 'ERROR')+': '+str(len(r['errors']))+' preflight findings');raise SystemExit(0 if r['passed'] else 2)