From 3b6c9e7d8b300943fd244e3c57972f9c68487ca2 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Sat, 30 Jul 2022 10:39:48 -0300 Subject: [PATCH] [iBoM] Added support for variants that change component fields Closes #242 --- CHANGELOG.md | 1 + kibot/out_base.py | 33 +++++++++++++++++++++++++++++++++ kibot/out_ibom.py | 47 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 676ba92f..1aa032ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Preflights can be imported (#181) - `--dont-stop` command line option, to try to continue even on errors (#209) - PDF/SVG PCB Print: option to print all pages/single page (#236) +- iBoM: Support for variants that change component fields (#242) ### Fixed - OAR computation (Report) (#225) diff --git a/kibot/out_base.py b/kibot/out_base.py index 7caf05ff..8efc7235 100644 --- a/kibot/out_base.py +++ b/kibot/out_base.py @@ -423,6 +423,39 @@ class VariantOptions(BaseOptions): GS.board.GetTitleBlock().SetTitle(self.old_title) self.old_title = None + def sch_fields_to_pcb(self, comps, board): + """ Change the module/footprint data according to the filtered fields. + iBoM can parse it. """ + comps_hash = self.get_refs_hash() + self.sch_fields_to_pcb_bkp = {} + for m in GS.get_modules_board(board): + ref = m.GetReference() + comp = comps_hash.get(ref, None) + if comp is not None: + properties = {f.name: f.value for f in comp.fields} + old_value = m.GetValue() + m.SetValue(properties['Value']) + if GS.ki6(): + old_properties = m.GetProperties() + old_fp = m.GetFPIDAsString() + m.SetProperties(properties) + m.SetFPIDAsString(properties['Footprint']) + self.sch_fields_to_pcb_bkp[ref] = (old_value, old_properties, old_fp) + else: + self.sch_fields_to_pcb_bkp[ref] = old_value + + def restore_sch_fields_to_pcb(self, board): + for m in GS.get_modules_board(board): + ref = m.GetReference() + data = self.sch_fields_to_pcb_bkp.get(ref, None) + if data is not None: + if GS.ki6(): + m.SetValue(data[0]) + m.SetProperties(data[1]) + m.SetFPIDAsString(data[2]) + else: + m.SetValue(data) + def save_tmp_board(self, dir=None): """ Save the PCB to a temporal file. Advantage: all relative paths inside the file remains valid diff --git a/kibot/out_ibom.py b/kibot/out_ibom.py index 1203bf86..fd17517c 100644 --- a/kibot/out_ibom.py +++ b/kibot/out_ibom.py @@ -20,7 +20,8 @@ Dependencies: """ import os from subprocess import (check_output, STDOUT, CalledProcessError) -from shutil import which +from shutil import which, rmtree +from tempfile import mkdtemp from .misc import BOM_ERROR, W_EXTNAME, W_NONETLIST from .gs import GS from .out_base import VariantOptions @@ -164,19 +165,35 @@ class IBoMOptions(VariantOptions): cur = os.path.join(output_dir, 'ibom.html') else: output_dir = name - cmd = [tool, GS.pcb_file, '--dest-dir', output_dir, '--no-browser', ] + # Solve the variants stuff + ori_extra_data_file = self.extra_data_file + net_dir = None + pcb_name = GS.pcb_file + if self._comps: + # Write a custom netlist to a temporal dir + net_dir = mkdtemp(prefix='tmp-kibot-ibom-') + netlist = os.path.join(net_dir, GS.pcb_basename+'.xml') + self.extra_data_file = netlist + logger.debug('Creating variant netlist `{}`'.format(netlist)) + with open(netlist, 'wb') as f: + GS.sch.save_netlist(f, self._comps) + # Write a board with the filtered values applied + self.sch_fields_to_pcb(self._comps, GS.board) + pcb_name = self.save_tmp_board(dir=net_dir) + else: + # Check if the user wants extra_fields but there is no data about them (#68) + if self.need_extra_fields() and not os.path.isfile(self.extra_data_file): + logger.warning(W_NONETLIST+'iBoM needs information about user defined fields and no netlist provided') + if self._extra_data_file_guess: + logger.warning(W_NONETLIST+'Create a BoM in XML format or use the `update_xml` preflight') + # If the name of the netlist is just a guess remove it from the options + self.extra_data_file = '' + else: + logger.warning(W_NONETLIST+"The file name used in `extra_data_file` doesn't exist") + cmd = [tool, pcb_name, '--dest-dir', output_dir, '--no-browser', ] if not which(tool) and not os.access(tool, os.X_OK): # Plugin could be installed without execute flags cmd.insert(0, 'python3') - # Check if the user wants extra_fields but there is no data about them (#68) - if self.need_extra_fields() and not os.path.isfile(self.extra_data_file): - logger.warning(W_NONETLIST+'iBoM needs information about user defined fields and no netlist provided') - if self._extra_data_file_guess: - logger.warning(W_NONETLIST+'Create a BoM in XML format or use the `update_xml` preflight') - # If the name of the netlist is just a guess remove it from the options - self.extra_data_file = '' - else: - logger.warning(W_NONETLIST+"The file name used in `extra_data_file` doesn't exist") # Apply variants/filters to_remove = ','.join(self.get_not_fitted_refs()) if self.blacklist and to_remove: @@ -204,6 +221,14 @@ class IBoMOptions(VariantOptions): if "'PCB_SHAPE' object has no attribute 'GetAngle'" in e.output.decode(): logger.error("Update Interactive HTML BoM your version doesn't support KiCad 6 files") exit(BOM_ERROR) + finally: + if net_dir: + logger.debug('Removing temporal variant dir `{}`'.format(net_dir)) + rmtree(net_dir) + # Restore the PCB properties and values + self.restore_sch_fields_to_pcb(GS.board) + # Restore the real name selected + self.extra_data_file = ori_extra_data_file logger.debug('Output from command:\n'+cmd_output_dec+'\n') if output: logger.debug('Renaming output file: {} -> {}'.format(cur, output))