Now layers are solved when we really need it.

So we can do some operations (like --list) without loading the board.
Also: now the names for the layers are asked to pcbnew classes (not from file)
This commit is contained in:
Salvador E. Tropea 2020-06-29 12:55:02 -03:00
parent ef4b2f8f95
commit 42d1f14056
8 changed files with 97 additions and 126 deletions

View File

@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Drill outputs: map.type and report.filename now should be map and report.
The old mechanism is currently supported, but deprecated.
- Now the command line usage is more clearly documented, but also more strict.
- The --list option doesn't need a PCB file anymore.
Note that passing it is now considered an error.
### Added
- Help for the supported outputs (--help-list-outputs, --help-outputs and

View File

@ -4,7 +4,7 @@
Usage:
kiplot [-b BOARD] [-c CONFIG] [-d OUT_DIR] [-s PRE] [-q | -v...] [-i]
[TARGET...]
kiplot [-b BOARD] [-c PLOT_CONFIG] --list
kiplot [-c PLOT_CONFIG] --list
kiplot --help-list-outputs
kiplot --help-output=HELP_OUTPUT
kiplot --help-outputs
@ -90,30 +90,6 @@ def main():
print_preflights_help()
sys.exit(0)
# Determine the PCB file
if args.board_file is None:
board_files = glob('*.kicad_pcb')
if len(board_files) == 1:
board_file = board_files[0]
logger.info('Using PCB file: '+board_file)
elif len(board_files) > 1:
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.')
else:
logger.error('No PCB file found (*.kicad_pcb), use -b to specify one.')
sys.exit(EXIT_BAD_ARGS)
else:
board_file = args.board_file
if not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
GS.pcb_file = board_file
GS.sch_file = os.path.splitext(board_file)[0] + '.sch'
if not os.path.isfile(GS.sch_file):
logger.warning('Missing schematic file: ' + GS.sch_file)
GS.sch_file = None
# Determine the YAML file
if args.plot_config is None:
plot_configs = glob('*.kiplot.yaml')
@ -151,6 +127,30 @@ def main():
list_pre_and_outs(logger, outputs)
sys.exit(0)
# Determine the PCB file
if args.board_file is None:
board_files = glob('*.kicad_pcb')
if len(board_files) == 1:
board_file = board_files[0]
logger.info('Using PCB file: '+board_file)
elif len(board_files) > 1:
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.')
else:
logger.error('No PCB file found (*.kicad_pcb), use -b to specify one.')
sys.exit(EXIT_BAD_ARGS)
else:
board_file = args.board_file
if not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
GS.pcb_file = board_file
GS.sch_file = os.path.splitext(board_file)[0] + '.sch'
if not os.path.isfile(GS.sch_file):
logger.warning('Missing schematic file: ' + GS.sch_file)
GS.sch_file = None
generate_outputs(outputs, args.target, args.invert_sel, args.skip_pre)

View File

@ -2,14 +2,11 @@
Class to read KiPlot config files
"""
import re
import sys
from collections import OrderedDict
import pcbnew
from .error import (KiPlotConfigurationError)
from .kiplot import (Layer, get_layer_id_from_pcb)
from .kiplot import (Layer)
from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS)
from mcpy import activate # noqa: F401
# Output classes
@ -69,52 +66,6 @@ class CfgYamlReader(object):
config_error("Unknown KiPlot config version: "+str(version))
return version
def _get_layer_from_str(self, s):
"""
Get the pcbnew layer from a string in the config
"""
D = {
'F.Cu': pcbnew.F_Cu,
'B.Cu': pcbnew.B_Cu,
'F.Adhes': pcbnew.F_Adhes,
'B.Adhes': pcbnew.B_Adhes,
'F.Paste': pcbnew.F_Paste,
'B.Paste': pcbnew.B_Paste,
'F.SilkS': pcbnew.F_SilkS,
'B.SilkS': pcbnew.B_SilkS,
'F.Mask': pcbnew.F_Mask,
'B.Mask': pcbnew.B_Mask,
'Dwgs.User': pcbnew.Dwgs_User,
'Cmts.User': pcbnew.Cmts_User,
'Eco1.User': pcbnew.Eco1_User,
'Eco2.User': pcbnew.Eco2_User,
'Edge.Cuts': pcbnew.Edge_Cuts,
'Margin': pcbnew.Margin,
'F.CrtYd': pcbnew.F_CrtYd,
'B.CrtYd': pcbnew.B_CrtYd,
'F.Fab': pcbnew.F_Fab,
'B.Fab': pcbnew.B_Fab,
}
layer = None
# Priority
# 1) Internal list
if s in D:
layer = Layer(D[s], False, s)
else:
id = get_layer_id_from_pcb(s)
if id is not None:
# 2) List from the PCB
layer = Layer(id, id < pcbnew.B_Cu, s)
elif s.startswith("Inner"):
# 3) Inner.N names
m = re.match(r"^Inner\.([0-9]+)$", s)
if not m:
raise KiPlotConfigurationError("Malformed inner layer name: {}, use Inner.N".format(s))
layer = Layer(int(m.group(1)), True, s)
else:
raise KiPlotConfigurationError('Unknown layer name: '+s)
return layer
def _parse_layers(self, layers_to_parse):
# Check we have a list of layers
if not isinstance(layers_to_parse, list):
@ -139,9 +90,7 @@ class CfgYamlReader(object):
if layer is None:
raise KiPlotConfigurationError("Missing `layer` attribute for layer entry ({})".format(l))
# Create an object for it
layer = self._get_layer_from_str(layer)
layer.set_extra(suffix, description)
layers.append(layer)
layers.append(Layer(layer, suffix, description))
return layers
def _parse_output(self, o_obj):

View File

@ -36,53 +36,78 @@ class GS(object):
out_dir = None
filter_file = None
debug_enabled = False
pcb_layers = None
class Layer(object):
""" A layer description """
def __init__(self, id, is_inner, name):
self.id = id
self.is_inner = is_inner
self.name = name
self.suffix = None
self.desc = None
# Default names
DEFAULT_LAYER_NAMES = {
'F.Cu': pcbnew.F_Cu,
'B.Cu': pcbnew.B_Cu,
'F.Adhes': pcbnew.F_Adhes,
'B.Adhes': pcbnew.B_Adhes,
'F.Paste': pcbnew.F_Paste,
'B.Paste': pcbnew.B_Paste,
'F.SilkS': pcbnew.F_SilkS,
'B.SilkS': pcbnew.B_SilkS,
'F.Mask': pcbnew.F_Mask,
'B.Mask': pcbnew.B_Mask,
'Dwgs.User': pcbnew.Dwgs_User,
'Cmts.User': pcbnew.Cmts_User,
'Eco1.User': pcbnew.Eco1_User,
'Eco2.User': pcbnew.Eco2_User,
'Edge.Cuts': pcbnew.Edge_Cuts,
'Margin': pcbnew.Margin,
'F.CrtYd': pcbnew.F_CrtYd,
'B.CrtYd': pcbnew.B_CrtYd,
'F.Fab': pcbnew.F_Fab,
'B.Fab': pcbnew.B_Fab,
}
# Names from the board file
pcb_layers = {}
def set_extra(self, suffix, desc):
def __init__(self, name, suffix, desc):
self.id = pcbnew.UNDEFINED_LAYER
self.is_inner = False
self.name = name
self.suffix = suffix
self.desc = desc
@staticmethod
def set_pcb_layers(board):
for id in range(pcbnew.PCBNEW_LAYER_ID_START, pcbnew.PCB_LAYER_ID_COUNT):
Layer.pcb_layers[board.GetLayerName(id)] = id
def get_layer_id_from_name(self, layer_cnt):
""" Get the pcbnew layer from the string provided in the config """
# Priority
# 1) Internal list
if self.name in Layer.DEFAULT_LAYER_NAMES:
self.id = Layer.DEFAULT_LAYER_NAMES[self.name]
else:
id = Layer.pcb_layers.get(self.name)
if id is not None:
# 2) List from the PCB
self.id = id
self.is_inner = id < pcbnew.B_Cu
elif self.name.startswith("Inner"):
# 3) Inner.N names
m = re.match(r"^Inner\.([0-9]+)$", self.name)
if not m:
raise PlotError("Malformed inner layer name: {}, use Inner.N".format(self.name))
self.id = int(m.group(1))
self.is_inner = True
else:
raise PlotError("Unknown layer name: "+self.name)
# Check if the layer is in use
if self.is_inner and (self.id < 1 or self.id >= layer_cnt - 1):
raise PlotError("Inner layer `{}` is not valid for this board".format(self))
return self.id
def __str__(self):
return "{} ({} '{}' {})".format(self.name, self.id, self.desc, self.suffix)
def load_pcb_layers():
""" Load layer names from the PCB """
GS.pcb_layers = {}
with open(GS.pcb_file, "r") as pcb_file:
collect_layers = False
for line in pcb_file:
if collect_layers:
z = re.match(r'\s+\((\d+)\s+(\S+)', line)
if z:
res = z.groups()
# print(res[1]+'->'+res[0])
GS.pcb_layers[res[1]] = int(res[0])
else:
if re.search(r'^\s+\)$', line):
collect_layers = False
break
else:
if re.search(r'\s+\(layers', line):
collect_layers = True
def get_layer_id_from_pcb(name):
if GS.pcb_layers is None:
load_pcb_layers()
return GS.pcb_layers.get(name)
def check_version(command, version):
cmd = [command, '--version']
logger.debug('Running: '+str(cmd))
@ -120,6 +145,8 @@ def load_board():
board = pcbnew.LoadBoard(GS.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
Layer.set_pcb_layers(board)
except OSError as e:
logger.error('Error loading PCB file. Currupted?')
logger.error(e)

View File

@ -99,11 +99,7 @@ class AnyLayer(BaseOutput):
for l in self._layers:
suffix = l.suffix
desc = l.desc
id = l.id
# for inner layers, we can now check if the layer exists
if l.is_inner:
if id < 1 or id >= layer_cnt - 1:
raise PlotError("Inner layer `{}` is not valid for this board".format(l))
id = l.get_layer_id_from_name(layer_cnt)
# Set current layer
plot_ctrl.SetLayer(id)
# Skipping NPTH is controlled by whether or not this is

View File

@ -1,7 +1,7 @@
import os
from subprocess import (call)
from .pre_base import BasePreFlight
from .error import (KiPlotConfigurationError, PlotError)
from .error import (KiPlotConfigurationError)
from .kiplot import (check_script, GS)
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT)
from kiplot.macros import macros, document, output_class # noqa: F401
@ -34,10 +34,7 @@ class PDF_Pcb_Print(BaseOutput): # noqa: F821
# Verify the inner layers
layer_cnt = board.GetCopperLayerCount()
for l in self._layers:
# for inner layers, we can now check if the layer exists
if l.is_inner:
if l.id < 1 or l.id >= layer_cnt - 1:
raise PlotError("Inner layer `{}` is not valid for this board".format(l))
l.get_layer_id_from_name(layer_cnt)
# Output file name
output = self.output_name
if not output:

View File

@ -206,7 +206,7 @@ def test_auto_pcb_and_cfg_2():
def test_list():
ctx = context.TestContext('List', '3Rs', 'pre_and_position', POS_DIR)
ctx.run(extra=['--list'], no_verbose=True, no_out_dir=True)
ctx.run(extra=['--list'], no_verbose=True, no_out_dir=True, no_board_file=True)
assert ctx.search_err('run_erc: True')
assert ctx.search_err('run_drc: True')

View File

@ -99,7 +99,7 @@ def test_drill_map_wrong_type():
def test_wrong_layer_1():
ctx = context.TestContext('ErrorWrongLayer1', '3Rs', 'error_wrong_layer_1', None)
ctx.run(EXIT_BAD_CONFIG)
ctx.run(PLOT_ERROR)
assert ctx.search_err("Unknown layer name: F.Bogus")
ctx.clean_up()
@ -113,7 +113,7 @@ def test_wrong_layer_2():
def test_wrong_layer_3():
ctx = context.TestContext('ErrorWrongLayer3', '3Rs', 'error_wrong_layer_3', None)
ctx.run(EXIT_BAD_CONFIG)
ctx.run(PLOT_ERROR)
assert ctx.search_err("Malformed inner layer name: Inner_1,")
ctx.clean_up()