ToolCalibration.py 58 KB

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