Created
December 2, 2021 20:46
-
-
Save gcormier/61addd28226bfd4ed6f645f312c3f13a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
@package | |
Output: CSV (comma-separated) | |
Grouped By: Value | |
Sorted By: Ref | |
Fields: Comment,Designator,Footprint,LCSC | |
Outputs the BOM for JLCPCB. Run this AFTER generating your POS file for SMT assembly. | |
Attempts to find and modify POS header to JLC format in the same output folder. | |
REQUIRES kicad_utils.py and kicad_netlist_reader.py in the same director as this script | |
https://gitlab.com/kicad/code/kicad/-/tree/master/eeschema/plugins/python_scripts | |
Command line: | |
python "pathToFile/bom_csv_grouped_by_value.py" "%I" "%O.csv" | |
Using a gerber output subfolder: | |
python "pathToFile/bom_csv_grouped_by_value.py" "%I" "%P/gerber/%B-bom.csv" | |
""" | |
from __future__ import print_function | |
# Import the KiCad python helper module and the csv formatter | |
import kicad_netlist_reader | |
import kicad_utils | |
import csv | |
import sys | |
import os | |
# A helper function to convert a UTF8/Unicode/locale string read in netlist | |
# for python2 or python3 (Windows/unix) | |
def fromNetlistText( aText ): | |
currpage = sys.stdout.encoding #the current code page. can be none | |
if currpage is None: | |
return aText | |
if currpage != 'utf-8': | |
try: | |
return aText.encode('utf-8').decode(currpage) | |
except UnicodeDecodeError: | |
return aText | |
else: | |
return aText | |
def myEqu(self, other): | |
"""myEqu is a more advanced equivalence function for components which is | |
used by component grouping. Normal operation is to group components based | |
on their value and footprint. | |
In this example of a custom equivalency operator we compare the | |
value, the part name and the footprint. | |
""" | |
result = True | |
if self.getValue() != other.getValue(): | |
result = False | |
elif self.getPartName() != other.getPartName(): | |
result = False | |
elif self.getFootprint() != other.getFootprint(): | |
result = False | |
return result | |
# Override the component equivalence operator - it is important to do this | |
# before loading the netlist, otherwise all components will have the original | |
# equivalency operator. | |
kicad_netlist_reader.comp.__eq__ = myEqu | |
if len(sys.argv) != 3: | |
print("Usage ", __file__, "<generic_netlist.xml> <output.csv>", file=sys.stderr) | |
sys.exit(1) | |
# Generate an instance of a generic netlist, and load the netlist tree from | |
# the command line option. If the file doesn't exist, execution will stop | |
net = kicad_netlist_reader.netlist(sys.argv[1]) | |
# Open a file to write to, if the file cannot be opened output to stdout | |
# instead | |
try: | |
f = kicad_utils.open_file_write(sys.argv[2], 'w') | |
except IOError: | |
e = "Can't open output file for writing: " + sys.argv[2] | |
print( __file__, ":", e, sys.stderr ) | |
f = sys.stdout | |
# subset the components to those wanted in the BOM, controlled | |
# by <configure> block in kicad_netlist_reader.py | |
components = net.getInterestingComponents() | |
compfields = net.gatherComponentFieldUnion(components) | |
partfields = net.gatherLibPartFieldUnion() | |
# remove Reference, Value, Datasheet, and Footprint, they will come from 'columns' below | |
partfields -= set( ['Reference', 'Value', 'Datasheet', 'Footprint'] ) | |
columnset = compfields | partfields # union | |
# prepend an initial 'hard coded' list and put the enchillada into list 'columns' | |
columns = ['Comment', 'Designator', 'Footprint', 'LCSC'] | |
# Create a new csv writer object to use as the output formatter | |
out = csv.writer( f, lineterminator='\n', delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL ) | |
# override csv.writer's writerow() to support encoding conversion (initial encoding is utf8): | |
def writerow( acsvwriter, columns ): | |
utf8row = [] | |
for col in columns: | |
utf8row.append( fromNetlistText( str(col) ) ) | |
acsvwriter.writerow( utf8row ) | |
writerow( out, columns ) | |
# Output all the interesting components individually first: | |
row = [] | |
for c in components: | |
del row[:] | |
row.append('') # item is blank in individual table | |
row.append('') # Qty is always 1, why print it | |
row.append( c.getRef() ) # Reference | |
row.append( c.getValue() ) # Value | |
row.append( c.getLibName() + ":" + c.getPartName() ) # LibPart | |
#row.append( c.getDescription() ) | |
row.append( c.getFootprint() ) | |
row.append( c.getDatasheet() ) | |
# from column 7 upwards, use the fieldnames to grab the data | |
for field in columns[7:]: | |
row.append( c.getField( field ) ); | |
# Get all of the components in groups of matching parts + values | |
# (see kicad_netlist_reader.py) | |
grouped = net.groupComponents(components) | |
# Output component information organized by group, aka as collated: | |
item = 0 | |
for group in grouped: | |
del row[:] | |
refs = "" | |
# Add the reference of every component in the group and keep a reference | |
# to the component so that the other data can be filled in once per group | |
for component in group: | |
if len(refs) > 0: | |
refs += ", " | |
refs += component.getRef() | |
c = component | |
# Fill in the component groups common data | |
# columns = ['Item', 'Qty', 'Reference(s)', 'Value', 'LibPart', 'Footprint', 'Datasheet'] + sorted(list(columnset)) | |
item += 1 | |
row.append( c.getValue() ) | |
row.append( refs ); | |
row.append( net.getGroupFootprint(group) ) | |
row.append( c.getField('LCSC') ) | |
# from column 7 upwards, use the fieldnames to grab the data | |
for field in columns[7:]: | |
row.append( net.getGroupField(group, field) ); | |
writerow( out, row ) | |
f.close() | |
# Try to see if POS files exist. If so, modify them to the JLCPCB header | |
# Looks for a few permutations defined in fileOptions which depends if user selected a single POS | |
# or if they select seperate for layers | |
inputFile = os.path.split(sys.argv[1])[1] | |
projectName = os.path.splitext(inputFile)[0] | |
destination = sys.argv[2] | |
destinationPath = os.path.dirname(destination) | |
print("Project name detected as :", projectName) | |
fileOptions = [ '-all-pos', '-top-pos', '-bottom-pos'] | |
for possibility in fileOptions: | |
positionFile = destinationPath + "/" + projectName + possibility + ".csv" | |
print("\nChecking for existance of ", positionFile) | |
if os.path.isfile(positionFile): | |
print("Modifying header of ", positionFile) | |
with open(positionFile) as f: | |
lines = f.readlines() | |
lines[0] = "Designator,Val,Package,Mid X,Mid Y,Rotation,Layer\n" | |
with open(positionFile, "w") as f: | |
f.writelines(lines) | |
else: | |
print("Not found (" + positionFile + ")") | |
lines 113 to 129 are useless, remove them
then fix line 138 " raw = [] "
What is the point of counting 'item' ? It serves nothing. (lines 136, 151)
Lines 156..158 also useless !
Line 186 brakes what JLC is expecting ??????!!!!!!!
Designator, ~~Val,Package, ~~ Mid X,Mid Y,Rotation,Layer
I got here from https://www.youtube.com/watch?v=v9FglN80EMM then https://jlcpcb.com/help/article/81-How-to-generate-the-BOM-and-Centroid-file-from-KiCAD then https://gist.github.com/arturo182/a8c4a4b96907cfccf616a1edb59d0389
What I did not get anywhere was an idea how to install and how to use this python file.
I am using Windows 10.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Any chance you can get together with this dev? because i like the idea of this just being a plugin instead of running command line scripts. https://github.com/bennymeg/JLC-Plugin-for-KiCad