ymmx
ymmx

Reputation: 4967

How do I rotate or translate an object at the mouse position in a vtk environment?

I would like to rotate or translate an object that look like a cylinder by pressing the left button of the mouse on it.

to rotate, i would like to click on the tip and make it rotate around the other tip when I move the mouse (the left button still pressed). To translate I would like to press the 't' key and move the object according to the plan of the camera.

The rotation works but it is not smooth because as soon as the mouse leave the object, the orientation is not updated anymore. the translation seems to work but the position do not correspond exactly to the position of the mouse.

When the mouse left button is pressed, I save the object I click on. When I pressed a key, i also save the key to know the 't' key is pressed for translating.

In the MouseMove function, if self.DetectMouseMove: self.OnMouseMove() clickPos = self.GetInteractor().GetEventPosition()

        pickerActor = vtk.vtkPropPicker()
        pickerActor.PickProp(clickPos[0], clickPos[1], self.GetDefaultRenderer())
        # get the new
        NewPickedActor = pickerActor.GetAssembly()
        #
        if self.key == 't':
            elec = self.elec
            Aelectrode = self.parent.Electrode_aAssemblys[elec]
            electrode = self.parent.ElectrodeList[elec]

            coordinate = vtk.vtkCoordinate()
            coordinate.SetCoordinateSystemToView()
            coordinate.SetValue(self.prevpos[0], self.prevpos[1], 0.5)
            prevviewCoord = np.asarray(coordinate.GetComputedWorldValue(self.parent.ren))

            coordinate = vtk.vtkCoordinate()
            coordinate.SetCoordinateSystemToView()
            coordinate.SetValue(clickPos[0], clickPos[1], 0.5)
            click3DviewCoord = np.asarray(coordinate.GetComputedWorldValue(self.parent.ren))

            a = (click3DviewCoord - prevviewCoord) / 300

            position = Aelectrode.GetPosition()
            Aelectrode.SetPosition(position[0] + a[0], position[1] + a[1], position[2] + a[2])

How can I position the object directly under the mouse?.

The second problem I have is that the rotation stops when the mouse leave the object. Is there a way to make the rotation even if the mouse is not on the vtkassembly object?

here is a screenshot

enter image description here

When I click on the cylinder tip (the blue one), I enter in the MouseMove(self, obj, event) function. I don't know how to apply a rotation on the Aelectrode object with repect to the mouse position self.GetInteractor().GetEventPosition() and the previuous mouse position self.prevpos

Here the full code I have for now:

from PyQt6.QtGui import *
from PyQt6.QtCore import *
from PyQt6.QtWidgets import *
import numpy as np
import sys
import vtk
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor


class Mesh_sEEGElectrodeView(QMainWindow):
    def __init__(self, parent=None):
        super(Mesh_sEEGElectrodeView, self).__init__()
        self.parent = parent
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        self.mainHBOX_param_scene = QVBoxLayout()
        self.MV_plot = VTK_sEEGElectrodeView(self)
        self.mainHBOX_param_scene.addWidget(self.MV_plot)
        self.centralWidget.setLayout(self.mainHBOX_param_scene)

        self.ElectrodeList = []
        self.ElectrodeList.append(Electrode())
        self.Display_parcellisation()

    def Display_parcellisation(self):
        self.MV_plot.draw_Mesh(self.ElectrodeList)


class Electrode():
    def __init__(self, NumOfSensors=15, Diameter=0.8, Resolution=36):
        self.NOS = NumOfSensors
        self.Diameter = Diameter
        self.Resolution = Resolution

    def Create(self):
        SensorLength = 2
        mm = 1
        self.cylActors = []

        # design tube holding electrodes
        tube = vtk.vtkCylinderSource()
        tube.SetHeight(((self.NOS * 2 + (self.NOS - 1) * 1.5) - 0.1) * mm)
        tube.SetCenter(0, (self.NOS * 2 + (self.NOS - 1) * 1.5) * mm / 2, 0)
        tube.SetRadius((self.Diameter - 0.05) * mm / 2)
        tube.SetResolution(self.Resolution)
        tubePoly = tube.GetOutput()
        # tubePoly.Update()
        tube.Update()
        TubeMT = vtk.vtkPolyDataMapper()
        TubeMT.SetInputData(tubePoly)
        TubeMT.Update()
        # TubeMT.GlobalImmediateModeRenderingOn()
        TubeA = vtk.vtkLODActor()
        TubeA.VisibilityOn()
        TubeA.SetMapper(TubeMT)
        TubeA.GetProperty().SetColor(1, 0, 0)
        self.cylActors.append(TubeA)

        # create the Electrodes
        for i in range(self.NOS):
            # create cylinder
            cyl = vtk.vtkCylinderSource()
            cyl.SetHeight(SensorLength * mm)
            cyl.SetCenter(0, (1 + i * (2 + 1.5)) * mm, 0)
            cyl.SetRadius(self.Diameter / 2 * mm)
            cyl.SetResolution(self.Resolution)
            cyl.Update()
            cMT = vtk.vtkPolyDataMapper()
            cMT.SetInputData(cyl.GetOutput())
            cA = vtk.vtkActor()
            cA.VisibilityOn()
            cA.SetMapper(cMT)
            if i == 0:
                cA.GetProperty().SetColor(0, 1, 0)
            else:
                cA.GetProperty().SetColor(0, 0, 1)
            self.cylActors.append(cA)
        return self.cylActors


