InteractiveHtmlBom 2.10.0__tar.gz → 2.11.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/DATAFORMAT.md +4 -0
  2. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/__init__.py +2 -2
  3. interactivehtmlbom-2.11.1/InteractiveHtmlBom/compat.py +23 -0
  4. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/config.py +31 -9
  5. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/ibom.py +8 -6
  6. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/dialog_base.py +8 -1
  7. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/settings_dialog.py +4 -1
  8. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad.py +67 -26
  9. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/schema/genericjsonpcbdata_v1.schema +14 -3
  10. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/generate_interactive_bom.py +19 -10
  11. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/version.py +1 -1
  12. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/render.js +7 -2
  13. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/table-util.js +1 -1
  14. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/util.js +4 -1
  15. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/PKG-INFO +1 -1
  16. interactivehtmlbom-2.11.1/make_release.py +158 -0
  17. interactivehtmlbom-2.11.1/settings_dialog.fbp +3978 -0
  18. interactivehtmlbom-2.10.0/settings_dialog.fbp +0 -3859
  19. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/.github/workflows/ci.yml +0 -0
  20. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/.github/workflows/release.yml +0 -0
  21. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/.gitignore +0 -0
  22. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/.jsbeautifyrc +0 -0
  23. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/.gitattributes +0 -0
  24. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/Run.bat +0 -0
  25. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/__init__.py +0 -0
  26. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/fontparser.py +0 -0
  27. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/lzstring.py +0 -0
  28. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/newstroke_font.py +0 -0
  29. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/core/units.py +0 -0
  30. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/__init__.py +0 -0
  31. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/bitmaps/btn-arrow-down.png +0 -0
  32. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/bitmaps/btn-arrow-up.png +0 -0
  33. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/bitmaps/btn-minus.png +0 -0
  34. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/bitmaps/btn-plus.png +0 -0
  35. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog/bitmaps/btn-question.png +0 -0
  36. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/dialog_test.py +0 -0
  37. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/__init__.py +0 -0
  38. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/common.py +0 -0
  39. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/easyeda.py +0 -0
  40. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/fusion_eagle.py +0 -0
  41. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/genericjson.py +0 -0
  42. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad_extra/__init__.py +0 -0
  43. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad_extra/netlistparser.py +0 -0
  44. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad_extra/parser_base.py +0 -0
  45. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad_extra/sexpressions.py +0 -0
  46. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/kicad_extra/xmlparser.py +0 -0
  47. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/ecad/svgpath.py +0 -0
  48. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/errors.py +0 -0
  49. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/i18n/language_en.bat +0 -0
  50. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/i18n/language_zh.bat +0 -0
  51. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/icon.png +0 -0
  52. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/ibom.css +0 -0
  53. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/ibom.html +0 -0
  54. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/ibom.js +0 -0
  55. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/lz-string.js +0 -0
  56. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/pep.js +0 -0
  57. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/InteractiveHtmlBom/web/split.js +0 -0
  58. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/LICENSE +0 -0
  59. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/README.md +0 -0
  60. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/__init__.py +0 -0
  61. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/baseline-settings-20px.svg +0 -0
  62. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-grouped-32px.svg +0 -0
  63. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-left-right-32px.svg +0 -0
  64. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-netlist-32px.svg +0 -0
  65. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-only-32px.svg +0 -0
  66. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-top-bot-32px.svg +0 -0
  67. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/bom-ungrouped-32px.svg +0 -0
  68. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/btn-arrow-down.svg +0 -0
  69. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/btn-arrow-up.svg +0 -0
  70. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/btn-minus.svg +0 -0
  71. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/btn-plus.svg +0 -0
  72. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/btn-question.svg +0 -0
  73. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/copy-48px.svg +0 -0
  74. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/io-36px.svg +0 -0
  75. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/plugin.svg +0 -0
  76. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/plugin_icon_big.png +0 -0
  77. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/icons/stats-36px.svg +0 -0
  78. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/pyproject.toml +0 -0
  79. {interactivehtmlbom-2.10.0 → interactivehtmlbom-2.11.1}/tests/test_module.py +0 -0
@@ -96,6 +96,7 @@ pcbdata = {
96
96
  "revision": "rev",
97
97
  "company": "Horns and Hoofs",
98
98
  "date": "2019-04-18",
99
+ "variant": "var" // optional
99
100
  },
100
101
  // Contains full bom table as well as filtered by front/back.
101
102
  // See bom row description below.
@@ -258,7 +259,10 @@ attribute.
258
259
  // 0: justify center
259
260
  // 1: justify right/bot
