ToolPanelize.py 38 KB

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