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 11 Sorted By: Ref 12 Fields: Item, Qty, Reference(s), Value, LibPart, Footprint, Datasheet, 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 20from __future__ import print_function 21 22# Import the KiCad python helper module and the csv formatter 23import kicad_netlist_reader 24import kicad_utils 25import csv 26import sys 27 28# A helper function to convert a UTF8/Unicode/locale string read in netlist 29# for python2 or python3 (Windows/unix) 30def fromNetlistText( aText ): 31 currpage = sys.stdout.encoding #the current code page. can be none 32 if currpage is None: 33 return aText 34 if currpage != 'utf-8': 35 try: 36 return aText.encode('utf-8').decode(currpage) 37 except UnicodeDecodeError: 38 return aText 39 else: 40 return aText 41 42def myEqu(self, other): 43 """myEqu is a more advanced equivalence function for components which is 44 used by component grouping. Normal operation is to group components based 45 on their value and footprint. 46 47 In this example of a custom equivalency operator we compare the 48 value, the part name and the footprint. 49 """ 50 result = True 51 if self.getValue() != other.getValue(): 52 result = False 53 elif self.getPartName() != other.getPartName(): 54 result = False 55 elif self.getFootprint() != other.getFootprint(): 56 result = False 57 58 return result 59 60# Override the component equivalence operator - it is important to do this 61# before loading the netlist, otherwise all components will have the original 62# equivalency operator. 63kicad_netlist_reader.comp.__eq__ = myEqu 64 65if len(sys.argv) != 3: 66 print("Usage ", __file__, "<generic_netlist.xml> <output.csv>", file=sys.stderr) 67 sys.exit(1) 68 69 70# Generate an instance of a generic netlist, and load the netlist tree from 71# the command line option. If the file doesn't exist, execution will stop 72net = kicad_netlist_reader.netlist(sys.argv[1]) 73 74# Open a file to write to, if the file cannot be opened output to stdout 75# instead 76try: 77 f = kicad_utils.open_file_write(sys.argv[2], 'w') 78except IOError: 79 e = "Can't open output file for writing: " + sys.argv[2] 80 print( __file__, ":", e, sys.stderr ) 81 f = sys.stdout 82 83# subset the components to those wanted in the BOM, controlled 84# by <configure> block in kicad_netlist_reader.py 85components = net.getInterestingComponents() 86 87compfields = net.gatherComponentFieldUnion(components) 88partfields = net.gatherLibPartFieldUnion() 89 90# remove Reference, Value, Datasheet, and Footprint, they will come from 'columns' below 91partfields -= set( ['Reference', 'Value', 'Datasheet', 'Footprint'] ) 92 93columnset = compfields | partfields # union 94 95# prepend an initial 'hard coded' list and put the enchillada into list 'columns' 96columns = ['Item', 'Qty', 'Reference(s)', 'Value', 'LibPart', 'Footprint', 'Datasheet'] + sorted(list(columnset)) 97 98# Create a new csv writer object to use as the output formatter 99out = csv.writer( f, lineterminator='\n', delimiter=',', quotechar='\"', quoting=csv.QUOTE_ALL ) 100 101# override csv.writer's writerow() to support encoding conversion (initial encoding is utf8): 102def writerow( acsvwriter, columns ): 103 utf8row = [] 104 for col in columns: 105 utf8row.append( fromNetlistText( str(col) ) ) 106 acsvwriter.writerow( utf8row ) 107 108# Output a set of rows as a header providing general information 109writerow( out, ['Source:', net.getSource()] ) 110writerow( out, ['Date:', net.getDate()] ) 111writerow( out, ['Tool:', net.getTool()] ) 112writerow( out, ['Generator:', sys.argv[0]] ) 113writerow( out, ['Component Count:', len(components)] ) 114writerow( out, [] ) 115writerow( out, ['Individual Components:'] ) 116writerow( out, [] ) # blank line 117writerow( out, columns ) 118 119# Output all the interesting components individually first: 120row = [] 121for c in components: 122 del row[:] 123 row.append('') # item is blank in individual table 124 row.append('') # Qty is always 1, why print it 125 row.append( c.getRef() ) # Reference 126 row.append( c.getValue() ) # Value 127 row.append( c.getLibName() + ":" + c.getPartName() ) # LibPart 128 #row.append( c.getDescription() ) 129 row.append( c.getFootprint() ) 130 row.append( c.getDatasheet() ) 131 132 # from column 7 upwards, use the fieldnames to grab the data 133 for field in columns[7:]: 134 row.append( c.getField( field ) ); 135 136 writerow( out, row ) 137 138 139writerow( out, [] ) # blank line 140writerow( out, [] ) # blank line 141writerow( out, [] ) # blank line 142 143writerow( out, ['Collated Components:'] ) 144writerow( out, [] ) # blank line 145writerow( out, columns ) # reuse same columns 146 147 148 149# Get all of the components in groups of matching parts + values 150# (see kicad_netlist_reader.py) 151grouped = net.groupComponents(components) 152 153 154# Output component information organized by group, aka as collated: 155item = 0 156for group in grouped: 157 del row[:] 158 refs = "" 159 160 # Add the reference of every component in the group and keep a reference 161 # to the component so that the other data can be filled in once per group 162 for component in group: 163 if len(refs) > 0: 164 refs += ", " 165 refs += component.getRef() 166 c = component 167 168 # Fill in the component groups common data 169 # columns = ['Item', 'Qty', 'Reference(s)', 'Value', 'LibPart', 'Footprint', 'Datasheet'] + sorted(list(columnset)) 170 item += 1 171 row.append( item ) 172 row.append( len(group) ) 173 row.append( refs ); 174 row.append( c.getValue() ) 175 row.append( c.getLibName() + ":" + c.getPartName() ) 176 row.append( net.getGroupFootprint(group) ) 177 row.append( net.getGroupDatasheet(group) ) 178 179 # from column 7 upwards, use the fieldnames to grab the data 180 for field in columns[7:]: 181 row.append( net.getGroupField(group, field) ); 182 183 writerow( out, row ) 184 185f.close() 186