diff --git a/eagle/eagle_png.py b/eagle/eagle_png.py new file mode 100644 index 0000000000000000000000000000000000000000..3534fe3a93e43173f6d7aa4748fc2981475c1494 --- /dev/null +++ b/eagle/eagle_png.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + +import os +import sys +import platform +import glob +import subprocess +import hashlib + +def find_eagle(): + if platform.uname()[0] == 'Darwin': + try: + eagle_dir = glob.glob('/Applications/EAGLE*')[-1] + except IndexError: + sys.stderr.write("Error: EAGLE not found.\n") + sys.exit(1) + + return eagle_dir + '/EAGLE.app/Contents/MacOS/EAGLE' + else: + if subprocess.call(['which','eagle'], + stdout = open(os.devnull, 'w')): + sys.stderr.write("Error: EAGLE not found.\n") + sys.exit(1) + return 'eagle' + +def create_images(name, resolution = 1500): + for img in ['top','bottom','cutout','holes','vias']: + file = '%s.%s.png' % (name, img) + if os.path.isfile(file): + os.remove(file) + + script = ''' +ratsnest; write; +set palette black; window; +display none top vias pads; +export image '{name}.top.png' monochrome {resolution}; +display none bottom vias pads; +export image '{name}.bottom.png' monochrome {resolution}; +display none milling; +export image '{name}.cutout.png' monochrome {resolution}; +display none holes; +export image '{name}.holes.png' monochrome {resolution}; +display none vias pads; +export image '{name}.vias.png' monochrome {resolution}; +quit'''.format(name = name, resolution = resolution) + subprocess.call([find_eagle(), '-C', script, name + '.brd']) + +def md5(filename): + with open(filename,'rb') as f: + m = hashlib.md5() + for chunk in iter(lambda: f.read(m.block_size*128), ''): + m.update(chunk) + return m.digest() + +def clean_up(name): + preserve = ['top','bottom','cutout'] + for img in ['top','bottom','cutout','holes','vias']: + file = '%s.%s.png' % (name, img) + file_ = '%s.%s_.png' % (name, img) + if os.path.isfile(file) and img not in preserve: + os.remove(file) + if os.path.isfile(file_): + os.remove(file_) + +def print_help(): + print """command line: eagle_png [options] target.brd + target.brd = EAGLE brd file to render + The board outline should be a solid polygon on the 'milling' layer + Internal cutouts should be solid shapes on the 'holes' layer + + Valid options: + --resolution NUM : sets output image resolution + --doublesided : forces double-sided mode""" + sys.exit(1) + +if __name__ == '__main__': + if len(sys.argv) == 1: + print_help() + sys.exit(1) + + # Parse arguments + sys.argv = sys.argv[1:] + resolution = 1500 + force_doublesided = False + + while sys.argv: + if sys.argv[0] == '--resolution': + try: + resolution = sys.argv[1] + sys.argv = sys.argv[2:] + except IndexError: + sys.stderr.write("Error: No resolution provided.\n") + sys.exit(1) + try: + resolution = int(resolution) + except ValueError: + sys.stderr.write("Error: Invalid resolution.\n") + sys.exit(1) + + elif sys.argv[0] == '--doublesided': + force_doublesided = True + sys.argv = sys.argv[1:] + + elif len(sys.argv) == 1: + break + else: + sys.stderr.write("Error: No filename provided.\n") + sys.exit(1) + + name = sys.argv[0].replace('.brd','') + if not os.path.isfile(name+'.brd'): + sys.stderr.write("Error: .brd file does not exist.\n") + sys.exit(1) + + vias = name + '.vias.png' + cutout = name + '.cutout.png' + top = name + '.top.png' + bottom = name + '.bottom.png' + holes = name + '.holes.png' + + print "Rendering images." + create_images(name, resolution) + + # Check to make sure that imagemagick is installed. + if subprocess.call(['which','convert'], stdout = open(os.devnull, 'w')): + sys.stderr.write("""Error: 'convert' not found. +ImageMagick command-line tools must be installed to use eagle_png.""") + sys.exit(1) + + print "Processing images." + + # The following command is a set of ImageMagick instructions that + # combine all of the images. + + # The following steps take place: + # - Perform a white flood fill on the vias image, starting in the upper + # left corner. This makes the via image a set of black holes on + # a uniform white background + # - Multiply the vias and cutout images, to cut the via holes from + # the cutout region. + # - Invert the cutout image. + # - Lighten the top and bottom traces with the inverted cutout. This + # ensures that we don't waste time milling traces in regions that + # will be cut out of the PCB. + # - Subtract the holes image from the original cutout image + # - Save this combined cutout image + + command = [ 'convert', + vias, '-fill', 'white', '-draw', 'color 0,0 floodfill', + cutout, '-compose', 'Darken', '-composite', + '-compose','Lighten', + '(', + '+clone', + '-negate' + ] + + # If this is a two-sided board, then process the bottom layer + if md5(bottom) != md5(vias) or force_doublesided: + command += [ + '(', + '+clone', bottom, '-composite', + '-flop', '-write', bottom, '+delete', + ')' + ] + else: + os.remove(bottom) + + # Process the top layer + command += [ + top, '-composite', '-write', top, + '+delete', + ')', + holes, '-compose', 'Minus_Src', '-composite', cutout + ] + + # Execute this whole mess + subprocess.call(command) + + os.remove(vias) + os.remove(holes) + + if bottom in command: + print "Generated %s, %s, %s." % (top, bottom, cutout) + else: + print "Generated %s, %s." % (top, cutout)