aboutsummaryrefslogtreecommitdiff
path: root/src/ee/kicad
diff options
context:
space:
mode:
Diffstat (limited to 'src/ee/kicad')
-rw-r--r--src/ee/kicad/doit.py125
-rwxr-xr-xsrc/ee/kicad/export_gerber.py259
2 files changed, 384 insertions, 0 deletions
diff --git a/src/ee/kicad/doit.py b/src/ee/kicad/doit.py
new file mode 100644
index 0000000..44abc61
--- /dev/null
+++ b/src/ee/kicad/doit.py
@@ -0,0 +1,125 @@
+import os.path
+from doit.exceptions import TaskFailed
+from doit.tools import check_timestamp_unchanged
+from configclass import Config
+
+class KicadDoitTasks(object):
+ config = Config({
+ "sch": None,
+ "kicad_pcb": None,
+ "gerber_dir": None,
+ "gerber_zip": None,
+ "components_dir": None,
+ })
+
+ def __init__(self, *args, **kwargs):
+ self.config = self.config.make(kwargs)
+
+ def echo(*args, **kwargs):
+ print("_task: args={}, kwars={}".format(args, kwargs))
+
+ def tasks(self, *args, **kwargs):
+ import ee.kicad
+ kicad_pcb = self.config["kicad_pcb"]
+ sch = self.config["sch"]
+ tasks = []
+
+ gerber_dir = self.config["gerber_dir"]
+ if gerber_dir:
+ gerber_zip = self.config["gerber_zip"] or "{}.zip".format(gerber_dir)
+ #print("gerber_zip={}".format(gerber_zip))
+
+ eg = next((p for p in (os.path.join(p, "export_gerber.py") for p in ee.kicad.__path__) if os.path.isfile(p)), None)
+ if not eg:
+ raise Exception("Could not find export_gerber.py")
+
+ # TODO: replace with python
+ mkdir = "mkdir -p {}".format(gerber_dir)
+ export_gerber = " ".join([
+ eg,
+ "--pcb", kicad_pcb,
+ "--output-directory", gerber_dir,
+ "--protel-extensions",
+ ])
+ def make_zip():
+ import zipfile
+ from pathlib import Path
+ with zipfile.ZipFile(gerber_zip, "w") as z:
+ for p in Path(gerber_dir).iterdir():
+ if not p.is_file():
+ continue
+ z.write(p, arcname=p.relative_to(gerber_dir))
+
+ tasks.append({
+ "name": "kicad_gerber",
+ "targets": [gerber_zip],
+ "actions": [mkdir, export_gerber, make_zip],
+ "file_dep": [kicad_pcb],
+ })
+
+ components_dir = self.config["components_dir"]
+ if sch and components_dir:
+ tasks.append(task_sch_to_component_files(sch, components_dir))
+
+ for t in tasks:
+ yield t
+
+
+def task_sch_to_component_files(sch, components_dir):
+ def action():
+ import ee.kicad
+ from ee.kicad.model import Component, ComponentField
+ import csv
+ import configparser
+ from ee.fact import Fact
+
+ schematics = ee.kicad.read_schematics(sch)
+ cs = [c for c in schematics.components]
+ os.makedirs(components_dir, exist_ok=True)
+ # TODO: remove directory?
+ for c in cs:
+ path = os.path.join(components_dir, "{}.ini".format(c.timestamp))
+ ini = configparser.ConfigParser(interpolation = None)
+ if os.path.isfile(path) and ini.read(path) != [path]:
+ print("Could not load component file: {}".format(path))
+ return False
+# if ini.has_section("kicad-schematic"):
+# ini.remove_section("kicad-schematic")
+# ini.add_section("kicad-schematic")
+# ini.set("kicad-schematic", "ref", c.ref)
+# ini.set("kicad-schematic", "ref-type", c.ref_type)
+ fact = Fact("schematic-symbol")
+ ks = fact.view("kicad-schematic")
+ ks.set("ref", c.ref)
+ ks.set("ref-type", c.ref_type)
+ if c.has_ref_num:
+# ini.set("kicad-schematic", "ref-num", str(c.ref_num))
+ ks.set("ref-num", str(c.ref_num))
+# ini.set("kicad-schematic", "value", c.value)
+ ks.set("value", c.value)
+ if c.footprint:
+# ini.set("kicad-schematic", "footprint", c.footprint)
+ ks.set("footprint", c.footprint)
+
+ fields = [f for f in c.fields if f.value and f.name not in ComponentField.names]
+
+ if fields:
+# if ini.has_section("kicad-fields"):
+# ini.remove_section("kicad-fields")
+# ini.add_section("kicad-fields")
+ kf = fact.kv("kicad-fields")
+ for f in fields:
+ kf.set(f.name, str(f.value))
+
+ with open(path, "w", newline="") as f:
+# ini.write(f)
+ fact.write(f)
+
+ component_files_cookie = "{}/components.cookie".format(components_dir)
+ gen_cookie = "date > %(targets)s"
+ return {
+ "name": "sch_to_component_files",
+ "file_dep": [sch],
+ "actions": [action, gen_cookie],
+ "targets": [component_files_cookie],
+ }
diff --git a/src/ee/kicad/export_gerber.py b/src/ee/kicad/export_gerber.py
new file mode 100755
index 0000000..511f9ee
--- /dev/null
+++ b/src/ee/kicad/export_gerber.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python
+from __future__ import print_function
+import sys
+import os
+import argparse
+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: <KiCAD Layer>=<extension>, 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)