InteractiveHtmlBom 2.8.1__py3-none-any.whl

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 (52) hide show
  1. InteractiveHtmlBom/.gitattributes +1 -0
  2. InteractiveHtmlBom/Run.bat +73 -0
  3. InteractiveHtmlBom/__init__.py +58 -0
  4. InteractiveHtmlBom/core/__init__.py +0 -0
  5. InteractiveHtmlBom/core/config.py +483 -0
  6. InteractiveHtmlBom/core/fontparser.py +52 -0
  7. InteractiveHtmlBom/core/ibom.py +365 -0
  8. InteractiveHtmlBom/core/lzstring.py +304 -0
  9. InteractiveHtmlBom/core/newstroke_font.py +11361 -0
  10. InteractiveHtmlBom/core/units.py +192 -0
  11. InteractiveHtmlBom/dialog/__init__.py +1 -0
  12. InteractiveHtmlBom/dialog/bitmaps/btn-arrow-down.png +0 -0
  13. InteractiveHtmlBom/dialog/bitmaps/btn-arrow-up.png +0 -0
  14. InteractiveHtmlBom/dialog/bitmaps/btn-minus.png +0 -0
  15. InteractiveHtmlBom/dialog/bitmaps/btn-plus.png +0 -0
  16. InteractiveHtmlBom/dialog/bitmaps/btn-question.png +0 -0
  17. InteractiveHtmlBom/dialog/dialog_base.py +578 -0
  18. InteractiveHtmlBom/dialog/settings_dialog.py +406 -0
  19. InteractiveHtmlBom/dialog_test.py +17 -0
  20. InteractiveHtmlBom/ecad/__init__.py +41 -0
  21. InteractiveHtmlBom/ecad/common.py +251 -0
  22. InteractiveHtmlBom/ecad/easyeda.py +462 -0
  23. InteractiveHtmlBom/ecad/fusion_eagle.py +920 -0
  24. InteractiveHtmlBom/ecad/genericjson.py +167 -0
  25. InteractiveHtmlBom/ecad/kicad.py +843 -0
  26. InteractiveHtmlBom/ecad/kicad_extra/__init__.py +59 -0
  27. InteractiveHtmlBom/ecad/kicad_extra/netlistparser.py +59 -0
  28. InteractiveHtmlBom/ecad/kicad_extra/parser_base.py +26 -0
  29. InteractiveHtmlBom/ecad/kicad_extra/sexpressions.py +32 -0
  30. InteractiveHtmlBom/ecad/kicad_extra/xmlparser.py +42 -0
  31. InteractiveHtmlBom/ecad/schema/genericjsonpcbdata_v1.schema +638 -0
  32. InteractiveHtmlBom/ecad/svgpath.py +538 -0
  33. InteractiveHtmlBom/errors.py +16 -0
  34. InteractiveHtmlBom/generate_interactive_bom.py +84 -0
  35. InteractiveHtmlBom/i18n/language_en.bat +12 -0
  36. InteractiveHtmlBom/i18n/language_zh.bat +17 -0
  37. InteractiveHtmlBom/icon.png +0 -0
  38. InteractiveHtmlBom/version.py +29 -0
  39. InteractiveHtmlBom/web/ibom.css +887 -0
  40. InteractiveHtmlBom/web/ibom.html +337 -0
  41. InteractiveHtmlBom/web/ibom.js +1294 -0
  42. InteractiveHtmlBom/web/lz-string.js +10 -0
  43. InteractiveHtmlBom/web/pep.js +43 -0
  44. InteractiveHtmlBom/web/render.js +1075 -0
  45. InteractiveHtmlBom/web/split.js +6 -0
  46. InteractiveHtmlBom/web/table-util.js +371 -0
  47. InteractiveHtmlBom/web/util.js +638 -0
  48. interactivehtmlbom-2.8.1.dist-info/METADATA +67 -0
  49. interactivehtmlbom-2.8.1.dist-info/RECORD +52 -0
  50. interactivehtmlbom-2.8.1.dist-info/WHEEL +4 -0
  51. interactivehtmlbom-2.8.1.dist-info/entry_points.txt +2 -0
  52. interactivehtmlbom-2.8.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ *.bat eol=crlf
