ExcellonGenPrefGroupUI.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import platform
  2. from PyQt5 import QtWidgets, QtCore, QtGui
  3. from PyQt5.QtCore import QSettings
  4. from appGUI.GUIElements import FCCheckBox, FCSpinner, RadioSet, FCEntry, FCSliderWithSpinner, FCColorEntry
  5. from appGUI.preferences.OptionsGroupUI import OptionsGroupUI
  6. import gettext
  7. import appTranslation as fcTranslate
  8. import builtins
  9. fcTranslate.apply_language('strings')
  10. if '_' not in builtins.__dict__:
  11. _ = gettext.gettext
  12. settings = QSettings("Open Source", "FlatCAM")
  13. if settings.contains("machinist"):
  14. machinist_setting = settings.value('machinist', type=int)
  15. else:
  16. machinist_setting = 0
  17. class ExcellonGenPrefGroupUI(OptionsGroupUI):
  18. def __init__(self, decimals=4, parent=None):
  19. # OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
  20. super(ExcellonGenPrefGroupUI, self).__init__(self, parent=parent)
  21. self.setTitle(str(_("Excellon General")))
  22. self.decimals = decimals
  23. # Plot options
  24. self.plot_options_label = QtWidgets.QLabel("<b>%s:</b>" % _("Plot Options"))
  25. self.layout.addWidget(self.plot_options_label)
  26. grid1 = QtWidgets.QGridLayout()
  27. self.layout.addLayout(grid1)
  28. # Plot CB
  29. self.plot_cb = FCCheckBox(label=_('Plot'))
  30. self.plot_cb.setToolTip(
  31. "Plot (show) this object."
  32. )
  33. grid1.addWidget(self.plot_cb, 0, 0)
  34. # Solid CB
  35. self.solid_cb = FCCheckBox(label=_('Solid'))
  36. self.solid_cb.setToolTip(
  37. "Plot as solid circles."
  38. )
  39. grid1.addWidget(self.solid_cb, 0, 1)
  40. # Multicolored CB
  41. self.multicolored_cb = FCCheckBox(label='%s' % _('M-Color'))
  42. self.multicolored_cb.setToolTip(
  43. _("Draw polygons in different colors.")
  44. )
  45. grid1.addWidget(self.multicolored_cb, 0, 2)
  46. separator_line = QtWidgets.QFrame()
  47. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  48. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  49. grid1.addWidget(separator_line, 1, 0, 1, 3)
  50. grid2 = QtWidgets.QGridLayout()
  51. self.layout.addLayout(grid2)
  52. grid2.setColumnStretch(0, 0)
  53. grid2.setColumnStretch(1, 1)
  54. # Excellon format
  55. self.excellon_format_label = QtWidgets.QLabel("<b>%s:</b>" % _("Excellon Format"))
  56. self.excellon_format_label.setToolTip(
  57. _("The NC drill files, usually named Excellon files\n"
  58. "are files that can be found in different formats.\n"
  59. "Here we set the format used when the provided\n"
  60. "coordinates are not using period.\n"
  61. "\n"
  62. "Possible presets:\n"
  63. "\n"
  64. "PROTEUS 3:3 MM LZ\n"
  65. "DipTrace 5:2 MM TZ\n"
  66. "DipTrace 4:3 MM LZ\n"
  67. "\n"
  68. "EAGLE 3:3 MM TZ\n"
  69. "EAGLE 4:3 MM TZ\n"
  70. "EAGLE 2:5 INCH TZ\n"
  71. "EAGLE 3:5 INCH TZ\n"
  72. "\n"
  73. "ALTIUM 2:4 INCH LZ\n"
  74. "Sprint Layout 2:4 INCH LZ"
  75. "\n"
  76. "KiCAD 3:5 INCH TZ")
  77. )
  78. grid2.addWidget(self.excellon_format_label, 0, 0, 1, 2)
  79. self.excellon_format_in_label = QtWidgets.QLabel('%s:' % _("INCH"))
  80. self.excellon_format_in_label.setToolTip(_("Default values for INCH are 2:4"))
  81. hlay1 = QtWidgets.QHBoxLayout()
  82. self.excellon_format_upper_in_entry = FCSpinner()
  83. self.excellon_format_upper_in_entry.set_range(0, 9)
  84. self.excellon_format_upper_in_entry.setMinimumWidth(30)
  85. self.excellon_format_upper_in_entry.setToolTip(
  86. _("This numbers signify the number of digits in\n"
  87. "the whole part of Excellon coordinates.")
  88. )
  89. hlay1.addWidget(self.excellon_format_upper_in_entry)
  90. excellon_separator_in_label = QtWidgets.QLabel(':')
  91. excellon_separator_in_label.setFixedWidth(5)
  92. hlay1.addWidget(excellon_separator_in_label)
  93. self.excellon_format_lower_in_entry = FCSpinner()
  94. self.excellon_format_lower_in_entry.set_range(0, 9)
  95. self.excellon_format_lower_in_entry.setMinimumWidth(30)
  96. self.excellon_format_lower_in_entry.setToolTip(
  97. _("This numbers signify the number of digits in\n"
  98. "the decimal part of Excellon coordinates.")
  99. )
  100. hlay1.addWidget(self.excellon_format_lower_in_entry)
  101. grid2.addWidget(self.excellon_format_in_label, 1, 0)
  102. grid2.addLayout(hlay1, 1, 1)
  103. self.excellon_format_mm_label = QtWidgets.QLabel('%s:' % _("METRIC"))
  104. self.excellon_format_mm_label.setToolTip(_("Default values for METRIC are 3:3"))
  105. hlay2 = QtWidgets.QHBoxLayout()
  106. self.excellon_format_upper_mm_entry = FCSpinner()
  107. self.excellon_format_upper_mm_entry.set_range(0, 9)
  108. self.excellon_format_upper_mm_entry.setMinimumWidth(30)
  109. self.excellon_format_upper_mm_entry.setToolTip(
  110. _("This numbers signify the number of digits in\n"
  111. "the whole part of Excellon coordinates.")
  112. )
  113. hlay2.addWidget(self.excellon_format_upper_mm_entry)
  114. excellon_separator_mm_label = QtWidgets.QLabel(':')
  115. excellon_separator_mm_label.setFixedWidth(5)
  116. hlay2.addWidget(excellon_separator_mm_label, QtCore.Qt.AlignLeft)
  117. self.excellon_format_lower_mm_entry = FCSpinner()
  118. self.excellon_format_lower_mm_entry.set_range(0, 9)
  119. self.excellon_format_lower_mm_entry.setMinimumWidth(30)
  120. self.excellon_format_lower_mm_entry.setToolTip(
  121. _("This numbers signify the number of digits in\n"
  122. "the decimal part of Excellon coordinates.")
  123. )
  124. hlay2.addWidget(self.excellon_format_lower_mm_entry)
  125. grid2.addWidget(self.excellon_format_mm_label, 2, 0)
  126. grid2.addLayout(hlay2, 2, 1)
  127. self.excellon_zeros_label = QtWidgets.QLabel('%s:' % _('Zeros'))
  128. self.excellon_zeros_label.setAlignment(QtCore.Qt.AlignLeft)
  129. self.excellon_zeros_label.setToolTip(
  130. _("This sets the type of Excellon zeros.\n"
  131. "If LZ then Leading Zeros are kept and\n"
  132. "Trailing Zeros are removed.\n"
  133. "If TZ is checked then Trailing Zeros are kept\n"
  134. "and Leading Zeros are removed.\n\n"
  135. "This is used when there is no information\n"
  136. "stored in the Excellon file.")
  137. )
  138. grid2.addWidget(self.excellon_zeros_label, 3, 0)
  139. self.excellon_zeros_radio = RadioSet([{'label': _('LZ'), 'value': 'L'},
  140. {'label': _('TZ'), 'value': 'T'}])
  141. grid2.addWidget(self.excellon_zeros_radio, 3, 1)
  142. self.excellon_units_label = QtWidgets.QLabel('%s:' % _('Units'))
  143. self.excellon_units_label.setAlignment(QtCore.Qt.AlignLeft)
  144. self.excellon_units_label.setToolTip(
  145. _("This sets the default units of Excellon files.\n"
  146. "If it is not detected in the parsed file the value here\n"
  147. "will be used."
  148. "Some Excellon files don't have an header\n"
  149. "therefore this parameter will be used.")
  150. )
  151. self.excellon_units_radio = RadioSet([{'label': _('INCH'), 'value': 'INCH'},
  152. {'label': _('MM'), 'value': 'METRIC'}])
  153. self.excellon_units_radio.setToolTip(
  154. _("This sets the units of Excellon files.\n"
  155. "Some Excellon files don't have an header\n"
  156. "therefore this parameter will be used.")
  157. )
  158. grid2.addWidget(self.excellon_units_label, 4, 0)
  159. grid2.addWidget(self.excellon_units_radio, 4, 1)
  160. self.update_excellon_cb = FCCheckBox(label=_('Update Export settings'))
  161. self.update_excellon_cb.setToolTip(
  162. "If checked, the Excellon Export settings will be updated with the ones above."
  163. )
  164. grid2.addWidget(self.update_excellon_cb, 5, 0, 1, 2)
  165. # Adding the Excellon Format Defaults Button
  166. self.excellon_defaults_button = QtWidgets.QPushButton()
  167. self.excellon_defaults_button.setText(str(_("Restore Defaults")))
  168. self.excellon_defaults_button.setMinimumWidth(80)
  169. grid2.addWidget(self.excellon_defaults_button, 6, 0, 1, 2)
  170. separator_line = QtWidgets.QFrame()
  171. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  172. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  173. grid2.addWidget(separator_line, 7, 0, 1, 2)
  174. self.excellon_general_label = QtWidgets.QLabel("<b>%s:</b>" % _("Path Optimization"))
  175. grid2.addWidget(self.excellon_general_label, 8, 0, 1, 2)
  176. self.excellon_optimization_label = QtWidgets.QLabel(_('Algorithm:'))
  177. self.excellon_optimization_label.setToolTip(
  178. _("This sets the optimization type for the Excellon drill path.\n"
  179. "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
  180. "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
  181. "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
  182. "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
  183. "drill path optimization.\n"
  184. "\n"
  185. "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
  186. "Travelling Salesman algorithm for path optimization.")
  187. )
  188. self.excellon_optimization_radio = RadioSet([{'label': _('MetaHeuristic'), 'value': 'M'},
  189. {'label': _('Basic'), 'value': 'B'},
  190. {'label': _('TSA'), 'value': 'T'}],
  191. orientation='vertical', stretch=False)
  192. self.excellon_optimization_radio.setToolTip(
  193. _("This sets the optimization type for the Excellon drill path.\n"
  194. "If <<MetaHeuristic>> is checked then Google OR-Tools algorithm with\n"
  195. "MetaHeuristic Guided Local Path is used. Default search time is 3sec.\n"
  196. "If <<Basic>> is checked then Google OR-Tools Basic algorithm is used.\n"
  197. "If <<TSA>> is checked then Travelling Salesman algorithm is used for\n"
  198. "drill path optimization.\n"
  199. "\n"
  200. "If this control is disabled, then FlatCAM works in 32bit mode and it uses\n"
  201. "Travelling Salesman algorithm for path optimization.")
  202. )
  203. grid2.addWidget(self.excellon_optimization_label, 9, 0)
  204. grid2.addWidget(self.excellon_optimization_radio, 9, 1)
  205. self.optimization_time_label = QtWidgets.QLabel('%s:' % _('Duration'))
  206. self.optimization_time_label.setAlignment(QtCore.Qt.AlignLeft)
  207. self.optimization_time_label.setToolTip(
  208. _("When OR-Tools Metaheuristic (MH) is enabled there is a\n"
  209. "maximum threshold for how much time is spent doing the\n"
  210. "path optimization. This max duration is set here.\n"
  211. "In seconds.")
  212. )
  213. self.optimization_time_entry = FCSpinner()
  214. self.optimization_time_entry.set_range(0, 999)
  215. grid2.addWidget(self.optimization_time_label, 10, 0)
  216. grid2.addWidget(self.optimization_time_entry, 10, 1)
  217. separator_line = QtWidgets.QFrame()
  218. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  219. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  220. grid2.addWidget(separator_line, 11, 0, 1, 2)
  221. # Fuse Tools
  222. self.join_geo_label = QtWidgets.QLabel('<b>%s</b>:' % _('Join Option'))
  223. grid2.addWidget(self.join_geo_label, 12, 0, 1, 2)
  224. self.fuse_tools_cb = FCCheckBox(_("Fuse Tools"))
  225. self.fuse_tools_cb.setToolTip(
  226. _("When checked the joined (merged) object tools\n"
  227. "will be merged also but only if they share some of their attributes.")
  228. )
  229. grid2.addWidget(self.fuse_tools_cb, 13, 0, 1, 2)
  230. separator_line = QtWidgets.QFrame()
  231. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  232. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  233. grid2.addWidget(separator_line, 14, 0, 1, 2)
  234. # Excellon Object Color
  235. self.gerber_color_label = QtWidgets.QLabel('<b>%s</b>' % _('Object Color'))
  236. grid2.addWidget(self.gerber_color_label, 17, 0, 1, 2)
  237. # Plot Line Color
  238. self.line_color_label = QtWidgets.QLabel('%s:' % _('Outline'))
  239. self.line_color_label.setToolTip(
  240. _("Set the line color for plotted objects.")
  241. )
  242. self.line_color_entry = FCColorEntry()
  243. grid2.addWidget(self.line_color_label, 19, 0)
  244. grid2.addWidget(self.line_color_entry, 19, 1)
  245. # Plot Fill Color
  246. self.fill_color_label = QtWidgets.QLabel('%s:' % _('Fill'))
  247. self.fill_color_label.setToolTip(
  248. _("Set the fill color for plotted objects.\n"
  249. "First 6 digits are the color and the last 2\n"
  250. "digits are for alpha (transparency) level.")
  251. )
  252. self.fill_color_entry = FCColorEntry()
  253. grid2.addWidget(self.fill_color_label, 22, 0)
  254. grid2.addWidget(self.fill_color_entry, 22, 1)
  255. # Plot Fill Transparency Level
  256. self.excellon_alpha_label = QtWidgets.QLabel('%s:' % _('Alpha'))
  257. self.excellon_alpha_label.setToolTip(
  258. _("Set the fill transparency for plotted objects.")
  259. )
  260. self.excellon_alpha_entry = FCSliderWithSpinner(0, 255, 1)
  261. grid2.addWidget(self.excellon_alpha_label, 24, 0)
  262. grid2.addWidget(self.excellon_alpha_entry, 24, 1)
  263. self.layout.addStretch()
  264. current_platform = platform.architecture()[0]
  265. if current_platform == '64bit':
  266. self.excellon_optimization_label.setDisabled(False)
  267. self.excellon_optimization_radio.setDisabled(False)
  268. self.optimization_time_label.setDisabled(False)
  269. self.optimization_time_entry.setDisabled(False)
  270. self.excellon_optimization_radio.activated_custom.connect(self.optimization_selection)
  271. else:
  272. self.excellon_optimization_label.setDisabled(True)
  273. self.excellon_optimization_radio.setDisabled(True)
  274. self.optimization_time_label.setDisabled(True)
  275. self.optimization_time_entry.setDisabled(True)
  276. # Setting plot colors signals
  277. self.line_color_entry.editingFinished.connect(self.on_line_color_entry)
  278. self.fill_color_entry.editingFinished.connect(self.on_fill_color_entry)
  279. self.excellon_alpha_entry.valueChanged.connect(self.on_excellon_alpha_changed) # alpha
  280. # Load the defaults values into the Excellon Format and Excellon Zeros fields
  281. self.excellon_defaults_button.clicked.connect(self.on_excellon_defaults_button)
  282. # Make sure that when the Excellon loading parameters are changed, the change is reflected in the
  283. # Export Excellon parameters.
  284. self.update_excellon_cb.stateChanged.connect(self.on_update_exc_export)
  285. # call it once to make sure it is updated at startup
  286. self.on_update_exc_export(state=self.app.defaults["excellon_update"])
  287. def optimization_selection(self):
  288. if self.excellon_optimization_radio.get_value() == 'M':
  289. self.optimization_time_label.setDisabled(False)
  290. self.optimization_time_entry.setDisabled(False)
  291. else:
  292. self.optimization_time_label.setDisabled(True)
  293. self.optimization_time_entry.setDisabled(True)
  294. # Setting plot colors handlers
  295. def on_fill_color_entry(self):
  296. self.app.defaults['excellon_plot_fill'] = self.fill_color_entry.get_value()[:7] + \
  297. self.app.defaults['excellon_plot_fill'][7:9]
  298. def on_line_color_entry(self):
  299. self.app.defaults['excellon_plot_line'] = self.line_color_entry.get_value()[:7] + \
  300. self.app.defaults['excellon_plot_line'][7:9]
  301. def on_excellon_alpha_changed(self, spinner_value):
  302. self.app.defaults['excellon_plot_fill'] = \
  303. self.app.defaults['excellon_plot_fill'][:7] + \
  304. (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
  305. self.app.defaults['excellon_plot_line'] = \
  306. self.app.defaults['excellon_plot_line'][:7] + \
  307. (hex(spinner_value)[2:] if int(hex(spinner_value)[2:], 16) > 0 else '00')
  308. def on_excellon_defaults_button(self):
  309. self.app.preferencesUiManager.defaults_form_fields["excellon_format_lower_in"].set_value('4')
  310. self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_in"].set_value('2')
  311. self.app.preferencesUiManager.defaults_form_fields["excellon_format_lower_mm"].set_value('3')
  312. self.app.preferencesUiManager.defaults_form_fields["excellon_format_upper_mm"].set_value('3')
  313. self.app.preferencesUiManager.defaults_form_fields["excellon_zeros"].set_value('L')
  314. self.app.preferencesUiManager.defaults_form_fields["excellon_units"].set_value('INCH')
  315. def on_update_exc_export(self, state):
  316. """
  317. This is handling the update of Excellon Export parameters based on the ones in the Excellon General but only
  318. if the update_excellon_cb checkbox is checked
  319. :param state: state of the checkbox whose signals is tied to his slot
  320. :return:
  321. """
  322. if state:
  323. # first try to disconnect
  324. try:
  325. self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  326. except TypeError:
  327. pass
  328. try:
  329. self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  330. except TypeError:
  331. pass
  332. try:
  333. self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  334. except TypeError:
  335. pass
  336. try:
  337. self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  338. except TypeError:
  339. pass
  340. try:
  341. self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
  342. except TypeError:
  343. pass
  344. try:
  345. self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
  346. except TypeError:
  347. pass
  348. # the connect them
  349. self.excellon_format_upper_in_entry.returnPressed.connect(self.on_excellon_format_changed)
  350. self.excellon_format_lower_in_entry.returnPressed.connect(self.on_excellon_format_changed)
  351. self.excellon_format_upper_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
  352. self.excellon_format_lower_mm_entry.returnPressed.connect(self.on_excellon_format_changed)
  353. self.excellon_zeros_radio.activated_custom.connect(self.on_excellon_zeros_changed)
  354. self.excellon_units_radio.activated_custom.connect(self.on_excellon_units_changed)
  355. else:
  356. # disconnect the signals
  357. try:
  358. self.excellon_format_upper_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  359. except TypeError:
  360. pass
  361. try:
  362. self.excellon_format_lower_in_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  363. except TypeError:
  364. pass
  365. try:
  366. self.excellon_format_upper_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  367. except TypeError:
  368. pass
  369. try:
  370. self.excellon_format_lower_mm_entry.returnPressed.disconnect(self.on_excellon_format_changed)
  371. except TypeError:
  372. pass
  373. try:
  374. self.excellon_zeros_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
  375. except TypeError:
  376. pass
  377. try:
  378. self.excellon_units_radio.activated_custom.disconnect(self.on_excellon_zeros_changed)
  379. except TypeError:
  380. pass
  381. def on_excellon_format_changed(self):
  382. """
  383. Slot activated when the user changes the Excellon format values in Preferences -> Excellon -> Excellon General
  384. :return: None
  385. """
  386. if self.excellon_units_radio.get_value().upper() == 'METRIC':
  387. self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
  388. self.excellon_format_upper_mm_entry.get_value())
  389. self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
  390. self.excellon_format_lower_mm_entry.get_value())
  391. else:
  392. self.app.ui.excellon_defaults_form.excellon_exp_group.format_whole_entry.set_value(
  393. self.excellon_format_upper_in_entry.get_value())
  394. self.app.ui.excellon_defaults_form.excellon_exp_group.format_dec_entry.set_value(
  395. self.excellon_format_lower_in_entry.get_value())
  396. def on_excellon_zeros_changed(self, val):
  397. """
  398. Slot activated when the user changes the Excellon zeros values in Preferences -> Excellon -> Excellon General
  399. :return: None
  400. """
  401. self.app.ui.excellon_defaults_form.excellon_exp_group.zeros_radio.set_value(val + 'Z')
  402. def on_excellon_units_changed(self, val):
  403. """
  404. Slot activated when the user changes the Excellon unit values in Preferences -> Excellon -> Excellon General
  405. :return: None
  406. """
  407. self.app.ui.excellon_defaults_form.excellon_exp_group.excellon_units_radio.set_value(val)
  408. self.on_excellon_format_changed()