Skip to content

Commit 1ac63fa

Browse files
committed
Initial commit
0 parents  commit 1ac63fa

24 files changed

+1481
-0
lines changed

.github/workflows/build_asset.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Build Asset
2+
on:
3+
workflow_dispatch:
4+
branches:
5+
6+
jobs:
7+
build:
8+
9+
name: Build
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@master
15+
16+
- name: Setup Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.10'
20+
21+
- name: Update zip
22+
run: |
23+
cd pcm
24+
python build.py
25+
cd build
26+
echo "ZIP_NAME=$(ls SparkFunKiCadBOMGenerator*.zip)" >> $GITHUB_ENV
27+
echo "PCM_NAME=$(ls SparkFunKiCadBOMGenerator*.zip | rev | cut -c 5- | rev)" >> $GITHUB_ENV
28+
29+
- name: Upload pcm build to action - avoid double-zip
30+
uses: actions/upload-artifact@v3
31+
with:
32+
name: ${{ env.PCM_NAME }}
33+
path: ./pcm/build/${{ env.PCM_NAME }}
34+
retention-days: 7
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build Asset for Release
2+
on:
3+
release:
4+
types: [published]
5+
6+
jobs:
7+
build:
8+
9+
name: Build
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@master
15+
16+
- name: Setup Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: '3.10'
20+
21+
- name: Update zip
22+
run: |
23+
cd pcm
24+
python build.py
25+
cd build
26+
echo "ZIP_NAME=$(ls SparkFunKiCadBOMGenerator*.zip)" >> $GITHUB_ENV
27+
echo "PCM_NAME=$(ls SparkFunKiCadBOMGenerator*.zip | rev | cut -c 5- | rev)" >> $GITHUB_ENV
28+
29+
- name: Publish release
30+
uses: softprops/action-gh-release@v1
31+
with:
32+
files: |
33+
./pcm/build/${{ env.ZIP_NAME }}

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
__pycache__/
2+
*.py[co]
3+
panel_config.json
4+
panelizer.log
5+
pcm/build/

LICENSE

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
**SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).**
3+
4+
The MIT License (MIT)
5+
6+
Copyright (c) 2023 SparkFun Electronics
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a copy
9+
of this software and associated documentation files (the "Software"), to deal
10+
in the Software without restriction, including without limitation the rights
11+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
copies of the Software, and to permit persons to whom the Software is
13+
furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included in all
16+
copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
SOFTWARE.

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SparkFun PCB BOM Generator plugin for KiCad 7
2+
3+
This plugin generates the Bill Of Materials (BOM) for a KiCad 7 PCB.
4+
5+
![BOM Generator](./img/bom_generator.png)
6+
7+
We've tried to keep this BOM generator simple and easy-to-use, while also including all the essential features of the original [SparkFun BOM Generator for Eagle](https://github.com/sparkfun/SparkFun_Eagle_Settings/blob/main/ulp/SparkFun-BOM_Generator.ulp). If you need a more comprehensive BOM generator, please check out [@openscopeproject](https://github.com/openscopeproject)'s [Interactive HTML BOM plugin for KiCad](https://github.com/openscopeproject/InteractiveHtmlBom).
8+
9+
## Limitations
10+
11+
This is a simple BOM generator and is very SparkFun-specific. It lists all board components on a PCB and identifies the ones with a SparkFun product ID (PROD_ID). It then generates a CSV list of those IDs which can be copied and pasted into our stock control system.
12+
13+
## Installation and Usage
14+
15+
Open the KiCad Plugin and Content Manager (PCM) from the main window and filter for `SparkFun BOM`.
16+
17+
To install manually, open the [GitHub Repo Releases page](https://github.com/sparkfun/SparkFun_KiCad_BOM_Generator/releases) and download the `SparkFunKiCadBOMGenerator-pcm.zip` file attached to the latest release. Then use the PCM _**Install from File...**_ option and select the .zip file to install it. For best results, **Uninstall** the previous version first, **Apply Pending Changes**, and then **Install from File...**.
18+
19+
![Install manually](./img/install_from_file.png)
20+
21+
The BOM generator plugin runs inside the KiCad PCB Editor window.
22+
23+
Click the icon to open the BOM generator GUI:
24+
25+
![Open BOM generator](./img/run_generator.png)
26+
27+
## License and Credits
28+
29+
The code for this plugin is licensed under the MIT license. Please see `LICENSE` for more info.
30+
31+
It is based heavily on [@openscopeproject](https://github.com/openscopeproject)'s [Interactive HTML BOM plugin for KiCad](https://github.com/openscopeproject/InteractiveHtmlBom).
32+
Special thanks to [@qu1ck](https://github.com/qu1ck) for the original BOM plugin and the many useful contributions on [KiCad Info](https://forum.kicad.info/).
33+
34+
## How It Works
35+
36+
The plugin GUI itself is designed with [wxFormBuilder](https://github.com/wxFormBuilder/wxFormBuilder/releases) and stored in `text_dialog.fbp`.
37+
Copy and paste the wx Python code from wxFormBuilder into `./SparkFunKiCadBOMGenerator/dialog/dialog_text_base.py`.
38+
39+
`.github/workflows/build_asset_release.yml` generates the .zip file containing the plugin Python code (`./plugins`), icon (`./resources`) and the Plugin and Content Manager (PCM) `metadata.json`. The workflow automatically attaches the zip file to each release as an asset. Edit `./SparkFunKiCadBOMGenerator/resource/_version.py` first and update the version number. `build.py` is called by the workflow, copies `metadata_template.json` into `metadata.json` and then updates it with the correct version and download URL. The version number is also added to the .zip filename. The PCM should automatically download and install new versions of the BOM generator for you.
40+
41+
- Your friends at SparkFun
42+

SparkFunKiCadBOMGenerator/__init__.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import os
2+
import sys
3+
import subprocess
4+
import threading
5+
import time
6+
7+
import wx
8+
import wx.aui
9+
from wx import FileConfig
10+
11+
import os
12+
from .util import add_paths
13+
dir_path = os.path.dirname(os.path.realpath(__file__))
14+
paths = dir_path
15+
#paths = [
16+
# os.path.join(dir_path, 'deps'),
17+
#]
18+
19+
def check_for_bom_button():
20+
# From Miles McCoo's blog
21+
# https://kicad.mmccoo.com/2017/03/05/adding-your-own-command-buttons-to-the-pcbnew-gui/
22+
def find_pcbnew_window():
23+
windows = wx.GetTopLevelWindows()
24+
pcbneww = [w for w in windows if "pcbnew" in w.GetTitle().lower()]
25+
if len(pcbneww) != 1:
26+
return None
27+
return pcbneww[0]
28+
29+
def callback(_):
30+
plugin.Run()
31+
32+
while not wx.GetApp():
33+
time.sleep(1)
34+
bm = wx.Bitmap(os.path.join(os.path.dirname(__file__),'icon.png'), wx.BITMAP_TYPE_PNG)
35+
button_wx_item_id = 0
36+
37+
from pcbnew import ID_H_TOOLBAR
38+
while True:
39+
time.sleep(1)
40+
pcbnew_window = find_pcbnew_window()
41+
if not pcbnew_window:
42+
continue
43+
44+
top_tb = pcbnew_window.FindWindowById(ID_H_TOOLBAR)
45+
if button_wx_item_id == 0 or not top_tb.FindTool(button_wx_item_id):
46+
top_tb.AddSeparator()
47+
button_wx_item_id = wx.NewId()
48+
top_tb.AddTool(button_wx_item_id, "SparkFunKiCadBomGenerator", bm,
49+
"SparkFun KiCad BOM Generator", wx.ITEM_NORMAL)
50+
top_tb.Bind(wx.EVT_TOOL, callback, id=button_wx_item_id)
51+
top_tb.Realize()
52+
53+
54+
try:
55+
with add_paths(paths):
56+
from .plugin import BomGeneratorPlugin
57+
plugin = BomGeneratorPlugin()
58+
plugin.register()
59+
except Exception as e:
60+
print(e)
61+
62+
# Add a button the hacky way if plugin button is not supported
63+
# in pcbnew, unless this is linux.
64+
if not plugin.pcbnew_icon_support and not sys.platform.startswith('linux'):
65+
t = threading.Thread(target=check_for_bom_button)
66+
t.daemon = True
67+
t.start()
68+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .dialog import Dialog
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
import wx
3+
4+
class DialogShim(wx.Dialog):
5+
def __init__(self, parent, **kwargs):
6+
try:
7+
wx.StockGDI._initStockObjects()
8+
except:
9+
pass
10+
11+
wx.Dialog.__init__(self, parent, **kwargs)
12+
13+
def SetSizeHints(self, a, b, c=None):
14+
if c is not None:
15+
super(wx.Dialog, self).SetSizeHints(a,b,c)
16+
else:
17+
super(wx.Dialog, self).SetSizeHints(a,b) # Was super(wx.Dialog, self).SetSizeHintsSz(a,b)
18+
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Subclass of dialog_text_base, which is generated by wxFormBuilder."""
2+
from logging import exception
3+
import os
4+
import wx
5+
import sys
6+
7+
from . import dialog_text_base
8+
9+
_APP_NAME = "SparkFun KiCad BOM Generator"
10+
11+
# sub folder for our resource files
12+
_RESOURCE_DIRECTORY = os.path.join("..", "resource")
13+
14+
#https://stackoverflow.com/a/50914550
15+
def resource_path(relative_path):
16+
""" Get absolute path to resource, works for dev and for PyInstaller """
17+
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
18+
return os.path.join(base_path, _RESOURCE_DIRECTORY, relative_path)
19+
20+
def get_version(rel_path: str) -> str:
21+
try:
22+
with open(resource_path(rel_path), encoding='utf-8') as fp:
23+
for line in fp.read().splitlines():
24+
if line.startswith("__version__"):
25+
delim = '"' if '"' in line else "'"
26+
return line.split(delim)[1]
27+
raise RuntimeError("Unable to find version string.")
28+
except:
29+
raise RuntimeError("Unable to find _version.py.")
30+
31+
_APP_VERSION = get_version("_version.py")
32+
33+
class Dialog(dialog_text_base.DIALOG_TEXT_BASE):
34+
35+
def __init__(self, parent):
36+
dialog_text_base.DIALOG_TEXT_BASE.__init__(self, parent)
37+
38+
# hack for some gtk themes that incorrectly calculate best size
39+
#best_size = self.BestSize
40+
#best_size.IncBy(dx=0, dy=30)
41+
#self.SetClientSize(best_size)
42+
43+
self.SetTitle(_APP_NAME + " - " + _APP_VERSION)
44+
45+
self.Bind(wx.EVT_SIZE, self.OnSize)
46+
47+
def OnSize(self, event):
48+
size = self.GetClientSize()
49+
50+
# Limit Refs column width
51+
52+
cols = self.bomGrid.GetNumberCols()
53+
colWidth = 0
54+
for col in range(cols - 1):
55+
colWidth += self.bomGrid.GetColSize(col)
56+
newWidth = size.width - (colWidth
57+
+ 50
58+
) # TODO: figure out how to do this properly!
59+
if newWidth < 0:
60+
newWidth = -1
61+
self.bomGrid.SetColSize(cols - 1, newWidth)
62+
63+
cols = self.nonBomGrid.GetNumberCols()
64+
colWidth = 0
65+
for col in range(cols - 1):
66+
colWidth += self.nonBomGrid.GetColSize(col)
67+
newWidth = size.width - (colWidth
68+
+ 50
69+
) # TODO: figure out how to do this properly!
70+
if newWidth < 0:
71+
newWidth = -1
72+
self.nonBomGrid.SetColSize(cols - 1, newWidth)
73+
74+
if event is not None:
75+
event.Skip()
76+
77+

0 commit comments

Comments
 (0)