ToolDistance.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  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
  8. from appTool import AppTool
  9. from appGUI.VisPyVisuals import *
  10. from appGUI.GUIElements import FCEntry, FCButton, FCCheckBox, FCLabel
  11. from shapely.geometry import Point, MultiLineString, Polygon
  12. import appTranslation as fcTranslate
  13. from camlib import FlatCAMRTreeStorage
  14. from appEditors.AppGeoEditor import DrawToolShape
  15. from copy import copy
  16. import math
  17. import logging
  18. import gettext
  19. import builtins
  20. fcTranslate.apply_language('strings')
  21. if '_' not in builtins.__dict__:
  22. _ = gettext.gettext
  23. log = logging.getLogger('base')
  24. class Distance(AppTool):
  25. def __init__(self, app):
  26. AppTool.__init__(self, app)
  27. self.app = app
  28. self.decimals = self.app.decimals
  29. self.canvas = self.app.plotcanvas
  30. self.units = self.app.defaults['units'].lower()
  31. # #############################################################################
  32. # ######################### Tool GUI ##########################################
  33. # #############################################################################
  34. self.ui = DistUI(layout=self.layout, app=self.app)
  35. self.toolName = self.ui.toolName
  36. # store here the first click and second click of the measurement process
  37. self.points = []
  38. self.rel_point1 = None
  39. self.rel_point2 = None
  40. self.active = False
  41. self.clicked_meas = None
  42. self.meas_line = None
  43. self.original_call_source = 'app'
  44. # store here the event connection ID's
  45. self.mm = None
  46. self.mr = None
  47. # monitor if the tool was used
  48. self.tool_done = False
  49. # store the grid status here
  50. self.grid_status_memory = False
  51. # store here if the snap button was clicked
  52. self.snap_toggled = None
  53. self.mouse_is_dragging = False
  54. # VisPy visuals
  55. if self.app.is_legacy is False:
  56. self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
  57. else:
  58. from appGUI.PlotCanvasLegacy import ShapeCollectionLegacy
  59. self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name='measurement')
  60. # Signals
  61. self.ui.measure_btn.clicked.connect(self.activate_measure_tool)
  62. def run(self, toggle=False):
  63. self.app.defaults.report_usage("ToolDistance()")
  64. self.points[:] = []
  65. self.rel_point1 = None
  66. self.rel_point2 = None
  67. self.tool_done = False
  68. if self.app.tool_tab_locked is True:
  69. return
  70. self.app.ui.notebook.setTabText(2, _("Distance Tool"))
  71. # if the splitter is hidden, display it
  72. if self.app.ui.splitter.sizes()[0] == 0:
  73. self.app.ui.splitter.setSizes([1, 1])
  74. if toggle:
  75. pass
  76. if self.active is False:
  77. self.activate_measure_tool()
  78. else:
  79. self.deactivate_measure_tool()
  80. def install(self, icon=None, separator=None, **kwargs):
  81. AppTool.install(self, icon, separator, shortcut='Ctrl+M', **kwargs)
  82. def set_tool_ui(self):
  83. # Remove anything else in the appGUI
  84. self.app.ui.tool_scroll_area.takeWidget()
  85. # Put ourselves in the appGUI
  86. self.app.ui.tool_scroll_area.setWidget(self)
  87. # Switch notebook to tool page
  88. self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
  89. self.units = self.app.defaults['units'].lower()
  90. self.app.command_active = "Distance"
  91. # initial view of the layout
  92. self.ui.start_entry.set_value('(0, 0)')
  93. self.ui.stop_entry.set_value('(0, 0)')
  94. self.ui.distance_x_entry.set_value('0.0')
  95. self.ui.distance_y_entry.set_value('0.0')
  96. self.ui.angle_entry.set_value('0.0')
  97. self.ui.total_distance_entry.set_value('0.0')
  98. self.ui.snap_center_cb.set_value(self.app.defaults['tools_dist_snap_center'])
  99. # snap center works only for Gerber and Execellon Editor's
  100. if self.original_call_source == 'exc_editor' or self.original_call_source == 'grb_editor':
  101. self.ui.snap_center_cb.show()
  102. snap_center = self.app.defaults['tools_dist_snap_center']
  103. self.on_snap_toggled(snap_center)
  104. self.ui.snap_center_cb.toggled.connect(self.on_snap_toggled)
  105. else:
  106. self.ui.snap_center_cb.hide()
  107. try:
  108. self.ui.snap_center_cb.toggled.disconnect(self.on_snap_toggled)
  109. except (TypeError, AttributeError):
  110. pass
  111. # this is a hack; seems that triggering the grid will make the visuals better
  112. # trigger it twice to return to the original state
  113. self.app.ui.grid_snap_btn.trigger()
  114. self.app.ui.grid_snap_btn.trigger()
  115. if self.app.ui.grid_snap_btn.isChecked():
  116. self.grid_status_memory = True
  117. log.debug("Distance Tool --> tool initialized")
  118. def on_snap_toggled(self, state):
  119. self.app.defaults['tools_dist_snap_center'] = state
  120. if state:
  121. # disengage the grid snapping since it will be hard to find the drills or pads on grid
  122. if self.app.ui.grid_snap_btn.isChecked():
  123. self.app.ui.grid_snap_btn.trigger()
  124. def activate_measure_tool(self):
  125. # ENABLE the Measuring TOOL
  126. self.active = True
  127. # disable the measuring button
  128. self.ui.measure_btn.setDisabled(True)
  129. self.ui.measure_btn.setText('%s...' % _("Working"))
  130. self.clicked_meas = 0
  131. self.original_call_source = copy(self.app.call_source)
  132. self.app.inform.emit(_("MEASURING: Click on the Start point ..."))
  133. self.units = self.app.defaults['units'].lower()
  134. # we can connect the app mouse events to the measurement tool
  135. # NEVER DISCONNECT THOSE before connecting some other handlers; it breaks something in VisPy
  136. self.mm = self.canvas.graph_event_connect('mouse_move', self.on_mouse_move_meas)
  137. self.mr = self.canvas.graph_event_connect('mouse_release', self.on_mouse_click_release)
  138. # we disconnect the mouse/key handlers from wherever the measurement tool was called
  139. if self.app.call_source == 'app':
  140. if self.app.is_legacy is False:
  141. self.canvas.graph_event_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
  142. self.canvas.graph_event_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
  143. self.canvas.graph_event_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
  144. else:
  145. self.canvas.graph_event_disconnect(self.app.mm)
  146. self.canvas.graph_event_disconnect(self.app.mp)
  147. self.canvas.graph_event_disconnect(self.app.mr)
  148. elif self.app.call_source == 'geo_editor':
  149. if self.app.is_legacy is False:
  150. self.canvas.graph_event_disconnect('mouse_move', self.app.geo_editor.on_canvas_move)
  151. self.canvas.graph_event_disconnect('mouse_press', self.app.geo_editor.on_canvas_click)
  152. self.canvas.graph_event_disconnect('mouse_release', self.app.geo_editor.on_geo_click_release)
  153. else:
  154. self.canvas.graph_event_disconnect(self.app.geo_editor.mm)
  155. self.canvas.graph_event_disconnect(self.app.geo_editor.mp)
  156. self.canvas.graph_event_disconnect(self.app.geo_editor.mr)
  157. elif self.app.call_source == 'exc_editor':
  158. if self.app.is_legacy is False:
  159. self.canvas.graph_event_disconnect('mouse_move', self.app.exc_editor.on_canvas_move)
  160. self.canvas.graph_event_disconnect('mouse_press', self.app.exc_editor.on_canvas_click)
  161. self.canvas.graph_event_disconnect('mouse_release', self.app.exc_editor.on_exc_click_release)
  162. else:
  163. self.canvas.graph_event_disconnect(self.app.exc_editor.mm)
  164. self.canvas.graph_event_disconnect(self.app.exc_editor.mp)
  165. self.canvas.graph_event_disconnect(self.app.exc_editor.mr)
  166. elif self.app.call_source == 'grb_editor':
  167. if self.app.is_legacy is False:
  168. self.canvas.graph_event_disconnect('mouse_move', self.app.grb_editor.on_canvas_move)
  169. self.canvas.graph_event_disconnect('mouse_press', self.app.grb_editor.on_canvas_click)
  170. self.canvas.graph_event_disconnect('mouse_release', self.app.grb_editor.on_grb_click_release)
  171. else:
  172. self.canvas.graph_event_disconnect(self.app.grb_editor.mm)
  173. self.canvas.graph_event_disconnect(self.app.grb_editor.mp)
  174. self.canvas.graph_event_disconnect(self.app.grb_editor.mr)
  175. self.app.call_source = 'measurement'
  176. self.set_tool_ui()
  177. def deactivate_measure_tool(self):
  178. # DISABLE the Measuring TOOL
  179. self.active = False
  180. self.points = []
  181. # disable the measuring button
  182. self.ui.measure_btn.setDisabled(False)
  183. self.ui.measure_btn.setText(_("Measure"))
  184. self.app.call_source = copy(self.original_call_source)
  185. if self.original_call_source == 'app':
  186. self.app.mm = self.canvas.graph_event_connect('mouse_move', self.app.on_mouse_move_over_plot)
  187. self.app.mp = self.canvas.graph_event_connect('mouse_press', self.app.on_mouse_click_over_plot)
  188. self.app.mr = self.canvas.graph_event_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
  189. elif self.original_call_source == 'geo_editor':
  190. self.app.geo_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.geo_editor.on_canvas_move)
  191. self.app.geo_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.geo_editor.on_canvas_click)
  192. self.app.geo_editor.mr = self.canvas.graph_event_connect('mouse_release',
  193. self.app.geo_editor.on_geo_click_release)
  194. elif self.original_call_source == 'exc_editor':
  195. self.app.exc_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.exc_editor.on_canvas_move)
  196. self.app.exc_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.exc_editor.on_canvas_click)
  197. self.app.exc_editor.mr = self.canvas.graph_event_connect('mouse_release',
  198. self.app.exc_editor.on_exc_click_release)
  199. elif self.original_call_source == 'grb_editor':
  200. self.app.grb_editor.mm = self.canvas.graph_event_connect('mouse_move', self.app.grb_editor.on_canvas_move)
  201. self.app.grb_editor.mp = self.canvas.graph_event_connect('mouse_press', self.app.grb_editor.on_canvas_click)
  202. self.app.grb_editor.mr = self.canvas.graph_event_connect('mouse_release',
  203. self.app.grb_editor.on_grb_click_release)
  204. # disconnect the mouse/key events from functions of measurement tool
  205. if self.app.is_legacy is False:
  206. self.canvas.graph_event_disconnect('mouse_move', self.on_mouse_move_meas)
  207. self.canvas.graph_event_disconnect('mouse_release', self.on_mouse_click_release)
  208. else:
  209. self.canvas.graph_event_disconnect(self.mm)
  210. self.canvas.graph_event_disconnect(self.mr)
  211. # self.app.ui.notebook.setTabText(2, _("Tools"))
  212. # self.app.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
  213. self.app.command_active = None
  214. # delete the measuring line
  215. self.delete_shape()
  216. # restore the grid status
  217. if (self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is False) or \
  218. (not self.app.ui.grid_snap_btn.isChecked() and self.grid_status_memory is True):
  219. self.app.ui.grid_snap_btn.trigger()
  220. log.debug("Distance Tool --> exit tool")
  221. if self.tool_done is False:
  222. self.app.inform.emit('%s' % _("Distance Tool finished."))
  223. def on_mouse_click_release(self, event):
  224. # mouse click releases will be accepted only if the left button is clicked
  225. # this is necessary because right mouse click or middle mouse click
  226. # are used for panning on the canvas
  227. log.debug("Distance Tool --> mouse click release")
  228. if self.app.is_legacy is False:
  229. event_pos = event.pos
  230. right_button = 2
  231. event_is_dragging = self.mouse_is_dragging
  232. else:
  233. event_pos = (event.xdata, event.ydata)
  234. right_button = 3
  235. event_is_dragging = self.app.plotcanvas.is_dragging
  236. if event.button == 1:
  237. pos_canvas = self.canvas.translate_coords(event_pos)
  238. if self.ui.snap_center_cb.get_value() is False:
  239. # if GRID is active we need to get the snapped positions
  240. if self.app.grid_status():
  241. pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
  242. else:
  243. pos = pos_canvas[0], pos_canvas[1]
  244. else:
  245. pos = (pos_canvas[0], pos_canvas[1])
  246. current_pt = Point(pos)
  247. shapes_storage = self.make_storage()
  248. if self.original_call_source == 'exc_editor':
  249. for storage in self.app.exc_editor.storage_dict:
  250. __, st_closest_shape = self.app.exc_editor.storage_dict[storage].nearest(pos)
  251. shapes_storage.insert(st_closest_shape)
  252. __, closest_shape = shapes_storage.nearest(pos)
  253. # if it's a drill
  254. if isinstance(closest_shape.geo, MultiLineString):
  255. radius = closest_shape.geo[0].length / 2.0
  256. center_pt = closest_shape.geo.centroid
  257. geo_buffered = center_pt.buffer(radius)
  258. if current_pt.within(geo_buffered):
  259. pos = (center_pt.x, center_pt.y)
  260. # if it's a slot
  261. elif isinstance(closest_shape.geo, Polygon):
  262. geo_buffered = closest_shape.geo.buffer(0)
  263. center_pt = geo_buffered.centroid
  264. if current_pt.within(geo_buffered):
  265. pos = (center_pt.x, center_pt.y)
  266. elif self.original_call_source == 'grb_editor':
  267. clicked_pads = []
  268. for storage in self.app.grb_editor.storage_dict:
  269. try:
  270. for shape_stored in self.app.grb_editor.storage_dict[storage]['geometry']:
  271. if 'solid' in shape_stored.geo:
  272. geometric_data = shape_stored.geo['solid']
  273. if Point(current_pt).within(geometric_data):
  274. if isinstance(shape_stored.geo['follow'], Point):
  275. clicked_pads.append(shape_stored.geo['follow'])
  276. except KeyError:
  277. pass
  278. if len(clicked_pads) > 1:
  279. self.tool_done = True
  280. self.deactivate_measure_tool()
  281. self.app.inform.emit('[WARNING_NOTCL] %s' % _("Pads overlapped. Aborting."))
  282. return
  283. if clicked_pads:
  284. pos = (clicked_pads[0].x, clicked_pads[0].y)
  285. self.app.on_jump_to(custom_location=pos, fit_center=False)
  286. # Update cursor
  287. self.app.app_cursor.enabled = True
  288. self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
  289. symbol='++', edge_color='#000000',
  290. edge_width=self.app.defaults["global_cursor_width"],
  291. size=self.app.defaults["global_cursor_size"])
  292. self.points.append(pos)
  293. # Reset here the relative coordinates so there is a new reference on the click position
  294. if self.rel_point1 is None:
  295. self.app.ui.rel_position_label.setText("<b>Dx</b>: %.*f&nbsp;&nbsp; <b>Dy</b>: "
  296. "%.*f&nbsp;&nbsp;&nbsp;&nbsp;" %
  297. (self.decimals, 0.0, self.decimals, 0.0))
  298. self.rel_point1 = pos
  299. else:
  300. self.rel_point2 = copy(self.rel_point1)
  301. self.rel_point1 = pos
  302. self.calculate_distance(pos=pos)
  303. elif event.button == right_button and event_is_dragging is False:
  304. self.deactivate_measure_tool()
  305. self.app.inform.emit(_("Distance Tool cancelled."))
  306. def calculate_distance(self, pos):
  307. if len(self.points) == 1:
  308. self.ui.start_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
  309. self.app.inform.emit(_("Click on the DESTINATION point ..."))
  310. elif len(self.points) == 2:
  311. # self.app.app_cursor.enabled = False
  312. dx = self.points[1][0] - self.points[0][0]
  313. dy = self.points[1][1] - self.points[0][1]
  314. d = math.sqrt(dx ** 2 + dy ** 2)
  315. self.ui.stop_entry.set_value("(%.*f, %.*f)" % (self.decimals, pos[0], self.decimals, pos[1]))
  316. self.app.inform.emit("{tx1}: {tx2} D(x) = {d_x} | D(y) = {d_y} | {tx3} = {d_z}".format(
  317. tx1=_("MEASURING"),
  318. tx2=_("Result"),
  319. tx3=_("Distance"),
  320. d_x='%*f' % (self.decimals, abs(dx)),
  321. d_y='%*f' % (self.decimals, abs(dy)),
  322. d_z='%*f' % (self.decimals, abs(d)))
  323. )
  324. self.ui.distance_x_entry.set_value('%.*f' % (self.decimals, abs(dx)))
  325. self.ui.distance_y_entry.set_value('%.*f' % (self.decimals, abs(dy)))
  326. try:
  327. angle = math.degrees(math.atan2(dy, dx))
  328. if angle < 0:
  329. angle += 360
  330. self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle))
  331. except Exception:
  332. pass
  333. self.ui.total_distance_entry.set_value('%.*f' % (self.decimals, abs(d)))
  334. self.app.ui.rel_position_label.setText(
  335. "<b>Dx</b>: {}&nbsp;&nbsp; <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
  336. '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
  337. )
  338. )
  339. self.tool_done = True
  340. self.deactivate_measure_tool()
  341. def on_mouse_move_meas(self, event):
  342. try: # May fail in case mouse not within axes
  343. if self.app.is_legacy is False:
  344. event_pos = event.pos
  345. self.mouse_is_dragging = event.is_dragging
  346. else:
  347. event_pos = (event.xdata, event.ydata)
  348. try:
  349. x = float(event_pos[0])
  350. y = float(event_pos[1])
  351. except TypeError:
  352. return
  353. pos_canvas = self.app.plotcanvas.translate_coords((x, y))
  354. if self.app.grid_status():
  355. pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
  356. # Update cursor
  357. self.app.app_cursor.set_data(np.asarray([(pos[0], pos[1])]),
  358. symbol='++', edge_color=self.app.cursor_color_3D,
  359. edge_width=self.app.defaults["global_cursor_width"],
  360. size=self.app.defaults["global_cursor_size"])
  361. else:
  362. pos = (pos_canvas[0], pos_canvas[1])
  363. self.app.ui.position_label.setText(
  364. "&nbsp;&nbsp;&nbsp;&nbsp;<b>X</b>: {}&nbsp;&nbsp; <b>Y</b>: {}".format(
  365. '%.*f' % (self.decimals, pos[0]), '%.*f' % (self.decimals, pos[1])
  366. )
  367. )
  368. units = self.app.defaults["units"].lower()
  369. self.app.plotcanvas.text_hud.text = \
  370. 'Dx:\t{:<.4f} [{:s}]\nDy:\t{:<.4f} [{:s}]\n\nX: \t{:<.4f} [{:s}]\nY: \t{:<.4f} [{:s}]'.format(
  371. 0.0000, units, 0.0000, units, pos[0], units, pos[1], units)
  372. if self.rel_point1 is not None:
  373. dx = pos[0] - float(self.rel_point1[0])
  374. dy = pos[1] - float(self.rel_point1[1])
  375. else:
  376. dx = pos[0]
  377. dy = pos[1]
  378. self.app.ui.rel_position_label.setText(
  379. "<b>Dx</b>: {}&nbsp;&nbsp; <b>Dy</b>: {}&nbsp;&nbsp;&nbsp;&nbsp;".format(
  380. '%.*f' % (self.decimals, dx), '%.*f' % (self.decimals, dy)
  381. )
  382. )
  383. # update utility geometry
  384. if len(self.points) == 1:
  385. self.utility_geometry(pos=pos)
  386. # and display the temporary angle
  387. try:
  388. angle = math.degrees(math.atan2(dy, dx))
  389. if angle < 0:
  390. angle += 360
  391. self.ui.angle_entry.set_value('%.*f' % (self.decimals, angle))
  392. except Exception as e:
  393. log.debug("Distance.on_mouse_move_meas() -> update utility geometry -> %s" % str(e))
  394. pass
  395. except Exception as e:
  396. log.debug("Distance.on_mouse_move_meas() --> %s" % str(e))
  397. self.app.ui.position_label.setText("")
  398. self.app.ui.rel_position_label.setText("")
  399. def utility_geometry(self, pos):
  400. # first delete old shape
  401. self.delete_shape()
  402. # second draw the new shape of the utility geometry
  403. meas_line = LineString([pos, self.points[0]])
  404. settings = QtCore.QSettings("Open Source", "FlatCAM")
  405. if settings.contains("theme"):
  406. theme = settings.value('theme', type=str)
  407. else:
  408. theme = 'white'
  409. if theme == 'white':
  410. color = '#000000FF'
  411. else:
  412. color = '#FFFFFFFF'
  413. self.sel_shapes.add(meas_line, color=color, update=True, layer=0, tolerance=None, linewidth=2)
  414. if self.app.is_legacy is True:
  415. self.sel_shapes.redraw()
  416. def delete_shape(self):
  417. self.sel_shapes.clear()
  418. self.sel_shapes.redraw()
  419. @staticmethod
  420. def make_storage():
  421. # ## Shape storage.
  422. storage = FlatCAMRTreeStorage()
  423. storage.get_points = DrawToolShape.get_pts
  424. return storage
  425. # def set_meas_units(self, units):
  426. # self.meas.units_label.setText("[" + self.app.options["units"].lower() + "]")
  427. class DistUI:
  428. toolName = _("Distance Tool")
  429. def __init__(self, layout, app):
  430. self.app = app
  431. self.decimals = self.app.decimals
  432. self.layout = layout
  433. self.units = self.app.defaults['units'].lower()
  434. # ## Title
  435. title_label = FCLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
  436. self.layout.addWidget(title_label)
  437. # ## Form Layout
  438. grid0 = QtWidgets.QGridLayout()
  439. grid0.setColumnStretch(0, 0)
  440. grid0.setColumnStretch(1, 1)
  441. self.layout.addLayout(grid0)
  442. self.units_label = FCLabel('%s:' % _("Units"))
  443. self.units_label.setToolTip(_("Those are the units in which the distance is measured."))
  444. self.units_value = FCLabel("%s" % str({'mm': _("METRIC (mm)"), 'in': _("INCH (in)")}[self.units]))
  445. self.units_value.setDisabled(True)
  446. grid0.addWidget(self.units_label, 0, 0)
  447. grid0.addWidget(self.units_value, 0, 1)
  448. self.snap_center_cb = FCCheckBox(_("Snap to center"))
  449. self.snap_center_cb.setToolTip(
  450. _("Mouse cursor will snap to the center of the pad/drill\n"
  451. "when it is hovering over the geometry of the pad/drill.")
  452. )
  453. grid0.addWidget(self.snap_center_cb, 1, 0, 1, 2)
  454. separator_line = QtWidgets.QFrame()
  455. separator_line.setFrameShape(QtWidgets.QFrame.HLine)
  456. separator_line.setFrameShadow(QtWidgets.QFrame.Sunken)
  457. grid0.addWidget(separator_line, 2, 0, 1, 2)
  458. self.start_label = FCLabel("%s:" % _('Start Coords'))
  459. self.start_label.setToolTip(_("This is measuring Start point coordinates."))
  460. self.start_entry = FCEntry()
  461. self.start_entry.setReadOnly(True)
  462. self.start_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  463. self.start_entry.setToolTip(_("This is measuring Start point coordinates."))
  464. grid0.addWidget(self.start_label, 3, 0)
  465. grid0.addWidget(self.start_entry, 3, 1)
  466. self.stop_label = FCLabel("%s:" % _('Stop Coords'))
  467. self.stop_label.setToolTip(_("This is the measuring Stop point coordinates."))
  468. self.stop_entry = FCEntry()
  469. self.stop_entry.setReadOnly(True)
  470. self.stop_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  471. self.stop_entry.setToolTip(_("This is the measuring Stop point coordinates."))
  472. grid0.addWidget(self.stop_label, 4, 0)
  473. grid0.addWidget(self.stop_entry, 4, 1)
  474. self.distance_x_label = FCLabel('%s:' % _("Dx"))
  475. self.distance_x_label.setToolTip(_("This is the distance measured over the X axis."))
  476. self.distance_x_entry = FCEntry()
  477. self.distance_x_entry.setReadOnly(True)
  478. self.distance_x_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  479. self.distance_x_entry.setToolTip(_("This is the distance measured over the X axis."))
  480. grid0.addWidget(self.distance_x_label, 5, 0)
  481. grid0.addWidget(self.distance_x_entry, 5, 1)
  482. self.distance_y_label = FCLabel('%s:' % _("Dy"))
  483. self.distance_y_label.setToolTip(_("This is the distance measured over the Y axis."))
  484. self.distance_y_entry = FCEntry()
  485. self.distance_y_entry.setReadOnly(True)
  486. self.distance_y_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  487. self.distance_y_entry.setToolTip(_("This is the distance measured over the Y axis."))
  488. grid0.addWidget(self.distance_y_label, 6, 0)
  489. grid0.addWidget(self.distance_y_entry, 6, 1)
  490. self.angle_label = FCLabel('%s:' % _("Angle"))
  491. self.angle_label.setToolTip(_("This is orientation angle of the measuring line."))
  492. self.angle_entry = FCEntry()
  493. self.angle_entry.setReadOnly(True)
  494. self.angle_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  495. self.angle_entry.setToolTip(_("This is orientation angle of the measuring line."))
  496. grid0.addWidget(self.angle_label, 7, 0)
  497. grid0.addWidget(self.angle_entry, 7, 1)
  498. self.total_distance_label = FCLabel("<b>%s:</b>" % _('DISTANCE'))
  499. self.total_distance_label.setToolTip(_("This is the point to point Euclidian distance."))
  500. self.total_distance_entry = FCEntry()
  501. self.total_distance_entry.setReadOnly(True)
  502. self.total_distance_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  503. self.total_distance_entry.setToolTip(_("This is the point to point Euclidian distance."))
  504. grid0.addWidget(self.total_distance_label, 8, 0)
  505. grid0.addWidget(self.total_distance_entry, 8, 1)
  506. self.measure_btn = FCButton(_("Measure"))
  507. # self.measure_btn.setFixedWidth(70)
  508. self.layout.addWidget(self.measure_btn)
  509. self.layout.addStretch()
  510. # #################################### FINSIHED GUI ###########################
  511. # #############################################################################
  512. def confirmation_message(self, accepted, minval, maxval):
  513. if accepted is False:
  514. self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%.*f, %.*f]' % (_("Edited value is out of range"),
  515. self.decimals,
  516. minval,
  517. self.decimals,
  518. maxval), False)
  519. else:
  520. self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)
  521. def confirmation_message_int(self, accepted, minval, maxval):
  522. if accepted is False:
  523. self.app.inform[str, bool].emit('[WARNING_NOTCL] %s: [%d, %d]' %
  524. (_("Edited value is out of range"), minval, maxval), False)
  525. else:
  526. self.app.inform[str, bool].emit('[success] %s' % _("Edited value is within limits."), False)