PCBmodE /

Full commit

#   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
#   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 datetime
import json
import argparse
import re
import hashlib # for MD5
import string # used for random string generation
import random # used for random string generation
import pyparsing as PYP
import pprint # for pretty printing 
from lxml import etree as et

# pcbmode modules
import utils.board as board 
import utils.utils as utils
import utils.svg as svg 
import utils.gerber as gerber
import utils.footprint as footprint 
import utils.routing as routing
import utils.excellon as excellon

def setup_commandline_arguments(pcbmode_version):
    Sets up the commandline arguments form and variables

    description = """
    PCBmodE is a script-based PCB design tool that generates SVG
    and Gerber files based on data stored on json files. For viewing
    layers in the SVGs that are generated, open them with Inkscape.

    epilog = """
    # commandline argument settings and parsing
    argp = argparse.ArgumentParser(description=description, 
                      add_help=True, version=pcbmode_version, epilog=epilog)
    #me_group = argp.add_mutually_exclusive_group()

    argp.add_argument('-b', '--board-name',
                      dest='boards', required=True, nargs=1,
                      help='Makes the specified board. The location of the files should be specified in the configuration file, otherwise defaults will be used')
    argp.add_argument('-f', '--filein', required=False,
                      help='Input file name')
    argp.add_argument('-o', '--fileout',
                      help='Output file name')
    argp.add_argument('-c', '--config-file', default='pcbmode_config.json',
                      help='Configuration file name (default=pcbmode_config.json)')
    argp.add_argument('-m', '--make-board',
                      action='store_true', dest='make', default=False,
                      help="This will produce an SVG of the board specified by '-b' / '--board_name' switch. The output's location can be specified in the configuration file")
    argp.add_argument('-bom', '--make-bom',
                      action='store_true', dest='bom', default=False,
                      help='Create a BOM')
    argp.add_argument('-r', '--extract-routing',
                      action='store_true', dest='routing', default=False,
                      help="Extract routing from the 'copper' layer of an SVG file")
    argp.add_argument('-fab', '--fab', nargs='?',
                      dest='fab', default=False,
                      help='Generate manufacturing files (Gerbers, Excellon, etc.)')

    argp.add_argument('-p', '--make-pngs',
                      action='store_true', dest='pngs', default=False,
                      help='Generate PNGs for the board and layers (requires Inkscape)')

#    argp.add_argument('-z', '--zip',
#                      action='store_true', dest='zip', default=False,
#                      help='Zip manufacturing files')    
    return argp

def create_board_configuration(board_name, version, cmdline_args):
    Returns a dictionary of configuration data based on content
    in configuration files or defaults

    default_pcbmode_cfg = (
         'boards': 'boards/',
         'parts': 'parts/',
         'fonts': 'fonts/',
         'build': 'build/',
         'styles': 'styles/'

    cfg = {}

    cfg['board_name'] = board_name

    # read in configuration data; if doesn't exist use default values
    print "-- processing PCBmodE's configuration file"
    pcbmode_cfg = utils.get_json_data_from_file(cmdline_args.config_file)
    if pcbmode_cfg is None:
        cfg['pcbmode'] = default_pcbmode_cfg
        cfg['pcbmode'] = pcbmode_cfg

    cfg['pcbmode']['version'] = version

    cfg['base_dir'] = os.path.join(cfg['pcbmode']['locations']['boards'], board_name)

    print "-- processing board's configuration file"
    # read in the board's configuration data
    board_config_filename = os.path.join(cfg['pcbmode']['locations']['boards'], 
                                         cfg['board_name'] + '.json')

    board_cfg = utils.get_json_data_from_file(board_config_filename)
    if board_cfg is not None:
        cfg['board'] = board_cfg

    # look for layout style file in the following places, in order
    style_files_to_look_for = [os.path.join(cfg['base_dir'],
                                            cfg['board']['meta'].get('layout_style') or 'default', 
                                            'layout.json'), # project directory
                                            cfg['board']['meta'].get('layout_style') or 'default', 
                                            'layout.json')] # specific PCBmodeE layout, or fall to default 

    for style_file in style_files_to_look_for:
        if os.path.isfile(style_file):
            cfg['layout_style'] = utils.get_json_data_from_file(style_file)

    # namespace URLs
    cfg['namespace'] = {
        None       : "",
        "dc"       : "",
        "cc"       : "",
        "rdf"      : "",
        "svg"      : "",
        "sodipodi" : "",
        "inkscape" : "",

    # significant digits
    if cfg['pcbmode'].get('significant_digits') is None:
        cfg['pcbmode']['significant_digits'] = 8

    # buffer from board outline to display block edge 
    if cfg['pcbmode'].get('display_frame_buffer') is None:
        cfg['pcbmode']['display_frame_buffer'] = 1.0

    # the style for masks used for copper pours
    cfg['pcbmode']['mask_style'] = "fill:#000;stroke:#000;stroke-linejoin:round;stroke-width:%s;"

    # the string prefix for via identifier
    cfg['via_prefix'] = 'via:'

    return cfg

def main():

    # get PCBmodE version
    pcbmode_version = utils.get_git_revision()

    # setup and parse commandline arguments
    argp = setup_commandline_arguments(pcbmode_version)
    cmdline_args = argp.parse_args()

    for board_name in cmdline_args.boards:
        #pcbmode_cfg, board_cfg, layout_style = create_global_parameters(board_name)
        cfg = create_board_configuration(board_name, pcbmode_version, cmdline_args)

    # check if build directory exists; if not, create
    build_dir = os.path.join(cfg['base_dir'], cfg['pcbmode']['locations']['build'])

    # extract routing from input SVG file
    if cmdline_args.routing is True:
        print "-- extracting routing"

    # make the board
    if cmdline_args.make is True:
        print "-- creating board"

    if cmdline_args.fab is not False:
        if cmdline_args.fab is None:
            manufacturer = 'default'
            manufacturer = cmdline_args.fab.lower()

        print "-- creating Gerbers"
        print "files:",
        gerber_filenames = gerber.gerberise(cfg, manufacturer)
        print "-- creating drill file(s)"
        print "files:",
        drill_filenames = excellon.make_excellon(cfg, manufacturer)

        filenames = gerber_filenames + drill_filenames

        # create a readme file
        readme = open(os.path.join(cfg['base_dir'],
                                    'production', 'README.txt'), 'w')

        readme.write("The following files are included in this archive:\n")
        for f in filenames:
            readme.write("%s\n" % os.path.basename(f))
        readme.write("Please refer to the docoumentation Gerber file for manufacturing instructions.")

#        if is True:
#            filenames = gerber_filenames + drill_filenames
#            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)
#            zip_name = base_name + '.zip'
#            print "-- creating zip of manufacturing files"
#            utils.zip_files(base_dir, zip_name, filenames)

    if cmdline_args.pngs is True:
        print "-- creating PNGs"
    if is True:
        print "-- creating BOM (not implemented yet!)"
    print "-- Done!"

if __name__ == "__main__":