260
261
  "justify": [horizontal, vertical],
262
+ // Either the thickness or the fillrule must be used
261
263
  "thickness": thickness,
264
+ // fillrule is only supported for svgpath
265
+ "fillrule": "nonzero" | "evenodd",
262
266
  "attr": [
263
267
  // may include none, one or both
264
268
  "italic", "mirrored"
@@ -3,11 +3,11 @@ import sys
3
3
  import threading
4
4
  import time
5
5
 
6
- import wx
7
- import wx.aui
6
+ from .compat import get_wx
8
7
 
9
8
 
10
9
  def check_for_bom_button():
10
+ wx = get_wx()
11
11
  # From Miles McCoo's blog
12
12
  # https://kicad.mmccoo.com/2017/03/05/adding-your-own-command-buttons-to-the-pcbnew-gui/
13
13
  def find_pcbnew_window():
@@ -0,0 +1,23 @@
1
+ """Compatibility utilities for optional wx dependency."""
2
+
3
+ import os
4
+
5
+ _wx = None
6
+
7
+
8
+ def should_create_wx_app():
9
+ """Check if we should create a wx app based on environment."""
10
+ return 'INTERACTIVE_HTML_BOM_NO_DISPLAY' not in os.environ
11
+
12
+
13
+ def get_wx():
14
+ """Get the wx module, or None if not available."""
15
+ global _wx
16
+ if _wx is not None:
17
+ return _wx
18
+ try:
19
+ import wx
20
+ _wx = wx
21
+ return _wx
22
+ except ImportError:
23
+ return None
@@ -4,9 +4,7 @@ import argparse
4
4
  import os
5
5
  import re
6
6
 
7
- from wx import FileConfig
8
-
9
- from .. import dialog
7
+ from ..compat import get_wx
10
8
 
11
9
 
12
10
  class Config:
@@ -17,6 +15,8 @@ class Config:
17
15
  ' %p : pcb/project title from pcb metadata.\n'
18
16
  ' %c : company from pcb metadata.\n'
19
17
  ' %r : revision from pcb metadata.\n'
18
+ ' %v : pcb variant.\n'
19
+ ' %V : pcb variant or \'default\', if empty.\n'
20
20
  ' %d : pcb date from metadata if available, '
21
21
  'file modification date otherwise.\n'
22
22
  ' %D : bom generation date.\n'
@@ -39,7 +39,7 @@ class Config:
39
39
  'dark_mode', 'show_pads', 'show_fabrication', 'show_silkscreen',
40
40
  'highlight_pin1', 'redraw_on_drag', 'board_rotation', 'checkboxes',
41
41
  'bom_view', 'layer_view', 'offset_back_rotation',
42
- 'kicad_text_formatting'
42
+ 'kicad_text_formatting', 'mark_when_checked'
43
43
  ]
44
44
  default_show_group_fields = ["Value", "Footprint"]
45
45
 
@@ -55,13 +55,14 @@ class Config:
55
55
  board_rotation = 0
56
56
  offset_back_rotation = False
57
57
  checkboxes = ','.join(default_checkboxes)
58
+ mark_when_checked = ''
58
59
  bom_view = bom_view_choices[1]
59
60
  layer_view = layer_view_choices[1]
60
61
  compression = True
61
62
  open_browser = True
62
63
 
63
64
  # General section
64
- bom_dest_dir = 'bom/' # This is relative to pcb file directory
65
+ bom_dest_dir = 'bom' # This is relative to pcb file directory
65
66
  bom_name_format = 'ibom'
66
67
  component_sort_order = default_sort_order
67
68
  component_blacklist = []
@@ -82,6 +83,9 @@ class Config:
82
83
  board_variant_blacklist = []
83
84
  dnp_field = ''
84
85
 
86
+ # this is cli only field
87
+ kicad_variant = ''
88
+
85
89
  @staticmethod
86
90
  def _split(s):
87
91
  """Splits string by ',' and drops empty strings from resulting array"""
@@ -106,7 +110,7 @@ class Config:
106
110
  else:
107
111
  return
108
112
 
109
- f = FileConfig(localFilename=file)
113
+ f = get_wx().FileConfig(localFilename=file)
110
114
 
111
115
  f.SetPath('/html_defaults')
112
116
  self.dark_mode = f.ReadBool('dark_mode', self.dark_mode)
@@ -121,6 +125,7 @@ class Config:
121
125
  self.offset_back_rotation = f.ReadBool(
122
126
  'offset_back_rotation', self.offset_back_rotation)
123
127
  self.checkboxes = f.Read('checkboxes', self.checkboxes)
128
+ self.mark_when_checked = f.Read('mark_when_checked', self.mark_when_checked)
124
129
  self.bom_view = f.Read('bom_view', self.bom_view)
125
130
  self.layer_view = f.Read('layer_view', self.layer_view)
126
131
  self.compression = f.ReadBool('compression', self.compression)
@@ -168,7 +173,7 @@ class Config:
168
173
  def save(self, locally):
169
174
  file = self.local_config_file if locally else self.global_config_file
170
175
  print('Saving to', file)
171
- f = FileConfig(localFilename=file)
176
+ f = get_wx().FileConfig(localFilename=file)
172
177
 
173
178
  f.SetPath('/html_defaults')
174
179
  f.WriteBool('dark_mode', self.dark_mode)
@@ -180,6 +185,7 @@ class Config:
180
185
  f.WriteInt('board_rotation', self.board_rotation)
181
186
  f.WriteBool('offset_back_rotation', self.offset_back_rotation)
182
187
  f.Write('checkboxes', self.checkboxes)
188
+ f.Write('mark_when_checked', self.mark_when_checked)
183
189
  f.Write('bom_view', self.bom_view)
184
190
  f.Write('layer_view', self.layer_view)
185
191
  f.WriteBool('compression', self.compression)
@@ -226,6 +232,7 @@ class Config:
226
232
  self.offset_back_rotation = \
227
233
  dlg.html.offsetBackRotationCheckbox.IsChecked()
228
234
  self.checkboxes = dlg.html.bomCheckboxesCtrl.Value
235
+ # No dialog for mark_when_checked ...
229
236
  self.bom_view = self.bom_view_choices[dlg.html.bomDefaultView.Selection]
230
237
  self.layer_view = self.layer_view_choices[
231
238
  dlg.html.layerDefaultView.Selection]
@@ -275,6 +282,7 @@ class Config:
275
282
  dlg.html.boardRotationSlider.Value = self.board_rotation
276
283
  dlg.html.offsetBackRotationCheckbox.Value = self.offset_back_rotation
277
284
  dlg.html.bomCheckboxesCtrl.Value = self.checkboxes
285
+ # No dialog for mark_when_checked ...
278
286
  dlg.html.bomDefaultView.Selection = self.bom_view_choices.index(
279
287
  self.bom_view)
280
288
  dlg.html.layerDefaultView.Selection = self.layer_view_choices.index(
@@ -320,14 +328,21 @@ class Config:
320
328
  self.board_variant_blacklist)
321
329
  dlg.fields.dnpFieldBox.Value = self.dnp_field
322
330
 
331
+ if self.kicad_variant != '':
332
+ dlg.fields.variantLabel.Show()
333
+ dlg.fields.variantLabel.SetLabel(f'Current variant: {self.kicad_variant}')
334
+
323
335
  dlg.finish_init()
324
336
 
325
337
  @classmethod
326
338
  def add_options(cls, parser, version):
327
339
  # type: (argparse.ArgumentParser, str) -> None
328
340
  parser.add_argument('--show-dialog', action='store_true',
329
- help='Shows config dialog. All other flags '
330
- 'will be ignored.')
341
+ help='Shows config dialog. All flags except '
342
+ '--kicad-variant will be ignored.')
343
+ parser.add_argument('--kicad-variant', default='',
344
+ help='KiCad board variant, empty is default '
345
+ 'variant. (Only for KiCad v10+)')
331
346
  parser.add_argument('--version', action='version', version=version)
332
347
  # Html
