#!/usr/bin/env python from __future__ import print_function import argparse import os import sys try: from pcbnew import * except ImportError as e: ee_hack = os.environ.get("EE_HACK", "0") print("ee_hack={}".format(ee_hack), file=sys.stderr) try_p2 = ee_hack == "0" try_system = ee_hack == "1" print("Could not import 'pcbnew' module. Make sure you run this command with KiCAD's python.", file=sys.stderr) ret = 1 try: import subprocess if try_p2: argv = sys.argv[1:] # Try running explicitly with Python 2 cmd = "/usr/bin/python2 {} {}".format(__file__, " ".join(argv)) env = dict(os.environ) env["EE_HACK"] = "1" print(cmd, file=sys.stderr) ret = subprocess.call(cmd, shell=True, env=env) elif try_system: argv = sys.argv[1:] # Try with the OS's main python, hopefully the KiCAD packages support that cmd = "/usr/bin/python3 {} {}".format(__file__, " ".join(argv)) env = dict(os.environ) env["EE_HACK"] = "2" print(cmd, file=sys.stderr) ret = subprocess.call(cmd, shell=True, env=env) finally: pass print("ret={}".format(ret), file=sys.stderr) sys.exit(ret) 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_true', help='Don\'t create the GERBER files, just list the files to be created') parser.add_argument('-M', dest='mkdep', help='Output a Makefile-compatible file') parser.add_argument('--index', dest='index') 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 output_directory = os.path.abspath(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.protel_extensions: n, e = os.path.splitext(drlFileOut) drlFileOut = n + ".txt" n, e = os.path.splitext(drlNpthFileOut) drlNpthFileOut = n + ".txt" 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 # if not args.protel_extensions: plan.postfix = plan.layerName plan.filename = basename + "-" + plan.postfix + ext # print "filename = " + plan.filename + ", postfix=" + plan.postfix # print "filename: " + plan.filename if args.mkdep: with open(args.mkdep, "w") as f: pcb = os.path.abspath(args.pcb) def w(path): p = path[len(os.path.commonprefix([path, pcb])):] print("{}: {}".format(p, args.pcb), file=f) for plan in plot_plan: w(plan.filename) w(drlFileOut) w(drlNpthFileOut) if args.index: with open(args.index, "w") as f: pcb = os.path.abspath(args.pcb) def w(path): p = path[len(os.path.commonprefix([path, pcb])):] print("{}".format(p), file=f) for plan in plot_plan: w(plan.filename) w(drlFileOut) w(drlNpthFileOut) if args.detect_files_only: for plan in plot_plan: print(plan.filename) print(drlFileOut) print(drlNpthFileOut) sys.exit(0) # Set some important plot options: popt.SetPlotFrameRef(False) popt.SetLineWidth(FromMM(0.35)) popt.SetAutoScale(False) popt.SetScale(1) popt.SetMirror(False) popt.SetUseGerberAttributes(args.extended_gerber_attributes) popt.SetScale(1) popt.SetUseAuxOrigin(True) # 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() popt.SetDrillMarksType(PCB_PLOT_PARAMS.FULL_DRILL_SHAPE) drlwriter = EXCELLON_WRITER(board) drlwriter.SetMapFileFormat(PLOT_FORMAT_GERBER) mirror = False minimalHeader = False offset = wxPoint(0, 0) # False to generate 2 separate drill files (one for plated holes, one for non plated holes) # True to generate only one drill file 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)