PlotCanvasLegacy.py 50 KB

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