Add plot and release scripts

main
Nathan L. Conrad 5 years ago
parent 8af97455ac
commit ea56e8020a

@ -5,9 +5,9 @@ Debug adapter for Silicon Labs Mini Simplicity targets
OVERVIEW
- 0.1" 20-pin female IDC header for Segger J-Link debuggers and the like
- 0.05" 10-pin male header for Silicon Labs Mini Simplicity targets
- 0.05" 10-pin male header for general ARM Cortex targets
- 2x10 100mil pitch IDC socket for Segger J-Link debuggers and the like
- 2x5 50mil pitch IDC header for Silicon Labs Mini Simplicity targets
- 2x5 50mil pitch IDC header for general ARM Cortex targets
- Voltage regulator and on/off switch for powering targets with the debugger
- Target reset push-button
@ -16,3 +16,27 @@ DEPENDENCIES
- KiCad version 5.1.6 for macOS. Results with previous or subsequent versions
will likely vary.
- kipy (https://git.alt-tek.com/alt-tek/kipy) is useful to simplify executing
scripts with KiCad's Python environment. It is required to generate board
plots with plot.py for releases.
- tea (https://gitea.com/gitea/tea) is required to generate releases. Install
and write your API token to ~/.config/git.alt-tek.com/api-token. Restrict
access to your API token by setting its file permissions accordingly
(i.e. 'chmod 600 ~/.config/git.alt-tek.com/api-token').
- In order to generate releases, SSH must be configured for git.alt-tek.com
such that SSH Git commands can be executed without explicitly specifying an
SSH identity or private key
RELEASING
1. See above dependencies
2. Create an annotated Git tag for the release commit using
"git tag -a v<version> -m 'Version <version>' <commit>" where version is the
board version in X.Y.Z form and <commit> is the commit hash
(e.g. "git tag -a v1.2.3 -m 'Version 1.2.3' d15c0cafe")
3. Push the tag upstream (i.e. 'git push origin tags/v<version>')
4. Use the release script to build the release and upload it as a draft
(i.e. './release <version>')
5. Review the release draft. If it is ready for pre-release or production,
publish it.

@ -0,0 +1,203 @@
#!/usr/bin/env kipy
__copyright__ = 'Copyright 2020 ALT-TEKNIK LLC'
from argparse import ArgumentParser
from os import path, rename
from os.path import dirname, realpath
from pcbnew import B_Cu, B_Mask, Edge_Cuts, EXCELLON_WRITER, F_Cu, F_Fab, \
F_Mask, F_Paste, F_SilkS, FILLED, FromMils, LoadBoard, LSET, \
PCB_PLOT_PARAMS, PLOT_CONTROLLER, PLOT_FORMAT_GERBER, PLOT_FORMAT_PDF, \
TEXTE_PCB
from uuid import uuid1
_UNNAMED_PAD = ''
class _PadExclusion:
def __init__(self, ref, name=None):
self.__ref = ref
self.__name = (None if name is None
else self.__normalize_name(str(name)))
def exclude_from_layer(self, board, layer_num):
count = 0
layer_mask = ~2**layer_num
for pad in board.FindModuleByReference(self.__ref).Pads():
if ((self.__name is None or
self.__name == self.__normalize_name(pad.GetName())) and
pad.IsOnLayer(layer_num)):
pad_mask = int(pad.GetLayerSet().FmtHex().replace('_', ''), 16)
pad_hex = hex(pad_mask & layer_mask)
layers = LSET()
layers.ParseHex(pad_hex, len(pad_hex))
pad.SetLayerSet(layers)
count += 1
if count:
if self.__name:
descr = 'pad {}'.format(self.__name)
else:
descr = 'pad' if count == 1 else 'pads'
if self.__name is not None:
descr = 'unnamed {}'.format(descr)
print(' Excluded {} {}'.format(self.__ref, descr))
@staticmethod
def __normalize_name(name):
return _UNNAMED_PAD if name == '~' or name.isspace() else name
_BOARD_NAME = 'silabs-dbg-adapter'
_VERSION_TOKEN = 'X.Y.Z'
_GERBER_EXT_OVERRIDES = {F_Fab: 'gm1', Edge_Cuts: 'gko'}
class _Layer:
def __init__(self, num, descr, **kwargs):
self.__num = num
self.__descr = descr
if kwargs.get('is_pdf'):
self.__use_aux_origin = False
self.__fmt = PLOT_FORMAT_PDF
self.__fmt_descr = 'PDF'
else:
self.__use_aux_origin = True
self.__fmt = PLOT_FORMAT_GERBER
self.__fmt_descr = 'Gerber'
scale = kwargs.get('scale')
self.__scale = 1.0 if not scale else scale
suffix = kwargs.get('suffix')
self.__suffix = '' if suffix is None else suffix
pad_exclusions = kwargs.get('pad_exclusions')
self.__pad_exclusions = (() if pad_exclusions is None
else pad_exclusions)
def plot(self, board_file, output_dir, version=None):
# Append the suffix for Gerber layers with file extension overrides to
# ease renaming after plotting
suffix = self.__suffix
override_gerber_ext = (self.__fmt == PLOT_FORMAT_GERBER and
self.__num in _GERBER_EXT_OVERRIDES)
if override_gerber_ext:
uuid = str(uuid1())
suffix = '{}-{}'.format(suffix, uuid) if suffix else uuid
# Indicate status
print('Plotting {} {}...'.format(self.__descr, self.__fmt_descr))
# Load the board
board = LoadBoard(board_file)
# Replace version text
if version is not None:
for drawing in board.GetDrawings():
if (drawing.IsOnLayer(self.__num) and
isinstance(drawing, TEXTE_PCB)):
text = drawing.GetText()
if _VERSION_TOKEN in text:
drawing.SetText(text.replace(_VERSION_TOKEN, version))
print(' Replaced version text')
# Apply pad exclusions
for pad_exclusion in self.__pad_exclusions:
pad_exclusion.exclude_from_layer(board, self.__num)
# Create a plot controller
controller = PLOT_CONTROLLER(board)
# Set the plot options
opts = controller.GetPlotOptions()
opts.SetOutputDirectory(output_dir)
opts.SetPlotFrameRef(False)
opts.SetPlotValue(False)
opts.SetPlotReference(True)
opts.SetPlotInvisibleText(False)
opts.SetExcludeEdgeLayer(True)
opts.SetSubtractMaskFromSilk(False)
opts.SetPlotViaOnMaskLayer(False)
opts.SetSkipPlotNPTH_Pads(True)
opts.SetUseAuxOrigin(self.__use_aux_origin)
opts.SetDrillMarksType(PCB_PLOT_PARAMS.NO_DRILL_SHAPE)
opts.SetAutoScale(False)
opts.SetScale(self.__scale)
opts.SetPlotMode(FILLED)
opts.SetLineWidth(FromMils(0.1))
opts.SetMirror(False)
opts.SetNegative(False)
if self.__fmt == PLOT_FORMAT_GERBER:
opts.SetUseGerberProtelExtensions(not override_gerber_ext)
opts.SetCreateGerberJobFile(False)
opts.SetGerberPrecision(6)
opts.SetUseGerberX2format(False)
opts.SetIncludeGerberNetlistInfo(False)
# Plot the layer
controller.SetColorMode(False)
controller.SetLayer(self.__num)
controller.OpenPlotfile(suffix, self.__fmt, self.__descr)
controller.PlotLayer()
controller.ClosePlot()
# Rename Gerber layers with file extension overrides
if override_gerber_ext:
src = '{}-{}.gbr'.format(_BOARD_NAME, suffix)
dest = _BOARD_NAME
if self.__suffix:
dest = '{}-{}'.format(dest, self.__suffix)
dest = '{}.{}'.format(dest, _GERBER_EXT_OVERRIDES[self.__num])
rename(path.join(output_dir, src), path.join(output_dir, dest))
_OUTPUT_DIR = 'plots'
_PDF_SCALE = 5.0
_DEFAULT_PAD_EXCLUSIONS = (
_PadExclusion('R4'),
_PadExclusion('R9'),
_PadExclusion('R11')
)
_LAYERS = (
_Layer(F_Fab, 'F.Fab'),
_Layer(F_Fab, 'F.Fab', is_pdf=True, scale=_PDF_SCALE, suffix='assembly'),
_Layer(F_Paste, 'F.Paste', pad_exclusions=_DEFAULT_PAD_EXCLUSIONS),
_Layer(F_SilkS, 'F.SilkS'),
_Layer(F_Mask, 'F.Mask'),
_Layer(F_Cu, 'F.Cu'),
_Layer(B_Cu, 'B.Cu'),
_Layer(B_Mask, 'B.Mask'),
_Layer(Edge_Cuts, 'Edge.Cuts')
)
def main(version=None):
# Canonicalize the board filename and output directory
project_dir = realpath(dirname(__file__))
board_file = '{}.kicad_pcb'.format(_BOARD_NAME)
abs_board_file = path.join(project_dir, board_file)
output_dir = realpath(path.join(project_dir, _OUTPUT_DIR))
# Plot the layers
for layer in _LAYERS:
layer.plot(abs_board_file, output_dir, version)
# Write the drill file
print('Writing Excellon drill file...')
board = LoadBoard(abs_board_file)
writer = EXCELLON_WRITER(board)
writer.SetOptions(False, # aMirror
False, # aMinimalHeader
board.GetDesignSettings().m_AuxOrigin, # aOffset
True) # aMerge_PTH_NPTH
writer.SetRouteModeForOvalHoles(True)
writer.SetFormat(True, # aMetric
EXCELLON_WRITER.DECIMAL_FORMAT) # aZerosFmt
writer.CreateDrillandMapFilesSet(output_dir, # aPlotDirectory
True, # aGenDrill
False) # aGenMap
rename(path.join(output_dir, '{}.drl'.format(_BOARD_NAME)),
path.join(output_dir, '{}.xln'.format(_BOARD_NAME)))
# Indicate status
print('Plotted {}'.format(board_file))
if __name__ == '__main__':
parser = ArgumentParser(description='creates board plots')
parser.add_argument('version', nargs='?', help='the board version')
args = parser.parse_args()
main(args.version)

@ -0,0 +1,133 @@
#!/usr/bin/env bash -e
#
# Copyright 2020 ALT-TEKNIK LLC
# Parse arguments
gitea_domain=git.alt-tek.com
unset version
while [[ $# -gt 0 ]]
do
arg=$1
shift
case $arg in
-h|--help)
IFS= read -rd '' usage << __eof__ || true
Usage: release [-h] version
Builds a release and uploads it to $gitea_domain as a draft
Positional arguments:
version The release version
Optional arguments:
-h, --help Show this help message and exit
__eof__
echo -n "$usage"
exit 0
;;
-*|--*)
echo "Invalid argument '$arg'. Use -h for help." >&2
exit 1
;;
*)
if [[ -v version ]]
then
echo "Invalid argument '$arg'. Use -h for help." >&2
exit 1
fi
version=$arg
esac
done
if [[ -z $version ]]
then
echo 'Missing version argument. Use -h for help.' >&2
exit 1
fi
# Initialize globals used by handle-trap
signals='INT TERM'
unset tmp_dir
prj_name=silabs-dbg-adapter
tea_login=$prj_name-release
# Usage: handle-trap [exit_code]
#
# Deletes temporary files and logs out of Tea upon script exit or signal
#
# Positional arguments:
# exit_code Optional exit code for which to trigger a script exit
function handle-trap {
trap - EXIT $signals
rm -rf "$tmp_dir"
tea logout -n "$tea_login"
if [[ $1 ]]
then
exit "$1"
fi
}
# Set the trap
trap handle-trap EXIT
trap 'handle-trap 1' $signals
# Create a temporary build directory
tmp_dir=$(mktemp -d)
# Git a clean clone
repo=alt-tek/$prj_name
git_dir=$tmp_dir/git
git clone "$gitea_domain:$repo" "$git_dir"
cd "$git_dir"
tag=v$version
git checkout "tags/$tag" -b "tmp/$tag-build"
# Create the archive directory
archive_name=${prj_name}_$version
archive_dir=$tmp_dir/$archive_name
mkdir "$archive_dir"
# Request a plot of the schematic
sch_dir_name=schematic
sch_dir=$tmp_dir/$sch_dir_name
mkdir "$sch_dir"
sch_basename=$prj_name.sch
IFS= read -rd '' sch_instructions << __eof__ || true
Opening $sch_basename with Eeschema...
1. From the 'File' menu, select 'Page Settings...'
a. Set 'Issue Date' to '$(date +%Y.%m.%d)'
b. Set 'Revision' to '$version'
c. Press 'OK'
2. From the 'File' menu, select 'Plot...'
a. Select 'PDF' for 'Output Format'
b. Select 'Schematic size' for 'Page size'
c. Enable 'Plot border and title block'
d. Select 'Color' for 'Output mode'
e. Set 'Default line width' to 6mils
e. Set 'Output directory' to '../$sch_dir_name'
f. Press 'Plot All Pages'
g. Press 'Close'
3. From the 'Eeschema' menu, select 'Quit eeschema'
a. Press 'Discard Changes' when prompted to save
__eof__
echo -n "$sch_instructions"
open -Wa eeschema "$git_dir/$sch_basename"
cp "$sch_dir/$prj_name.pdf" "$archive_dir/$prj_name-schematic.pdf"
echo "Plotted $sch_basename"
# Generate board plots
plot_dir=$git_dir/plots
rm -rf "$plot_dir"
./plot.py "$version"
cp "$plot_dir"/* "$archive_dir"
# Tar and Gzip the archive
archive=$tmp_dir/$archive_name.tar.gz
tar -czf "$archive" -C "$tmp_dir" "$archive_name"
# Draft the release
tea_token=$(<"$HOME/.config/$gitea_domain/api-token")
tea login add -n "$tea_login" -u "https://$gitea_domain" -t "$tea_token"
title="Version $version"
tea releases create -l "$tea_login" -r "$repo" --tag "$tag" -d -t "$title" \
-a "$archive"
echo "Drafted $title at https://$gitea_domain/$repo/releases"
Loading…
Cancel
Save