shape_draw.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2018, Felix Schill.
  3. # Distributed under the (new) BSD License. See LICENSE.txt for more info.
  4. # vispy: gallery 2
  5. """
  6. Draw and Edit Shapes with Mouse
  7. ===============================
  8. Simple demonstration of drawing and editing shapes with the mouse
  9. This demo implements mouse picking on visuals and markers using the
  10. vispy scene and "visual_at" mechanism.
  11. Left mouse button on empty space creates new objects. Objects can be
  12. selected by clicking, and moved by dragging. Dragging control points
  13. changes the size of the object.
  14. Vispy takes care of coordinate transforms from screen to ViewBox - the
  15. demo works on different zoom levels.
  16. Lastly, additional objects are added to the view in a fixed position as
  17. "buttons" to select which type of object is being created. Selecting
  18. the arrow symbol will switch into select/pan mode where the left drag
  19. moves the workplane or moves objects/controlpoints.
  20. """
  21. import numpy as np
  22. from vispy import app, scene
  23. from vispy.color import Color
  24. class ControlPoints(scene.visuals.Compound):
  25. def __init__(self, parent):
  26. scene.visuals.Compound.__init__(self, [])
  27. self.unfreeze()
  28. self.parent = parent
  29. self._center = [0, 0]
  30. self._width = 0.0
  31. self._height = 0.0
  32. self.selected_cp = None
  33. self.opposed_cp = None
  34. self.control_points = [scene.visuals.Markers(parent=self)
  35. for i in range(0, 4)]
  36. for c in self.control_points:
  37. c.set_data(pos=np.array([[0, 0]],
  38. dtype=np.float32),
  39. symbol="s",
  40. edge_color="red",
  41. size=6)
  42. c.interactive = True
  43. self.freeze()
  44. def update_bounds(self):
  45. self._center = [0.5 * (self.parent.bounds(0)[1] +
  46. self.parent.bounds(0)[0]),
  47. 0.5 * (self.parent.bounds(1)[1] +
  48. self.parent.bounds(1)[0])]
  49. self._width = self.parent.bounds(0)[1] - self.parent.bounds(0)[0]
  50. self._height = self.parent.bounds(1)[1] - self.parent.bounds(1)[0]
  51. self.update_points()
  52. def update_points(self):
  53. self.control_points[0].set_data(
  54. pos=np.array([[self._center[0] - 0.5 * self._width,
  55. self._center[1] + 0.5 * self._height]]))
  56. self.control_points[1].set_data(
  57. pos=np.array([[self._center[0] + 0.5 * self._width,
  58. self._center[1] + 0.5 * self._height]]))
  59. self.control_points[2].set_data(
  60. pos=np.array([[self._center[0] + 0.5 * self._width,
  61. self._center[1] - 0.5 * self._height]]))
  62. self.control_points[3].set_data(
  63. pos=np.array([[self._center[0] - 0.5 * self._width,
  64. self._center[1] - 0.5 * self._height]]))
  65. def select(self, val, obj=None):
  66. self.visible(val)
  67. self.selected_cp = None
  68. self.opposed_cp = None
  69. if obj is not None:
  70. n_cp = len(self.control_points)
  71. for i in range(0, n_cp):
  72. c = self.control_points[i]
  73. if c == obj:
  74. self.selected_cp = c
  75. self.opposed_cp = \
  76. self.control_points[int((i + n_cp / 2)) % n_cp]
  77. def start_move(self, start):
  78. None
  79. def move(self, end):
  80. if not self.parent.editable:
  81. return
  82. if self.selected_cp is not None:
  83. self._width = 2 * (end[0] - self._center[0])
  84. self._height = 2 * (end[1] - self._center[1])
  85. self.update_points()
  86. self.parent.update_from_controlpoints()
  87. def visible(self, v):
  88. for c in self.control_points:
  89. c.visible = v
  90. def get_center(self):
  91. return self._center
  92. def set_center(self, val):
  93. self._center = val
  94. self.update_points()
  95. class EditVisual(scene.visuals.Compound):
  96. def __init__(self, editable=True, selectable=True, on_select_callback=None,
  97. callback_argument=None, *args, **kwargs):
  98. scene.visuals.Compound.__init__(self, [], *args, **kwargs)
  99. self.unfreeze()
  100. self.editable = editable
  101. self._selectable = selectable
  102. self._on_select_callback = on_select_callback
  103. self._callback_argument = callback_argument
  104. self.control_points = ControlPoints(parent=self)
  105. self.drag_reference = [0, 0]
  106. self.freeze()
  107. def add_subvisual(self, visual):
  108. scene.visuals.Compound.add_subvisual(self, visual)
  109. visual.interactive = True
  110. self.control_points.update_bounds()
  111. self.control_points.visible(False)
  112. def select(self, val, obj=None):
  113. if self.selectable:
  114. self.control_points.visible(val)
  115. if self._on_select_callback is not None:
  116. self._on_select_callback(self._callback_argument)
  117. def start_move(self, start):
  118. self.drag_reference = start[0:2] - self.control_points.get_center()
  119. def move(self, end):
  120. if self.editable:
  121. shift = end[0:2] - self.drag_reference
  122. self.set_center(shift)
  123. def update_from_controlpoints(self):
  124. None
  125. @property
  126. def selectable(self):
  127. return self._selectable
  128. @selectable.setter
  129. def selectable(self, val):
  130. self._selectable = val
  131. @property
  132. def center(self):
  133. return self.control_points.get_center()
  134. @center.setter
  135. # this method redirects to set_center. Override set_center in subclasses.
  136. def center(self, val):
  137. self.set_center(val)
  138. # override this method in subclass
  139. def set_center(self, val):
  140. self.control_points.set_center(val[0:2])
  141. def select_creation_controlpoint(self):
  142. self.control_points.select(True, self.control_points.control_points[2])
  143. class EditRectVisual(EditVisual):
  144. def __init__(self, center=[0, 0], width=20, height=20, *args, **kwargs):
  145. EditVisual.__init__(self, *args, **kwargs)
  146. self.unfreeze()
  147. self.rect = scene.visuals.Rectangle(center=center, width=width,
  148. height=height,
  149. color=Color("#e88834"),
  150. border_color="white",
  151. radius=0, parent=self)
  152. self.rect.interactive = True
  153. self.freeze()
  154. self.add_subvisual(self.rect)
  155. self.control_points.update_bounds()
  156. self.control_points.visible(False)
  157. def set_center(self, val):
  158. self.control_points.set_center(val[0:2])
  159. self.rect.center = val[0:2]
  160. def update_from_controlpoints(self):
  161. try:
  162. self.rect.width = abs(self.control_points._width)
  163. except ValueError:
  164. None
  165. try:
  166. self.rect.height = abs(self.control_points._height)
  167. except ValueError:
  168. None
  169. class EditEllipseVisual(EditVisual):
  170. def __init__(self, center=[0, 0], radius=[2, 2], *args, **kwargs):
  171. EditVisual.__init__(self, *args, **kwargs)
  172. self.unfreeze()
  173. self.ellipse = scene.visuals.Ellipse(center=center, radius=radius,
  174. color=Color("#e88834"),
  175. border_color="white",
  176. parent=self)
  177. self.ellipse.interactive = True
  178. self.freeze()
  179. self.add_subvisual(self.ellipse)
  180. self.control_points.update_bounds()
  181. self.control_points.visible(False)
  182. def set_center(self, val):
  183. self.control_points.set_center(val)
  184. self.ellipse.center = val
  185. def update_from_controlpoints(self):
  186. try:
  187. self.ellipse.radius = [0.5 * abs(self.control_points._width),
  188. 0.5 * abs(self.control_points._height)]
  189. except ValueError:
  190. None
  191. class Canvas(scene.SceneCanvas):
  192. """ A simple test canvas for drawing demo """
  193. def __init__(self):
  194. scene.SceneCanvas.__init__(self, keys='interactive',
  195. size=(800, 800))
  196. self.unfreeze()
  197. self.view = self.central_widget.add_view()
  198. self.view.camera = scene.PanZoomCamera(rect=(-100, -100, 200, 200),
  199. aspect=1.0)
  200. # the left mouse button pan has to be disabled in the camera, as it
  201. # interferes with dragging line points
  202. # Proposed change in camera: make mouse buttons configurable
  203. self.view.camera._viewbox.events.mouse_move.disconnect(
  204. self.view.camera.viewbox_mouse_event)
  205. scene.visuals.Text("Click and drag to add objects, " +
  206. "right-click to delete.",
  207. color='w',
  208. anchor_x='left',
  209. parent=self.view,
  210. pos=(20, 30))
  211. self.select_arrow = \
  212. EditVisual(parent=self.view, editable=False,
  213. on_select_callback=self.set_creation_mode,
  214. callback_argument=None)
  215. arrow = scene.visuals.Arrow(parent=self.select_arrow,
  216. pos=np.array([[50, 60], [60, 70]]),
  217. arrows=np.array([[60, 70, 50, 60]]),
  218. width=5, arrow_size=15.0,
  219. arrow_type="angle_60",
  220. color="w",
  221. arrow_color="w",
  222. method="agg"
  223. )
  224. self.select_arrow.add_subvisual(arrow)
  225. self.rect_button = \
  226. EditRectVisual(parent=self.view, editable=False,
  227. on_select_callback=self.set_creation_mode,
  228. callback_argument=EditRectVisual,
  229. center=[50, 120], width=30, height=30)
  230. self.ellipse_button = \
  231. EditEllipseVisual(parent=self.view,
  232. editable=False,
  233. on_select_callback=self.set_creation_mode,
  234. callback_argument=EditEllipseVisual,
  235. center=[50, 170],
  236. radius=[15, 10])
  237. self.objects = []
  238. self.show()
  239. self.selected_point = None
  240. self.selected_object = None
  241. self.creation_mode = EditRectVisual
  242. self.mouse_start_pos = [0, 0]
  243. scene.visuals.GridLines(parent=self.view.scene)
  244. self.freeze()
  245. def set_creation_mode(self, object_kind):
  246. self.creation_mode = object_kind
  247. def on_mouse_press(self, event):
  248. tr = self.scene.node_transform(self.view.scene)
  249. pos = tr.map(event.pos)
  250. self.view.interactive = False
  251. selected = self.visual_at(event.pos)
  252. self.view.interactive = True
  253. if self.selected_object is not None:
  254. self.selected_object.select(False)
  255. self.selected_object = None
  256. if event.button == 1:
  257. if selected is not None:
  258. self.selected_object = selected.parent
  259. # update transform to selected object
  260. tr = self.scene.node_transform(self.selected_object)
  261. pos = tr.map(event.pos)
  262. self.selected_object.select(True, obj=selected)
  263. self.selected_object.start_move(pos)
  264. self.mouse_start_pos = event.pos
  265. # create new object:
  266. if self.selected_object is None and self.creation_mode is not None:
  267. # new_object = EditRectVisual(parent=self.view.scene)
  268. new_object = self.creation_mode(parent=self.view.scene)
  269. self.objects.append(new_object)
  270. new_object.select_creation_controlpoint()
  271. new_object.set_center(pos[0:2])
  272. self.selected_object = new_object.control_points
  273. if event.button == 2: # right button deletes object
  274. if selected is not None and selected.parent in self.objects:
  275. self.objects.remove(selected.parent)
  276. selected.parent.parent = None
  277. self.selected_object = None
  278. def on_mouse_move(self, event):
  279. if event.button == 1:
  280. if self.selected_object is not None:
  281. self.view.camera._viewbox.events.mouse_move.disconnect(
  282. self.view.camera.viewbox_mouse_event)
  283. # update transform to selected object
  284. tr = self.scene.node_transform(self.selected_object)
  285. pos = tr.map(event.pos)
  286. self.selected_object.move(pos[0:2])
  287. else:
  288. self.view.camera._viewbox.events.mouse_move.connect(
  289. self.view.camera.viewbox_mouse_event)
  290. else:
  291. None
  292. if __name__ == '__main__':
  293. canvas = Canvas()
  294. app.run()