ToolCalibration.py 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171
  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
  10. from flatcamGUI.GUIElements import FCTable, FCComboBox, RadioSet
  11. from flatcamEditors.FlatCAMTextEditor import TextEditor
  12. from shapely.geometry import Point
  13. from shapely.geometry.base import *
  14. import math
  15. from datetime import datetime
  16. import logging
  17. from copy import deepcopy
  18. import gettext
  19. import FlatCAMTranslation as fcTranslate
  20. import builtins
  21. fcTranslate.apply_language('strings')
  22. if '_' not in builtins.__dict__:
  23. _ = gettext.gettext
  24. log = logging.getLogger('base')
  25. class ToolCalibration(FlatCAMTool):
  26. toolName = _("Calibration Tool")
  27. def __init__(self, app):
  28. FlatCAMTool.__init__(self, app)
  29. self.app = app
  30. self.canvas = self.app.plotcanvas
  31. self.decimals = self.app.decimals
  32. # ## Title
  33. title_label = QtWidgets.QLabel("%s" % self.toolName)
  34. title_label.setStyleSheet("""
  35. QLabel
  36. {
  37. font-size: 16px;
  38. font-weight: bold;
  39. }
  40. """)
  41. self.layout.addWidget(title_label)
  42. self.layout.addWidget(QtWidgets.QLabel(''))
  43. # ## Grid Layout
  44. grid_lay = QtWidgets.QGridLayout()
  45. self.layout.addLayout(grid_lay)
  46. grid_lay.setColumnStretch(0, 0)
  47. grid_lay.setColumnStretch(1, 1)
  48. grid_lay.setColumnStretch(2, 0)
  49. step_1 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 1: Acquire Calibration Points"))
  50. step_1.setToolTip(
  51. _("Pick four points by clicking inside the drill holes.\n"
  52. "Those four points should be in the four\n"
  53. "(as much as possible) corners of the Excellon object.")
  54. )
  55. grid_lay.addWidget(step_1, 0, 0, 1, 3)
  56. self.cal_source_lbl = QtWidgets.QLabel("<b>%s:</b>" % _("Source Type"))
  57. self.cal_source_lbl.setToolTip(_("The source of calibration points.\n"
  58. "It can be:\n"
  59. "- Object -> click a hole geo for Excellon or a pad for Gerber\n"
  60. "- Free -> click freely on canvas to acquire the calibration points"))
  61. self.cal_source_radio = RadioSet([{'label': _('Object'), 'value': 'object'},
  62. {'label': _('Free'), 'value': 'free'}],
  63. stretch=False)
  64. grid_lay.addWidget(self.cal_source_lbl, 1, 0)
  65. grid_lay.addWidget(self.cal_source_radio, 1, 1, 1, 2)
  66. self.obj_type_label = QtWidgets.QLabel("%s:" % _("Object Type"))
  67. self.obj_type_combo = FCComboBox()
  68. self.obj_type_combo.addItem(_("Gerber"))
  69. self.obj_type_combo.addItem(_("Excellon"))
  70. self.obj_type_combo.setCurrentIndex(1)
  71. grid_lay.addWidget(self.obj_type_label, 2, 0)
  72. grid_lay.addWidget(self.obj_type_combo, 2, 1, 1, 2)
  73. self.object_combo = FCComboBox()
  74. self.object_combo.setModel(self.app.collection)
  75. self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  76. self.object_combo.setCurrentIndex(1)
  77. self.object_label = QtWidgets.QLabel("%s:" % _("Source object selection"))
  78. self.object_label.setToolTip(
  79. _("FlatCAM Object to be used as a source for reference points.")
  80. )
  81. grid_lay.addWidget(self.object_label, 3, 0, 1, 3)
  82. grid_lay.addWidget(self.object_combo, 4, 0, 1, 3)
  83. self.points_table_label = QtWidgets.QLabel('<b>%s</b>' % _('Calibration Points'))
  84. self.points_table_label.setToolTip(
  85. _("Contain the expected calibration points and the\n"
  86. "ones measured.")
  87. )
  88. grid_lay.addWidget(self.points_table_label, 5, 0, 1, 3)
  89. self.points_table = FCTable()
  90. self.points_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
  91. # self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  92. grid_lay.addWidget(self.points_table, 6, 0, 1, 3)
  93. self.points_table.setColumnCount(4)
  94. self.points_table.setHorizontalHeaderLabels(
  95. [
  96. '#',
  97. _("Name"),
  98. _("Target"),
  99. _("Found Delta")
  100. ]
  101. )
  102. self.points_table.setRowCount(8)
  103. row = 0
  104. # BOTTOM LEFT
  105. id_item_1 = QtWidgets.QTableWidgetItem('%d' % 1)
  106. flags = QtCore.Qt.ItemIsEnabled
  107. id_item_1.setFlags(flags)
  108. self.points_table.setItem(row, 0, id_item_1) # Tool name/id
  109. self.bottom_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Left X'))
  110. self.points_table.setCellWidget(row, 1, self.bottom_left_coordx_lbl)
  111. self.bottom_left_coordx_tgt = EvalEntry()
  112. self.points_table.setCellWidget(row, 2, self.bottom_left_coordx_tgt)
  113. self.bottom_left_coordx_tgt.setReadOnly(True)
  114. self.bottom_left_coordx_found = EvalEntry()
  115. self.points_table.setCellWidget(row, 3, self.bottom_left_coordx_found)
  116. row += 1
  117. self.bottom_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Left Y'))
  118. self.points_table.setCellWidget(row, 1, self.bottom_left_coordy_lbl)
  119. self.bottom_left_coordy_tgt = EvalEntry()
  120. self.points_table.setCellWidget(row, 2, self.bottom_left_coordy_tgt)
  121. self.bottom_left_coordy_tgt.setReadOnly(True)
  122. self.bottom_left_coordy_found = EvalEntry()
  123. self.points_table.setCellWidget(row, 3, self.bottom_left_coordy_found)
  124. self.bottom_left_coordx_found.set_value(_("Origin"))
  125. self.bottom_left_coordy_found.set_value(_("Origin"))
  126. self.bottom_left_coordx_found.setDisabled(True)
  127. self.bottom_left_coordy_found.setDisabled(True)
  128. row += 1
  129. # BOTTOM RIGHT
  130. id_item_2 = QtWidgets.QTableWidgetItem('%d' % 2)
  131. flags = QtCore.Qt.ItemIsEnabled
  132. id_item_2.setFlags(flags)
  133. self.points_table.setItem(row, 0, id_item_2) # Tool name/id
  134. self.bottom_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Bot Right X'))
  135. self.points_table.setCellWidget(row, 1, self.bottom_right_coordx_lbl)
  136. self.bottom_right_coordx_tgt = EvalEntry()
  137. self.points_table.setCellWidget(row, 2, self.bottom_right_coordx_tgt)
  138. self.bottom_right_coordx_tgt.setReadOnly(True)
  139. self.bottom_right_coordx_found = EvalEntry()
  140. self.points_table.setCellWidget(row, 3, self.bottom_right_coordx_found)
  141. row += 1
  142. self.bottom_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Bot Right Y'))
  143. self.points_table.setCellWidget(row, 1, self.bottom_right_coordy_lbl)
  144. self.bottom_right_coordy_tgt = EvalEntry()
  145. self.points_table.setCellWidget(row, 2, self.bottom_right_coordy_tgt)
  146. self.bottom_right_coordy_tgt.setReadOnly(True)
  147. self.bottom_right_coordy_found = EvalEntry()
  148. self.points_table.setCellWidget(row, 3, self.bottom_right_coordy_found)
  149. row += 1
  150. # TOP LEFT
  151. id_item_3 = QtWidgets.QTableWidgetItem('%d' % 3)
  152. flags = QtCore.Qt.ItemIsEnabled
  153. id_item_3.setFlags(flags)
  154. self.points_table.setItem(row, 0, id_item_3) # Tool name/id
  155. self.top_left_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Left X'))
  156. self.points_table.setCellWidget(row, 1, self.top_left_coordx_lbl)
  157. self.top_left_coordx_tgt = EvalEntry()
  158. self.points_table.setCellWidget(row, 2, self.top_left_coordx_tgt)
  159. self.top_left_coordx_tgt.setReadOnly(True)
  160. self.top_left_coordx_found = EvalEntry()
  161. self.points_table.setCellWidget(row, 3, self.top_left_coordx_found)
  162. row += 1
  163. self.top_left_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Left Y'))
  164. self.points_table.setCellWidget(row, 1, self.top_left_coordy_lbl)
  165. self.top_left_coordy_tgt = EvalEntry()
  166. self.points_table.setCellWidget(row, 2, self.top_left_coordy_tgt)
  167. self.top_left_coordy_tgt.setReadOnly(True)
  168. self.top_left_coordy_found = EvalEntry()
  169. self.points_table.setCellWidget(row, 3, self.top_left_coordy_found)
  170. row += 1
  171. # TOP RIGHT
  172. id_item_4 = QtWidgets.QTableWidgetItem('%d' % 4)
  173. flags = QtCore.Qt.ItemIsEnabled
  174. id_item_4.setFlags(flags)
  175. self.points_table.setItem(row, 0, id_item_4) # Tool name/id
  176. self.top_right_coordx_lbl = QtWidgets.QLabel('%s' % _('Top Right X'))
  177. self.points_table.setCellWidget(row, 1, self.top_right_coordx_lbl)
  178. self.top_right_coordx_tgt = EvalEntry()
  179. self.points_table.setCellWidget(row, 2, self.top_right_coordx_tgt)
  180. self.top_right_coordx_tgt.setReadOnly(True)
  181. self.top_right_coordx_found = EvalEntry()
  182. self.points_table.setCellWidget(row, 3, self.top_right_coordx_found)
  183. row += 1
  184. self.top_right_coordy_lbl = QtWidgets.QLabel('%s' % _('Top Right Y'))
  185. self.points_table.setCellWidget(row, 1, self.top_right_coordy_lbl)
  186. self.top_right_coordy_tgt = EvalEntry()
  187. self.points_table.setCellWidget(row, 2, self.top_right_coordy_tgt)
  188. self.top_right_coordy_tgt.setReadOnly(True)
  189. self.top_right_coordy_found = EvalEntry()
  190. self.points_table.setCellWidget(row, 3, self.top_right_coordy_found)
  191. vertical_header = self.points_table.verticalHeader()
  192. vertical_header.hide()
  193. self.points_table.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  194. horizontal_header = self.points_table.horizontalHeader()
  195. horizontal_header.setMinimumSectionSize(10)
  196. horizontal_header.setDefaultSectionSize(70)
  197. self.points_table.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
  198. # for x in range(4):
  199. # self.points_table.resizeColumnToContents(x)
  200. self.points_table.resizeColumnsToContents()
  201. self.points_table.resizeRowsToContents()
  202. horizontal_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Fixed)
  203. horizontal_header.resizeSection(0, 20)
  204. horizontal_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
  205. horizontal_header.setSectionResizeMode(2, QtWidgets.QHeaderView.Stretch)
  206. horizontal_header.setSectionResizeMode(3, QtWidgets.QHeaderView.Stretch)
  207. self.points_table.setMinimumHeight(self.points_table.getHeight() + 2)
  208. self.points_table.setMaximumHeight(self.points_table.getHeight() + 3)
  209. # ## Get Points Button
  210. self.start_button = QtWidgets.QPushButton(_("Get Points"))
  211. self.start_button.setToolTip(
  212. _("Pick four points by clicking on canvas if the source choice\n"
  213. "is 'free' or inside the object geometry if the source is 'object'.\n"
  214. "Those four points should be in the four squares of\n"
  215. "the object.")
  216. )
  217. self.start_button.setStyleSheet("""
  218. QPushButton
  219. {
  220. font-weight: bold;
  221. }
  222. """)
  223. grid_lay.addWidget(self.start_button, 7, 0, 1, 3)
  224. separator_line = QtWidgets.QFrame()
  225. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  226. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  227. grid_lay.addWidget(separator_line, 8, 0, 1, 3)
  228. grid_lay.addWidget(QtWidgets.QLabel(''), 9, 0)
  229. # STEP 2 #
  230. step_2 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 2: Verification GCode"))
  231. step_2.setToolTip(
  232. _("Generate GCode file to locate and align the PCB by using\n"
  233. "the four points acquired above.")
  234. )
  235. grid_lay.addWidget(step_2, 10, 0, 1, 3)
  236. self.gcode_title_label = QtWidgets.QLabel('<b>%s</b>' % _('GCode Parameters'))
  237. self.gcode_title_label.setToolTip(
  238. _("Parameters used when creating the GCode in this tool.")
  239. )
  240. grid_lay.addWidget(self.gcode_title_label, 11, 0, 1, 3)
  241. # Travel Z entry
  242. travelz_lbl = QtWidgets.QLabel('%s:' % _("Travel Z"))
  243. travelz_lbl.setToolTip(
  244. _("Height (Z) for travelling between the points.")
  245. )
  246. self.travelz_entry = FCDoubleSpinner()
  247. self.travelz_entry.set_range(-9999.9999, 9999.9999)
  248. self.travelz_entry.set_precision(self.decimals)
  249. self.travelz_entry.setSingleStep(0.1)
  250. grid_lay.addWidget(travelz_lbl, 12, 0)
  251. grid_lay.addWidget(self.travelz_entry, 12, 1, 1, 2)
  252. # Verification Z entry
  253. verz_lbl = QtWidgets.QLabel('%s:' % _("Verification Z"))
  254. verz_lbl.setToolTip(
  255. _("Height (Z) for checking the point.")
  256. )
  257. self.verz_entry = FCDoubleSpinner()
  258. self.verz_entry.set_range(-9999.9999, 9999.9999)
  259. self.verz_entry.set_precision(self.decimals)
  260. self.verz_entry.setSingleStep(0.1)
  261. grid_lay.addWidget(verz_lbl, 13, 0)
  262. grid_lay.addWidget(self.verz_entry, 13, 1, 1, 2)
  263. # Zero the Z of the verification tool
  264. self.zeroz_cb = FCCheckBox('%s' % _("Zero Z tool"))
  265. self.zeroz_cb.setToolTip(
  266. _("Include a sequence to zero the height (Z)\n"
  267. "of the verification tool.")
  268. )
  269. grid_lay.addWidget(self.zeroz_cb, 14, 0, 1, 3)
  270. # Toochange Z entry
  271. toolchangez_lbl = QtWidgets.QLabel('%s:' % _("Toolchange Z"))
  272. toolchangez_lbl.setToolTip(
  273. _("Height (Z) for mounting the verification probe.")
  274. )
  275. self.toolchangez_entry = FCDoubleSpinner()
  276. self.toolchangez_entry.set_range(0.0000, 9999.9999)
  277. self.toolchangez_entry.set_precision(self.decimals)
  278. self.toolchangez_entry.setSingleStep(0.1)
  279. grid_lay.addWidget(toolchangez_lbl, 15, 0)
  280. grid_lay.addWidget(self.toolchangez_entry, 15, 1, 1, 2)
  281. self.z_ois = OptionalInputSection(self.zeroz_cb, [toolchangez_lbl, self.toolchangez_entry])
  282. # ## GCode Button
  283. self.gcode_button = QtWidgets.QPushButton(_("Generate GCode"))
  284. self.gcode_button.setToolTip(
  285. _("Generate GCode file to locate and align the PCB by using\n"
  286. "the four points acquired above.")
  287. )
  288. self.gcode_button.setStyleSheet("""
  289. QPushButton
  290. {
  291. font-weight: bold;
  292. }
  293. """)
  294. grid_lay.addWidget(self.gcode_button, 16, 0, 1, 3)
  295. separator_line1 = QtWidgets.QFrame()
  296. separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
  297. separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
  298. grid_lay.addWidget(separator_line1, 17, 0, 1, 3)
  299. grid_lay.addWidget(QtWidgets.QLabel(''), 18, 0, 1, 3)
  300. # STEP 3 #
  301. step_3 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 3: Adjustments"))
  302. step_3.setToolTip(
  303. _("Calculate Scale and Skew factors based on the differences (delta)\n"
  304. "found when checking the PCB pattern. The differences must be filled\n"
  305. "in the fields Found (Delta).")
  306. )
  307. grid_lay.addWidget(step_3, 19, 0, 1, 3)
  308. # ## Factors Button
  309. self.generate_factors_button = QtWidgets.QPushButton(_("Calculate Factors"))
  310. self.generate_factors_button.setToolTip(
  311. _("Calculate Scale and Skew factors based on the differences (delta)\n"
  312. "found when checking the PCB pattern. The differences must be filled\n"
  313. "in the fields Found (Delta).")
  314. )
  315. self.generate_factors_button.setStyleSheet("""
  316. QPushButton
  317. {
  318. font-weight: bold;
  319. }
  320. """)
  321. grid_lay.addWidget(self.generate_factors_button, 20, 0, 1, 3)
  322. separator_line1 = QtWidgets.QFrame()
  323. separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
  324. separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
  325. grid_lay.addWidget(separator_line1, 21, 0, 1, 3)
  326. grid_lay.addWidget(QtWidgets.QLabel(''), 22, 0, 1, 3)
  327. # STEP 4 #
  328. step_4 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 4: Adjusted GCode"))
  329. step_4.setToolTip(
  330. _("Generate verification GCode file adjusted with\n"
  331. "the factors above.")
  332. )
  333. grid_lay.addWidget(step_4, 23, 0, 1, 3)
  334. self.scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
  335. self.scalex_label.setToolTip(
  336. _("Factor for Scale action over X axis.")
  337. )
  338. self.scalex_entry = FCDoubleSpinner()
  339. self.scalex_entry.set_range(0, 9999.9999)
  340. self.scalex_entry.set_precision(self.decimals)
  341. self.scalex_entry.setSingleStep(0.1)
  342. grid_lay.addWidget(self.scalex_label, 24, 0)
  343. grid_lay.addWidget(self.scalex_entry, 24, 1, 1, 2)
  344. self.scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
  345. self.scaley_label.setToolTip(
  346. _("Factor for Scale action over Y axis.")
  347. )
  348. self.scaley_entry = FCDoubleSpinner()
  349. self.scaley_entry.set_range(0, 9999.9999)
  350. self.scaley_entry.set_precision(self.decimals)
  351. self.scaley_entry.setSingleStep(0.1)
  352. grid_lay.addWidget(self.scaley_label, 25, 0)
  353. grid_lay.addWidget(self.scaley_entry, 25, 1, 1, 2)
  354. self.scale_button = QtWidgets.QPushButton(_("Apply Scale Factors"))
  355. self.scale_button.setToolTip(
  356. _("Apply Scale factors on the calibration points.")
  357. )
  358. self.scale_button.setStyleSheet("""
  359. QPushButton
  360. {
  361. font-weight: bold;
  362. }
  363. """)
  364. grid_lay.addWidget(self.scale_button, 26, 0, 1, 3)
  365. self.skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
  366. self.skewx_label.setToolTip(
  367. _("Angle for Skew action, in degrees.\n"
  368. "Float number between -360 and 359.")
  369. )
  370. self.skewx_entry = FCDoubleSpinner()
  371. self.skewx_entry.set_range(-360, 360)
  372. self.skewx_entry.set_precision(self.decimals)
  373. self.skewx_entry.setSingleStep(0.1)
  374. grid_lay.addWidget(self.skewx_label, 27, 0)
  375. grid_lay.addWidget(self.skewx_entry, 27, 1, 1, 2)
  376. self.skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
  377. self.skewy_label.setToolTip(
  378. _("Angle for Skew action, in degrees.\n"
  379. "Float number between -360 and 359.")
  380. )
  381. self.skewy_entry = FCDoubleSpinner()
  382. self.skewy_entry.set_range(-360, 360)
  383. self.skewy_entry.set_precision(self.decimals)
  384. self.skewy_entry.setSingleStep(0.1)
  385. grid_lay.addWidget(self.skewy_label, 28, 0)
  386. grid_lay.addWidget(self.skewy_entry, 28, 1, 1, 2)
  387. self.skew_button = QtWidgets.QPushButton(_("Apply Skew Factors"))
  388. self.skew_button.setToolTip(
  389. _("Apply Skew factors on the calibration points.")
  390. )
  391. self.skew_button.setStyleSheet("""
  392. QPushButton
  393. {
  394. font-weight: bold;
  395. }
  396. """)
  397. grid_lay.addWidget(self.skew_button, 29, 0, 1, 3)
  398. # final_factors_lbl = QtWidgets.QLabel('<b>%s</b>' % _("Final Factors"))
  399. # final_factors_lbl.setToolTip(
  400. # _("Generate verification GCode file adjusted with\n"
  401. # "the factors above.")
  402. # )
  403. # grid_lay.addWidget(final_factors_lbl, 27, 0, 1, 3)
  404. #
  405. # self.fin_scalex_label = QtWidgets.QLabel(_("Scale Factor X:"))
  406. # self.fin_scalex_label.setToolTip(
  407. # _("Final factor for Scale action over X axis.")
  408. # )
  409. # self.fin_scalex_entry = FCDoubleSpinner()
  410. # self.fin_scalex_entry.set_range(0, 9999.9999)
  411. # self.fin_scalex_entry.set_precision(self.decimals)
  412. # self.fin_scalex_entry.setSingleStep(0.1)
  413. #
  414. # grid_lay.addWidget(self.fin_scalex_label, 28, 0)
  415. # grid_lay.addWidget(self.fin_scalex_entry, 28, 1, 1, 2)
  416. #
  417. # self.fin_scaley_label = QtWidgets.QLabel(_("Scale Factor Y:"))
  418. # self.fin_scaley_label.setToolTip(
  419. # _("Final factor for Scale action over Y axis.")
  420. # )
  421. # self.fin_scaley_entry = FCDoubleSpinner()
  422. # self.fin_scaley_entry.set_range(0, 9999.9999)
  423. # self.fin_scaley_entry.set_precision(self.decimals)
  424. # self.fin_scaley_entry.setSingleStep(0.1)
  425. #
  426. # grid_lay.addWidget(self.fin_scaley_label, 29, 0)
  427. # grid_lay.addWidget(self.fin_scaley_entry, 29, 1, 1, 2)
  428. #
  429. # self.fin_skewx_label = QtWidgets.QLabel(_("Skew Angle X:"))
  430. # self.fin_skewx_label.setToolTip(
  431. # _("Final value for angle for Skew action, in degrees.\n"
  432. # "Float number between -360 and 359.")
  433. # )
  434. # self.fin_skewx_entry = FCDoubleSpinner()
  435. # self.fin_skewx_entry.set_range(-360, 360)
  436. # self.fin_skewx_entry.set_precision(self.decimals)
  437. # self.fin_skewx_entry.setSingleStep(0.1)
  438. #
  439. # grid_lay.addWidget(self.fin_skewx_label, 30, 0)
  440. # grid_lay.addWidget(self.fin_skewx_entry, 30, 1, 1, 2)
  441. #
  442. # self.fin_skewy_label = QtWidgets.QLabel(_("Skew Angle Y:"))
  443. # self.fin_skewy_label.setToolTip(
  444. # _("Final value for angle for Skew action, in degrees.\n"
  445. # "Float number between -360 and 359.")
  446. # )
  447. # self.fin_skewy_entry = FCDoubleSpinner()
  448. # self.fin_skewy_entry.set_range(-360, 360)
  449. # self.fin_skewy_entry.set_precision(self.decimals)
  450. # self.fin_skewy_entry.setSingleStep(0.1)
  451. #
  452. # grid_lay.addWidget(self.fin_skewy_label, 31, 0)
  453. # grid_lay.addWidget(self.fin_skewy_entry, 31, 1, 1, 2)
  454. # ## Adjusted GCode Button
  455. self.adj_gcode_button = QtWidgets.QPushButton(_("Generate Adjusted GCode"))
  456. self.adj_gcode_button.setToolTip(
  457. _("Generate verification GCode file adjusted with\n"
  458. "the factors above.")
  459. )
  460. self.adj_gcode_button.setStyleSheet("""
  461. QPushButton
  462. {
  463. font-weight: bold;
  464. }
  465. """)
  466. grid_lay.addWidget(self.adj_gcode_button, 35, 0, 1, 3)
  467. separator_line1 = QtWidgets.QFrame()
  468. separator_line1.setFrameShape(QtWidgets.QFrame.HLine)
  469. separator_line1.setFrameShadow(QtWidgets.QFrame.Sunken)
  470. grid_lay.addWidget(separator_line1, 36, 0, 1, 3)
  471. grid_lay.addWidget(QtWidgets.QLabel(''), 37, 0, 1, 3)
  472. # STEP 5 #
  473. step_5 = QtWidgets.QLabel('<b>%s</b>' % _("STEP 5: Calibrate FlatCAM Objects"))
  474. step_5.setToolTip(
  475. _("Adjust the FlatCAM objects\n"
  476. "with the factors determined and verified above.")
  477. )
  478. grid_lay.addWidget(step_5, 38, 0, 1, 3)
  479. self.adj_object_type_combo = QtWidgets.QComboBox()
  480. self.adj_object_type_combo.addItems([_("Gerber"), _("Excellon"), _("Geometry")])
  481. self.adj_object_type_combo.setCurrentIndex(0)
  482. self.adj_object_type_label = QtWidgets.QLabel("%s:" % _("Adjusted object type"))
  483. self.adj_object_type_label.setToolTip(
  484. _("Type of the FlatCAM Object to be adjusted.")
  485. )
  486. grid_lay.addWidget(self.adj_object_type_label, 39, 0, 1, 3)
  487. grid_lay.addWidget(self.adj_object_type_combo, 40, 0, 1, 3)
  488. self.adj_object_combo = FCComboBox()
  489. self.adj_object_combo.setModel(self.app.collection)
  490. self.adj_object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
  491. self.adj_object_combo.setCurrentIndex(0)
  492. self.adj_object_label = QtWidgets.QLabel("%s:" % _("Adjusted object selection"))
  493. self.adj_object_label.setToolTip(
  494. _("The FlatCAM Object to be adjusted.")
  495. )
  496. grid_lay.addWidget(self.adj_object_label, 41, 0, 1, 3)
  497. grid_lay.addWidget(self.adj_object_combo, 42, 0, 1, 3)
  498. # ## Adjust Objects Button
  499. self.cal_button = QtWidgets.QPushButton(_("Calibrate"))
  500. self.cal_button.setToolTip(
  501. _("Adjust (scale and/or skew) the objects\n"
  502. "with the factors determined above.")
  503. )
  504. self.cal_button.setStyleSheet("""
  505. QPushButton
  506. {
  507. font-weight: bold;
  508. }
  509. """)
  510. grid_lay.addWidget(self.cal_button, 43, 0, 1, 3)
  511. separator_line2 = QtWidgets.QFrame()
  512. separator_line2.setFrameShape(QtWidgets.QFrame.HLine)
  513. separator_line2.setFrameShadow(QtWidgets.QFrame.Sunken)
  514. grid_lay.addWidget(separator_line2, 44, 0, 1, 3)
  515. grid_lay.addWidget(QtWidgets.QLabel(''), 45, 0, 1, 3)
  516. self.layout.addStretch()
  517. # ## Reset Tool
  518. self.reset_button = QtWidgets.QPushButton(_("Reset Tool"))
  519. self.reset_button.setToolTip(
  520. _("Will reset the tool parameters.")
  521. )
  522. self.reset_button.setStyleSheet("""
  523. QPushButton
  524. {
  525. font-weight: bold;
  526. }
  527. """)
  528. self.layout.addWidget(self.reset_button)
  529. self.mr = None
  530. self.units = ''
  531. # here store 4 points to be used for calibration
  532. self.click_points = list()
  533. # store the status of the grid
  534. self.grid_status_memory = None
  535. self.target_obj = None
  536. # if the mouse events are connected to a local method set this True
  537. self.local_connected = False
  538. # reference for the tab where to open and view the verification GCode
  539. self.gcode_editor_tab = None
  540. # calibrated object
  541. self.cal_object = None
  542. # ## Signals
  543. self.start_button.clicked.connect(self.on_start_collect_points)
  544. self.gcode_button.clicked.connect(self.generate_verification_gcode)
  545. self.generate_factors_button.clicked.connect(self.calculate_factors)
  546. self.reset_button.clicked.connect(self.set_tool_ui)
  547. self.cal_source_radio.activated_custom.connect(self.on_cal_source_radio)
  548. self.obj_type_combo.currentIndexChanged.connect(self.on_obj_type_combo)
  549. self.adj_object_type_combo.currentIndexChanged.connect(self.on_adj_obj_type_combo)
  550. self.cal_button.clicked.connect(self.on_cal_button_click)
  551. def run(self, toggle=True):
  552. self.app.report_usage("ToolCalibration()")
  553. if toggle:
  554. # if the splitter is hidden, display it, else hide it but only if the current widget is the same
  555. if self.app.ui.splitter.sizes()[0] == 0:
  556. self.app.ui.splitter.setSizes([1, 1])
  557. else:
  558. try:
  559. if self.app.ui.tool_scroll_area.widget().objectName() == self.toolName:
  560. # if tab is populated with the tool but it does not have the focus, focus on it
  561. if not self.app.ui.notebook.currentWidget() is self.app.ui.tool_tab:
  562. # focus on Tool Tab
  563. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  564. else:
  565. self.app.ui.splitter.setSizes([0, 1])
  566. except AttributeError:
  567. pass
  568. else:
  569. if self.app.ui.splitter.sizes()[0] == 0:
  570. self.app.ui.splitter.setSizes([1, 1])
  571. FlatCAMTool.run(self)
  572. self.set_tool_ui()
  573. self.app.ui.notebook.setTabText(2, _("Calibrate Tool"))
  574. def install(self, icon=None, separator=None, **kwargs):
  575. FlatCAMTool.install(self, icon, separator, shortcut='ALT+E', **kwargs)
  576. def set_tool_ui(self):
  577. self.units = self.app.defaults['units'].upper()
  578. if self.local_connected is True:
  579. self.disconnect_cal_events()
  580. self.reset_calibration_points()
  581. self.cal_source_radio.set_value(self.app.defaults['tools_cal_calsource'])
  582. self.travelz_entry.set_value(self.app.defaults['tools_cal_travelz'])
  583. self.verz_entry.set_value(self.app.defaults['tools_cal_verz'])
  584. self.zeroz_cb.set_value(self.app.defaults['tools_cal_zeroz'])
  585. self.toolchangez_entry.set_value(self.app.defaults['tools_cal_toolchangez'])
  586. self.scalex_entry.set_value(1.0)
  587. self.scaley_entry.set_value(1.0)
  588. self.skewx_entry.set_value(0.0)
  589. self.skewy_entry.set_value(0.0)
  590. # calibrated object
  591. self.cal_object = None
  592. self.app.inform.emit('%s...' % _("Tool initialized"))
  593. def on_obj_type_combo(self):
  594. obj_type = self.obj_type_combo.currentIndex()
  595. self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  596. self.object_combo.setCurrentIndex(0)
  597. def on_adj_obj_type_combo(self):
  598. obj_type = self.adj_object_type_combo.currentIndex()
  599. self.adj_object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
  600. self.adj_object_combo.setCurrentIndex(0)
  601. def on_cal_source_radio(self, val):
  602. if val == 'object':
  603. self.obj_type_label.setDisabled(False)
  604. self.obj_type_combo.setDisabled(False)
  605. self.object_label.setDisabled(False)
  606. self.object_combo.setDisabled(False)
  607. else:
  608. self.obj_type_label.setDisabled(True)
  609. self.obj_type_combo.setDisabled(True)
  610. self.object_label.setDisabled(True)
  611. self.object_combo.setDisabled(True)
  612. def on_start_collect_points(self):
  613. if self.cal_source_radio.get_value() == 'object':
  614. selection_index = self.object_combo.currentIndex()
  615. model_index = self.app.collection.index(selection_index, 0, self.object_combo.rootModelIndex())
  616. try:
  617. self.target_obj = model_index.internalPointer().obj
  618. except Exception:
  619. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no source FlatCAM object selected..."))
  620. return
  621. # disengage the grid snapping since it will be hard to find the drills on grid
  622. if self.app.ui.grid_snap_btn.isChecked():
  623. self.grid_status_memory = True
  624. self.app.ui.grid_snap_btn.trigger()
  625. else:
  626. self.grid_status_memory = False
  627. self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
  628. if self.app.is_legacy is False:
  629. self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
  630. else:
  631. self.canvas.graph_event_disconnect(self.app.mr)
  632. self.local_connected = True
  633. self.reset_calibration_points()
  634. self.app.inform.emit(_("Get First calibration point. Bottom Left..."))
  635. def on_mouse_click_release(self, event):
  636. if event.button == 1:
  637. if self.app.is_legacy is False:
  638. event_pos = event.pos
  639. else:
  640. event_pos = (event.xdata, event.ydata)
  641. pos_canvas = self.canvas.translate_coords(event_pos)
  642. click_pt = Point([pos_canvas[0], pos_canvas[1]])
  643. if self.cal_source_radio.get_value() == 'object':
  644. if self.target_obj.kind.lower() == 'excellon':
  645. for tool, tool_dict in self.target_obj.tools.items():
  646. for geo in tool_dict['solid_geometry']:
  647. if click_pt.within(geo):
  648. center_pt = geo.centroid
  649. self.click_points.append(
  650. (
  651. float('%.*f' % (self.decimals, center_pt.x)),
  652. float('%.*f' % (self.decimals, center_pt.y))
  653. )
  654. )
  655. self.check_points()
  656. else:
  657. for apid, apid_val in self.target_obj.apertures.items():
  658. for geo_el in apid_val['geometry']:
  659. if 'solid' in geo_el:
  660. if click_pt.within(geo_el['solid']):
  661. if isinstance(geo_el['follow'], Point):
  662. center_pt = geo_el['solid'].centroid
  663. self.click_points.append(
  664. (
  665. float('%.*f' % (self.decimals, center_pt.x)),
  666. float('%.*f' % (self.decimals, center_pt.y))
  667. )
  668. )
  669. self.check_points()
  670. else:
  671. self.click_points.append(
  672. (
  673. float('%.*f' % (self.decimals, click_pt.x)),
  674. float('%.*f' % (self.decimals, click_pt.y))
  675. )
  676. )
  677. self.check_points()
  678. def check_points(self):
  679. if len(self.click_points) == 1:
  680. self.bottom_left_coordx_tgt.set_value(self.click_points[0][0])
  681. self.bottom_left_coordy_tgt.set_value(self.click_points[0][1])
  682. self.app.inform.emit(_("Get Second calibration point. Bottom Right..."))
  683. elif len(self.click_points) == 2:
  684. self.bottom_right_coordx_tgt.set_value(self.click_points[1][0])
  685. self.bottom_right_coordy_tgt.set_value(self.click_points[1][1])
  686. self.app.inform.emit(_("Get Third calibration point. Top Left..."))
  687. elif len(self.click_points) == 3:
  688. self.top_left_coordx_tgt.set_value(self.click_points[2][0])
  689. self.top_left_coordy_tgt.set_value(self.click_points[2][1])
  690. self.app.inform.emit(_("Get Forth calibration point. Top Right..."))
  691. elif len(self.click_points) == 4:
  692. self.top_right_coordx_tgt.set_value(self.click_points[3][0])
  693. self.top_right_coordy_tgt.set_value(self.click_points[3][1])
  694. self.app.inform.emit('[success] %s' % _("Done. All four points have been acquired."))
  695. self.disconnect_cal_events()
  696. def reset_calibration_points(self):
  697. self.click_points = list()
  698. self.bottom_left_coordx_tgt.set_value('')
  699. self.bottom_left_coordy_tgt.set_value('')
  700. self.bottom_right_coordx_tgt.set_value('')
  701. self.bottom_right_coordy_tgt.set_value('')
  702. self.top_left_coordx_tgt.set_value('')
  703. self.top_left_coordy_tgt.set_value('')
  704. self.top_right_coordx_tgt.set_value('')
  705. self.top_right_coordy_tgt.set_value('')
  706. def gcode_header(self):
  707. log.debug("ToolCalibration.gcode_header()")
  708. time_str = "{:%A, %d %B %Y at %H:%M}".format(datetime.now())
  709. gcode = '(G-CODE GENERATED BY FLATCAM v%s - www.flatcam.org - Version Date: %s)\n' % \
  710. (str(self.app.version), str(self.app.version_date)) + '\n'
  711. gcode += '(Name: ' + _('Verification GCode for FlatCAM Calibrate Tool') + ')\n'
  712. gcode += '(Units: ' + self.units.upper() + ')\n' + "\n"
  713. gcode += '(Created on ' + time_str + ')\n' + '\n'
  714. gcode += 'G20\n' if self.units.upper() == 'IN' else 'G21\n'
  715. gcode += 'G90\n'
  716. gcode += 'G17\n'
  717. gcode += 'G94\n\n'
  718. return gcode
  719. def close_tab(self):
  720. for idx in range(self.app.ui.plot_tab_area.count()):
  721. if self.app.ui.plot_tab_area.tabText(idx) == _("Gcode Viewer"):
  722. wdg = self.app.ui.plot_tab_area.widget(idx)
  723. wdg.deleteLater()
  724. self.app.ui.plot_tab_area.removeTab(idx)
  725. def generate_verification_gcode(self):
  726. travel_z = '%.*f' % (self.decimals, self.travelz_entry.get_value())
  727. toolchange_z = '%.*f' % (self.decimals, self.toolchangez_entry.get_value())
  728. verification_z = '%.*f' % (self.decimals, self.verz_entry.get_value())
  729. if len(self.click_points) != 4:
  730. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Cancelled. Four points are needed for GCode generation."))
  731. return 'fail'
  732. gcode = self.gcode_header()
  733. if self.zeroz_cb.get_value():
  734. gcode += 'M5\n'
  735. gcode += f'G00 Z{toolchange_z}\n'
  736. gcode += 'M0\n'
  737. gcode += 'G01 Z0\n'
  738. gcode += 'M0\n'
  739. gcode += f'G00 Z{toolchange_z}\n'
  740. gcode += 'M0\n'
  741. gcode += f'G00 Z{travel_z}\n'
  742. gcode += f'G00 X{self.click_points[0][0]} Y{self.click_points[0][1]}\n'
  743. gcode += f'G01 Z{verification_z}\n'
  744. gcode += 'M0\n'
  745. gcode += f'G00 Z{travel_z}\n'
  746. gcode += f'G00 X{self.click_points[2][0]} Y{self.click_points[2][1]}\n'
  747. gcode += f'G01 Z{verification_z}\n'
  748. gcode += 'M0\n'
  749. gcode += f'G00 Z{travel_z}\n'
  750. gcode += f'G00 X{self.click_points[3][0]} Y{self.click_points[3][1]}\n'
  751. gcode += f'G01 Z{verification_z}\n'
  752. gcode += 'M0\n'
  753. gcode += f'G00 Z{travel_z}\n'
  754. gcode += f'G00 X{self.click_points[1][0]} Y{self.click_points[1][1]}\n'
  755. gcode += f'G01 Z{verification_z}\n'
  756. gcode += 'M0\n'
  757. gcode += f'G00 Z{travel_z}\n'
  758. gcode += f'G00 X0 Y0\n'
  759. gcode += f'G00 Z{toolchange_z}\n'
  760. gcode += 'M2'
  761. self.gcode_editor_tab = TextEditor(app=self.app, plain_text=True)
  762. # add the tab if it was closed
  763. self.app.ui.plot_tab_area.addTab(self.gcode_editor_tab, '%s' % _("Gcode Viewer"))
  764. self.gcode_editor_tab.setObjectName('gcode_viewer_tab')
  765. # delete the absolute and relative position and messages in the infobar
  766. self.app.ui.position_label.setText("")
  767. self.app.ui.rel_position_label.setText("")
  768. # first clear previous text in text editor (if any)
  769. self.gcode_editor_tab.code_editor.clear()
  770. self.gcode_editor_tab.code_editor.setReadOnly(False)
  771. self.gcode_editor_tab.code_editor.completer_enable = False
  772. self.gcode_editor_tab.buttonRun.hide()
  773. # Switch plot_area to CNCJob tab
  774. self.app.ui.plot_tab_area.setCurrentWidget(self.gcode_editor_tab)
  775. self.gcode_editor_tab.t_frame.hide()
  776. # then append the text from GCode to the text editor
  777. try:
  778. self.gcode_editor_tab.code_editor.setPlainText(gcode)
  779. except Exception as e:
  780. self.app.inform.emit('[ERROR] %s %s' % ('ERROR -->', str(e)))
  781. return
  782. self.gcode_editor_tab.code_editor.moveCursor(QtGui.QTextCursor.Start)
  783. self.gcode_editor_tab.t_frame.show()
  784. self.app.proc_container.view.set_idle()
  785. self.app.inform.emit('[success] %s...' % _('Loaded Machine Code into Code Editor'))
  786. _filter_ = "G-Code Files (*.nc);;All Files (*.*)"
  787. self.gcode_editor_tab.buttonSave.clicked.disconnect()
  788. self.gcode_editor_tab.buttonSave.clicked.connect(
  789. lambda: self.gcode_editor_tab.handleSaveGCode(name='fc_ver_gcode', filt=_filter_, callback=self.close_tab))
  790. def calculate_factors(self):
  791. origin_x = self.click_points[0][0]
  792. origin_y = self.click_points[0][1]
  793. top_left_x = float('%.*f' % (self.decimals, self.click_points[2][0]))
  794. top_left_y = float('%.*f' % (self.decimals, self.click_points[2][1]))
  795. try:
  796. top_left_dx = float('%.*f' % (self.decimals, self.top_left_coordx_found.get_value()))
  797. except TypeError:
  798. top_left_dx = top_left_x
  799. try:
  800. top_left_dy = float('%.*f' % (self.decimals, self.top_left_coordy_found.get_value()))
  801. except TypeError:
  802. top_left_dy = top_left_y
  803. # top_right_x = float('%.*f' % (self.decimals, self.click_points[3][0]))
  804. # top_right_y = float('%.*f' % (self.decimals, self.click_points[3][1]))
  805. # try:
  806. # top_right_dx = float('%.*f' % (self.decimals, self.top_right_coordx_found.get_value()))
  807. # except TypeError:
  808. # top_right_dx = top_right_x
  809. #
  810. # try:
  811. # top_right_dy = float('%.*f' % (self.decimals, self.top_right_coordy_found.get_value()))
  812. # except TypeError:
  813. # top_right_dy = top_right_y
  814. bot_right_x = float('%.*f' % (self.decimals, self.click_points[1][0]))
  815. bot_right_y = float('%.*f' % (self.decimals, self.click_points[1][1]))
  816. try:
  817. bot_right_dx = float('%.*f' % (self.decimals, self.bottom_right_coordx_found.get_value()))
  818. except TypeError:
  819. bot_right_dx = bot_right_x
  820. try:
  821. bot_right_dy = float('%.*f' % (self.decimals, self.bottom_right_coordy_found.get_value()))
  822. except TypeError:
  823. bot_right_dy = bot_right_y
  824. # ------------------------------------------------------------------------------- #
  825. # --------------------------- FACTORS CALCULUS ---------------------------------- #
  826. # ------------------------------------------------------------------------------- #
  827. if top_left_dy != float('%.*f' % (self.decimals, 0.0)):
  828. # we have scale on Y
  829. scale_y = (top_left_dy + top_left_y - origin_y) / (top_left_y - origin_y)
  830. self.scaley_entry.set_value(scale_y)
  831. if top_left_dx != float('%.*f' % (self.decimals, 0.0)):
  832. # we have skew on X
  833. dx = top_left_dx
  834. dy = top_left_y - origin_y
  835. skew_angle_x = math.degrees(math.atan(dx / dy))
  836. self.skewx_entry.set_value(skew_angle_x)
  837. if bot_right_dx != float('%.*f' % (self.decimals, 0.0)):
  838. # we have scale on X
  839. scale_x = (bot_right_dx + bot_right_x - origin_x) / (bot_right_x - origin_x)
  840. self.scalex_entry.set_value(scale_x)
  841. if bot_right_dy != float('%.*f' % (self.decimals, 0.0)):
  842. # we have skew on Y
  843. dx = bot_right_x - origin_x
  844. dy = bot_right_dy + origin_y
  845. skew_angle_y = math.degrees(math.atan(dy / dx))
  846. self.skewy_entry.set_value(skew_angle_y)
  847. def on_cal_button_click(self):
  848. # get the FlatCAM object to calibrate
  849. selection_index = self.adj_object_combo.currentIndex()
  850. model_index = self.app.collection.index(selection_index, 0, self.adj_object_combo.rootModelIndex())
  851. try:
  852. self.cal_object = model_index.internalPointer().obj
  853. except Exception as e:
  854. log.debug("ToolCalibration.on_cal_button_click() --> %s" % str(e))
  855. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
  856. return 'fail'
  857. obj_name = self.cal_object.options["name"] + "_calibrated"
  858. self.app.worker_task.emit({'fcn': self.new_calibrated_object, 'params': [obj_name]})
  859. def new_calibrated_object(self, obj_name):
  860. try:
  861. origin_x = self.click_points[0][0]
  862. origin_y = self.click_points[0][1]
  863. except IndexError as e:
  864. log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
  865. return 'fail'
  866. scalex = self.scalex_entry.get_value()
  867. scaley = self.scaley_entry.get_value()
  868. skewx = self.skewx_entry.get_value()
  869. skewy = self.skewy_entry.get_value()
  870. # create a new object adjusted (calibrated)
  871. def initialize_geometry(obj_init, app):
  872. obj_init.solid_geometry = deepcopy(obj.solid_geometry)
  873. try:
  874. obj_init.follow_geometry = deepcopy(obj.follow_geometry)
  875. except AttributeError:
  876. pass
  877. try:
  878. obj_init.apertures = deepcopy(obj.apertures)
  879. except AttributeError:
  880. pass
  881. try:
  882. if obj.tools:
  883. obj_init.tools = deepcopy(obj.tools)
  884. except Exception as e:
  885. log.debug("App.on_copy_object() --> %s" % str(e))
  886. obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
  887. obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
  888. try:
  889. obj_init.source_file = deepcopy(obj.source_file)
  890. except (AttributeError, TypeError):
  891. pass
  892. def initialize_gerber(obj_init, app):
  893. obj_init.solid_geometry = deepcopy(obj.solid_geometry)
  894. try:
  895. obj_init.follow_geometry = deepcopy(obj.follow_geometry)
  896. except AttributeError:
  897. pass
  898. try:
  899. obj_init.apertures = deepcopy(obj.apertures)
  900. except AttributeError:
  901. pass
  902. try:
  903. if obj.tools:
  904. obj_init.tools = deepcopy(obj.tools)
  905. except Exception as e:
  906. log.debug("App.on_copy_object() --> %s" % str(e))
  907. obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
  908. obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
  909. try:
  910. obj_init.source_file = self.export_gerber(obj_name=obj_name, filename=None, local_use=obj_init,
  911. use_thread=False)
  912. except (AttributeError, TypeError):
  913. pass
  914. def initialize_excellon(obj_init, app):
  915. obj_init.tools = deepcopy(obj.tools)
  916. # drills are offset, so they need to be deep copied
  917. obj_init.drills = deepcopy(obj.drills)
  918. # slots are offset, so they need to be deep copied
  919. obj_init.slots = deepcopy(obj.slots)
  920. obj_init.scale(xfactor=scalex, yfactor=scaley, point=(origin_x, origin_y))
  921. obj_init.skew(angle_x=skewx, angle_y=skewy, point=(origin_x, origin_y))
  922. obj_init.create_geometry()
  923. obj_init.source_file = self.app.export_excellon(obj_name=obj_name, local_use=obj, filename=None,
  924. use_thread=False)
  925. obj = self.cal_object
  926. obj_name = obj_name
  927. if obj is None:
  928. self.app.inform.emit('[WARNING_NOTCL] %s' % _("There is no FlatCAM object selected..."))
  929. log.debug("ToolCalibration.new_calibrated_object() --> No object to calibrate")
  930. return 'fail'
  931. try:
  932. if obj.kind.lower() == 'excellon':
  933. self.app.new_object("excellon", str(obj_name), initialize_excellon)
  934. elif obj.kind.lower() == 'gerber':
  935. self.app.new_object("gerber", str(obj_name), initialize_gerber)
  936. elif obj.kind.lower() == 'geometry':
  937. self.app.new_object("geometry", str(obj_name), initialize_geometry)
  938. except Exception as e:
  939. log.debug("ToolCalibration.new_calibrated_object() --> %s" % str(e))
  940. return "Operation failed: %s" % str(e)
  941. def disconnect_cal_events(self):
  942. # restore the Grid snapping if it was active before
  943. if self.grid_status_memory is True:
  944. self.app.ui.grid_snap_btn.trigger()
  945. self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
  946. if self.app.is_legacy is False:
  947. self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
  948. else:
  949. self.canvas.graph_event_disconnect(self.mr)
  950. self.local_connected = False
  951. def reset_fields(self):
  952. self.object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  953. self.adj_exc_object_combo.setRootModelIndex(self.app.collection.index(1, 0, QtCore.QModelIndex()))
  954. self.adj_geo_object_combo.setRootModelIndex(self.app.collection.index(2, 0, QtCore.QModelIndex()))
  955. # end of file