DblSidedTool.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. from PyQt4 import QtGui
  2. from GUIElements import RadioSet, EvalEntry, LengthEntry
  3. from FlatCAMTool import FlatCAMTool
  4. #from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon
  5. from FlatCAMObj import *
  6. from shapely.geometry import Point
  7. from shapely import affinity
  8. class DblSidedTool(FlatCAMTool):
  9. toolName = "Double-Sided PCB Tool"
  10. def __init__(self, app):
  11. FlatCAMTool.__init__(self, app)
  12. ## Title
  13. title_label = QtGui.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
  14. self.layout.addWidget(title_label)
  15. ## Form Layout
  16. form_layout = QtGui.QFormLayout()
  17. self.layout.addLayout(form_layout)
  18. ## Layer to mirror
  19. self.object_combo = QtGui.QComboBox()
  20. self.object_combo.setModel(self.app.collection.model)
  21. self.botlay_label = QtGui.QLabel("Bottom Layer:")
  22. self.botlay_label.setToolTip(
  23. "Layer to be mirrorer."
  24. )
  25. # form_layout.addRow("Bottom Layer:", self.object_combo)
  26. form_layout.addRow(self.botlay_label, self.object_combo)
  27. ## Axis
  28. self.mirror_axis = RadioSet([{'label': 'X', 'value': 'X'},
  29. {'label': 'Y', 'value': 'Y'}])
  30. self.mirax_label = QtGui.QLabel("Mirror Axis:")
  31. self.mirax_label.setToolTip(
  32. "Mirror vertically (X) or horizontally (Y)."
  33. )
  34. # form_layout.addRow("Mirror Axis:", self.mirror_axis)
  35. form_layout.addRow(self.mirax_label, self.mirror_axis)
  36. ## Axis Location
  37. self.axis_location = RadioSet([{'label': 'Point', 'value': 'point'},
  38. {'label': 'Box', 'value': 'box'}])
  39. self.axloc_label = QtGui.QLabel("Axis Location:")
  40. self.axloc_label.setToolTip(
  41. "The axis should pass through a <b>point</b> or cut "
  42. "a specified <b>box</b> (in a Geometry object) in "
  43. "the middle."
  44. )
  45. # form_layout.addRow("Axis Location:", self.axis_location)
  46. form_layout.addRow(self.axloc_label, self.axis_location)
  47. ## Point/Box
  48. self.point_box_container = QtGui.QVBoxLayout()
  49. self.pb_label = QtGui.QLabel("Point/Box:")
  50. self.pb_label.setToolTip(
  51. "Specify the point (x, y) through which the mirror axis "
  52. "passes or the Geometry object containing a rectangle "
  53. "that the mirror axis cuts in half."
  54. )
  55. # form_layout.addRow("Point/Box:", self.point_box_container)
  56. form_layout.addRow(self.pb_label, self.point_box_container)
  57. self.point = EvalEntry()
  58. self.point_box_container.addWidget(self.point)
  59. self.box_combo = QtGui.QComboBox()
  60. self.box_combo.setModel(self.app.collection.model)
  61. self.point_box_container.addWidget(self.box_combo)
  62. self.box_combo.hide()
  63. ## Alignment holes
  64. self.alignment_holes = EvalEntry()
  65. self.ah_label = QtGui.QLabel("Alignment Holes:")
  66. self.ah_label.setToolTip(
  67. "Alignment holes (x1, y1), (x2, y2), ... "
  68. "on one side of the mirror axis."
  69. )
  70. form_layout.addRow(self.ah_label, self.alignment_holes)
  71. ## Drill diameter for alignment holes
  72. self.drill_dia = LengthEntry()
  73. self.dd_label = QtGui.QLabel("Drill diam.:")
  74. self.dd_label.setToolTip(
  75. "Diameter of the drill for the "
  76. "alignment holes."
  77. )
  78. form_layout.addRow(self.dd_label, self.drill_dia)
  79. ## Buttons
  80. hlay = QtGui.QHBoxLayout()
  81. self.layout.addLayout(hlay)
  82. hlay.addStretch()
  83. self.create_alignment_hole_button = QtGui.QPushButton("Create Alignment Drill")
  84. self.create_alignment_hole_button.setToolTip(
  85. "Creates an Excellon Object containing the "
  86. "specified alignment holes and their mirror "
  87. "images."
  88. )
  89. self.mirror_object_button = QtGui.QPushButton("Mirror Object")
  90. self.mirror_object_button.setToolTip(
  91. "Mirrors (flips) the specified object around "
  92. "the specified axis. Does not create a new "
  93. "object, but modifies it."
  94. )
  95. hlay.addWidget(self.create_alignment_hole_button)
  96. hlay.addWidget(self.mirror_object_button)
  97. self.layout.addStretch()
  98. ## Signals
  99. self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
  100. self.mirror_object_button.clicked.connect(self.on_mirror)
  101. self.axis_location.group_toggle_fn = self.on_toggle_pointbox
  102. ## Initialize form
  103. self.mirror_axis.set_value('X')
  104. self.axis_location.set_value('point')
  105. def on_create_alignment_holes(self):
  106. axis = self.mirror_axis.get_value()
  107. mode = self.axis_location.get_value()
  108. if mode == "point":
  109. px, py = self.point.get_value()
  110. else:
  111. selection_index = self.box_combo.currentIndex()
  112. bb_obj = self.app.collection.object_list[selection_index] # TODO: Direct access??
  113. xmin, ymin, xmax, ymax = bb_obj.bounds()
  114. px = 0.5 * (xmin + xmax)
  115. py = 0.5 * (ymin + ymax)
  116. xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
  117. dia = self.drill_dia.get_value()
  118. tools = {"1": {"C": dia}}
  119. # holes = self.alignment_holes.get_value()
  120. holes = eval('[{}]'.format(self.alignment_holes.text()))
  121. drills = []
  122. for hole in holes:
  123. point = Point(hole)
  124. point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
  125. drills.append({"point": point, "tool": "1"})
  126. drills.append({"point": point_mirror, "tool": "1"})
  127. def obj_init(obj_inst, app_inst):
  128. obj_inst.tools = tools
  129. obj_inst.drills = drills
  130. obj_inst.create_geometry()
  131. self.app.new_object("excellon", "Alignment Drills", obj_init)
  132. def on_mirror(self):
  133. selection_index = self.object_combo.currentIndex()
  134. fcobj = self.app.collection.object_list[selection_index]
  135. # For now, lets limit to Gerbers and Excellons.
  136. # assert isinstance(gerb, FlatCAMGerber)
  137. if not isinstance(fcobj, FlatCAMGerber) and \
  138. not isinstance(fcobj, FlatCAMExcellon) and \
  139. not isinstance(fcobj, FlatCAMGeometry):
  140. self.info("ERROR: Only Gerber, Excellon and Geometry objects can be mirrored.")
  141. return
  142. axis = self.mirror_axis.get_value()
  143. mode = self.axis_location.get_value()
  144. if mode == "point":
  145. px, py = self.point.get_value()
  146. else:
  147. selection_index = self.box_combo.currentIndex()
  148. bb_obj = self.app.collection.object_list[selection_index] # TODO: Direct access??
  149. xmin, ymin, xmax, ymax = bb_obj.bounds()
  150. px = 0.5 * (xmin + xmax)
  151. py = 0.5 * (ymin + ymax)
  152. # Ensure that the selected object will display when it is mirrored.
  153. # If an object's plot setting is False it will still be available in
  154. # the combo box. If the plot is not enforced to True then the user
  155. # gets no feedback of the operation.
  156. fcobj.options["plot"] = True;
  157. fcobj.mirror(axis, [px, py])
  158. fcobj.plot()
  159. def on_toggle_pointbox(self):
  160. if self.axis_location.get_value() == "point":
  161. self.point.show()
  162. self.box_combo.hide()
  163. else:
  164. self.point.hide()
  165. self.box_combo.show()