PlotCanvas.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. # ########################################################## ##
  8. from PyQt5 import QtCore
  9. import logging
  10. from flatcamGUI.VisPyCanvas import VisPyCanvas, time
  11. from flatcamGUI.VisPyVisuals import ShapeGroup, ShapeCollection, TextCollection, TextGroup, Cursor
  12. from vispy.scene.visuals import InfiniteLine, Line
  13. import numpy as np
  14. from vispy.geometry import Rect
  15. log = logging.getLogger('base')
  16. class PlotCanvas(QtCore.QObject):
  17. """
  18. Class handling the plotting area in the application.
  19. """
  20. def __init__(self, container, app):
  21. """
  22. The constructor configures the VisPy figure that
  23. will contain all plots, creates the base axes and connects
  24. events to the plotting area.
  25. :param container: The parent container in which to draw plots.
  26. :rtype: PlotCanvas
  27. """
  28. super(PlotCanvas, self).__init__()
  29. self.app = app
  30. # Parent container
  31. self.container = container
  32. # workspace lines; I didn't use the rectangle because I didn't want to add another VisPy Node,
  33. # which might decrease performance
  34. self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
  35. # Attach to parent
  36. self.vispy_canvas = VisPyCanvas()
  37. self.vispy_canvas.create_native()
  38. self.vispy_canvas.native.setParent(self.app.ui)
  39. self.container.addWidget(self.vispy_canvas.native)
  40. # ## AXIS # ##
  41. self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=True,
  42. parent=self.vispy_canvas.view.scene)
  43. self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=False,
  44. parent=self.vispy_canvas.view.scene)
  45. # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
  46. # all CNC have a limited workspace
  47. self.draw_workspace()
  48. # if self.app.defaults['global_workspace'] is True:
  49. # if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
  50. # self.wkspace_t = Line(pos=)
  51. self.shape_collections = []
  52. self.shape_collection = self.new_shape_collection()
  53. self.app.pool_recreated.connect(self.on_pool_recreated)
  54. self.text_collection = self.new_text_collection()
  55. # TODO: Should be setting to show/hide CNC job annotations (global or per object)
  56. self.text_collection.enabled = True
  57. # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
  58. # all CNC have a limited workspace
  59. def draw_workspace(self):
  60. a = np.empty((0, 0))
  61. a4p_in = np.array([(0, 0), (8.3, 0), (8.3, 11.7), (0, 11.7)])
  62. a4l_in = np.array([(0, 0), (11.7, 0), (11.7, 8.3), (0, 8.3)])
  63. a3p_in = np.array([(0, 0), (11.7, 0), (11.7, 16.5), (0, 16.5)])
  64. a3l_in = np.array([(0, 0), (16.5, 0), (16.5, 11.7), (0, 11.7)])
  65. a4p_mm = np.array([(0, 0), (210, 0), (210, 297), (0, 297)])
  66. a4l_mm = np.array([(0, 0), (297, 0), (297,210), (0, 210)])
  67. a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
  68. a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
  69. if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
  70. if self.app.defaults['global_workspaceT'] == 'A4P':
  71. a = a4p_mm
  72. elif self.app.defaults['global_workspaceT'] == 'A4L':
  73. a = a4l_mm
  74. elif self.app.defaults['global_workspaceT'] == 'A3P':
  75. a = a3p_mm
  76. elif self.app.defaults['global_workspaceT'] == 'A3L':
  77. a = a3l_mm
  78. else:
  79. if self.app.defaults['global_workspaceT'] == 'A4P':
  80. a = a4p_in
  81. elif self.app.defaults['global_workspaceT'] == 'A4L':
  82. a = a4l_in
  83. elif self.app.defaults['global_workspaceT'] == 'A3P':
  84. a = a3p_in
  85. elif self.app.defaults['global_workspaceT'] == 'A3L':
  86. a = a3l_in
  87. self.delete_workspace()
  88. self.b_line = Line(pos=a[0:2], color=(0.70, 0.3, 0.3, 1.0),
  89. antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
  90. self.r_line = Line(pos=a[1:3], color=(0.70, 0.3, 0.3, 1.0),
  91. antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
  92. self.t_line = Line(pos=a[2:4], color=(0.70, 0.3, 0.3, 1.0),
  93. antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
  94. self.l_line = Line(pos=np.array((a[0], a[3])), color=(0.70, 0.3, 0.3, 1.0),
  95. antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
  96. if self.app.defaults['global_workspace'] is False:
  97. self.delete_workspace()
  98. # delete the workspace lines from the plot by removing the parent
  99. def delete_workspace(self):
  100. try:
  101. self.b_line.parent = None
  102. self.r_line.parent = None
  103. self.t_line.parent = None
  104. self.l_line.parent = None
  105. except Exception as e:
  106. pass
  107. # redraw the workspace lines on the plot by readding them to the parent view.scene
  108. def restore_workspace(self):
  109. try:
  110. self.b_line.parent = self.vispy_canvas.view.scene
  111. self.r_line.parent = self.vispy_canvas.view.scene
  112. self.t_line.parent = self.vispy_canvas.view.scene
  113. self.l_line.parent = self.vispy_canvas.view.scene
  114. except Exception as e:
  115. pass
  116. def vis_connect(self, event_name, callback):
  117. return getattr(self.vispy_canvas.events, event_name).connect(callback)
  118. def vis_disconnect(self, event_name, callback=None):
  119. if callback is None:
  120. getattr(self.vispy_canvas.events, event_name).disconnect()
  121. else:
  122. getattr(self.vispy_canvas.events, event_name).disconnect(callback)
  123. def zoom(self, factor, center=None):
  124. """
  125. Zooms the plot by factor around a given
  126. center point. Takes care of re-drawing.
  127. :param factor: Number by which to scale the plot.
  128. :type factor: float
  129. :param center: Coordinates [x, y] of the point around which to scale the plot.
  130. :type center: list
  131. :return: None
  132. """
  133. self.vispy_canvas.view.camera.zoom(factor, center)
  134. def new_shape_group(self, shape_collection=None):
  135. if shape_collection:
  136. return ShapeGroup(shape_collection)
  137. return ShapeGroup(self.shape_collection)
  138. def new_shape_collection(self, **kwargs):
  139. # sc = ShapeCollection(parent=self.vispy_canvas.view.scene, pool=self.app.pool, **kwargs)
  140. # self.shape_collections.append(sc)
  141. # return sc
  142. return ShapeCollection(parent=self.vispy_canvas.view.scene, pool=self.app.pool, **kwargs)
  143. def new_cursor(self):
  144. c = Cursor(pos=np.empty((0, 2)), parent=self.vispy_canvas.view.scene)
  145. c.antialias = 0
  146. return c
  147. def new_text_group(self, collection=None):
  148. if collection:
  149. return TextGroup(collection)
  150. else:
  151. return TextGroup(self.text_collection)
  152. def new_text_collection(self, **kwargs):
  153. return TextCollection(parent=self.vispy_canvas.view.scene, **kwargs)
  154. def fit_view(self, rect=None):
  155. # Lock updates in other threads
  156. self.shape_collection.lock_updates()
  157. if not rect:
  158. rect = Rect(-1, -1, 20, 20)
  159. try:
  160. rect.left, rect.right = self.shape_collection.bounds(axis=0)
  161. rect.bottom, rect.top = self.shape_collection.bounds(axis=1)
  162. except TypeError:
  163. pass
  164. # adjust the view camera to be slightly bigger than the bounds so the shape colleaction can be seen clearly
  165. # otherwise the shape collection boundary will have no border
  166. rect.left *= 0.96
  167. rect.bottom *= 0.96
  168. rect.right *= 1.01
  169. rect.top *= 1.01
  170. self.vispy_canvas.view.camera.rect = rect
  171. self.shape_collection.unlock_updates()
  172. def fit_center(self, loc, rect=None):
  173. # Lock updates in other threads
  174. self.shape_collection.lock_updates()
  175. if not rect:
  176. try:
  177. rect = Rect(loc[0]-20, loc[1]-20, 40, 40)
  178. except TypeError:
  179. pass
  180. self.vispy_canvas.view.camera.rect = rect
  181. self.shape_collection.unlock_updates()
  182. def clear(self):
  183. pass
  184. def redraw(self):
  185. self.shape_collection.redraw([])
  186. self.text_collection.redraw()
  187. def on_pool_recreated(self, pool):
  188. self.shape_collection.pool = pool