Add plot and release scripts
parent
8af97455ac
commit
ea56e8020a
@ -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…
Reference in New Issue