Source

PCBmodE / utils / excellon.py

Full commit
#!/usr/bin/python

#   Copyright 2013 Boldport Limited
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.


import os
import re
from lxml import etree as et

# pcbmode modules
import utils
from Point import Point


def make_excellon(cfg, manufacturer='default'):
    """
    Generates an Excellon drill file
    """

    def excellonise_point(point):
        """
        Convers a Point type into an Excellon coordinate
        """
        return "X%.6fY%.6f\n" % (point.x, -point.y)

    input_file = os.path.join(cfg['base_dir'], cfg['pcbmode']['locations']['build'], cfg['board_name'] + '.svg')
    svg_in = et.ElementTree(file=input_file)

    transform = Point()
    tmp = Point()

    drills = {}

    # drill index
    tool_index = 1

    drills_layer = svg_in.find("//svg:g[@inkscape:label='%s']" % 'drills', namespaces={'inkscape':cfg['namespace']['inkscape'],'svg':cfg['namespace']['svg']})

    process_drills = drills_layer.xpath(".//svg:g[@id='%s']//svg:path[@d]" % 'drills_layer', namespaces={'inkscape':cfg['namespace']['inkscape'],'svg':cfg['namespace']['svg']}) + svg_in.xpath("//svg:g[@inkscape:label='%s']//svg:g[@id='%s']//svg:path[@d]" % ('drills', 'board_drills') , namespaces={'inkscape':cfg['namespace']['inkscape'],'svg':cfg['namespace']['svg']})

    for path in process_drills:

        t = path.get('transform') or ''
        d = path.get('d')
        diameter = path.get('diameter')

        transform = Point()

        if diameter not in drills:
            drills[diameter] = {}
            drills[diameter]['drill_list'] = []
            drills[diameter]['tool_index'] = "T%d" % (tool_index)
            drills[diameter]['tool_definition'] = "T%dC%s" % (tool_index, diameter)
            drills[diameter]['tool_text'] = "%s mm circular drill" % (diameter) 
            tool_index += 1

        regex = r".*?translate\s?\(\s?(?P<x>-?[0-9]*\.?[0-9]+)\s?[\s,]\s?(?P<y>-?[0-9]*\.?[0-9]+\s?)\s?\).*"
     
        tr = re.match(regex, t)
        if tr:
            transform.assign(tr.group('x'), tr.group('y'))
        else:
            transform.assign(0, 0)
        ancestors = path.xpath("ancestor::*[@transform]")
        for ancestor in ancestors:
            t = ancestor.get('transform')
            tr = re.match(regex, t)
            if tr:
                tmp.assign(tr.group('x'), tr.group('y'))
                transform += tmp    

        drills[diameter]['drill_list'].append(transform)


    # directory for storing the Gerbers within the build path
    production_path = os.path.join(cfg['base_dir'], cfg['pcbmode']['locations']['build'], 'production')
    utils.create_dir(production_path)

    pcbmode_version = cfg['pcbmode']['version']
    board_name = cfg['board_name']
    board_revision = cfg['board']['meta'].get('board_revision') or 'A'    

    base_dir = os.path.join(cfg['base_dir'], 
                            cfg['pcbmode']['locations']['build'], 
                            'production')
    base_name = "%s_rev_%s" % (board_name, board_revision)


    filenames = []

    filename_info = cfg['pcbmode']['manufacturers'][manufacturer]['filenames']['drills']
    add = '_%s.%s' % ('drills', filename_info['plated'].get('ext') or 'txt')
    filename = os.path.join(base_dir, base_name + add)
    print '%s' % base_name + add,
    filenames.append(filename)

    excellon = open(filename, 'w')

    excellon.write('M48\n') # Beginning of a part program header
    excellon.write('METRIC,TZ\n') # Metric, trailing zeros
    excellon.write('G90\n') # Absolute mode
    excellon.write('M71\n') # Metric measuring mode
    # drill definitions
    for diameter in drills:
        excellon.write(drills[diameter]['tool_definition']+'\n')
    excellon.write('M95\n') # End of a part program header
    excellon.write('M47, Hello operator!\n') # Message to operator

    for diameter in drills:
        excellon.write(drills[diameter]['tool_index']+'\n')
        for drill in drills[diameter]['drill_list']:
            excellon.write(excellonise_point(drill))

    excellon.write('M47, Thanks operator!\n') # Message to operator
    excellon.write('M30\n') # End of Program, rewind

    excellon.close()

    return filenames