[Python] Python VTK Repeated Point Picking Unexpected Behavior

Discussão em 'Python' iniciado por Stack, Março 21, 2025.

  1. Stack

    Stack Membro Participativo

    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?

    [​IMG]

    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...

Compartilhe esta Página