seveibar/am3352-dev-board-4layer-dogbone

Defines PCB footprints and pin mappings for a TI AM3352ZCZ100 processor, JST GH connectors, and assembly-part lookup helpers for capacitors, resistors, and headers.

Version
0.1.5
License
unset
Stars
0

scripts/check-assembly.py

"""Validate assembly-specific land geometry and the RTC crystal's copper keepout."""
import json,math,runpy,hashlib
from pathlib import Path
from shapely.geometry import box
x=runpy.run_path('scripts/audit-copper.py');c=x['c'];src={e['source_component_id']:e for e in c if e['type']=='source_component'};pcs={src[e['source_component_id']]['name']:e for e in c if e['type']=='pcb_component'}
# Manufacturer-recommended land patterns, in the component's unrotated frame.
expected={'L1':(.8,.8,1.8),'L2':(.8,.8,1.8),'L3':(.8,.8,1.8),'Y1':(2.05,1.9,2.4),'Y2':(1.25,1.,1.8)}
land=[]
for name,(offset,width,height) in expected.items():
 pc=pcs[name];a=math.radians(-pc['rotation']);pads=[e for e in c if e['type']=='pcb_smtpad' and e['pcb_component_id']==pc['pcb_component_id']];assert len(pads)==2
 for p in pads:
  dx,dy=p['x']-pc['center']['x'],p['y']-pc['center']['y'];px,py=dx*math.cos(a)-dy*math.sin(a),dx*math.sin(a)+dy*math.cos(a)
  w,h=(p['height'],p['width']) if round(pc['rotation'])%180==90 else (p['width'],p['height'])
  assert max(abs(abs(px)-offset),abs(py),abs(w-width),abs(h-height))<1e-5,(name,p)
 land.append({'reference':name,'padCentersMm':[-offset,offset],'padSizeMm':[width,height]})
keepouts=[]
for k in c:
 if k['type']!='pcb_keepout':continue
 center=k['center'];g=box(center['x']-k['width']/2,center['y']-k['height']/2,center['x']+k['width']/2,center['y']+k['height']/2)
 hits=[m[2] for s,m in zip(x['shapes'],x['meta']) if m[0] in k['layers'] and s.intersection(g).area>1e-8]
 assert not hits,('Copper in assembly keepout',hits)
 keepouts.append(k['pcb_keepout_id'])
assert keepouts,'RTC crystal body keepout missing'
missing=[e['name'] for e in src.values() if e['ftype']!='simple_test_point' and not e.get('manufacturer_part_number')];assert not missing,missing
bare=[e['name'] for e in src.values() if e['ftype']=='simple_test_point'];assert bare==['TP_PB'],bare
warnings=[e for e in c if e['type']=='pcb_component_missing_courtyard_warning'];assert all(e.get('pcb_component_id')==pcs['TP_PB']['pcb_component_id'] or e.get('pcb_component_ids')==[pcs['TP_PB']['pcb_component_id']] for e in warnings),warnings
# Harwin drawing: housing 3*1.27 +/-0.50 long, 2.10 +/-0.25 wide.
# The manual-placement courtyard displays nominal housing +0.10 per edge.
# Independently check the maximum housing envelope against nearby parts.
headerGaps=[]
for name,pc in pcs.items():
 if not name.startswith('J_BOOT'):continue
 hx,hy=pc['center']['x'],pc['center']['y'];body=box(hx-2.35/2,hy-4.31/2,hx+2.35/2,hy+4.31/2)
 gaps=[]
 for other,op in pcs.items():
  if other==name or op['layer']!=pc['layer']:continue
  if other.startswith('R_BOOT'):
   # 0402 resistor maximum body 1.10 x 0.60 mm; it is horizontal here.
   ox,oy=op['center'].values();otherBody=box(ox-.55,oy-.30,ox+.55,oy+.30)
  else:
   court=next((e for e in c if e['type']=='pcb_courtyard_rect' and e['pcb_component_id']==op['pcb_component_id']),None)
   if not court:continue
   otherBody=x['padshape'](dict(court,x=court['center']['x'],y=court['center']['y'],shape='rect'))
  assert not body.intersects(otherBody),(name,other,'maximum housing collision')
  gaps.append((body.distance(otherBody),other))
 headerGaps.append({'reference':name,'minimumGapMm':min(gaps)[0],'nearestPart':min(gaps)[1]})
orientation=json.load(open('assembly/orientation-audit.json'));assert orientation['circuitSha256']==x['report']['circuitSha256'];assert orientation['unresolvedRotations']==0
report={'circuitSha256':x['report']['circuitSha256'],'populatedComponents':len(src)-len(bare),'bareTestPads':bare,'manufacturerLandPatterns':land,'emptyCopperKeepouts':keepouts,'maximumHeaderBodyClearances':headerGaps,'smtComponents':orientation['smtComponents'],'manualHeaders':orientation['manualComponents'],'unresolvedRotations':0,'scope':'Geometry, part assignment, and supplier-library rotation mapping. Electrical startup, DC-bias derating, SI/PI and actual assembler preview not qualified.'}
Path('design/assembly-check.json').write_text(json.dumps(report,indent=2)+'\n');print('Assembly: populated parts have MPNs; five manufacturer land patterns match; RTC body has no copper; all SMT rotations mapped')