diff --git a/CHANGELOG.md b/CHANGELOG.md index 588d347b..c4f95427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --help-output) - Help for the supported preflights (--help-preflights) - Better YAML validation. +- Added the following InteractiveHtmlBom options: + - dark_mode + - hide_pads + - show_fabrication + - hide_silkscreen + - highlight_pin1 + - no_redraw_on_drag + - board_rotation + - checkboxes + - bom_view + - layer_view + - include_tracks + - include_nets + - sort_order + - no_blacklist_virtual + - blacklist_empty_val + - netlist_file + - extra_fields + - normalize_field_case + - variant_field + - variants_whitelist + - variants_blacklist + - dnp_field ### Fixed - The `sketch_plot` option is now implemented. diff --git a/kiplot/config_reader.py b/kiplot/config_reader.py index 243ec94a..8b124710 100644 --- a/kiplot/config_reader.py +++ b/kiplot/config_reader.py @@ -301,13 +301,10 @@ def print_output_options(name, cl): obj = cl('', name, '') print(' * Options:') num_opts = 0 - attrs = BaseOutput.get_attrs_for(obj) - for k, v in attrs.items(): - if k[0] != '_': - help_attr = '_help_'+k - help = attrs.get(help_attr) - print(' - {}: {}.'.format(k, help.rstrip() if help else 'Undocumented')) - num_opts = num_opts+1 + for k, v in BaseOutput.get_attrs_gen(obj): + help = getattr(obj, '_help_'+k) + print(' - {}: {}.'.format(k, help.rstrip() if help else 'Undocumented')) + num_opts = num_opts+1 if num_opts == 0: print(' - No available options') diff --git a/kiplot/out_base.py b/kiplot/out_base.py index 239f6543..efa7f2a7 100644 --- a/kiplot/out_base.py +++ b/kiplot/out_base.py @@ -49,8 +49,19 @@ class BaseOutput(object): @staticmethod def get_attrs_for(obj): + """ Returns all attributes """ return dict(inspect.getmembers(obj, filter)) + @staticmethod + def get_attrs_gen(obj): + """ Returns a (key, val) iterator on public attributes """ + attrs = BaseOutput.get_attrs_for(obj) + return ((k, v) for k, v in attrs.items() if k[0] != '_') + + @staticmethod + def attr2longopt(attr): + return '--'+attr.replace('_', '-') + @staticmethod def register(name, aclass): BaseOutput._registered[name] = aclass diff --git a/kiplot/out_ibom.py b/kiplot/out_ibom.py index c681c63f..2e15e754 100644 --- a/kiplot/out_ibom.py +++ b/kiplot/out_ibom.py @@ -2,6 +2,7 @@ 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 .error import KiPlotConfigurationError from kiplot.macros import macros, document, output_class # noqa: F401 from . import log @@ -19,10 +20,84 @@ class IBoM(BaseOutput): # noqa: F821 self._sch_related = True # Options with document: - self.blacklist = '' - """ regular expression for the components to exclude (using the Config field) """ + self.dark_mode = False + """ Default to dark mode """ + self.hide_pads = False + """ Hide footprint pads by default """ + self.show_fabrication = False + """ Show fabrication layer by default """ + self.hide_silkscreen = False + """ Hide silkscreen by default """ + self.highlight_pin1 = False + """ Highlight pin1 by default """ + self.no_redraw_on_drag = False + """ Do not redraw pcb on drag by default """ + self.board_rotation = 0 + """ Board rotation in degrees (-180 to 180). Will be rounded to multiple of 5 """ + self.checkboxes = 'Sourced,Placed' + """ Comma separated list of checkbox columns """ + self._bom_view = 'left-right' + """ Default BOM view {bom-only,left-right,top-bottom} """ + self._layer_view = 'FB' + """ Default layer view {F,FB,B} """ self.name_format = 'ibom' - """ format of the output name, example: %f_%r_iBoM will contain the revision and _iBoM """ # pragma: no cover + """ Output file name format supports substitutions: + %f : original pcb file name without extension. + %p : pcb/project title from pcb metadata. + %c : company from pcb metadata. + %r : revision from pcb metadata. + %d : pcb date from metadata if available, file modification date otherwise. + %D : bom generation date. + %T : bom generation time. Extension .html will be added automatically """ + self.include_tracks = False + """ Include track/zone information in output. F.Cu and B.Cu layers only """ + self.include_nets = False + """ Include netlist information in output. """ + self.sort_order = 'C,R,L,D,U,Y,X,F,SW,A,~,HS,CNN,J,P,NT,MH' + """ Default sort order for components. Must contain '~' once """ + self.blacklist = '' + """ List of comma separated blacklisted components or prefixes with *. E.g. 'X1,MH*' """ + self.no_blacklist_virtual = False + """ Do not blacklist virtual components """ + self.blacklist_empty_val = False + """ Blacklist components with empty value """ + self.netlist_file = '' + """ Path to netlist or xml file """ + self.extra_fields = '' + """ Comma separated list of extra fields to pull from netlist or xml file """ + self.normalize_field_case = False + """ Normalize extra field name case. E.g. 'MPN' and 'mpn' will be considered the same field """ + self.variant_field = '' + """ Name of the extra field that stores board variant for component """ + self.variants_whitelist = '' + """ List of board variants to include in the BOM """ + self.variants_blacklist = '' + """ List of board variants to exclude from the BOM """ + self.dnp_field = '' + """ Name of the extra field that indicates do not populate status. Components with this field not empty will be + blacklisted """ # pragma: no cover + + @property + def bom_view(self): + return self._bom_view + + @bom_view.setter + def bom_view(self, val): + valid = ['bom-only', 'left-right', 'top-bottom'] + if val not in valid: + raise KiPlotConfigurationError("`bom_view` must be any of "+str(valid)) + self._bom_view = val + + @property + def layer_view(self): + return self._layer_view + + @layer_view.setter + def layer_view(self, val): + valid = ['F', 'FB', 'B'] + if val not in valid: + raise KiPlotConfigurationError("`layer_view` must be any of "+str(valid)) + self._layer_view = val def run(self, output_dir, board): check_script(CMD_IBOM, URL_IBOM) @@ -30,12 +105,14 @@ class IBoM(BaseOutput): # noqa: F821 # Tell ibom we don't want to use the screen os.environ['INTERACTIVE_HTML_BOM_NO_DISPLAY'] = '' cmd = [CMD_IBOM, GS.pcb_file, '--dest-dir', output_dir, '--no-browser', ] - if self.blacklist: - cmd.append('--blacklist') - cmd.append(self.blacklist) - if self.name_format: - cmd.append('--name-format') - cmd.append(self.name_format) + # Convert attributes into options + for k, v in BaseOutput.get_attrs_gen(self): # noqa: F821 + if not v: + continue + cmd.append(BaseOutput.attr2longopt(k)) # noqa: F821 + if not isinstance(v, bool): # must be str/(int, float) + cmd.append(str(v)) + # Run the command logger.debug('Running: '+str(cmd)) try: cmd_output = check_output(cmd, stderr=STDOUT) diff --git a/tests/test_plot/test_ibom.py b/tests/test_plot/test_ibom.py index 089eea68..ff5128bd 100644 --- a/tests/test_plot/test_ibom.py +++ b/tests/test_plot/test_ibom.py @@ -24,6 +24,7 @@ if prev_dir not in sys.path: from kiplot.misc import (BOM_ERROR) BOM_DIR = 'BoM' +IBOM_OUT = 'ibom.html' def test_ibom(): @@ -42,7 +43,7 @@ def test_ibom_no_ops(): prj = 'bom' ctx = context.TestContext('BoM_interactiveNoOps', prj, 'ibom_no_ops', BOM_DIR) ctx.run() - ctx.expect_out_file(os.path.join(BOM_DIR, 'ibom.html')) + ctx.expect_out_file(os.path.join(BOM_DIR, IBOM_OUT)) ctx.clean_up() @@ -50,3 +51,24 @@ def test_ibom_fail(): ctx = context.TestContext('BoM_interactiveFail', 'bom_no_xml', 'ibom', BOM_DIR) ctx.run(BOM_ERROR) ctx.clean_up() + + +def test_ibom_all_ops(): + prj = 'bom' + ctx = context.TestContext('BoM_interactiveAll', prj, 'ibom_all_ops', BOM_DIR) + ctx.run() + out = os.path.join(BOM_DIR, IBOM_OUT) + ctx.expect_out_file(out) + # These options are transferred as defaults: + ctx.search_in_file(out, [r'"dark_mode": true', + r'"show_pads": false', + r'"show_fabrication": true', + r'"show_silkscreen": false', + r'"highlight_pin1": true', + r'"redraw_on_drag": false', + r'"board_rotation": 18.0', # 90/5 + r'"checkboxes": "Sourced,Placed,Bogus"', + r'"bom_view": "top-bottom"', + r'"layer_view": "B"', + r'"extra_fields": \["EF"\]']) + ctx.clean_up() diff --git a/tests/yaml_samples/ibom.kiplot.yaml b/tests/yaml_samples/ibom.kiplot.yaml index 44cde7ec..7663b6ce 100644 --- a/tests/yaml_samples/ibom.kiplot.yaml +++ b/tests/yaml_samples/ibom.kiplot.yaml @@ -8,6 +8,27 @@ outputs: type: ibom dir: BoM options: - blacklist: 'DNF*' + hide_pads: false + show_fabrication: false + hide_silkscreen: false + highlight_pin1: false + no_redraw_on_drag: false + board_rotation: 0 + checkboxes: 'Sourced,Placed' + bom_view: 'left-right' + layer_view: 'FB' name_format: '%f_iBoM' + include_tracks: false + include_nets: false + sort_order: 'C,R,L,D,U,Y,X,F,SW,A,~,HS,CNN,J,P,NT,MH' + blacklist: 'DNF*' + no_blacklist_virtual: false + blacklist_empty_val: false + netlist_file: '' + extra_fields: '' + normalize_field_case: false + variant_field: '' + variants_whitelist: '' + variants_blacklist: '' + dnp_field: '' diff --git a/tests/yaml_samples/ibom_all_ops.kiplot.yaml b/tests/yaml_samples/ibom_all_ops.kiplot.yaml new file mode 100644 index 00000000..49e29da2 --- /dev/null +++ b/tests/yaml_samples/ibom_all_ops.kiplot.yaml @@ -0,0 +1,35 @@ +# Example KiPlot config file +kiplot: + version: 1 + +outputs: + - name: 'interactive_bom' + comment: "Interactive Bill of Materials (HTML)" + type: ibom + dir: BoM + options: + dark_mode: true + hide_pads: true + show_fabrication: true + hide_silkscreen: true + highlight_pin1: true + no_redraw_on_drag: true + board_rotation: 90 + checkboxes: 'Sourced,Placed,Bogus' + bom_view: 'top-bottom' + layer_view: 'B' + name_format: 'ibom' + include_tracks: true + include_nets: true + sort_order: 'C,R,L,D,U,Y,X,F,SW,A,~,HS,CNN,J,P,NT,MH' + blacklist: 'DNF*' + no_blacklist_virtual: true + blacklist_empty_val: true + netlist_file: 'tests/board_samples/bom.xml' + extra_fields: 'EF' + normalize_field_case: true + variant_field: 'DL' + variants_whitelist: 'bla,ble,bli' + variants_blacklist: 'blo,blu' + dnp_field: 'DNP' +