{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%gui qt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Draw and Edit Shapes with Mouse\n\nSimple demonstration of drawing and editing shapes with the mouse\nThis demo implements mouse picking on visuals and markers using the\nvispy scene and \"visual_at\" mechanism.\nLeft mouse button on empty space creates new objects. Objects can be\nselected by clicking, and moved by dragging. Dragging control points\nchanges the size of the object.\nVispy takes care of coordinate transforms from screen to ViewBox - the\ndemo works on different zoom levels.\nLastly, additional objects are added to the view in a fixed position as\n\"buttons\" to select which type of object is being created. Selecting\nthe arrow symbol will switch into select/pan mode where the left drag\nmoves the workplane or moves objects/controlpoints.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n\nfrom vispy import app, scene\nfrom vispy.color import Color\n\n\nclass ControlPoints(scene.visuals.Compound):\n def __init__(self, parent):\n scene.visuals.Compound.__init__(self, [])\n self.unfreeze()\n self.parent = parent\n self._center = [0, 0]\n self._width = 0.0\n self._height = 0.0\n self.selected_cp = None\n self.opposed_cp = None\n\n self.control_points = [scene.visuals.Markers(parent=self)\n for i in range(0, 4)]\n for c in self.control_points:\n c.set_data(pos=np.array([[0, 0]],\n dtype=np.float32),\n symbol=\"s\",\n edge_color=\"red\",\n size=6)\n c.interactive = True\n self.freeze()\n\n def update_bounds(self):\n self._center = [0.5 * (self.parent.bounds(0)[1] +\n self.parent.bounds(0)[0]),\n 0.5 * (self.parent.bounds(1)[1] +\n self.parent.bounds(1)[0])]\n self._width = self.parent.bounds(0)[1] - self.parent.bounds(0)[0]\n self._height = self.parent.bounds(1)[1] - self.parent.bounds(1)[0]\n self.update_points()\n\n def update_points(self):\n self.control_points[0].set_data(\n pos=np.array([[self._center[0] - 0.5 * self._width,\n self._center[1] + 0.5 * self._height]]))\n self.control_points[1].set_data(\n pos=np.array([[self._center[0] + 0.5 * self._width,\n self._center[1] + 0.5 * self._height]]))\n self.control_points[2].set_data(\n pos=np.array([[self._center[0] + 0.5 * self._width,\n self._center[1] - 0.5 * self._height]]))\n self.control_points[3].set_data(\n pos=np.array([[self._center[0] - 0.5 * self._width,\n self._center[1] - 0.5 * self._height]]))\n\n def select(self, val, obj=None):\n self.visible(val)\n self.selected_cp = None\n self.opposed_cp = None\n\n if obj is not None:\n n_cp = len(self.control_points)\n for i in range(0, n_cp):\n c = self.control_points[i]\n if c == obj:\n self.selected_cp = c\n self.opposed_cp = \\\n self.control_points[int((i + n_cp / 2)) % n_cp]\n\n def start_move(self, start):\n None\n\n def move(self, end):\n if not self.parent.editable:\n return\n if self.selected_cp is not None:\n self._width = 2 * (end[0] - self._center[0])\n self._height = 2 * (end[1] - self._center[1])\n self.update_points()\n self.parent.update_from_controlpoints()\n\n def visible(self, v):\n for c in self.control_points:\n c.visible = v\n\n def get_center(self):\n return self._center\n\n def set_center(self, val):\n self._center = val\n self.update_points()\n\n\nclass EditVisual(scene.visuals.Compound):\n def __init__(self, editable=True, selectable=True, on_select_callback=None,\n callback_argument=None, *args, **kwargs):\n scene.visuals.Compound.__init__(self, [], *args, **kwargs)\n self.unfreeze()\n self.editable = editable\n self._selectable = selectable\n self._on_select_callback = on_select_callback\n self._callback_argument = callback_argument\n self.control_points = ControlPoints(parent=self)\n self.drag_reference = [0, 0]\n self.freeze()\n\n def add_subvisual(self, visual):\n scene.visuals.Compound.add_subvisual(self, visual)\n visual.interactive = True\n self.control_points.update_bounds()\n self.control_points.visible(False)\n\n def select(self, val, obj=None):\n if self.selectable:\n self.control_points.visible(val)\n if self._on_select_callback is not None:\n self._on_select_callback(self._callback_argument)\n\n def start_move(self, start):\n self.drag_reference = start[0:2] - self.control_points.get_center()\n\n def move(self, end):\n if self.editable:\n shift = end[0:2] - self.drag_reference\n self.set_center(shift)\n\n def update_from_controlpoints(self):\n None\n\n @property\n def selectable(self):\n return self._selectable\n\n @selectable.setter\n def selectable(self, val):\n self._selectable = val\n\n @property\n def center(self):\n return self.control_points.get_center()\n\n @center.setter\n # this method redirects to set_center. Override set_center in subclasses.\n def center(self, val):\n self.set_center(val)\n\n # override this method in subclass\n def set_center(self, val):\n self.control_points.set_center(val[0:2])\n\n def select_creation_controlpoint(self):\n self.control_points.select(True, self.control_points.control_points[2])\n\n\nclass EditRectVisual(EditVisual):\n def __init__(self, center=[0, 0], width=20, height=20, *args, **kwargs):\n EditVisual.__init__(self, *args, **kwargs)\n self.unfreeze()\n self.rect = scene.visuals.Rectangle(center=center, width=width,\n height=height,\n color=Color(\"#e88834\"),\n border_color=\"white\",\n radius=0, parent=self)\n self.rect.interactive = True\n\n self.freeze()\n self.add_subvisual(self.rect)\n self.control_points.update_bounds()\n self.control_points.visible(False)\n\n def set_center(self, val):\n self.control_points.set_center(val[0:2])\n self.rect.center = val[0:2]\n\n def update_from_controlpoints(self):\n try:\n self.rect.width = abs(self.control_points._width)\n except ValueError:\n None\n try:\n self.rect.height = abs(self.control_points._height)\n except ValueError:\n None\n\n\nclass EditEllipseVisual(EditVisual):\n def __init__(self, center=[0, 0], radius=[2, 2], *args, **kwargs):\n EditVisual.__init__(self, *args, **kwargs)\n self.unfreeze()\n self.ellipse = scene.visuals.Ellipse(center=center, radius=radius,\n color=Color(\"#e88834\"),\n border_color=\"white\",\n parent=self)\n self.ellipse.interactive = True\n\n self.freeze()\n self.add_subvisual(self.ellipse)\n self.control_points.update_bounds()\n self.control_points.visible(False)\n\n def set_center(self, val):\n self.control_points.set_center(val)\n self.ellipse.center = val\n\n def update_from_controlpoints(self):\n try:\n self.ellipse.radius = [0.5 * abs(self.control_points._width),\n 0.5 * abs(self.control_points._height)]\n except ValueError:\n None\n\n\nclass Canvas(scene.SceneCanvas):\n \"\"\" A simple test canvas for drawing demo \"\"\"\n\n def __init__(self):\n scene.SceneCanvas.__init__(self, keys='interactive',\n size=(800, 800))\n\n self.unfreeze()\n\n self.view = self.central_widget.add_view()\n self.view.camera = scene.PanZoomCamera(rect=(-100, -100, 200, 200),\n aspect=1.0)\n # the left mouse button pan has to be disabled in the camera, as it\n # interferes with dragging line points\n # Proposed change in camera: make mouse buttons configurable\n self.view.camera._viewbox.events.mouse_move.disconnect(\n self.view.camera.viewbox_mouse_event)\n\n scene.visuals.Text(\"Click and drag to add objects, \" +\n \"right-click to delete.\",\n color='w',\n anchor_x='left',\n parent=self.view,\n pos=(20, 30))\n\n self.select_arrow = \\\n EditVisual(parent=self.view, editable=False,\n on_select_callback=self.set_creation_mode,\n callback_argument=None)\n arrow = scene.visuals.Arrow(parent=self.select_arrow,\n pos=np.array([[50, 60], [60, 70]]),\n arrows=np.array([[60, 70, 50, 60]]),\n width=5, arrow_size=15.0,\n arrow_type=\"angle_60\",\n color=\"w\",\n arrow_color=\"w\",\n method=\"agg\"\n )\n self.select_arrow.add_subvisual(arrow)\n\n self.rect_button = \\\n EditRectVisual(parent=self.view, editable=False,\n on_select_callback=self.set_creation_mode,\n callback_argument=EditRectVisual,\n center=[50, 120], width=30, height=30)\n self.ellipse_button = \\\n EditEllipseVisual(parent=self.view,\n editable=False,\n on_select_callback=self.set_creation_mode,\n callback_argument=EditEllipseVisual,\n center=[50, 170],\n radius=[15, 10])\n\n self.objects = []\n self.show()\n self.selected_point = None\n self.selected_object = None\n self.creation_mode = EditRectVisual\n self.mouse_start_pos = [0, 0]\n scene.visuals.GridLines(parent=self.view.scene)\n self.freeze()\n\n def set_creation_mode(self, object_kind):\n self.creation_mode = object_kind\n\n def on_mouse_press(self, event):\n\n tr = self.scene.node_transform(self.view.scene)\n pos = tr.map(event.pos)\n self.view.interactive = False\n selected = self.visual_at(event.pos)\n self.view.interactive = True\n if self.selected_object is not None:\n self.selected_object.select(False)\n self.selected_object = None\n\n if event.button == 1:\n if selected is not None:\n self.selected_object = selected.parent\n # update transform to selected object\n tr = self.scene.node_transform(self.selected_object)\n pos = tr.map(event.pos)\n\n self.selected_object.select(True, obj=selected)\n self.selected_object.start_move(pos)\n self.mouse_start_pos = event.pos\n\n # create new object:\n if self.selected_object is None and self.creation_mode is not None:\n # new_object = EditRectVisual(parent=self.view.scene)\n new_object = self.creation_mode(parent=self.view.scene)\n self.objects.append(new_object)\n new_object.select_creation_controlpoint()\n new_object.set_center(pos[0:2])\n self.selected_object = new_object.control_points\n\n if event.button == 2: # right button deletes object\n if selected is not None and selected.parent in self.objects:\n self.objects.remove(selected.parent)\n selected.parent.parent = None\n self.selected_object = None\n\n def on_mouse_move(self, event):\n\n if event.button == 1:\n if self.selected_object is not None:\n self.view.camera._viewbox.events.mouse_move.disconnect(\n self.view.camera.viewbox_mouse_event)\n # update transform to selected object\n tr = self.scene.node_transform(self.selected_object)\n pos = tr.map(event.pos)\n\n self.selected_object.move(pos[0:2])\n else:\n self.view.camera._viewbox.events.mouse_move.connect(\n self.view.camera.viewbox_mouse_event)\n else:\n None\n\n\nif __name__ == '__main__':\n canvas = Canvas()\n app.run()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.19" } }, "nbformat": 4, "nbformat_minor": 0 }