class VTK_sEEGElectrodeView(QMainWindow):
    def __init__(self, parent=None):
        super(VTK_sEEGElectrodeView, self).__init__(parent)
        self.parent = parent
        self.frame = QFrame()
        self.vl = QVBoxLayout()
        self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
        self.vl.addWidget(self.vtkWidget)

        self.ren = vtk.vtkRenderer()
        self.ren.SetBackground(.1, .1, .1)
        # self.ren.SetUseDepthPeeling(1)
        # self.ren.SetMaximumNumberOfPeels(100)
        self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
        self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
        # style = vtk.vtkInteractorStyleTrackballCamera()
        self.style1 = MouseInteractorHighLightActor_sEEGElectrode(self)
        self.style1.SetDefaultRenderer(self.ren)
        self.iren.SetInteractorStyle(self.style1)

        self.ren.ResetCamera()

        self.frame.setLayout(self.vl)
        self.setCentralWidget(self.frame)

        self.show()
        self.iren.Initialize()
        self.iren.Start()

    def draw_Mesh(self, ElectrodeList, smoothing=0):
        self.ren.RemoveAllViewProps()
        self.ElectrodeList = ElectrodeList
        self.Electrode_a = []
        self.Electrode_aAssemblys = []

        for i in range(len(self.ElectrodeList)):
            self.Electrode_a.append(self.ElectrodeList[i].Create())
            actAssembly = vtk.vtkAssembly()
            for j in range(self.ElectrodeList[i].NOS + 1):
                actAssembly.AddPart(self.Electrode_a[i][j])
            actAssembly.id = i
            n = [0, 50, 0]
            actAssembly.SetOrientation(np.degrees(np.arctan(n[2] / (n[1] + 0.000001))), 0,
                                       np.degrees(np.arctan(n[0] / (n[1] + 0.000001))))
            actAssembly.SetPickable(True)
            self.Electrode_aAssemblys.append(actAssembly)
            self.ren.AddActor(self.Electrode_aAssemblys[i])

        for i in range(10):
            source = vtk.vtkSphereSource()
            source.SetCenter(np.random.randint(-50, 50, 1), np.random.randint(-50, 50, 1),
                             np.random.randint(-50, 50, 1))
            source.SetRadius(3.0)
            mapper = vtk.vtkPolyDataMapper()
            mapper.SetInputConnection(source.GetOutputPort())
            actor = vtk.vtkActor()
            actor.SetMapper(mapper)
            self.ren.AddActor(actor)
        self.ren.ResetCamera()
        self.iren.ReInitialize()
        self.iren.GetRenderWindow().Render()
        self.ren.Render()


