|
| 1 | +# |
| 2 | +# Example python script to generate a BOM from a KiCad generic netlist |
| 3 | +# |
| 4 | +# Example: Sorted and Grouped CSV BOM |
| 5 | +# |
| 6 | + |
| 7 | +""" |
| 8 | + @package |
| 9 | + Output: CSV (comma-separated) |
| 10 | + Grouped By: Value, Symbol Name, Footprint, DNP |
| 11 | + Sorted By: Ref |
| 12 | + Fields: Item, Qty, Reference(s), Value, LibPart, Footprint, Datasheet, DNP, all additional symbol fields |
| 13 | +
|
| 14 | + Outputs ungrouped components first, then outputs grouped components. |
| 15 | +
|
| 16 | + Command line: |
| 17 | + python "pathToFile/bom_csv_grouped_by_value.py" "%I" "%O.csv" |
| 18 | +""" |
| 19 | + |
| 20 | +from __future__ import print_function |
| 21 | + |
| 22 | +# Import the KiCad python helper module and the csv formatter |
| 23 | +import kicad_netlist_reader |
| 24 | +import kicad_utils |
| 25 | +import csv |
| 26 | +import sys |
| 27 | + |
| 28 | +# A helper function to filter/convert a string read in netlist |
| 29 | +#currently: do nothing |
| 30 | +def fromNetlistText( aText ): |
| 31 | + return aText |
| 32 | + |
| 33 | +def myEqu(self, other): |
| 34 | + """myEqu is a more advanced equivalence function for components which is |
| 35 | + used by component grouping. Normal operation is to group components based |
| 36 | + on their value and footprint. |
| 37 | +
|
| 38 | + In this example of a custom equivalency operator we compare the |
| 39 | + value, the part name and the footprint. |
| 40 | + """ |
| 41 | + result = True |
| 42 | + if self.getValue() != other.getValue(): |
| 43 | + result = False |
| 44 | + elif self.getPartName() != other.getPartName(): |
| 45 | + result = False |
| 46 | + elif self.getFootprint() != other.getFootprint(): |
| 47 | + result = False |
| 48 | + elif self.getDNP() != other.getDNP(): |
| 49 | + result = False |
| 50 | + |
| 51 | + return result |
| 52 | + |
| 53 | +# Override the component equivalence operator - it is important to do this |
| 54 | +# before loading the netlist, otherwise all components will have the original |
| 55 | +# equivalency operator. |
| 56 | +kicad_netlist_reader.comp.__eq__ = myEqu |
| 57 | + |
| 58 | +if len(sys.argv) != 3: |
| 59 | + print("Usage ", __file__, "<generic_netlist.xml> <output.csv>", file=sys.stderr) |
| 60 | + sys.exit(1) |
| 61 | + |
| 62 | + |
| 63 | +# Generate an instance of a generic netlist, and load the netlist tree from |
| 64 | +# the command line option. If the file doesn't exist, execution will stop |
| 65 | +net = kicad_netlist_reader.netlist(sys.argv[1]) |
| 66 | + |
| 67 | +# Open a file to write to, if the file cannot be opened output to stdout |
| 68 | +# instead |
| 69 | +canOpenFile = True |
| 70 | +try: |
| 71 | + f = kicad_utils.open_file_writeUTF8(sys.argv[2], 'w') |
| 72 | +except IOError: |
| 73 | + e = "Can't open output file for writing: " + sys.argv[2] |
| 74 | + print( __file__, ":", e, sys.stderr ) |
| 75 | + f = sys.stdout |
| 76 | + canOpenFile = False |
| 77 | + |
| 78 | +# subset the components to those wanted in the BOM, controlled |
| 79 | +# by <configure> block in kicad_netlist_reader.py |
| 80 | +components = net.getInterestingComponents( excludeBOM=True ) |
| 81 | + |
| 82 | +compfields = net.gatherComponentFieldUnion(components) |
| 83 | +partfields = net.gatherLibPartFieldUnion() |
| 84 | + |
| 85 | +# remove Reference, Value, Datasheet, and Footprint, they will come from 'columns' below |
| 86 | +partfields -= set( ['Reference', 'Value', 'Datasheet', 'Footprint'] ) |
| 87 | + |
| 88 | +columnset = compfields | partfields # union |
| 89 | + |
| 90 | +# prepend an initial 'hard coded' list and put the enchillada into list 'columns' |
| 91 | +hardcoded_columns = ['Item', 'Qty', 'Reference(s)', 'Value', 'LibPart', 'Footprint', 'Datasheet', 'DNP'] |
| 92 | +columns = hardcoded_columns + sorted(list(columnset)) |
| 93 | + |
| 94 | +# Create a new csv writer object to use as the output formatter |
| 95 | +out = csv.writer( f, lineterminator='\n', delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL ) |
| 96 | + |
| 97 | +# override csv.writer's writerow() to support encoding conversion (initial encoding is utf8): |
| 98 | +def writerow( acsvwriter, columns ): |
| 99 | + utf8row = [] |
| 100 | + for col in columns: |
| 101 | + utf8row.append( fromNetlistText( str(col) ) ) |
| 102 | + acsvwriter.writerow( utf8row ) |
| 103 | + |
| 104 | +# Output a set of rows as a header providing general information |
| 105 | +writerow( out, ['Source:', net.getSource()] ) |
| 106 | +writerow( out, ['Date:', net.getDate()] ) |
| 107 | +writerow( out, ['Tool:', net.getTool()] ) |
| 108 | +writerow( out, ['Generator:', sys.argv[0]] ) |
| 109 | +writerow( out, ['Component Count:', len(components)] ) |
| 110 | +writerow( out, [] ) |
| 111 | + |
| 112 | +writerow( out, ['Collated Components:'] ) |
| 113 | +writerow( out, [] ) # blank line |
| 114 | +writerow( out, columns ) # reuse same columns |
| 115 | + |
| 116 | +row=[] |
| 117 | + |
| 118 | +# Get all of the components in groups of matching parts + values |
| 119 | +# (see kicad_netlist_reader.py) |
| 120 | +grouped = net.groupComponents(components) |
| 121 | + |
| 122 | + |
| 123 | +# Output component information organized by group, aka as collated: |
| 124 | +item = 0 |
| 125 | +for group in grouped: |
| 126 | + del row[:] |
| 127 | + refs = "" |
| 128 | + refs_l = [] |
| 129 | + |
| 130 | + # Add the reference of every component in the group and keep a reference |
| 131 | + # to the component so that the other data can be filled in once per group |
| 132 | + for component in group: |
| 133 | + refs_l.append( component.getRef() ) |
| 134 | + c = component |
| 135 | + |
| 136 | + refs = ", ".join(refs_l) |
| 137 | + |
| 138 | + # Fill in the component groups common data |
| 139 | + # columns = ['Item', 'Qty', 'Reference(s)', 'Value', 'LibPart', 'Footprint', 'Datasheet', 'DNP'] + sorted(list(columnset)) |
| 140 | + item += 1 |
| 141 | + row.append( item ) |
| 142 | + row.append( len(group) ) |
| 143 | + row.append( refs ); |
| 144 | + row.append( c.getValue() ) |
| 145 | + row.append( c.getLibName() + ":" + c.getPartName() ) |
| 146 | + row.append( net.getGroupFootprint(group) ) |
| 147 | + row.append( net.getGroupDatasheet(group) ) |
| 148 | + row.append( c.getDNPString() ) |
| 149 | + |
| 150 | + # add user fields by name |
| 151 | + for field in columns[len(hardcoded_columns):]: |
| 152 | + row.append( net.getGroupField(group, field) ); |
| 153 | + |
| 154 | + writerow( out, row ) |
| 155 | + |
| 156 | +f.close() |
| 157 | + |
| 158 | +if not canOpenFile: |
| 159 | + sys.exit(1) |
0 commit comments