ToolCalibration.py 60 KB

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