333
348
  parser.add_argument('--dark-mode', help='Default to dark mode.',
@@ -360,6 +375,10 @@ class Config:
360
375
  parser.add_argument('--checkboxes',
361
376
  default=cls.checkboxes,
362
377
  help='Comma separated list of checkbox columns.')
378
+ parser.add_argument('--mark-when-checked',
379
+ default=cls.mark_when_checked,
380
+ help='Name of the checkbox column used to mark '
381
+ 'components when checked.')
363
382
  parser.add_argument('--bom-view', default=cls.bom_view,
364
383
  choices=cls.bom_view_choices,
365
384
  help='Default BOM view.')
@@ -436,6 +455,8 @@ class Config:
436
455
  # type: (argparse.Namespace) -> None
437
456
  import math
438
457
 
458
+ self.kicad_variant = args.kicad_variant
459
+
439
460
  # Html
440
461
  self.dark_mode = args.dark_mode
441
462
  self.show_pads = not args.hide_pads
@@ -446,6 +467,7 @@ class Config:
446
467
  self.board_rotation = math.fmod(args.board_rotation // 5, 37)
447
468
  self.offset_back_rotation = args.offset_back_rotation
448
469
  self.checkboxes = args.checkboxes
470
+ self.mark_when_checked = args.mark_when_checked
449
471
  self.bom_view = args.bom_view
450
472
  self.layer_view = args.layer_view
451
473
  self.compression = not args.no_compression
@@ -8,13 +8,11 @@ import re
8
8
  import sys
9
9
  from datetime import datetime
10
10
 
11
- import wx
12
-
13
11
  from . import units
14
12
  from .config import Config
15
- from ..dialog import SettingsDialog
16
13
  from ..ecad.common import EcadParser, Component
17
14
  from ..errors import ParsingException
15
+ from ..compat import get_wx
18
16
 
19
17
 
20
18
  class Logger(object):
@@ -38,13 +36,13 @@ class Logger(object):
38
36
  if self.cli:
39
37
  self.logger.error(msg)
40
38
  else:
41
- wx.MessageBox(msg)
39
+ get_wx().MessageBox(msg)
42
40
 
43
41
  def warn(self, msg):
44
42
  if self.cli:
45
43
  self.logger.warning(msg)
46
44
  else:
47
- wx.LogWarning(msg)
45
+ get_wx().LogWarning(msg)
48
46
 
49
47
 
50
48
  log = None
@@ -234,6 +232,8 @@ def process_substitutions(bom_name_format, pcb_file_name, metadata):
234
232
  name = name.replace('%c', metadata['company'])
235
233
  name = name.replace('%r', metadata['revision'])
236
234
  name = name.replace('%d', metadata['date'].replace(':', '-'))
235
+ name = name.replace('%v', metadata.get('variant', ''))
236
+ name = name.replace('%V', metadata.get('variant', '') or 'default')
237
237
  now = datetime.now()
238
238
  name = name.replace('%D', now.strftime('%Y-%m-%d'))
239
239
  name = name.replace('%T', now.strftime('%H-%M-%S'))
@@ -341,6 +341,8 @@ def main(parser, config, logger):
341
341
 
342
342
  def run_with_dialog(parser, config, logger):
343
343
  # type: (EcadParser, Config, Logger) -> None
344
+ from ..dialog import SettingsDialog
345
+
344
346
  def save_config(dialog_panel, locally=False):
345
347
  config.set_from_dialog(dialog_panel)
346
348
  config.save(locally)
@@ -358,7 +360,7 @@ def run_with_dialog(parser, config, logger):
358
360
  if extra_data_file is not None:
359
361
  dlg.set_extra_data_path(extra_data_file)
360
362
  config.transfer_to_dialog(dlg.panel)
361
- if dlg.ShowModal() == wx.ID_OK:
363
+ if dlg.ShowModal() == get_wx().ID_OK:
362
364
  config.set_from_dialog(dlg.panel)
363
365
  main(parser, config, logger)
364
366
  finally:
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  ###########################################################################
4
- ## Python code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
4
+ ## Python code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
5
5
  ## http://www.wxformbuilder.org/
6
6
  ##
7
7
  ## PLEASE DO *NOT* EDIT THIS FILE!
@@ -413,6 +413,13 @@ class FieldsPanelBase ( wx.Panel ):
413
413
  self.extraDataFilePicker = wx.FilePickerCtrl( sbSizer7.GetStaticBox(), wx.ID_ANY, wx.EmptyString, u"Select a file", u"Netlist and xml files (*.net; *.xml)|*.net;*.xml", wx.DefaultPosition, wx.DefaultSize, wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST|wx.FLP_OPEN|wx.FLP_SMALL|wx.FLP_USE_TEXTCTRL|wx.BORDER_SIMPLE )
414
414
  sbSizer7.Add( self.extraDataFilePicker, 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT|wx.LEFT, 5 )
415
415
 
416
+ self.variantLabel = wx.StaticText( sbSizer7.GetStaticBox(), wx.ID_ANY, u"Current variant:", wx.DefaultPosition, wx.DefaultSize, 0 )
417
+ self.variantLabel.Wrap( -1 )
418
+
419
+ self.variantLabel.Hide()
420
+
421
+ sbSizer7.Add( self.variantLabel, 0, wx.ALL, 5 )
422
+
416
423
 
417
424
  bSizer42.Add( sbSizer7, 0, wx.ALL|wx.EXPAND, 5 )
418
425
 
@@ -334,8 +334,11 @@ class FieldsPanel(dialog_base.FieldsPanelBase):
334
334
  self.extra_field_data = self.extra_data_func(
335
335
  extra_data_file, self.normalizeCaseCheckbox.Value)
336
336
  except Exception as e:
337
+ import traceback
337
338
  pop_error(
338
- "Failed to parse file %s\n\n%s" % (extra_data_file, e))
339
+ "Failed to parse file %s\n\n%s" % (
340
+ extra_data_file,
341
+ str(e) + '\n' + traceback.format_exc()))
339
342
  self.extraDataFilePicker.Path = ''
340
343
 
341
344
  if self.extra_field_data is not None:
@@ -30,10 +30,16 @@ class PcbnewParser(EcadParser):
30
30
  self.board = board
31
31
  if self.board is None:
32
32
  self.board = pcbnew.LoadBoard(self.file_name) # type: pcbnew.BOARD
33
+ if not self.board:
34
+ raise Exception('Failed to load board file')
35
+ if hasattr(self.board, "SetCurrentVariant"):
36
+ self.board.SetCurrentVariant(config.kicad_variant)
33
37
  if hasattr(self.board, 'GetModules'):
34
- self.footprints = list(self.board.GetModules()) # type: list[pcbnew.MODULE]
38
+ # type: list[pcbnew.MODULE]
39
+ self.footprints = list(self.board.GetModules())
35
40
  else:
36
- self.footprints = list(self.board.GetFootprints()) # type: list[pcbnew.FOOTPRINT]
41
+ # type: list[pcbnew.FOOTPRINT]
42
+ self.footprints = list(self.board.GetFootprints())
37
43
  self.font_parser = FontParser()
38
44
 
39
45
  def get_extra_field_data(self, file_name):
@@ -46,8 +52,7 @@ class PcbnewParser(EcadParser):
46
52
 
47
53
  return ExtraFieldData(data[0], data[1])
48
54
 
49
- @staticmethod
50
- def get_footprint_fields(f):
55
+ def get_footprint_fields(self, f):
51
56
  # type: (pcbnew.FOOTPRINT) -> dict
52
57
  props = {}
53
58
  if hasattr(f, "GetProperties"):
@@ -60,6 +65,15 @@ class PcbnewParser(EcadParser):
60
65
  if hasattr(f, "IsDNP"):
61
66
  if f.IsDNP():
62
67
  props["kicad_dnp"] = "DNP"
68
+ if hasattr(f, 'GetVariant'):
69
+ variant = f.GetVariant(self.config.kicad_variant)
70
+ if variant:
71
+ var_fields = variant.GetFields()
72
+ for k in var_fields.keys():
73
+ props[str(k)] = str(f.GetFieldShownText(str(k)))
74
+ if variant.GetDNP():
75
+ props["kicad_dnp"] = "DNP"
76
+
63
77
  return props
64
78
 
65
79
  def parse_extra_data_from_pcb(self):
@@ -209,7 +223,8 @@ class PcbnewParser(EcadParser):
209
223
  "angle": angle,
210
224
  "polygons": polygons
211
225
  }
212
- if hasattr(d, "IsFilled") and not d.IsFilled():
226
+ if ((hasattr(d, "IsFilled") and not d.IsFilled()) or
227
+ (hasattr(d, "IsSolidFill") and not d.IsSolidFill())):
213
228
  shape_dict["filled"] = 0
214
229
  shape_dict["width"] = d.GetWidth() * 1e-6
215
230
  return shape_dict
@@ -273,7 +288,8 @@ class PcbnewParser(EcadParser):
273
288
  "svgpath": create_path(lines)
274
289
  }
275
290
  elif hasattr(d, 'GetEffectiveTextShape'):
276
- shape = d.GetEffectiveTextShape(False) # type: pcbnew.SHAPE_COMPOUND
291
+ # type: pcbnew.SHAPE_COMPOUND
292
+ shape = d.GetEffectiveTextShape(False)
277
293
  segments = []
278
294
  polygons = []
279
295
  for s in shape.GetSubshapes():
@@ -426,7 +442,7 @@ class PcbnewParser(EcadParser):
426
442
  for d in f.GraphicalItems():
427
443
  drawings.append((d.GetClass(), d))
428
444
  if hasattr(f, "GetFields"):
429
- fields = f.GetFields() # type: list[pcbnew.PCB_FIELD]
445
+ fields = f.GetFields() # type: list[pcbnew.PCB_FIELD]
430
446
  for field in fields:
431
447
  if field.IsReference() or field.IsValue():
432
448
  continue
@@ -434,16 +450,31 @@ class PcbnewParser(EcadParser):
434
450
 
435
451
  return drawings
436
452
 
453
+ @staticmethod
454
+ def _pad_is_through_hole(pad):
455
+ # type: (pcbnew.PAD) -> bool
456
+ if hasattr(pcbnew, 'PAD_ATTRIB_PTH'):
457
+ through_hole_attributes = [pcbnew.PAD_ATTRIB_PTH,
458
+ pcbnew.PAD_ATTRIB_NPTH]
459
+ else:
460
+ through_hole_attributes = [pcbnew.PAD_ATTRIB_STANDARD,
461
+ pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED]
462
+ return pad.GetAttribute() in through_hole_attributes
463
+
437
464
  def parse_pad(self, pad):
438
465
  # type: (pcbnew.PAD) -> list[dict]
439
466
  custom_padstack = False
440
467
  outer_layers = [(pcbnew.F_Cu, "F"), (pcbnew.B_Cu, "B")]
441
468
  if hasattr(pad, 'Padstack'):
442
- padstack = pad.Padstack() # type: pcbnew.PADSTACK
469
+ padstack = pad.Padstack() # type: pcbnew.PADSTACK
443
470
  layers_set = list(padstack.LayerSet().Seq())
471
+ if hasattr(pcbnew, "UNCONNECTED_LAYER_MODE_REMOVE_ALL"):
472
+ ULMRA = pcbnew.UNCONNECTED_LAYER_MODE_REMOVE_ALL
473
+ else:
474
+ ULMRA = padstack.UNCONNECTED_LAYER_MODE_REMOVE_ALL
444
475
  custom_padstack = (
445
- padstack.Mode() != padstack.MODE_NORMAL or \
446
- padstack.UnconnectedLayerMode() == padstack.UNCONNECTED_LAYER_MODE_REMOVE_ALL
476
+ padstack.Mode() != padstack.MODE_NORMAL or
477
+ padstack.UnconnectedLayerMode() == ULMRA
447
478
  )
448
479
  else:
449
480
  layers_set = list(pad.GetLayerSet().Seq())
@@ -451,7 +482,7 @@ class PcbnewParser(EcadParser):
451
482
  for layer, letter in outer_layers:
452
483
  if layer in layers_set:
453
484
  layers.append(letter)
454
- if not layers:
485
+ if not layers and not self._pad_is_through_hole(pad):
455
486
  return []
456
487
 
457
488
  if custom_padstack:
@@ -537,13 +568,8 @@ class PcbnewParser(EcadParser):
537
568
  except TypeError:
538
569
  pad_dict["chamfpos"] = pad.GetChamferPositions()
539
570
  pad_dict["chamfratio"] = pad.GetChamferRectRatio()
540
- if hasattr(pcbnew, 'PAD_ATTRIB_PTH'):
541
- through_hole_attributes = [pcbnew.PAD_ATTRIB_PTH,
542
- pcbnew.PAD_ATTRIB_NPTH]
543
- else:
544
- through_hole_attributes = [pcbnew.PAD_ATTRIB_STANDARD,
545
- pcbnew.PAD_ATTRIB_HOLE_NOT_PLATED]
546
- if pad.GetAttribute() in through_hole_attributes:
571
+
572
+ if self._pad_is_through_hole(pad):
547
573
  pad_dict["type"] = "th"
