vtkRenderWindowInteractor 阻塞程序

2 投票
2 回答
2727 浏览
提问于 2025-04-18 11:21

我正在用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 个回答

1

如果你也遇到过无法通过 vtkInteractorStyle 类来操作相机的问题,可以看看 Dolly()Pan()Spin()Rotate()Zoom()UniformScale() 这些方法。它们都可以让你在使用的任何 vtkInteractorStyle 子类中访问相机。祝你好运!

补充一下:更简单的方法是把你的相机作为属性直接附加到继承自 vtkInteractorStyle 的类上,比如:

style = MyInteractorStyleClass() style.camera = myCam

这样你就可以在自定义类的任何地方访问相机了!这个方法很基础,但我当时没想到。

3

调用 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)

撰写回答