Added --example/-x option to generate a configuration example.

The example contains all the available preflights and outputs.
If the user specifies a PCB the names of the layers are from the provided PCB
This commit is contained in:
Salvador E. Tropea 2020-06-30 20:09:14 -03:00
parent f579d648bf
commit 4ae54f3ded
14 changed files with 993 additions and 247 deletions

View File

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- -e/--schematic option to specify any schematic (not just derived from the PCB
name.
- -x/--example option to generate a complete configuration example.
- Help for the supported outputs (--help-list-outputs, --help-outputs and
--help-output)
- Help for the supported preflights (--help-preflights)

View File

@ -427,6 +427,21 @@ Most options are the same you'll find in the KiCad dialogs.
## Using KiPlot
If you need a template for the configuration file try:
```
kiplot --example
```
This will generate a file named `example.kiplot.yaml` containing all the available options and comments about them.
You can use it to create your own configuration file.
If you want to use the layers of a particular PCB in the example use:
```
kiplot -b PCB_FILE --example
```
If the current directory contains only one PCB file and only one configuration file (named *.kiplot.yaml)
you can just call `kiplot`. No arguments needed. The tool will figure out which files to use.
@ -495,6 +510,7 @@ Usage:
kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
[-q | -v...] [-i] [TARGET...]
kiplot [-c PLOT_CONFIG] --list
kiplot [-b BOARD] --example
kiplot --help-list-outputs
kiplot --help-output=HELP_OUTPUT
kiplot --help-outputs
@ -520,7 +536,8 @@ Options:
-q, --quiet Remove information logs
-s PRE, --skip-pre PRE Skip preflights, comma separated or `all`
-v, --verbose Show debugging information
--version, -V Show program's version number and exit
-V, --version Show program's version number and exit
-x, --example Create an example configuration file.
```

View File

@ -1,7 +1,10 @@
#!/usr/bin/make
all: ../README.md
all: ../README.md samples/generic_plot.kiplot.yaml
../README.md: README.in replace_tags.pl ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/__main__.py
cat README.in | perl replace_tags.pl > ../README.md
samples/generic_plot.kiplot.yaml: ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/config_reader.py
../src/kiplot --example
mv example.kiplot.yaml $@

View File

@ -196,6 +196,21 @@ Most options are the same you'll find in the KiCad dialogs.
## Using KiPlot
If you need a template for the configuration file try:
```
kiplot --example
```
This will generate a file named `example.kiplot.yaml` containing all the available options and comments about them.
You can use it to create your own configuration file.
If you want to use the layers of a particular PCB in the example use:
```
kiplot -b PCB_FILE --example
```
If the current directory contains only one PCB file and only one configuration file (named *.kiplot.yaml)
you can just call `kiplot`. No arguments needed. The tool will figure out which files to use.

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ Usage:
kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
[-q | -v...] [-i] [TARGET...]
kiplot [-c PLOT_CONFIG] --list
kiplot [-b BOARD] --example
kiplot --help-list-outputs
kiplot --help-output=HELP_OUTPUT
kiplot --help-outputs
@ -30,7 +31,8 @@ Options:
-q, --quiet Remove information logs
-s PRE, --skip-pre PRE Skip preflights, comma separated or `all`
-v, --verbose Show debugging information
--version, -V Show program's version number and exit
-V, --version Show program's version number and exit
-x, --example Create an example configuration file.
"""
__author__ = 'John Beard, Salvador E. Tropea'
@ -52,7 +54,7 @@ log.set_domain('kiplot')
from .gs import (GS)
from .kiplot import (generate_outputs)
from .pre_base import (BasePreFlight)
from .config_reader import (CfgYamlReader, print_outputs_help, print_output_help, print_preflights_help)
from .config_reader import (CfgYamlReader, print_outputs_help, print_output_help, print_preflights_help, create_example)
from .misc import (NO_PCB_FILE, NO_SCH_FILE, EXIT_BAD_ARGS)
from .docopt import docopt
from .__version__ import __version__
@ -122,6 +124,12 @@ def solve_config(a_plot_config):
return plot_config
def check_board_file(board_file):
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
def solve_board_file(schematic, a_board_file):
board_file = a_board_file
if not board_file and schematic:
@ -137,9 +145,7 @@ def solve_board_file(schematic, a_board_file):
board_file = board_files[0]
logger.warning('More than one PCB file found in current directory.\n'
' Using '+board_file+' if you want to use another use -b option.')
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
check_board_file(board_file)
return board_file
@ -164,6 +170,10 @@ def main():
if args.help_preflights:
print_preflights_help()
sys.exit(0)
if args.example:
check_board_file(args.board_file)
create_example(args.board_file)
sys.exit(0)
# Determine the YAML file
plot_config = solve_config(args.plot_config)

View File

@ -2,12 +2,13 @@
Class to read KiPlot config files
"""
import sys
import os
from sys import (exit, maxsize)
from collections import OrderedDict
from .error import (KiPlotConfigurationError)
from .kiplot import (Layer)
from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS)
from .kiplot import (Layer, load_board)
from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE)
from mcpy import activate # noqa: F401
# Output classes
from .out_base import BaseOutput
@ -43,12 +44,12 @@ try:
except ImportError: # pragma: no cover
log.init(False, False)
logger.error('No yaml module for Python, install python3-yaml')
sys.exit(NO_YAML_MODULE)
exit(NO_YAML_MODULE)
def config_error(msg):
logger.error(msg)
sys.exit(EXIT_BAD_CONFIG)
exit(EXIT_BAD_CONFIG)
class CfgYamlReader(object):
@ -228,14 +229,14 @@ def trim(docstring):
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
# Determine minimum indentation (first line doesn't count):
indent = sys.maxsize
indent = maxsize
for line in lines[1:]:
stripped = line.lstrip()
if stripped:
indent = min(indent, len(line) - len(stripped))
# Remove indentation (first line is special):
trimmed = [lines[0].strip()]
if indent < sys.maxsize:
if indent < maxsize:
for line in lines[1:]:
trimmed.append(line[indent:].rstrip())
# Strip off trailing and leading blank lines:
@ -287,7 +288,7 @@ def print_outputs_help(details=False):
def print_output_help(name):
if not BaseOutput.is_registered(name):
logger.error('Unknown output type `{}`, try --help-list-outputs'.format(name))
sys.exit(EXIT_BAD_ARGS)
exit(EXIT_BAD_ARGS)
print_one_out_help(True, name, BaseOutput.get_class_for(name))
@ -300,3 +301,63 @@ def print_preflights_help():
if help is None:
help = 'Undocumented'
print('- {}: {}.'.format(n, help.rstrip()))
def create_example(pcb_file):
if os.path.isfile(EXAMPLE_CFG):
logger.error(EXAMPLE_CFG+" already exists, won't overwrite")
exit(WONT_OVERWRITE)
with open(EXAMPLE_CFG, 'w') as f:
logger.info('Creating {} example configuration'.format(EXAMPLE_CFG))
f.write('kiplot:\n version: 1\n')
# Preflights
f.write('\npreflight:\n')
pres = BasePreFlight.get_registered()
for n, o in pres.items():
if o.__doc__:
f.write(' #'+o.__doc__.rstrip()+'\n')
f.write(' {}: {}\n'.format(n, o.get_example()))
# Outputs
outs = BaseOutput.get_registered()
f.write('\noutputs:\n')
# List of layers
if pcb_file:
# We have a PCB to take as reference
load_board(pcb_file)
layers = Layer.get_pcb_layers()
else:
# Use the default list of layers
layers = Layer.get_default_layers()
for n, o in OrderedDict(sorted(outs.items())).items():
lines = trim(o.__doc__)
if len(lines) == 0:
lines = ['Undocumented', 'No description']
f.write(' # '+lines[0].rstrip()+':\n')
for ln in range(2, len(lines)):
f.write(' # '+lines[ln].rstrip()+'\n')
f.write(" - name: '{}_example'\n".format(n))
f.write(" comment: '{}'\n".format(lines[1]))
f.write(" type: '{}'\n".format(n))
f.write(" dir: 'Example/{}_dir'\n".format(n))
f.write(" options:\n")
obj = o('', n, '')
for k, v in BaseOutput.get_attrs_gen(obj):
help = getattr(obj, '_help_'+k)
if help:
help_lines = help.split('\n')
for hl in help_lines:
f.write(' # '+hl.strip()+'\n')
val = getattr(obj, k)
if isinstance(val, str):
val = "'{}'".format(val)
elif isinstance(val, bool):
val = str(val).lower()
f.write(' {}: {}\n'.format(k, val))
if '_layers' in obj.__dict__:
f.write(' layers:\n')
for layer in layers:
f.write(" - layer: '{}'\n".format(layer.name))
f.write(" suffix: '{}'\n".format(layer.suffix))
if layer.desc:
f.write(" description: '{}'\n".format(layer.desc))
f.write('\n')

View File

@ -53,6 +53,29 @@ class Layer(object):
'F.Fab': pcbnew.F_Fab,
'B.Fab': pcbnew.B_Fab,
}
# Default names
DEFAULT_LAYER_DESC = {
'F.Cu': 'Front copper',
'B.Cu': 'Bottom copper',
'F.Adhes': 'Front adhesive (glue)',
'B.Adhes': 'Bottom adhesive (glue)',
'F.Paste': 'Front solder paste',
'B.Paste': 'Bottom solder paste',
'F.SilkS': 'Front silkscreen (artwork)',
'B.SilkS': 'Bottom silkscreen (artwork)',
'F.Mask': 'Front soldermask (negative)',
'B.Mask': 'Bottom soldermask (negative)',
'Dwgs.User': 'User drawings',
'Cmts.User': 'User comments',
'Eco1.User': 'For user usage 1',
'Eco2.User': 'For user usage 2',
'Edge.Cuts': 'Board shape',
'Margin': 'Margin relative to edge cut',
'F.CrtYd': 'Front courtyard area',
'B.CrtYd': 'Bottom courtyard area',
'F.Fab': 'Front documentation',
'B.Fab': 'Bottom documentation',
}
# Names from the board file
pcb_layers = {}
@ -61,13 +84,24 @@ class Layer(object):
self.is_inner = False
self.name = name
self.suffix = suffix
if desc is None and name in Layer.DEFAULT_LAYER_DESC:
desc = Layer.DEFAULT_LAYER_DESC[name]
self.desc = desc
@staticmethod
def set_pcb_layers(board):
for id in range(pcbnew.PCBNEW_LAYER_ID_START, pcbnew.PCB_LAYER_ID_COUNT):
for id in board.GetEnabledLayers().Seq():
Layer.pcb_layers[board.GetLayerName(id)] = id
@staticmethod
def get_pcb_layers():
layers = []
for n, id in Layer.pcb_layers.items():
s = n.replace('.', '_')
d = Layer.DEFAULT_LAYER_DESC.get(n)
layers.append(Layer(n, s, d))
return layers
def get_layer_id_from_name(self, layer_cnt):
""" Get the pcbnew layer from the string provided in the config """
# Priority
@ -94,6 +128,14 @@ class Layer(object):
raise PlotError("Inner layer `{}` is not valid for this board".format(self))
return self.id
@staticmethod
def get_default_layers():
layers = []
for n, d in Layer.DEFAULT_LAYER_DESC.items():
s = n.replace('.', '_')
layers.append(Layer(n, s, d))
return layers
def __str__(self):
return "{} ({} '{}' {})".format(self.name, self.id, self.desc, self.suffix)
@ -130,10 +172,12 @@ def check_eeschema_do():
exit(NO_SCH_FILE)
def load_board():
GS.check_pcb()
def load_board(pcb_file=None):
if not pcb_file:
GS.check_pcb()
pcb_file = GS.pcb_file
try:
board = pcbnew.LoadBoard(GS.pcb_file)
board = pcbnew.LoadBoard(pcb_file)
if BasePreFlight.get_option('check_zone_fills'):
pcbnew.ZONE_FILLER(board).Fill(board.Zones())
# Now we know the names of the layers for this board

View File

@ -20,6 +20,7 @@ NO_YAML_MODULE = 15
NO_PCBNEW_MODULE = 16
CORRUPTED_PCB = 17
KICAD2STEP_ERR = 18
WONT_OVERWRITE = 19
CMD_EESCHEMA_DO = 'eeschema_do'
URL_EESCHEMA_DO = 'https://github.com/INTI-CMNB/kicad-automation-scripts'
@ -32,3 +33,4 @@ URL_KIBOM = 'https://github.com/INTI-CMNB/KiBoM'
CMD_IBOM = 'generate_interactive_bom.py'
URL_IBOM = 'https://github.com/INTI-CMNB/InteractiveHtmlBom'
KICAD2STEP = 'kicad2step'
EXAMPLE_CFG = 'example.kiplot.yaml'

View File

@ -13,6 +13,8 @@ AUTO_SCALE = 0
class AnyLayer(BaseOutput):
def __init__(self, name, type, description):
super(AnyLayer, self).__init__(name, type, description)
# We need layers, so we define it
self._layers = None
# Options
with document:
self.exclude_edge_layer = True

View File

@ -19,6 +19,8 @@ class PDF_Pcb_Print(BaseOutput): # noqa: F821
This output is what you get from the 'File/Print' menu in pcbnew. """
def __init__(self, name, type, description):
super(PDF_Pcb_Print, self).__init__(name, type, description)
# We need layers, so we define it
self._layers = None
# Options
with document:
self.output_name = ''

View File

@ -84,6 +84,10 @@ class BasePreFlight(object):
""" True for preflights that needs the PCB """
return self._pcb_related
def get_example():
""" Returns a YAML value for the example config """
return 'true'
def run(self, brd_file): # pragma: no cover
logger.error("The run method for the preflight class name `{}` isn't implemented".format(self._name))

View File

@ -9,6 +9,10 @@ class Filters(BasePreFlight): # noqa: F821
def __init__(self, name, value):
super().__init__(name, value)
def get_example():
""" Returns a YAML value for the example config """
return "\n - filter: 'Filter description'\n number: 10\n regex: 'Regular expression to match'"
def run(self):
pass

View File

@ -11,6 +11,10 @@ class Ignore_Unconnected(BasePreFlight): # noqa: F821
raise KiPlotConfigurationError('must be boolean')
self._enabled = value
def get_example():
""" Returns a YAML value for the example config """
return 'false'
def run(self, brd_file):
pass