Commit f7b5fc4d6a0a17b0359510217568644ca344dc5a
1 parent
071dff96
Exists in
interactor_style
Created a new interactor style to handle the cross tool
Showing
2 changed files
with
264 additions
and
141 deletions
Show diff stats
invesalius/data/styles.py
| ... | ... | @@ -19,8 +19,145 @@ |
| 19 | 19 | |
| 20 | 20 | import vtk |
| 21 | 21 | |
| 22 | +from wx.lib.pubsub import pub as Publisher | |
| 23 | + | |
| 22 | 24 | import constants as const |
| 23 | 25 | |
| 26 | +class ZoomInteractorStyle(vtk.vtkInteractorStyleImage): | |
| 27 | + """ | |
| 28 | + Interactor style responsible for zoom the camera. | |
| 29 | + """ | |
| 30 | + def __init__(self): | |
| 31 | + self.right_pressed = False | |
| 32 | + | |
| 33 | + # Zoom using right button | |
| 34 | + self.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | |
| 35 | + self.AddObserver("MouseMoveEvent", self.OnZoomRightMove) | |
| 36 | + self.AddObserver("RightButtonReleaseEvent", self.OnZoomRightRelease) | |
| 37 | + | |
| 38 | + def OnZoomRightMove(self, evt, obj): | |
| 39 | + if (self.right_pressed): | |
| 40 | + evt.Dolly() | |
| 41 | + evt.OnRightButtonDown() | |
| 42 | + | |
| 43 | + def OnZoomRightClick(self, evt, obj): | |
| 44 | + self.right_pressed = 1 | |
| 45 | + evt.StartDolly() | |
| 46 | + | |
| 47 | + def OnZoomRightRelease(self, evt, obj): | |
| 48 | + self.right_pressed = False | |
| 49 | + | |
| 50 | +class CrossInteractorStyle(ZoomInteractorStyle): | |
| 51 | + """ | |
| 52 | + Interactor style responsible for the Cross. | |
| 53 | + """ | |
| 54 | + def __init__(self, orientation, slice_data): | |
| 55 | + ZoomInteractorStyle.__init__(self) | |
| 56 | + | |
| 57 | + self.orientation = orientation | |
| 58 | + self.slice_actor = slice_data.actor | |
| 59 | + self.slice_data = slice_data | |
| 60 | + | |
| 61 | + self.left_pressed = False | |
| 62 | + self.picker = vtk.vtkWorldPointPicker() | |
| 63 | + | |
| 64 | + self.AddObserver("MouseMoveEvent", self.OnCrossMove) | |
| 65 | + self.AddObserver("LeftButtonPressEvent", self.OnCrossMouseClick) | |
| 66 | + self.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | |
| 67 | + | |
| 68 | + def OnCrossMouseClick(self, obj, evt): | |
| 69 | + self.left_pressed = True | |
| 70 | + iren = obj.GetInteractor() | |
| 71 | + self.ChangeCrossPosition(iren) | |
| 72 | + | |
| 73 | + def OnCrossMove(self, obj, evt): | |
| 74 | + # The user moved the mouse with left button pressed | |
| 75 | + if self.left_pressed: | |
| 76 | + print "OnCrossMove interactor style" | |
| 77 | + iren = obj.GetInteractor() | |
| 78 | + self.ChangeCrossPosition(iren) | |
| 79 | + | |
| 80 | + def OnReleaseLeftButton(self, obj, evt): | |
| 81 | + self.left_pressed = False | |
| 82 | + | |
| 83 | + def ChangeCrossPosition(self, iren): | |
| 84 | + mouse_x, mouse_y = iren.GetEventPosition() | |
| 85 | + ren = iren.GetRenderWindow().GetRenderers().GetFirstRenderer() | |
| 86 | + self.picker.Pick(mouse_x, mouse_y, 0, ren) | |
| 87 | + | |
| 88 | + # Get in what slice data the click occurred | |
| 89 | + # pick to get click position in the 3d world | |
| 90 | + coord_cross = self.get_coordinate_cursor() | |
| 91 | + position = self.slice_actor.GetInput().FindPoint(coord_cross) | |
| 92 | + # Forcing focal point to be setted in the center of the pixel. | |
| 93 | + coord_cross = self.slice_actor.GetInput().GetPoint(position) | |
| 94 | + | |
| 95 | + coord = self.calcultate_scroll_position(position) | |
| 96 | + self.ScrollSlice(coord) | |
| 97 | + | |
| 98 | + Publisher.sendMessage('Update cross position', coord_cross) | |
| 99 | + Publisher.sendMessage('Set ball reference position based on bound', | |
| 100 | + coord_cross) | |
| 101 | + Publisher.sendMessage('Set camera in volume', coord_cross) | |
| 102 | + Publisher.sendMessage('Render volume viewer') | |
| 103 | + | |
| 104 | + iren.Render() | |
| 105 | + | |
| 106 | + def get_coordinate_cursor(self): | |
| 107 | + # Find position | |
| 108 | + x, y, z = self.picker.GetPickPosition() | |
| 109 | + bounds = self.slice_actor.GetBounds() | |
| 110 | + if bounds[0] == bounds[1]: | |
| 111 | + x = bounds[0] | |
| 112 | + elif bounds[2] == bounds[3]: | |
| 113 | + y = bounds[2] | |
| 114 | + elif bounds[4] == bounds[5]: | |
| 115 | + z = bounds[4] | |
| 116 | + return x, y, z | |
| 117 | + | |
| 118 | + def calcultate_scroll_position(self, position): | |
| 119 | + # Based in the given coord (x, y, z), returns a list with the scroll positions for each | |
| 120 | + # orientation, being the first position the sagital, second the coronal | |
| 121 | + # and the last, axial. | |
| 122 | + | |
| 123 | + if self.orientation == 'AXIAL': | |
| 124 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | |
| 125 | + axial = self.slice_data.number | |
| 126 | + coronal = position / image_width | |
| 127 | + sagital = position % image_width | |
| 128 | + | |
| 129 | + elif self.orientation == 'CORONAL': | |
| 130 | + image_width = self.slice_actor.GetInput().GetDimensions()[0] | |
| 131 | + axial = position / image_width | |
| 132 | + coronal = self.slice_data.number | |
| 133 | + sagital = position % image_width | |
| 134 | + | |
| 135 | + elif self.orientation == 'SAGITAL': | |
| 136 | + image_width = self.slice_actor.GetInput().GetDimensions()[1] | |
| 137 | + axial = position / image_width | |
| 138 | + coronal = position % image_width | |
| 139 | + sagital = self.slice_data.number | |
| 140 | + | |
| 141 | + return sagital, coronal, axial | |
| 142 | + | |
| 143 | + def ScrollSlice(self, coord): | |
| 144 | + if self.orientation == "AXIAL": | |
| 145 | + Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | |
| 146 | + coord[0]) | |
| 147 | + Publisher.sendMessage(('Set scroll position', 'CORONAL'), | |
| 148 | + coord[1]) | |
| 149 | + elif self.orientation == "SAGITAL": | |
| 150 | + Publisher.sendMessage(('Set scroll position', 'AXIAL'), | |
| 151 | + coord[2]) | |
| 152 | + Publisher.sendMessage(('Set scroll position', 'CORONAL'), | |
| 153 | + coord[1]) | |
| 154 | + elif self.orientation == "CORONAL": | |
| 155 | + Publisher.sendMessage(('Set scroll position', 'AXIAL'), | |
| 156 | + coord[2]) | |
| 157 | + Publisher.sendMessage(('Set scroll position', 'SAGITAL'), | |
| 158 | + coord[0]) | |
| 159 | + | |
| 160 | + | |
| 24 | 161 | class ViewerStyle: |
| 25 | 162 | def __init__(self): |
| 26 | 163 | self.interactor = None | ... | ... |
invesalius/data/viewer_slice.py
| ... | ... | @@ -27,6 +27,8 @@ import numpy |
| 27 | 27 | import vtk |
| 28 | 28 | from vtk.wx.wxVTKRenderWindowInteractor import wxVTKRenderWindowInteractor |
| 29 | 29 | |
| 30 | +import styles | |
| 31 | + | |
| 30 | 32 | import wx |
| 31 | 33 | from wx.lib.pubsub import pub as Publisher |
| 32 | 34 | |
| ... | ... | @@ -173,127 +175,142 @@ class Viewer(wx.Panel): |
| 173 | 175 | interactor.SetInteractorStyle(style) |
| 174 | 176 | |
| 175 | 177 | def SetInteractorStyle(self, state): |
| 176 | - self.state = state | |
| 177 | - action = {const.SLICE_STATE_CROSS: | |
| 178 | - { | |
| 179 | - "MouseMoveEvent": self.OnCrossMove, | |
| 180 | - "LeftButtonPressEvent": self.OnCrossMouseClick, | |
| 181 | - }, | |
| 182 | - const.SLICE_STATE_EDITOR: | |
| 183 | - { | |
| 184 | - "MouseMoveEvent": self.OnBrushMove, | |
| 185 | - "LeftButtonPressEvent": self.OnBrushClick, | |
| 186 | - "LeftButtonReleaseEvent": self.OnBrushRelease, | |
| 187 | - "EnterEvent": self.OnEnterInteractor, | |
| 188 | - "LeaveEvent": self.OnLeaveInteractor | |
| 189 | - }, | |
| 190 | - const.STATE_PAN: | |
| 191 | - { | |
| 192 | - "MouseMoveEvent": self.OnPanMove, | |
| 193 | - "LeftButtonPressEvent": self.OnPanClick, | |
| 194 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 195 | - }, | |
| 196 | - const.STATE_SPIN: | |
| 197 | - { | |
| 198 | - "MouseMoveEvent": self.OnSpinMove, | |
| 199 | - "LeftButtonPressEvent": self.OnSpinClick, | |
| 200 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 201 | - }, | |
| 202 | - const.STATE_ZOOM: | |
| 203 | - { | |
| 204 | - "MouseMoveEvent": self.OnZoomMoveLeft, | |
| 205 | - "LeftButtonPressEvent": self.OnZoomLeftClick, | |
| 206 | - "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 207 | - }, | |
| 208 | - const.SLICE_STATE_SCROLL: | |
| 209 | - { | |
| 210 | - "MouseMoveEvent": self.OnChangeSliceMove, | |
| 211 | - "LeftButtonPressEvent": self.OnChangeSliceClick, | |
| 212 | - }, | |
| 213 | - const.STATE_WL: | |
| 214 | - { | |
| 215 | - "MouseMoveEvent": self.OnWindowLevelMove, | |
| 216 | - "LeftButtonPressEvent": self.OnWindowLevelClick, | |
| 217 | - }, | |
| 218 | - const.STATE_DEFAULT: | |
| 219 | - { | |
| 220 | - }, | |
| 221 | - const.STATE_MEASURE_DISTANCE: | |
| 222 | - { | |
| 223 | - "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint | |
| 224 | - }, | |
| 225 | - const.STATE_MEASURE_ANGLE: | |
| 226 | - { | |
| 227 | - "LeftButtonPressEvent": self.OnInsertAngularMeasurePoint | |
| 228 | - }, | |
| 229 | - } | |
| 178 | + if state == const.SLICE_STATE_CROSS: | |
| 179 | + style = styles.CrossInteractorStyle(self.orientation, | |
| 180 | + self.slice_data) | |
| 181 | + self.style = style | |
| 182 | + self.interactor.SetInteractorStyle(style) | |
| 183 | + self.interactor.Render() | |
| 230 | 184 | |
| 185 | + ## Zoom using right button | |
| 186 | + #style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | |
| 187 | + #style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | |
| 188 | + #style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | |
| 189 | + | |
| 190 | + #Scroll change slice | |
| 191 | + style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | |
| 192 | + style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | |
| 231 | 193 | |
| 232 | - if state == const.SLICE_STATE_CROSS: | |
| 233 | 194 | self.__set_cross_visibility(1) |
| 234 | 195 | Publisher.sendMessage('Activate ball reference') |
| 235 | 196 | else: |
| 236 | - self.__set_cross_visibility(0) | |
| 237 | - Publisher.sendMessage('Deactivate ball reference') | |
| 197 | + self.state = state | |
| 198 | + action = { | |
| 199 | + const.SLICE_STATE_EDITOR: | |
| 200 | + { | |
| 201 | + "MouseMoveEvent": self.OnBrushMove, | |
| 202 | + "LeftButtonPressEvent": self.OnBrushClick, | |
| 203 | + "LeftButtonReleaseEvent": self.OnBrushRelease, | |
| 204 | + "EnterEvent": self.OnEnterInteractor, | |
| 205 | + "LeaveEvent": self.OnLeaveInteractor | |
| 206 | + }, | |
| 207 | + const.STATE_PAN: | |
| 208 | + { | |
| 209 | + "MouseMoveEvent": self.OnPanMove, | |
| 210 | + "LeftButtonPressEvent": self.OnPanClick, | |
| 211 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 212 | + }, | |
| 213 | + const.STATE_SPIN: | |
| 214 | + { | |
| 215 | + "MouseMoveEvent": self.OnSpinMove, | |
| 216 | + "LeftButtonPressEvent": self.OnSpinClick, | |
| 217 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 218 | + }, | |
| 219 | + const.STATE_ZOOM: | |
| 220 | + { | |
| 221 | + "MouseMoveEvent": self.OnZoomMoveLeft, | |
| 222 | + "LeftButtonPressEvent": self.OnZoomLeftClick, | |
| 223 | + "LeftButtonReleaseEvent": self.OnVtkRightRelease | |
| 224 | + }, | |
| 225 | + const.SLICE_STATE_SCROLL: | |
| 226 | + { | |
| 227 | + "MouseMoveEvent": self.OnChangeSliceMove, | |
| 228 | + "LeftButtonPressEvent": self.OnChangeSliceClick, | |
| 229 | + }, | |
| 230 | + const.STATE_WL: | |
| 231 | + { | |
| 232 | + "MouseMoveEvent": self.OnWindowLevelMove, | |
| 233 | + "LeftButtonPressEvent": self.OnWindowLevelClick, | |
| 234 | + }, | |
| 235 | + const.STATE_DEFAULT: | |
| 236 | + { | |
| 237 | + }, | |
| 238 | + const.STATE_MEASURE_DISTANCE: | |
| 239 | + { | |
| 240 | + "LeftButtonPressEvent": self.OnInsertLinearMeasurePoint | |
| 241 | + }, | |
| 242 | + const.STATE_MEASURE_ANGLE: | |
| 243 | + { | |
| 244 | + "LeftButtonPressEvent": self.OnInsertAngularMeasurePoint | |
| 245 | + }, | |
| 246 | + } | |
| 247 | + | |
| 248 | + | |
| 249 | + if state == const.SLICE_STATE_CROSS: | |
| 250 | + self.__set_cross_visibility(1) | |
| 251 | + Publisher.sendMessage('Activate ball reference') | |
| 252 | + else: | |
| 253 | + self.__set_cross_visibility(0) | |
| 254 | + Publisher.sendMessage('Deactivate ball reference') | |
| 238 | 255 | |
| 239 | - if state == const.STATE_WL: | |
| 240 | - self.on_wl = True | |
| 241 | - self.wl_text.Show() | |
| 242 | - else: | |
| 243 | - self.on_wl = False | |
| 244 | - self.wl_text.Hide() | |
| 256 | + if state == const.STATE_WL: | |
| 257 | + self.on_wl = True | |
| 258 | + self.wl_text.Show() | |
| 259 | + else: | |
| 260 | + self.on_wl = False | |
| 261 | + self.wl_text.Hide() | |
| 245 | 262 | |
| 246 | - self.__set_editor_cursor_visibility(0) | |
| 247 | - | |
| 248 | - # Bind method according to current mode | |
| 249 | - if(state == const.STATE_ZOOM_SL): | |
| 250 | - style = vtk.vtkInteractorStyleRubberBandZoom() | |
| 263 | + self.__set_editor_cursor_visibility(0) | |
| 264 | + | |
| 265 | + # Bind method according to current mode | |
| 266 | + if(state == const.STATE_ZOOM_SL): | |
| 267 | + style = vtk.vtkInteractorStyleRubberBandZoom() | |
| 251 | 268 | |
| 252 | - style.AddObserver("RightButtonPressEvent", self.QuitRubberBandZoom) | |
| 253 | - #style.AddObserver("RightButtonPressEvent", self.EnterRubberBandZoom) | |
| 269 | + style.AddObserver("RightButtonPressEvent", self.QuitRubberBandZoom) | |
| 270 | + #style.AddObserver("RightButtonPressEvent", self.EnterRubberBandZoom) | |
| 254 | 271 | |
| 255 | - else: | |
| 256 | - style = vtk.vtkInteractorStyleImage() | |
| 257 | - | |
| 258 | - # Check each event available for each state | |
| 259 | - for event in action[state]: | |
| 260 | - # Bind event | |
| 261 | - style.AddObserver(event, | |
| 262 | - action[state][event]) | |
| 263 | - | |
| 264 | - # Common to all styles | |
| 265 | - # Mouse Buttons' presses / releases | |
| 266 | - style.AddObserver("LeftButtonPressEvent", self.OnLeftClick) | |
| 267 | - style.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | |
| 268 | - style.AddObserver("RightButtonPressEvent", self.OnRightClick) | |
| 269 | - style.AddObserver("RightButtonReleaseEvent", self.OnReleaseRightButton) | |
| 270 | - | |
| 271 | - # Zoom using right button | |
| 272 | - style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | |
| 273 | - style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | |
| 274 | - style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | |
| 275 | - | |
| 276 | - #Scroll change slice | |
| 277 | - style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | |
| 278 | - style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | |
| 279 | - | |
| 280 | - if ((state == const.STATE_ZOOM) or (state == const.STATE_ZOOM_SL)): | |
| 281 | - self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | |
| 282 | - else: | |
| 283 | - self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnSpinPan) | |
| 272 | + else: | |
| 273 | + style = vtk.vtkInteractorStyleImage() | |
| 274 | + | |
| 275 | + # Check each event available for each state | |
| 276 | + for event in action[state]: | |
| 277 | + # Bind event | |
| 278 | + style.AddObserver(event, | |
| 279 | + action[state][event]) | |
| 280 | + | |
| 281 | + # Common to all styles | |
| 282 | + # Mouse Buttons' presses / releases | |
| 283 | + style.AddObserver("LeftButtonPressEvent", self.OnLeftClick) | |
| 284 | + style.AddObserver("LeftButtonReleaseEvent", self.OnReleaseLeftButton) | |
| 285 | + style.AddObserver("RightButtonPressEvent", self.OnRightClick) | |
| 286 | + style.AddObserver("RightButtonReleaseEvent", self.OnReleaseRightButton) | |
| 287 | + | |
| 288 | + # Zoom using right button | |
| 289 | + style.AddObserver("RightButtonPressEvent",self.OnZoomRightClick) | |
| 290 | + style.AddObserver("MouseMoveEvent", self.OnZoomMoveRight) | |
| 291 | + style.AddObserver("RightButtonReleaseEvent", self.OnVtkRightRelease) | |
| 292 | + | |
| 293 | + #Scroll change slice | |
| 294 | + style.AddObserver("MouseWheelForwardEvent",self.OnScrollForward) | |
| 295 | + style.AddObserver("MouseWheelBackwardEvent", self.OnScrollBackward) | |
| 296 | + | |
| 297 | + if ((state == const.STATE_ZOOM) or (state == const.STATE_ZOOM_SL)): | |
| 298 | + self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnZoom) | |
| 299 | + else: | |
| 300 | + self.interactor.Bind(wx.EVT_LEFT_DCLICK, self.OnUnSpinPan) | |
| 284 | 301 | |
| 285 | - # Measures are using vtkPropPicker because they need to get which actor | |
| 286 | - # was picked. | |
| 287 | - if state in (const.STATE_MEASURE_DISTANCE, const.STATE_MEASURE_ANGLE): | |
| 288 | - self.pick = vtk.vtkPropPicker() | |
| 289 | - self.interactor.SetPicker(self.pick) | |
| 290 | - else: | |
| 291 | - self.pick = vtk.vtkWorldPointPicker() | |
| 292 | - self.interactor.SetPicker(self.pick) | |
| 302 | + # Measures are using vtkPropPicker because they need to get which actor | |
| 303 | + # was picked. | |
| 304 | + if state in (const.STATE_MEASURE_DISTANCE, const.STATE_MEASURE_ANGLE): | |
| 305 | + self.pick = vtk.vtkPropPicker() | |
| 306 | + self.interactor.SetPicker(self.pick) | |
| 307 | + else: | |
| 308 | + self.pick = vtk.vtkWorldPointPicker() | |
| 309 | + self.interactor.SetPicker(self.pick) | |
| 293 | 310 | |
| 294 | - self.style = style | |
| 295 | - self.interactor.SetInteractorStyle(style) | |
| 296 | - self.interactor.Render() | |
| 311 | + self.style = style | |
| 312 | + self.interactor.SetInteractorStyle(style) | |
| 313 | + self.interactor.Render() | |
| 297 | 314 | |
| 298 | 315 | def QuitRubberBandZoom(self, evt, obj): |
| 299 | 316 | style = vtk.vtkInteractorStyleImage() |
| ... | ... | @@ -808,37 +825,6 @@ class Viewer(wx.Panel): |
| 808 | 825 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
| 809 | 826 | self._flush_buffer = False |
| 810 | 827 | |
| 811 | - def OnCrossMouseClick(self, evt, obj): | |
| 812 | - self.ChangeCrossPosition() | |
| 813 | - | |
| 814 | - def OnCrossMove(self, evt, obj): | |
| 815 | - # The user moved the mouse with left button pressed | |
| 816 | - if self.left_pressed: | |
| 817 | - self.ChangeCrossPosition() | |
| 818 | - | |
| 819 | - def ChangeCrossPosition(self): | |
| 820 | - mouse_x, mouse_y = self.interactor.GetEventPosition() | |
| 821 | - renderer = self.slice_data.renderer | |
| 822 | - self.pick.Pick(mouse_x, mouse_y, 0, renderer) | |
| 823 | - | |
| 824 | - # Get in what slice data the click occurred | |
| 825 | - # pick to get click position in the 3d world | |
| 826 | - coord_cross = self.get_coordinate_cursor() | |
| 827 | - position = self.slice_data.actor.GetInput().FindPoint(coord_cross) | |
| 828 | - # Forcing focal point to be setted in the center of the pixel. | |
| 829 | - coord_cross = self.slice_data.actor.GetInput().GetPoint(position) | |
| 830 | - | |
| 831 | - coord = self.calcultate_scroll_position(position) | |
| 832 | - self.ScrollSlice(coord) | |
| 833 | - | |
| 834 | - Publisher.sendMessage('Update cross position', coord_cross) | |
| 835 | - Publisher.sendMessage('Set ball reference position based on bound', | |
| 836 | - coord_cross) | |
| 837 | - Publisher.sendMessage('Set camera in volume', coord_cross) | |
| 838 | - Publisher.sendMessage('Render volume viewer') | |
| 839 | - | |
| 840 | - self.interactor.Render() | |
| 841 | - | |
| 842 | 828 | def Navigation(self, pubsub_evt): |
| 843 | 829 | # Get point from base change |
| 844 | 830 | x, y, z = pubsub_evt.data | ... | ... |