Skip to content

Instantly share code, notes, and snippets.

@xaxaxa
Last active July 7, 2018 12:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xaxaxa/9a57705e701738ec38ea01df3699b7d2 to your computer and use it in GitHub Desktop.
Save xaxaxa/9a57705e701738ec38ea01df3699b7d2 to your computer and use it in GitHub Desktop.
#!/usr/bin/python
import sys,os,cairo,logging
if len(sys.argv) < 4:
print 'usage: ' + sys.argv[0] + ' board.pcb board.bom.csv board.png [dpi]'
sys.exit(1)
pcbFile = sys.argv[1]
bomFile = sys.argv[2]
imageFile = sys.argv[3]
outDir = pcbFile+"_out/"
assembleBackSide = False
if len(sys.argv)>4:
dpi = float(sys.argv[4])
else: dpi = 500
dpmm = dpi*0.0393701
# array of tuples: (refdes, dni, device, footprint, value)
components = []
# map[refdes] => tuple(x1, y1, x2, y2)
# all values in mm
pcbComponents = {}
def parseValue(s):
if s.endswith('mm'):
return float(s[:-2])
if s.endswith('mil'):
return float(s[:-3])*.0254
return float(s)
# t: tuple (device, footprint, value, [refdes, refdes, ...])
def processComponentType(t):
surf = cairo.ImageSurface.create_from_png(imageFile)
imgH = surf.get_height()
imgW = surf.get_width()
ctx = cairo.Context(surf)
ctx.select_font_face('Sans')
ctx.set_font_size(60)
# draw header background
ctx.set_source_rgb(0, 0, 0)
ctx.rectangle(0, 0, imgW, 96)
ctx.fill()
if assembleBackSide:
ctx.set_matrix(cairo.Matrix(yy=-1.0,y0=imgH))
# go through all components and draw bounding rect
ctx.set_source_rgba(1,1,0,0.8)
cnt = 0
for refdes in t[3]:
if not refdes in pcbComponents:
logging.warning('ERROR: component '+refdes+' not found on pcb')
continue
br = pcbComponents[refdes]
if br == None:
logging.warning('WARNING: component '+refdes+' is on opposite side')
continue
br = list(br)
for i in xrange(len(br)):
br[i] = br[i]*dpmm
cnt += 1
ctx.rectangle(br[0],br[1],br[2]-br[0],br[3]-br[1])
ctx.fill()
if cnt == 0: return
ctx.set_matrix(cairo.Matrix())
# draw header text
ctx.set_source_rgb(1, 1, 0)
ctx.move_to(20, 70)
ctx.show_text('%s, %s, %s (%d pcs)' % tuple(t[0:3] + [cnt]))
surf.write_to_png(outDir + ('%s_%s_%s.png' % tuple(t[0:3])));
print '%s\t%s\t%s\t%d' % tuple(t[0:3] + [cnt])
def processPcbComponent(f, attrs):
refdes = attrs[2][1:-1]
backside = False
# component is on the backside if it has "onsolder" attribute
if attrs[0] == '"onsolder"':
backside = True
if backside != assembleBackSide:
pcbComponents[refdes] = None
return
# get component bounding rect
comp_x, comp_y = [parseValue(x) for x in attrs[4:6]]
br_x1 = None
br_y1 = None
br_x2 = None
br_y2 = None
while True:
line = f.readline()
if not line: break
line = line.strip()
if line.endswith(')'): break
if not line.startswith('Pad['):
continue
line = line[4:-1]
values = line.split(' ')
x1, y1, x2, y2, dia = [parseValue(x) for x in values[:5]]
rad = dia/2
xs = (x1-rad, x1+rad, x2-rad, x2+rad)
ys = (y1-rad, y1+rad, y2-rad, y2+rad)
for x in xs:
if x<br_x1 or br_x1 == None: br_x1 = x
if x>br_x2 or br_x2 == None: br_x2 = x
for y in ys:
if y<br_y1 or br_y1 == None: br_y1 = y
if y>br_y2 or br_y2 == None: br_y2 = y
if br_x1 == None or br_y1 == None or br_x2 == None or br_y2 == None:
return
pcbComponents[refdes] = (comp_x+br_x1, comp_y+br_y1, comp_x+br_x2, comp_y+br_y2)
if not os.path.isdir(outDir):
os.mkdir(outDir)
# read bom file
with open(bomFile) as f:
lines = f.readlines()
for line in lines[1:]:
values = line.strip().split('\t')
if len(values) == 0: continue
if len(values) != 5:
print 'bad line in file (incorrect # of fields):'
print line
continue
if values[1] == '1': continue #dni
components.append(values)
# sort bom file and group by (device, footprint, value) tuple
components.sort(key=lambda x:x[2:])
types = []
prevType = components[0][2:]
curComponents = [components[0][0]]
for x in components[1:]:
t = x[2:]
if t == prevType:
curComponents.append(x[0])
else:
types.append(prevType + [curComponents])
prevType = t
curComponents = [x[0]]
types.append(prevType + [curComponents])
# parse pcb file and get all components on top side
with open(pcbFile) as f:
while True:
line = f.readline()
if not line: break
line = line.strip()
if line.startswith('Element['):
attrs = line[8:-1].split(' ')
processPcbComponent(f, attrs)
print 'device\tfootprint\tvalue\tqty'
for t in types:
processComponentType(t)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment