Skip to content

Commit fecad4b

Browse files
committed
Make standard fields optional, grouping configurable
Rename extra fields GUI tab to fields Add ability to include fields without affecting grouping Add flags --show-fields, --group-fields which default to "Footprint,Value" --extra-fields is now a shortcut to show and group additional fields (along with standard Footprint, Value) Fixes #167
1 parent 1bf9fa4 commit fecad4b

File tree

11 files changed

+476
-363
lines changed

11 files changed

+476
-363
lines changed

DATAFORMAT.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ pcbdata = {
101101
"B": [bomrow1, bomrow2, ...],
102102
// numeric IDs of DNP components that are not in BOM
103103
"skipped": [id1, id2, ...]
104+
// Fields map is keyed on component ID with values being field data.
105+
// It's order corresponds to order of fields data in config struct.
106+
"fields" {
107+
id1: [field1, field2, ...],
108+
id2: [field1, field2, ...],
109+
...
110+
}
104111
},
105112
// Contains parsed stroke data from newstroke font for
106113
// characters used on the pcb.
@@ -333,17 +340,7 @@ Footprints are a collection of pads, drawings and some metadata.
333340

334341
# bom row struct
335342

336-
Bom row is a 5-tuple (array in js)
337-
338-
```js
339-
[
340-
group_component_count,
341-
value,
342-
footprint_name,
343-
reference_set,
344-
extra_data
345-
]
346-
```
343+
Bom row is a list of reference sets
347344

348345
Reference set is array of tuples of (ref, id) where id is just
349346
a unique numeric identifier for each footprint that helps avoid
@@ -356,13 +353,6 @@ collisions when references are duplicated.
356353
]
357354
```
358355

359-
Extra data is array of extra field data. It's order corresponds
360-
to order of extra field data in config struct.
361-
362-
```js
363-
[field1_value, field2_value, ...]
364-
```
365-
366356
# config struct
367357

368358
```js

InteractiveHtmlBom/core/config.py

Lines changed: 82 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ class Config:
3838
html_config_fields = [
3939
'dark_mode', 'show_pads', 'show_fabrication', 'show_silkscreen',
4040
'highlight_pin1', 'redraw_on_drag', 'board_rotation', 'checkboxes',
41-
'bom_view', 'layer_view', 'extra_fields'
41+
'bom_view', 'layer_view'
4242
]
43+
default_show_group_fields = ["Value", "Footprint"]
4344

4445
# Defaults
4546

@@ -70,7 +71,8 @@ class Config:
7071
# Extra fields section
7172
extra_data_file = None
7273
netlist_initial_directory = '' # This is relative to pcb file directory
73-
extra_fields = []
74+
show_fields = default_show_group_fields
75+
group_fields = default_show_group_fields
7476
normalize_field_case = False
7577
board_variant_field = ''
7678
board_variant_whitelist = []
@@ -79,7 +81,7 @@ class Config:
7981

8082
@staticmethod
8183
def _split(s):
82-
"""Splits string by ',' and drops empty strings from resulting array."""
84+
"""Splits string by ',' and drops empty strings from resulting array"""
8385
return [a.replace('\\,', ',') for a in re.split(r'(?<!\\),', s) if a]
8486

8587
@staticmethod
@@ -99,9 +101,9 @@ def load_from_ini(self):
99101
self.dark_mode = f.ReadBool('dark_mode', self.dark_mode)
100102
self.show_pads = f.ReadBool('show_pads', self.show_pads)
101103
self.show_fabrication = f.ReadBool(
102-
'show_fabrication', self.show_fabrication)
104+
'show_fabrication', self.show_fabrication)
103105
self.show_silkscreen = f.ReadBool(
104-
'show_silkscreen', self.show_silkscreen)
106+
'show_silkscreen', self.show_silkscreen)
105107
self.highlight_pin1 = f.ReadBool('highlight_pin1', self.highlight_pin1)
106108
self.redraw_on_drag = f.ReadBool('redraw_on_drag', self.redraw_on_drag)
107109
self.board_rotation = f.ReadInt('board_rotation', self.board_rotation)
@@ -115,32 +117,33 @@ def load_from_ini(self):
115117
self.bom_dest_dir = f.Read('bom_dest_dir', self.bom_dest_dir)
116118
self.bom_name_format = f.Read('bom_name_format', self.bom_name_format)
117119
self.component_sort_order = self._split(f.Read(
118-
'component_sort_order',
119-
','.join(self.component_sort_order)))
120+
'component_sort_order',
121+
','.join(self.component_sort_order)))
120122
self.component_blacklist = self._split(f.Read(
121-
'component_blacklist',
122-
','.join(self.component_blacklist)))
123+
'component_blacklist',
124+
','.join(self.component_blacklist)))
123125
self.blacklist_virtual = f.ReadBool(
124-
'blacklist_virtual', self.blacklist_virtual)
126+
'blacklist_virtual', self.blacklist_virtual)
125127
self.blacklist_empty_val = f.ReadBool(
126-
'blacklist_empty_val', self.blacklist_empty_val)
128+
'blacklist_empty_val', self.blacklist_empty_val)
127129
self.include_tracks = f.ReadBool('include_tracks', self.include_tracks)
128130
self.include_nets = f.ReadBool('include_nets', self.include_nets)
129131

130-
f.SetPath('/extra_fields')
131-
self.extra_fields = self._split(f.Read(
132-
'extra_fields',
133-
self._join(self.extra_fields)))
132+
f.SetPath('/fields')
133+
self.show_fields = self._split(f.Read(
134+
'show_fields', self._join(self.show_fields)))
135+
self.group_fields = self._split(f.Read(
136+
'group_fields', self._join(self.group_fields)))
134137
self.normalize_field_case = f.ReadBool(
135-
'normalize_field_case', self.normalize_field_case)
138+
'normalize_field_case', self.normalize_field_case)
136139
self.board_variant_field = f.Read(
137-
'board_variant_field', self.board_variant_field)
140+
'board_variant_field', self.board_variant_field)
138141
self.board_variant_whitelist = self._split(f.Read(
139-
'board_variant_whitelist',
140-
','.join(self.board_variant_whitelist)))
142+
'board_variant_whitelist',
143+
self._join(self.board_variant_whitelist)))
141144
self.board_variant_blacklist = self._split(f.Read(
142-
'board_variant_blacklist',
143-
','.join(self.board_variant_blacklist)))
145+
'board_variant_blacklist',
146+
self._join(self.board_variant_blacklist)))
144147
self.dnp_field = f.Read('dnp_field', self.dnp_field)
145148

146149
def save(self):
@@ -164,7 +167,7 @@ def save(self):
164167
bom_dest_dir = self.bom_dest_dir
165168
if bom_dest_dir.startswith(self.netlist_initial_directory):
166169
bom_dest_dir = os.path.relpath(
167-
bom_dest_dir, self.netlist_initial_directory)
170+
bom_dest_dir, self.netlist_initial_directory)
168171
f.Write('bom_dest_dir', bom_dest_dir)
169172
f.Write('bom_name_format', self.bom_name_format)
170173
f.Write('component_sort_order',
@@ -176,14 +179,15 @@ def save(self):
176179
f.WriteBool('include_tracks', self.include_tracks)
177180
f.WriteBool('include_nets', self.include_nets)
178181

179-
f.SetPath('/extra_fields')
180-
f.Write('extra_fields', self._join(self.extra_fields))
182+
f.SetPath('/fields')
183+
f.Write('show_fields', self._join(self.show_fields))
184+
f.Write('group_fields', self._join(self.group_fields))
181185
f.WriteBool('normalize_field_case', self.normalize_field_case)
182186
f.Write('board_variant_field', self.board_variant_field)
183187
f.Write('board_variant_whitelist',
184-
','.join(self.board_variant_whitelist))
188+
self._join(self.board_variant_whitelist))
185189
f.Write('board_variant_blacklist',
186-
','.join(self.board_variant_blacklist))
190+
self._join(self.board_variant_blacklist))
187191
f.Write('dnp_field', self.dnp_field)
188192
f.Flush()
189193

@@ -216,19 +220,20 @@ def set_from_dialog(self, dlg):
216220
self.include_tracks = dlg.general.includeTracksCheckbox.IsChecked()
217221
self.include_nets = dlg.general.includeNetsCheckbox.IsChecked()
218222