class MouseInteractorHighLightActor_sEEGElectrode(vtk.vtkInteractorStyleTrackballCamera):
    def __init__(self, parent=None):
        self.parent = parent

        self.AddObserver("KeyPressEvent", self.keyPressEvent)
        self.AddObserver("KeyReleaseEvent", self.KeyReleaseEvent)

        self.AddObserver('LeftButtonPressEvent', self.MouseMoveOK)
        self.AddObserver('LeftButtonReleaseEvent', self.MouseMoveNot)
        self.AddObserver("MouseMoveEvent", self.MouseMove)

        self.DetectMouseMove = 0
        self.prevpos = [0, 0]
        self.elec = 0
        self.key = None

    def keyPressEvent(self, obj, event):
        self.key = obj.GetInteractor().GetKeySym()

    def KeyReleaseEvent(self, obj, event):
        self.key = None

    def MouseMove(self, obj, event):
        if self.DetectMouseMove:
            self.OnMouseMove()
            clickPos = self.GetInteractor().GetEventPosition()

            pickerActor = vtk.vtkPropPicker()
            pickerActor.PickProp(clickPos[0], clickPos[1], self.GetDefaultRenderer())
            # get the new
            NewPickedActor = pickerActor.GetAssembly()
            #
            if self.key == 't':
                elec = self.elec
                Aelectrode = self.parent.Electrode_aAssemblys[elec]
                electrode = self.parent.ElectrodeList[elec]

                coordinate = vtk.vtkCoordinate()
                coordinate.SetCoordinateSystemToView()
                coordinate.SetValue(self.prevpos[0], self.prevpos[1], 0.5)
                prevviewCoord = np.asarray(coordinate.GetComputedWorldValue(self.parent.ren))

                coordinate = vtk.vtkCoordinate()
                coordinate.SetCoordinateSystemToView()
                coordinate.SetValue(clickPos[0], clickPos[1], 0.5)
                click3DviewCoord = np.asarray(coordinate.GetComputedWorldValue(self.parent.ren))

                a = (click3DviewCoord - prevviewCoord) / 300

                position = Aelectrode.GetPosition()
                Aelectrode.SetPosition(position[0] + a[0], position[1] + a[1], position[2] + a[2])


            else:
                if NewPickedActor:
                    if NewPickedActor in self.parent.Electrode_aAssemblys:
                        elec = self.elec
                        Aelectrode = self.parent.Electrode_aAssemblys[elec]

                        dest = np.asarray(pickerActor.GetPickPosition())
                        Origine = np.asarray(Aelectrode.GetPosition())

                        org = np.asarray(Aelectrode.GetParts().GetLastProp3D().GetCenter())
                        dest_E_corrd = dest - Origine

                        def rotation_matrix_from_vectors(vec1, vec2):
                            a, b = (vec1 / np.linalg.norm(vec1)).reshape(3), (vec2 / np.linalg.norm(vec2)).reshape(3)
                            v = np.cross(a, b)
                            c = np.dot(a, b)
                            s = np.linalg.norm(v)
                            kmat = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]])
                            rotation_matrix = np.eye(3) + kmat + kmat.dot(kmat) * ((1 - c) / (s ** 2))
                            return rotation_matrix

                        RotMat = rotation_matrix_from_vectors(org, dest_E_corrd)
                        vec1_rot = RotMat.dot(org)
                        vec1_rot = np.zeros((4, 4))
                        vec1_rot[:3, :3] = RotMat
                        vec1_rot[3, 3] = 1
                        vec1_rot[:3, -1] = Origine
                        rot = vtk.vtkMatrix4x4()
                        for i in range(0, 4):
                            for j in range(0, 4):
                                rot.SetElement(i, j, vec1_rot[i, j])
                        Aelectrode.PokeMatrix(rot)

            self.GetInteractor().GetRenderWindow().Render()
            self.prevpos = clickPos
        else:
            self.OnMouseMove()

    def MouseMoveOK(self, obj, event):
        clickPos = self.GetInteractor().GetEventPosition()
        pickerActor = vtk.vtkPropPicker()
        pickerActor.PickProp(clickPos[0], clickPos[1], self.GetDefaultRenderer())
        self.prevpos = clickPos
        print(self.prevpos)
        pickerActor = vtk.vtkPropPicker()
        pickerActor.PickProp(clickPos[0], clickPos[1], self.GetDefaultRenderer())
        # get the new
        NewPickedActor = pickerActor.GetAssembly()
        if NewPickedActor:
            if NewPickedActor in self.parent.Electrode_aAssemblys:
                self.DetectMouseMove = 1
                self.elec = NewPickedActor.id
                self.parent.ElectrodeList[self.elec].cylActors[-1].GetProperty().SetColor(1, 1, 0)



        else:
            self.OnLeftButtonDown()

    def MouseMoveNot(self, obj, event):
        self.DetectMouseMove = 0
        self.parent.ElectrodeList[self.elec].cylActors[-1].GetProperty().SetColor(0, 0, 1)
        self.OnLeftButtonUp()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Mesh_sEEGElectrodeView()
    window.show()
    sys.exit(app.exec())

Upvotes: 0

Views: 158

Answers (0)

Related Questions