Added KiBoM and InteractiveHtmlBoM support

This commit is contained in:
Salvador E. Tropea 2020-03-19 16:26:45 -03:00
parent 85acaadf26
commit 438142dabd
3 changed files with 120 additions and 7 deletions

View File

@ -108,7 +108,7 @@ class CfgYamlReader(CfgReader):
if mapping['required'](cfg_options): if mapping['required'](cfg_options):
cfg_val = self._get_required(cfg_options, key) 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 # not required but given anyway
cfg_val = cfg_options[key] cfg_val = cfg_options[key]
else: else:
@ -337,7 +337,7 @@ class CfgYamlReader(CfgReader):
}, },
{ {
'key': 'format', 'key': 'format',
'types': ['position'], 'types': ['position','kibom'],
'to': 'format', 'to': 'format',
'required': lambda opts: True, 'required': lambda opts: True,
}, },
@ -358,7 +358,19 @@ class CfgYamlReader(CfgReader):
'types': ['position'], 'types': ['position'],
'to': 'only_smd', 'to': 'only_smd',
'required': lambda opts: True, '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) po = PC.OutputOptions(otype)
@ -444,13 +456,16 @@ class CfgYamlReader(CfgReader):
raise YamlError("Output needs a type") raise YamlError("Output needs a type")
if otype not in ['gerber', 'ps', 'hpgl', 'dxf', 'pdf', 'svg', 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)) raise YamlError("Unknown output type: {}".format(otype))
try: try:
options = o_obj['options'] options = o_obj['options']
except KeyError: 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)) logger.debug("Parsing output options for {} ({})".format(name, otype))

View File

@ -7,7 +7,7 @@ import os
from sys import exit from sys import exit
import operator import operator
from shutil import which from shutil import which
from subprocess import call, run, PIPE from subprocess import call, run, PIPE, check_output, CalledProcessError, STDOUT
import logging import logging
from distutils.version import StrictVersion from distutils.version import StrictVersion
import re import re
@ -102,6 +102,8 @@ class Plotter(object):
self._do_drill_plot(board, pc, op) self._do_drill_plot(board, pc, op)
elif self._output_is_position(op): elif self._output_is_position(op):
self._do_position_plot(board, pc, op) self._do_position_plot(board, pc, op)
elif self._output_is_bom(op):
self._do_bom(board, pc, op, brd_file)
else: else:
raise PlotError("Don't know how to plot type {}" raise PlotError("Don't know how to plot type {}"
.format(op.options.type)) .format(op.options.type))
@ -218,6 +220,12 @@ class Plotter(object):
def _output_is_position(self, output): def _output_is_position(self, output):
return output.options.type == PCfg.OutputOptions.POSITION 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): def _get_layer_plot_format(self, output):
""" """
Gets the Pcbnew plot format for a given KiPlot output type Gets the Pcbnew plot format for a given KiPlot output type
@ -528,6 +536,59 @@ class Plotter(object):
else: else:
raise PlotError("Format is invalid: {}".format(to.format)) 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): def _configure_gerber_opts(self, po, output):
# true if gerber # true if gerber
@ -582,6 +643,14 @@ class Plotter(object):
assert(output.options.type == PCfg.OutputOptions.POSITION) 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): def _configure_output_dir(self, plot_ctrl, output):
po = plot_ctrl.GetPlotOptions() po = plot_ctrl.GetPlotOptions()
@ -637,6 +706,10 @@ class Plotter(object):
self._configure_hpgl_opts(po, output) self._configure_hpgl_opts(po, output)
elif output.options.type == PCfg.OutputOptions.POSITION: elif output.options.type == PCfg.OutputOptions.POSITION:
self._configure_position_opts(po, output) 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) po.SetDrillMarksType(opts.drill_marks)

View File

@ -377,6 +377,25 @@ class PositionOptions(TypeOptions):
return errs 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): class OutputOptions(object):
GERBER = 'gerber' GERBER = 'gerber'
@ -389,6 +408,8 @@ class OutputOptions(object):
EXCELLON = 'excellon' EXCELLON = 'excellon'
GERB_DRILL = 'gerb_drill' GERB_DRILL = 'gerb_drill'
POSITION = 'position' POSITION = 'position'
KIBOM = 'kibom'
IBOM = 'ibom'
def __init__(self, otype): def __init__(self, otype):
self.type = otype self.type = otype
@ -411,12 +432,16 @@ class OutputOptions(object):
self.type_options = GerberDrillOptions() self.type_options = GerberDrillOptions()
elif otype == self.POSITION: elif otype == self.POSITION:
self.type_options = PositionOptions() self.type_options = PositionOptions()
elif otype == self.KIBOM:
self.type_options = KiBoMOptions()
elif otype == self.IBOM:
self.type_options = IBoMOptions()
else: else:
self.type_options = None self.type_options = None
def validate(self): 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 ["No type specific options found"]
return self.type_options.validate() return self.type_options.validate()