Advertisement
Guest User

Untitled

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