548
574
  pad_dict["drillshape"] = {
549
575
  pcbnew.PAD_DRILL_SHAPE_CIRCLE: "circle",
@@ -552,6 +578,7 @@ class PcbnewParser(EcadParser):
552
578
  pad_dict["drillsize"] = self.normalize(pad.GetDrillSize())
553
579
  else:
554
580
  pad_dict["type"] = "smd"
581
+
555
582
  if hasattr(pad, "GetOffset"):
556
583
  try:
557
584
  pad_dict["offset"] = self.normalize(pad.GetOffset(layer))
@@ -708,6 +735,8 @@ class PcbnewParser(EcadParser):
708
735
  if (hasattr(zone, 'GetFilledPolysUseThickness') and
709
736
  not zone.GetFilledPolysUseThickness()):
710
737
  width = 0
738
+ if KICAD_VERSION[0] >= 7:
739
+ width = 0
711
740
  zone_dict = {
712
741
  "polygons": self.parse_poly_set(poly_set),
713
742
  "width": width,
@@ -728,16 +757,24 @@ class PcbnewParser(EcadParser):
728
757
  nets = sorted([str(s) for s in nets])
729
758
  return nets
730
759
 
731
- @staticmethod
732
- def footprint_to_component(footprint, extra_fields):
760
+ def footprint_to_component(self, footprint, extra_fields):
761
+ # type: (pcbnew.FOOTPRINT, list) -> Component
733
762
  try:
734
763
  footprint_name = str(footprint.GetFPID().GetFootprintName())
735
764
  except AttributeError:
736
765
  footprint_name = str(footprint.GetFPID().GetLibItemName())
737
766
 
767
+ value = footprint.GetValue()
768
+ if hasattr(footprint, 'GetFieldValueForVariant'):
769
+ value = footprint.GetFieldValueForVariant(
770
+ self.config.kicad_variant, 'Value')
738
771
  attr = 'Normal'
739
772
  if hasattr(pcbnew, 'FP_EXCLUDE_FROM_BOM'):
740
- if footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
773
+ if hasattr(footprint, 'GetExcludedFromBOMForVariant'):
774
+ if footprint.GetExcludedFromBOMForVariant(
775
+ self.config.kicad_variant):
776
+ attr = 'Virtual'
777
+ elif footprint.GetAttributes() & pcbnew.FP_EXCLUDE_FROM_BOM:
741
778
  attr = 'Virtual'
742
779
  elif hasattr(pcbnew, 'MOD_VIRTUAL'):
743
780
  if footprint.GetAttributes() == pcbnew.MOD_VIRTUAL:
@@ -748,7 +785,7 @@ class PcbnewParser(EcadParser):
748
785
  }.get(footprint.GetLayer())
749
786
 
750
787
  return Component(footprint.GetReference(),
751
- footprint.GetValue(),
788
+ value,
752
789
  footprint_name,
753
790
  layer,
754
791
  attr,
@@ -767,9 +804,9 @@ class PcbnewParser(EcadParser):
767
804
  self.config.dnp_field)
768
805
 
769
806
  if not self.config.extra_data_file and need_extra_fields:
770
- self.logger.warn('Ignoring extra fields related config parameters '
771
- 'since no netlist/xml file was specified.')
772
- need_extra_fields = False
807
+ self.config.extra_data_file = self.file_name
808
+ self.logger.warn('Assuming extra data file to be the pcb file '
809
+ 'since --extra-data-file was not specified.')
773
810
 
774
811
  extra_field_data = None
775
812
  if (self.config.extra_data_file and
@@ -832,6 +869,7 @@ class PcbnewParser(EcadParser):
832
869
  "revision": revision,
833
870
  "company": company,
834
871
  "date": file_date,
872
+ "variant": self.config.kicad_variant,
835
873
  },
836
874
  "bom": {},
837
875
  "font_data": self.font_parser.get_parsed_font()
@@ -898,7 +936,7 @@ class InteractiveHtmlBomPlugin(pcbnew.ActionPlugin, object):
898
936
  from ..errors import ParsingException
899
937
 
900
938
  logger = ibom.Logger()
901
- board = pcbnew.GetBoard()
939
+ board = pcbnew.GetBoard() # type: pcbnew.BOARD
902
940
  pcb_file_name = board.GetFileName()
903
941
 
904
942
  if not pcb_file_name:
@@ -906,6 +944,9 @@ class InteractiveHtmlBomPlugin(pcbnew.ActionPlugin, object):
906
944
  return
907
945
 
908
946
  config = Config(version, os.path.dirname(pcb_file_name))
947
+ if hasattr(board, 'GetCurrentVariant'):
948
+ config.kicad_variant = board.GetCurrentVariant()
949
+
909
950
  parser = PcbnewParser(pcb_file_name, config, logger, board)
910
951
 
911
952
  try:
@@ -305,6 +305,9 @@
305
305
  },
306
306
  "date": {
307
307
  "type": "string"
308
+ },
309
+ "variant": {
310
+ "type": "string"
308
311
  }
309
312
  },
310
313
  "required": ["title", "revision", "company", "date"],
@@ -443,12 +446,20 @@
443
446
  "properties": {
444
447
  "svgpath": { "type": "string" },
445
448
  "thickness": { "type": "number" },
449
+ "fillrule": {
450
+ "type": "string",
451
+ "enum": [
452
+ "nonzero",
453
+ "evenodd"
454
+ ]
455
+ },
446
456
  "ref": { "type": "integer" , "const": 1 },
447
457
  "val": { "type": "integer" , "const": 1 }
448
458
  },
449
- "required": [
450
- "svgpath",
451
- "thickness"
459
+ "required": ["svgpath"],
460
+ "oneOf": [
461
+ { "required": ["thickness"] },
462
+ { "required": ["fillrule"] }
452
463
  ],
453
464
  "title": "DrawingText"
454
465
  },
@@ -4,6 +4,7 @@ from __future__ import absolute_import
4
4
  import argparse
5
5
  import os
6
6
  import sys
7
+
7
8
  # Add ../ to the path
8
9
  # Works if this script is executed without installing the module
9
10
  script_dir = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
@@ -23,16 +24,22 @@ def to_utf(s):
23
24
 
24
25
 
25
26
  def main():
26
- create_wx_app = 'INTERACTIVE_HTML_BOM_NO_DISPLAY' not in os.environ
27
-
28
- import wx
29
-
30
- if create_wx_app:
31
- app = wx.App()
32
- if hasattr(wx, "APP_ASSERT_SUPPRESS"):
33
- app.SetAssertMode(wx.APP_ASSERT_SUPPRESS)
34
- elif hasattr(wx, "DisableAsserts"):
35
- wx.DisableAsserts()
27
+ from .compat import get_wx, should_create_wx_app
28
+ wx = get_wx()
29
+ create_wx_app = should_create_wx_app()
30
+
31
+ if wx is None and create_wx_app:
32
+ print("wxpython is required unless INTERACTIVE_HTML_BOM_NO_DISPLAY "
33
+ "environment variable is set")
34
+ sys.exit(1)
35
+
36
+ if wx is not None:
37
+ if create_wx_app:
38
+ app = wx.App()
39
+ if hasattr(wx, "APP_ASSERT_SUPPRESS"):
40
+ app.SetAssertMode(wx.APP_ASSERT_SUPPRESS)
41
+ elif hasattr(wx, "DisableAsserts"):
42
+ wx.DisableAsserts()
36
43
 
37
44
  from .core import ibom
38
45
  from .core.config import Config
@@ -40,6 +47,7 @@ def main():
40
47
  from .version import version
41
48
  from .errors import (ExitCodes, ParsingException, exit_error)
42
49
 
50
+
43
51
  parser = argparse.ArgumentParser(
44
52
  description='KiCad InteractiveHtmlBom plugin CLI.',
45
53
  formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@@ -58,6 +66,7 @@ def main():
58
66
  print("Loading %s" % args.file)
59
67
 
60
68
  config = Config(version, os.path.dirname(os.path.abspath(args.file)))
69
+ config.kicad_variant = args.kicad_variant
61
70
 
62
71
  parser = get_parser_by_extension(
63
72
  os.path.abspath(args.file), config, logger)
@@ -3,7 +3,7 @@ import os
3
3
  import subprocess
4
4
 
5
5
 
6
- LAST_TAG = 'v2.10.0'
6
+ LAST_TAG = 'v2.11.1'
7
7
 
8
8
 
9
9
  def _get_git_version():
@@ -24,12 +24,17 @@ function drawText(ctx, text, color) {
24
24
  ctx.strokeStyle = color;
25
25
  ctx.lineCap = "round";
26
26
  ctx.lineJoin = "round";
27
- ctx.lineWidth = text.thickness;
28
27
  if ("svgpath" in text) {
29
- ctx.stroke(new Path2D(text.svgpath));
28
+ if ("thickness" in text) {
29
+ ctx.lineWidth = text.thickness;
30
+ ctx.stroke(new Path2D(text.svgpath));
31
+ } else if ("fillrule" in text) {
32
+ ctx.fill(new Path2D(text.svgpath), text.fillrule);
33
+ }
30
34
  ctx.restore();
31
35
  return;
32
36
  }
37
+ ctx.lineWidth = text.thickness;
33
38
  if ("polygons" in text) {
34
39
  ctx.fill(getPolygonsPath(text));
35
40
  ctx.restore();
@@ -20,7 +20,7 @@ function setBomHandlers() {
20
20
  draggingElement.remove();
21
21
 
22
22
  // Make BOM selectable again
23
- bom.style.removeProperty("userSelect");
23
+ bom.style.removeProperty("user-select");
24
24
 
25
25
  // Remove listeners
26
26
  document.removeEventListener('mousemove', mouseMoveHandler);
@@ -549,7 +549,10 @@ function initDefaults() {
549
549
  setHighlightPin1(highlightpin1);
550
550
  document.forms.highlightpin1.highlightpin1.value = highlightpin1;
551
551
 
552
- settings.markWhenChecked = readStorage("markWhenChecked") || "";
552
+ settings.markWhenChecked = readStorage("markWhenChecked");
553
+ if (settings.markWhenChecked == null) {
554
+ settings.markWhenChecked = config.mark_when_checked;
555
+ }
553
556
  populateMarkWhenCheckedOptions();
554
557
 
555
558
  function initBooleanSetting(storageString, def, elementId, func) {