Advertisement
Guest User

Untitled

a guest
Mar 23rd, 2017
74
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.45 KB | None | 0 0
  1. #!/usr/bin/python2
  2. import re
  3. import os
  4. import numpy as np
  5. import pcbnew
  6.  
  7. import matplotlib.pyplot as plt
  8. from matplotlib.patches import Rectangle, Circle, Ellipse, FancyBboxPatch
  9.  
  10.  
  11. def create_board_figure(pcb, bom_row, layer=pcbnew.F_Cu):
  12. qty, value, footpr, highlight_refs = bom_row
  13.  
  14. plt.figure(figsize=(5.8, 8.2))
  15. ax = plt.subplot("111", aspect="equal")
  16.  
  17. color_pad1 = "lightgray"
  18. color_pad2 = "#AA0000"
  19. color_pad3 = "#CC4444"
  20. color_bbox1 = "None"
  21. color_bbox2 = "#E9AFAF"
  22.  
  23. # get board edges (assuming rectangular, axis aligned pcb)
  24. edge_coords = []
  25. for d in pcb.GetDrawings():
  26. if (d.GetLayer() == pcbnew.Edge_Cuts):
  27. edge_coords.append(d.GetStart())
  28. edge_coords.append(d.GetEnd())
  29. edge_coords = np.asarray(edge_coords) * 1e-6
  30. board_xmin, board_ymin = edge_coords.min(axis=0)
  31. board_xmax, board_ymax = edge_coords.max(axis=0)
  32.  
  33. # draw board edges
  34. rct = Rectangle((board_xmin, board_ymin), board_xmax - board_xmin, board_ymax - board_ymin, angle=0)
  35. rct.set_color("None")
  36. rct.set_edgecolor("black")
  37. rct.set_linewidth(3)
  38. ax.add_patch(rct)
  39.  
  40. # add title
  41. ax.text(board_xmin + .5 * (board_xmax - board_xmin), board_ymin - 0.5,
  42. "%dx %s, %s" % (qty, value, footpr), wrap=True,
  43. horizontalalignment='center', verticalalignment='bottom')\
  44.  
  45. # add ref list
  46. ax.text(board_xmin + .5 * (board_xmax - board_xmin), board_ymax + 0.5,
  47. ", ".join(highlight_refs), wrap=True,
  48. horizontalalignment='center', verticalalignment='top')
  49.  
  50. # draw parts
  51. for m in pcb.GetModules():
  52. if m.GetLayer() != layer:
  53. continue
  54. ref, center = m.GetReference(), np.asarray(m.GetCenter()) * 1e-6
  55. highlight = ref in highlight_refs
  56.  
  57. # bounding box
  58. mrect = m.GetFootprintRect()
  59. mrect_pos = np.asarray(mrect.GetPosition()) * 1e-6
  60. mrect_size = np.asarray(mrect.GetSize()) * 1e-6
  61. rct = Rectangle(mrect_pos, mrect_size[0], mrect_size[1])
  62. rct.set_color(color_bbox2 if highlight else color_bbox1)
  63. rct.set_zorder(-1)
  64. if highlight:
  65. rct.set_linewidth(.1)
  66. rct.set_edgecolor(color_pad2)
  67. ax.add_patch(rct)
  68.  
  69. # center marker
  70. if highlight:
  71. plt.plot(center[0], center[1], ".", markersize=mrect_size.min(), color=color_pad2)
  72.  
  73. # plot pads
  74. for p in m.Pads():
  75. pos = np.asarray(p.GetPosition()) * 1e-6
  76. size = np.asarray(p.GetSize()) * 1e-6 * .9
  77.  
  78. is_pin1 = p.GetPadName() == "1" or p.GetPadName() == "A1"
  79. shape = p.GetShape()
  80. offset = p.GetOffset() # TODO: check offset
  81.  
  82. # pad rect
  83. angle = p.GetOrientation() * 0.1
  84. cos, sin = np.cos(np.pi / 180. * angle), np.sin(np.pi / 180. * angle)
  85. dpos = np.dot([[cos, -sin], [sin, cos]], -.5 * size)
  86.  
  87. if shape == 1:
  88. rct = Rectangle(pos + dpos, size[0], size[1], angle=angle)
  89. elif shape == 2:
  90. rct = Rectangle(pos + dpos, size[0], size[1], angle=angle)
  91. elif shape == 0:
  92. rct = Ellipse(pos, size[0], size[1], angle=angle)
  93. else:
  94. print("Unsupported pad shape")
  95. continue
  96. rct.set_linewidth(0)
  97. rct.set_color(color_pad2 if highlight else color_pad1)
  98. rct.set_zorder(1)
  99. # highlight pin1
  100. if highlight and is_pin1:
  101. rct.set_color(color_pad3)
  102. rct.set_linewidth(.1)
  103. rct.set_edgecolor(color_pad2)
  104. ax.add_patch(rct)
  105.  
  106. plt.xlim(board_xmin, board_xmax)
  107. plt.ylim(board_ymax, board_ymin)
  108.  
  109. plt.axis('off')
  110.  
  111.  
  112. def natural_sort(l):
  113. """
  114. Natural sort for strings containing numbers
  115. """
  116. convert = lambda text: int(text) if text.isdigit() else text.lower()
  117. alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
  118. return sorted(l, key=alphanum_key)
  119.  
  120.  
  121. def generate_bom(pcb, filter_layer=None):
  122. """
  123. Generate BOM from pcb layout.
  124. :param filter_layer: include only parts for given layer
  125. :return: BOM table (qty, value, footprint, refs)
  126. """
  127. attrDict = {0: 'Normal',
  128. 1: 'Normal+Insert',
  129. 2: 'Virtual'}
  130.  
  131. # build grouped part list
  132. part_groups = {}
  133. for m in pcb.GetModules():
  134. # filter part by layer
  135. if filter_layer is not None and filter_layer != m.GetLayer():
  136. continue
  137. # group part refs by value and footprint
  138. value = m.GetValue()
  139. try:
  140. footpr = str(m.GetFPID().GetFootprintName())
  141. except:
  142. footpr = str(m.GetFPID().GetLibItemName())
  143. attr = m.GetAttributes()
  144. if attr in attrDict:
  145. attr = attrDict[attr]
  146. else:
  147. attr = str(attr)
  148.  
  149. group_key = (value, footpr, attr)
  150. refs = part_groups.setdefault(group_key, [])
  151. refs.append(m.GetReference())
  152.  
  153. # build bom table, sort refs
  154. bom_table = []
  155. for (value, footpr, attr), refs in part_groups.items():
  156. if attr == 'Virtual':
  157. continue
  158. line = (len(refs), value, footpr, natural_sort(refs))
  159. bom_table.append(line)
  160.  
  161. # sort table by reference prefix and quantity
  162. def sort_func(row):
  163. qty, _, _, rf = row
  164. ref_ord = {"R": 3, "C": 3, "L": 1, "D": 1, "J": -1, "P": -1}.get(rf[0][0], 0)
  165. return -ref_ord, -qty
  166. bom_table = sorted(bom_table, key=sort_func)
  167.  
  168. return bom_table
  169.  
  170.  
  171. if __name__ == "__main__":
  172. import argparse
  173. from matplotlib.backends.backend_pdf import PdfPages
  174.  
  175. parser = argparse.ArgumentParser(description='KiCad PCB pick and place assistant')
  176. parser.add_argument('file', type=str, help="KiCad PCB file")
  177. args = parser.parse_args()
  178.  
  179. # build BOM
  180. print("Loading %s" % args.file)
  181. pcb = pcbnew.LoadBoard(args.file)
  182.  
  183. for layer in (pcbnew.F_Cu, pcbnew.B_Cu):
  184. bom_table = generate_bom(pcb, filter_layer=layer)
  185.  
  186. # for each part group, print page to PDF
  187. fname_out = os.path.splitext(args.file)[0] + "_picknplace_{}.pdf".format(pcbnew.BOARD_GetStandardLayerName(layer))
  188. with PdfPages(fname_out) as pdf:
  189. for i, bom_row in enumerate(bom_table):
  190. print("Plotting page (%d/%d)" % (i+1, len(bom_table)))
  191. create_board_figure(pcb, bom_row, layer=layer)
  192. pdf.savefig()
  193. plt.close()
  194. print("Output written to %s" % fname_out)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement