ToolCopperThieving.py 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  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, Point, LineString
  15. from shapely.geometry import box as box
  16. import shapely.affinity as affinity
  17. import logging
  18. from copy import deepcopy
  19. import numpy as np
  20. from collections import Iterable
  21. import gettext
  22. import FlatCAMTranslation as fcTranslate
  23. import builtins
  24. fcTranslate.apply_language('strings')
  25. if '_' not in builtins.__dict__:
  26. _ = gettext.gettext
  27. log = logging.getLogger('base')
  28. class ToolCopperThieving(FlatCAMTool):
  29. toolName = _("Copper Thieving Tool")
  30. def __init__(self, app):
  31. FlatCAMTool.__init__(self, app)
  32. self.app = app
  33. self.canvas = self.app.plotcanvas
  34. self.decimals = 4
  35. self.units = ''
  36. # ## Title
  37. title_label = QtWidgets.QLabel("%s" % self.toolName)
  38. title_label.setStyleSheet("""
  39. QLabel
  40. {
  41. font-size: 16px;
  42. font-weight: bold;
  43. }
  44. """)
  45. self.layout.addWidget(title_label)
  46. self.layout.addWidget(QtWidgets.QLabel(''))
  47. # ## Grid Layout
  48. i_grid_lay = QtWidgets.QGridLayout()
  49. self.layout.addLayout(i_grid_lay)
  50. i_grid_lay.setColumnStretch(0, 0)
  51. i_grid_lay.setColumnStretch(1, 1)
  52. self.grb_object_combo = QtWidgets.QComboBox()
  53. self.grb_object_combo.setModel(self.app.collection)
  54. self.grb_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  55. self.grb_object_combo.setCurrentIndex(1)
  56. self.grbobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  57. self.grbobj_label.setToolTip(
  58. _("Gerber Object to which will be added a copper thieving.")
  59. )
  60. i_grid_lay.addWidget(self.grbobj_label, 0, 0)
  61. i_grid_lay.addWidget(self.grb_object_combo, 0, 1, 1, 2)
  62. i_grid_lay.addWidget(QtWidgets.QLabel(''), 1, 0)
  63. # ## Grid Layout
  64. grid_lay = QtWidgets.QGridLayout()
  65. self.layout.addLayout(grid_lay)
  66. grid_lay.setColumnStretch(0, 0)
  67. grid_lay.setColumnStretch(1, 1)
  68. self.copper_fill_label = QtWidgets.QLabel('<b>%s</b>' % _('Parameters'))
  69. self.copper_fill_label.setToolTip(
  70. _("Parameters used for this tool.")
  71. )
  72. grid_lay.addWidget(self.copper_fill_label, 0, 0, 1, 2)
  73. # CLEARANCE #
  74. self.clearance_label = QtWidgets.QLabel('%s:' % _("Clearance"))
  75. self.clearance_label.setToolTip(
  76. _("This set the distance between the copper thieving components\n"
  77. "(the polygon fill may be split in multiple polygons)\n"
  78. "and the copper traces in the Gerber file.")
  79. )
  80. self.clearance_entry = FCDoubleSpinner()
  81. self.clearance_entry.set_range(0.00001, 9999.9999)
  82. self.clearance_entry.set_precision(self.decimals)
  83. self.clearance_entry.setSingleStep(0.1)
  84. grid_lay.addWidget(self.clearance_label, 1, 0)
  85. grid_lay.addWidget(self.clearance_entry, 1, 1)
  86. # MARGIN #
  87. self.margin_label = QtWidgets.QLabel('%s:' % _("Margin"))
  88. self.margin_label.setToolTip(
  89. _("Bounding box margin.")
  90. )
  91. self.margin_entry = FCDoubleSpinner()
  92. self.margin_entry.set_range(0.0, 9999.9999)
  93. self.margin_entry.set_precision(self.decimals)
  94. self.margin_entry.setSingleStep(0.1)
  95. grid_lay.addWidget(self.margin_label, 2, 0)
  96. grid_lay.addWidget(self.margin_entry, 2, 1)
  97. # Reference #
  98. self.reference_radio = RadioSet([
  99. {'label': _('Itself'), 'value': 'itself'},
  100. {"label": _("Area Selection"), "value": "area"},
  101. {'label': _("Reference Object"), 'value': 'box'}
  102. ], orientation='vertical', stretch=False)
  103. self.reference_label = QtWidgets.QLabel(_("Reference:"))
  104. self.reference_label.setToolTip(
  105. _("- 'Itself' - the copper thieving extent is based on the object that is copper cleared.\n "
  106. "- 'Area Selection' - left mouse click to start selection of the area to be filled.\n"
  107. "- 'Reference Object' - will do copper thieving within the area specified by another object.")
  108. )
  109. grid_lay.addWidget(self.reference_label, 3, 0)
  110. grid_lay.addWidget(self.reference_radio, 3, 1)
  111. self.box_combo_type_label = QtWidgets.QLabel('%s:' % _("Ref. Type"))
  112. self.box_combo_type_label.setToolTip(
  113. _("The type of FlatCAM object to be used as copper thieving reference.\n"
  114. "It can be Gerber, Excellon or Geometry.")
  115. )
  116. self.box_combo_type = QtWidgets.QComboBox()
  117. self.box_combo_type.addItem(_("Reference Gerber"))
  118. self.box_combo_type.addItem(_("Reference Excellon"))
  119. self.box_combo_type.addItem(_("Reference Geometry"))
  120. grid_lay.addWidget(self.box_combo_type_label, 4, 0)
  121. grid_lay.addWidget(self.box_combo_type, 4, 1)
  122. self.box_combo_label = QtWidgets.QLabel('%s:' % _("Ref. Object"))
  123. self.box_combo_label.setToolTip(
  124. _("The FlatCAM object to be used as non copper clearing reference.")
  125. )
  126. self.box_combo = QtWidgets.QComboBox()
  127. self.box_combo.setModel(self.app.collection)
  128. self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  129. self.box_combo.setCurrentIndex(1)
  130. grid_lay.addWidget(self.box_combo_label, 5, 0)
  131. grid_lay.addWidget(self.box_combo, 5, 1)
  132. self.box_combo.hide()
  133. self.box_combo_label.hide()
  134. self.box_combo_type.hide()
  135. self.box_combo_type_label.hide()
  136. # Bounding Box Type #
  137. self.bbox_type_radio = RadioSet([
  138. {'label': _('Rectangular'), 'value': 'rect'},
  139. {"label": _("Minimal"), "value": "min"}
  140. ], stretch=False)
  141. self.bbox_type_label = QtWidgets.QLabel(_("Box Type:"))
  142. self.bbox_type_label.setToolTip(
  143. _("- 'Rectangular' - the bounding box will be of rectangular shape.\n "
  144. "- 'Minimal' - the bounding box will be the convex hull shape.")
  145. )
  146. grid_lay.addWidget(self.bbox_type_label, 6, 0)
  147. grid_lay.addWidget(self.bbox_type_radio, 6, 1)
  148. self.bbox_type_label.hide()
  149. self.bbox_type_radio.hide()
  150. separator_line = QtWidgets.QFrame()
  151. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  152. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  153. grid_lay.addWidget(separator_line, 7, 0, 1, 2)
  154. # Fill Type
  155. self.fill_type_radio = RadioSet([
  156. {'label': _('Solid'), 'value': 'solid'},
  157. {"label": _("Dots Grid"), "value": "dot"},
  158. {"label": _("Squares Grid"), "value": "square"},
  159. {"label": _("Lines Grid"), "value": "line"}
  160. ], orientation='vertical', stretch=False)
  161. self.fill_type_label = QtWidgets.QLabel(_("Fill Type:"))
  162. self.fill_type_label.setToolTip(
  163. _("- 'Solid' - copper thieving will be a solid polygon.\n "
  164. "- 'Dots Grid' - the empty area will be filled with a pattern of dots.\n"
  165. "- 'Squares Grid' - the empty area will be filled with a pattern of squares.\n"
  166. "- 'Lines Grid' - the empty area will be filled with a pattern of lines.")
  167. )
  168. grid_lay.addWidget(self.fill_type_label, 8, 0)
  169. grid_lay.addWidget(self.fill_type_radio, 8, 1)
  170. # DOTS FRAME
  171. self.dots_frame = QtWidgets.QFrame()
  172. self.dots_frame.setContentsMargins(0, 0, 0, 0)
  173. self.layout.addWidget(self.dots_frame)
  174. dots_grid = QtWidgets.QGridLayout()
  175. dots_grid.setColumnStretch(0, 0)
  176. dots_grid.setColumnStretch(1, 1)
  177. dots_grid.setContentsMargins(0, 0, 0, 0)
  178. self.dots_frame.setLayout(dots_grid)
  179. self.dots_frame.hide()
  180. self.dots_label = QtWidgets.QLabel('<b>%s</b>:' % _("Dots Grid Parameters"))
  181. dots_grid.addWidget(self.dots_label, 0, 0, 1, 2)
  182. # Dot diameter #
  183. self.dotdia_label = QtWidgets.QLabel('%s:' % _("Dia"))
  184. self.dotdia_label.setToolTip(
  185. _("Dot diameter in Dots Grid.")
  186. )
  187. self.dot_dia_entry = FCDoubleSpinner()
  188. self.dot_dia_entry.set_range(0.0, 9999.9999)
  189. self.dot_dia_entry.set_precision(self.decimals)
  190. self.dot_dia_entry.setSingleStep(0.1)
  191. dots_grid.addWidget(self.dotdia_label, 1, 0)
  192. dots_grid.addWidget(self.dot_dia_entry, 1, 1)
  193. # Dot spacing #
  194. self.dotspacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
  195. self.dotspacing_label.setToolTip(
  196. _("Distance between each two dots in Dots Grid.")
  197. )
  198. self.dot_spacing_entry = FCDoubleSpinner()
  199. self.dot_spacing_entry.set_range(0.0, 9999.9999)
  200. self.dot_spacing_entry.set_precision(self.decimals)
  201. self.dot_spacing_entry.setSingleStep(0.1)
  202. dots_grid.addWidget(self.dotspacing_label, 2, 0)
  203. dots_grid.addWidget(self.dot_spacing_entry, 2, 1)
  204. # SQUARES FRAME
  205. self.squares_frame = QtWidgets.QFrame()
  206. self.squares_frame.setContentsMargins(0, 0, 0, 0)
  207. self.layout.addWidget(self.squares_frame)
  208. squares_grid = QtWidgets.QGridLayout()
  209. squares_grid.setColumnStretch(0, 0)
  210. squares_grid.setColumnStretch(1, 1)
  211. squares_grid.setContentsMargins(0, 0, 0, 0)
  212. self.squares_frame.setLayout(squares_grid)
  213. self.squares_frame.hide()
  214. self.squares_label = QtWidgets.QLabel('<b>%s</b>:' % _("Squares Grid Parameters"))
  215. squares_grid.addWidget(self.squares_label, 0, 0, 1, 2)
  216. # Square Size #
  217. self.square_size_label = QtWidgets.QLabel('%s:' % _("Size"))
  218. self.square_size_label.setToolTip(
  219. _("Square side size in Squares Grid.")
  220. )
  221. self.square_size_entry = FCDoubleSpinner()
  222. self.square_size_entry.set_range(0.0, 9999.9999)
  223. self.square_size_entry.set_precision(self.decimals)
  224. self.square_size_entry.setSingleStep(0.1)
  225. squares_grid.addWidget(self.square_size_label, 1, 0)
  226. squares_grid.addWidget(self.square_size_entry, 1, 1)
  227. # Squares spacing #
  228. self.squares_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
  229. self.squares_spacing_label.setToolTip(
  230. _("Distance between each two squares in Squares Grid.")
  231. )
  232. self.squares_spacing_entry = FCDoubleSpinner()
  233. self.squares_spacing_entry.set_range(0.0, 9999.9999)
  234. self.squares_spacing_entry.set_precision(self.decimals)
  235. self.squares_spacing_entry.setSingleStep(0.1)
  236. squares_grid.addWidget(self.squares_spacing_label, 2, 0)
  237. squares_grid.addWidget(self.squares_spacing_entry, 2, 1)
  238. # LINES FRAME
  239. self.lines_frame = QtWidgets.QFrame()
  240. self.lines_frame.setContentsMargins(0, 0, 0, 0)
  241. self.layout.addWidget(self.lines_frame)
  242. lines_grid = QtWidgets.QGridLayout()
  243. lines_grid.setColumnStretch(0, 0)
  244. lines_grid.setColumnStretch(1, 1)
  245. lines_grid.setContentsMargins(0, 0, 0, 0)
  246. self.lines_frame.setLayout(lines_grid)
  247. self.lines_frame.hide()
  248. self.lines_label = QtWidgets.QLabel('<b>%s</b>:' % _("Lines Grid Parameters"))
  249. lines_grid.addWidget(self.lines_label, 0, 0, 1, 2)
  250. # Square Size #
  251. self.line_size_label = QtWidgets.QLabel('%s:' % _("Size"))
  252. self.line_size_label.setToolTip(
  253. _("Line thickness size in Lines Grid.")
  254. )
  255. self.line_size_entry = FCDoubleSpinner()
  256. self.line_size_entry.set_range(0.0, 9999.9999)
  257. self.line_size_entry.set_precision(self.decimals)
  258. self.line_size_entry.setSingleStep(0.1)
  259. lines_grid.addWidget(self.line_size_label, 1, 0)
  260. lines_grid.addWidget(self.line_size_entry, 1, 1)
  261. # Lines spacing #
  262. self.lines_spacing_label = QtWidgets.QLabel('%s:' % _("Spacing"))
  263. self.lines_spacing_label.setToolTip(
  264. _("Distance between each two lines in Lines Grid.")
  265. )
  266. self.lines_spacing_entry = FCDoubleSpinner()
  267. self.lines_spacing_entry.set_range(0.0, 9999.9999)
  268. self.lines_spacing_entry.set_precision(self.decimals)
  269. self.lines_spacing_entry.setSingleStep(0.1)
  270. lines_grid.addWidget(self.lines_spacing_label, 2, 0)
  271. lines_grid.addWidget(self.lines_spacing_entry, 2, 1)
  272. # ## Insert Copper Thieving
  273. self.fill_button = QtWidgets.QPushButton(_("Insert Copper thieving"))
  274. self.fill_button.setToolTip(
  275. _("Will add a polygon (may be split in multiple parts)\n"
  276. "that will surround the actual Gerber traces at a certain distance.")
  277. )
  278. self.layout.addWidget(self.fill_button)
  279. self.layout.addStretch()
  280. # Objects involved in Copper thieving
  281. self.grb_object = None
  282. self.ref_obj = None
  283. self.sel_rect = list()
  284. # store the flattened geometry here:
  285. self.flat_geometry = list()
  286. # Events ID
  287. self.mr = None
  288. self.mm = None
  289. # Mouse cursor positions
  290. self.mouse_is_dragging = False
  291. self.cursor_pos = (0, 0)
  292. self.first_click = False
  293. self.area_method = False
  294. # Tool properties
  295. self.clearance_val = None
  296. self.margin_val = None
  297. self.geo_steps_per_circle = 128
  298. # SIGNALS
  299. self.fill_button.clicked.connect(self.execute)
  300. self.box_combo_type.currentIndexChanged.connect(self.on_combo_box_type)
  301. self.reference_radio.group_toggle_fn = self.on_toggle_reference
  302. self.fill_type_radio.activated_custom.connect(self.on_thieving_type)
  303. def run(self, toggle=True):
  304. self.app.report_usage("ToolCopperThieving()")
  305. if toggle:
  306. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  307. if self.app.ui.splitter.sizes()[0] == 0:
  308. self.app.ui.splitter.setSizes([1, 1])
  309. else:
  310. try:
  311. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  312. # if tab is populated with the tool but it does not have the focus, focus on it
  313. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  314. # focus on Tool Tab
  315. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  316. else:
  317. self.app.ui.splitter.setSizes([0, 1])
  318. except AttributeError:
  319. pass
  320. else:
  321. if self.app.ui.splitter.sizes()[0] == 0:
  322. self.app.ui.splitter.setSizes([1, 1])
  323. FlatCAMTool.run(self)
  324. self.set_tool_ui()
  325. self.app.ui.notebook.setTabText(2, _("Copper Thieving Tool"))
  326. def install(self, icon=None, separator=None, **kwargs):
  327. FlatCAMTool.install(self, icon, separator, shortcut='ALT+F', **kwargs)
  328. def set_tool_ui(self):
  329. self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value()
  330. self.clearance_entry.set_value(float(self.app.defaults["tools_copper_thieving_clearance"]))
  331. self.margin_entry.set_value(float(self.app.defaults["tools_copper_thieving_margin"]))
  332. self.reference_radio.set_value(self.app.defaults["tools_copper_thieving_reference"])
  333. self.bbox_type_radio.set_value(self.app.defaults["tools_copper_thieving_box_type"])
  334. self.fill_type_radio.set_value(self.app.defaults["tools_copper_thieving_fill_type"])
  335. self.geo_steps_per_circle = int(self.app.defaults["tools_copper_thieving_circle_steps"])
  336. self.dot_dia_entry.set_value(self.app.defaults["tools_copper_thieving_dots_dia"])
  337. self.dot_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_dots_spacing"])
  338. self.square_size_entry.set_value(self.app.defaults["tools_copper_thieving_squares_size"])
  339. self.squares_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_squares_spacing"])
  340. self.line_size_entry.set_value(self.app.defaults["tools_copper_thieving_lines_size"])
  341. self.lines_spacing_entry.set_value(self.app.defaults["tools_copper_thieving_lines_spacing"])
  342. # INIT SECTION
  343. self.area_method = False
  344. def on_combo_box_type(self):
  345. obj_type = self.box_combo_type.currentIndex()
  346. self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  347. self.box_combo.setCurrentIndex(0)
  348. def on_toggle_reference(self):
  349. if self.reference_radio.get_value() == "itself" or self.reference_radio.get_value() == "area":
  350. self.box_combo.hide()
  351. self.box_combo_label.hide()
  352. self.box_combo_type.hide()
  353. self.box_combo_type_label.hide()
  354. else:
  355. self.box_combo.show()
  356. self.box_combo_label.show()
  357. self.box_combo_type.show()
  358. self.box_combo_type_label.show()
  359. if self.reference_radio.get_value() == "itself":
  360. self.bbox_type_label.show()
  361. self.bbox_type_radio.show()
  362. else:
  363. if self.fill_type_radio.get_value() == 'line':
  364. self.reference_radio.set_value('itself')
  365. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
  366. return
  367. self.bbox_type_label.hide()
  368. self.bbox_type_radio.hide()
  369. def on_thieving_type(self, choice):
  370. if choice == 'solid':
  371. self.dots_frame.hide()
  372. self.squares_frame.hide()
  373. self.lines_frame.hide()
  374. self.app.inform.emit(_("Solid fill selected."))
  375. elif choice == 'dot':
  376. self.dots_frame.show()
  377. self.squares_frame.hide()
  378. self.lines_frame.hide()
  379. self.app.inform.emit(_("Dots grid fill selected."))
  380. elif choice == 'square':
  381. self.dots_frame.hide()
  382. self.squares_frame.show()
  383. self.lines_frame.hide()
  384. self.app.inform.emit(_("Squares grid fill selected."))
  385. else:
  386. if self.reference_radio.get_value() != 'itself':
  387. self.reference_radio.set_value('itself')
  388. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Lines Grid works only for 'itself' reference ..."))
  389. self.dots_frame.hide()
  390. self.squares_frame.hide()
  391. self.lines_frame.show()
  392. def execute(self):
  393. self.app.call_source = "copper_thieving_tool"
  394. self.clearance_val = self.clearance_entry.get_value()
  395. self.margin_val = self.margin_entry.get_value()
  396. reference_method = self.reference_radio.get_value()
  397. # get the Gerber object on which the Copper thieving will be inserted
  398. selection_index = self.grb_object_combo.currentIndex()
  399. model_index = self.app.collection.index(selection_index, 0, self.grb_object_combo.rootModelIndex())
  400. try:
  401. self.grb_object = model_index.internalPointer().obj
  402. except Exception as e:
  403. log.debug("ToolCopperThieving.execute() --> %s" % str(e))
  404. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  405. return 'fail'
  406. if reference_method == 'itself':
  407. bound_obj_name = self.grb_object_combo.currentText()
  408. # Get reference object.
  409. try:
  410. self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
  411. except Exception as e:
  412. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), str(e)))
  413. return "Could not retrieve object: %s" % self.obj_name
  414. self.on_copper_thieving(
  415. thieving_obj=self.grb_object,
  416. c_val=self.clearance_val,
  417. margin=self.margin_val
  418. )
  419. elif reference_method == 'area':
  420. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the start point of the area."))
  421. self.area_method = True
  422. if self.app.is_legacy is False:
  423. self.app.plotcanvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
  424. self.app.plotcanvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
  425. self.app.plotcanvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
  426. else:
  427. self.app.plotcanvas.graph_event_disconnect(self.app.mp)
  428. self.app.plotcanvas.graph_event_disconnect(self.app.mm)
  429. self.app.plotcanvas.graph_event_disconnect(self.app.mr)
  430. self.mr = self.app.plotcanvas.graph_event_connect('mouse_release', self.on_mouse_release)
  431. self.mm = self.app.plotcanvas.graph_event_connect('mouse_move', self.on_mouse_move)
  432. elif reference_method == 'box':
  433. bound_obj_name = self.box_combo.currentText()
  434. # Get reference object.
  435. try:
  436. self.ref_obj = self.app.collection.get_by_name(bound_obj_name)
  437. except Exception as e:
  438. self.app.inform.emit('[ERROR_NOTCL] %s: %s' % (_("Could not retrieve object"), bound_obj_name))
  439. return "Could not retrieve object: %s. Error: %s" % (bound_obj_name, str(e))
  440. self.on_copper_thieving(
  441. thieving_obj=self.grb_object,
  442. ref_obj=self.ref_obj,
  443. c_val=self.clearance_val,
  444. margin=self.margin_val
  445. )
  446. # To be called after clicking on the plot.
  447. def on_mouse_release(self, event):
  448. if self.app.is_legacy is False:
  449. event_pos = event.pos
  450. # event_is_dragging = event.is_dragging
  451. right_button = 2
  452. else:
  453. event_pos = (event.xdata, event.ydata)
  454. # event_is_dragging = self.app.plotcanvas.is_dragging
  455. right_button = 3
  456. event_pos = self.app.plotcanvas.translate_coords(event_pos)
  457. # do clear area only for left mouse clicks
  458. if event.button == 1:
  459. if self.first_click is False:
  460. self.first_click = True
  461. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Click the end point of the filling area."))
  462. self.cursor_pos = self.app.plotcanvas.translate_coords(event_pos)
  463. if self.app.grid_status() is True:
  464. self.cursor_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
  465. else:
  466. self.app.inform.emit(_("Zone added. Click to start adding next zone or right click to finish."))
  467. self.app.delete_selection_shape()
  468. if self.app.grid_status() is True:
  469. curr_pos = self.app.geo_editor.snap(event_pos[0], event_pos[1])
  470. else:
  471. curr_pos = (event_pos[0], event_pos[1])
  472. x0, y0 = self.cursor_pos[0], self.cursor_pos[1]
  473. x1, y1 = curr_pos[0], curr_pos[1]
  474. pt1 = (x0, y0)
  475. pt2 = (x1, y0)
  476. pt3 = (x1, y1)
  477. pt4 = (x0, y1)
  478. new_rectangle = Polygon([pt1, pt2, pt3, pt4])
  479. self.sel_rect.append(new_rectangle)
  480. # add a temporary shape on canvas
  481. self.draw_tool_selection_shape(old_coords=(x0, y0), coords=(x1, y1))
  482. self.first_click = False
  483. return
  484. elif event.button == right_button and self.mouse_is_dragging is False:
  485. self.area_method = False
  486. self.first_click = False
  487. self.delete_tool_selection_shape()
  488. if self.app.is_legacy is False:
  489. self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
  490. self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
  491. else:
  492. self.app.plotcanvas.graph_event_disconnect(self.mr)
  493. self.app.plotcanvas.graph_event_disconnect(self.mm)
  494. self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
  495. self.app.on_mouse_click_over_plot)
  496. self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
  497. self.app.on_mouse_move_over_plot)
  498. self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
  499. self.app.on_mouse_click_release_over_plot)
  500. if len(self.sel_rect) == 0:
  501. return
  502. self.sel_rect = cascaded_union(self.sel_rect)
  503. if not isinstance(self.sel_rect, Iterable):
  504. self.sel_rect = [self.sel_rect]
  505. self.on_copper_thieving(
  506. thieving_obj=self.grb_object,
  507. ref_obj=self.sel_rect,
  508. c_val=self.clearance_val,
  509. margin=self.margin_val
  510. )
  511. # called on mouse move
  512. def on_mouse_move(self, event):
  513. if self.app.is_legacy is False:
  514. event_pos = event.pos
  515. event_is_dragging = event.is_dragging
  516. # right_button = 2
  517. else:
  518. event_pos = (event.xdata, event.ydata)
  519. event_is_dragging = self.app.plotcanvas.is_dragging
  520. # right_button = 3
  521. curr_pos = self.app.plotcanvas.translate_coords(event_pos)
  522. # detect mouse dragging motion
  523. if event_is_dragging is True:
  524. self.mouse_is_dragging = True
  525. else:
  526. self.mouse_is_dragging = False
  527. # update the cursor position
  528. if self.app.grid_status() is True:
  529. # Update cursor
  530. curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
  531. self.app.app_cursor.set_data(np.asarray([(curr_pos[0], curr_pos[1])]),
  532. symbol='++', edge_color=self.app.cursor_color_3D,
  533. size=self.app.defaults["global_cursor_size"])
  534. # update the positions on status bar
  535. self.app.ui.position_label.setText("&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: %.4f&nbsp;&nbsp; "
  536. "<b>Y</b>: %.4f" % (curr_pos[0], curr_pos[1]))
  537. if self.cursor_pos is None:
  538. self.cursor_pos = (0, 0)
  539. dx = curr_pos[0] - float(self.cursor_pos[0])
  540. dy = curr_pos[1] - float(self.cursor_pos[1])
  541. self.app.ui.rel_position_label.setText("<b>Dx</b>: %.4f&nbsp;&nbsp; <b>Dy</b>: "
  542. "%.4f&nbsp;&nbsp;&nbsp;&nbsp;" % (dx, dy))
  543. # draw the utility geometry
  544. if self.first_click:
  545. self.app.delete_selection_shape()
  546. self.app.draw_moving_selection_shape(old_coords=(self.cursor_pos[0], self.cursor_pos[1]),
  547. coords=(curr_pos[0], curr_pos[1]))
  548. def on_copper_thieving(self, thieving_obj, ref_obj=None, c_val=None, margin=None, run_threaded=True):
  549. """
  550. :param thieving_obj:
  551. :param ref_obj:
  552. :param c_val:
  553. :param margin:
  554. :param run_threaded:
  555. :return:
  556. """
  557. if run_threaded:
  558. proc = self.app.proc_container.new('%s ...' % _("Thieving"))
  559. else:
  560. QtWidgets.QApplication.processEvents()
  561. self.app.proc_container.view.set_busy('%s ...' % _("Thieving"))
  562. # #####################################################################
  563. # ####### Read the parameters #########################################
  564. # #####################################################################
  565. log.debug("Copper Thieving Tool started. Reading parameters.")
  566. self.app.inform.emit(_("Copper Thieving Tool started. Reading parameters."))
  567. ref_selected = self.reference_radio.get_value()
  568. if c_val is None:
  569. c_val = float(self.app.defaults["tools_copperfill_clearance"])
  570. if margin is None:
  571. margin = float(self.app.defaults["tools_copperfill_margin"])
  572. fill_type = self.fill_type_radio.get_value()
  573. dot_dia = self.dot_dia_entry.get_value()
  574. dot_spacing = self.dot_spacing_entry.get_value()
  575. square_size = self.square_size_entry.get_value()
  576. square_spacing = self.squares_spacing_entry.get_value()
  577. line_size = self.line_size_entry.get_value()
  578. line_spacing = self.lines_spacing_entry.get_value()
  579. # make sure that the source object solid geometry is an Iterable
  580. if not isinstance(self.grb_object.solid_geometry, Iterable):
  581. self.grb_object.solid_geometry = [self.grb_object.solid_geometry]
  582. def job_thread_thieving(app_obj):
  583. # #########################################################################################
  584. # Prepare isolation polygon. This will create the clearance over the Gerber features ######
  585. # #########################################################################################
  586. log.debug("Copper Thieving Tool. Preparing isolation polygons.")
  587. app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing isolation polygons."))
  588. # variables to display the percentage of work done
  589. geo_len = 0
  590. try:
  591. for pol in app_obj.grb_object.solid_geometry:
  592. geo_len += 1
  593. except TypeError:
  594. geo_len = 1
  595. old_disp_number = 0
  596. pol_nr = 0
  597. clearance_geometry = []
  598. try:
  599. for pol in app_obj.grb_object.solid_geometry:
  600. if app_obj.app.abort_flag:
  601. # graceful abort requested by the user
  602. raise FlatCAMApp.GracefulException
  603. clearance_geometry.append(
  604. pol.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
  605. )
  606. pol_nr += 1
  607. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  608. if old_disp_number < disp_number <= 100:
  609. app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
  610. (_("Thieving"), int(disp_number)))
  611. old_disp_number = disp_number
  612. except TypeError:
  613. # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
  614. # MultiPolygon (not an iterable)
  615. clearance_geometry.append(
  616. app_obj.grb_object.solid_geometry.buffer(c_val, int(int(app_obj.geo_steps_per_circle) / 4))
  617. )
  618. app_obj.app.proc_container.update_view_text(' %s ...' % _("Buffering"))
  619. clearance_geometry = unary_union(clearance_geometry)
  620. # #########################################################################################
  621. # Prepare the area to fill with copper. ###################################################
  622. # #########################################################################################
  623. log.debug("Copper Thieving Tool. Preparing areas to fill with copper.")
  624. app_obj.app.inform.emit(_("Copper Thieving Tool. Preparing areas to fill with copper."))
  625. try:
  626. if ref_obj is None or ref_obj == 'itself':
  627. working_obj = thieving_obj
  628. else:
  629. working_obj = ref_obj
  630. except Exception as e:
  631. log.debug("ToolCopperThieving.on_copper_thieving() --> %s" % str(e))
  632. return 'fail'
  633. app_obj.app.proc_container.update_view_text(' %s' % _("Working..."))
  634. if ref_selected == 'itself':
  635. geo_n = working_obj.solid_geometry
  636. try:
  637. if app_obj.bbox_type_radio.get_value() == 'min':
  638. if isinstance(geo_n, MultiPolygon):
  639. env_obj = geo_n.convex_hull
  640. elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
  641. (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
  642. env_obj = cascaded_union(geo_n)
  643. else:
  644. env_obj = cascaded_union(geo_n)
  645. env_obj = env_obj.convex_hull
  646. bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  647. else:
  648. if isinstance(geo_n, Polygon):
  649. bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
  650. elif isinstance(geo_n, list):
  651. geo_n = unary_union(geo_n)
  652. bounding_box = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre).exterior
  653. elif isinstance(geo_n, MultiPolygon):
  654. x0, y0, x1, y1 = geo_n.bounds
  655. geo = box(x0, y0, x1, y1)
  656. bounding_box = geo.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  657. else:
  658. app_obj.app.inform.emit(
  659. '[ERROR_NOTCL] %s: %s' % (_("Geometry not supported for bounding box"), type(geo_n))
  660. )
  661. return 'fail'
  662. except Exception as e:
  663. log.debug("ToolCopperFIll.on_copper_thieving() 'itself' --> %s" % str(e))
  664. app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("No object available."))
  665. return 'fail'
  666. elif ref_selected == 'area':
  667. geo_buff_list = []
  668. try:
  669. for poly in working_obj:
  670. if app_obj.app.abort_flag:
  671. # graceful abort requested by the user
  672. raise FlatCAMApp.GracefulException
  673. geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
  674. except TypeError:
  675. geo_buff_list.append(working_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
  676. bounding_box = MultiPolygon(geo_buff_list)
  677. else: # ref_selected == 'box'
  678. geo_n = working_obj.solid_geometry
  679. if isinstance(working_obj, FlatCAMGeometry):
  680. try:
  681. __ = iter(geo_n)
  682. except Exception as e:
  683. log.debug("ToolCopperFIll.on_copper_thieving() 'box' --> %s" % str(e))
  684. geo_n = [geo_n]
  685. geo_buff_list = []
  686. for poly in geo_n:
  687. if app_obj.app.abort_flag:
  688. # graceful abort requested by the user
  689. raise FlatCAMApp.GracefulException
  690. geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
  691. bounding_box = cascaded_union(geo_buff_list)
  692. elif isinstance(working_obj, FlatCAMGerber):
  693. geo_n = cascaded_union(geo_n).convex_hull
  694. bounding_box = cascaded_union(thieving_obj.solid_geometry).convex_hull.intersection(geo_n)
  695. bounding_box = bounding_box.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  696. else:
  697. app_obj.app.inform.emit('[ERROR_NOTCL] %s' % _("The reference object type is not supported."))
  698. return 'fail'
  699. log.debug("Copper Thieving Tool. Finished creating areas to fill with copper.")
  700. app_obj.app.inform.emit(_("Copper Thieving Tool. Appending new geometry and buffering."))
  701. # #########################################################################################
  702. # ########## Generate filling geometry. ###################################################
  703. # #########################################################################################
  704. new_solid_geometry = bounding_box.difference(clearance_geometry)
  705. # determine the bounding box polygon for the entire Gerber object to which we add copper thieving
  706. # if isinstance(geo_n, list):
  707. # env_obj = unary_union(geo_n).buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  708. # else:
  709. # env_obj = geo_n.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
  710. #
  711. # x0, y0, x1, y1 = env_obj.bounds
  712. # bounding_box = box(x0, y0, x1, y1)
  713. app_obj.app.proc_container.update_view_text(' %s' % _("Create geometry"))
  714. bounding_box = thieving_obj.solid_geometry.envelope.buffer(
  715. distance=margin,
  716. join_style=base.JOIN_STYLE.mitre
  717. )
  718. x0, y0, x1, y1 = bounding_box.bounds
  719. if fill_type == 'dot' or fill_type == 'square':
  720. # build the MultiPolygon of dots/squares that will fill the entire bounding box
  721. thieving_list = list()
  722. if fill_type == 'dot':
  723. radius = dot_dia / 2.0
  724. new_x = x0 + radius
  725. new_y = y0 + radius
  726. while new_x <= x1 - radius:
  727. while new_y <= y1 - radius:
  728. dot_geo = Point((new_x, new_y)).buffer(radius, resolution=64)
  729. thieving_list.append(dot_geo)
  730. new_y += dot_dia + dot_spacing
  731. new_x += dot_dia + dot_spacing
  732. new_y = y0 + radius
  733. else:
  734. h_size = square_size / 2.0
  735. new_x = x0 + h_size
  736. new_y = y0 + h_size
  737. while new_x <= x1 - h_size:
  738. while new_y <= y1 - h_size:
  739. a, b, c, d = (Point((new_x, new_y)).buffer(h_size)).bounds
  740. square_geo = box(a, b, c, d)
  741. thieving_list.append(square_geo)
  742. new_y += square_size + square_spacing
  743. new_x += square_size + square_spacing
  744. new_y = y0 + h_size
  745. thieving_box_geo = MultiPolygon(thieving_list)
  746. dx = bounding_box.centroid.x - thieving_box_geo.centroid.x
  747. dy = bounding_box.centroid.y - thieving_box_geo.centroid.y
  748. thieving_box_geo = affinity.translate(thieving_box_geo, xoff=dx, yoff=dy)
  749. try:
  750. _it = iter(new_solid_geometry)
  751. except TypeError:
  752. new_solid_geometry = [new_solid_geometry]
  753. try:
  754. _it = iter(thieving_box_geo)
  755. except TypeError:
  756. thieving_box_geo = [thieving_box_geo]
  757. thieving_geo = list()
  758. for dot_geo in thieving_box_geo:
  759. for geo_t in new_solid_geometry:
  760. if dot_geo.within(geo_t):
  761. thieving_geo.append(dot_geo)
  762. new_solid_geometry = thieving_geo
  763. if fill_type == 'line':
  764. half_thick_line = line_size / 2.0
  765. # create a thick polygon-line that surrounds the copper features
  766. outline_geometry = []
  767. try:
  768. for pol in app_obj.grb_object.solid_geometry:
  769. if app_obj.app.abort_flag:
  770. # graceful abort requested by the user
  771. raise FlatCAMApp.GracefulException
  772. outline_geometry.append(
  773. pol.buffer(c_val+half_thick_line, int(int(app_obj.geo_steps_per_circle) / 4))
  774. )
  775. pol_nr += 1
  776. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  777. if old_disp_number < disp_number <= 100:
  778. app_obj.app.proc_container.update_view_text(' %s ... %d%%' %
  779. (_("Buffering"), int(disp_number)))
  780. old_disp_number = disp_number
  781. except TypeError:
  782. # taking care of the case when the self.solid_geometry is just a single Polygon, not a list or a
  783. # MultiPolygon (not an iterable)
  784. outline_geometry.append(
  785. app_obj.grb_object.solid_geometry.buffer(
  786. c_val+half_thick_line,
  787. int(int(app_obj.geo_steps_per_circle) / 4)
  788. )
  789. )
  790. app_obj.app.proc_container.update_view_text(' %s' % _("Buffering"))
  791. outline_geometry = unary_union(outline_geometry)
  792. outline_line = list()
  793. try:
  794. for geo_o in outline_geometry:
  795. outline_line.append(
  796. geo_o.exterior.buffer(
  797. half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
  798. )
  799. )
  800. except TypeError:
  801. outline_line.append(
  802. outline_geometry.exterior.buffer(
  803. half_thick_line, resolution=int(int(app_obj.geo_steps_per_circle) / 4)
  804. )
  805. )
  806. outline_geometry = unary_union(outline_line)
  807. # create a polygon-line that surrounds in the inside the bounding box polygon of the target Gerber
  808. box_outline_geo = box(x0, y0, x1, y1).buffer(-half_thick_line)
  809. box_outline_geo_exterior = box_outline_geo.exterior
  810. box_outline_geometry = box_outline_geo_exterior.buffer(
  811. half_thick_line,
  812. resolution=int(int(app_obj.geo_steps_per_circle) / 4)
  813. )
  814. bx0, by0, bx1, by1 = box_outline_geo.bounds
  815. thieving_lines_geo = list()
  816. new_x = bx0
  817. new_y = by0
  818. while new_x <= x1 - half_thick_line:
  819. line_geo = LineString([(new_x, by0), (new_x, by1)]).buffer(
  820. half_thick_line,
  821. resolution=int(int(app_obj.geo_steps_per_circle) / 4)
  822. )
  823. thieving_lines_geo.append(line_geo)
  824. new_x += line_size + line_spacing
  825. while new_y <= y1 - half_thick_line:
  826. line_geo = LineString([(bx0, new_y), (bx1, new_y)]).buffer(
  827. half_thick_line,
  828. resolution=int(int(app_obj.geo_steps_per_circle) / 4)
  829. )
  830. thieving_lines_geo.append(line_geo)
  831. new_y += line_size + line_spacing
  832. # merge everything together
  833. diff_lines_geo = list()
  834. for line_poly in thieving_lines_geo:
  835. rest_line = line_poly.difference(clearance_geometry)
  836. diff_lines_geo.append(rest_line)
  837. app_obj.flatten([outline_geometry, box_outline_geometry, diff_lines_geo])
  838. new_solid_geometry = app_obj.flat_geometry
  839. app_obj.app.proc_container.update_view_text(' %s' % _("Append geometry"))
  840. geo_list = app_obj.grb_object.solid_geometry
  841. if isinstance(app_obj.grb_object.solid_geometry, MultiPolygon):
  842. geo_list = list(app_obj.grb_object.solid_geometry.geoms)
  843. if '0' not in app_obj.grb_object.apertures:
  844. app_obj.grb_object.apertures['0'] = dict()
  845. app_obj.grb_object.apertures['0']['geometry'] = list()
  846. app_obj.grb_object.apertures['0']['type'] = 'REG'
  847. app_obj.grb_object.apertures['0']['size'] = 0.0
  848. try:
  849. for poly in new_solid_geometry:
  850. # append to the new solid geometry
  851. geo_list.append(poly)
  852. # append into the '0' aperture
  853. geo_elem = dict()
  854. geo_elem['solid'] = poly
  855. geo_elem['follow'] = poly.exterior
  856. app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
  857. except TypeError:
  858. # append to the new solid geometry
  859. geo_list.append(new_solid_geometry)
  860. # append into the '0' aperture
  861. geo_elem = dict()
  862. geo_elem['solid'] = new_solid_geometry
  863. geo_elem['follow'] = new_solid_geometry.exterior
  864. app_obj.grb_object.apertures['0']['geometry'].append(deepcopy(geo_elem))
  865. app_obj.grb_object.solid_geometry = MultiPolygon(geo_list).buffer(0.0000001).buffer(-0.0000001)
  866. app_obj.app.proc_container.update_view_text(' %s' % _("Append source file"))
  867. # update the source file with the new geometry:
  868. app_obj.grb_object.source_file = app_obj.app.export_gerber(obj_name=app_obj.grb_object.options['name'],
  869. filename=None,
  870. local_use=app_obj.grb_object,
  871. use_thread=False)
  872. app_obj.app.proc_container.update_view_text(' %s' % '')
  873. app_obj.on_exit()
  874. app_obj.app.inform.emit('[success] %s' % _("Copper Thieving Tool done."))
  875. if run_threaded:
  876. self.app.worker_task.emit({'fcn': job_thread_thieving, 'params': [self]})
  877. else:
  878. job_thread_thieving(self)
  879. def replot(self, obj):
  880. def worker_task():
  881. with self.app.proc_container.new('%s...' % _("Plotting")):
  882. obj.plot()
  883. self.app.worker_task.emit({'fcn': worker_task, 'params': []})
  884. def on_exit(self):
  885. # plot the object
  886. self.replot(obj=self.grb_object)
  887. # update the bounding box values
  888. try:
  889. a, b, c, d = self.grb_object.bounds()
  890. self.grb_object.options['xmin'] = a
  891. self.grb_object.options['ymin'] = b
  892. self.grb_object.options['xmax'] = c
  893. self.grb_object.options['ymax'] = d
  894. except Exception as e:
  895. log.debug("ToolCopperThieving.on_exit() bounds error --> %s" % str(e))
  896. # reset the variables
  897. self.grb_object = None
  898. self.ref_obj = None
  899. self.sel_rect = list()
  900. # Events ID
  901. self.mr = None
  902. self.mm = None
  903. # Mouse cursor positions
  904. self.mouse_is_dragging = False
  905. self.cursor_pos = (0, 0)
  906. self.first_click = False
  907. # if True it means we exited from tool in the middle of area adding therefore disconnect the events
  908. if self.area_method is True:
  909. self.app.delete_selection_shape()
  910. self.area_method = False
  911. if self.app.is_legacy is False:
  912. self.app.plotcanvas.graph_event_disconnect('mouse_release', self.on_mouse_release)
  913. self.app.plotcanvas.graph_event_disconnect('mouse_move', self.on_mouse_move)
  914. else:
  915. self.app.plotcanvas.graph_event_disconnect(self.mr)
  916. self.app.plotcanvas.graph_event_disconnect(self.mm)
  917. self.app.mp = self.app.plotcanvas.graph_event_connect('mouse_press',
  918. self.app.on_mouse_click_over_plot)
  919. self.app.mm = self.app.plotcanvas.graph_event_connect('mouse_move',
  920. self.app.on_mouse_move_over_plot)
  921. self.app.mr = self.app.plotcanvas.graph_event_connect('mouse_release',
  922. self.app.on_mouse_click_release_over_plot)
  923. self.app.call_source = "app"
  924. self.app.inform.emit('[success] %s' % _("Copper Thieving Tool exit."))
  925. def flatten(self, geometry):
  926. """
  927. Creates a list of non-iterable linear geometry objects.
  928. :param geometry: Shapely type or list or list of list of such.
  929. Results are placed in self.flat_geometry
  930. """
  931. # ## If iterable, expand recursively.
  932. try:
  933. for geo in geometry:
  934. if geo is not None:
  935. self.flatten(geometry=geo)
  936. # ## Not iterable, do the actual indexing and add.
  937. except TypeError:
  938. self.flat_geometry.append(geometry)
  939. return self.flat_geometry