ToolCopperFill.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # File Author: Marius Adrian Stanciu (c) #
  4. # Date: 10/25/2019 #
  5. # MIT Licence #
  6. # ##########################################################
  7. from PyQt5 import QtWidgets, QtCore
  8. import FlatCAMApp
  9. from FlatCAMTool import FlatCAMTool
  10. from flatcamGUI.GUIElements import FCDoubleSpinner, RadioSet
  11. from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMExcellon
  12. import shapely.geometry.base as base
  13. from shapely.ops import cascaded_union, unary_union
  14. from shapely.geometry import Polygon, MultiPolygon
  15. import logging
  16. from copy import deepcopy
  17. import numpy as np
  18. from collections import Iterable
  19. import gettext
  20. import FlatCAMTranslation as fcTranslate
  21. import builtins
  22. fcTranslate.apply_language('strings')
  23. if '_' not in builtins.__dict__:
  24. _ = gettext.gettext
  25. log = logging.getLogger('base')
  26. class ToolCopperFill(FlatCAMTool):
  27. toolName = _("Copper Fill Tool")
  28. def __init__(self, app):
  29. FlatCAMTool.__init__(self, app)
  30. self.app = app
  31. self.canvas = self.app.plotcanvas
  32. self.decimals = 4
  33. self.units = ''
  34. # ## Title
  35. title_label = QtWidgets.QLabel("%s" % self.toolName)
  36. title_label.setStyleSheet("""
  37. QLabel
  38. {
  39. font-size: 16px;
  40. font-weight: bold;
  41. }
  42. """)
  43. self.layout.addWidget(title_label)
  44. self.layout.addWidget(QtWidgets.QLabel(''))
  45. # ## Grid Layout
  46. i_grid_lay = QtWidgets.QGridLayout()
  47. self.layout.addLayout(i_grid_lay)
  48. i_grid_lay.setColumnStretch(0, 0)
  49. i_grid_lay.setColumnStretch(1, 1)
  50. self.grb_object_combo = QtWidgets.QComboBox()
  51. self.grb_object_combo.setModel(self.app.collection)
  52. self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  53. self.grb_object_combo.setCurrentIndex(1)
  54. self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  55. self.grbobj_label.setToolTip(
  56. _("Gerber Object to which will be added a copper fill.")
  57. )
  58. i_grid_lay.addWidget(self.grbobj_label, 0, 0)
  59. i_grid_lay.addWidget(self.grb_object_combo, 0, 1, 1, 2)
  60. i_grid_lay.addWidget(QtWidgets.QLabel(''), 1, 0)
  61. # ## Grid Layout
  62. grid_lay = QtWidgets.QGridLayout()
  63. self.layout.addLayout(grid_lay)
  64. grid_lay.setColumnStretch(0, 0)
  65. grid_lay.setColumnStretch(1, 1)
  66. self.copper_fill_label = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
  67. self.copper_fill_label.setToolTip(
  68. _("Parameters used for this tool.")
  69. )
  70. grid_lay.addWidget(self.copper_fill_label, 0, 0, 1, 2)
  71. # CLEARANCE #
  72. self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
  73. self.clearance_label.setToolTip(
  74. _("This set the distance between the copper fill components\n"
  75. "(the polygon fill may be split in multiple polygons)\n"
  76. "and the copper traces in the Gerber file.")
  77. )
  78. self.clearance_entry = FCDoubleSpinner()
  79. self.clearance_entry.setMinimum(0.00001)
  80. self.clearance_entry.set_precision(self.decimals)
  81. self.clearance_entry.setSingleStep(0.1)
  82. grid_lay.addWidget(self.clearance_label, 1, 0)
  83. grid_lay.addWidget(self.clearance_entry, 1, 1)
  84. # MARGIN #
  85. self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
  86. self.margin_label.setToolTip(
  87. _("Bounding box margin.")
  88. )
  89. self.margin_entry = FCDoubleSpinner()
  90. self.margin_entry.setMinimum(0.0)
  91. self.margin_entry.set_precision(self.decimals)
  92. self.margin_entry.setSingleStep(0.1)
  93. grid_lay.addWidget(self.margin_label, 2, 0)
  94. grid_lay.addWidget(self.margin_entry, 2, 1)
  95. # Reference #
  96. self.reference_radio = RadioSet([
  97. {'label': _('Itself'), 'value': 'itself'},
  98. {"label": _("Area Selection"), "value": "area"},
  99. {'label': _("Reference Object"), 'value': 'box'}
  100. ], orientation='vertical', stretch=False)
  101. self.reference_label = QtWidgets.QLabel(_("Reference:"))
  102. self.reference_label.setToolTip(
  103. _("- 'Itself' - the copper fill extent is based on the object that is copper cleared.\n "
  104. "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
  105. "- 'Reference Object' - will do copper filling within the area specified by another object.")
  106. )
  107. grid_lay.addWidget(self.reference_label, 3, 0)
  108. grid_lay.addWidget(self.reference_radio, 3, 1)
  109. self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
  110. self.box_combo_type_label.setToolTip(
  111. _("The type of FlatCAM object to be used as copper filling reference.\n"
  112. "It can be Gerber, Excellon or Geometry.")
  113. )
  114. self.box_combo_type = QtWidgets.QComboBox()
  115. self.box_combo_type.addItem(_("Gerber Reference Box Object"))
  116. self.box_combo_type.addItem(_("Excellon Reference Box Object"))
  117. self.box_combo_type.addItem(_("Geometry Reference Box Object"))
  118. grid_lay.addWidget(self.box_combo_type_label, 4, 0)
  119. grid_lay.addWidget(self.box_combo_type, 4, 1)
  120. self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
  121. self.box_combo_label.setToolTip(
  122. _("The FlatCAM object to be used as non copper clearing reference.")
  123. )
  124. self.box_combo = QtWidgets.QComboBox()
  125. self.box_combo.setModel(self.app.collection)
  126. self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  127. self.box_combo.setCurrentIndex(1)
  128. grid_lay.addWidget(self.box_combo_label, 5, 0)
  129. grid_lay.addWidget(self.box_combo, 5, 1)
  130. self.box_combo.hide()
  131. self.box_combo_label.hide()
  132. self.box_combo_type.hide()
  133. self.box_combo_type_label.hide()
  134. # ## Insert Copper Fill
  135. self.fill_button = QtWidgets.QPushButton(_("Insert Copper Fill"))
  136. self.fill_button.setToolTip(
  137. _("Will add a polygon (may be split in multiple parts)\n"
  138. "that will surround the actual Gerber traces at a certain distance.")
  139. )
  140. self.layout.addWidget(self.fill_button)
  141. self.layout.addStretch()
  142. # Objects involved in Copper filling
  143. self.grb_object = None
  144. self.ref_obj = None
  145. self.sel_rect = list()
  146. # Events ID
  147. self.mr = None
  148. self.mm = None
  149. # Mouse cursor positions
  150. self.mouse_is_dragging = False
  151. self.cursor_pos = (0, 0)
  152. self.first_click = False
  153. # Tool properties
  154. self.clearance_val = None
  155. self.margin_val = None
  156. self.geo_steps_per_circle = 128
  157. # SIGNALS
  158. self.fill_button.clicked.connect(self.execute)
  159. self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
  160. self.reference_radio.group_toggle_fn = self.on_toggle_reference
  161. def run(self, toggle=True):
  162. self.app.report_usage("ToolCopperFill()")
  163. if toggle:
  164. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  165. if self.app.ui.splitter.sizes()[0] == 0:
  166. self.app.ui.splitter.setSizes([1, 1])
  167. else:
  168. try:
  169. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  170. # if tab is populated with the tool but it does not have the focus, focus on it
  171. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  172. # focus on Tool Tab
  173. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  174. else:
  175. self.app.ui.splitter.setSizes([0, 1])
  176. except AttributeError:
  177. pass
  178. else:
  179. if self.app.ui.splitter.sizes()[0] == 0:
  180. self.app.ui.splitter.setSizes([1, 1])
  181. FlatCAMTool.run(self)
  182. self.set_tool_ui()
  183. self.app.ui.notebook.setTabText(2, _("Copper Fill Tool"))
  184. def install(self, icon=None, separator=None, **kwargs):
  185. FlatCAMTool.install(self, icon, separator, shortcut='ALT+F', **kwargs)
  186. def set_tool_ui(self):
  187. self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
  188. # self.clearance_entry.set_value(float(self.app.defaults["tools_copperfill_clearance"]))
  189. # self.margin_entry.set_value(float(self.app.defaults["tools_copperfill_margin"]))
  190. # self.reference_radio.set_value(self.app.defaults["tools_copperfill_reference"])
  191. # self.geo_steps_per_circle = int(self.app.defaults["tools_copperfill_circle_steps"])
  192. self.clearance_entry.set_value(0.5)
  193. self.margin_entry.set_value(1.0)
  194. self.reference_radio.set_value('itself')
  195. def on_combo_box_type(self):
  196. obj_type = self.box_combo_type.currentIndex()
  197. self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  198. self.box_combo.setCurrentIndex(0)
  199. def on_toggle_reference(self):
  200. if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
  201. self.box_combo.hide()
  202. self.box_combo_label.hide()
  203. self.box_combo_type.hide()
  204. self.box_combo_type_label.hide()
  205. else:
  206. self.box_combo.show()
  207. self.box_combo_label.show()
  208. self.box_combo_type.show()
  209. self.box_combo_type_label.show()
  210. def execute(self):
  211. self.clearance_val = self.clearance_entry.get_value()
  212. self.margin_val = self.margin_entry.get_value()
  213. reference_method = self.reference_radio.get_value()
  214. # get the Gerber object on which the Copper fill will be inserted
  215. selection_index = self.grb_object_combo.currentIndex()
  216. model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
  217. try:
  218. self.grb_object = model_index.internalPointer().obj
  219. except Exception as e:
  220. log.debug("ToolCopperFill.execute() --> %s" % str(e))
  221. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  222. return 'fail'
  223. if reference_method == 'itself':
  224. bound_obj_name = self.grb_object_combo.currentText()
  225. # Get reference object.
  226. try:
  227. self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
  228. except Exception as e:
  229. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(e)))
  230. return "Could not retrieve object: %s" % self.obj_name
  231. self.on_copper_fill(
  232. fill_obj=self.grb_object,
  233. c_val=self.clearance_val,
  234. margin=self.margin_val
  235. )
  236. elif reference_method == 'area':
  237. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
  238. if self.app.is_legacy is False:
  239. self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
  240. self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
  241. self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
  242. else:
  243. self.app.plotcanvas.graph_event_disconnect(self.app.mp)
  244. self.app.plotcanvas.graph_event_disconnect(self.app.mm)
  245. self.app.plotcanvas.graph_event_disconnect(self.app.mr)
  246. self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
  247. self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
  248. elif reference_method == 'box':
  249. bound_obj_name = self.box_combo.currentText()
  250. # Get reference object.
  251. try:
  252. self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
  253. except Exception as e:
  254. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), bound_obj_name))
  255. return "Could not retrieve object: %s. Error: %s" % (bound_obj_name, str(e))
  256. self.on_copper_fill(
  257. fill_obj=self.grb_object,
  258. ref_obj=self.ref_obj,
  259. c_val=self.clearance_val,
  260. margin=self.margin_val
  261. )
  262. # To be called after clicking on the plot.
  263. def on_mouse_release(self, event):
  264. if self.app.is_legacy is False:
  265. event_pos = event.pos
  266. event_is_dragging = event.is_dragging
  267. right_button = 2
  268. else:
  269. event_pos = (event.xdata, event.ydata)
  270. event_is_dragging = self.app.plotcanvas.is_dragging
  271. right_button = 3
  272. event_pos = self.app.plotcanvas.translate_coords(event_pos)
  273. # do clear area only for left mouse clicks
  274. if event.button == 1:
  275. if self.first_click is False:
  276. self.first_click = True
  277. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the filling area."))
  278. self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
  279. if self.app.grid_status() == True:
  280. self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
  281. else:
  282. self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
  283. if self.app.grid_status() == True:
  284. curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
  285. else:
  286. curr_pos = (event_pos[0], event_pos[1])
  287. x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
  288. x1, y1 = curr_pos[0], curr_pos[1]
  289. pt1 = (x0, y0)
  290. pt2 = (x1, y0)
  291. pt3 = (x1, y1)
  292. pt4 = (x0, y1)
  293. self.sel_rect.append(Polygon([pt1, pt2, pt3, pt4]))
  294. self.first_click = False
  295. return
  296. elif event.button == right_button and self.mouse_is_dragging == False:
  297. self.app.delete_selection_shape()
  298. self.first_click = False
  299. if self.app.is_legacy is False:
  300. self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
  301. self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
  302. else:
  303. self.app.plotcanvas.graph_event_disconnect(self.mr)
  304. self.app.plotcanvas.graph_event_disconnect(self.mm)
  305. self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
  306. self.app.on_mouse_click_over_plot)
  307. self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
  308. self.app.on_mouse_move_over_plot)
  309. self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
  310. self.app.on_mouse_click_release_over_plot)
  311. if len(self.sel_rect) == 0:
  312. return
  313. self.sel_rect = cascaded_union(self.sel_rect)
  314. if not isinstance(self.sel_rect, Iterable):
  315. self.sel_rect = [self.sel_rect]
  316. self.on_copper_fill(
  317. fill_obj=self.grb_object,
  318. ref_obj=self.sel_rect,
  319. c_val=self.clearance_val,
  320. margin=self.margin_val
  321. )
  322. # called on mouse move
  323. def on_mouse_move(self, event):
  324. if self.app.is_legacy is False:
  325. event_pos = event.pos
  326. event_is_dragging = event.is_dragging
  327. right_button = 2
  328. else:
  329. event_pos = (event.xdata, event.ydata)
  330. event_is_dragging = self.app.plotcanvas.is_dragging
  331. right_button = 3
  332. curr_pos = self.app.plotcanvas.translate_coords(event_pos)
  333. # detect mouse dragging motion
  334. if event_is_dragging is True:
  335. self.mouse_is_dragging = True
  336. else:
  337. self.mouse_is_dragging = False
  338. # update the cursor position
  339. if self.app.grid_status() == True:
  340. # Update cursor
  341. curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
  342. self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
  343. symbol='++', edge_color=self.app.cursor_color_3D,
  344. size=self.app.defaults["global_cursor_size"])
  345. # update the positions on status bar
  346. self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
  347. "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
  348. if self.cursor_pos is None:
  349. self.cursor_pos = (0, 0)
  350. dx = curr_pos[0] - float(self.cursor_pos[0])
  351. dy = curr_pos[1] - float(self.cursor_pos[1])
  352. self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
  353. "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
  354. # draw the utility geometry
  355. if self.first_click:
  356. self.app.delete_selection_shape()
  357. self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
  358. coords=(curr_pos[0], curr_pos[1]))
  359. def on_copper_fill(self, fill_obj, ref_obj=None, c_val=None, margin=None, run_threaded=True):
  360. """
  361. :param fill_obj:
  362. :param ref_obj:
  363. :param c_val:
  364. :param margin:
  365. :param run_threaded:
  366. :return:
  367. """
  368. if run_threaded:
  369. proc = self.app.proc_container.new('%s ...' % _("Copper filling"))
  370. else:
  371. self.app.proc_container.view.set_busy('%s ...' % _("Copper filling"))
  372. QtWidgets.QApplication.processEvents()
  373. # #####################################################################
  374. # ####### Read the parameters #########################################
  375. # #####################################################################
  376. log.debug("Copper Filling Tool started. Reading parameters.")
  377. self.app.inform.emit(_("Copper Filling Tool started. Reading parameters."))
  378. units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
  379. ref_selected = self.reference_radio.get_value()
  380. if c_val is None:
  381. c_val = float(self.app.defaults["tools_copperfill_clearance"])
  382. if margin is None:
  383. margin = float(self.app.defaults["tools_copperfill_margin"])
  384. # make sure that the source object solid geometry is an Iterable
  385. if not isinstance(self.grb_object.solid_geometry, Iterable):
  386. self.grb_object.solid_geometry = [self.grb_object.solid_geometry]
  387. # #########################################################################################
  388. # Prepare isolation polygon. This will create the clearance over the Gerber features ######
  389. # #########################################################################################
  390. log.debug("Copper Filling Tool. Preparing isolation polygons.")
  391. self.app.inform.emit(_("Copper Filling Tool. Preparing isolation polygons."))
  392. # variables to display the percentage of work done
  393. geo_len = 0
  394. try:
  395. for pol in self.grb_object.solid_geometry:
  396. geo_len += 1
  397. except TypeError:
  398. geo_len = 1
  399. old_disp_number = 0
  400. pol_nr = 0
  401. clearance_geometry = []
  402. try:
  403. for pol in self.grb_object.solid_geometry:
  404. if self.app.abort_flag:
  405. # graceful abort requested by the user
  406. raise FlatCAMApp.GracefulException
  407. clearance_geometry.append(
  408. pol.buffer(c_val, int(int(self.geo_steps_per_circle) / 4))
  409. )
  410. pol_nr += 1
  411. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  412. if old_disp_number < disp_number <= 100:
  413. self.app.proc_container.update_view_text(' %s ... %d%%' %
  414. (_("Buffering"), int(disp_number)))
  415. old_disp_number = disp_number
  416. except TypeError:
  417. # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
  418. # MultiPolygon (not an iterable)
  419. clearance_geometry.append(
  420. self.grb_object.solid_geometry.buffer(c_val, int(int(self.geo_steps_per_circle) / 4))
  421. )
  422. self.app.proc_container.update_view_text(' %s' % _("Buffering"))
  423. clearance_geometry = unary_union(clearance_geometry)
  424. # #########################################################################################
  425. # Prepare the area to fill with copper. ###################################################
  426. # #########################################################################################
  427. log.debug("Copper Filling Tool. Preparing areas to fill with copper.")
  428. self.app.inform.emit(_("Copper Filling Tool. Preparing areas to fill with copper."))
  429. try:
  430. if ref_obj is None or ref_obj == 'itself':
  431. working_obj = fill_obj
  432. else:
  433. working_obj = ref_obj
  434. except Exception as e:
  435. log.debug("ToolCopperFIll.on_copper_fill() --> %s" % str(e))
  436. return 'fail'
  437. bounding_box = None
  438. if ref_selected == 'itself':
  439. geo_n = working_obj.solid_geometry
  440. try:
  441. if isinstance(geo_n, MultiPolygon):
  442. env_obj = geo_n.convex_hull
  443. elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
  444. (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
  445. env_obj = cascaded_union(geo_n)
  446. else:
  447. env_obj = cascaded_union(geo_n)
  448. env_obj = env_obj.convex_hull
  449. bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  450. except Exception as e:
  451. log.debug("ToolCopperFIll.on_copper_fill() 'itself' --> %s" % str(e))
  452. self.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
  453. return 'fail'
  454. elif ref_selected == 'area':
  455. geo_n = cascaded_union(working_obj)
  456. try:
  457. __ = iter(geo_n)
  458. except Exception as e:
  459. log.debug("ToolCopperFIll.on_copper_fill() 'area' --> %s" % str(e))
  460. geo_n = [geo_n]
  461. geo_buff_list = []
  462. for poly in geo_n:
  463. if self.app.abort_flag:
  464. # graceful abort requested by the user
  465. raise FlatCAMApp.GracefulException
  466. geo_buff_list.append(poly.buffer(distance=0.0, join_style=base.JOIN_STYLE.mitre))
  467. bounding_box = cascaded_union(geo_buff_list)
  468. elif ref_selected == 'box':
  469. geo_n = working_obj.solid_geometry
  470. if isinstance(working_obj, FlatCAMGeometry):
  471. try:
  472. __ = iter(geo_n)
  473. except Exception as e:
  474. log.debug("ToolCopperFIll.on_copper_fill() 'box' --> %s" % str(e))
  475. geo_n = [geo_n]
  476. geo_buff_list = []
  477. for poly in geo_n:
  478. if self.app.abort_flag:
  479. # graceful abort requested by the user
  480. raise FlatCAMApp.GracefulException
  481. geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
  482. bounding_box = cascaded_union(geo_buff_list)
  483. elif isinstance(working_obj, FlatCAMGerber):
  484. geo_n = cascaded_union(geo_n).convex_hull
  485. bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n)
  486. bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  487. else:
  488. self.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
  489. return 'fail'
  490. log.debug("Copper Filling Tool. Finished creating areas to fill with copper.")
  491. self.app.inform.emit(_("Copper Filling Tool. Appending new geometry and buffering."))
  492. new_solid_geometry = bounding_box.difference(clearance_geometry)
  493. geo_list = self.grb_object.solid_geometry
  494. if isinstance(self.grb_object.solid_geometry, MultiPolygon):
  495. geo_list = list(self.grb_object.solid_geometry.geoms)
  496. if '0' not in self.grb_object.apertures:
  497. self.grb_object.apertures['0'] = dict()
  498. self.grb_object.apertures['0']['geometry'] = list()
  499. self.grb_object.apertures['0']['type'] = 'REG'
  500. self.grb_object.apertures['0']['size'] = 0.0
  501. try:
  502. for poly in new_solid_geometry:
  503. # append to the new solid geometry
  504. geo_list.append(poly)
  505. # append into the '0' aperture
  506. geo_elem = dict()
  507. geo_elem['solid'] = poly
  508. geo_elem['follow'] = poly.exterior
  509. self.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
  510. except TypeError:
  511. # append to the new solid geometry
  512. geo_list.append(new_solid_geometry)
  513. # append into the '0' aperture
  514. geo_elem = dict()
  515. geo_elem['solid'] = new_solid_geometry
  516. geo_elem['follow'] = new_solid_geometry.exterior
  517. self.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
  518. self.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
  519. # update the source file with the new geometry:
  520. self.grb_object.source_file = self.app.export_gerber(obj_name=self.grb_object.options['name'], filename=None,
  521. local_use=self.grb_object, use_thread=False)
  522. self.on_exit()
  523. self.app.inform.emit('[success] %s' % _("Copper Fill Tool done."))
  524. def replot(self, obj):
  525. def worker_task():
  526. with self.app.proc_container.new('%s...' % _("Plotting")):
  527. obj.plot()
  528. self.app.worker_task.emit({'fcn': worker_task, 'params': []})
  529. def on_exit(self):
  530. # plot the object
  531. self.replot(obj=self.grb_object)
  532. # update the bounding box values
  533. try:
  534. a, b, c, d = self.grb_object.bounds()
  535. self.grb_object.options['xmin'] = a
  536. self.grb_object.options['ymin'] = b
  537. self.grb_object.options['xmax'] = c
  538. self.grb_object.options['ymax'] = d
  539. except Exception as e:
  540. log.debug("ToolCopperFill.on_exit() bounds error --> %s" % str(e))
  541. # reset the variables
  542. self.grb_object = None
  543. self.ref_obj = None
  544. self.sel_rect = list()
  545. # Events ID
  546. self.mr = None
  547. self.mm = None
  548. # Mouse cursor positions
  549. self.mouse_is_dragging = False
  550. self.cursor_pos = (0, 0)
  551. self.first_click = False