From 438142dabd9c09bb63522783f14680853a509304 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Thu, 19 Mar 2020 16:26:45 -0300 Subject: [PATCH] Added KiBoM and InteractiveHtmlBoM support --- kiplot/config_reader.py | 25 +++++++++++--- kiplot/kiplot.py | 75 ++++++++++++++++++++++++++++++++++++++++- kiplot/plot_config.py | 27 ++++++++++++++- 3 files changed, 120 insertions(+), 7 deletions(-) diff --git a/kiplot/config_reader.py b/kiplot/config_reader.py index 5e21e8b4..7a91fc00 100644 --- a/kiplot/config_reader.py +++ b/kiplot/config_reader.py @@ -108,7 +108,7 @@ class CfgYamlReader(CfgReader): if mapping['required'](cfg_options): cfg_val = self._get_required(cfg_options, key) - elif key in cfg_options: + elif not(cfg_options is None) and key in cfg_options: # not required but given anyway cfg_val = cfg_options[key] else: @@ -337,7 +337,7 @@ class CfgYamlReader(CfgReader): }, { 'key': 'format', - 'types': ['position'], + 'types': ['position','kibom'], 'to': 'format', 'required': lambda opts: True, }, @@ -358,7 +358,19 @@ class CfgYamlReader(CfgReader): 'types': ['position'], 'to': 'only_smd', 'required': lambda opts: True, - } + }, + { + 'key': 'blacklist', + 'types': ['ibom'], + 'to': 'blacklist', + 'required': lambda opts: False, + }, + { + 'key': 'name_format', + 'types': ['ibom'], + 'to': 'name_format', + 'required': lambda opts: False, + }, ] po = PC.OutputOptions(otype) @@ -444,13 +456,16 @@ class CfgYamlReader(CfgReader): raise YamlError("Output needs a type") if otype not in ['gerber', 'ps', 'hpgl', 'dxf', 'pdf', 'svg', - 'gerb_drill', 'excellon', 'position']: + 'gerb_drill', 'excellon', 'position', + 'kibom', 'ibom']: raise YamlError("Unknown output type: {}".format(otype)) try: options = o_obj['options'] except KeyError: - raise YamlError("Output need to have options specified") + if otype != 'ibom': + raise YamlError("Output need to have options specified") + options = None logger.debug("Parsing output options for {} ({})".format(name, otype)) diff --git a/kiplot/kiplot.py b/kiplot/kiplot.py index 5c26fd1c..5a7eaac7 100644 --- a/kiplot/kiplot.py +++ b/kiplot/kiplot.py @@ -7,7 +7,7 @@ import os from sys import exit import operator from shutil import which -from subprocess import call, run, PIPE +from subprocess import call, run, PIPE, check_output, CalledProcessError, STDOUT import logging from distutils.version import StrictVersion import re @@ -102,6 +102,8 @@ class Plotter(object): self._do_drill_plot(board, pc, op) elif self._output_is_position(op): self._do_position_plot(board, pc, op) + elif self._output_is_bom(op): + self._do_bom(board, pc, op, brd_file) else: raise PlotError("Don't know how to plot type {}" .format(op.options.type)) @@ -218,6 +220,12 @@ class Plotter(object): def _output_is_position(self, output): return output.options.type == PCfg.OutputOptions.POSITION + def _output_is_bom(self, output): + return output.options.type in [ + PCfg.OutputOptions.KIBOM, + PCfg.OutputOptions.IBOM, + ] + def _get_layer_plot_format(self, output): """ Gets the Pcbnew plot format for a given KiPlot output type @@ -528,6 +536,59 @@ class Plotter(object): else: raise PlotError("Format is invalid: {}".format(to.format)) + def _do_bom(self, board, plot_ctrl, output, brd_file): + if output.options.type == 'kibom': + self._do_kibom(board, plot_ctrl, output, brd_file) + else: + self._do_ibom(board, plot_ctrl, output, brd_file) + + def _do_kibom(self, board, plot_ctrl, output, brd_file): + if which('KiBOM_CLI.py') is None: + logger.error('No `KiBOM_CLI.py` command found.\n' + 'Please install it, visit: https://github.com/INTI-CMNB/KiBoM') + exit(misc.MISSING_TOOL) + to = output.options.type_options + format = to.format.lower() + outdir = plot_ctrl.GetPlotOptions().GetOutputDirectory() + if not os.path.exists(outdir): + os.makedirs(outdir) + prj = os.path.splitext(os.path.relpath(brd_file))[0] + logger.debug('Doing BoM, format '+format+' prj: '+prj) + cmd = [ 'KiBOM_CLI.py', prj+'.xml', os.path.join(outdir, prj)+'.'+format ] + logger.debug('Running: '+str(cmd)) + try: + check_output(cmd, stderr=STDOUT) + except CalledProcessError as e: + logger.error('Failed to create BoM, error %d', e.returncode) + exit(misc.BOM_ERROR) + + def _do_ibom(self, board, plot_ctrl, output, brd_file): + if which('generate_interactive_bom.py') is None: + logger.error('No `generate_interactive_bom.py` command found.\n' + 'Please install it, visit: https://github.com/INTI-CMNB/InteractiveHtmlBom') + exit(misc.MISSING_TOOL) + outdir = plot_ctrl.GetPlotOptions().GetOutputDirectory() + if not os.path.exists(outdir): + os.makedirs(outdir) + prj = os.path.splitext(os.path.relpath(brd_file))[0] + logger.debug('Doing Interactive BoM, prj: '+prj) + cmd = [ 'generate_interactive_bom.py', brd_file, + '--dest-dir', outdir, + '--no-browser', ] + to = output.options.type_options + if to.blacklist: + cmd.append('--blacklist') + cmd.append(to.blacklist) + if to.name_format: + cmd.append('--name-format') + cmd.append(to.name_format) + logger.debug('Running: '+str(cmd)) + try: + check_output(cmd, stderr=STDOUT) + except CalledProcessError as e: + logger.error('Failed to create BoM, error %d', e.returncode) + exit(misc.BOM_ERROR) + def _configure_gerber_opts(self, po, output): # true if gerber @@ -582,6 +643,14 @@ class Plotter(object): assert(output.options.type == PCfg.OutputOptions.POSITION) + def _configure_kibom_opts(self, po, output): + + assert(output.options.type == PCfg.OutputOptions.KIBOM) + + def _configure_ibom_opts(self, po, output): + + assert(output.options.type == PCfg.OutputOptions.IBOM) + def _configure_output_dir(self, plot_ctrl, output): po = plot_ctrl.GetPlotOptions() @@ -637,6 +706,10 @@ class Plotter(object): self._configure_hpgl_opts(po, output) elif output.options.type == PCfg.OutputOptions.POSITION: self._configure_position_opts(po, output) + elif output.options.type == PCfg.OutputOptions.KIBOM: + self._configure_kibom_opts(po, output) + elif output.options.type == PCfg.OutputOptions.IBOM: + self._configure_ibom_opts(po, output) po.SetDrillMarksType(opts.drill_marks) diff --git a/kiplot/plot_config.py b/kiplot/plot_config.py index 0f3caa34..5b5ca989 100644 --- a/kiplot/plot_config.py +++ b/kiplot/plot_config.py @@ -377,6 +377,25 @@ class PositionOptions(TypeOptions): return errs +class KiBoMOptions(TypeOptions): + + def __init__(self): + self.format = None + + def validate(self): + errs = [] + if self.format not in ["HTML", "CSV"]: + errs.append("Format must be either HTML or CSV") + return errs + + +class IBoMOptions(TypeOptions): + + def __init__(self): + self.blacklist = None + self.name_format = None + + class OutputOptions(object): GERBER = 'gerber' @@ -389,6 +408,8 @@ class OutputOptions(object): EXCELLON = 'excellon' GERB_DRILL = 'gerb_drill' POSITION = 'position' + KIBOM = 'kibom' + IBOM = 'ibom' def __init__(self, otype): self.type = otype @@ -411,12 +432,16 @@ class OutputOptions(object): self.type_options = GerberDrillOptions() elif otype == self.POSITION: self.type_options = PositionOptions() + elif otype == self.KIBOM: + self.type_options = KiBoMOptions() + elif otype == self.IBOM: + self.type_options = IBoMOptions() else: self.type_options = None def validate(self): - if self.type_options is None: + if self.type_options is None and self.type != self.IBOM: return ["No type specific options found"] return self.type_options.validate()