PlotCanvasLegacy.py 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351
  1. ############################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://caram.cl/software/flatcam #
  4. # Author: Juan Pablo Caram (c) #
  5. # Date: 2/5/2014 #
  6. # MIT Licence #
  7. # Modified by Marius Stanciu 09/21/2019 #
  8. ############################################################
  9. from PyQt5 import QtCore
  10. from PyQt5.QtCore import pyqtSignal
  11. # needed for legacy mode
  12. # Used for solid polygons in Matplotlib
  13. from descartes.patch import PolygonPatch
  14. from shapely.geometry import Polygon, LineString, LinearRing, Point, MultiPolygon, MultiLineString
  15. import FlatCAMApp
  16. from copy import deepcopy
  17. import logging
  18. import gettext
  19. import FlatCAMTranslation as fcTranslate
  20. import builtins
  21. # Prevent conflict with Qt5 and above.
  22. from matplotlib import use as mpl_use
  23. mpl_use("Qt5Agg")
  24. from matplotlib.figure import Figure
  25. from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
  26. from matplotlib.lines import Line2D
  27. # from matplotlib.widgets import Cursor
  28. fcTranslate.apply_language('strings')
  29. if '_' not in builtins.__dict__:
  30. _ = gettext.gettext
  31. log = logging.getLogger('base')
  32. class CanvasCache(QtCore.QObject):
  33. """
  34. Case story #1:
  35. 1) No objects in the project.
  36. 2) Object is created (new_object() emits object_created(obj)).
  37. on_object_created() adds (i) object to collection and emits
  38. (ii) new_object_available() then calls (iii) object.plot()
  39. 3) object.plot() creates axes if necessary on
  40. app.collection.figure. Then plots on it.
  41. 4) Plots on a cache-size canvas (in background).
  42. 5) Plot completes. Bitmap is generated.
  43. 6) Visible canvas is painted.
  44. """
  45. # Signals:
  46. # A bitmap is ready to be displayed.
  47. new_screen = QtCore.pyqtSignal()
  48. def __init__(self, plotcanvas, app, dpi=50):
  49. super(CanvasCache, self).__init__()
  50. self.app = app
  51. self.plotcanvas = plotcanvas
  52. self.dpi = dpi
  53. self.figure = Figure(dpi=dpi)
  54. self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
  55. self.axes.set_frame_on(False)
  56. self.axes.set_xticks([])
  57. self.axes.set_yticks([])
  58. if self.app.defaults['global_theme'] == 'white':
  59. self.axes.set_facecolor('#FFFFFF')
  60. else:
  61. self.axes.set_facecolor('#000000')
  62. self.canvas = FigureCanvas(self.figure)
  63. self.cache = None
  64. def run(self):
  65. log.debug("CanvasCache Thread Started!")
  66. self.plotcanvas.update_screen_request.connect(self.on_update_req)
  67. def on_update_req(self, extents):
  68. """
  69. Event handler for an updated display request.
  70. :param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
  71. """
  72. # log.debug("Canvas update requested: %s" % str(extents))
  73. # Note: This information below might be out of date. Establish
  74. # a protocol regarding when to change the canvas in the main
  75. # thread and when to check these values here in the background,
  76. # or pass this data in the signal (safer).
  77. # log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
  78. # log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
  79. # Move the requested screen portion to the main thread
  80. # and inform about the update:
  81. self.new_screen.emit()
  82. # Continue to update the cache.
  83. # def on_new_object_available(self):
  84. #
  85. # log.debug("A new object is available. Should plot it!")
  86. class PlotCanvasLegacy(QtCore.QObject):
  87. """
  88. Class handling the plotting area in the application.
  89. """
  90. # Signals:
  91. # Request for new bitmap to display. The parameter
  92. # is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
  93. update_screen_request = QtCore.pyqtSignal(list)
  94. double_click = QtCore.pyqtSignal(object)
  95. def __init__(self, container, app):
  96. """
  97. The constructor configures the Matplotlib figure that
  98. will contain all plots, creates the base axes and connects
  99. events to the plotting area.
  100. :param container: The parent container in which to draw plots.
  101. :rtype: PlotCanvas
  102. """
  103. super(PlotCanvasLegacy, self).__init__()
  104. self.app = app
  105. if self.app.defaults['global_theme'] == 'white':
  106. theme_color = '#FFFFFF'
  107. tick_color = '#000000'
  108. else:
  109. theme_color = '#000000'
  110. tick_color = '#FFFFFF'
  111. # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
  112. # which might decrease performance
  113. # self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
  114. self.workspace_line = None
  115. self.pagesize_dict = dict()
  116. self.pagesize_dict.update(
  117. {
  118. 'A0': (841, 1189),
  119. 'A1': (594, 841),
  120. 'A2': (420, 594),
  121. 'A3': (297, 420),
  122. 'A4': (210, 297),
  123. 'A5': (148, 210),
  124. 'A6': (105, 148),
  125. 'A7': (74, 105),
  126. 'A8': (52, 74),
  127. 'A9': (37, 52),
  128. 'A10': (26, 37),
  129. 'B0': (1000, 1414),
  130. 'B1': (707, 1000),
  131. 'B2': (500, 707),
  132. 'B3': (353, 500),
  133. 'B4': (250, 353),
  134. 'B5': (176, 250),
  135. 'B6': (125, 176),
  136. 'B7': (88, 125),
  137. 'B8': (62, 88),
  138. 'B9': (44, 62),
  139. 'B10': (31, 44),
  140. 'C0': (917, 1297),
  141. 'C1': (648, 917),
  142. 'C2': (458, 648),
  143. 'C3': (324, 458),
  144. 'C4': (229, 324),
  145. 'C5': (162, 229),
  146. 'C6': (114, 162),
  147. 'C7': (81, 114),
  148. 'C8': (57, 81),
  149. 'C9': (40, 57),
  150. 'C10': (28, 40),
  151. # American paper sizes
  152. 'LETTER': (8.5*25.4, 11*25.4),
  153. 'LEGAL': (8.5*25.4, 14*25.4),
  154. 'ELEVENSEVENTEEN': (11*25.4, 17*25.4),
  155. # From https://en.wikipedia.org/wiki/Paper_size
  156. 'JUNIOR_LEGAL': (5*25.4, 8*25.4),
  157. 'HALF_LETTER': (5.5*25.4, 8*25.4),
  158. 'GOV_LETTER': (8*25.4, 10.5*25.4),
  159. 'GOV_LEGAL': (8.5*25.4, 13*25.4),
  160. 'LEDGER': (17*25.4, 11*25.4),
  161. }
  162. )
  163. # Options
  164. self.x_margin = 15 # pixels
  165. self.y_margin = 25 # Pixels
  166. # Parent container
  167. self.container = container
  168. # Plots go onto a single matplotlib.figure
  169. self.figure = Figure(dpi=50) # TODO: dpi needed?
  170. self.figure.patch.set_visible(True)
  171. self.figure.set_facecolor(theme_color)
  172. # These axes show the ticks and grid. No plotting done here.
  173. # New axes must have a label, otherwise mpl returns an existing one.
  174. self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
  175. self.axes.set_aspect(1)
  176. self.axes.grid(True, color='gray')
  177. self.h_line = self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
  178. self.v_line = self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
  179. self.axes.tick_params(axis='x', color=tick_color, labelcolor=tick_color)
  180. self.axes.tick_params(axis='y', color=tick_color, labelcolor=tick_color)
  181. self.axes.spines['bottom'].set_color(tick_color)
  182. self.axes.spines['top'].set_color(tick_color)
  183. self.axes.spines['right'].set_color(tick_color)
  184. self.axes.spines['left'].set_color(tick_color)
  185. self.axes.set_facecolor(theme_color)
  186. self.ch_line = None
  187. self.cv_line = None
  188. # The canvas is the top level container (FigureCanvasQTAgg)
  189. self.canvas = FigureCanvas(self.figure)
  190. self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
  191. self.canvas.setFocus()
  192. self.native = self.canvas
  193. self.adjust_axes(-10, -10, 100, 100)
  194. # self.canvas.set_can_focus(True) # For key press
  195. # Attach to parent
  196. # self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns??
  197. self.container.addWidget(self.canvas) # Qt
  198. # Copy a bitmap of the canvas for quick animation.
  199. # Update every time the canvas is re-drawn.
  200. self.background = self.canvas.copy_from_bbox(self.axes.bbox)
  201. # ################### NOT IMPLEMENTED YET - EXPERIMENTAL #######################
  202. # ## Bitmap Cache
  203. # self.cache = CanvasCache(self, self.app)
  204. # self.cache_thread = QtCore.QThread()
  205. # self.cache.moveToThread(self.cache_thread)
  206. # # super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
  207. # self.cache_thread.started.connect(self.cache.run)
  208. #
  209. # self.cache_thread.start()
  210. # self.cache.new_screen.connect(self.on_new_screen)
  211. # ##############################################################################
  212. # Events
  213. self.mp = self.graph_event_connect('button_press_event', self.on_mouse_press)
  214. self.mr = self.graph_event_connect('button_release_event', self.on_mouse_release)
  215. self.mm = self.graph_event_connect('motion_notify_event', self.on_mouse_move)
  216. # self.canvas.connect('configure-event', self.auto_adjust_axes)
  217. self.aaa = self.graph_event_connect('resize_event', self.auto_adjust_axes)
  218. # self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
  219. # self.canvas.connect("scroll-event", self.on_scroll)
  220. self.osc = self.graph_event_connect('scroll_event', self.on_scroll)
  221. # self.graph_event_connect('key_press_event', self.on_key_down)
  222. # self.graph_event_connect('key_release_event', self.on_key_up)
  223. self.odr = self.graph_event_connect('draw_event', self.on_draw)
  224. self.key = None
  225. self.pan_axes = []
  226. self.panning = False
  227. self.mouse = [0, 0]
  228. self.big_cursor = False
  229. self.big_cursor_isdisabled = None
  230. # signal is the mouse is dragging
  231. self.is_dragging = False
  232. # signal if there is a doubleclick
  233. self.is_dblclk = False
  234. # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
  235. # all CNC have a limited workspace
  236. if self.app.defaults['global_workspace'] is True:
  237. self.draw_workspace(workspace_size=self.app.defaults["global_workspaceT"])
  238. def draw_workspace(self, workspace_size):
  239. """
  240. Draw a rectangular shape on canvas to specify our valid workspace.
  241. :param workspace_size: the workspace size; tuple
  242. :return:
  243. """
  244. try:
  245. if self.app.defaults['units'].upper() == 'MM':
  246. dims = self.pagesize_dict[workspace_size]
  247. else:
  248. dims = (self.pagesize_dict[workspace_size][0]/25.4, self.pagesize_dict[workspace_size][1]/25.4)
  249. except Exception as e:
  250. log.debug("PlotCanvasLegacy.draw_workspace() --> %s" % str(e))
  251. return
  252. if self.app.defaults['global_workspace_orientation'] == 'l':
  253. dims = (dims[1], dims[0])
  254. xdata = [0, dims[0], dims[0], 0, 0]
  255. ydata = [0, 0, dims[1], dims[1], 0]
  256. if self.workspace_line not in self.axes.lines:
  257. self.workspace_line = Line2D(xdata=xdata, ydata=ydata, linewidth=2, antialiased=True, color='#b34d4d')
  258. self.axes.add_line(self.workspace_line)
  259. self.canvas.draw()
  260. def delete_workspace(self):
  261. try:
  262. self.axes.lines.remove(self.workspace_line)
  263. self.canvas.draw()
  264. except Exception:
  265. pass
  266. def graph_event_connect(self, event_name, callback):
  267. """
  268. Attach an event handler to the canvas through the Matplotlib interface.
  269. :param event_name: Name of the event
  270. :type event_name: str
  271. :param callback: Function to call
  272. :type callback: func
  273. :return: Connection id
  274. :rtype: int
  275. """
  276. if event_name == 'mouse_move':
  277. event_name = 'motion_notify_event'
  278. if event_name == 'mouse_press':
  279. event_name = 'button_press_event'
  280. if event_name == 'mouse_release':
  281. event_name = 'button_release_event'
  282. if event_name == 'mouse_double_click':
  283. return self.double_click.connect(callback)
  284. if event_name == 'key_press':
  285. event_name = 'key_press_event'
  286. return self.canvas.mpl_connect(event_name, callback)
  287. def graph_event_disconnect(self, cid):
  288. """
  289. Disconnect callback with the give id.
  290. :param cid: Callback id.
  291. :return: None
  292. """
  293. self.canvas.mpl_disconnect(cid)
  294. def on_new_screen(self):
  295. pass
  296. # log.debug("Cache updated the screen!")
  297. def new_cursor(self, axes=None, big=None):
  298. # if axes is None:
  299. # c = MplCursor(axes=self.axes, color='black', linewidth=1)
  300. # else:
  301. # c = MplCursor(axes=axes, color='black', linewidth=1)
  302. if self.app.defaults["global_cursor_color_enabled"]:
  303. color = self.app.defaults["global_cursor_color"]
  304. else:
  305. if self.app.defaults['global_theme'] == 'white':
  306. color = '#000000'
  307. else:
  308. color = '#FFFFFF'
  309. if big is True:
  310. self.big_cursor = True
  311. self.ch_line = self.axes.axhline(color=color, linewidth=self.app.defaults["global_cursor_width"])
  312. self.cv_line = self.axes.axvline(color=color, linewidth=self.app.defaults["global_cursor_width"])
  313. self.big_cursor_isdisabled = False
  314. else:
  315. self.big_cursor = False
  316. c = FakeCursor()
  317. c.mouse_state_updated.connect(self.clear_cursor)
  318. return c
  319. def draw_cursor(self, x_pos, y_pos, color=None):
  320. """
  321. Draw a cursor at the mouse grid snapped position
  322. :param x_pos: mouse x position
  323. :param y_pos: mouse y position
  324. :param color: custom color of the mouse
  325. :return:
  326. """
  327. # there is no point in drawing mouse cursor when panning as it jumps in a confusing way
  328. if self.app.app_cursor.enabled is True and self.panning is False:
  329. if color:
  330. color = color
  331. else:
  332. if self.app.defaults['global_theme'] == 'white':
  333. color = '#000000'
  334. else:
  335. color = '#FFFFFF'
  336. if self.big_cursor is False:
  337. try:
  338. x, y = self.app.geo_editor.snap(x_pos, y_pos)
  339. # Pointer (snapped)
  340. # The size of the cursor is multiplied by 1.65 because that value made the cursor similar with the
  341. # one in the OpenGL(3D) graphic engine
  342. pointer_size = int(float(self.app.defaults["global_cursor_size"] ) * 1.65)
  343. elements = self.axes.plot(x, y, '+', color=color, ms=pointer_size,
  344. mew=self.app.defaults["global_cursor_width"], animated=True)
  345. for el in elements:
  346. self.axes.draw_artist(el)
  347. except Exception as e:
  348. # this happen at app initialization since self.app.geo_editor does not exist yet
  349. # I could reshuffle the object instantiating order but what's the point?
  350. # I could crash something else and that's pythonic, too
  351. log.debug("PlotCanvasLegacy.draw_cursor() big_cursor is False --> %s" % str(e))
  352. else:
  353. try:
  354. self.ch_line.set_markeredgewidth(self.app.defaults["global_cursor_width"])
  355. self.cv_line.set_markeredgewidth(self.app.defaults["global_cursor_width"])
  356. except Exception:
  357. pass
  358. try:
  359. x, y = self.app.geo_editor.snap(x_pos, y_pos)
  360. self.ch_line.set_ydata(y)
  361. self.cv_line.set_xdata(x)
  362. except Exception:
  363. # this happen at app initialization since self.app.geo_editor does not exist yet
  364. # I could reshuffle the object instantiating order but what's the point?
  365. # I could crash something else and that's pythonic, too
  366. pass
  367. self.canvas.draw_idle()
  368. self.canvas.blit(self.axes.bbox)
  369. def clear_cursor(self, state):
  370. if state is True:
  371. if self.big_cursor is True and self.big_cursor_isdisabled is True:
  372. if self.app.defaults["global_cursor_color_enabled"]:
  373. color = self.app.defaults["global_cursor_color"]
  374. else:
  375. if self.app.defaults['global_theme'] == 'white':
  376. color = '#000000'
  377. else:
  378. color = '#FFFFFF'
  379. self.ch_line = self.axes.axhline(color=color, linewidth=self.app.defaults["global_cursor_width"])
  380. self.cv_line = self.axes.axvline(color=color, linewidth=self.app.defaults["global_cursor_width"])
  381. self.big_cursor_isdisabled = False
  382. if self.app.defaults["global_cursor_color_enabled"] is True:
  383. self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1], color=self.app.cursor_color_3D)
  384. else:
  385. self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])
  386. else:
  387. if self.big_cursor is True:
  388. self.big_cursor_isdisabled = True
  389. try:
  390. self.ch_line.remove()
  391. self.cv_line.remove()
  392. self.canvas.draw_idle()
  393. except Exception as e:
  394. log.debug("PlotCanvasLegacy.clear_cursor() big_cursor is True --> %s" % str(e))
  395. self.canvas.restore_region(self.background)
  396. self.canvas.blit(self.axes.bbox)
  397. def on_key_down(self, event):
  398. """
  399. :param event:
  400. :return:
  401. """
  402. FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
  403. self.key = event.key
  404. def on_key_up(self, event):
  405. """
  406. :param event:
  407. :return:
  408. """
  409. self.key = None
  410. def connect(self, event_name, callback):
  411. """
  412. Attach an event handler to the canvas through the native Qt interface.
  413. :param event_name: Name of the event
  414. :type event_name: str
  415. :param callback: Function to call
  416. :type callback: function
  417. :return: Nothing
  418. """
  419. self.canvas.connect(event_name, callback)
  420. def clear(self):
  421. """
  422. Clears axes and figure.
  423. :return: None
  424. """
  425. # Clear
  426. self.axes.cla()
  427. try:
  428. self.figure.clf()
  429. except KeyError:
  430. FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
  431. # Re-build
  432. self.figure.add_axes(self.axes)
  433. self.axes.set_aspect(1)
  434. self.axes.grid(True)
  435. self.axes.axhline(color=(0.70, 0.3, 0.3), linewidth=2)
  436. self.axes.axvline(color=(0.70, 0.3, 0.3), linewidth=2)
  437. self.adjust_axes(-10, -10, 100, 100)
  438. # Re-draw
  439. self.canvas.draw_idle()
  440. def redraw(self):
  441. """
  442. Created only to serve for compatibility with the VisPy plotcanvas (the other graphic engine, 3D)
  443. :return:
  444. """
  445. self.clear()
  446. def adjust_axes(self, xmin, ymin, xmax, ymax):
  447. """
  448. Adjusts all axes while maintaining the use of the whole canvas
  449. and an aspect ratio to 1:1 between x and y axes. The parameters are an original
  450. request that will be modified to fit these restrictions.
  451. :param xmin: Requested minimum value for the X axis.
  452. :type xmin: float
  453. :param ymin: Requested minimum value for the Y axis.
  454. :type ymin: float
  455. :param xmax: Requested maximum value for the X axis.
  456. :type xmax: float
  457. :param ymax: Requested maximum value for the Y axis.
  458. :type ymax: float
  459. :return: None
  460. """
  461. # FlatCAMApp.App.log.debug("PC.adjust_axes()")
  462. if not self.app.collection.get_list():
  463. xmin = -10
  464. ymin = -10
  465. xmax = 100
  466. ymax = 100
  467. width = xmax - xmin
  468. height = ymax - ymin
  469. try:
  470. r = width / height
  471. except ZeroDivisionError:
  472. FlatCAMApp.App.log.error("Height is %f" % height)
  473. return
  474. canvas_w, canvas_h = self.canvas.get_width_height()
  475. canvas_r = float(canvas_w) / canvas_h
  476. x_ratio = float(self.x_margin) / canvas_w
  477. y_ratio = float(self.y_margin) / canvas_h
  478. if r > canvas_r:
  479. ycenter = (ymin + ymax) / 2.0
  480. newheight = height * r / canvas_r
  481. ymin = ycenter - newheight / 2.0
  482. ymax = ycenter + newheight / 2.0
  483. else:
  484. xcenter = (xmax + xmin) / 2.0
  485. newwidth = width * canvas_r / r
  486. xmin = xcenter - newwidth / 2.0
  487. xmax = xcenter + newwidth / 2.0
  488. # Adjust axes
  489. for ax in self.figure.get_axes():
  490. if ax._label != 'base':
  491. ax.set_frame_on(False) # No frame
  492. ax.set_xticks([]) # No tick
  493. ax.set_yticks([]) # No ticks
  494. ax.patch.set_visible(False) # No background
  495. ax.set_aspect(1)
  496. ax.set_xlim((xmin, xmax))
  497. ax.set_ylim((ymin, ymax))
  498. ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
  499. # Sync re-draw to proper paint on form resize
  500. self.canvas.draw()
  501. # #### Temporary place-holder for cached update #####
  502. self.update_screen_request.emit([0, 0, 0, 0, 0])
  503. def auto_adjust_axes(self, *args):
  504. """
  505. Calls ``adjust_axes()`` using the extents of the base axes.
  506. :rtype : None
  507. :return: None
  508. """
  509. xmin, xmax = self.axes.get_xlim()
  510. ymin, ymax = self.axes.get_ylim()
  511. self.adjust_axes(xmin, ymin, xmax, ymax)
  512. def fit_view(self):
  513. self.auto_adjust_axes()
  514. def fit_center(self, loc, rect=None):
  515. x = loc[0]
  516. y = loc[1]
  517. xmin, xmax = self.axes.get_xlim()
  518. ymin, ymax = self.axes.get_ylim()
  519. half_width = (xmax - xmin) / 2
  520. half_height = (ymax - ymin) / 2
  521. # Adjust axes
  522. for ax in self.figure.get_axes():
  523. ax.set_xlim((x - half_width, x + half_width))
  524. ax.set_ylim((y - half_height, y + half_height))
  525. # Re-draw
  526. self.canvas.draw()
  527. # #### Temporary place-holder for cached update #####
  528. self.update_screen_request.emit([0, 0, 0, 0, 0])
  529. def zoom(self, factor, center=None):
  530. """
  531. Zooms the plot by factor around a given
  532. center point. Takes care of re-drawing.
  533. :param factor: Number by which to scale the plot.
  534. :type factor: float
  535. :param center: Coordinates [x, y] of the point around which to scale the plot.
  536. :type center: list
  537. :return: None
  538. """
  539. factor = 1 / factor
  540. xmin, xmax = self.axes.get_xlim()
  541. ymin, ymax = self.axes.get_ylim()
  542. width = xmax - xmin
  543. height = ymax - ymin
  544. if center is None or center == [None, None]:
  545. center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
  546. # For keeping the point at the pointer location
  547. relx = (xmax - center[0]) / width
  548. rely = (ymax - center[1]) / height
  549. new_width = width / factor
  550. new_height = height / factor
  551. xmin = center[0] - new_width * (1 - relx)
  552. xmax = center[0] + new_width * relx
  553. ymin = center[1] - new_height * (1 - rely)
  554. ymax = center[1] + new_height * rely
  555. # Adjust axes
  556. for ax in self.figure.get_axes():
  557. ax.set_xlim((xmin, xmax))
  558. ax.set_ylim((ymin, ymax))
  559. # Async re-draw
  560. self.canvas.draw_idle()
  561. # #### Temporary place-holder for cached update #####
  562. self.update_screen_request.emit([0, 0, 0, 0, 0])
  563. def pan(self, x, y, idle=True):
  564. xmin, xmax = self.axes.get_xlim()
  565. ymin, ymax = self.axes.get_ylim()
  566. width = xmax - xmin
  567. height = ymax - ymin
  568. # Adjust axes
  569. for ax in self.figure.get_axes():
  570. ax.set_xlim((xmin + x * width, xmax + x * width))
  571. ax.set_ylim((ymin + y * height, ymax + y * height))
  572. # Re-draw
  573. if idle:
  574. self.canvas.draw_idle()
  575. else:
  576. self.canvas.draw()
  577. # #### Temporary place-holder for cached update #####
  578. self.update_screen_request.emit([0, 0, 0, 0, 0])
  579. def new_axes(self, name):
  580. """
  581. Creates and returns an Axes object attached to this object's Figure.
  582. :param name: Unique label for the axes.
  583. :return: Axes attached to the figure.
  584. :rtype: Axes
  585. """
  586. new_ax = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
  587. return new_ax
  588. def remove_current_axes(self):
  589. """
  590. :return: The name of the deleted axes
  591. """
  592. axes_to_remove = self.figure.axes.gca()
  593. current_axes_name = deepcopy(axes_to_remove._label)
  594. self.figure.axes.remove(axes_to_remove)
  595. return current_axes_name
  596. def on_scroll(self, event):
  597. """
  598. Scroll event handler.
  599. :param event: Event object containing the event information.
  600. :return: None
  601. """
  602. # So it can receive key presses
  603. # self.canvas.grab_focus()
  604. self.canvas.setFocus()
  605. # Event info
  606. # z, direction = event.get_scroll_direction()
  607. if self.key is None:
  608. if event.button == 'up':
  609. self.zoom(1 / 1.5, self.mouse)
  610. else:
  611. self.zoom(1.5, self.mouse)
  612. return
  613. if self.key == 'shift':
  614. if event.button == 'up':
  615. self.pan(0.3, 0)
  616. else:
  617. self.pan(-0.3, 0)
  618. return
  619. if self.key == 'control':
  620. if event.button == 'up':
  621. self.pan(0, 0.3)
  622. else:
  623. self.pan(0, -0.3)
  624. return
  625. def on_mouse_press(self, event):
  626. self.is_dragging = True
  627. # Check for middle mouse button press
  628. if self.app.defaults["global_pan_button"] == '2':
  629. pan_button = 3 # right button for Matplotlib
  630. else:
  631. pan_button = 2 # middle button for Matplotlib
  632. if event.button == pan_button:
  633. # Prepare axes for pan (using 'matplotlib' pan function)
  634. self.pan_axes = []
  635. for a in self.figure.get_axes():
  636. if (event.x is not None and event.y is not None and a.in_axes(event) and
  637. a.get_navigate() and a.can_pan()):
  638. a.start_pan(event.x, event.y, 1)
  639. self.pan_axes.append(a)
  640. # Set pan view flag
  641. if len(self.pan_axes) > 0:
  642. self.panning = True
  643. if event.dblclick:
  644. self.double_click.emit(event)
  645. def on_mouse_release(self, event):
  646. self.is_dragging = False
  647. # Check for middle mouse button release to complete pan procedure
  648. # Check for middle mouse button press
  649. if self.app.defaults["global_pan_button"] == '2':
  650. pan_button = 3 # right button for Matplotlib
  651. else:
  652. pan_button = 2 # middle button for Matplotlib
  653. if event.button == pan_button:
  654. for a in self.pan_axes:
  655. a.end_pan()
  656. # Clear pan flag
  657. self.panning = False
  658. # And update the cursor
  659. if self.app.defaults["global_cursor_color_enabled"] is True:
  660. self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1], color=self.app.cursor_color_3D)
  661. else:
  662. self.draw_cursor(x_pos=self.mouse[0], y_pos=self.mouse[1])
  663. def on_mouse_move(self, event):
  664. """
  665. Mouse movement event handler. Stores the coordinates. Updates view on pan.
  666. :param event: Contains information about the event.
  667. :return: None
  668. """
  669. try:
  670. x = float(event.xdata)
  671. y = float(event.ydata)
  672. except TypeError:
  673. return
  674. self.mouse = [event.xdata, event.ydata]
  675. self.canvas.restore_region(self.background)
  676. # Update pan view on mouse move
  677. if self.panning is True:
  678. for a in self.pan_axes:
  679. a.drag_pan(1, event.key, event.x, event.y)
  680. # x_pan, y_pan = self.app.geo_editor.snap(event.xdata, event.ydata)
  681. # self.draw_cursor(x_pos=x_pan, y_pos=y_pan)
  682. # Async re-draw (redraws only on thread idle state, uses timer on backend)
  683. self.canvas.draw_idle()
  684. # #### Temporary place-holder for cached update #####
  685. self.update_screen_request.emit([0, 0, 0, 0, 0])
  686. if self.app.defaults["global_cursor_color_enabled"] is True:
  687. self.draw_cursor(x_pos=x, y_pos=y, color=self.app.cursor_color_3D)
  688. else:
  689. self.draw_cursor(x_pos=x, y_pos=y)
  690. # self.canvas.blit(self.axes.bbox)
  691. def translate_coords(self, position):
  692. """
  693. This does not do much. It's just for code compatibility
  694. :param position: Mouse event position
  695. :return: Tuple with mouse position
  696. """
  697. return position[0], position[1]
  698. def on_draw(self, renderer):
  699. # Store background on canvas redraw
  700. self.background = self.canvas.copy_from_bbox(self.axes.bbox)
  701. def get_axes_pixelsize(self):
  702. """
  703. Axes size in pixels.
  704. :return: Pixel width and height
  705. :rtype: tuple
  706. """
  707. bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
  708. width, height = bbox.width, bbox.height
  709. width *= self.figure.dpi
  710. height *= self.figure.dpi
  711. return width, height
  712. def get_density(self):
  713. """
  714. Returns unit length per pixel on horizontal
  715. and vertical axes.
  716. :return: X and Y density
  717. :rtype: tuple
  718. """
  719. xpx, ypx = self.get_axes_pixelsize()
  720. xmin, xmax = self.axes.get_xlim()
  721. ymin, ymax = self.axes.get_ylim()
  722. width = xmax - xmin
  723. height = ymax - ymin
  724. return width / xpx, height / ypx
  725. class FakeCursor(QtCore.QObject):
  726. """
  727. This is a fake cursor to ensure compatibility with the OpenGL engine (VisPy).
  728. This way I don't have to chane (disable) things related to the cursor all over when
  729. using the low performance Matplotlib 2D graphic engine.
  730. """
  731. mouse_state_updated = pyqtSignal(bool)
  732. def __init__(self):
  733. super().__init__()
  734. self._enabled = True
  735. @property
  736. def enabled(self):
  737. return True if self._enabled else False
  738. @enabled.setter
  739. def enabled(self, value):
  740. self._enabled = value
  741. self.mouse_state_updated.emit(value)
  742. def set_data(self, pos, **kwargs):
  743. """Internal event handler to draw the cursor when the mouse moves."""
  744. return
  745. class ShapeCollectionLegacy:
  746. """
  747. This will create the axes for each collection of shapes and will also
  748. hold the collection of shapes into a dict self._shapes.
  749. This handles the shapes redraw on canvas.
  750. """
  751. def __init__(self, obj, app, name=None, annotation_job=None):
  752. """
  753. :param obj: this is the object to which the shapes collection is attached and for
  754. which it will have to draw shapes
  755. :param app: this is the FLatCAM.App usually, needed because we have to access attributes there
  756. :param name: this is the name given to the Matplotlib axes; it needs to be unique due of Matplotlib requurements
  757. :param annotation_job: make this True if the job needed is just for annotation
  758. """
  759. self.obj = obj
  760. self.app = app
  761. self.annotation_job = annotation_job
  762. self._shapes = dict()
  763. self.shape_dict = dict()
  764. self.shape_id = 0
  765. self._color = None
  766. self._face_color = None
  767. self._visible = True
  768. self._update = False
  769. self._alpha = None
  770. self._tool_tolerance = None
  771. self._tooldia = None
  772. self._obj = None
  773. self._gcode_parsed = None
  774. if name is None:
  775. axes_name = self.obj.options['name']
  776. else:
  777. axes_name = name
  778. # Axes must exist and be attached to canvas.
  779. if axes_name not in self.app.plotcanvas.figure.axes:
  780. self.axes = self.app.plotcanvas.new_axes(axes_name)
  781. def add(self, shape=None, color=None, face_color=None, alpha=None, visible=True,
  782. update=False, layer=1, tolerance=0.01, obj=None, gcode_parsed=None, tool_tolerance=None, tooldia=None,
  783. linewidth=None):
  784. """
  785. This function will add shapes to the shape collection
  786. :param shape: the Shapely shape to be added to the shape collection
  787. :param color: edge color of the shape, hex value
  788. :param face_color: the body color of the shape, hex value
  789. :param alpha: level of transparency of the shape [0.0 ... 1.0]; Float
  790. :param visible: if True will allow the shapes to be added
  791. :param update: not used; just for compatibility with VIsPy canvas
  792. :param layer: just for compatibility with VIsPy canvas
  793. :param tolerance: just for compatibility with VIsPy canvas
  794. :param obj: not used
  795. :param gcode_parsed: not used; just for compatibility with VIsPy canvas
  796. :param tool_tolerance: just for compatibility with VIsPy canvas
  797. :param tooldia:
  798. :param linewidth: the width of the line
  799. :return:
  800. """
  801. self._color = color if color is not None else "#006E20"
  802. self._face_color = face_color if face_color is not None else "#BBF268"
  803. if len(self._color) > 7:
  804. self._color = self._color[:7]
  805. if len(self._face_color) > 7:
  806. self._face_color = self._face_color[:7]
  807. # self._alpha = int(self._face_color[-2:], 16) / 255
  808. self._alpha = 0.75
  809. if alpha is not None:
  810. self._alpha = alpha
  811. self._visible = visible
  812. self._update = update
  813. # CNCJob object related arguments
  814. self._obj = obj
  815. self._gcode_parsed = gcode_parsed
  816. self._tool_tolerance = tool_tolerance
  817. self._tooldia = tooldia
  818. # if self._update:
  819. # self.clear()
  820. try:
  821. for sh in shape:
  822. self.shape_id += 1
  823. self.shape_dict.update({
  824. 'color': self._color,
  825. 'face_color': self._face_color,
  826. 'linewidth': linewidth,
  827. 'alpha': self._alpha,
  828. 'shape': sh
  829. })
  830. self._shapes.update({
  831. self.shape_id: deepcopy(self.shape_dict)
  832. })
  833. except TypeError:
  834. self.shape_id += 1
  835. self.shape_dict.update({
  836. 'color': self._color,
  837. 'face_color': self._face_color,
  838. 'linewidth': linewidth,
  839. 'alpha': self._alpha,
  840. 'shape': shape
  841. })
  842. self._shapes.update({
  843. self.shape_id: deepcopy(self.shape_dict)
  844. })
  845. return self.shape_id
  846. def remove(self, shape_id, update=None):
  847. for k in list(self._shapes.keys()):
  848. if shape_id == k:
  849. self._shapes.pop(k, None)
  850. if update is True:
  851. self.redraw()
  852. def clear(self, update=None):
  853. """
  854. Clear the canvas of the shapes.
  855. :param update:
  856. :return: None
  857. """
  858. self._shapes.clear()
  859. self.shape_id = 0
  860. self.axes.cla()
  861. try:
  862. self.app.plotcanvas.auto_adjust_axes()
  863. except Exception as e:
  864. log.debug("ShapeCollectionLegacy.clear() --> %s" % str(e))
  865. if update is True:
  866. self.redraw()
  867. def redraw(self, update_colors=None):
  868. """
  869. This draw the shapes in the shapes collection, on canvas
  870. :return: None
  871. """
  872. path_num = 0
  873. local_shapes = deepcopy(self._shapes)
  874. try:
  875. obj_type = self.obj.kind
  876. except AttributeError:
  877. obj_type = 'utility'
  878. if self._visible:
  879. # if we don't use this then when adding each new shape, the old ones will be added again, too
  880. if obj_type == 'utility':
  881. self.axes.patches.clear()
  882. for element in local_shapes:
  883. if obj_type == 'excellon':
  884. # Plot excellon (All polygons?)
  885. if self.obj.options["solid"] and isinstance(local_shapes[element]['shape'], Polygon):
  886. patch = PolygonPatch(local_shapes[element]['shape'],
  887. facecolor="#C40000",
  888. edgecolor="#750000",
  889. alpha=local_shapes[element]['alpha'],
  890. zorder=3)
  891. self.axes.add_patch(patch)
  892. else:
  893. x, y = local_shapes[element]['shape'].exterior.coords.xy
  894. self.axes.plot(x, y, 'r-')
  895. for ints in local_shapes[element]['shape'].interiors:
  896. x, y = ints.coords.xy
  897. self.axes.plot(x, y, 'o-')
  898. elif obj_type == 'geometry':
  899. if type(local_shapes[element]['shape']) == Polygon:
  900. x, y = local_shapes[element]['shape'].exterior.coords.xy
  901. self.axes.plot(x, y, local_shapes[element]['color'],
  902. linestyle='-',
  903. linewidth=local_shapes[element]['linewidth'])
  904. for ints in local_shapes[element]['shape'].interiors:
  905. x, y = ints.coords.xy
  906. self.axes.plot(x, y, local_shapes[element]['color'],
  907. linestyle='-',
  908. linewidth=local_shapes[element]['linewidth'])
  909. elif type(local_shapes[element]['shape']) == LineString or \
  910. type(local_shapes[element]['shape']) == LinearRing:
  911. x, y = local_shapes[element]['shape'].coords.xy
  912. self.axes.plot(x, y, local_shapes[element]['color'],
  913. linestyle='-',
  914. linewidth=local_shapes[element]['linewidth'])
  915. elif obj_type == 'gerber':
  916. if self.obj.options["multicolored"]:
  917. linespec = '-'
  918. else:
  919. linespec = 'k-'
  920. if self.obj.options["solid"]:
  921. if update_colors:
  922. gerber_fill_color = update_colors[0]
  923. gerber_outline_color = update_colors[1]
  924. else:
  925. gerber_fill_color = local_shapes[element]['face_color']
  926. gerber_outline_color = local_shapes[element]['color']
  927. try:
  928. patch = PolygonPatch(local_shapes[element]['shape'],
  929. facecolor=gerber_fill_color,
  930. edgecolor=gerber_outline_color,
  931. alpha=local_shapes[element]['alpha'],
  932. zorder=2)
  933. self.axes.add_patch(patch)
  934. except AssertionError:
  935. FlatCAMApp.App.log.warning("A geometry component was not a polygon:")
  936. FlatCAMApp.App.log.warning(str(element))
  937. except Exception as e:
  938. FlatCAMApp.App.log.debug("PlotCanvasLegacy.ShepeCollectionLegacy.redraw() --> %s" % str(e))
  939. else:
  940. x, y = local_shapes[element]['shape'].exterior.xy
  941. self.axes.plot(x, y, linespec)
  942. for ints in local_shapes[element]['shape'].interiors:
  943. x, y = ints.coords.xy
  944. self.axes.plot(x, y, linespec)
  945. elif obj_type == 'cncjob':
  946. if local_shapes[element]['face_color'] is None:
  947. linespec = '--'
  948. linecolor = local_shapes[element]['color']
  949. # if geo['kind'][0] == 'C':
  950. # linespec = 'k-'
  951. x, y = local_shapes[element]['shape'].coords.xy
  952. self.axes.plot(x, y, linespec, color=linecolor)
  953. else:
  954. path_num += 1
  955. if self.obj.ui.annotation_cb.get_value():
  956. if isinstance(local_shapes[element]['shape'], Polygon):
  957. self.axes.annotate(
  958. str(path_num),
  959. xy=local_shapes[element]['shape'].exterior.coords[0],
  960. xycoords='data', fontsize=20)
  961. else:
  962. self.axes.annotate(
  963. str(path_num),
  964. xy=local_shapes[element]['shape'].coords[0],
  965. xycoords='data', fontsize=20)
  966. patch = PolygonPatch(local_shapes[element]['shape'],
  967. facecolor=local_shapes[element]['face_color'],
  968. edgecolor=local_shapes[element]['color'],
  969. alpha=local_shapes[element]['alpha'], zorder=2)
  970. self.axes.add_patch(patch)
  971. elif obj_type == 'utility':
  972. # not a FlatCAM object, must be utility
  973. if local_shapes[element]['face_color']:
  974. try:
  975. patch = PolygonPatch(local_shapes[element]['shape'],
  976. facecolor=local_shapes[element]['face_color'],
  977. edgecolor=local_shapes[element]['color'],
  978. alpha=local_shapes[element]['alpha'],
  979. zorder=2)
  980. self.axes.add_patch(patch)
  981. except Exception as e:
  982. log.debug("ShapeCollectionLegacy.redraw() --> %s" % str(e))
  983. else:
  984. if isinstance(local_shapes[element]['shape'], Polygon):
  985. ext_shape = local_shapes[element]['shape'].exterior
  986. if ext_shape is not None:
  987. x, y = ext_shape.xy
  988. self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
  989. for ints in local_shapes[element]['shape'].interiors:
  990. if ints is not None:
  991. x, y = ints.coords.xy
  992. self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
  993. else:
  994. if local_shapes[element]['shape'] is not None:
  995. x, y = local_shapes[element]['shape'].coords.xy
  996. self.axes.plot(x, y, local_shapes[element]['color'], linestyle='-')
  997. self.app.plotcanvas.auto_adjust_axes()
  998. def set(self, text, pos, visible=True, font_size=16, color=None):
  999. """
  1000. This will set annotations on the canvas.
  1001. :param text: a list of text elements to be used as annotations
  1002. :param pos: a list of positions for showing the text elements above
  1003. :param visible: if True will display annotations, if False will clear them on canvas
  1004. :param font_size: the font size or the annotations
  1005. :param color: color of the annotations
  1006. :return: None
  1007. """
  1008. if color is None:
  1009. color = "#000000FF"
  1010. if visible is not True:
  1011. self.clear()
  1012. return
  1013. if len(text) != len(pos):
  1014. self.app.inform.emit('[ERROR_NOTCL] %s' % _("Could not annotate due of a difference between the number "
  1015. "of text elements and the number of text positions."))
  1016. return
  1017. for idx in range(len(text)):
  1018. try:
  1019. self.axes.annotate(text[idx], xy=pos[idx], xycoords='data', fontsize=font_size, color=color)
  1020. except Exception as e:
  1021. log.debug("ShapeCollectionLegacy.set() --> %s" % str(e))
  1022. self.app.plotcanvas.auto_adjust_axes()
  1023. @property
  1024. def visible(self):
  1025. return self._visible
  1026. @visible.setter
  1027. def visible(self, value):
  1028. if value is False:
  1029. self.axes.cla()
  1030. self.app.plotcanvas.auto_adjust_axes()
  1031. else:
  1032. if self._visible is False:
  1033. self.redraw()
  1034. self._visible = value
  1035. @property
  1036. def enabled(self):
  1037. return self._visible
  1038. @enabled.setter
  1039. def enabled(self, value):
  1040. if value is False:
  1041. self.axes.cla()
  1042. self.app.plotcanvas.auto_adjust_axes()
  1043. else:
  1044. if self._visible is False:
  1045. self.redraw()
  1046. self._visible = value
  1047. # class MplCursor(Cursor):
  1048. # """
  1049. # Unfortunately this gets attached to the current axes and if a new axes is added
  1050. # it will not be showed until that axes is deleted.
  1051. # Not the kind of behavior needed here so I don't use it anymore.
  1052. # """
  1053. # def __init__(self, axes, color='red', linewidth=1):
  1054. #
  1055. # super().__init__(ax=axes, useblit=True, color=color, linewidth=linewidth)
  1056. # self._enabled = True
  1057. #
  1058. # self.axes = axes
  1059. # self.color = color
  1060. # self.linewidth = linewidth
  1061. #
  1062. # self.x = None
  1063. # self.y = None
  1064. #
  1065. # @property
  1066. # def enabled(self):
  1067. # return True if self._enabled else False
  1068. #
  1069. # @enabled.setter
  1070. # def enabled(self, value):
  1071. # self._enabled = value
  1072. # self.visible = self._enabled
  1073. # self.canvas.draw()
  1074. #
  1075. # def onmove(self, event):
  1076. # pass
  1077. #
  1078. # def set_data(self, event, pos):
  1079. # """Internal event handler to draw the cursor when the mouse moves."""
  1080. # self.x = pos[0]
  1081. # self.y = pos[1]
  1082. #
  1083. # if self.ignore(event):
  1084. # return
  1085. # if not self.canvas.widgetlock.available(self):
  1086. # return
  1087. # if event.inaxes != self.ax:
  1088. # self.linev.set_visible(False)
  1089. # self.lineh.set_visible(False)
  1090. #
  1091. # if self.needclear:
  1092. # self.canvas.draw()
  1093. # self.needclear = False
  1094. # return
  1095. # self.needclear = True
  1096. # if not self.visible:
  1097. # return
  1098. # self.linev.set_xdata((self.x, self.x))
  1099. #
  1100. # self.lineh.set_ydata((self.y, self.y))
  1101. # self.linev.set_visible(self.visible and self.vertOn)
  1102. # self.lineh.set_visible(self.visible and self.horizOn)
  1103. #
  1104. # self._update()