bom_csv_grouped_by_manufacture.py 6.1 KB

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