ToolPanelize.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  1. # ########################################################## ##
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # File Author: Marius Adrian Stanciu (c) #
  5. # Date: 3/10/2019 #
  6. # MIT Licence #
  7. # ########################################################## ##
  8. from FlatCAMTool import FlatCAMTool
  9. from copy import copy, deepcopy
  10. from ObjectCollection import *
  11. import time
  12. import gettext
  13. import FlatCAMTranslation as fcTranslate
  14. import builtins
  15. fcTranslate.apply_language('strings')
  16. if '_' not in builtins.__dict__:
  17. _ = gettext.gettext
  18. class Panelize(FlatCAMTool):
  19. toolName = _("Panelize PCB")
  20. def __init__(self, app):
  21. super(Panelize, self).__init__(self)
  22. self.app = app
  23. # ## Title
  24. title_label = QtWidgets.QLabel("%s" % self.toolName)
  25. title_label.setStyleSheet("""
  26. QLabel
  27. {
  28. font-size: 16px;
  29. font-weight: bold;
  30. }
  31. """)
  32. self.layout.addWidget(title_label)
  33. # Form Layout
  34. form_layout_0 = QtWidgets.QFormLayout()
  35. self.layout.addLayout(form_layout_0)
  36. # Type of object to be panelized
  37. self.type_obj_combo = QtWidgets.QComboBox()
  38. self.type_obj_combo.addItem("Gerber")
  39. self.type_obj_combo.addItem("Excellon")
  40. self.type_obj_combo.addItem("Geometry")
  41. self.type_obj_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
  42. self.type_obj_combo.setItemIcon(1, QtGui.QIcon("share/drill16.png"))
  43. self.type_obj_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
  44. self.type_obj_combo_label = QtWidgets.QLabel('%s:' % _("Object Type"))
  45. self.type_obj_combo_label.setToolTip(
  46. _("Specify the type of object to be panelized\n"
  47. "It can be of type: Gerber, Excellon or Geometry.\n"
  48. "The selection here decide the type of objects that will be\n"
  49. "in the Object combobox.")
  50. )
  51. form_layout_0.addRow(self.type_obj_combo_label, self.type_obj_combo)
  52. # Object to be panelized
  53. self.object_combo = QtWidgets.QComboBox()
  54. self.object_combo.setModel(self.app.collection)
  55. self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  56. self.object_combo.setCurrentIndex(1)
  57. self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
  58. self.object_label.setToolTip(
  59. _("Object to be panelized. This means that it will\n"
  60. "be duplicated in an array of rows and columns.")
  61. )
  62. form_layout_0.addRow(self.object_label, self.object_combo)
  63. form_layout_0.addRow(QtWidgets.QLabel(""))
  64. # Form Layout
  65. form_layout = QtWidgets.QFormLayout()
  66. self.layout.addLayout(form_layout)
  67. # Type of box Panel object
  68. self.reference_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
  69. {'label': _('Bounding Box'), 'value': 'bbox'}])
  70. self.box_label = QtWidgets.QLabel("<b>%s:</b>" % _("Penelization Reference"))
  71. self.box_label.setToolTip(
  72. _("Choose the reference for panelization:\n"
  73. "- Object = the bounding box of a different object\n"
  74. "- Bounding Box = the bounding box of the object to be panelized\n"
  75. "\n"
  76. "The reference is useful when doing panelization for more than one\n"
  77. "object. The spacings (really offsets) will be applied in reference\n"
  78. "to this reference object therefore maintaining the panelized\n"
  79. "objects in sync.")
  80. )
  81. form_layout.addRow(self.box_label)
  82. form_layout.addRow(self.reference_radio)
  83. # Type of Box Object to be used as an envelope for panelization
  84. self.type_box_combo = QtWidgets.QComboBox()
  85. self.type_box_combo.addItem("Gerber")
  86. self.type_box_combo.addItem("Excellon")
  87. self.type_box_combo.addItem("Geometry")
  88. # we get rid of item1 ("Excellon") as it is not suitable for use as a "box" for panelizing
  89. self.type_box_combo.view().setRowHidden(1, True)
  90. self.type_box_combo.setItemIcon(0, QtGui.QIcon("share/flatcam_icon16.png"))
  91. self.type_box_combo.setItemIcon(2, QtGui.QIcon("share/geometry16.png"))
  92. self.type_box_combo_label = QtWidgets.QLabel('%s:' % _("Box Type"))
  93. self.type_box_combo_label.setToolTip(
  94. _("Specify the type of object to be used as an container for\n"
  95. "panelization. It can be: Gerber or Geometry type.\n"
  96. "The selection here decide the type of objects that will be\n"
  97. "in the Box Object combobox.")
  98. )
  99. form_layout.addRow(self.type_box_combo_label, self.type_box_combo)
  100. # Box
  101. self.box_combo = QtWidgets.QComboBox()
  102. self.box_combo.setModel(self.app.collection)
  103. self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  104. self.box_combo.setCurrentIndex(1)
  105. self.box_combo_label = QtWidgets.QLabel('%s:' % _("Box Object"))
  106. self.box_combo_label.setToolTip(
  107. _("The actual object that is used a container for the\n "
  108. "selected object that is to be panelized.")
  109. )
  110. form_layout.addRow(self.box_combo_label, self.box_combo)
  111. form_layout.addRow(QtWidgets.QLabel(""))
  112. panel_data_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Data"))
  113. panel_data_label.setToolTip(
  114. _("This informations will shape the resulting panel.\n"
  115. "The number of rows and columns will set how many\n"
  116. "duplicates of the original geometry will be generated.\n"
  117. "\n"
  118. "The spacings will set the distance between any two\n"
  119. "elements of the panel array.")
  120. )
  121. form_layout.addRow(panel_data_label)
  122. # Spacing Columns
  123. self.spacing_columns = FCEntry()
  124. self.spacing_columns_label = QtWidgets.QLabel('%s:' % _("Spacing cols"))
  125. self.spacing_columns_label.setToolTip(
  126. _("Spacing between columns of the desired panel.\n"
  127. "In current units.")
  128. )
  129. form_layout.addRow(self.spacing_columns_label, self.spacing_columns)
  130. # Spacing Rows
  131. self.spacing_rows = FCEntry()
  132. self.spacing_rows_label = QtWidgets.QLabel('%s:' % _("Spacing rows"))
  133. self.spacing_rows_label.setToolTip(
  134. _("Spacing between rows of the desired panel.\n"
  135. "In current units.")
  136. )
  137. form_layout.addRow(self.spacing_rows_label, self.spacing_rows)
  138. # Columns
  139. self.columns = FCEntry()
  140. self.columns_label = QtWidgets.QLabel('%s:' % _("Columns"))
  141. self.columns_label.setToolTip(
  142. _("Number of columns of the desired panel")
  143. )
  144. form_layout.addRow(self.columns_label, self.columns)
  145. # Rows
  146. self.rows = FCEntry()
  147. self.rows_label = QtWidgets.QLabel('%s:' % _("Rows"))
  148. self.rows_label.setToolTip(
  149. _("Number of rows of the desired panel")
  150. )
  151. form_layout.addRow(self.rows_label, self.rows)
  152. form_layout.addRow(QtWidgets.QLabel(""))
  153. # Type of resulting Panel object
  154. self.panel_type_radio = RadioSet([{'label': _('Gerber'), 'value': 'gerber'},
  155. {'label': _('Geo'), 'value': 'geometry'}])
  156. self.panel_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Panel Type"))
  157. self.panel_type_label.setToolTip(
  158. _("Choose the type of object for the panel object:\n"
  159. "- Geometry\n"
  160. "- Gerber")
  161. )
  162. form_layout.addRow(self.panel_type_label)
  163. form_layout.addRow(self.panel_type_radio)
  164. # Constrains
  165. self.constrain_cb = FCCheckBox('%s:' % _("Constrain panel within"))
  166. self.constrain_cb.setToolTip(
  167. _("Area define by DX and DY within to constrain the panel.\n"
  168. "DX and DY values are in current units.\n"
  169. "Regardless of how many columns and rows are desired,\n"
  170. "the final panel will have as many columns and rows as\n"
  171. "they fit completely within selected area.")
  172. )
  173. form_layout.addRow(self.constrain_cb)
  174. self.x_width_entry = FCEntry()
  175. self.x_width_lbl = QtWidgets.QLabel('%s:' % _("Width (DX)"))
  176. self.x_width_lbl.setToolTip(
  177. _("The width (DX) within which the panel must fit.\n"
  178. "In current units.")
  179. )
  180. form_layout.addRow(self.x_width_lbl, self.x_width_entry)
  181. self.y_height_entry = FCEntry()
  182. self.y_height_lbl = QtWidgets.QLabel('%s:' % _("Height (DY)"))
  183. self.y_height_lbl.setToolTip(
  184. _("The height (DY)within which the panel must fit.\n"
  185. "In current units.")
  186. )
  187. form_layout.addRow(self.y_height_lbl, self.y_height_entry)
  188. self.constrain_sel = OptionalInputSection(
  189. self.constrain_cb, [self.x_width_lbl, self.x_width_entry, self.y_height_lbl, self.y_height_entry])
  190. # Buttons
  191. hlay_2 = QtWidgets.QHBoxLayout()
  192. self.layout.addLayout(hlay_2)
  193. hlay_2.addStretch()
  194. self.panelize_object_button = QtWidgets.QPushButton(_("Panelize Object"))
  195. self.panelize_object_button.setToolTip(
  196. _("Panelize the specified object around the specified box.\n"
  197. "In other words it creates multiple copies of the source object,\n"
  198. "arranged in a 2D array of rows and columns.")
  199. )
  200. hlay_2.addWidget(self.panelize_object_button)
  201. self.layout.addStretch()
  202. # Signals
  203. self.reference_radio.activated_custom.connect(self.on_reference_radio_changed)
  204. self.panelize_object_button.clicked.connect(self.on_panelize)
  205. self.type_obj_combo.currentIndexChanged.connect(self.on_type_obj_index_changed)
  206. self.type_box_combo.currentIndexChanged.connect(self.on_type_box_index_changed)
  207. # list to hold the temporary objects
  208. self.objs = []
  209. # final name for the panel object
  210. self.outname = ""
  211. # flag to signal the constrain was activated
  212. self.constrain_flag = False
  213. def run(self, toggle=True):
  214. self.app.report_usage("ToolPanelize()")
  215. if toggle:
  216. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  217. if self.app.ui.splitter.sizes()[0] == 0:
  218. self.app.ui.splitter.setSizes([1, 1])
  219. else:
  220. try:
  221. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  222. # if tab is populated with the tool but it does not have the focus, focus on it
  223. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  224. # focus on Tool Tab
  225. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  226. else:
  227. self.app.ui.splitter.setSizes([0, 1])
  228. except AttributeError:
  229. pass
  230. else:
  231. if self.app.ui.splitter.sizes()[0] == 0:
  232. self.app.ui.splitter.setSizes([1, 1])
  233. FlatCAMTool.run(self)
  234. self.set_tool_ui()
  235. self.app.ui.notebook.setTabText(2, _("Panel. Tool"))
  236. def install(self, icon=None, separator=None, **kwargs):
  237. FlatCAMTool.install(self, icon, separator, shortcut='ALT+Z', **kwargs)
  238. def set_tool_ui(self):
  239. self.reset_fields()
  240. self.reference_radio.set_value('bbox')
  241. sp_c = self.app.defaults["tools_panelize_spacing_columns"] if \
  242. self.app.defaults["tools_panelize_spacing_columns"] else 0.0
  243. self.spacing_columns.set_value(float(sp_c))
  244. sp_r = self.app.defaults["tools_panelize_spacing_rows"] if \
  245. self.app.defaults["tools_panelize_spacing_rows"] else 0.0
  246. self.spacing_rows.set_value(float(sp_r))
  247. rr = self.app.defaults["tools_panelize_rows"] if \
  248. self.app.defaults["tools_panelize_rows"] else 0.0
  249. self.rows.set_value(int(rr))
  250. cc = self.app.defaults["tools_panelize_columns"] if \
  251. self.app.defaults["tools_panelize_columns"] else 0.0
  252. self.columns.set_value(int(cc))
  253. c_cb = self.app.defaults["tools_panelize_constrain"] if \
  254. self.app.defaults["tools_panelize_constrain"] else False
  255. self.constrain_cb.set_value(c_cb)
  256. x_w = self.app.defaults["tools_panelize_constrainx"] if \
  257. self.app.defaults["tools_panelize_constrainx"] else 0.0
  258. self.x_width_entry.set_value(float(x_w))
  259. y_w = self.app.defaults["tools_panelize_constrainy"] if \
  260. self.app.defaults["tools_panelize_constrainy"] else 0.0
  261. self.y_height_entry.set_value(float(y_w))
  262. panel_type = self.app.defaults["tools_panelize_panel_type"] if \
  263. self.app.defaults["tools_panelize_panel_type"] else 'gerber'
  264. self.panel_type_radio.set_value(panel_type)
  265. def on_type_obj_index_changed(self):
  266. obj_type = self.type_obj_combo.currentIndex()
  267. self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  268. self.object_combo.setCurrentIndex(0)
  269. # hide the panel type for Excellons, the panel can be only of type Geometry
  270. if self.type_obj_combo.currentText() != 'Excellon':
  271. self.panel_type_label.setDisabled(False)
  272. self.panel_type_radio.setDisabled(False)
  273. else:
  274. self.panel_type_label.setDisabled(True)
  275. self.panel_type_radio.setDisabled(True)
  276. self.panel_type_radio.set_value('geometry')
  277. def on_type_box_index_changed(self):
  278. obj_type = self.type_box_combo.currentIndex()
  279. self.box_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  280. self.box_combo.setCurrentIndex(0)
  281. def on_reference_radio_changed(self, current_val):
  282. if current_val == 'object':
  283. self.type_box_combo.setDisabled(False)
  284. self.type_box_combo_label.setDisabled(False)
  285. self.box_combo.setDisabled(False)
  286. self.box_combo_label.setDisabled(False)
  287. else:
  288. self.type_box_combo.setDisabled(True)
  289. self.type_box_combo_label.setDisabled(True)
  290. self.box_combo.setDisabled(True)
  291. self.box_combo_label.setDisabled(True)
  292. def on_panelize(self):
  293. name = self.object_combo.currentText()
  294. # Get source object.
  295. try:
  296. obj = self.app.collection.get_by_name(str(name))
  297. except Exception as e:
  298. log.debug("Panelize.on_panelize() --> %s" % str(e))
  299. self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
  300. (_("Could not retrieve object"), name))
  301. return "Could not retrieve object: %s" % name
  302. panel_obj = obj
  303. if panel_obj is None:
  304. self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
  305. (_("Object not found"), panel_obj))
  306. return "Object not found: %s" % panel_obj
  307. boxname = self.box_combo.currentText()
  308. try:
  309. box = self.app.collection.get_by_name(boxname)
  310. except Exception as e:
  311. log.debug("Panelize.on_panelize() --> %s" % str(e))
  312. self.app.inform.emit('[ERROR_NOTCL] %s: %s' %
  313. (_("Could not retrieve object"), boxname))
  314. return "Could not retrieve object: %s" % boxname
  315. if box is None:
  316. self.app.inform.emit('[WARNING_NOTCL]%s: %s' %
  317. (_("No object Box. Using instead"), panel_obj))
  318. self.reference_radio.set_value('bbox')
  319. if self.reference_radio.get_value() == 'bbox':
  320. box = panel_obj
  321. self.outname = name + '_panelized'
  322. try:
  323. spacing_columns = float(self.spacing_columns.get_value())
  324. except ValueError:
  325. # try to convert comma to decimal point. if it's still not working error message and return
  326. try:
  327. spacing_columns = float(self.spacing_columns.get_value().replace(',', '.'))
  328. except ValueError:
  329. self.app.inform.emit('[ERROR_NOTCL] %s' %
  330. _("Wrong value format entered, use a number."))
  331. return
  332. spacing_columns = spacing_columns if spacing_columns is not None else 0
  333. try:
  334. spacing_rows = float(self.spacing_rows.get_value())
  335. except ValueError:
  336. # try to convert comma to decimal point. if it's still not working error message and return
  337. try:
  338. spacing_rows = float(self.spacing_rows.get_value().replace(',', '.'))
  339. except ValueError:
  340. self.app.inform.emit('[ERROR_NOTCL] %s' %
  341. _("Wrong value format entered, use a number."))
  342. return
  343. spacing_rows = spacing_rows if spacing_rows is not None else 0
  344. try:
  345. rows = int(self.rows.get_value())
  346. except ValueError:
  347. # try to convert comma to decimal point. if it's still not working error message and return
  348. try:
  349. rows = float(self.rows.get_value().replace(',', '.'))
  350. rows = int(rows)
  351. except ValueError:
  352. self.app.inform.emit('[ERROR_NOTCL] %s' %
  353. _("Wrong value format entered, use a number."))
  354. return
  355. rows = rows if rows is not None else 1
  356. try:
  357. columns = int(self.columns.get_value())
  358. except ValueError:
  359. # try to convert comma to decimal point. if it's still not working error message and return
  360. try:
  361. columns = float(self.columns.get_value().replace(',', '.'))
  362. columns = int(columns)
  363. except ValueError:
  364. self.app.inform.emit('[ERROR_NOTCL] %s' %
  365. _("Wrong value format entered, use a number."))
  366. return
  367. columns = columns if columns is not None else 1
  368. try:
  369. constrain_dx = float(self.x_width_entry.get_value())
  370. except ValueError:
  371. # try to convert comma to decimal point. if it's still not working error message and return
  372. try:
  373. constrain_dx = float(self.x_width_entry.get_value().replace(',', '.'))
  374. except ValueError:
  375. self.app.inform.emit('[ERROR_NOTCL] %s' %
  376. _("Wrong value format entered, use a number."))
  377. return
  378. try:
  379. constrain_dy = float(self.y_height_entry.get_value())
  380. except ValueError:
  381. # try to convert comma to decimal point. if it's still not working error message and return
  382. try:
  383. constrain_dy = float(self.y_height_entry.get_value().replace(',', '.'))
  384. except ValueError:
  385. self.app.inform.emit('[ERROR_NOTCL] %s' %
  386. _("Wrong value format entered, use a number."))
  387. return
  388. panel_type = str(self.panel_type_radio.get_value())
  389. if 0 in {columns, rows}:
  390. self.app.inform.emit('[ERROR_NOTCL] %s' %
  391. _("Columns or Rows are zero value. Change them to a positive integer."))
  392. return "Columns or Rows are zero value. Change them to a positive integer."
  393. xmin, ymin, xmax, ymax = box.bounds()
  394. lenghtx = xmax - xmin + spacing_columns
  395. lenghty = ymax - ymin + spacing_rows
  396. # check if constrain within an area is desired
  397. if self.constrain_cb.isChecked():
  398. panel_lengthx = ((xmax - xmin) * columns) + (spacing_columns * (columns - 1))
  399. panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
  400. # adjust the number of columns and/or rows so the panel will fit within the panel constraint area
  401. if (panel_lengthx > constrain_dx) or (panel_lengthy > constrain_dy):
  402. self.constrain_flag = True
  403. while panel_lengthx > constrain_dx:
  404. columns -= 1
  405. panel_lengthx = ((xmax - xmin) * columns) + (spacing_columns * (columns - 1))
  406. while panel_lengthy > constrain_dy:
  407. rows -= 1
  408. panel_lengthy = ((ymax - ymin) * rows) + (spacing_rows * (rows - 1))
  409. def panelize_2():
  410. if panel_obj is not None:
  411. self.app.inform.emit(_("Generating panel ... "))
  412. self.app.progress.emit(0)
  413. def job_init_excellon(obj_fin, app_obj):
  414. currenty = 0.0
  415. self.app.progress.emit(10)
  416. obj_fin.tools = panel_obj.tools.copy()
  417. obj_fin.drills = []
  418. obj_fin.slots = []
  419. obj_fin.solid_geometry = []
  420. for option in panel_obj.options:
  421. if option is not 'name':
  422. try:
  423. obj_fin.options[option] = panel_obj.options[option]
  424. except KeyError:
  425. log.warning("Failed to copy option. %s" % str(option))
  426. geo_len_drills = len(panel_obj.drills) if panel_obj.drills else 0
  427. geo_len_slots = len(panel_obj.slots) if panel_obj.slots else 0
  428. element = 0
  429. for row in range(rows):
  430. currentx = 0.0
  431. for col in range(columns):
  432. element += 1
  433. disp_number = 0
  434. old_disp_number = 0
  435. if panel_obj.drills:
  436. drill_nr = 0
  437. for tool_dict in panel_obj.drills:
  438. if self.app.abort_flag:
  439. # graceful abort requested by the user
  440. raise FlatCAMApp.GracefulException
  441. point_offseted = affinity.translate(tool_dict['point'], currentx, currenty)
  442. obj_fin.drills.append(
  443. {
  444. "point": point_offseted,
  445. "tool": tool_dict['tool']
  446. }
  447. )
  448. drill_nr += 1
  449. disp_number = int(np.interp(drill_nr, [0, geo_len_drills], [0, 100]))
  450. if disp_number > old_disp_number and disp_number <= 100:
  451. self.app.proc_container.update_view_text(' %s: %d D:%d%%' %
  452. (_("Copy"),
  453. int(element),
  454. disp_number))
  455. old_disp_number = disp_number
  456. if panel_obj.slots:
  457. slot_nr = 0
  458. for tool_dict in panel_obj.slots:
  459. if self.app.abort_flag:
  460. # graceful abort requested by the user
  461. raise FlatCAMApp.GracefulException
  462. start_offseted = affinity.translate(tool_dict['start'], currentx, currenty)
  463. stop_offseted = affinity.translate(tool_dict['stop'], currentx, currenty)
  464. obj_fin.slots.append(
  465. {
  466. "start": start_offseted,
  467. "stop": stop_offseted,
  468. "tool": tool_dict['tool']
  469. }
  470. )
  471. slot_nr += 1
  472. disp_number = int(np.interp(slot_nr, [0, geo_len_slots], [0, 100]))
  473. if disp_number > old_disp_number and disp_number <= 100:
  474. self.app.proc_container.update_view_text(' %s: %d S:%d%%' %
  475. (_("Copy"),
  476. int(element),
  477. disp_number))
  478. old_disp_number = disp_number
  479. currentx += lenghtx
  480. currenty += lenghty
  481. obj_fin.create_geometry()
  482. obj_fin.zeros = panel_obj.zeros
  483. obj_fin.units = panel_obj.units
  484. self.app.proc_container.update_view_text('')
  485. def job_init_geometry(obj_fin, app_obj):
  486. currentx = 0.0
  487. currenty = 0.0
  488. def translate_recursion(geom):
  489. if type(geom) == list:
  490. geoms = list()
  491. for local_geom in geom:
  492. res_geo = translate_recursion(local_geom)
  493. try:
  494. geoms += res_geo
  495. except TypeError:
  496. geoms.append(res_geo)
  497. return geoms
  498. else:
  499. return affinity.translate(geom, xoff=currentx, yoff=currenty)
  500. obj_fin.solid_geometry = []
  501. # create the initial structure on which to create the panel
  502. if isinstance(panel_obj, FlatCAMGeometry):
  503. obj_fin.multigeo = panel_obj.multigeo
  504. obj_fin.tools = deepcopy(panel_obj.tools)
  505. if panel_obj.multigeo is True:
  506. for tool in panel_obj.tools:
  507. obj_fin.tools[tool]['solid_geometry'][:] = []
  508. elif isinstance(panel_obj, FlatCAMGerber):
  509. obj_fin.apertures = deepcopy(panel_obj.apertures)
  510. for ap in obj_fin.apertures:
  511. obj_fin.apertures[ap]['geometry'] = list()
  512. # find the number of polygons in the source solid_geometry
  513. geo_len = 0
  514. if isinstance(panel_obj, FlatCAMGeometry):
  515. if panel_obj.multigeo is True:
  516. for tool in panel_obj.tools:
  517. try:
  518. for pol in panel_obj.tools[tool]['solid_geometry']:
  519. geo_len += 1
  520. except TypeError:
  521. geo_len = 1
  522. else:
  523. try:
  524. for pol in panel_obj.solid_geometry:
  525. geo_len += 1
  526. except TypeError:
  527. geo_len = 1
  528. elif isinstance(panel_obj, FlatCAMGerber):
  529. for ap in panel_obj.apertures:
  530. for elem in panel_obj.apertures[ap]['geometry']:
  531. geo_len += 1
  532. self.app.progress.emit(0)
  533. element = 0
  534. for row in range(rows):
  535. currentx = 0.0
  536. for col in range(columns):
  537. element += 1
  538. disp_number = 0
  539. old_disp_number = 0
  540. if isinstance(panel_obj, FlatCAMGeometry):
  541. if panel_obj.multigeo is True:
  542. for tool in panel_obj.tools:
  543. if self.app.abort_flag:
  544. # graceful abort requested by the user
  545. raise FlatCAMApp.GracefulException
  546. # geo = translate_recursion(panel_obj.tools[tool]['solid_geometry'])
  547. # if isinstance(geo, list):
  548. # obj_fin.tools[tool]['solid_geometry'] += geo
  549. # else:
  550. # obj_fin.tools[tool]['solid_geometry'].append(geo)
  551. # calculate the number of polygons
  552. geo_len = len(panel_obj.tools[tool]['solid_geometry'])
  553. pol_nr = 0
  554. for geo_el in panel_obj.tools[tool]['solid_geometry']:
  555. trans_geo = translate_recursion(geo_el)
  556. obj_fin.tools[tool]['solid_geometry'].append(trans_geo)
  557. pol_nr += 1
  558. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  559. if old_disp_number < disp_number <= 100:
  560. self.app.proc_container.update_view_text(' %s: %d %d%%' %
  561. (_("Copy"),
  562. int(element),
  563. disp_number))
  564. old_disp_number = disp_number
  565. else:
  566. # geo = translate_recursion(panel_obj.solid_geometry)
  567. # if isinstance(geo, list):
  568. # obj_fin.solid_geometry += geo
  569. # else:
  570. # obj_fin.solid_geometry.append(geo)
  571. if self.app.abort_flag:
  572. # graceful abort requested by the user
  573. raise FlatCAMApp.GracefulException
  574. try:
  575. # calculate the number of polygons
  576. geo_len = len(panel_obj.solid_geometry)
  577. except TypeError:
  578. geo_len = 1
  579. pol_nr = 0
  580. try:
  581. for geo_el in panel_obj.solid_geometry:
  582. if self.app.abort_flag:
  583. # graceful abort requested by the user
  584. raise FlatCAMApp.GracefulException
  585. trans_geo = translate_recursion(geo_el)
  586. obj_fin.solid_geometry.append(trans_geo)
  587. pol_nr += 1
  588. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  589. if old_disp_number < disp_number <= 100:
  590. self.app.proc_container.update_view_text(' %s: %d %d%%' %
  591. (_("Copy"),
  592. int(element),
  593. disp_number))
  594. old_disp_number = disp_number
  595. except TypeError:
  596. trans_geo = translate_recursion(panel_obj.solid_geometry)
  597. obj_fin.solid_geometry.append(trans_geo)
  598. else:
  599. # geo = translate_recursion(panel_obj.solid_geometry)
  600. # if isinstance(geo, list):
  601. # obj_fin.solid_geometry += geo
  602. # else:
  603. # obj_fin.solid_geometry.append(geo)
  604. if self.app.abort_flag:
  605. # graceful abort requested by the user
  606. raise FlatCAMApp.GracefulException
  607. try:
  608. for geo_el in panel_obj.solid_geometry:
  609. if self.app.abort_flag:
  610. # graceful abort requested by the user
  611. raise FlatCAMApp.GracefulException
  612. trans_geo = translate_recursion(geo_el)
  613. obj_fin.solid_geometry.append(trans_geo)
  614. except TypeError:
  615. trans_geo = translate_recursion(panel_obj.solid_geometry)
  616. obj_fin.solid_geometry.append(trans_geo)
  617. for apid in panel_obj.apertures:
  618. if self.app.abort_flag:
  619. # graceful abort requested by the user
  620. raise FlatCAMApp.GracefulException
  621. try:
  622. # calculate the number of polygons
  623. geo_len = len(panel_obj.apertures[apid]['geometry'])
  624. except TypeError:
  625. geo_len = 1
  626. pol_nr = 0
  627. for el in panel_obj.apertures[apid]['geometry']:
  628. if self.app.abort_flag:
  629. # graceful abort requested by the user
  630. raise FlatCAMApp.GracefulException
  631. new_el = dict()
  632. if 'solid' in el:
  633. geo_aper = translate_recursion(el['solid'])
  634. new_el['solid'] = geo_aper
  635. if 'clear' in el:
  636. geo_aper = translate_recursion(el['clear'])
  637. new_el['clear'] = geo_aper
  638. if 'follow' in el:
  639. geo_aper = translate_recursion(el['follow'])
  640. new_el['follow'] = geo_aper
  641. obj_fin.apertures[apid]['geometry'].append(deepcopy(new_el))
  642. pol_nr += 1
  643. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  644. if old_disp_number < disp_number <= 100:
  645. self.app.proc_container.update_view_text(' %s: %d %d%%' %
  646. (_("Copy"),
  647. int(element),
  648. disp_number))
  649. old_disp_number = disp_number
  650. currentx += lenghtx
  651. currenty += lenghty
  652. if panel_type == 'gerber':
  653. self.app.inform.emit('%s %s' %
  654. (_("Generating panel ..."), _("Adding the Gerber code.")))
  655. obj_fin.source_file = self.app.export_gerber(obj_name=self.outname, filename=None,
  656. local_use=obj_fin, use_thread=False)
  657. # app_obj.log.debug("Found %s geometries. Creating a panel geometry cascaded union ..." %
  658. # len(obj_fin.solid_geometry))
  659. # obj_fin.solid_geometry = cascaded_union(obj_fin.solid_geometry)
  660. # app_obj.log.debug("Finished creating a cascaded union for the panel.")
  661. self.app.proc_container.update_view_text('')
  662. self.app.inform.emit('%s %s: %d' %
  663. (_("Generating panel ..."), _("Spawning copies"), (int(rows * columns))))
  664. if isinstance(panel_obj, FlatCAMExcellon):
  665. self.app.progress.emit(50)
  666. self.app.new_object("excellon", self.outname, job_init_excellon, plot=True, autoselected=True)
  667. else:
  668. self.app.progress.emit(50)
  669. self.app.new_object(panel_type, self.outname, job_init_geometry,
  670. plot=True, autoselected=True)
  671. if self.constrain_flag is False:
  672. self.app.inform.emit('[success] %s' % _("Panel done..."))
  673. else:
  674. self.constrain_flag = False
  675. self.app.inform.emit(_("{text} Too big for the constrain area. "
  676. "Final panel has {col} columns and {row} rows").format(
  677. text='[WARNING] ', col=columns, row=rows))
  678. proc = self.app.proc_container.new(_("Working..."))
  679. def job_thread(app_obj):
  680. try:
  681. panelize_2()
  682. self.app.inform.emit('[success] %s' % _("Panel created successfully."))
  683. except Exception as ee:
  684. proc.done()
  685. log.debug(str(ee))
  686. return
  687. proc.done()
  688. self.app.collection.promise(self.outname)
  689. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  690. def reset_fields(self):
  691. self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  692. self.box_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))