I am using pyQT5 and VTK to render meshes and select points. I have a custom interactor that I use to intercept the QT events and perform operations. For my application, I want to allow the user to use the standard CTRL+click behavior for selecting multiple points. I have implemented this to the best of my ability, however I have noticed that repeated clicking leads to unexpected behavior. All of a sudden the picker will start going in sequential order, regardless of the cursor position. In the below image, I clicked on the more center, red point (18), and, while holding CTRL, clicked again, and it selected point 0 without moving the cursor. Does the picker need to be reset? The code in full: import sys import numpy as np import vtk from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor from PyQt5 import QtCore, QtGui, QtWidgets # Subclass QVTKRenderWindowInteractor. This uses PyQT event handling # rather than vtk's to avoid clashes. class MyRenderWindowInteractor(QVTKRenderWindowInteractor): def __init__(self, parent, mygui): super().__init__(parent) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.mygui = mygui # Initialize the renderer and interactor self.renderer = vtk.vtkRenderer() self.interactor = self.GetRenderWindow().GetInteractor() # Booleans for mouse click events self.left_mouse_pressed = False self.middle_mouse_pressed = False self.right_mouse_pressed = False self.mouse_position = None self.mouse_pressed_position = None self.mouse_released_position = None # Initialize objects for selection self.selection_ids = vtk.vtkIdTypeArray() self.selection_ids.SetNumberOfComponents(1) self.selection_grid = vtk.vtkUnstructuredGrid() self.selection_node = vtk.vtkSelectionNode() self.selection_node.SetSelectionList(self.selection_ids) self.selection_node.SetFieldType(vtk.vtkSelectionNode.CELL) self.selection_node.SetContentType(vtk.vtkSelectionNode.INDICES) self.selection = vtk.vtkSelection() self.selection.AddNode(self.selection_node) self.selection_extractor = vtk.vtkExtractSelection() self.selection_data_mapper = vtk.vtkDataSetMapper() self.selection_data_mapper.SetInputData(self.selection_grid) # Initialize objects for point selection self.picked_point_id = -1 self.point_picker = vtk.vtkPointPicker() self.point_picker.UseCellsOn() self.selection_node_actor = vtk.vtkActor() self.selection_node_actor.SetMapper(self.selection_data_mapper) self.selection_node_actor.GetProperty().SetPointSize(5) self.selection_node_actor.GetProperty().SetColor(vtk.vtkNamedColors().GetColor3d("Red")) self.renderer.AddActor(self.selection_node_actor) self.selection_node.SetFieldType(vtk.vtkSelectionNode.POINT) self.selection_node.SetContentType(vtk.vtkSelectionNode.INDICES) # Override def mousePressEvent(self, event): # Overall Try/Except try: # Update mouse pressed position self.mouse_pressed_position = event.localPos() self.mouse_position = self.mouse_pressed_position # If the left button is pressed if event.button() == QtCore.Qt.LeftButton: # Update pressed boolean self.left_mouse_pressed = True # Get the position of the click world_pos = np.array(self.point_picker.GetPickPosition()) # Extract the point self.point_picker.Pick(event.localPos().x(), self.height() - event.localPos().y(), 0, self.renderer) picked_point_id = self.point_picker.GetPointId() print(picked_point_id) # If a point has been selectioned if picked_point_id != -1 and picked_point_id != self.picked_point_id: # Reset the list of selectioned points only if CTRL is not pressed if event.modifiers() != QtCore.Qt.ControlModifier: self.selection_ids.Initialize() # Reset items self.picked_point_id = picked_point_id # Copy the selectioned point to a new unstructred grid self.selection_ids.InsertNextValue(picked_point_id) self.selection.Modified() self.selection_extractor.SetInputData(0, self.mygui.mesh_grid) self.selection_extractor.SetInputData(1, self.selection) self.selection_extractor.Update() self.selection_grid.ShallowCopy(self.selection_extractor.GetOutput()) self.selection_grid.Modified() self.selection_data_mapper.Update() self.interactor.Render() # If the middle button is pressed elif event.button() == QtCore.Qt.MiddleButton: # Update pressed boolean self.middle_mouse_pressed = True # If the right button is pressed elif event.button() == QtCore.Qt.RightButton: # Update pressed boolean self.right_mouse_pressed = True # Print Error except Exception as error: print('MOUSE PRESSED EVENT ERROR: '+str(error)+'.') # Override def mouseReleaseEvent(self, event): # Overall Try/Except try: # Update mouse released position self.mouse_released_position = event.localPos() self.mouse_position = self.mouse_released_position # If the left button is released if event.button() == QtCore.Qt.LeftButton: # Update pressed boolean self.left_mouse_pressed = False # If the middle button is released elif event.button() == QtCore.Qt.MiddleButton: # Update pressed boolean self.middle_mouse_pressed = False # If the right button is released elif event.button() == QtCore.Qt.RightButton: # Update pressed boolean self.right_mouse_pressed = False # Print Error except Exception as error: print('MOUSE RELEASE EVENT ERROR: '+str(error)+'.') # Override def mouseMoveEvent(self, event): # If the middle mouse button is currently pressed, rotate if self.middle_mouse_pressed: # Get the old and new mouse positions on the display in vtk coordinates display_pos_old = QtCore.QPoint(int(self.mouse_position.x()), int(self.height() - self.mouse_position.y())) display_pos_new = QtCore.QPoint(int(event.localPos().x()), int(self.height() - event.localPos().y())) display_motion = display_pos_new - display_pos_old # Compute the new display focal position, and new world positions display_focal = np.array([0., 0., 0.]) world_pos_old = np.array([0., 0., 0., 0.]) world_pos_new = np.array([0., 0., 0., 0.]) world_pos = np.array(self.renderer.GetActiveCamera().GetPosition()) world_focal = np.array(self.renderer.GetActiveCamera().GetFocalPoint()) self.interactor.GetInteractorStyle().ComputeWorldToDisplay(self.renderer, world_focal[0], world_focal[1], world_focal[2], display_focal) self.interactor.GetInteractorStyle().ComputeDisplayToWorld(self.renderer, display_pos_old.x(), display_pos_old.y(), display_focal[2], world_pos_old) self.interactor.GetInteractorStyle().ComputeDisplayToWorld(self.renderer, display_pos_new.x(), display_pos_new.y(), display_focal[2], world_pos_new) world_motion = world_pos_old - world_pos_new # Rotate as appropriate if np.abs(display_motion.x()) > np.abs(display_motion.y()): self.renderer.GetActiveCamera().Azimuth(-2.0 * display_motion.x()) else: self.renderer.GetActiveCamera().Elevation(-1.0 * display_motion.y()) self.renderer.GetActiveCamera().OrthogonalizeViewUp() self.renderer.ResetCameraClippingRange() self.interactor.Render() # Reset mouse position to the latest event self.mouse_position = event.localPos() # If the right mouse button is currently pressed, pan if self.right_mouse_pressed: # Get the old and new mouse positions on the display display_pos_old = QtCore.QPoint(int(self.mouse_position.x()), int(self.height() - self.mouse_position.y())) display_pos_new = QtCore.QPoint(int(event.localPos().x()), int(self.height() - event.localPos().y())) display_motion = display_pos_new - display_pos_old # Compute the new display focal position, and new world positions display_focal = np.array([0., 0., 0.]) world_pos_old = np.array([0., 0., 0., 0.]) world_pos_new = np.array([0., 0., 0., 0.]) world_pos = np.array(self.renderer.GetActiveCamera().GetPosition()) world_focal = np.array(self.renderer.GetActiveCamera().GetFocalPoint()) self.interactor.GetInteractorStyle().ComputeWorldToDisplay(self.renderer, world_focal[0], world_focal[1], world_focal[2], display_focal) self.interactor.GetInteractorStyle().ComputeDisplayToWorld(self.renderer, display_pos_old.x(), display_pos_old.y(), display_focal[2], world_pos_old) self.interactor.GetInteractorStyle().ComputeDisplayToWorld(self.renderer, display_pos_new.x(), display_pos_new.y(), display_focal[2], world_pos_new) world_motion = world_pos_old - world_pos_new # Pan self.renderer.GetActiveCamera().SetFocalPoint(*(world_focal[0:3] + world_motion[0:3])) self.renderer.GetActiveCamera().SetPosition(*(world_pos[0:3] + world_motion[0:3])) self.renderer.ResetCameraClippingRange() self.interactor.Render() # Reset mouse pressed position to the latest event self.mouse_position = event.localPos() class GUIForm(object): def setupUi(self, GUIForm): GUIForm.setObjectName("GUIForm") self.centralwidget = QtWidgets.QWidget(GUIForm) self.centralwidget.setObjectName("centralwidget") self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget) self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") GUIForm.setCentralWidget(self.centralwidget) self.retranslateUi(GUIForm) QtCore.QMetaObject.connectSlotsByName(GUIForm) # Add vtk window, renderer, and interactor self.frame_vtk = QtWidgets.QFrame() self.vtk_window = MyRenderWindowInteractor(self.frame_vtk, self) self.vtk_window.setMinimumWidth(30) self.verticalLayout.addWidget(self.vtk_window) self.vtk_renderer = self.vtk_window.renderer self.vtk_window.GetRenderWindow().AddRenderer(self.vtk_renderer) self.vtk_interactor = self.vtk_window.GetRenderWindow().GetInteractor() self.vtk_interactor.Initialize() def retranslateUi(self, GUIForm): _translate = QtCore.QCoreApplication.translate class MyGUI(QtWidgets.QMainWindow, GUIForm): # Initialize def __init__(self, parent=None): # Call inherited class functions super(MyGUI, self).__init__(parent) self.setupUi(self) # Add the user style to the interactor and observers for button controls self.vtk_interactor.SetInteractorStyle(vtk.vtkInteractorStyleUser()) self.vtk_interactor.LightFollowCameraOff() # Create some points and cells self.mesh_grid = vtk.vtkUnstructuredGrid() self.mesh_grid.AllocateExact(25, 36) self.points = vtk.vtkPoints() self.points.SetNumberOfPoints(36) for i in range(0, 6): for j in range(0, 6): self.points.SetPoint(6*i+j, (float(i), float(j), 0.0)) self.mesh_grid.SetPoints(self.points) for i in range(0, 5): for j in range(0, 5): self.mesh_grid.InsertNextCell(vtk.VTK_QUAD,4,(6*i+j, 6*i+j+1, 6*(i+1)+j+1, 6*(i+1)+j)) # Setup mesh mapper and actor self.mesh_mapper = vtk.vtkDataSetMapper() if vtk.VTK_MAJOR_VERSION <= 5: self.mesh_mapper.SetInput(self.mesh_grid) else: self.mesh_mapper.SetInputData(self.mesh_grid) self.mesh_actor = vtk.vtkActor() self.mesh_actor.SetMapper(self.mesh_mapper) self.mesh_actor.GetProperty().EdgeVisibilityOn() self.vtk_renderer.AddActor(self.mesh_actor) self.vtk_renderer.ResetCamera() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) w = MyGUI() w.resize(900, 900) w.show() sys.exit(app.exec_()) Continue reading...