VTK更新多个渲染窗口的位置
我在写一个Python VTK应用的时候遇到了一点问题,想要同时运行多个渲染窗口。这个应用的目的是为了在两个不同的视角下渲染一个3D模型,适用于立体显示(也就是左视图和右视图)。但是,我在同时更新每个窗口的摄像头时遇到了麻烦。目前我有两个几乎一模一样的渲染管道,每个都有自己的vtkCamera
、vtkRenderWindow
、vtkRenderer
和vtkRenderWindowInteractor
,唯一的不同是右边的摄像头在X轴上移动了30个单位。
每个渲染窗口的交互器都是通过vtkRenderWindowInteractor.AddObserver()
方法来更新的,这个方法会调用一个简单的函数来重置摄像头到它们的原始位置和方向。最大的问题是,这个更新似乎只能在一个窗口上发生,特别是当前获得焦点的窗口。就好像交互器的计时器在失去焦点后就停止了一样。此外,当我按住鼠标(这样就可以移动摄像头)时,渲染的图像开始“漂移”,重置到一个越来越不正确的位置,尽管我已经把坐标硬编码到函数里了。
显然,我对VTK非常陌生,很多事情都让人困惑,因为很多细节都隐藏在后台,所以如果能得到一些帮助就太好了。我的代码在下面。谢谢大家!
from vtk import*
from parse import *
import os
import time, signal, threading
def ParseSIG(signum, stack):
print signum
return
class vtkGyroCallback():
def __init__(self):
pass
def execute(self, obj, event):
#Modified segment to accept input for leftCam position
gyro = (raw_input())
xyz = parse("{} {} {}", gyro)
#This still prints every 100ms, but camera doesn't update!
print xyz
#These arguments are updated and the call is made.
self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
self.leftCam.SetFocalPoint(0,0,0)
self.leftCam.SetViewUp(0,1,0)
self.leftCam.OrthogonalizeViewUp()
self.rightCam.SetPosition(10, 40, 100)
self.rightCam.SetFocalPoint(0,0,0)
self.rightCam.SetViewUp(0,1,0)
self.rightCam.OrthogonalizeViewUp()
#Just a guess
obj.Update()
return
def main():
# create two cameras
cameraR = vtkCamera()
cameraR.SetPosition(0,0,200)
cameraR.SetFocalPoint(0,0,0)
cameraL = vtkCamera()
cameraL.SetPosition(40,0,200)
cameraL.SetFocalPoint(0,0,0)
# create a rendering window and renderer
renR = vtkRenderer()
renR.SetActiveCamera(cameraR)
renL = vtkRenderer()
renL.SetActiveCamera(cameraL)
# create source
reader = vtkPolyDataReader()
path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
reader.SetFileName(path)
reader.Update()
# create render window
renWinR = vtkRenderWindow()
renWinR.AddRenderer(renR)
renWinR.SetWindowName("Right")
renWinL = vtkRenderWindow()
renWinL.AddRenderer(renL)
renWinL.SetWindowName("Left")
# create a render window interactor
irenR = vtkRenderWindowInteractor()
irenR.SetRenderWindow(renWinR)
irenL = vtkRenderWindowInteractor()
irenL.SetRenderWindow(renWinL)
# mapper
mapper = vtkPolyDataMapper()
mapper.SetInput(reader.GetOutput())
# actor
actor = vtkActor()
actor.SetMapper(mapper)
# assign actor to the renderer
renR.AddActor(actor)
renL.AddActor(actor)
# enable user interface interactor
renWinR.Render()
renWinL.Render()
irenR.Initialize()
irenL.Initialize()
#Create callback object for camera manipulation
cb = vtkGyroCallback()
cb.rightCam = cameraR
cb.leftCam = cameraL
renWinR.AddObserver('InteractionEvent', cb.execute)
renWinL.AddObserver('InteractionEvent', cb.execute)
irenR.AddObserver('TimerEvent', cb.execute)
irenL.AddObserver('TimerEvent', cb.execute)
timerIDR = irenR.CreateRepeatingTimer(100)
timerIDL = irenL.CreateRepeatingTimer(100)
irenR.Start()
irenL.Start()
if __name__ == '__main__':
main()
编辑:
经过进一步查看,似乎在鼠标点击事件后,计时器事件不会连续触发多次,我不知道为什么。
编辑2:算了,实际上它们肯定是触发的,我在代码中嵌入了一些测试输出。我修改了代码,让用户输入self.leftCam.SetPosition()
调用中的参数(把硬编码的“10, 40, 100”替换成三个输入变量),然后将一个简单的脚本的输出(显示三个随机值)传递到我的主程序中。这样做应该能让渲染窗口的位置不断变化。但实际上,什么都没有发生,直到我点击屏幕,这时预期的功能才开始。整个过程中,计时器事件仍然在触发,输入也在被接受,但摄像头就是不更新,直到在它们的窗口内发生鼠标事件。这是怎么回事?
编辑3:我又深入挖掘了一下,发现每次交互事件中调用的vtkObject::InvokeEvent()
方法里有一个焦点循环,它会覆盖所有与当前焦点对象无关的观察者。我打算调查一下是否有办法移除焦点,这样就可以绕过这个焦点循环,转而进入处理非焦点对象的循环。
1 个回答
这个解决方案其实很简单,但由于VTK提供的文档质量不高,我不得不自己翻阅源代码来找到答案。基本上,你只需要通过你用来处理TimerEvent
的回调方法,让每个交互器的Render()
调用变成伪线程。我是通过给每个交互器添加ID属性来实现的(下面的代码中可以看到)。你会发现,每当irenR
交互器的内部计时器触发TimerEvent
时(irenR
负责右眼),irenL
的Render()
函数就会被调用,反之亦然。
为了解决这个问题,我首先意识到标准的交互器功能(比如鼠标事件等)是正常工作的。于是我在vtkRenderWindowInteractor.cxx的源代码中查找,发现这些方法实际上是抽象到各个vtkInteractorStyle
实现中的。在查看vtkInteractorStyleTrackball.cxx的源代码时,我发现vtkRenderWindowInteractor
类中实际上有一个Render()
函数。真是让人意外!文档里可没提到这一点!
不过,不幸的是,同时进行两个渲染其实是非常慢的。如果我只用一个窗口来做这个方法(这时就变得没必要了),运行得很好。但是如果有第二个窗口,帧率就会下降。唉,这也没办法。
这是我修正后的代码(终于可以开始做我原本应该开发的东西了):
from vtk import*
from parse import *
import os
import time, signal, threading
def ParseSIG(signum, stack):
print signum
return
class vtkGyroCallback():
def __init__(self):
pass
def execute(self, obj, event):
#Modified segment to accept input for leftCam position
gyro = (raw_input())
xyz = parse("{} {} {}", gyro)
#print xyz
# "Thread" the renders. Left is called on a right TimerEvent and right is called on a left TimerEvent.
if obj.ID == 1 and event == 'TimerEvent':
self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
self.irenL.Render()
#print "Left"
elif obj.ID == 2 and event == 'TimerEvent':
self.rightCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
self.irenR.Render()
#print "Right"
return
def main():
# create two cameras
cameraR = vtkCamera()
cameraR.SetPosition(0,0,200)
cameraR.SetFocalPoint(0,0,0)
cameraL = vtkCamera()
cameraL.SetPosition(40,0,200)
cameraL.SetFocalPoint(0,0,0)
# create a rendering window and renderer
renR = vtkRenderer()
renR.SetActiveCamera(cameraR)
renL = vtkRenderer()
renL.SetActiveCamera(cameraL)
# create source
reader = vtkPolyDataReader()
path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
reader.SetFileName(path)
reader.Update()
# create render window
renWinR = vtkRenderWindow()
renWinR.AddRenderer(renR)
renWinR.SetWindowName("Right")
renWinL = vtkRenderWindow()
renWinL.AddRenderer(renL)
renWinL.SetWindowName("Left")
# create a render window interactor
irenR = vtkRenderWindowInteractor()
irenR.SetRenderWindow(renWinR)
irenL = vtkRenderWindowInteractor()
irenL.SetRenderWindow(renWinL)
# mapper
mapper = vtkPolyDataMapper()
mapper.SetInput(reader.GetOutput())
# actor
actor = vtkActor()
actor.SetMapper(mapper)
# assign actor to the renderer
renR.AddActor(actor)
renL.AddActor(actor)
# enable user interface interactor
renWinR.Render()
renWinL.Render()
irenR.Initialize()
irenL.Initialize()
#Create callback object for camera manipulation
cb = vtkGyroCallback()
cb.rightCam = renR.GetActiveCamera()#cameraR
cb.leftCam = renL.GetActiveCamera()#cameraL
cb.irenR = irenR
cb.irenL = irenL
irenR.ID = 1
irenL.ID = 2
irenR.AddObserver('TimerEvent', cb.execute)
irenL.AddObserver('TimerEvent', cb.execute)
timerIDR = irenR.CreateRepeatingTimer(100)
timerIDL = irenL.CreateRepeatingTimer(100)
irenL.Start()
irenR.Start()
if __name__ == '__main__':
main()