219-
# Extra fields
220-
self.extra_data_file = dlg.extra.extraDataFilePicker.Path
221-
self.extra_fields = list(dlg.extra.extraFieldsList.GetCheckedStrings())
222-
self.normalize_field_case = dlg.extra.normalizeCaseCheckbox.Value
223-
self.board_variant_field = dlg.extra.boardVariantFieldBox.Value
224-
if self.board_variant_field == dlg.extra.NONE_STRING:
223+
# Fields
224+
self.extra_data_file = dlg.fields.extraDataFilePicker.Path
225+
self.show_fields = dlg.fields.GetShowFields()
226+
self.group_fields = dlg.fields.GetGroupFields()
227+
self.normalize_field_case = dlg.fields.normalizeCaseCheckbox.Value
228+
self.board_variant_field = dlg.fields.boardVariantFieldBox.Value
229+
if self.board_variant_field == dlg.fields.NONE_STRING:
225230
self.board_variant_field = ''
226231
self.board_variant_whitelist = list(
227-
dlg.extra.boardVariantWhitelist.GetCheckedStrings())
232+
dlg.fields.boardVariantWhitelist.GetCheckedStrings())
228233
self.board_variant_blacklist = list(
229-
dlg.extra.boardVariantBlacklist.GetCheckedStrings())
230-
self.dnp_field = dlg.extra.dnpFieldBox.Value
231-
if self.dnp_field == dlg.extra.NONE_STRING:
234+
dlg.fields.boardVariantBlacklist.GetCheckedStrings())
235+
self.dnp_field = dlg.fields.dnpFieldBox.Value
236+
if self.dnp_field == dlg.fields.NONE_STRING:
232237
self.dnp_field = ''
233238

234239
def transfer_to_dialog(self, dlg):
@@ -243,9 +248,9 @@ def transfer_to_dialog(self, dlg):
243248
dlg.html.boardRotationSlider.Value = self.board_rotation
244249
dlg.html.bomCheckboxesCtrl.Value = self.checkboxes
245250
dlg.html.bomDefaultView.Selection = self.bom_view_choices.index(
246-
self.bom_view)
251+
self.bom_view)
247252
dlg.html.layerDefaultView.Selection = self.layer_view_choices.index(
248-
self.layer_view)
253+
self.layer_view)
249254
dlg.html.compressionCheckbox.Value = self.compression
250255
dlg.html.openBrowserCheckbox.Value = self.open_browser
251256

@@ -255,7 +260,7 @@ def transfer_to_dialog(self, dlg):
255260
dlg.general.bomDirPicker.Path = self.bom_dest_dir
256261
else:
257262
dlg.general.bomDirPicker.Path = os.path.join(
258-
self.netlist_initial_directory, self.bom_dest_dir)
263+
self.netlist_initial_directory, self.bom_dest_dir)
259264
dlg.general.fileNameFormatTextControl.Value = self.bom_name_format
260265
dlg.general.componentSortOrderBox.SetItems(self.component_sort_order)
261266
dlg.general.blacklistBox.SetItems(self.component_blacklist)
@@ -264,28 +269,28 @@ def transfer_to_dialog(self, dlg):
264269
dlg.general.includeTracksCheckbox.Value = self.include_tracks
265270
dlg.general.includeNetsCheckbox.Value = self.include_nets
266271

267-
# Extra fields
268-
dlg.extra.extraDataFilePicker.SetInitialDirectory(
269-
self.netlist_initial_directory)
272+
# Fields
273+
dlg.fields.extraDataFilePicker.SetInitialDirectory(
274+
self.netlist_initial_directory)
270275

271276
def safe_set_checked_strings(clb, strings):
272-
safe_strings = list(clb.GetStrings())
273-
if safe_strings:
274-
present_strings = [s for s in strings if s in safe_strings]
275-
not_present_strings = [s for s in safe_strings if s not in strings]
277+
current = list(clb.GetStrings())
278+
if current:
279+
present_strings = [s for s in strings if s in current]
280+
not_present_strings = [s for s in current if s not in strings]
276281
clb.Clear()
277282
clb.InsertItems(present_strings + not_present_strings, 0)
278283
clb.SetCheckedStrings(present_strings)
279284

280-
safe_set_checked_strings(dlg.extra.extraFieldsList, self.extra_fields)
281-
dlg.extra.normalizeCaseCheckbox.Value = self.normalize_field_case
282-
dlg.extra.boardVariantFieldBox.Value = self.board_variant_field
283-
dlg.extra.OnBoardVariantFieldChange(None)
284-
safe_set_checked_strings(dlg.extra.boardVariantWhitelist,
285+
dlg.fields.SetCheckedFields(self.show_fields, self.group_fields)
286+
dlg.fields.normalizeCaseCheckbox.Value = self.normalize_field_case
287+
dlg.fields.boardVariantFieldBox.Value = self.board_variant_field
288+
dlg.fields.OnBoardVariantFieldChange(None)
289+
safe_set_checked_strings(dlg.fields.boardVariantWhitelist,
285290
self.board_variant_whitelist)
286-
safe_set_checked_strings(dlg.extra.boardVariantBlacklist,
291+
safe_set_checked_strings(dlg.fields.boardVariantBlacklist,
287292
self.board_variant_blacklist)
288-
dlg.extra.dnpFieldBox.Value = self.dnp_field
293+
dlg.fields.dnpFieldBox.Value = self.dnp_field
289294

290295
dlg.finish_init()
291296

@@ -350,24 +355,31 @@ def add_options(self, parser, file_name_format_hint):
350355
parser.add_argument('--blacklist',
351356
default=','.join(self.component_blacklist),
352357
help='List of comma separated blacklisted '
353-
'components or prefixes with *. E.g. "X1,MH*"')
358+
'components or prefixes with *. '
359+
'E.g. "X1,MH*"')
354360
parser.add_argument('--no-blacklist-virtual', action='store_true',
355361
help='Do not blacklist virtual components.')
356362
parser.add_argument('--blacklist-empty-val', action='store_true',
357363
help='Blacklist components with empty value.')
358364

359-
# Extra fields section
365+
# Fields section
360366
parser.add_argument('--netlist-file',
361367
help='(Deprecated) Path to netlist or xml file.')
362368
parser.add_argument('--extra-data-file',
363369
help='Path to netlist or xml file.')
364370
parser.add_argument('--extra-fields',
365-
default=self._join(self.extra_fields),
366-
help='Comma separated list of extra fields to '
367-
'pull from netlist or xml file.')
371+
help='Passing --extra-fields "X,Y" is a shortcut '
372+
'for --show-fields and --group-fields '
373+
'with values "Value,Footprint,X,Y"')
374+
parser.add_argument('--show-fields',
375+
default=self._join(self.show_fields),
376+
help='List of fields to show in the BOM.')
377+
parser.add_argument('--group-fields',
378+
default=self._join(self.group_fields),
379+
help='Fields that components will be grouped by.')
368380
parser.add_argument('--normalize-field-case',
369381
help='Normalize extra field name case. E.g. "MPN" '
370-
'and "mpn" will be considered the same field.',
382+
', "mpn" will be considered the same field.',
371383
action='store_true')
372384
parser.add_argument('--variant-field',
373385
help='Name of the extra field that stores board '
@@ -380,8 +392,8 @@ def add_options(self, parser, file_name_format_hint):
380392
'exclude from the BOM.')
381393
parser.add_argument('--dnp-field', default=self.dnp_field,
382394
help='Name of the extra field that indicates '
383-
'do not populate status. Components with this '
384-
'field not empty will be blacklisted.')
395+
'do not populate status. Components with '
396+
'this field not empty will be excluded.')
385397

386398
def set_from_args(self, args):
387399
# type: (argparse.Namespace) -> None
@@ -411,9 +423,15 @@ def set_from_args(self, args):
411423
self.include_tracks = args.include_tracks
412424
self.include_nets = args.include_nets
413425

414-
# Extra
426+
# Fields
415427
self.extra_data_file = args.extra_data_file or args.netlist_file
416-
self.extra_fields = self._split(args.extra_fields)
428+
if args.extra_fields is not None:
429+
self.show_fields = self.default_show_group_fields + \
430+
self._split(args.extra_fields)
431+
self.group_fields = self.show_fields
432+
else:
433+
self.show_fields = self._split(args.show_fields)
434+
self.group_fields = self._split(args.group_fields)
417435
self.normalize_field_case = args.normalize_field_case
418436
self.board_variant_field = args.variant_field
419437
self.board_variant_whitelist = self._split(args.variants_whitelist)
@@ -423,7 +441,5 @@ def set_from_args(self, args):
423441
def get_html_config(self):
424442
import json
425443
d = {f: getattr(self, f) for f in self.html_config_fields}
426-
# Temporary until currently hardcoded columns are made configurable
427-
d["fields"] = ["References"] + self.extra_fields + \
428-
["Value", "Footprint", "Quantity"]
444+
d["fields"] = self.show_fields
429445
return json.dumps(d)

0 commit comments

Comments
 (0)