ToolOptimal.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # ##########################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # File Author: Marius Adrian Stanciu (c) #
  5. # Date: 09/27/2019 #
  6. # MIT Licence #
  7. # ##########################################################
  8. from FlatCAMTool import FlatCAMTool
  9. from FlatCAMObj import *
  10. from shapely.geometry import Point
  11. from shapely import affinity
  12. from PyQt5 import QtCore
  13. import collections
  14. import gettext
  15. import FlatCAMTranslation as fcTranslate
  16. import builtins
  17. fcTranslate.apply_language('strings')
  18. if '_' not in builtins.__dict__:
  19. _ = gettext.gettext
  20. class ToolOptimal(FlatCAMTool):
  21. toolName = _("Optimal Tool")
  22. def __init__(self, app):
  23. FlatCAMTool.__init__(self, app)
  24. self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
  25. # ## Title
  26. title_label = QtWidgets.QLabel("%s" % self.toolName)
  27. title_label.setStyleSheet("""
  28. QLabel
  29. {
  30. font-size: 16px;
  31. font-weight: bold;
  32. }
  33. """)
  34. self.layout.addWidget(title_label)
  35. # ## Form Layout
  36. form_lay = QtWidgets.QFormLayout()
  37. self.layout.addLayout(form_lay)
  38. # ## Gerber Object to mirror
  39. self.gerber_object_combo = QtWidgets.QComboBox()
  40. self.gerber_object_combo.setModel(self.app.collection)
  41. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  42. self.gerber_object_combo.setCurrentIndex(1)
  43. self.gerber_object_label = QtWidgets.QLabel("<b>%s:</b>" % _("GERBER"))
  44. self.gerber_object_label.setToolTip(
  45. "Gerber object for which to find the minimum distance between copper features."
  46. )
  47. self.title_res_label = QtWidgets.QLabel('<b>%s</b>' % _("Minimum distance between copper features"))
  48. self.result_label = QtWidgets.QLabel('%s:' % _("Determined"))
  49. self.show_res = QtWidgets.QLabel('%s' % '')
  50. self.units_lbl = QtWidgets.QLabel(self.units)
  51. self.freq_label = QtWidgets.QLabel('%s:' % _("Frequency"))
  52. self.freq_label.setToolTip(_("How many times this minimum is found."))
  53. self.freq_res = QtWidgets.QLabel('%s' % '')
  54. self.precision_label = QtWidgets.QLabel('%s:' % _("Precision"))
  55. self.precision_label.setToolTip(_("Number of decimals kept for found distances."))
  56. self.precision_spinner = FCSpinner()
  57. self.precision_spinner.set_range(2, 10)
  58. self.precision_spinner.setWrapping(True)
  59. hlay = QtWidgets.QHBoxLayout()
  60. hlay.addWidget(self.show_res)
  61. hlay.addStretch()
  62. hlay.addWidget(self.units_lbl)
  63. form_lay.addRow(QtWidgets.QLabel(""))
  64. form_lay.addRow(self.gerber_object_label, self.gerber_object_combo)
  65. form_lay.addRow(self.precision_label, self.precision_spinner)
  66. form_lay.addRow(QtWidgets.QLabel(""))
  67. form_lay.addRow(self.title_res_label)
  68. form_lay.addRow(self.result_label, hlay)
  69. form_lay.addRow(self.freq_label, self.freq_res)
  70. self.calculate_button = QtWidgets.QPushButton(_("Find Distance"))
  71. self.calculate_button.setToolTip(
  72. _("Calculate the minimum distance between copper features,\n"
  73. "this will allow the determination of the right tool to\n"
  74. "use for isolation or copper clearing.")
  75. )
  76. self.calculate_button.setMinimumWidth(60)
  77. self.layout.addWidget(self.calculate_button)
  78. self.decimals = 4
  79. # self.dt_label = QtWidgets.QLabel("<b>%s:</b>" % _('Alignment Drill Diameter'))
  80. # self.dt_label.setToolTip(
  81. # _("Diameter of the drill for the "
  82. # "alignment holes.")
  83. # )
  84. # self.layout.addWidget(self.dt_label)
  85. #
  86. # hlay = QtWidgets.QHBoxLayout()
  87. # self.layout.addLayout(hlay)
  88. #
  89. # self.drill_dia = FCEntry()
  90. # self.dd_label = QtWidgets.QLabel('%s:' % _("Drill dia"))
  91. # self.dd_label.setToolTip(
  92. # _("Diameter of the drill for the "
  93. # "alignment holes.")
  94. # )
  95. # hlay.addWidget(self.dd_label)
  96. # hlay.addWidget(self.drill_dia)
  97. self.calculate_button.clicked.connect(self.find_minimum_distance)
  98. self.layout.addStretch()
  99. # ## Signals
  100. def install(self, icon=None, separator=None, **kwargs):
  101. FlatCAMTool.install(self, icon, separator, shortcut='ALT+O', **kwargs)
  102. def run(self, toggle=True):
  103. self.app.report_usage("ToolOptimal()")
  104. self.show_res.setText('')
  105. if toggle:
  106. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  107. if self.app.ui.splitter.sizes()[0] == 0:
  108. self.app.ui.splitter.setSizes([1, 1])
  109. else:
  110. try:
  111. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  112. # if tab is populated with the tool but it does not have the focus, focus on it
  113. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  114. # focus on Tool Tab
  115. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  116. else:
  117. self.app.ui.splitter.setSizes([0, 1])
  118. except AttributeError:
  119. pass
  120. else:
  121. if self.app.ui.splitter.sizes()[0] == 0:
  122. self.app.ui.splitter.setSizes([1, 1])
  123. FlatCAMTool.run(self)
  124. self.set_tool_ui()
  125. self.app.ui.notebook.setTabText(2, _("Optimal Tool"))
  126. def set_tool_ui(self):
  127. self.precision_spinner.set_value(int(self.decimals))
  128. self.reset_fields()
  129. def find_minimum_distance(self):
  130. self.units = self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper()
  131. self.decimals = int(self.precision_spinner.get_value())
  132. selection_index = self.gerber_object_combo.currentIndex()
  133. model_index = self.app.collection.index(selection_index, 0, self.gerber_object_combo.rootModelIndex())
  134. try:
  135. fcobj = model_index.internalPointer().obj
  136. except Exception as e:
  137. log.debug("ToolOptimal.find_minimum_distance() --> %s" % str(e))
  138. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no Gerber object loaded ..."))
  139. return
  140. if not isinstance(fcobj, FlatCAMGerber):
  141. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Only Gerber objects can be evaluated."))
  142. return
  143. proc = self.app.proc_container.new(_("Working..."))
  144. def job_thread(app_obj):
  145. app_obj.inform.emit(_("Optimal Tool. Started to search for the minimum distance between copper features."))
  146. try:
  147. old_disp_number = 0
  148. pol_nr = 0
  149. app_obj.proc_container.update_view_text(' %d%%' % 0)
  150. total_geo = list()
  151. for ap in list(fcobj.apertures.keys()):
  152. if 'geometry' in fcobj.apertures[ap]:
  153. app_obj.inform.emit(
  154. '%s: %s' % (_("Optimal Tool. Parsing geometry for aperture"), str(ap)))
  155. for geo_el in fcobj.apertures[ap]['geometry']:
  156. if self.app.abort_flag:
  157. # graceful abort requested by the user
  158. raise FlatCAMApp.GracefulException
  159. if 'solid' in geo_el and geo_el['solid'] is not None and geo_el['solid'].is_valid:
  160. total_geo.append(geo_el['solid'])
  161. app_obj.inform.emit(
  162. _("Optimal Tool. Creating a buffer for the object geometry."))
  163. total_geo = MultiPolygon(total_geo)
  164. total_geo = total_geo.buffer(0)
  165. geo_len = len(total_geo)
  166. geo_len = (geo_len * (geo_len - 1)) / 2
  167. app_obj.inform.emit(
  168. '%s: %s' % (_("Optimal Tool. Finding the distances between each two elements. Iterations"),
  169. str(geo_len)))
  170. min_list = list()
  171. idx = 1
  172. for geo in total_geo:
  173. for s_geo in total_geo[idx:]:
  174. if self.app.abort_flag:
  175. # graceful abort requested by the user
  176. raise FlatCAMApp.GracefulException
  177. # minimize the number of distances by not taking into considerations those that are too small
  178. dist = geo.distance(s_geo)
  179. dist = float('%.*f' % (self.decimals, dist))
  180. min_list.append(dist)
  181. pol_nr += 1
  182. disp_number = int(np.interp(pol_nr, [0, geo_len], [0, 100]))
  183. if old_disp_number < disp_number <= 100:
  184. app_obj.proc_container.update_view_text(' %d%%' % disp_number)
  185. old_disp_number = disp_number
  186. idx += 1
  187. app_obj.inform.emit(
  188. _("Optimal Tool. Finding the minimum distance."))
  189. counter = collections.Counter(min_list)
  190. min_dist = min(min_list)
  191. min_dist_string = '%.*f' % (self.decimals, min_dist)
  192. self.show_res.setText(min_dist_string)
  193. freq = counter[min_dist]
  194. freq = '%d' % freq
  195. self.freq_res.setText(freq)
  196. app_obj.inform.emit('[success] %s' % _("Optimal Tool. Finished successfully."))
  197. except Exception as ee:
  198. proc.done()
  199. log.debug(str(ee))
  200. return
  201. proc.done()
  202. self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
  203. def reset_fields(self):
  204. self.gerber_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  205. self.gerber_object_combo.setCurrentIndex(0)