Skip to content

Instantly share code, notes, and snippets.

@mdeweerd
Forked from Salamandar/kicad_generate_gerber.py
Last active October 23, 2021 12:54
Show Gist options
  • Save mdeweerd/df5b6a1462d3052a42dbaa0e35a5b952 to your computer and use it in GitHub Desktop.
Save mdeweerd/df5b6a1462d3052a42dbaa0e35a5b952 to your computer and use it in GitHub Desktop.
Python script that generates Gerber, Drill map, Position file, and zips everything.
@echo OFF
REM
REM WRAPPER FOR WINDOWS INCLUDING THE PYTHON PATH FROM KICAD ITSELF
REM
REM GIT: git@gist.github.com:df5b6a1462d3052a42dbaa0e35a5b952.git
REM URL: https://gist.github.com/df5b6a1462d3052a42dbaa0e35a5b952
REM
PATH="c:\Program Files\KiCad\bin";%PATH%
echo %~dp0
python %~dp0\kicad_generate_gerber.py %*
#!/bin/env python3
## -*- coding: utf-8 -*-
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42):
# Salamandar <felix@piedallu.me> wrote this file. As long as you retain this notice you
# can do whatever you want with this stuff. If we meet some day, and you think
# this stuff is worth it, you can buy me a beer in return. Félix Piédallu
# ----------------------------------------------------------------------------
# Modified by MDW - Added 'coding'
import sys
import os
import shutil
import pcbnew
import csv
import re
def generate_gerber(name, output_dir):
board = pcbnew.LoadBoard(name)
pctl = pcbnew.PLOT_CONTROLLER(board)
popt = pctl.GetPlotOptions()
###########################################################################
# Output options
plot_format = pcbnew.PLOT_FORMAT_GERBER
popt.SetOutputDirectory(output_dir)
###########################################################################
# Included Layers:
layers = [
{'layer': pcbnew.F_Cu, 'suffix': 'F.Cu'},
{'layer': pcbnew.B_Cu, 'suffix': 'B.Cu'},
{'layer': pcbnew.F_SilkS, 'suffix': 'F.SilkS'},
{'layer': pcbnew.B_SilkS, 'suffix': 'B.SilkS'},
{'layer': pcbnew.B_Paste, 'suffix': 'B.Paste'},
{'layer': pcbnew.F_Paste, 'suffix': 'F.Paste'},
{'layer': pcbnew.B_Mask, 'suffix': 'B.Mask'},
{'layer': pcbnew.F_Mask, 'suffix': 'F.Mask'},
{'layer': pcbnew.Edge_Cuts, 'suffix': 'Edge.Cuts'},
]
###########################################################################
# General Options:
popt.SetPlotFrameRef(False)
popt.SetPlotValue(True)
popt.SetPlotReference(True)
# popt.SetPlotInvisibleText(False)
# popt.SetPlotViaOnMaskLayer(False)
popt.SetExcludeEdgeLayer(True)
popt.SetPlotPadsOnSilkLayer(False)
popt.SetUseAuxOrigin(False)
# popt.SetMirror(False)
# popt.SetNegative(False)
###########################################################################
popt.SetDrillMarksType(popt.NO_DRILL_SHAPE)
popt.SetAutoScale(False)
popt.SetScale(1)
popt.SetPlotMode(pcbnew.FILLED)
popt.SetLineWidth(pcbnew.FromMM(0.1))
###########################################################################
# Gerber Options:
popt.SetUseGerberProtelExtensions(True)
# popt.SetUseGerberAttributes(True)
popt.SetCreateGerberJobFile(False)
popt.SetSubtractMaskFromSilk(False)
# popt.SetFormat()
###########################################################################
# Generate files now
for l in layers:
pctl.SetLayer(l['layer'])
# pctl.OpenPlotfile()
pctl.OpenPlotfile(l['suffix'], pcbnew.PLOT_FORMAT_GERBER, l['layer'])
print(l['suffix'])
pctl.PlotLayer()
pctl.ClosePlot()
def generate_drillmap(name, output_dir):
board = pcbnew.LoadBoard(name)
writer = pcbnew.EXCELLON_WRITER(board)
print('Drill map')
writer.SetFormat(True)
writer.SetOptions(
aMirror=False,
aMinimalHeader=False,
aOffset=writer.GetOffset(),
aMerge_PTH_NPTH=True
)
writer.CreateDrillandMapFilesSet(
output_dir,
aGenDrill=True,
aGenMap=False,
aReporter=None
)
def generate_position_csv(name, output_dir):
board = pcbnew.LoadBoard(name)
board_name, ext = os.path.splitext(name)
print('Position file')
with open(os.path.join(output_dir, board_name + '-top-pos.csv'), 'w') as out_top,\
open(os.path.join(output_dir, board_name + '-bottom-pos.csv'), 'w') as out_bot:
fieldnames = ['Ref','Val','Package','PosX','PosY','Rot','Side']
csv_top = csv.DictWriter(out_top, fieldnames=fieldnames, quoting=csv.QUOTE_NONNUMERIC)
csv_bot = csv.DictWriter(out_bot, fieldnames=fieldnames, quoting=csv.QUOTE_NONNUMERIC)
csv_top.writeheader()
csv_bot.writeheader()
def sorted_nicely(l):
convert = lambda text: int(text) if text.isdigit() else text
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key.GetReference())]
return sorted(l, key = alphanum_key)
sorted_modules = sorted_nicely(board.GetModules())
aux_origin = board.GetAuxOrigin()
for m in sorted_modules:
is_cms = (m.GetAttributes() == pcbnew.MOD_CMS)
is_top = not m.IsFlipped()
is_bot = not is_top
if not is_cms:
continue
side = 'top' if is_top \
else 'bottom' if is_bot \
else 'none'
values = {
'Ref': m.GetReference(),
'Val': m.GetValue(),
'Package': m.GetFPID().GetLibItemName(),
'PosX': pcbnew.ToMM(m.GetPosition().x - aux_origin.x),
'PosY': pcbnew.ToMM(aux_origin.y - m.GetPosition().y),
'Rot': m.GetOrientationDegrees(),
'Side': side,
}
if side == 'top':
csv_top.writerow(values)
if side == 'bottom':
csv_bot.writerow(values)
def archive_dir(dirname, filename):
shutil.make_archive(filename, 'zip', root_dir=None, base_dir=dirname)
if __name__ == '__main__':
files = []
if len(sys.argv) == 1:
import glob
files = glob.glob("*.kicad_pcb")
if len(sys.argv) >= 2:
files = sys.argv[1:]
for filename in files:
project_dir = os.path.dirname(filename)
gerber_dir = os.path.join(project_dir, 'gerber')
print('Using', filename)
generate_gerber(filename, 'gerber')
generate_drillmap(filename, gerber_dir)
generate_position_csv(filename, 'gerber')
zipname, ext = os.path.splitext(filename)
archive_dir(gerber_dir, zipname)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment