Python & VTK & PyQt5:如何在 def __init__ 中截取 vtk 渲染?
Python & VTK & PyQt5: How to screenshot vtk render in def __init__?
我做了什么:
用 PyQt5 和 VTK 创建一个应用程序,我在 2 个 QTWidget 中添加了 2 个 VTK 渲染。
在每个 VTK 渲染中,我读取一个 .stl 文件并显示它。
我添加了一个功能screen_shot,它可以截屏VTK渲染并保存这张照片。
我添加了一个菜单栏,我可以点击一个菜单栏并截图 VTK 渲染。
我想做的事情:
我想在加载 VTK 渲染后执行 screen_shot,我将其写在 def __init__ 中。但是我用这种方式只能得到一张 100 * 30 的图片(实际上是 900 * 900)。当我点击菜单时,我会得到一张 900 * 900 的图片。
也许我的描述无法理解,这是我的代码:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.workspace = ""
self.data_path = ""
self.data_index = 0
self.bkcolor = (1.0, 1.0, 1.0)
self.frame = QtWidgets.QFrame()
self.setWindowTitle("PythonVTKViewer")
# self.frame.setLayout(self.box)
self.stl_interactor = QtWidgets.QHBoxLayout()
self.init_menubar()
self.showMaximized()
self.load_Interactor()
self.shot_screen()
def init_menubar(self):
# init a menubar
menubar = self.menuBar()
fileMenu = menubar.addMenu('&Function')
self.screen_shot_button = QtWidgets.QAction('ScreenShot', self)
self.screen_shot_button.setShortcut('Ctrl+S')
self.screen_shot_button.setStatusTip('ScreenShot')
self.screen_shot_button.triggered.connect(self.shot_screen)
fileMenu.addAction(self.screen_shot_button)
def load_Interactor(self):
# Create two render windows
self.vtkWidget_left = QVTKRenderWindowInteractor(self.frame)
self.vtkWidget_right = QVTKRenderWindowInteractor(self.frame)
self.stl_interactor.addWidget(self.vtkWidget_left)
self.stl_interactor.addWidget(self.vtkWidget_right)
# Left renderer and right renderer
self.ren_left = vtk.vtkRenderer()
self.vtkWidget_left.GetRenderWindow().AddRenderer(self.ren_left)
self.renWinLeft = self.vtkWidget_left.GetRenderWindow()
self.iren_left = self.renWinLeft.GetInteractor()
self.ren_right = vtk.vtkRenderer()
self.vtkWidget_right.GetRenderWindow().AddRenderer(self.ren_right)
self.iren_right = self.vtkWidget_right.GetRenderWindow().GetInteractor()
# read stl file
upper_path = os.path.join(self.workspace, "stl/scanner/start/UpperTeeth.stl")
lower_path = os.path.join(self.workspace, "stl/scanner/start/LowerTeeth.stl")
self.init_stl("./temp/upperteeth.stl")
self.init_stl("./temp/lowerteeth.stl")
self.frame.setLayout(self.stl_interactor)
self.setCentralWidget(self.frame)
self.ren_left.SetBackground(self.bkcolor[0], self.bkcolor[1], self.bkcolor[2])
self.ren_right.SetBackground(self.bkcolor[0], self.bkcolor[1], self.bkcolor[2])
# adjust camera
self.init_camera()
self.iren_left.Initialize()
self.iren_right.Initialize()
def init_stl(self, file):
# reader = Reader.read_data(file)
reader = vtk.vtkSTLReader()
if "upper" in file or "Upper" in file:
reader.SetFileName(file)
reader.Update()
self.mapper_left = vtk.vtkPolyDataMapper()
self.mapper_left.SetInputConnection(reader.GetOutputPort())
self.actor_left = vtk.vtkActor()
self.actor_left.SetMapper(self.mapper_left)
self.ren_left.AddActor(self.actor_left)
else:
reader.SetFileName(file)
reader.Update()
self.mapper_right = vtk.vtkPolyDataMapper()
self.mapper_right.SetInputConnection(reader.GetOutputPort())
self.actor_right = vtk.vtkActor()
self.actor_right.SetMapper(self.mapper_right)
self.ren_right.AddActor(self.actor_right)
def init_camera(self):
self.ren_left.GetActiveCamera().SetFocalPoint(0, 0, 0)
self.ren_left.GetActiveCamera().SetPosition(0, 0, -150)
self.ren_left.GetActiveCamera().Roll(90)
self.ren_left.GetActiveCamera().ParallelProjectionOn()
self.ren_left.GetActiveCamera().SetParallelScale(40)
self.ren_right.GetActiveCamera().SetFocalPoint(0, 0, 0)
self.ren_right.GetActiveCamera().SetPosition(0, 0, 150)
self.ren_right.GetActiveCamera().Roll(270)
self.ren_right.GetActiveCamera().ParallelProjectionOn()
self.ren_right.GetActiveCamera().SetParallelScale(40)
def shot_screen(self):
if os.path.exists("./temp/input/upper.png"): os.remove("./temp/input/upper.png")
elif os.path.exists("./temp/input/lower.png"): os.remove("./temp/input/lower.png")
filter = vtk.vtkRenderLargeImage()
filter.SetMagnification(1)
filter.SetInput(self.ren_left)
writer = vtk.vtkPNGWriter()
writer.SetFileName("./temp/input/upper.png")
writer.SetInputConnection(filter.GetOutputPort())
writer.Write()
filter = vtk.vtkRenderLargeImage()
filter.SetMagnification(1)
filter.SetInput(self.ren_right)
writer = vtk.vtkPNGWriter()
writer.SetFileName("./temp/input/lower.png")
writer.SetInputConnection(filter.GetOutputPort())
writer.Write()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
如何在 def __init__ 中截屏?我需要完成这个功能吗?
您需要等到主事件循环开始并且 GUI 完全呈现后再进行屏幕截图。这意味着启动主事件循环的 app.exec_()
应该在 MainWindow.shot_screen
之前执行。从 MainWindow.__init__
中实现此目的的一种方法是使用单次拍摄 QTimer
来延迟 MainWindow.shot_screen
的执行,直到事件循环开始之后,例如
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
...
QtCore.QTimer.singleShot(10, self.shot_screen)
...
在您的 __init__
方法结束时输入:
QtWidgets.QApplication.instance().processEvents()
self.shot_screen()
QtWidgets.QApplication.instance()
得到你的 app
。 .processEvents()
执行事件循环一次,这允许发生@Heike 在他的回答中提到的所需初始化。 app.exec_()
大致等同于:
while windows_are_open:
app.processEvents()
我做了什么:
用 PyQt5 和 VTK 创建一个应用程序,我在 2 个 QTWidget 中添加了 2 个 VTK 渲染。
在每个 VTK 渲染中,我读取一个 .stl 文件并显示它。
我添加了一个功能screen_shot,它可以截屏VTK渲染并保存这张照片。
我添加了一个菜单栏,我可以点击一个菜单栏并截图 VTK 渲染。
我想做的事情:
我想在加载 VTK 渲染后执行 screen_shot,我将其写在 def __init__ 中。但是我用这种方式只能得到一张 100 * 30 的图片(实际上是 900 * 900)。当我点击菜单时,我会得到一张 900 * 900 的图片。
也许我的描述无法理解,这是我的代码:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.workspace = ""
self.data_path = ""
self.data_index = 0
self.bkcolor = (1.0, 1.0, 1.0)
self.frame = QtWidgets.QFrame()
self.setWindowTitle("PythonVTKViewer")
# self.frame.setLayout(self.box)
self.stl_interactor = QtWidgets.QHBoxLayout()
self.init_menubar()
self.showMaximized()
self.load_Interactor()
self.shot_screen()
def init_menubar(self):
# init a menubar
menubar = self.menuBar()
fileMenu = menubar.addMenu('&Function')
self.screen_shot_button = QtWidgets.QAction('ScreenShot', self)
self.screen_shot_button.setShortcut('Ctrl+S')
self.screen_shot_button.setStatusTip('ScreenShot')
self.screen_shot_button.triggered.connect(self.shot_screen)
fileMenu.addAction(self.screen_shot_button)
def load_Interactor(self):
# Create two render windows
self.vtkWidget_left = QVTKRenderWindowInteractor(self.frame)
self.vtkWidget_right = QVTKRenderWindowInteractor(self.frame)
self.stl_interactor.addWidget(self.vtkWidget_left)
self.stl_interactor.addWidget(self.vtkWidget_right)
# Left renderer and right renderer
self.ren_left = vtk.vtkRenderer()
self.vtkWidget_left.GetRenderWindow().AddRenderer(self.ren_left)
self.renWinLeft = self.vtkWidget_left.GetRenderWindow()
self.iren_left = self.renWinLeft.GetInteractor()
self.ren_right = vtk.vtkRenderer()
self.vtkWidget_right.GetRenderWindow().AddRenderer(self.ren_right)
self.iren_right = self.vtkWidget_right.GetRenderWindow().GetInteractor()
# read stl file
upper_path = os.path.join(self.workspace, "stl/scanner/start/UpperTeeth.stl")
lower_path = os.path.join(self.workspace, "stl/scanner/start/LowerTeeth.stl")
self.init_stl("./temp/upperteeth.stl")
self.init_stl("./temp/lowerteeth.stl")
self.frame.setLayout(self.stl_interactor)
self.setCentralWidget(self.frame)
self.ren_left.SetBackground(self.bkcolor[0], self.bkcolor[1], self.bkcolor[2])
self.ren_right.SetBackground(self.bkcolor[0], self.bkcolor[1], self.bkcolor[2])
# adjust camera
self.init_camera()
self.iren_left.Initialize()
self.iren_right.Initialize()
def init_stl(self, file):
# reader = Reader.read_data(file)
reader = vtk.vtkSTLReader()
if "upper" in file or "Upper" in file:
reader.SetFileName(file)
reader.Update()
self.mapper_left = vtk.vtkPolyDataMapper()
self.mapper_left.SetInputConnection(reader.GetOutputPort())
self.actor_left = vtk.vtkActor()
self.actor_left.SetMapper(self.mapper_left)
self.ren_left.AddActor(self.actor_left)
else:
reader.SetFileName(file)
reader.Update()
self.mapper_right = vtk.vtkPolyDataMapper()
self.mapper_right.SetInputConnection(reader.GetOutputPort())
self.actor_right = vtk.vtkActor()
self.actor_right.SetMapper(self.mapper_right)
self.ren_right.AddActor(self.actor_right)
def init_camera(self):
self.ren_left.GetActiveCamera().SetFocalPoint(0, 0, 0)
self.ren_left.GetActiveCamera().SetPosition(0, 0, -150)
self.ren_left.GetActiveCamera().Roll(90)
self.ren_left.GetActiveCamera().ParallelProjectionOn()
self.ren_left.GetActiveCamera().SetParallelScale(40)
self.ren_right.GetActiveCamera().SetFocalPoint(0, 0, 0)
self.ren_right.GetActiveCamera().SetPosition(0, 0, 150)
self.ren_right.GetActiveCamera().Roll(270)
self.ren_right.GetActiveCamera().ParallelProjectionOn()
self.ren_right.GetActiveCamera().SetParallelScale(40)
def shot_screen(self):
if os.path.exists("./temp/input/upper.png"): os.remove("./temp/input/upper.png")
elif os.path.exists("./temp/input/lower.png"): os.remove("./temp/input/lower.png")
filter = vtk.vtkRenderLargeImage()
filter.SetMagnification(1)
filter.SetInput(self.ren_left)
writer = vtk.vtkPNGWriter()
writer.SetFileName("./temp/input/upper.png")
writer.SetInputConnection(filter.GetOutputPort())
writer.Write()
filter = vtk.vtkRenderLargeImage()
filter.SetMagnification(1)
filter.SetInput(self.ren_right)
writer = vtk.vtkPNGWriter()
writer.SetFileName("./temp/input/lower.png")
writer.SetInputConnection(filter.GetOutputPort())
writer.Write()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
如何在 def __init__ 中截屏?我需要完成这个功能吗?
您需要等到主事件循环开始并且 GUI 完全呈现后再进行屏幕截图。这意味着启动主事件循环的 app.exec_()
应该在 MainWindow.shot_screen
之前执行。从 MainWindow.__init__
中实现此目的的一种方法是使用单次拍摄 QTimer
来延迟 MainWindow.shot_screen
的执行,直到事件循环开始之后,例如
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
...
QtCore.QTimer.singleShot(10, self.shot_screen)
...
在您的 __init__
方法结束时输入:
QtWidgets.QApplication.instance().processEvents()
self.shot_screen()
QtWidgets.QApplication.instance()
得到你的 app
。 .processEvents()
执行事件循环一次,这允许发生@Heike 在他的回答中提到的所需初始化。 app.exec_()
大致等同于:
while windows_are_open:
app.processEvents()