Last active
July 7, 2018 12:58
-
-
Save xaxaxa/9a57705e701738ec38ea01df3699b7d2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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