Now the schematic can be specified separately.

We now test if the PCB and/or SCH are there only when we need them.
This commit is contained in:
Salvador E. Tropea 2020-06-29 19:25:54 -03:00
parent 017f2ace47
commit 3c6f4950c7
21 changed files with 236 additions and 82 deletions

View File

@ -13,8 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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.
- Now we test the PCB and/or SCH only when we are doing something that needs
them.
### Added
- -e/--schematic option to specify any schematic (not just derived from the PCB
name.
- Help for the supported outputs (--help-list-outputs, --help-outputs and
--help-output)
- Help for the supported preflights (--help-preflights)

View File

@ -492,8 +492,8 @@ kiplot --list
KiPlot: Command-line Plotting for KiCad
Usage:
kiplot [-b BOARD] [-c CONFIG] [-d OUT_DIR] [-s PRE] [-q | -v...] [-i]
[TARGET...]
kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
[-q | -v...] [-i] [TARGET...]
kiplot [-c PLOT_CONFIG] --list
kiplot --help-list-outputs
kiplot --help-output=HELP_OUTPUT
@ -510,6 +510,7 @@ Options:
-b BOARD, --board-file BOARD The PCB .kicad-pcb board file
-c CONFIG, --plot-config CONFIG The plotting config file to use
-d OUT_DIR, --out-dir OUT_DIR The output directory [default: .]
-e SCHEMA, --schematic SCHEMA The schematic file (.sch)
--help-list-outputs List supported outputs
--help-output HELP_OUTPUT Help for this particular output
--help-outputs List supported outputs and details

View File

@ -2,8 +2,8 @@
"""KiPlot: Command-line Plotting for KiCad
Usage:
kiplot [-b BOARD] [-c CONFIG] [-d OUT_DIR] [-s PRE] [-q | -v...] [-i]
[TARGET...]
kiplot [-b BOARD] [-e SCHEMA] [-c CONFIG] [-d OUT_DIR] [-s PRE]
[-q | -v...] [-i] [TARGET...]
kiplot [-c PLOT_CONFIG] --list
kiplot --help-list-outputs
kiplot --help-output=HELP_OUTPUT
@ -20,6 +20,7 @@ Options:
-b BOARD, --board-file BOARD The PCB .kicad-pcb board file
-c CONFIG, --plot-config CONFIG The plotting config file to use
-d OUT_DIR, --out-dir OUT_DIR The output directory [default: .]
-e SCHEMA, --schematic SCHEMA The schematic file (.sch)
--help-list-outputs List supported outputs
--help-output HELP_OUTPUT Help for this particular output
--help-outputs List supported outputs and details
@ -48,13 +49,16 @@ from logging import DEBUG
# Import log first to set the domain
from . import log
log.set_domain('kiplot')
from .kiplot import (GS, generate_outputs)
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 .misc import (NO_PCB_FILE, EXIT_BAD_ARGS)
from .misc import (NO_PCB_FILE, NO_SCH_FILE, EXIT_BAD_ARGS)
from .docopt import docopt
from .__version__ import __version__
logger = None
def list_pre_and_outs(logger, outputs):
logger.info('Available actions:\n')
@ -69,11 +73,82 @@ def list_pre_and_outs(logger, outputs):
logger.info('- '+str(o))
def solve_schematic(a_schematic, a_board_file):
schematic = a_schematic
if not schematic and a_board_file:
sch = os.path.splitext(a_board_file)[0]+'.sch'
if os.path.isfile(sch):
schematic = sch
if not schematic:
schematics = glob('*.sch')
if len(schematics) == 1:
schematic = schematics[0]
logger.info('Using SCH file: '+schematic)
elif len(schematics) > 1:
# Look for a schematic with a PCB
boards = glob('*.kicad_pcb')
boards_schs = [x[:-9]+'sch' for x in boards]
for sch in schematics:
if sch in boards_schs:
schematic = sch
break
if schematic is None:
schematic = schematics[0]
logger.warning('More than one SCH file found in current directory.\n'
' Using '+schematic+' if you want to use another use -e option.')
if schematic and not os.path.isfile(schematic):
logger.error("Schematic file not found: "+schematic)
sys.exit(NO_SCH_FILE)
return schematic
def solve_config(a_plot_config):
plot_config = a_plot_config
if not plot_config:
plot_configs = glob('*.kiplot.yaml')
if len(plot_configs) == 1:
plot_config = plot_configs[0]
logger.info('Using config file: '+plot_config)
elif len(plot_configs) > 1:
plot_config = plot_configs[0]
logger.warning('More than one config file found in current directory.\n'
' Using '+plot_config+' if you want to use another use -c option.')
else:
logger.error('No config file found (*.kiplot.yaml), use -c to specify one.')
sys.exit(EXIT_BAD_ARGS)
if not os.path.isfile(plot_config):
logger.error("Plot config file not found: "+plot_config)
sys.exit(EXIT_BAD_ARGS)
return plot_config
def solve_board_file(schematic, a_board_file):
board_file = a_board_file
if not board_file and schematic:
pcb = os.path.splitext(schematic)[0]+'.kicad_pcb'
if os.path.isfile(pcb):
board_file = pcb
if not board_file:
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.')
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
sys.exit(NO_PCB_FILE)
return board_file
def main():
ver = 'KiPlot '+__version__+' - '+__copyright__+' - License: '+__license__
args = docopt(__doc__, version=ver, options_first=True)
# Create a logger with the specified verbosity
global logger
logger = log.init(args.verbose, args.quiet)
GS.debug_enabled = logger.getEffectiveLevel() <= DEBUG
@ -91,24 +166,7 @@ def main():
sys.exit(0)
# Determine the YAML file
if args.plot_config is None:
plot_configs = glob('*.kiplot.yaml')
if len(plot_configs) == 1:
plot_config = plot_configs[0]
logger.info('Using config file: '+plot_config)
elif len(plot_configs) > 1:
plot_config = plot_configs[0]
logger.warning('More than one config file found in current directory.\n'
' Using '+plot_config+' if you want to use another use -c option.')
else:
logger.error('No config file found (*.kiplot.yaml), use -c to specify one.')
sys.exit(EXIT_BAD_ARGS)
else:
plot_config = args.plot_config
if not os.path.isfile(plot_config):
logger.error("Plot config file not found: "+plot_config)
sys.exit(EXIT_BAD_ARGS)
plot_config = solve_config(args.plot_config)
# Read the config file
cr = CfgYamlReader()
outputs = None
@ -127,29 +185,10 @@ def main():
list_pre_and_outs(logger, outputs)
sys.exit(0)
# Determine the SCH file
GS.sch_file = solve_schematic(args.schematic, args.board_file)
# 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
GS.pcb_file = solve_board_file(GS.sch_file, args.board_file)
generate_outputs(outputs, args.target, args.invert_sel, args.skip_pre)

29
kiplot/gs.py Normal file
View File

@ -0,0 +1,29 @@
from sys import exit
from .misc import (EXIT_BAD_ARGS)
from .log import (get_logger)
logger = get_logger(__name__)
class GS(object):
"""
Class to keep the global settings.
Is a static class, just a placeholder for some global variables.
"""
pcb_file = None
sch_file = None
out_dir = None
filter_file = None
debug_enabled = False
@staticmethod
def check_pcb():
if not GS.pcb_file:
logger.error('No PCB file found (*.kicad_pcb), use -b to specify one.')
exit(EXIT_BAD_ARGS)
@staticmethod
def check_sch():
if not GS.sch_file:
logger.error('No SCH file found (*.sch), use -e to specify one.')
exit(EXIT_BAD_ARGS)

View File

@ -9,6 +9,7 @@ from shutil import which
from subprocess import (run, PIPE)
from distutils.version import StrictVersion
from .gs import (GS)
from .misc import (PLOT_ERROR, NO_PCBNEW_MODULE, MISSING_TOOL, CMD_EESCHEMA_DO, URL_EESCHEMA_DO, NO_SCH_FILE, CORRUPTED_PCB,
EXIT_BAD_ARGS)
from .error import (PlotError)
@ -27,17 +28,6 @@ except ImportError: # pragma: no cover
exit(NO_PCBNEW_MODULE)
class GS(object):
"""
Class to keep the global settings.
Is a static class, just a placeholder for some global variables.
"""
pcb_file = None
out_dir = None
filter_file = None
debug_enabled = False
class Layer(object):
""" A layer description """
# Default names
@ -141,6 +131,7 @@ def check_eeschema_do():
def load_board():
GS.check_pcb()
try:
board = pcbnew.LoadBoard(GS.pcb_file)
if BasePreFlight.get_option('check_zone_fills'):
@ -210,6 +201,8 @@ def generate_outputs(outputs, target, invert, skip_pre):
# Should we load the PCB?
if out.is_pcb() and (board is None):
board = load_board()
if out.is_sch():
GS.check_sch()
try:
out.run(get_output_dir(out.get_outdir()), board)
except PlotError as e:

View File

@ -2,7 +2,7 @@ import os
from pcbnew import (GERBER_JOBFILE_WRITER, PCB_PLOT_PARAMS, FromMM, PLOT_CONTROLLER, IsCopperLayer, SKETCH)
from .out_base import (BaseOutput)
from .error import (PlotError, KiPlotConfigurationError)
from .kiplot import (GS)
from .gs import (GS)
from kiplot.macros import macros, document # noqa: F401
from . import log

View File

@ -1,7 +1,8 @@
import os
from subprocess import (check_output, STDOUT, CalledProcessError)
from .misc import (CMD_IBOM, URL_IBOM, BOM_ERROR)
from .kiplot import (GS, check_script)
from .gs import (GS)
from .kiplot import (check_script)
from .error import KiPlotConfigurationError
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log

View File

@ -3,7 +3,8 @@ from glob import (glob)
from subprocess import (check_output, STDOUT, CalledProcessError)
from .error import KiPlotConfigurationError
from .misc import (CMD_KIBOM, URL_KIBOM, BOM_ERROR)
from .kiplot import (GS, check_script)
from .kiplot import (check_script)
from .gs import (GS)
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log

View File

@ -2,7 +2,8 @@ import os
from subprocess import (call)
from .pre_base import BasePreFlight
from .error import (KiPlotConfigurationError)
from .kiplot import (check_script, GS)
from .gs import (GS)
from .kiplot import (check_script)
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT)
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log

View File

@ -1,6 +1,7 @@
import os
from subprocess import (call)
from .kiplot import (check_eeschema_do, GS)
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .misc import (CMD_EESCHEMA_DO, PDF_SCH_PRINT)
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log

View File

@ -3,7 +3,7 @@ import re
from subprocess import (check_output, STDOUT, CalledProcessError)
from .error import KiPlotConfigurationError
from .misc import (KICAD2STEP, KICAD2STEP_ERR)
from .kiplot import (GS)
from .gs import (GS)
from kiplot.macros import macros, document, output_class # noqa: F401
from . import log

View File

@ -1,6 +1,7 @@
from . import log
from .gs import (GS)
from .log import (get_logger)
logger = log.get_logger(__name__)
logger = get_logger(__name__)
class BasePreFlight(object):
@ -55,6 +56,10 @@ class BasePreFlight(object):
def run_enabled():
for k, v in BasePreFlight._in_use.items():
if v._enabled:
if v.is_sch():
GS.check_sch()
if v.is_pcb():
GS.check_pcb()
logger.debug('Preflight apply '+k)
v.apply()
for k, v in BasePreFlight._in_use.items():
@ -72,11 +77,11 @@ class BasePreFlight(object):
return "{}: {}".format(self._name, self._enabled)
def is_sch(self):
""" True for outputs that works on the schematic """
""" True for preflights that needs the schematic """
return self._sch_related
def is_pcb(self):
""" True for outputs that works on the PCB """
""" True for preflights that needs the PCB """
return self._pcb_related
def run(self, brd_file): # pragma: no cover

View File

@ -10,7 +10,6 @@ class Check_Zone_Fills(BasePreFlight): # noqa: F821
if not isinstance(value, bool):
raise KiPlotConfigurationError('must be boolean')
self._enabled = value
self._pcb_related = True
def run(self):
pass

View File

@ -2,7 +2,8 @@ from sys import (exit)
from subprocess import (call)
from kiplot.macros import macros, pre_class # noqa: F401
from .error import (KiPlotConfigurationError)
from .kiplot import (GS, check_script)
from .gs import (GS)
from .kiplot import (check_script)
from .misc import (CMD_PCBNEW_RUN_DRC, URL_PCBNEW_RUN_DRC, DRC_ERROR)
from .log import (get_logger)

View File

@ -1,7 +1,8 @@
from sys import (exit)
from subprocess import (call)
from kiplot.macros import macros, pre_class # noqa: F401
from .kiplot import (GS, check_eeschema_do)
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .error import (KiPlotConfigurationError)
from .misc import (CMD_EESCHEMA_DO, ERC_ERROR)
from .log import (get_logger)

View File

@ -1,5 +1,5 @@
import os
from .kiplot import (GS)
from .gs import (GS)
from kiplot.macros import macros, pre_class # noqa: F401

View File

@ -10,7 +10,6 @@ class Ignore_Unconnected(BasePreFlight): # noqa: F821
if not isinstance(value, bool):
raise KiPlotConfigurationError('must be boolean')
self._enabled = value
self._pcb_related = True
def run(self, brd_file):
pass

View File

@ -2,7 +2,8 @@ from sys import (exit)
from subprocess import (call)
from kiplot.macros import macros, pre_class # noqa: F401
from .error import (KiPlotConfigurationError)
from .kiplot import (GS, check_eeschema_do)
from .gs import (GS)
from .kiplot import (check_eeschema_do)
from .misc import (CMD_EESCHEMA_DO, BOM_ERROR)
from .log import (get_logger)

View File

@ -26,10 +26,10 @@ from kiplot.misc import (BOM_ERROR)
BOM_DIR = 'BoM'
def test_bom():
def test_bom_ok():
prj = 'bom'
ctx = context.TestContext('BoM_simple', prj, prj, BOM_DIR)
ctx.run()
ctx.run(no_board_file=True, extra=['-e', os.path.join(ctx.get_board_dir(), 'bom.sch')])
# Check all outputs are there
# Default format is PRJ_bom_REVISION
name = os.path.join(BOM_DIR, prj+'_bom_')

View File

@ -10,10 +10,13 @@ Tests miscellaneous stuff.
- Missing schematic
- Wrong PCB name
- Missing PCB
- Missing SCH
- Missing config
- Wrong config name
- Guess the PCB and YAML
- Guess the PCB and YAML when more than one is present
- Guess the SCH and YAML
- Guess the SCH and YAML when more than one is present
- --list
For debug information use:
@ -32,7 +35,7 @@ from utils import context
prev_dir = os.path.dirname(prev_dir)
if prev_dir not in sys.path:
sys.path.insert(0, prev_dir)
from kiplot.misc import (EXIT_BAD_ARGS, EXIT_BAD_CONFIG, NO_SCH_FILE, NO_PCB_FILE)
from kiplot.misc import (EXIT_BAD_ARGS, EXIT_BAD_CONFIG, NO_PCB_FILE, NO_SCH_FILE)
POS_DIR = 'positiondir'
@ -112,9 +115,19 @@ def test_select_output():
def test_miss_sch():
prj = 'fail-project'
ctx = context.TestContext('MissingSCH', prj, 'pre_and_position', POS_DIR)
ctx.run(NO_SCH_FILE, extra=['pos_ascii'])
ctx.run(EXIT_BAD_ARGS, extra=['pos_ascii'])
assert ctx.search_err('Missing schematic')
assert ctx.search_err('No SCH file found')
ctx.clean_up()
def test_miss_sch_2():
prj = 'fail-project'
ctx = context.TestContext('MissingSCH_2', prj, 'pre_and_position', POS_DIR)
ctx.run(NO_SCH_FILE, no_board_file=True, extra=['-e', 'bogus', 'pos_ascii'])
assert ctx.search_err('Schematic file not found')
ctx.clean_up()
@ -132,7 +145,7 @@ def test_miss_pcb():
def test_miss_pcb_2():
ctx = context.TestContext('MissingPCB_2', '3Rs', 'pre_and_position', POS_DIR)
ctx.run(EXIT_BAD_ARGS, no_board_file=True)
ctx.run(EXIT_BAD_ARGS, no_board_file=True, extra=['-s', 'run_erc,update_xml', 'pos_ascii'])
assert ctx.search_err('No PCB file found')
@ -161,6 +174,8 @@ def test_miss_yaml_2():
def test_auto_pcb_and_cfg():
""" Test guessing the PCB and config file.
Only one them is there. """
prj = '3Rs'
ctx = context.TestContext('GuessPCB_cfg', prj, 'pre_and_position', POS_DIR)
@ -180,6 +195,8 @@ def test_auto_pcb_and_cfg():
def test_auto_pcb_and_cfg_2():
""" Test guessing the PCB and config file.
Two of them are there. """
prj = '3Rs'
ctx = context.TestContext('GuessPCB_cfg_rep', prj, 'pre_and_position', POS_DIR)
@ -204,6 +221,68 @@ def test_auto_pcb_and_cfg_2():
ctx.clean_up()
def test_auto_pcb_and_cfg_3():
""" Test guessing the SCH and config file.
Only one them is there. """
prj = '3Rs'
ctx = context.TestContext('GuessSCH_cfg', prj, 'pre_and_position', POS_DIR)
sch = os.path.basename(ctx.sch_file)
shutil.copy2(ctx.sch_file, ctx.get_out_path(sch))
yaml_file = os.path.basename(ctx.yaml_file)
shutil.copy2(ctx.yaml_file, ctx.get_out_path(yaml_file))
ctx.run(extra=['-s', 'all', '-i'], no_out_dir=True, no_board_file=True, no_yaml_file=True, chdir_out=True)
assert ctx.search_err('Using SCH file: '+sch)
assert ctx.search_err('Using config file: '+yaml_file)
ctx.clean_up()
def test_auto_pcb_and_cfg_4():
""" Test guessing the SCH and config file.
Two SCHs and one PCB.
The SCH with same name as the PCB should be selected. """
prj = '3Rs'
ctx = context.TestContext('GuessSCH_cfg_2', prj, 'pre_and_position', POS_DIR)
sch = os.path.basename(ctx.sch_file)
shutil.copy2(ctx.sch_file, ctx.get_out_path(sch))
shutil.copy2(ctx.sch_file, ctx.get_out_path('b_'+sch))
brd = os.path.basename(ctx.board_file)
shutil.copy2(ctx.board_file, ctx.get_out_path(brd))
yaml_file = os.path.basename(ctx.yaml_file)
shutil.copy2(ctx.yaml_file, ctx.get_out_path(yaml_file))
ctx.run(extra=['-s', 'all', '-i'], no_out_dir=True, no_board_file=True, no_yaml_file=True, chdir_out=True)
assert ctx.search_err('Using '+sch)
assert ctx.search_err('Using config file: '+yaml_file)
ctx.clean_up()
def test_auto_pcb_and_cfg_5():
""" Test guessing the SCH and config file.
Two SCHs. """
prj = '3Rs'
ctx = context.TestContext('GuessSCH_cfg_3', prj, 'pre_and_position', POS_DIR)
sch = os.path.basename(ctx.sch_file)
shutil.copy2(ctx.sch_file, ctx.get_out_path(sch))
shutil.copy2(ctx.sch_file, ctx.get_out_path('b_'+sch))
yaml_file = os.path.basename(ctx.yaml_file)
shutil.copy2(ctx.yaml_file, ctx.get_out_path(yaml_file))
ctx.run(extra=['-s', 'all', '-i'], no_out_dir=True, no_board_file=True, no_yaml_file=True, chdir_out=True)
assert ctx.search_err('Using (b_)?'+sch)
assert ctx.search_err('Using config file: '+yaml_file)
ctx.clean_up()
def test_list():
ctx = context.TestContext('List', '3Rs', 'pre_and_position', POS_DIR)
ctx.run(extra=['--list'], no_verbose=True, no_out_dir=True, no_board_file=True)

View File

@ -45,9 +45,8 @@ class TestContext(object):
return os.path.join(this_dir, '../board_samples')
def _get_board_file(self):
self.board_file = os.path.abspath(os.path.join(self.get_board_dir(),
self.board_name +
(KICAD_PCB_EXT if self.mode == MODE_PCB else KICAD_SCH_EXT)))
self.board_file = os.path.abspath(os.path.join(self.get_board_dir(), self.board_name + KICAD_PCB_EXT))
self.sch_file = os.path.abspath(os.path.join(self.get_board_dir(), self.board_name + KICAD_SCH_EXT))
logging.info('KiCad file: '+self.board_file)
assert os.path.isfile(self.board_file)