最近重新给电脑配置了一遍 Python 环境,也顺手把一个原本使用 PyQt5 的医学影像可视化项目迁移到了 PySide6。

改动并不复杂:替换导入路径,调整部分枚举和信号写法。程序可以启动,终端也没有报错,但主窗口一出现就卡死了——只看到窗口轮廓,内容没有完成绘制,鼠标也无法操作。类似于下图:

2026-08-11_22-56-32

先排除 PySide6

当时的环境是:

macOS 26.5.2, Apple Silicon
Python 3.13.14
PySide6 / Qt 6.11.1
VTK 9.6.2

我先运行了一个只有 QLabel 的 PySide6 窗口,显示和交互都正常。接着把原项目中的数据加载、Matplotlib、体渲染和模型代码全部去掉,只保留一个嵌入 Qt 的 VTK 球体,卡死仍然存在。

最小复现如下:

import sys

import vtk
from PySide6.QtWidgets import QApplication, QMainWindow
from vtkmodules.qt.QVTKRenderWindowInteractor import (
    QVTKRenderWindowInteractor,
)


app = QApplication(sys.argv)
window = QMainWindow()
vtk_widget = QVTKRenderWindowInteractor(window)

renderer = vtk.vtkRenderer()
sphere = vtk.vtkSphereSource()
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(sphere.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
renderer.AddActor(actor)

vtk_widget.GetRenderWindow().AddRenderer(renderer)
window.setCentralWidget(vtk_widget)
window.resize(640, 480)
window.show()

renderer.ResetCamera()
vtk_widget.Initialize()
sys.exit(app.exec())

这说明问题不在项目业务代码,也不是 PySide6 无法创建 macOS 窗口,而是在 Qt 与 VTK 的集成层。

卡在了 paintEvent()

程序没有抛异常,于是我在终端按下 Ctrl+C。调用栈停在:

QVTKRenderWindowInteractor.paintEvent()
    self._Iren.Render()

检查 QVTKRenderWindowInteractor 的实现后,问题变得清楚了。它在初始化时设置了:

self.setAttribute(Qt.WidgetAttribute.WA_PaintOnScreen)

同时,它又在 Qt 的 paintEvent() 中调用 VTK 的 Render()。在当前 macOS 与 Qt 组合下,一次 VTK 渲染会再次触发 Qt 绘制事件,于是形成循环:

paintEvent → Render → paintEvent → Render → ...

GUI 主线程一直忙于重绘,因此窗口无法响应输入,看起来就像程序彻底卡死。

一行修复

在第一次 Initialize()Render() 之前关闭该属性即可:

import sys

from PySide6.QtCore import Qt


if sys.platform == "darwin":
    vtk_widget.setAttribute(
        Qt.WidgetAttribute.WA_PaintOnScreen,
        False,
    )

加入这段代码后,球体正常显示,窗口移动、鼠标交互和 Qt 定时器也恢复了。

项目中如果封装了自己的 VTK 控件,可以把它放在 super().__init__() 之后:

class VTKVisualizer(QVTKRenderWindowInteractor):
    def __init__(self, parent=None):
        super().__init__(parent)

        if sys.platform == "darwin":
            self.setAttribute(
                Qt.WidgetAttribute.WA_PaintOnScreen,
                False,
            )

        # 随后再初始化 renderer 和 interactor

最终,这并不是 PySide6 不支持 macOS,而是 VTK 的 Qt 控件与新版 Qt/Cocoa 绘制行为产生了冲突。VTK 官方也记录了相同问题:WA_PaintOnScreen 会让渲染不断触发新的 paint event。

以后遇到这种“没有异常但界面卡死”的问题,比起继续在完整项目里猜测,先缩减到一个最小窗口,再观察主线程调用栈,往往更容易找到真正卡住的位置。

最后,放下测试结果,成功解决。

image-20260811231218884

参考资料