ToolCorners.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 5/17/2020 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets, QtCore
  8. from AppTool import AppTool
  9. from AppGUI.GUIElements import FCDoubleSpinner, FCCheckBox, FCComboBox, FCButton
  10. from shapely.geometry import MultiPolygon, LineString
  11. from copy import deepcopy
  12. import logging
  13. import gettext
  14. import AppTranslation as fcTranslate
  15. import builtins
  16. fcTranslate.apply_language('strings')
  17. if '_' not in builtins.__dict__:
  18. _ = gettext.gettext
  19. log = logging.getLogger('base')
  20. class ToolCorners(AppTool):
  21. toolName = _("Corner Markers Tool")
  22. def __init__(self, app):
  23. AppTool.__init__(self, app)
  24. self.app = app
  25. self.canvas = self.app.plotcanvas
  26. self.decimals = self.app.decimals
  27. self.units = ''
  28. # ## Title
  29. title_label = QtWidgets.QLabel("%s" % self.toolName)
  30. title_label.setStyleSheet("""
  31. QLabel
  32. {
  33. font-size: 16px;
  34. font-weight: bold;
  35. }
  36. """)
  37. self.layout.addWidget(title_label)
  38. self.layout.addWidget(QtWidgets.QLabel(''))
  39. # Gerber object #
  40. self.object_label = QtWidgets.QLabel('<b>%s:</b>' % _("Gerber Object"))
  41. self.object_label.setToolTip(
  42. _("The Gerber object that to which will be added corner markers.")
  43. )
  44. self.object_combo = FCComboBox()
  45. self.object_combo.setModel(self.app.collection)
  46. self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  47. self.object_combo.is_last = True
  48. self.object_combo.obj_type = "Gerber"
  49. self.layout.addWidget(self.object_label)
  50. self.layout.addWidget(self.object_combo)
  51. separator_line = QtWidgets.QFrame()
  52. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  53. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  54. self.layout.addWidget(separator_line)
  55. self.points_label = QtWidgets.QLabel('<b>%s:</b>' % _('Locations'))
  56. self.points_label.setToolTip(
  57. _("Locations where to place corner markers.")
  58. )
  59. self.layout.addWidget(self.points_label)
  60. # BOTTOM LEFT
  61. self.bl_cb = FCCheckBox(_("Bottom Left"))
  62. self.layout.addWidget(self.bl_cb)
  63. # BOTTOM RIGHT
  64. self.br_cb = FCCheckBox(_("Bottom Right"))
  65. self.layout.addWidget(self.br_cb)
  66. # TOP LEFT
  67. self.tl_cb = FCCheckBox(_("Top Left"))
  68. self.layout.addWidget(self.tl_cb)
  69. # TOP RIGHT
  70. self.tr_cb = FCCheckBox(_("Top Right"))
  71. self.layout.addWidget(self.tr_cb)
  72. separator_line = QtWidgets.QFrame()
  73. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  74. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  75. self.layout.addWidget(separator_line)
  76. # ## Grid Layout
  77. grid_lay = QtWidgets.QGridLayout()
  78. self.layout.addLayout(grid_lay)
  79. grid_lay.setColumnStretch(0, 0)
  80. grid_lay.setColumnStretch(1, 1)
  81. self.param_label = QtWidgets.QLabel('<b>%s:</b>' % _('Parameters'))
  82. self.param_label.setToolTip(
  83. _("Parameters used for this tool.")
  84. )
  85. grid_lay.addWidget(self.param_label, 0, 0, 1, 2)
  86. # Thickness #
  87. self.thick_label = QtWidgets.QLabel('%s:' % _("Thickness"))
  88. self.thick_label.setToolTip(
  89. _("The thickness of the line that makes the corner marker.")
  90. )
  91. self.thick_entry = FCDoubleSpinner(callback=self.confirmation_message)
  92. self.thick_entry.set_range(0.0000, 9.9999)
  93. self.thick_entry.set_precision(self.decimals)
  94. self.thick_entry.setWrapping(True)
  95. self.thick_entry.setSingleStep(10 ** -self.decimals)
  96. grid_lay.addWidget(self.thick_label, 1, 0)
  97. grid_lay.addWidget(self.thick_entry, 1, 1)
  98. # Length #
  99. self.l_label = QtWidgets.QLabel('%s:' % _("Length"))
  100. self.l_label.setToolTip(
  101. _("The length of the line that makes the corner marker.")
  102. )
  103. self.l_entry = FCDoubleSpinner(callback=self.confirmation_message)
  104. self.l_entry.set_range(-9999.9999, 9999.9999)
  105. self.l_entry.set_precision(self.decimals)
  106. self.l_entry.setSingleStep(10 ** -self.decimals)
  107. grid_lay.addWidget(self.l_label, 2, 0)
  108. grid_lay.addWidget(self.l_entry, 2, 1)
  109. # Margin #
  110. self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
  111. self.margin_label.setToolTip(
  112. _("Bounding box margin.")
  113. )
  114. self.margin_entry = FCDoubleSpinner(callback=self.confirmation_message)
  115. self.margin_entry.set_range(-9999.9999, 9999.9999)
  116. self.margin_entry.set_precision(self.decimals)
  117. self.margin_entry.setSingleStep(0.1)
  118. grid_lay.addWidget(self.margin_label, 3, 0)
  119. grid_lay.addWidget(self.margin_entry, 3, 1)
  120. separator_line_2 = QtWidgets.QFrame()
  121. separator_line_2.setFrameShape(QtWidgets.QFrame.HLine)
  122. separator_line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
  123. grid_lay.addWidget(separator_line_2, 4, 0, 1, 2)
  124. # ## Insert Corner Marker
  125. self.add_marker_button = FCButton(_("Add Marker"))
  126. self.add_marker_button.setToolTip(
  127. _("Will add corner markers to the selected Gerber file.")
  128. )
  129. self.add_marker_button.setStyleSheet("""
  130. QPushButton
  131. {
  132. font-weight: bold;
  133. }
  134. """)
  135. grid_lay.addWidget(self.add_marker_button, 11, 0, 1, 2)
  136. self.layout.addStretch()
  137. # ## Reset Tool
  138. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  139. self.reset_button.setToolTip(
  140. _("Will reset the tool parameters.")
  141. )
  142. self.reset_button.setStyleSheet("""
  143. QPushButton
  144. {
  145. font-weight: bold;
  146. }
  147. """)
  148. self.layout.addWidget(self.reset_button)
  149. # Objects involved in Copper thieving
  150. self.grb_object = None
  151. # store the flattened geometry here:
  152. self.flat_geometry = []
  153. # Tool properties
  154. self.fid_dia = None
  155. self.grb_steps_per_circle = self.app.defaults["gerber_circle_steps"]
  156. # SIGNALS
  157. self.add_marker_button.clicked.connect(self.add_markers)
  158. def run(self, toggle=True):
  159. self.app.defaults.report_usage("ToolCorners()")
  160. if toggle:
  161. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  162. if self.app.ui.splitter.sizes()[0] == 0:
  163. self.app.ui.splitter.setSizes([1, 1])
  164. else:
  165. try:
  166. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  167. # if tab is populated with the tool but it does not have the focus, focus on it
  168. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  169. # focus on Tool Tab
  170. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  171. else:
  172. self.app.ui.splitter.setSizes([0, 1])
  173. except AttributeError:
  174. pass
  175. else:
  176. if self.app.ui.splitter.sizes()[0] == 0:
  177. self.app.ui.splitter.setSizes([1, 1])
  178. AppTool.run(self)
  179. self.set_tool_ui()
  180. self.app.ui.notebook.setTabText(2, _("Corners Tool"))
  181. def install(self, icon=None, separator=None, **kwargs):
  182. AppTool.install(self, icon, separator, shortcut='Alt+M', **kwargs)
  183. def set_tool_ui(self):
  184. self.units = self.app.defaults['units']
  185. self.thick_entry.set_value(self.app.defaults["tools_corners_thickness"])
  186. self.l_entry.set_value(float(self.app.defaults["tools_corners_length"]))
  187. self.margin_entry.set_value(float(self.app.defaults["tools_corners_margin"]))
  188. def add_markers(self):
  189. self.app.call_source = "corners_tool"
  190. tl_state = self.tl_cb.get_value()
  191. tr_state = self.tr_cb.get_value()
  192. bl_state = self.bl_cb.get_value()
  193. br_state = self.br_cb.get_value()
  194. # get the Gerber object on which the corner marker will be inserted
  195. selection_index = self.object_combo.currentIndex()
  196. model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
  197. try:
  198. self.grb_object = model_index.internalPointer().obj
  199. except Exception as e:
  200. log.debug("ToolCorners.add_markers() --> %s" % str(e))
  201. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  202. return
  203. xmin, ymin, xmax, ymax = self.grb_object.bounds()
  204. points = {}
  205. if tl_state:
  206. points['tl'] = (xmin, ymax)
  207. if tr_state:
  208. points['tr'] = (xmax, ymax)
  209. if bl_state:
  210. points['bl'] = (xmin, ymin)
  211. if br_state:
  212. points['br'] = (xmax, ymin)
  213. self.add_corners_geo(points, g_obj=self.grb_object)
  214. self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'],
  215. filename=None,
  216. local_use=self.grb_object, use_thread=False)
  217. self.on_exit()
  218. def add_corners_geo(self, points_storage, g_obj):
  219. """
  220. Add geometry to the solid_geometry of the copper Gerber object
  221. :param points_storage: a dictionary holding the points where to add corners
  222. :param g_obj: the Gerber object where to add the geometry
  223. :return: None
  224. """
  225. line_thickness = self.thick_entry.get_value()
  226. line_length = self.l_entry.get_value()
  227. margin = self.margin_entry.get_value()
  228. geo_list = []
  229. if not points_storage:
  230. self.app.inform.emit("[ERROR_NOTCL] %s." % _("Please select at least a location"))
  231. return
  232. for key in points_storage:
  233. if key == 'tl':
  234. pt = points_storage[key]
  235. x = pt[0] - margin - line_thickness / 2.0
  236. y = pt[1] + margin + line_thickness / 2.0
  237. line_geo_hor = LineString([
  238. (x, y), (x + line_length, y)
  239. ])
  240. line_geo_vert = LineString([
  241. (x, y), (x, y - line_length)
  242. ])
  243. geo_list.append(line_geo_hor)
  244. geo_list.append(line_geo_vert)
  245. if key == 'tr':
  246. pt = points_storage[key]
  247. x = pt[0] + margin + line_thickness / 2.0
  248. y = pt[1] + margin + line_thickness / 2.0
  249. line_geo_hor = LineString([
  250. (x, y), (x - line_length, y)
  251. ])
  252. line_geo_vert = LineString([
  253. (x, y), (x, y - line_length)
  254. ])
  255. geo_list.append(line_geo_hor)
  256. geo_list.append(line_geo_vert)
  257. if key == 'bl':
  258. pt = points_storage[key]
  259. x = pt[0] - margin - line_thickness / 2.0
  260. y = pt[1] - margin - line_thickness / 2.0
  261. line_geo_hor = LineString([
  262. (x, y), (x + line_length, y)
  263. ])
  264. line_geo_vert = LineString([
  265. (x, y), (x, y + line_length)
  266. ])
  267. geo_list.append(line_geo_hor)
  268. geo_list.append(line_geo_vert)
  269. if key == 'br':
  270. pt = points_storage[key]
  271. x = pt[0] + margin + line_thickness / 2.0
  272. y = pt[1] - margin - line_thickness / 2.0
  273. line_geo_hor = LineString([
  274. (x, y), (x - line_length, y)
  275. ])
  276. line_geo_vert = LineString([
  277. (x, y), (x, y + line_length)
  278. ])
  279. geo_list.append(line_geo_hor)
  280. geo_list.append(line_geo_vert)
  281. aperture_found = None
  282. for ap_id, ap_val in g_obj.apertures.items():
  283. if ap_val['type'] == 'C' and ap_val['size'] == line_thickness:
  284. aperture_found = ap_id
  285. break
  286. geo_buff_list = []
  287. if aperture_found:
  288. for geo in geo_list:
  289. geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2)
  290. geo_buff_list.append(geo_buff)
  291. dict_el = {}
  292. dict_el['follow'] = geo
  293. dict_el['solid'] = geo_buff
  294. g_obj.apertures[aperture_found]['geometry'].append(deepcopy(dict_el))
  295. else:
  296. ap_keys = list(g_obj.apertures.keys())
  297. if ap_keys:
  298. new_apid = str(int(max(ap_keys)) + 1)
  299. else:
  300. new_apid = '10'
  301. g_obj.apertures[new_apid] = {}
  302. g_obj.apertures[new_apid]['type'] = 'C'
  303. g_obj.apertures[new_apid]['size'] = line_thickness
  304. g_obj.apertures[new_apid]['geometry'] = []
  305. for geo in geo_list:
  306. geo_buff = geo.buffer(line_thickness / 2.0, resolution=self.grb_steps_per_circle, join_style=2)
  307. geo_buff_list.append(geo_buff)
  308. dict_el = {}
  309. dict_el['follow'] = geo
  310. dict_el['solid'] = geo_buff
  311. g_obj.apertures[new_apid]['geometry'].append(deepcopy(dict_el))
  312. s_list = []
  313. if g_obj.solid_geometry:
  314. try:
  315. for poly in g_obj.solid_geometry:
  316. s_list.append(poly)
  317. except TypeError:
  318. s_list.append(g_obj.solid_geometry)
  319. geo_buff_list = MultiPolygon(geo_buff_list)
  320. geo_buff_list = geo_buff_list.buffer(0)
  321. for poly in geo_buff_list:
  322. s_list.append(poly)
  323. g_obj.solid_geometry = MultiPolygon(s_list)
  324. def replot(self, obj, run_thread=True):
  325. def worker_task():
  326. with self.app.proc_container.new('%s...' % _("Plotting")):
  327. obj.plot()
  328. if run_thread:
  329. self.app.worker_task.emit({'fcn': worker_task, 'params': []})
  330. else:
  331. worker_task()
  332. def on_exit(self):
  333. # plot the object
  334. try:
  335. self.replot(obj=self.grb_object)
  336. except (AttributeError, TypeError):
  337. return
  338. # update the bounding box values
  339. try:
  340. a, b, c, d = self.grb_object.bounds()
  341. self.grb_object.options['xmin'] = a
  342. self.grb_object.options['ymin'] = b
  343. self.grb_object.options['xmax'] = c
  344. self.grb_object.options['ymax'] = d
  345. except Exception as e:
  346. log.debug("ToolCorners.on_exit() copper_obj bounds error --> %s" % str(e))
  347. # reset the variables
  348. self.grb_object = None
  349. self.app.call_source = "app"
  350. self.app.inform.emit('[success] %s' % _("Corners Tool exit."))