parent
9edaef32b6
commit
95edf0b1fe
@ -0,0 +1,193 @@
|
|||||||
|
#!/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 = '2x10-dip-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'
|
||||||
|
_LAYERS = (
|
||||||
|
_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,105 @@
|
|||||||
|
#!/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=2x10-dip-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"
|
||||||
|
|
||||||
|
# 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