Skip to content

Instantly share code, notes, and snippets.

@projectgus
Last active October 17, 2018 23:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save projectgus/cbf15d2e9eab881e653a47ca2d2e0ea9 to your computer and use it in GitHub Desktop.
Save projectgus/cbf15d2e9eab881e653a47ca2d2e0ea9 to your computer and use it in GitHub Desktop.
Hacky way to get a BoM text file from a Kicad Netlist
#!/usr/bin/env python3
#
# Quick and rough KiCAD Netlist to text & CSV BoM file converter
#
# Usage: net2bom.py <project.net>
#
# Outputs:
# - project_bom.txt
# - project_bom.csv
# - project_bom_grouped.csv (has all of same value/footprint grouped on the same line)
#
# Note: Consider using KiCad's built-in tools (which are better than when I wrote this script) or
# use https://github.com/openscopeproject/InteractiveHtmlBom for 1000x more features
#
import csv,sys,os,collections,itertools, re
components = []
component = None
item = 1
FIELDS = {
"item" : "item",
"Reference" : "ref",
"ValeurCmp" : "value",
"IdModule" : "footprint"
}
netfile = sys.argv[1]
bomfile = os.path.splitext(os.path.basename(netfile))[0] + "_bom.txt"
csvfile = os.path.splitext(os.path.basename(netfile))[0] + "_bom.csv"
grouped_bomfile = os.path.splitext(os.path.basename(netfile))[0] + "_bom_grouped.csv"
# worst ever, just parsing line by line instead of parsing s-expressions...
with open(netfile) as f:
for line in f:
if "comp (ref" in line:
if component is not None:
components.append(component)
ref = re.search(r"ref (.+)\)",line).group(1)
component = { "ref" : ref, "item" : int(item) }
item += 1
elif component is not None:
if "value" in line and not "value" in component:
component["value"] = re.search(r'value "?(.+?)"?\)',line).group(1)
elif "footprint" in line and not "footprint" in component and not "footprints" in line:
component["footprint"] = re.search(r'footprint "?(.+?)"?\)',line).group(1)
if component is not None:
components.append(component)
components = [ c for c in components if c["value"] != "SJ" ]
# remove library prefixes from footprint names, clean up stray commas
for component in components:
component["footprint"] = component.get("footprint", "").split(":")[-1]
component["value"] = component.get("value", "").replace(",", " ")
# convert purely numeric footprints to give an indicator of type
for component in components:
try:
if not int(component["footprint"]):
continue
if component["ref"].startswith("C"):
component["footprint"] = "CAP" + component["footprint"]
elif component["ref"].startswith("R"):
component["footprint"] = "RES" + component["footprint"]
elif component["ref"].startswith("L"):
component["footprint"] = "IND" + component["footprint"]
except ValueError:
continue
#%(item)8s
FORMAT="%(ref)8s%(value)20s%(footprint)20s\n"
CSV_FORMAT="%(ref)s,%(value)s,%(footprint)s\n"
# write plaintext BOM & CSV BOM
with open(bomfile, "w") as f:
with open(csvfile, "w") as csv:
header = dict((x,x) for x in [ "item","ref","value","footprint", "notes" ])
f.write(FORMAT % header)
csv.write(",".join(header) + "\n")
for component in components:
component["item"] = item
item += 1
f.write(FORMAT % component)
csv.write(CSV_FORMAT % component)
# create grouped BO
grouping_key = lambda i: (i["footprint"],i["value"])
grouped = itertools.groupby(sorted(components, key=grouping_key), key=grouping_key)
result = []
for k,g in grouped:
g = list(g)
result.append((" ".join(i["ref"] for i in g), str(len(g)), k[1], k[0]))
with open(grouped_bomfile, "w") as f:
f.write("Refs,Quantity,Value,Footprint\n")
for line in sorted(result):
f.write(",".join(line) + "\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment