ToolDistance.py 27 KB

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