ToolCalibrate.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  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, QtCore, QtGui
  8. from FlatCAMTool import FlatCAMTool
  9. from flatcamGUI.GUIElements import FCDoubleSpinner, EvalEntry, FCCheckBox, OptionalInputSection, FCTable, FCComboBox
  10. from flatcamEditors.FlatCAMTextEditor import TextEditor
  11. from shapely.geometry import Point
  12. from shapely.geometry.base import *
  13. import math
  14. from datetime import datetime
  15. import logging
  16. import gettext
  17. import FlatCAMTranslation as fcTranslate
  18. import builtins
  19. fcTranslate.apply_language('strings')
  20. if '_' not in builtins.__dict__:
  21. _ = gettext.gettext
  22. log = logging.getLogger('base')
  23. class ToolCalibrate(FlatCAMTool):
  24. toolName = _("Calibrate Tool")
  25. def __init__(self, app):
  26. FlatCAMTool.__init__(self, app)
  27. self.app = app
  28. self.canvas = self.app.plotcanvas
  29. self.decimals = self.app.decimals
  30. # ## Title
  31. title_label = QtWidgets.QLabel("%s" % self.toolName)
  32. title_label.setStyleSheet("""
  33. QLabel
  34. {
  35. font-size: 16px;
  36. font-weight: bold;
  37. }
  38. """)
  39. self.layout.addWidget(title_label)
  40. # ## Grid Layout
  41. i_grid_lay = QtWidgets.QGridLayout()
  42. self.layout.addLayout(i_grid_lay)
  43. i_grid_lay.setColumnStretch(0, 0)
  44. i_grid_lay.setColumnStretch(1, 1)
  45. i_grid_lay.setColumnStretch(2, 1)
  46. self.obj_type_label = QtWidgets.QLabel("<b>%s:</b>" % _("Object Type"))
  47. self.obj_type_combo = FCComboBox()
  48. self.obj_type_combo.addItem(_("Gerber"))
  49. self.obj_type_combo.addItem(_("Excellon"))
  50. self.obj_type_combo.setCurrentIndex(1)
  51. i_grid_lay.addWidget(self.obj_type_label, 0, 0)
  52. i_grid_lay.addWidget(self.obj_type_combo, 0, 1, 1,2)
  53. self.object_combo = FCComboBox()
  54. self.object_combo.setModel(self.app.collection)
  55. self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  56. self.object_combo.setCurrentIndex(1)
  57. self.excobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("Target Object"))
  58. self.excobj_label.setToolTip(
  59. _("FlatCAM Object to be used as a source for reference points.")
  60. )
  61. i_grid_lay.addWidget(self.excobj_label, 1, 0, 1, 3)
  62. i_grid_lay.addWidget(self.object_combo, 2, 0, 1, 3)
  63. i_grid_lay.addWidget(QtWidgets.QLabel(''), 3, 0)
  64. self.gcode_title_label = QtWidgets.QLabel('<b>%s</b>' % _('GCode Parameters'))
  65. self.gcode_title_label.setToolTip(
  66. _("Parameters used when creating the GCode in this tool.")
  67. )
  68. i_grid_lay.addWidget(self.gcode_title_label, 4, 0, 1, 3)
  69. # Travel Z entry
  70. travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
  71. travelz_lbl.setToolTip(
  72. _("Height (Z) for travelling between the points.")
  73. )
  74. self.travelz_entry = FCDoubleSpinner()
  75. self.travelz_entry.set_range(-9999.9999, 9999.9999)
  76. self.travelz_entry.set_precision(self.decimals)
  77. self.travelz_entry.setSingleStep(0.1)
  78. i_grid_lay.addWidget(travelz_lbl, 5, 0)
  79. i_grid_lay.addWidget(self.travelz_entry, 5, 1, 1, 2)
  80. # Verification Z entry
  81. verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
  82. verz_lbl.setToolTip(
  83. _("Height (Z) for checking the point.")
  84. )
  85. self.verz_entry = FCDoubleSpinner()
  86. self.verz_entry.set_range(-9999.9999, 9999.9999)
  87. self.verz_entry.set_precision(self.decimals)
  88. self.verz_entry.setSingleStep(0.1)
  89. i_grid_lay.addWidget(verz_lbl, 6, 0)
  90. i_grid_lay.addWidget(self.verz_entry, 6, 1, 1, 2)
  91. # Zero the Z of the verification tool
  92. self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
  93. self.zeroz_cb.setToolTip(
  94. _("Include a sequence to zero the height (Z)\n"
  95. "of the verification tool.")
  96. )
  97. i_grid_lay.addWidget(self.zeroz_cb, 7, 0, 1, 3)
  98. # Toochange Z entry
  99. toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
  100. toolchangez_lbl.setToolTip(
  101. _("Height (Z) for mounting the verification probe.")
  102. )
  103. self.toolchangez_entry = FCDoubleSpinner()
  104. self.toolchangez_entry.set_range(0.0000, 9999.9999)
  105. self.toolchangez_entry.set_precision(self.decimals)
  106. self.toolchangez_entry.setSingleStep(0.1)
  107. i_grid_lay.addWidget(toolchangez_lbl, 8, 0)
  108. i_grid_lay.addWidget(self.toolchangez_entry, 8, 1, 1, 2)
  109. self.z_ois = OptionalInputSection(self.zeroz_cb, [toolchangez_lbl, self.toolchangez_entry])
  110. i_grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0, 1, 3)
  111. # ## Grid Layout
  112. grid_lay = QtWidgets.QGridLayout()
  113. self.layout.addLayout(grid_lay)
  114. grid_lay.setColumnStretch(0, 0)
  115. grid_lay.setColumnStretch(1, 1)
  116. grid_lay.setColumnStretch(2, 1)
  117. self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points'))
  118. self.points_table_label.setToolTip(
  119. _("Contain the expected calibration points and the\n"
  120. "ones measured.")
  121. )
  122. grid_lay.addWidget(self.points_table_label, 1, 0, 1, 3)
  123. self.points_table = FCTable()
  124. self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
  125. # self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  126. grid_lay.addWidget(self.points_table, 2, 0, 9, 3)
  127. self.points_table.setColumnCount(4)
  128. self.points_table.setHorizontalHeaderLabels(
  129. [
  130. '#',
  131. _("Name"),
  132. _("Target"),
  133. _("Found Delta")
  134. ]
  135. )
  136. self.points_table.setRowCount(8)
  137. row = 0
  138. # BOTTOM LEFT
  139. id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
  140. flags = QtCore.Qt.ItemIsEnabled
  141. id_item_1.setFlags(flags)
  142. self.points_table.setItem(row, 0, id_item_1) # Tool name/id
  143. self.bottom_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Left X'))
  144. self.points_table.setCellWidget(row, 1, self.bottom_left_coordx_lbl)
  145. self.bottom_left_coordx_tgt = EvalEntry()
  146. self.points_table.setCellWidget(row, 2, self.bottom_left_coordx_tgt)
  147. self.bottom_left_coordx_tgt.setReadOnly(True)
  148. self.bottom_left_coordx_found = EvalEntry()
  149. self.points_table.setCellWidget(row, 3, self.bottom_left_coordx_found)
  150. row += 1
  151. self.bottom_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Left Y'))
  152. self.points_table.setCellWidget(row, 1, self.bottom_left_coordy_lbl)
  153. self.bottom_left_coordy_tgt = EvalEntry()
  154. self.points_table.setCellWidget(row, 2, self.bottom_left_coordy_tgt)
  155. self.bottom_left_coordy_tgt.setReadOnly(True)
  156. self.bottom_left_coordy_found = EvalEntry()
  157. self.points_table.setCellWidget(row, 3, self.bottom_left_coordy_found)
  158. self.bottom_left_coordx_found.set_value(_("Origin"))
  159. self.bottom_left_coordy_found.set_value(_("Origin"))
  160. self.bottom_left_coordx_found.setDisabled(True)
  161. self.bottom_left_coordy_found.setDisabled(True)
  162. row += 1
  163. # BOTTOM RIGHT
  164. id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
  165. flags = QtCore.Qt.ItemIsEnabled
  166. id_item_2.setFlags(flags)
  167. self.points_table.setItem(row, 0, id_item_2) # Tool name/id
  168. self.bottom_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Right X'))
  169. self.points_table.setCellWidget(row, 1, self.bottom_right_coordx_lbl)
  170. self.bottom_right_coordx_tgt = EvalEntry()
  171. self.points_table.setCellWidget(row, 2, self.bottom_right_coordx_tgt)
  172. self.bottom_right_coordx_tgt.setReadOnly(True)
  173. self.bottom_right_coordx_found = EvalEntry()
  174. self.points_table.setCellWidget(row, 3, self.bottom_right_coordx_found)
  175. row += 1
  176. self.bottom_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Right Y'))
  177. self.points_table.setCellWidget(row, 1, self.bottom_right_coordy_lbl)
  178. self.bottom_right_coordy_tgt = EvalEntry()
  179. self.points_table.setCellWidget(row, 2, self.bottom_right_coordy_tgt)
  180. self.bottom_right_coordy_tgt.setReadOnly(True)
  181. self.bottom_right_coordy_found = EvalEntry()
  182. self.points_table.setCellWidget(row, 3, self.bottom_right_coordy_found)
  183. row += 1
  184. # TOP LEFT
  185. id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
  186. flags = QtCore.Qt.ItemIsEnabled
  187. id_item_3.setFlags(flags)
  188. self.points_table.setItem(row, 0, id_item_3) # Tool name/id
  189. self.top_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Left X'))
  190. self.points_table.setCellWidget(row, 1, self.top_left_coordx_lbl)
  191. self.top_left_coordx_tgt = EvalEntry()
  192. self.points_table.setCellWidget(row, 2, self.top_left_coordx_tgt)
  193. self.top_left_coordx_tgt.setReadOnly(True)
  194. self.top_left_coordx_found = EvalEntry()
  195. self.points_table.setCellWidget(row, 3, self.top_left_coordx_found)
  196. row += 1
  197. self.top_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Left Y'))
  198. self.points_table.setCellWidget(row, 1, self.top_left_coordy_lbl)
  199. self.top_left_coordy_tgt = EvalEntry()
  200. self.points_table.setCellWidget(row, 2, self.top_left_coordy_tgt)
  201. self.top_left_coordy_tgt.setReadOnly(True)
  202. self.top_left_coordy_found = EvalEntry()
  203. self.points_table.setCellWidget(row, 3, self.top_left_coordy_found)
  204. row += 1
  205. # TOP RIGHT
  206. id_item_4 = QtWidgets.QTableWidgetItem('%d' % 4)
  207. flags = QtCore.Qt.ItemIsEnabled
  208. id_item_4.setFlags(flags)
  209. self.points_table.setItem(row, 0, id_item_4) # Tool name/id
  210. self.top_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Right X'))
  211. self.points_table.setCellWidget(row, 1, self.top_right_coordx_lbl)
  212. self.top_right_coordx_tgt = EvalEntry()
  213. self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
  214. self.top_right_coordx_tgt.setReadOnly(True)
  215. self.top_right_coordx_found = EvalEntry()
  216. self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
  217. row += 1
  218. self.top_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Right Y'))
  219. self.points_table.setCellWidget(row, 1, self.top_right_coordy_lbl)
  220. self.top_right_coordy_tgt = EvalEntry()
  221. self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
  222. self.top_right_coordy_tgt.setReadOnly(True)
  223. self.top_right_coordy_found = EvalEntry()
  224. self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
  225. vertical_header = self.points_table.verticalHeader()
  226. vertical_header.hide()
  227. self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  228. horizontal_header = self.points_table.horizontalHeader()
  229. horizontal_header.setMinimumSectionSize(10)
  230. horizontal_header.setDefaultSectionSize(70)
  231. self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  232. # for x in range(4):
  233. # self.points_table.resizeColumnToContents(x)
  234. self.points_table.resizeColumnsToContents()
  235. self.points_table.resizeRowsToContents()
  236. horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
  237. horizontal_header.resizeSection(0, 20)
  238. horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
  239. horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
  240. horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
  241. self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
  242. self.points_table.setMaximumHeight(self.points_table.getHeight() + 3)
  243. step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1"))
  244. step_1.setToolTip(
  245. _("Pick four points by clicking inside the drill holes.\n"
  246. "Those four points should be in the four\n"
  247. "(as much as possible) corners of the Excellon object.")
  248. )
  249. grid_lay.addWidget(step_1, 15, 0, 1, 3)
  250. # ## Start Button
  251. self.start_button = QtWidgets.QPushButton(_("Acquire Calibration Points"))
  252. self.start_button.setToolTip(
  253. _("Pick four points by clicking inside the drill holes.\n"
  254. "Those four points should be in the four squares of\n"
  255. "the object.")
  256. )
  257. self.start_button.setStyleSheet("""
  258. QPushButton
  259. {
  260. font-weight: bold;
  261. }
  262. """)
  263. grid_lay.addWidget(self.start_button, 16, 0, 1, 3)
  264. # STEP 2 #
  265. step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2"))
  266. step_2.setToolTip(
  267. _("Generate GCode file to locate and align the PCB by using\n"
  268. "the four points acquired above.")
  269. )
  270. grid_lay.addWidget(step_2, 17, 0, 1, 3)
  271. # ## GCode Button
  272. self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
  273. self.gcode_button.setToolTip(
  274. _("Generate GCode file to locate and align the PCB by using\n"
  275. "the four points acquired above.")
  276. )
  277. self.gcode_button.setStyleSheet("""
  278. QPushButton
  279. {
  280. font-weight: bold;
  281. }
  282. """)
  283. grid_lay.addWidget(self.gcode_button, 18, 0, 1, 3)
  284. # STEP 3 #
  285. step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3"))
  286. step_3.setToolTip(
  287. _("Calculate Scale and Skew factors based on the differences (delta)\n"
  288. "found when checking the PCB pattern. The differences must be filled\n"
  289. "in the fields Found (Delta).")
  290. )
  291. grid_lay.addWidget(step_3, 19, 0, 1, 3)
  292. # ## Factors Button
  293. self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
  294. self.generate_factors_button.setToolTip(
  295. _("Calculate Scale and Skew factors based on the differences (delta)\n"
  296. "found when checking the PCB pattern. The differences must be filled\n"
  297. "in the fields Found (Delta).")
  298. )
  299. self.generate_factors_button.setStyleSheet("""
  300. QPushButton
  301. {
  302. font-weight: bold;
  303. }
  304. """)
  305. grid_lay.addWidget(self.generate_factors_button, 20, 0, 1, 3)
  306. scale_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Scale"))
  307. grid_lay.addWidget(scale_lbl, 21, 0, 1, 3)
  308. self.scalex_label = QtWidgets.QLabel(_("Factor X:"))
  309. self.scalex_label.setToolTip(
  310. _("Factor for Scale action over X axis.")
  311. )
  312. self.scalex_entry = FCDoubleSpinner()
  313. self.scalex_entry.set_range(0, 9999.9999)
  314. self.scalex_entry.set_precision(self.decimals)
  315. self.scalex_entry.setSingleStep(0.1)
  316. grid_lay.addWidget(self.scalex_label, 22, 0)
  317. grid_lay.addWidget(self.scalex_entry, 22, 1, 1, 2)
  318. self.scaley_label = QtWidgets.QLabel(_("Factor Y:"))
  319. self.scaley_label.setToolTip(
  320. _("Factor for Scale action over Y axis.")
  321. )
  322. self.scaley_entry = FCDoubleSpinner()
  323. self.scaley_entry.set_range(0, 9999.9999)
  324. self.scaley_entry.set_precision(self.decimals)
  325. self.scaley_entry.setSingleStep(0.1)
  326. grid_lay.addWidget(self.scaley_label, 23, 0)
  327. grid_lay.addWidget(self.scaley_entry, 23, 1, 1, 2)
  328. self.scale_button = QtWidgets.QPushButton(_("Scale"))
  329. self.scale_button.setToolTip(
  330. _("Apply Scale factors on the calibration points.")
  331. )
  332. self.scale_button.setStyleSheet("""
  333. QPushButton
  334. {
  335. font-weight: bold;
  336. }
  337. """)
  338. grid_lay.addWidget(self.scale_button, 24, 0, 1, 3)
  339. skew_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Skew"))
  340. grid_lay.addWidget(skew_lbl, 25, 0, 1, 3)
  341. self.skewx_label = QtWidgets.QLabel(_("Angle X:"))
  342. self.skewx_label.setToolTip(
  343. _("Angle for Skew action, in degrees.\n"
  344. "Float number between -360 and 359.")
  345. )
  346. self.skewx_entry = FCDoubleSpinner()
  347. self.skewx_entry.set_range(-360, 360)
  348. self.skewx_entry.set_precision(self.decimals)
  349. self.skewx_entry.setSingleStep(0.1)
  350. grid_lay.addWidget(self.skewx_label, 26, 0)
  351. grid_lay.addWidget(self.skewx_entry, 26, 1, 1, 2)
  352. self.skewy_label = QtWidgets.QLabel(_("Angle Y:"))
  353. self.skewy_label.setToolTip(
  354. _("Angle for Skew action, in degrees.\n"
  355. "Float number between -360 and 359.")
  356. )
  357. self.skewy_entry = FCDoubleSpinner()
  358. self.skewy_entry.set_range(-360, 360)
  359. self.skewy_entry.set_precision(self.decimals)
  360. self.skewy_entry.setSingleStep(0.1)
  361. grid_lay.addWidget(self.skewy_label, 27, 0)
  362. grid_lay.addWidget(self.skewy_entry, 27, 1, 1, 2)
  363. self.skew_button = QtWidgets.QPushButton(_("Skew"))
  364. self.skew_button.setToolTip(
  365. _("Apply Skew factors on the calibration points.")
  366. )
  367. self.skew_button.setStyleSheet("""
  368. QPushButton
  369. {
  370. font-weight: bold;
  371. }
  372. """)
  373. grid_lay.addWidget(self.skew_button, 28, 0, 1, 3)
  374. # STEP 4 #
  375. step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4"))
  376. step_4.setToolTip(
  377. _("Generate verification GCode file adjusted with\n"
  378. "the factors above.")
  379. )
  380. grid_lay.addWidget(step_4, 29, 0, 1, 3)
  381. # ## Adjusted GCode Button
  382. self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
  383. self.adj_gcode_button.setToolTip(
  384. _("Generate verification GCode file adjusted with\n"
  385. "the factors above.")
  386. )
  387. self.adj_gcode_button.setStyleSheet("""
  388. QPushButton
  389. {
  390. font-weight: bold;
  391. }
  392. """)
  393. grid_lay.addWidget(self.adj_gcode_button, 30, 0, 1, 3)
  394. # STEP 5 #
  395. step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5"))
  396. step_5.setToolTip(
  397. _("Ajust the Excellon and Cutout Geometry objects\n"
  398. "with the factors determined, and verified, above.")
  399. )
  400. grid_lay.addWidget(step_5, 31, 0, 1, 3)
  401. self.adj_exc_object_combo = QtWidgets.QComboBox()
  402. self.adj_exc_object_combo.setModel(self.app.collection)
  403. self.adj_exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  404. self.adj_exc_object_combo.setCurrentIndex(1)
  405. self.adj_excobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("EXCELLON"))
  406. self.adj_excobj_label.setToolTip(
  407. _("Excellon Object to be adjusted.")
  408. )
  409. grid_lay.addWidget(self.adj_excobj_label, 32, 0)
  410. grid_lay.addWidget(self.adj_exc_object_combo, 32, 1, 1, 2)
  411. self.adj_geo_object_combo = QtWidgets.QComboBox()
  412. self.adj_geo_object_combo.setModel(self.app.collection)
  413. self.adj_geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
  414. self.adj_geo_object_combo.setCurrentIndex(1)
  415. self.adj_geoobj_label = QtWidgets.QLabel("<b>%s:</b>" % _("GEOMETRY"))
  416. self.adj_geoobj_label.setToolTip(
  417. _("Geometry Object to be adjusted.")
  418. )
  419. grid_lay.addWidget(self.adj_geoobj_label, 33, 0)
  420. grid_lay.addWidget(self.adj_geo_object_combo, 33, 1, 1, 2)
  421. # ## Adjust Objects Button
  422. self.adj_obj_button = QtWidgets.QPushButton(_("Adjust Objects"))
  423. self.adj_obj_button.setToolTip(
  424. _("Adjust (scale and/or skew) the objects\n"
  425. "with the factors determined above.")
  426. )
  427. self.adj_obj_button.setStyleSheet("""
  428. QPushButton
  429. {
  430. font-weight: bold;
  431. }
  432. """)
  433. grid_lay.addWidget(self.adj_obj_button, 34, 0, 1, 3)
  434. grid_lay.addWidget(QtWidgets.QLabel(''), 35, 0)
  435. self.layout.addStretch()
  436. # ## Reset Tool
  437. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  438. self.reset_button.setToolTip(
  439. _("Will reset the tool parameters.")
  440. )
  441. self.reset_button.setStyleSheet("""
  442. QPushButton
  443. {
  444. font-weight: bold;
  445. }
  446. """)
  447. self.layout.addWidget(self.reset_button)
  448. self.mr = None
  449. self.units = ''
  450. # here store 4 points to be used for calibration
  451. self.click_points = list()
  452. # store the status of the grid
  453. self.grid_status_memory = None
  454. self.target_obj = None
  455. # ## Signals
  456. self.start_button.clicked.connect(self.on_start_collect_points)
  457. self.gcode_button.clicked.connect(self.generate_verification_gcode)
  458. self.generate_factors_button.clicked.connect(self.calculate_factors)
  459. self.reset_button.clicked.connect(self.set_tool_ui)
  460. self.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo)
  461. def run(self, toggle=True):
  462. self.app.report_usage("ToolCalibrate()")
  463. if toggle:
  464. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  465. if self.app.ui.splitter.sizes()[0] == 0:
  466. self.app.ui.splitter.setSizes([1, 1])
  467. else:
  468. try:
  469. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  470. # if tab is populated with the tool but it does not have the focus, focus on it
  471. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  472. # focus on Tool Tab
  473. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  474. else:
  475. self.app.ui.splitter.setSizes([0, 1])
  476. except AttributeError:
  477. pass
  478. else:
  479. if self.app.ui.splitter.sizes()[0] == 0:
  480. self.app.ui.splitter.setSizes([1, 1])
  481. FlatCAMTool.run(self)
  482. self.set_tool_ui()
  483. self.app.ui.notebook.setTabText(2, _("Calibrate Tool"))
  484. def install(self, icon=None, separator=None, **kwargs):
  485. FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs)
  486. def set_tool_ui(self):
  487. self.units = self.app.defaults['units'].upper()
  488. # ## Initialize form
  489. # self.mm_entry.set_value('%.*f' % (self.decimals, 0))
  490. def on_obj_type_combo(self):
  491. obj_type = self.obj_type_combo.currentIndex()
  492. self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  493. self.object_combo.setCurrentIndex(0)
  494. def on_start_collect_points(self):
  495. # disengage the grid snapping since it will be hard to find the drills on grid
  496. if self.app.ui.grid_snap_btn.isChecked():
  497. self.grid_status_memory = True
  498. self.app.ui.grid_snap_btn.trigger()
  499. else:
  500. self.grid_status_memory = False
  501. self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
  502. if self.app.is_legacy is False:
  503. self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
  504. else:
  505. self.canvas.graph_event_disconnect(self.app.mr)
  506. selection_index = self.object_combo.currentIndex()
  507. model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
  508. try:
  509. self.target_obj = model_index.internalPointer().obj
  510. except Exception:
  511. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no target object loaded ..."))
  512. return
  513. self.app.inform.emit(_("Click inside the First drill point. Bottom Left..."))
  514. def on_mouse_click_release(self, event):
  515. if event.button == 1:
  516. if self.app.is_legacy is False:
  517. event_pos = event.pos
  518. else:
  519. event_pos = (event.xdata, event.ydata)
  520. pos_canvas = self.canvas.translate_coords(event_pos)
  521. click_pt = Point([pos_canvas[0], pos_canvas[1]])
  522. if self.target_obj.kind.lower() == 'excellon':
  523. for tool, tool_dict in self.target_obj.tools.items():
  524. for geo in tool_dict['solid_geometry']:
  525. if click_pt.within(geo):
  526. center_pt = geo.centroid
  527. self.click_points.append(
  528. (
  529. float('%.*f' % (self.decimals, center_pt.x)),
  530. float('%.*f' % (self.decimals, center_pt.y))
  531. )
  532. )
  533. self.check_points()
  534. else:
  535. for tool, tool_dict in self.target_obj.apertures.items():
  536. pass
  537. def check_points(self):
  538. if len(self.click_points) == 1:
  539. self.bottom_left_coordx_tgt.set_value(self.click_points[0][0])
  540. self.bottom_left_coordy_tgt.set_value(self.click_points[0][1])
  541. self.app.inform.emit(_("Click inside the Second drill point. Bottom Right..."))
  542. elif len(self.click_points) == 2:
  543. self.bottom_right_coordx_tgt.set_value(self.click_points[1][0])
  544. self.bottom_right_coordy_tgt.set_value(self.click_points[1][1])
  545. self.app.inform.emit(_("Click inside the Third drill point. Top Left..."))
  546. elif len(self.click_points) == 3:
  547. self.top_left_coordx_tgt.set_value(self.click_points[2][0])
  548. self.top_left_coordy_tgt.set_value(self.click_points[2][1])
  549. self.app.inform.emit(_("Click inside the Fourth drill point. Top Right..."))
  550. elif len(self.click_points) == 4:
  551. self.top_right_coordx_tgt.set_value(self.click_points[3][0])
  552. self.top_right_coordy_tgt.set_value(self.click_points[3][1])
  553. self.app.inform.emit('[success] %s' % _("Done. All four points have been acquired."))
  554. self.disconnect_cal_events()
  555. # restore the Grid snapping if it was active before
  556. if self.grid_status_memory is True:
  557. self.app.ui.grid_snap_btn.trigger()
  558. def gcode_header(self):
  559. log.debug("ToolCalibrate.gcode_header()")
  560. time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
  561. gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
  562. (str(self.app.version), str(self.app.version_date)) + '\n'
  563. gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibrate Tool') + ')\n'
  564. gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
  565. gcode += '(Created on ' + time_str + ')\n' + '\n'
  566. gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
  567. gcode += 'G90\n'
  568. gcode += 'G17\n'
  569. gcode += 'G94\n\n'
  570. return gcode
  571. def close_tab(self):
  572. for idx in range(self.app.ui.plot_tab_area.count()):
  573. if self.app.ui.plot_tab_area.tabText(idx) == _("Gcode Viewer"):
  574. wdg = self.app.ui.plot_tab_area.widget(idx)
  575. wdg.deleteLater()
  576. self.app.ui.plot_tab_area.removeTab(idx)
  577. def generate_verification_gcode(self):
  578. travel_z = '%.*f' % (self.decimals, self.travelz_entry.get_value())
  579. toolchange_z = '%.*f' % (self.decimals, self.toolchangez_entry.get_value())
  580. verification_z = '%.*f' % (self.decimals, self.verz_entry.get_value())
  581. if len(self.click_points) != 4:
  582. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Four points are needed for GCode generation."))
  583. return 'fail'
  584. gcode = self.gcode_header()
  585. if self.zeroz_cb.get_value():
  586. gcode += 'M5\n'
  587. gcode += f'G00 Z{toolchange_z}\n'
  588. gcode += 'M0\n'
  589. gcode += 'G01 Z0\n'
  590. gcode += 'M0\n'
  591. gcode += f'G00 Z{toolchange_z}\n'
  592. gcode += 'M0\n'
  593. gcode += f'G00 Z{travel_z}\n'
  594. gcode += f'G00 X{self.click_points[0][0]} Y{self.click_points[0][1]}\n'
  595. gcode += f'G01 Z{verification_z}\n'
  596. gcode += 'M0\n'
  597. gcode += f'G00 Z{travel_z}\n'
  598. gcode += f'G00 X{self.click_points[2][0]} Y{self.click_points[2][1]}\n'
  599. gcode += f'G01 Z{verification_z}\n'
  600. gcode += 'M0\n'
  601. gcode += f'G00 Z{travel_z}\n'
  602. gcode += f'G00 X{self.click_points[3][0]} Y{self.click_points[3][1]}\n'
  603. gcode += f'G01 Z{verification_z}\n'
  604. gcode += 'M0\n'
  605. gcode += f'G00 Z{travel_z}\n'
  606. gcode += f'G00 X{self.click_points[1][0]} Y{self.click_points[1][1]}\n'
  607. gcode += f'G01 Z{verification_z}\n'
  608. gcode += 'M0\n'
  609. gcode += f'G00 Z{travel_z}\n'
  610. gcode += f'G00 X0 Y0\n'
  611. gcode += f'G00 Z{toolchange_z}\n'
  612. gcode += 'M2'
  613. self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True)
  614. # add the tab if it was closed
  615. self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Gcode Viewer"))
  616. self.gcode_editor_tab.setObjectName('gcode_viewer_tab')
  617. # delete the absolute and relative position and messages in the infobar
  618. self.app.ui.position_label.setText("")
  619. self.app.ui.rel_position_label.setText("")
  620. # first clear previous text in text editor (if any)
  621. self.gcode_editor_tab.code_editor.clear()
  622. self.gcode_editor_tab.code_editor.setReadOnly(False)
  623. self.gcode_editor_tab.code_editor.completer_enable = False
  624. self.gcode_editor_tab.buttonRun.hide()
  625. # Switch plot_area to CNCJob tab
  626. self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
  627. self.gcode_editor_tab.t_frame.hide()
  628. # then append the text from GCode to the text editor
  629. try:
  630. self.gcode_editor_tab.code_editor.setPlainText(gcode)
  631. except Exception as e:
  632. self.app.inform.emit('[ERROR] %s %s' % ('ERROR -->', str(e)))
  633. return
  634. self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
  635. self.gcode_editor_tab.t_frame.show()
  636. self.app.proc_container.view.set_idle()
  637. self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
  638. _filter_ = "G-Code Files (*.nc);;All Files (*.*)"
  639. self.gcode_editor_tab.buttonSave.clicked.disconnect()
  640. self.gcode_editor_tab.buttonSave.clicked.connect(
  641. lambda: self.gcode_editor_tab.handleSaveGCode(name='fc_ver_gcode', filt=_filter_, callback=self.close_tab))
  642. #
  643. # try:
  644. # dir_file_to_save = self.app.get_last_save_folder() + '/' + 'ver_gcode'
  645. # filename, _f = QtWidgets.QFileDialog.getSaveFileName(
  646. # caption=_("Export Machine Code ..."),
  647. # directory=dir_file_to_save,
  648. # filter=_filter_
  649. # )
  650. # except TypeError:
  651. # filename, _f = QtWidgets.QFileDialog.getSaveFileName(caption=_("Export Machine Code ..."), filter=_filter_)
  652. #
  653. # filename = str(filename)
  654. #
  655. # if filename == '':
  656. # self.app.inform.emit('[WARNING_NOTCL] %s' % _("Export Machine Code cancelled ..."))
  657. # return
  658. #
  659. # with open(filename, 'w') as f:
  660. # f.write(gcode)
  661. def calculate_factors(self):
  662. origin_x = self.click_points[0][0]
  663. origin_y = self.click_points[0][1]
  664. top_left_x = float('%.*f' % (self.decimals, self.click_points[2][0]))
  665. top_left_y = float('%.*f' % (self.decimals, self.click_points[2][1]))
  666. try:
  667. top_left_dx = float('%.*f' % (self.decimals, self.top_left_coordx_found.get_value()))
  668. except TypeError:
  669. top_left_dx = top_left_x
  670. try:
  671. top_left_dy = float('%.*f' % (self.decimals, self.top_left_coordy_found.get_value()))
  672. except TypeError:
  673. top_left_dy = top_left_y
  674. top_right_x = float('%.*f' % (self.decimals, self.click_points[3][0]))
  675. top_right_y = float('%.*f' % (self.decimals, self.click_points[3][1]))
  676. try:
  677. top_right_dx = float('%.*f' % (self.decimals, self.top_right_coordx_found.get_value()))
  678. except TypeError:
  679. top_right_dx = top_right_x
  680. try:
  681. top_right_dy = float('%.*f' % (self.decimals, self.top_right_coordy_found.get_value()))
  682. except TypeError:
  683. top_right_dy = top_right_y
  684. bot_right_x = float('%.*f' % (self.decimals, self.click_points[1][0]))
  685. bot_right_y = float('%.*f' % (self.decimals, self.click_points[1][1]))
  686. try:
  687. bot_right_dx = float('%.*f' % (self.decimals, self.bottom_right_coordx_found.get_value()))
  688. except TypeError:
  689. bot_right_dx = bot_right_x
  690. try:
  691. bot_right_dy = float('%.*f' % (self.decimals, self.bottom_right_coordy_found.get_value()))
  692. except TypeError:
  693. bot_right_dy = bot_right_y
  694. # ------------------------------------------------------------------------------- #
  695. # --------------------------- FACTORS CALCULUS ---------------------------------- #
  696. # ------------------------------------------------------------------------------- #
  697. if top_left_dy != float('%.*f' % (self.decimals, 0.0)):
  698. # we have scale on Y
  699. scale_y = (top_left_dy + top_left_y - origin_y) / (top_left_y - origin_y)
  700. self.scaley_entry.set_value(scale_y)
  701. if top_left_dx != float('%.*f' % (self.decimals, 0.0)):
  702. # we have skew on X
  703. dx = top_left_dx
  704. dy = top_left_y - origin_y
  705. skew_angle_x = math.degrees(math.atan(dx / dy))
  706. self.skewx_entry.set_value(skew_angle_x)
  707. if bot_right_dx != float('%.*f' % (self.decimals, 0.0)):
  708. # we have scale on X
  709. scale_x = (bot_right_dx + bot_right_x - origin_x) / (bot_right_x - origin_x)
  710. self.scalex_entry.set_value(scale_x)
  711. if bot_right_dy != float('%.*f' % (self.decimals, 0.0)):
  712. # we have skew on Y
  713. dx = bot_right_x - origin_x
  714. dy = bot_right_dy + origin_y
  715. skew_angle_y = math.degrees(math.atan(dy / dx))
  716. self.skewy_entry.set_value(skew_angle_y)
  717. def disconnect_cal_events(self):
  718. self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
  719. if self.app.is_legacy is False:
  720. self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
  721. else:
  722. self.canvas.graph_event_disconnect(self.mr)
  723. def reset_fields(self):
  724. self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  725. # end of file