#!/usr/bin/env python2 from __future__ import print_function import argparse import os import sys from pcbnew import * def layer_name_parser(s): parts = s.split('=') if len(parts) != 2: raise argparse.ArgumentTypeError("Invalid layer renaming: " + s) else: return parts parser = argparse.ArgumentParser(description='KiCAD PCB to GERBER converter') parser.add_argument('--pcb', required=True, dest='pcb', action='store', help='A foo.kicad_pcb file') parser.add_argument('--output-directory', required=True, dest='output_directory', action='store', help='Directory to store output files') parser.add_argument('--detect-files-only', dest='detect_files_only', action='store', help='Don\'t create the GERBER files, just write a list of to be created') parser.add_argument('--create-drill-map-file', dest='create_drill_map_file', action='store_true', help='Create drill map file') parser.add_argument('--protel-extensions', dest='protel_extensions', action='store_true', help='Use Protel filename extensions instead of .gbr') parser.add_argument('--extended-gerber-attributes', dest='extended_gerber_attributes', action='store_true', help='Use extended Gerber attributes') parser.add_argument('--uppercase-extensions', action='store_true', help='Uppercase all extensions') parser.add_argument('--layer-extension', dest='layer_extensions', metavar='LAYER_AND_EXTENSION', action='append', type=layer_name_parser, help='Set the file extension of a KiCAD layer. Format: =, example: F.SilkS=GSILK') args = parser.parse_args() # print "args: " + str(args) # print "args.name_layer: " + str(args.name_layers) renames = {} if args.layer_extensions is not None: for s in args.layer_extensions: renames[s[0]] = s[1] # print("renames: " + str(renames)) board = LoadBoard(args.pcb) class Plan: def __init__(self, layerNum, layerName, description): self.layerNum = layerNum self.layerName = layerName self.description = description self.postfix = "" self.ext = None @staticmethod def standard(layerNum, description): layerName = board.GetLayerName(layerNum) return Plan(layerNum, layerName, description) @staticmethod def copper(layerNum): layerName = board.GetLayerName(layerNum) description = "Copper Layer " + layerName return Plan(layerNum, layerName, description) plot_plan = [Plan.standard(layerNum, description) for (layerNum, description) in [ (F_SilkS, "Silk front"), (F_Mask, "Mask front"), (F_Paste, "Paste front"), (B_SilkS, "Silk bottom"), (B_Mask, "Mask bottom"), (B_Paste, "Paste bottom"), (Edge_Cuts, "Edges")]] layers = board.GetEnabledLayers() for layerNum in layers.CuStack(): plot_plan.append(Plan.copper(layerNum)) pctl = PLOT_CONTROLLER(board) popt = pctl.GetPlotOptions() output_directory = args.output_directory popt.SetOutputDirectory(output_directory) if not os.path.isdir(output_directory): try: os.makedirs(output_directory) except: print("Could not make output directory", file=sys.stderr) sys.exit(1) # A nasty hack to get the base filename pctl.SetLayer(F_Cu) pctl.OpenPlotfile("", PLOT_FORMAT_GERBER, "") filename = pctl.GetPlotFileName() try: os.remove(filename) except: pass pctl.ClosePlot() # "Use protel filename extensions", default=False popt.SetUseGerberProtelExtensions(args.protel_extensions) basename = os.path.splitext(filename)[0] drlFileOut = drlFile = basename + "-PTH.drl" drlNpthFileOut = drlNpthFile = basename + "-NPTH.drl" if args.uppercase_extensions: n, e = os.path.splitext(drlFileOut) drlFileOut = n + e.upper() n, e = os.path.splitext(drlNpthFileOut) drlNpthFileOut = n + e.upper() values = vars(args) for plan in plot_plan: pctl.SetLayer(plan.layerNum) pctl.OpenPlotfile("", PLOT_FORMAT_GERBER, plan.description) filename = pctl.GetPlotFileName() pctl.ClosePlot() # By opening and closing the plot we create an empty plot file. try: os.remove(filename) except: pass filename, ext = os.path.splitext(filename) if args.uppercase_extensions: ext = ext.upper() if plan.layerName in renames: ext = "." + renames[plan.layerName] # if plan.arg is not None: # newExt = values[plan.arg] # if newExt is not None: # ext = "." + newExt plan.postfix = plan.layerName plan.filename = basename + "-" + plan.postfix + ext # print "filename = " + plan.filename + ", postfix=" + plan.postfix # print "filename: " + plan.filename if args.detect_files_only: with open(args.detect_files_only, "w") as f: for plan in plot_plan: print(plan.filename, file=f) print(drlFileOut, file=f) print(drlNpthFileOut, file=f) sys.exit(0) # Set some important plot options: popt.SetPlotFrameRef(False) # "Default line width (mm)", default=0.1mm popt.SetLineWidth(FromMM(0.1)) popt.SetAutoScale(False) # "Mirrored plot", default=False. Not applicable for Gerber popt.SetMirror(False) # "Include extended attributes", default=False popt.SetUseGerberAttributes(args.extended_gerber_attributes) # "Drill marks" popt.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE) # "Scaling" popt.SetScale(1) # "Use auxilary axis as origin" popt.SetUseAuxOrigin(False) # This by gerbers only (also the name is truly horrid!) popt.SetSubtractMaskFromSilk(False) for plan in plot_plan: pctl.SetLayer(plan.layerNum) # print "filename = " + plan.filename + ", postfix=" + plan.postfix pctl.OpenPlotfile(plan.postfix, PLOT_FORMAT_GERBER, plan.description) actualFilename = pctl.GetPlotFileName() expectedFilename = plan.filename if expectedFilename != actualFilename: os.rename(actualFilename, expectedFilename) pctl.PlotLayer() pctl.ClosePlot() drlwriter = EXCELLON_WRITER(board) drlwriter.SetMapFileFormat(PLOT_FORMAT_GERBER) # "Drill file options" # "Mirror y axis", default=False mirror = False # "Minimal header", default=True minimalHeader = True offset = wxPoint(0, 0) # "Merge PTH and NPTH into one file", default=False mergeNPTH = False drlwriter.SetOptions(mirror, minimalHeader, offset, mergeNPTH) metricFmt = True drlwriter.SetFormat(metricFmt) genDrl = True genMap = args.create_drill_map_file drlwriter.CreateDrillandMapFilesSet(pctl.GetPlotDirName(), genDrl, genMap) # This has to be verified that this is the right way to do it. if False: # Check that the drill files actually was generated if not os.path.isfile(drlFile): print("No drill file generated: {}".format(drlFile), file=sys.stderr) if not os.path.isfile(drlNpthFile): print("No drill file generated: {}".format(drlNpthFile), file=sys.stderr) if drlFile != drlFileOut: os.rename(drlFile, drlFileOut) if drlNpthFile != drlNpthFileOut: os.rename(drlNpthFile, drlNpthFileOut)