@@ -0,0 +1,73 @@
1
+ @echo off
2
+ set pathofEDASourceFile=%1
3
+ set FilePath=%~dp0
4
+
5
+ ::delete --show-dialog after first start up and setting
6
+ set option=--show-dialog
7
+
8
+ ::detect current language of user.
9
+ reg query "HKCU\Control Panel\Desktop" /v PreferredUILanguages>nul 2>nul&&goto _dosearch1_||goto _dosearch2_
10
+
11
+ :_dosearch1_
12
+ FOR /F "tokens=3" %%a IN (
13
+ 'reg query "HKCU\Control Panel\Desktop" /v PreferredUILanguages ^| find "PreferredUILanguages"'
14
+ ) DO (
15
+ set language=%%a
16
+ )
17
+ set language=%language:~,2%
18
+ goto _setlanguage_
19
+
20
+ :_dosearch2_
21
+ FOR /F "tokens=3" %%a IN (
22
+ 'reg query "HKLM\SYSTEM\ControlSet001\Control\Nls\Language" /v InstallLanguage ^| find "InstallLanguage"'
23
+ ) DO (
24
+ set language=%%a
25
+ )
26
+ if %language%==0804 (
27
+ set language=zh
28
+ )
29
+ goto _setlanguage_
30
+
31
+ :_setlanguage_
32
+ if %language%==zh (
33
+ call %FilePath%\i18n\language_zh.bat
34
+ ) else (
35
+ call %FilePath%\i18n\language_en.bat
36
+ )
37
+
38
+ cls
39
+
40
+ echo -------------------------------------------------------------------------------------------------------------------
41
+ echo -------------------------------------------------------------------------------------------------------------------
42
+ echo.
43
+ echo %i18n_thx4using%
44
+ echo %i18n_gitAddr%
45
+ echo %i18n_batScar%
46
+ echo.
47
+ echo -------------------------------------------------------------------------------------------------------------------
48
+ echo -------------------------------------------------------------------------------------------------------------------
49
+
50
+ set pyFilePath=%FilePath%generate_interactive_bom.py
51
+
52
+ :_convert_
53
+ if not defined pathofEDASourceFile (
54
+ set /p pathofEDASourceFile=%i18n_draghere%
55
+ )
56
+ echo.
57
+ echo %i18n_converting%
58
+ echo.
59
+ python %pyFilePath% %pathofEDASourceFile% %option%
60
+ set pathofEDASourceFile=
61
+
62
+ echo -------------------------------------------------------------------------------------------------------------------
63
+ echo -------------------------------------------------------------------------------------------------------------------
64
+ echo.
65
+ echo %i18n_converted%
66
+ echo.
67
+ echo -------------------------------------------------------------------------------------------------------------------
68
+ echo -------------------------------------------------------------------------------------------------------------------
69
+
70
+
71
+ CHOICE /C YN /N /M "%i18n_again% [ Y/N ]"
72
+ if errorlevel 2 exit
73
+ if errorlevel 1 goto _convert_
@@ -0,0 +1,58 @@
1
+ import os
2
+ import sys
3
+ import threading
4
+ import time
5
+
6
+ import wx
7
+ import wx.aui
8
+
9
+
10
+ def check_for_bom_button():
11
+ # From Miles McCoo's blog
12
+ # https://kicad.mmccoo.com/2017/03/05/adding-your-own-command-buttons-to-the-pcbnew-gui/
13
+ def find_pcbnew_window():
14
+ windows = wx.GetTopLevelWindows()
15
+ pcbneww = [w for w in windows if "pcbnew" in w.GetTitle().lower()]
16
+ if len(pcbneww) != 1:
17
+ return None
18
+ return pcbneww[0]
19
+
20
+ def callback(_):
21
+ plugin.Run()
22
+
23
+ path = os.path.dirname(__file__)
24
+ while not wx.GetApp():
25
+ time.sleep(1)
26
+ bm = wx.Bitmap(path + '/icon.png', wx.BITMAP_TYPE_PNG)
27
+ button_wx_item_id = 0
28
+
29
+ from pcbnew import ID_H_TOOLBAR
30
+ while True:
31
+ time.sleep(1)
32
+ pcbnew_window = find_pcbnew_window()
33
+ if not pcbnew_window:
34
+ continue
35
+
36
+ top_tb = pcbnew_window.FindWindowById(ID_H_TOOLBAR)
37
+ if button_wx_item_id == 0 or not top_tb.FindTool(button_wx_item_id):
38
+ top_tb.AddSeparator()
39
+ button_wx_item_id = wx.NewId()
40
+ top_tb.AddTool(button_wx_item_id, "iBOM", bm,
41
+ "Generate interactive BOM", wx.ITEM_NORMAL)
42
+ top_tb.Bind(wx.EVT_TOOL, callback, id=button_wx_item_id)
43
+ top_tb.Realize()
44
+
45
+
46
+ if (not os.environ.get('INTERACTIVE_HTML_BOM_CLI_MODE', False) and
47
+ not os.path.basename(sys.argv[0]).startswith('generate_interactive_bom')):
48
+ from .ecad.kicad import InteractiveHtmlBomPlugin
49
+
50
+ plugin = InteractiveHtmlBomPlugin()
51
+ plugin.register()
52
+
53
+ # Add a button the hacky way if plugin button is not supported
54
+ # in pcbnew, unless this is linux.
55
+ if not plugin.pcbnew_icon_support and not sys.platform.startswith('linux'):
56
+ t = threading.Thread(target=check_for_bom_button)
57
+ t.daemon = True
58
+ t.start()
File without changes
@@ -0,0 +1,483 @@
1
+ """Config object"""
2
+
3
+ import argparse
4
+ import os
5
+ import re
6
+
7
+ from wx import FileConfig
8
+
9
+ from .. import dialog
10
+
11
+
12
+ class Config:
13
+ FILE_NAME_FORMAT_HINT = (
14
+ 'Output file name format supports substitutions:\n'
15
+ '\n'
16
+ ' %f : original pcb file name without extension.\n'
17
+ ' %p : pcb/project title from pcb metadata.\n'
18
+ ' %c : company from pcb metadata.\n'
19
+ ' %r : revision from pcb metadata.\n'
20
+ ' %d : pcb date from metadata if available, '
21
+ 'file modification date otherwise.\n'
22
+ ' %D : bom generation date.\n'
23
+ ' %T : bom generation time.\n'
24
+ '\n'
25
+ 'Extension .html will be added automatically.'
26
+ ) # type: str
27
+
28
+ # Helper constants
29
+ bom_view_choices = ['bom-only', 'left-right', 'top-bottom']
30
+ layer_view_choices = ['F', 'FB', 'B']
31
+ default_sort_order = [
32
+ 'C', 'R', 'L', 'D', 'U', 'Y', 'X', 'F', 'SW', 'A',
33
+ '~',
34
+ 'HS', 'CNN', 'J', 'P', 'NT', 'MH',
35
+ ]
36
+ highlight_pin1_choices = ['none', 'all', 'selected']
37
+ default_checkboxes = ['Sourced', 'Placed']
38
+ html_config_fields = [
39
+ 'dark_mode', 'show_pads', 'show_fabrication', 'show_silkscreen',
40
+ 'highlight_pin1', 'redraw_on_drag', 'board_rotation', 'checkboxes',
41
+ 'bom_view', 'layer_view', 'offset_back_rotation',
42
+ 'kicad_text_formatting'
43
+ ]
44
+ default_show_group_fields = ["Value", "Footprint"]
45
+
46
+ # Defaults
47
+
48
+ # HTML section
49
+ dark_mode = False
50
+ show_pads = True
51
+ show_fabrication = False
52
+ show_silkscreen = True
53
+ redraw_on_drag = True
54
+ highlight_pin1 = highlight_pin1_choices[0]
55
+ board_rotation = 0
56
+ offset_back_rotation = False
57
+ checkboxes = ','.join(default_checkboxes)
58
+ bom_view = bom_view_choices[1]
59
+ layer_view = layer_view_choices[1]
60
+ compression = True
61
+ open_browser = True
62
+
63
+ # General section
64
+ bom_dest_dir = 'bom/' # This is relative to pcb file directory
65
+ bom_name_format = 'ibom'
66
+ component_sort_order = default_sort_order
67
+ component_blacklist = []
68
+ blacklist_virtual = True
69
+ blacklist_empty_val = False
70
+ include_tracks = False
71
+ include_nets = False
72
+ kicad_text_formatting = True
73
+
74
+ # Extra fields section
75
+ extra_data_file = None
76
+ netlist_initial_directory = '' # This is relative to pcb file directory
77
+ show_fields = default_show_group_fields
78
+ group_fields = default_show_group_fields
79
+ normalize_field_case = False
80
+ board_variant_field = ''
81
+ board_variant_whitelist = []
82
+ board_variant_blacklist = []
83
+ dnp_field = ''
84
+
85
+ @staticmethod
86
+ def _split(s):
87
+ """Splits string by ',' and drops empty strings from resulting array"""
88
+ return [a.replace('\\,', ',') for a in re.split(r'(?<!\\),', s) if a]
89
+
90
+ @staticmethod
91
+ def _join(lst):
92
+ return ','.join([s.replace(',', '\\,') for s in lst])
93
+
94
+ def __init__(self, version, local_dir):
95
+ self.version = version
96
+ self.local_config_file = os.path.join(local_dir, 'ibom.config.ini')
97
+ self.global_config_file = os.path.join(
98
+ os.path.dirname(__file__), '..', 'config.ini')
99
+
100
+ def load_from_ini(self):
101
+ """Init from config file if it exists."""
102
+ if os.path.isfile(self.local_config_file):
103
+ file = self.local_config_file
104
+ elif os.path.isfile(self.global_config_file):
105
+ file = self.global_config_file
106
+ else:
107
+ return
108
+
109
+ f = FileConfig(localFilename=file)
110
+
111
+ f.SetPath('/html_defaults')
112
+ self.dark_mode = f.ReadBool('dark_mode', self.dark_mode)
113
+ self.show_pads = f.ReadBool('show_pads', self.show_pads)
114
+ self.show_fabrication = f.ReadBool(
115
+ 'show_fabrication', self.show_fabrication)
116
+ self.show_silkscreen = f.ReadBool(
117
+ 'show_silkscreen', self.show_silkscreen)
118
+ self.redraw_on_drag = f.ReadBool('redraw_on_drag', self.redraw_on_drag)
119
+ self.highlight_pin1 = f.Read('highlight_pin1', self.highlight_pin1)
120
+ self.board_rotation = f.ReadInt('board_rotation', self.board_rotation)
121
+ self.offset_back_rotation = f.ReadBool(
122
+ 'offset_back_rotation', self.offset_back_rotation)
123
+ self.checkboxes = f.Read('checkboxes', self.checkboxes)
124
+ self.bom_view = f.Read('bom_view', self.bom_view)
125
+ self.layer_view = f.Read('layer_view', self.layer_view)
126
+ self.compression = f.ReadBool('compression', self.compression)
127
+ self.open_browser = f.ReadBool('open_browser', self.open_browser)
128
+
129
+ f.SetPath('/general')
130
+ self.bom_dest_dir = f.Read('bom_dest_dir', self.bom_dest_dir)
131
+ self.bom_name_format = f.Read('bom_name_format', self.bom_name_format)
132
+ self.component_sort_order = self._split(f.Read(
133
+ 'component_sort_order',
134
+ ','.join(self.component_sort_order)))
135
+ self.component_blacklist = self._split(f.Read(
136
+ 'component_blacklist',
137
+ ','.join(self.component_blacklist)))
138
+ self.blacklist_virtual = f.ReadBool(
139
+ 'blacklist_virtual', self.blacklist_virtual)
140
+ self.blacklist_empty_val = f.ReadBool(
141
+ 'blacklist_empty_val', self.blacklist_empty_val)
142
+ self.include_tracks = f.ReadBool('include_tracks', self.include_tracks)
143
+ self.include_nets = f.ReadBool('include_nets', self.include_nets)
144
+
145
+ f.SetPath('/fields')
146
+ self.show_fields = self._split(f.Read(
147
+ 'show_fields', self._join(self.show_fields)))
148
+ self.group_fields = self._split(f.Read(
149
+ 'group_fields', self._join(self.group_fields)))
150
+ self.normalize_field_case = f.ReadBool(
151
+ 'normalize_field_case', self.normalize_field_case)
152
+ self.board_variant_field = f.Read(
153
+ 'board_variant_field', self.board_variant_field)
154
+ self.board_variant_whitelist = self._split(f.Read(
155
+ 'board_variant_whitelist',
156
+ self._join(self.board_variant_whitelist)))
157
+ self.board_variant_blacklist = self._split(f.Read(
158
+ 'board_variant_blacklist',
159
+ self._join(self.board_variant_blacklist)))
160
+ self.dnp_field = f.Read('dnp_field', self.dnp_field)
161
+
162
+ # migration from previous settings
163
+ if self.highlight_pin1 == '0':
164
+ self.highlight_pin1 = 'none'
165
+ if self.highlight_pin1 == '1':
166
+ self.highlight_pin1 = 'all'
167
+
168
+ def save(self, locally):
169
+ file = self.local_config_file if locally else self.global_config_file
170
+ print('Saving to', file)
171
+ f = FileConfig(localFilename=file)
172
+
173
+ f.SetPath('/html_defaults')
174
+ f.WriteBool('dark_mode', self.dark_mode)
175
+ f.WriteBool('show_pads', self.show_pads)
176
+ f.WriteBool('show_fabrication', self.show_fabrication)
177
+ f.WriteBool('show_silkscreen', self.show_silkscreen)
178
+ f.WriteBool('redraw_on_drag', self.redraw_on_drag)
179
+ f.Write('highlight_pin1', self.highlight_pin1)
180
+ f.WriteInt('board_rotation', self.board_rotation)
181
+ f.WriteBool('offset_back_rotation', self.offset_back_rotation)
182
+ f.Write('checkboxes', self.checkboxes)
183
+ f.Write('bom_view', self.bom_view)
184
+ f.Write('layer_view', self.layer_view)
185
+ f.WriteBool('compression', self.compression)
186
+ f.WriteBool('open_browser', self.open_browser)
187
+
188
+ f.SetPath('/general')
189
+ bom_dest_dir = self.bom_dest_dir
190
+ if bom_dest_dir.startswith(self.netlist_initial_directory):
191
+ bom_dest_dir = os.path.relpath(
192
+ bom_dest_dir, self.netlist_initial_directory)
193
+ f.Write('bom_dest_dir', bom_dest_dir)
194
+ f.Write('bom_name_format', self.bom_name_format)
195
+ f.Write('component_sort_order',
196
+ ','.join(self.component_sort_order))
197
+ f.Write('component_blacklist',
198
+ ','.join(self.component_blacklist))
199
+ f.WriteBool('blacklist_virtual', self.blacklist_virtual)
200
+ f.WriteBool('blacklist_empty_val', self.blacklist_empty_val)
201
+ f.WriteBool('include_tracks', self.include_tracks)
202
+ f.WriteBool('include_nets', self.include_nets)
203
+
204
+ f.SetPath('/fields')
205
+ f.Write('show_fields', self._join(self.show_fields))
206
+ f.Write('group_fields', self._join(self.group_fields))
207
+ f.WriteBool('normalize_field_case', self.normalize_field_case)
208
+ f.Write('board_variant_field', self.board_variant_field)
209
+ f.Write('board_variant_whitelist',
210
+ self._join(self.board_variant_whitelist))
211
+ f.Write('board_variant_blacklist',
212
+ self._join(self.board_variant_blacklist))
213
+ f.Write('dnp_field', self.dnp_field)
214
+ f.Flush()
215
+
216
+ def set_from_dialog(self, dlg):
217
+ # type: (dialog.settings_dialog.SettingsDialogPanel) -> None
218
+ # Html
219
+ self.dark_mode = dlg.html.darkModeCheckbox.IsChecked()
220
+ self.show_pads = dlg.html.showPadsCheckbox.IsChecked()
221
+ self.show_fabrication = dlg.html.showFabricationCheckbox.IsChecked()
222
+ self.show_silkscreen = dlg.html.showSilkscreenCheckbox.IsChecked()
223
+ self.redraw_on_drag = dlg.html.continuousRedrawCheckbox.IsChecked()
224
+ self.highlight_pin1 = self.highlight_pin1_choices[dlg.html.highlightPin1.Selection]
225
+ self.board_rotation = dlg.html.boardRotationSlider.Value
226
+ self.offset_back_rotation = \
227
+ dlg.html.offsetBackRotationCheckbox.IsChecked()
228
+ self.checkboxes = dlg.html.bomCheckboxesCtrl.Value
229
+ self.bom_view = self.bom_view_choices[dlg.html.bomDefaultView.Selection]
230
+ self.layer_view = self.layer_view_choices[
231
+ dlg.html.layerDefaultView.Selection]
232
+ self.compression = dlg.html.compressionCheckbox.IsChecked()
233
+ self.open_browser = dlg.html.openBrowserCheckbox.IsChecked()
234
+
235
+ # General
236
+ self.bom_dest_dir = dlg.general.bomDirPicker.Path
237
+ self.bom_name_format = dlg.general.fileNameFormatTextControl.Value
238
+ self.component_sort_order = dlg.general.componentSortOrderBox.GetItems()
239
+ self.component_blacklist = dlg.general.blacklistBox.GetItems()
240
+ self.blacklist_virtual = \
241
+ dlg.general.blacklistVirtualCheckbox.IsChecked()
242
+ self.blacklist_empty_val = \
243
+ dlg.general.blacklistEmptyValCheckbox.IsChecked()
244
+ self.include_tracks = dlg.general.includeTracksCheckbox.IsChecked()
245
+ self.include_nets = dlg.general.includeNetsCheckbox.IsChecked()
246
+
247
+ # Fields
248
+ self.extra_data_file = dlg.fields.extraDataFilePicker.Path
249
+ self.show_fields = dlg.fields.GetShowFields()
250
+ self.group_fields = dlg.fields.GetGroupFields()
251
+ self.normalize_field_case = dlg.fields.normalizeCaseCheckbox.Value
252
+ self.board_variant_field = dlg.fields.boardVariantFieldBox.Value
253
+ if self.board_variant_field == dlg.fields.NONE_STRING:
254
+ self.board_variant_field = ''
255
+ self.board_variant_whitelist = list(
256
+ dlg.fields.boardVariantWhitelist.GetCheckedStrings())
257
+ self.board_variant_blacklist = list(
258
+ dlg.fields.boardVariantBlacklist.GetCheckedStrings())
259
+ self.dnp_field = dlg.fields.dnpFieldBox.Value
260
+ if self.dnp_field == dlg.fields.NONE_STRING:
261
+ self.dnp_field = ''
262
+
263
+ def transfer_to_dialog(self, dlg):
264
+ # type: (dialog.settings_dialog.SettingsDialogPanel) -> None
265
+ # Html
266
+ dlg.html.darkModeCheckbox.Value = self.dark_mode
267
+ dlg.html.showPadsCheckbox.Value = self.show_pads
268
+ dlg.html.showFabricationCheckbox.Value = self.show_fabrication
269
+ dlg.html.showSilkscreenCheckbox.Value = self.show_silkscreen
270
+ dlg.html.highlightPin1.Selection = 0
271
+ if self.highlight_pin1 in self.highlight_pin1_choices:
272
+ dlg.html.highlightPin1.Selection = \
273
+ self.highlight_pin1_choices.index(self.highlight_pin1)
274
+ dlg.html.continuousRedrawCheckbox.value = self.redraw_on_drag
275
+ dlg.html.boardRotationSlider.Value = self.board_rotation
276
+ dlg.html.offsetBackRotationCheckbox.Value = self.offset_back_rotation
277
+ dlg.html.bomCheckboxesCtrl.Value = self.checkboxes
278
+ dlg.html.bomDefaultView.Selection = self.bom_view_choices.index(
279
+ self.bom_view)
280
+ dlg.html.layerDefaultView.Selection = self.layer_view_choices.index(
281
+ self.layer_view)
282
+ dlg.html.compressionCheckbox.Value = self.compression
283
+ dlg.html.openBrowserCheckbox.Value = self.open_browser
284
+
285
+ # General
286
+ import os.path
287
+ if os.path.isabs(self.bom_dest_dir):
288
+ dlg.general.bomDirPicker.Path = self.bom_dest_dir
289
+ else:
290
+ dlg.general.bomDirPicker.Path = os.path.join(
291
+ self.netlist_initial_directory, self.bom_dest_dir)
292
+ dlg.general.fileNameFormatTextControl.Value = self.bom_name_format
293
+ dlg.general.componentSortOrderBox.SetItems(self.component_sort_order)
294
+ dlg.general.blacklistBox.SetItems(self.component_blacklist)
295
+ dlg.general.blacklistVirtualCheckbox.Value = self.blacklist_virtual
296
+ dlg.general.blacklistEmptyValCheckbox.Value = self.blacklist_empty_val
297
+ dlg.general.includeTracksCheckbox.Value = self.include_tracks
298
+ dlg.general.includeNetsCheckbox.Value = self.include_nets
299
+
300
+ # Fields
301
+ dlg.fields.extraDataFilePicker.SetInitialDirectory(
302
+ self.netlist_initial_directory)
303
+
304
+ def safe_set_checked_strings(clb, strings):
305
+ current = list(clb.GetStrings())
306
+ if current:
307
+ present_strings = [s for s in strings if s in current]
308
+ not_present_strings = [s for s in current if s not in strings]
309
+ clb.Clear()
310
+ clb.InsertItems(present_strings + not_present_strings, 0)
311
+ clb.SetCheckedStrings(present_strings)
312
+
313
+ dlg.fields.SetCheckedFields(self.show_fields, self.group_fields)
314
+ dlg.fields.normalizeCaseCheckbox.Value = self.normalize_field_case
315
+ dlg.fields.boardVariantFieldBox.Value = self.board_variant_field
316
+ dlg.fields.OnBoardVariantFieldChange(None)
317
+ safe_set_checked_strings(dlg.fields.boardVariantWhitelist,
318
+ self.board_variant_whitelist)
319
+ safe_set_checked_strings(dlg.fields.boardVariantBlacklist,
320
+ self.board_variant_blacklist)
321
+ dlg.fields.dnpFieldBox.Value = self.dnp_field
322
+
323
+ dlg.finish_init()
324
+
325
+ @classmethod
326
+ def add_options(cls, parser, version):
327
+ # type: (argparse.ArgumentParser, str) -> None
328
+ parser.add_argument('--show-dialog', action='store_true',
329
+ help='Shows config dialog. All other flags '
330
+ 'will be ignored.')
331
+ parser.add_argument('--version', action='version', version=version)
332
+ # Html
333
+ parser.add_argument('--dark-mode', help='Default to dark mode.',
334
+ action='store_true')
335
+ parser.add_argument('--hide-pads',
336
+ help='Hide footprint pads by default.',
337
+ action='store_true')
338
+ parser.add_argument('--show-fabrication',
339
+ help='Show fabrication layer by default.',
340
+ action='store_true')
341
+ parser.add_argument('--hide-silkscreen',
342
+ help='Hide silkscreen by default.',
343
+ action='store_true')
344
+ parser.add_argument('--highlight-pin1',
345
+ default=cls.highlight_pin1_choices[0],
346
+ const=cls.highlight_pin1_choices[1],
347
+ choices=cls.highlight_pin1_choices,
348
+ nargs='?',
349
+ help='Highlight first pin.')
350
+ parser.add_argument('--no-redraw-on-drag',
351
+ help='Do not redraw pcb on drag by default.',
352
+ action='store_true')
353
+ parser.add_argument('--board-rotation', type=int,
354
+ default=cls.board_rotation * 5,
355
+ help='Board rotation in degrees (-180 to 180). '
356
+ 'Will be rounded to multiple of 5.')
357
+ parser.add_argument('--offset-back-rotation',
358
+ help='Offset the back of the pcb by 180 degrees',
359
+ action='store_true')
360
+ parser.add_argument('--checkboxes',
361
+ default=cls.checkboxes,
362
+ help='Comma separated list of checkbox columns.')
363
+ parser.add_argument('--bom-view', default=cls.bom_view,
364
+ choices=cls.bom_view_choices,
365
+ help='Default BOM view.')
366
+ parser.add_argument('--layer-view', default=cls.layer_view,
367
+ choices=cls.layer_view_choices,
368
+ help='Default layer view.')
369
+ parser.add_argument('--no-compression',
370
+ help='Disable compression of pcb data.',
371
+ action='store_true')
372
+ parser.add_argument('--no-browser', help='Do not launch browser.',
373
+ action='store_true')
374
+
375
+ # General
376
+ parser.add_argument('--dest-dir', default=cls.bom_dest_dir,
377
+ help='Destination directory for bom file '
378
+ 'relative to pcb file directory.')
379
+ parser.add_argument('--name-format', default=cls.bom_name_format,
380
+ help=cls.FILE_NAME_FORMAT_HINT.replace('%', '%%'))
381
+ parser.add_argument('--include-tracks', action='store_true',
382
+ help='Include track/zone information in output. '
383
+ 'F.Cu and B.Cu layers only.')
384
+ parser.add_argument('--include-nets', action='store_true',
385
+ help='Include netlist information in output.')
386
+ parser.add_argument('--sort-order',
387
+ help='Default sort order for components. '
388
+ 'Must contain "~" once.',
389
+ default=','.join(cls.component_sort_order))
390
+ parser.add_argument('--blacklist',
391
+ default=','.join(cls.component_blacklist),
392
+ help='List of comma separated blacklisted '
393
+ 'components or prefixes with *. '
394
+ 'E.g. "X1,MH*"')
395
+ parser.add_argument('--no-blacklist-virtual', action='store_true',
396
+ help='Do not blacklist virtual components.')
397
+ parser.add_argument('--blacklist-empty-val', action='store_true',
398
+ help='Blacklist components with empty value.')
399
+
400
+ # Fields section
401
+ parser.add_argument('--netlist-file',
402
+ help='(Deprecated) Path to netlist or xml file.')
403
+ parser.add_argument('--extra-data-file',
404
+ help='Path to netlist or xml file.')
405
+ parser.add_argument('--extra-fields',
406
+ help='Passing --extra-fields "X,Y" is a shortcut '
407
+ 'for --show-fields and --group-fields '
408
+ 'with values "Value,Footprint,X,Y"')
409
+ parser.add_argument('--show-fields',
410
+ default=cls._join(cls.show_fields),
411
+ help='List of fields to show in the BOM.')
412
+ parser.add_argument('--group-fields',
413
+ default=cls._join(cls.group_fields),
414
+ help='Fields that components will be grouped by.')
415
+ parser.add_argument('--normalize-field-case',
416
+ help='Normalize extra field name case. E.g. "MPN" '
417
+ ', "mpn" will be considered the same field.',
418
+ action='store_true')
419
+ parser.add_argument('--variant-field',
420
+ help='Name of the extra field that stores board '
421
+ 'variant for component.')
422
+ parser.add_argument('--variants-whitelist', default='',
423
+ help='List of board variants to '
424
+ 'include in the BOM. Use "<empty>" to denote '
425
+ 'not set or empty value.')
426
+ parser.add_argument('--variants-blacklist', default='',
427
+ help='List of board variants to '
428
+ 'exclude from the BOM. Use "<empty>" to denote '
429
+ 'not set or empty value.')
430
+ parser.add_argument('--dnp-field', default=cls.dnp_field,
431
+ help='Name of the extra field that indicates '
432
+ 'do not populate status. Components with '
433
+ 'this field not empty will be excluded.')
434
+
435
+ def set_from_args(self, args):
436
+ # type: (argparse.Namespace) -> None
437
+ import math
438
+
439
+ # Html
440
+ self.dark_mode = args.dark_mode
441
+ self.show_pads = not args.hide_pads
442
+ self.show_fabrication = args.show_fabrication
443
+ self.show_silkscreen = not args.hide_silkscreen
444
+ self.highlight_pin1 = args.highlight_pin1
445
+ self.redraw_on_drag = not args.no_redraw_on_drag
446
+ self.board_rotation = math.fmod(args.board_rotation // 5, 37)
447
+ self.offset_back_rotation = args.offset_back_rotation
448
+ self.checkboxes = args.checkboxes
449
+ self.bom_view = args.bom_view
450
+ self.layer_view = args.layer_view
451
+ self.compression = not args.no_compression
452
+ self.open_browser = not args.no_browser
453
+
454
+ # General
455
+ self.bom_dest_dir = args.dest_dir
456
+ self.bom_name_format = args.name_format
457
+ self.component_sort_order = self._split(args.sort_order)
458
+ self.component_blacklist = self._split(args.blacklist)
459
+ self.blacklist_virtual = not args.no_blacklist_virtual
460
+ self.blacklist_empty_val = args.blacklist_empty_val
461
+ self.include_tracks = args.include_tracks
462
+ self.include_nets = args.include_nets
463
+
464
+ # Fields
465
+ self.extra_data_file = args.extra_data_file or args.netlist_file
466
+ if args.extra_fields is not None:
467
+ self.show_fields = self.default_show_group_fields + \
468
+ self._split(args.extra_fields)
469
+ self.group_fields = self.show_fields
470
+ else:
471
+ self.show_fields = self._split(args.show_fields)
472
+ self.group_fields = self._split(args.group_fields)
473
+ self.normalize_field_case = args.normalize_field_case
474
+ self.board_variant_field = args.variant_field
475
+ self.board_variant_whitelist = self._split(args.variants_whitelist)
476
+ self.board_variant_blacklist = self._split(args.variants_blacklist)
477
+ self.dnp_field = args.dnp_field
478
+
479
+ def get_html_config(self):
480
+ import json
481
+ d = {f: getattr(self, f) for f in self.html_config_fields}
482
+ d["fields"] = self.show_fields
483
+ return json.dumps(d)
@@ -0,0 +1,52 @@
1
+ from .newstroke_font import NEWSTROKE_FONT
2
+
3
+
4
+ class FontParser:
5
+ STROKE_FONT_SCALE = 1.0 / 21.0
6
+ FONT_OFFSET = -10
7
+
8
+ def __init__(self):
9
+ self.parsed_font = {}
10
+
11
+ def parse_font_char(self, chr):
12
+ lines = []
13
+ line = []
14
+ glyph_x = 0
15
+ index = ord(chr) - ord(' ')
16
+ if index >= len(NEWSTROKE_FONT):
17
+ index = ord('?') - ord(' ')
18
+ glyph_str = NEWSTROKE_FONT[index]
19
+ for i in range(0, len(glyph_str), 2):
20
+ coord = glyph_str[i:i + 2]
21
+
22
+ # The first two values contain the width of the char
23
+ if i < 2:
24
+ glyph_x = (ord(coord[0]) - ord('R')) * self.STROKE_FONT_SCALE
25
+ glyph_width = (ord(coord[1]) - ord(coord[0])) * self.STROKE_FONT_SCALE
26
+ elif coord[0] == ' ' and coord[1] == 'R':
27
+ lines.append(line)
28
+ line = []
29
+ else:
30
+ line.append([
31
+ (ord(coord[0]) - ord('R')) * self.STROKE_FONT_SCALE - glyph_x,
32
+ (ord(coord[1]) - ord('R') + self.FONT_OFFSET) * self.STROKE_FONT_SCALE
33
+ ])
34
+
35
+ if len(line) > 0:
36
+ lines.append(line)
37
+
38
+ return {
39
+ 'w': glyph_width,
40
+ 'l': lines
41
+ }
42
+
43
+ def parse_font_for_string(self, s):
44
+ for c in s:
45
+ if c == '\t' and ' ' not in self.parsed_font:
46
+ # tabs rely on space char to calculate offset
47
+ self.parsed_font[' '] = self.parse_font_char(' ')
48
+ if c not in self.parsed_font and ord(c) >= ord(' '):
49
+ self.parsed_font[c] = self.parse_font_char(c)
50
+
51
+ def get_parsed_font(self):
52
+ return self.parsed_font