vtkRenderWindowInteractor 阻塞程序
我正在用Python开发一个原型应用,目的是通过VTK实现3D模型的立体成像,但在界面方面遇到了一些问题。下面这段代码的目标是,当按下中间鼠标键时,两个渲染窗口都能放大。然而,当我调用vtkRenderWindowInteractor.Start()
这个函数时,我的vtkRenderWindowInteractor
似乎让整个程序停滞,就好像它们在同一个线程中运行一样。更奇怪的是,当我在UNIX终端中使用CTRL-C时,并没有出现键盘中断,直到我手动关闭渲染窗口,点击'x'按钮。如果我只是手动关闭窗口,而没有按CTRL-C,程序会在Start()
调用后直接继续运行(比如下面的代码中的无限循环)。我在这篇帖子最后提供了一些屏幕截图,以便更清楚地展示发生了什么,以防我的解释让人困惑。
我尝试了多种解决方法,但到目前为止都没有成功。把渲染放到独立线程中并没有改变情况,即使我尝试使用ncurses进行输入,而将它们分叉到新进程中又导致了一些我不想处理的操作系统问题。目前使用的交互器样式方法(如下所示),我只是用内置的VTK监听器,这在一定程度上有效,允许我在窗口聚焦且交互器活跃时检测输入,但由于相机和MyInteractorStyle
类之间缺乏关联,我实际上无法访问相机,除非在Start()
调用后加一个循环,这又让我回到了最开始的问题。
有没有什么想法?我是不是误解了VTK的渲染工具应该怎么用?
from vtk import*
import os.path
#import thread
#import time
#import threading
#import curses
class MyInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
pos1 = [0, 0, 200]
foc1 = [0, 0, 0]
pos2 = [40, 0, 200]
foc2 = [0, 0, 0]
def __init__(self,parent=None):
self.AddObserver("MiddleButtonPressEvent", self.middleButtonPressEvent)
self.AddObserver("MiddleButtonReleaseEvent", self.middleButtonReleaseEvent)
def middleButtonPressEvent(self,obj,event):
print "Middle button pressed"
self.pos1[2] += 10
self.pos2[2] += 30
self.OnMiddleButtonDown()
return
def middleButtonReleaseEvent(self,obj,event):
print "Middle button released"
self.OnMiddleButtonUp()
return
def main():
# create two cameras
camera1 = vtkCamera()
camera1.SetPosition(0,0,200)
camera1.SetFocalPoint(0,0,0)
camera2 = vtkCamera()
camera2.SetPosition(40,0,200)
camera2.SetFocalPoint(0,0,0)
# create a rendering window and renderer
ren1 = vtkRenderer()
ren1.SetActiveCamera(camera1)
ren2 = vtkRenderer()
ren2.SetActiveCamera(camera2)
# create source
reader = vtkPolyDataReader()
path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
reader.SetFileName(path)
print(path)
reader.Update()
# create render window
renWin1 = vtkRenderWindow()
renWin1.AddRenderer(ren1)
renWin2 = vtkRenderWindow()
renWin2.AddRenderer(ren2)
# create a render window interactor
inputHandler = MyInteractorStyle()
iren1 = vtkRenderWindowInteractor()
iren1.SetRenderWindow(renWin1)
iren1.SetInteractorStyle(inputHandler)
iren2 = vtkRenderWindowInteractor()
iren2.SetRenderWindow(renWin2)
iren2.SetInteractorStyle(inputHandler)
# mapper
mapper = vtkPolyDataMapper()
mapper.SetInput(reader.GetOutput())
# actor
actor = vtkActor()
actor.SetMapper(mapper)
# assign actor to the renderer
ren1.AddActor(actor)
ren2.AddActor(actor)
# enable user interface interactor
iren1.Initialize()
iren2.Initialize()
renWin1.Render()
renWin2.Render()
iren1.Start()
iren2.Start()
print "Test"
while 1:
pos1 = iren1.GetInteractorStyle().pos1
foc1 = iren1.GetInteractorStyle().foc1
pos2 = iren2.GetInteractorStyle().pos2
foc2 = iren2.GetInteractorStyle().foc2
print
if __name__ == '__main__':
main()
程序运行中
键盘中断(CTRL-C被按下并在终端中回显,但没有反应)
手动关闭渲染窗口后,键盘中断被触发
2 个回答
如果你也遇到过无法通过 vtkInteractorStyle
类来操作相机的问题,可以看看 Dolly()
、Pan()
、Spin()
、Rotate()
、Zoom()
和 UniformScale()
这些方法。它们都可以让你在使用的任何 vtkInteractorStyle
子类中访问相机。祝你好运!
补充一下:更简单的方法是把你的相机作为属性直接附加到继承自 vtkInteractorStyle
的类上,比如:
style = MyInteractorStyleClass() style.camera = myCam
这样你就可以在自定义类的任何地方访问相机了!这个方法很基础,但我当时没想到。
调用 Start()
方法会启动一个事件循环,这个循环是用来处理渲染事件的,类似于图形用户界面(GUI)中的事件循环。所以你想要同时启动两个事件循环,这样做其实是没有意义的。
一个可以考虑的解决办法是,不在 RenderWindowInteractor 上调用 Start
,而是创建一个小的图形界面,里面包含多个特定工具包的 RenderWindowInteractors,然后使用这个图形界面的事件循环。
举个例子,这里展示了如何在 tvtk 的 wxVtkRenderWindowInteractor 类中使用特定于 GUI 工具包的代码来实现,它并没有在 RenderWindowInteractor 上调用 start,而是利用图形界面的事件循环来管理事件:
def wxVTKRenderWindowInteractorConeExample():
"""Like it says, just a simple example
"""
# every wx app needs an app
app = wx.PySimpleApp()
# create the top-level frame, sizer and wxVTKRWI
frame = wx.Frame(None, -1, "wxVTKRenderWindowInteractor", size=(400,400))
widget = wxVTKRenderWindowInteractor(frame, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(widget, 1, wx.EXPAND)
frame.SetSizer(sizer)
frame.Layout()
# It would be more correct (API-wise) to call widget.Initialize() and
# widget.Start() here, but Initialize() calls RenderWindow.Render().
# That Render() call will get through before we can setup the
# RenderWindow() to render via the wxWidgets-created context; this
# causes flashing on some platforms and downright breaks things on
# other platforms. Instead, we call widget.Enable(). This means
# that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE,
# that doesn't matter.
widget.Enable(1)
widget.AddObserver("ExitEvent", lambda o,e,f=frame: f.Close())
ren = vtk.vtkRenderer()
widget.GetRenderWindow().AddRenderer(ren)
cone = vtk.vtkConeSource()
cone.SetResolution(8)
coneMapper = vtk.vtkPolyDataMapper()
coneMapper.SetInput(cone.GetOutput())
coneActor = vtk.vtkActor()
coneActor.SetMapper(coneMapper)
ren.AddActor(coneActor)
# show the window
frame.Show()
app.MainLoop()
(请注意,这段代码没有被修改,并且与你想要做的事情有明显的不同。)
另外,ctrl+C 不起作用的原因是 VTK 的事件循环并没有处理这个事件。有些图形用户界面会处理这个事件,包括 wxpython。但如果你使用的图形界面不处理这个事件(例如 Qt),你可以手动告诉 Python 解释器去拦截这个事件,而不是将其转发给 GUI 的事件循环,这样就会导致程序崩溃:
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)