Skip to content
Snippets Groups Projects
eagle_png.py 5.8 KiB
Newer Older
Jake Read's avatar
Jake Read committed
#!/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)