ToolInvertGerber.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 2/14/2020 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets, QtCore
  8. from FlatCAMTool import FlatCAMTool
  9. from flatcamGUI.GUIElements import FCButton, FCDoubleSpinner
  10. from shapely.geometry import Polygon, MultiPolygon, MultiLineString, LineString, box
  11. from shapely.ops import cascaded_union
  12. import traceback
  13. from copy import deepcopy
  14. import time
  15. import logging
  16. import gettext
  17. import FlatCAMTranslation as fcTranslate
  18. import builtins
  19. fcTranslate.apply_language('strings')
  20. if '_' not in builtins.__dict__:
  21. _ = gettext.gettext
  22. log = logging.getLogger('base')
  23. class ToolInvertGerber(FlatCAMTool):
  24. toolName = _("Invert Tool")
  25. def __init__(self, app):
  26. self.app = app
  27. self.decimals = self.app.decimals
  28. FlatCAMTool.__init__(self, app)
  29. self.tools_frame = QtWidgets.QFrame()
  30. self.tools_frame.setContentsMargins(0, 0, 0, 0)
  31. self.layout.addWidget(self.tools_frame)
  32. self.tools_box = QtWidgets.QVBoxLayout()
  33. self.tools_box.setContentsMargins(0, 0, 0, 0)
  34. self.tools_frame.setLayout(self.tools_box)
  35. # Title
  36. title_label = QtWidgets.QLabel("%s" % self.toolName)
  37. title_label.setStyleSheet("""
  38. QLabel
  39. {
  40. font-size: 16px;
  41. font-weight: bold;
  42. }
  43. """)
  44. self.tools_box.addWidget(title_label)
  45. # Form Layout
  46. grid0 = QtWidgets.QGridLayout()
  47. grid0.setColumnStretch(0, 0)
  48. grid0.setColumnStretch(1, 1)
  49. self.tools_box.addLayout(grid0)
  50. grid0.addWidget(QtWidgets.QLabel(''), 0, 0, 1, 2)
  51. # Target Gerber Object
  52. self.gerber_combo = QtWidgets.QComboBox()
  53. self.gerber_combo.setModel(self.app.collection)
  54. self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  55. self.gerber_combo.setCurrentIndex(1)
  56. self.gerber_label = QtWidgets.QLabel('%s:' % _("Gerber Object"))
  57. self.gerber_label.setToolTip(
  58. _("Gerber object that will be inverted.")
  59. )
  60. grid0.addWidget(self.gerber_label, 1, 0, 1, 2)
  61. grid0.addWidget(self.gerber_combo, 2, 0, 1, 2)
  62. # Margin
  63. self.margin_label = QtWidgets.QLabel('%s:' % _('Margin'))
  64. self.margin_label.setToolTip(
  65. _("Distance by which to avoid\n"
  66. "the edges of the Gerber object.")
  67. )
  68. self.margin_entry = FCDoubleSpinner()
  69. self.margin_entry.set_precision(self.decimals)
  70. self.margin_entry.set_range(0.0000, 9999.9999)
  71. self.margin_entry.setObjectName(_("Margin"))
  72. grid0.addWidget(self.margin_label, 3, 0)
  73. grid0.addWidget(self.margin_entry, 3, 1)
  74. separator_line = QtWidgets.QFrame()
  75. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  76. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  77. grid0.addWidget(separator_line, 4, 0, 1, 2)
  78. self.invert_btn = FCButton(_('Invert Gerber'))
  79. self.invert_btn.setToolTip(
  80. _("Will invert the Gerber object: areas that have copper\n"
  81. "will be emty of copper and previous empty area will be\n"
  82. "filled with copper.")
  83. )
  84. self.invert_btn.setStyleSheet("""
  85. QPushButton
  86. {
  87. font-weight: bold;
  88. }
  89. """)
  90. grid0.addWidget(self.invert_btn, 5, 0, 1, 2)
  91. self.tools_box.addStretch()
  92. # ## Reset Tool
  93. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  94. self.reset_button.setToolTip(
  95. _("Will reset the tool parameters.")
  96. )
  97. self.reset_button.setStyleSheet("""
  98. QPushButton
  99. {
  100. font-weight: bold;
  101. }
  102. """)
  103. self.tools_box.addWidget(self.reset_button)
  104. self.invert_btn.clicked.connect(self.on_grb_invert)
  105. self.reset_button.clicked.connect(self.set_tool_ui)
  106. def install(self, icon=None, separator=None, **kwargs):
  107. FlatCAMTool.install(self, icon, separator, shortcut='', **kwargs)
  108. def run(self, toggle=True):
  109. self.app.report_usage("ToolInvertGerber()")
  110. log.debug("ToolInvertGerber() is running ...")
  111. if toggle:
  112. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  113. if self.app.ui.splitter.sizes()[0] == 0:
  114. self.app.ui.splitter.setSizes([1, 1])
  115. else:
  116. try:
  117. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  118. # if tab is populated with the tool but it does not have the focus, focus on it
  119. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  120. # focus on Tool Tab
  121. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  122. else:
  123. self.app.ui.splitter.setSizes([0, 1])
  124. except AttributeError:
  125. pass
  126. else:
  127. if self.app.ui.splitter.sizes()[0] == 0:
  128. self.app.ui.splitter.setSizes([1, 1])
  129. FlatCAMTool.run(self)
  130. self.set_tool_ui()
  131. self.app.ui.notebook.setTabText(2, _("Invert Tool"))
  132. def set_tool_ui(self):
  133. self.margin_entry.set_value(0.0)
  134. def on_grb_invert(self):
  135. margin = self.margin_entry.get_value()
  136. if round(margin, self.decimals) == 0.0:
  137. margin = 1E-10
  138. grb_circle_steps = int(self.app.defaults["gerber_circle_steps"])
  139. obj_name = self.gerber_combo.currentText()
  140. outname = obj_name + "_inverted"
  141. # Get source object.
  142. try:
  143. grb_obj = self.app.collection.get_by_name(obj_name)
  144. except Exception as e:
  145. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(obj_name)))
  146. return "Could not retrieve object: %s with error: %s" % (obj_name, str(e))
  147. if grb_obj is None:
  148. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Object not found"), str(obj_name)))
  149. return
  150. xmin, ymin, xmax, ymax = grb_obj.bounds()
  151. grb_box = box(xmin, ymin, xmax, ymax).buffer(margin, resolution=grb_circle_steps, join_style=2)
  152. try:
  153. __ = iter(grb_obj.solid_geometry)
  154. except TypeError:
  155. grb_obj.solid_geometry = list(grb_obj.solid_geometry)
  156. new_solid_geometry = deepcopy(grb_box)
  157. for poly in grb_obj.solid_geometry:
  158. new_solid_geometry = new_solid_geometry.difference(poly)
  159. new_options = dict()
  160. for opt in grb_obj.options:
  161. new_options[opt] = deepcopy(grb_obj.options[opt])
  162. new_apertures = dict()
  163. # for apid, val in grb_obj.apertures.items():
  164. # new_apertures[apid] = dict()
  165. # for key in val:
  166. # if key == 'geometry':
  167. # new_apertures[apid]['geometry'] = list()
  168. # for elem in val['geometry']:
  169. # geo_elem = dict()
  170. # if 'follow' in elem:
  171. # try:
  172. # geo_elem['clear'] = elem['follow'].buffer(val['size'] / 2.0).exterior
  173. # except AttributeError:
  174. # # TODO should test if width or height is bigger
  175. # geo_elem['clear'] = elem['follow'].buffer(val['width'] / 2.0).exterior
  176. # if 'clear' in elem:
  177. # if isinstance(elem['clear'], Polygon):
  178. # try:
  179. # geo_elem['solid'] = elem['clear'].buffer(val['size'] / 2.0, grb_circle_steps)
  180. # except AttributeError:
  181. # # TODO should test if width or height is bigger
  182. # geo_elem['solid'] = elem['clear'].buffer(val['width'] / 2.0, grb_circle_steps)
  183. # else:
  184. # geo_elem['follow'] = elem['clear']
  185. # new_apertures[apid]['geometry'].append(deepcopy(geo_elem))
  186. # else:
  187. # new_apertures[apid][key] = deepcopy(val[key])
  188. if '0' not in new_apertures:
  189. new_apertures['0'] = dict()
  190. new_apertures['0']['type'] = 'C'
  191. new_apertures['0']['size'] = 0.0
  192. new_apertures['0']['geometry'] = list()
  193. try:
  194. for poly in new_solid_geometry:
  195. new_el = dict()
  196. new_el['solid'] = poly
  197. new_el['follow'] = poly.exterior
  198. new_apertures['0']['geometry'].append(new_el)
  199. except TypeError:
  200. new_el = dict()
  201. new_el['solid'] = new_solid_geometry
  202. new_el['follow'] = new_solid_geometry.exterior
  203. new_apertures['0']['geometry'].append(new_el)
  204. for td in new_apertures:
  205. print(td, new_apertures[td])
  206. def init_func(new_obj, app_obj):
  207. new_obj.options.update(new_options)
  208. new_obj.options['name'] = outname
  209. new_obj.fill_color = deepcopy(grb_obj.fill_color)
  210. new_obj.outline_color = deepcopy(grb_obj.outline_color)
  211. new_obj.apertures = deepcopy(new_apertures)
  212. new_obj.solid_geometry = deepcopy(new_solid_geometry)
  213. new_obj.source_file = self.app.export_gerber(obj_name=outname, filename=None,
  214. local_use=new_obj, use_thread=False)
  215. self.app.new_object('gerber', outname, init_func)
  216. def reset_fields(self):
  217. self.gerber_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  218. @staticmethod
  219. def poly2rings(poly):
  220. return [poly.exterior] + [interior for interior in poly.interiors]
  221. # end of file