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.
- InteractiveHtmlBom/.gitattributes +1 -0
- InteractiveHtmlBom/Run.bat +73 -0
- InteractiveHtmlBom/__init__.py +58 -0
- InteractiveHtmlBom/core/__init__.py +0 -0
- InteractiveHtmlBom/core/config.py +483 -0
- InteractiveHtmlBom/core/fontparser.py +52 -0
- InteractiveHtmlBom/core/ibom.py +365 -0
- InteractiveHtmlBom/core/lzstring.py +304 -0
- InteractiveHtmlBom/core/newstroke_font.py +11361 -0
- InteractiveHtmlBom/core/units.py +192 -0
- InteractiveHtmlBom/dialog/__init__.py +1 -0
- InteractiveHtmlBom/dialog/bitmaps/btn-arrow-down.png +0 -0
- InteractiveHtmlBom/dialog/bitmaps/btn-arrow-up.png +0 -0
- InteractiveHtmlBom/dialog/bitmaps/btn-minus.png +0 -0
- InteractiveHtmlBom/dialog/bitmaps/btn-plus.png +0 -0
- InteractiveHtmlBom/dialog/bitmaps/btn-question.png +0 -0
- InteractiveHtmlBom/dialog/dialog_base.py +578 -0
- InteractiveHtmlBom/dialog/settings_dialog.py +406 -0
- InteractiveHtmlBom/dialog_test.py +17 -0
- InteractiveHtmlBom/ecad/__init__.py +41 -0
- InteractiveHtmlBom/ecad/common.py +251 -0
- InteractiveHtmlBom/ecad/easyeda.py +462 -0
- InteractiveHtmlBom/ecad/fusion_eagle.py +920 -0
- InteractiveHtmlBom/ecad/genericjson.py +167 -0
- InteractiveHtmlBom/ecad/kicad.py +843 -0
- InteractiveHtmlBom/ecad/kicad_extra/__init__.py +59 -0
- InteractiveHtmlBom/ecad/kicad_extra/netlistparser.py +59 -0
- InteractiveHtmlBom/ecad/kicad_extra/parser_base.py +26 -0
- InteractiveHtmlBom/ecad/kicad_extra/sexpressions.py +32 -0
- InteractiveHtmlBom/ecad/kicad_extra/xmlparser.py +42 -0
- InteractiveHtmlBom/ecad/schema/genericjsonpcbdata_v1.schema +638 -0
- InteractiveHtmlBom/ecad/svgpath.py +538 -0
- InteractiveHtmlBom/errors.py +16 -0
- InteractiveHtmlBom/generate_interactive_bom.py +84 -0
- InteractiveHtmlBom/i18n/language_en.bat +12 -0
- InteractiveHtmlBom/i18n/language_zh.bat +17 -0
- InteractiveHtmlBom/icon.png +0 -0
- InteractiveHtmlBom/version.py +29 -0
- InteractiveHtmlBom/web/ibom.css +887 -0
- InteractiveHtmlBom/web/ibom.html +337 -0
- InteractiveHtmlBom/web/ibom.js +1294 -0
- InteractiveHtmlBom/web/lz-string.js +10 -0
- InteractiveHtmlBom/web/pep.js +43 -0
- InteractiveHtmlBom/web/render.js +1075 -0
- InteractiveHtmlBom/web/split.js +6 -0
- InteractiveHtmlBom/web/table-util.js +371 -0
- InteractiveHtmlBom/web/util.js +638 -0
- interactivehtmlbom-2.8.1.dist-info/METADATA +67 -0
- interactivehtmlbom-2.8.1.dist-info/RECORD +52 -0
- interactivehtmlbom-2.8.1.dist-info/WHEEL +4 -0
- interactivehtmlbom-2.8.1.dist-info/entry_points.txt +2 -0
- 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
|