10.VTK+Qt 集成

VTK+Qt集成学习(第九阶段)

背景:之前写的全部是控制台独立vtk程序;工业软件不会这么用,三维视图必须嵌入Qt窗口。 核心组件:QVTKRenderWindowInteractor,VTK官方提供的Qt封装控件,把vtkRenderWindow嵌入Qt Widget。

⚠️版本提醒:VTK9.7,Qt5.15 / Qt6都支持;QVTKRenderWindowInteractor不再是单独的头文件,属于模块 VTK::GUISupportQt,CMake必须开启这个模块。

一、编译VTK时必须打开的CMake选项

编译VTK源码,CMake-GUI勾选:

VTK_MODULE_ENABLE_VTK_GUISupportQt  → ON
VTK_QT_VERSION → 自动检测,选5或者6,和你本机Qt版本对齐

如果不开启GUISupportQt,就找不到QVTKRenderWindowInteractor,编译报错。

编译安装完成之后,你的工程CMake才可以找到这个组件。

二、Qt项目CMakeLists模板

cmake_minimum_required(VERSION 3.20)
project(VtkQtDemo)

set(CMAKE_CXX_STANDARD 17)

# Qt
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

# VTK,必须引入GUISupportQt
find_package(VTK REQUIRED COMPONENTS
    CommonCore
    RenderingCore
    RenderingOpenGL2
    InteractionStyle
    GUISupportQt   # 关键,Qt绑定模块
)

qt5_wrap_cpp(MOC_SOURCES mainwindow.h)

add_executable(VtkQtDemo
    main.cpp
    mainwindow.h
    mainwindow.cpp
    ${MOC_SOURCES}
)

target_link_libraries(VtkQtDemo
    Qt5::Core Qt5::Gui Qt5::Widgets
    ${VTK_LIBRARIES}
)

# vtk初始化宏,必须写
vtk_module_autoinit(
  TARGETS VtkQtDemo
  MODULES ${VTK_LIBRARIES}
)

三、代码示例:把VTK三维窗口嵌入Qt窗口

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QVTKRenderWindowInteractor.h>

#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkCylinderSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkActor.h>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);

private:
    // Qt控件,承载VTK渲染窗口
    QVTKRenderWindowInteractor* m_vtkWidget{nullptr};

    vtkSmartPointer<vtkRenderer> m_renderer;
    vtkSmartPointer<vtkActor> m_actor;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QVBoxLayout>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->resize(800,600);

    // 1. 创建Qt-VTK混合控件
    m_vtkWidget = new QVTKRenderWindowInteractor(this);

    // 2. 渲染器
    m_renderer = vtkSmartPointer<vtkRenderer>::New();
    m_renderer->SetBackground(0.2,0.2,0.2);

    // 将渲染器绑定到控件的渲染窗口
    m_vtkWidget->GetRenderWindow()->AddRenderer(m_renderer);

    // 造一个测试几何体
    auto cylinder = vtkSmartPointer<vtkCylinderSource>::New();
    cylinder->SetHeight(10);
    cylinder->SetRadius(3);
    cylinder->Update();

    auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    mapper->SetInputData(cylinder->GetOutput());

    m_actor = vtkSmartPointer<vtkActor>::New();
    m_actor->SetMapper(mapper);
    m_renderer->AddActor(m_actor);

    m_renderer->ResetCamera();

    // 布局:把vtkWidget放到主窗口
    QWidget* central = new QWidget(this);
    QVBoxLayout* lay = new QVBoxLayout(central);
    lay->addWidget(m_vtkWidget);
    this->setCentralWidget(central);

    // ⭐重要:启动交互器
    m_vtkWidget->GetInteractor()->Initialize();
}

main.cpp

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

运行效果:Qt窗口里面出现三维视图,可以鼠标拖拽旋转缩放模型,和控制台VTK行为完全一致。

四、核心概念理解

  1. QVTKRenderWindowInteractor 它本身就是一个QWidget,同时内部持有vtkRenderWindow + vtkRenderWindowInteractor。

    不要自己再外部新建vtkRenderWindowInteractor,直接使用m_vtkWidget->GetInteractor()。

  2. 生命周期:控件销毁时,内部vtk对象会依靠vtkSmartPointer自动释放;不要手动delete内部vtk对象。

五、高频踩坑(工业开发必看)

坑1:看不到模型/黑屏

  • 忘记调用GetInteractor()->Initialize()
  • 没有调用ResetCamera(),相机位置不对,模型跑到视锥外面。

坑2:窗口缩放之后,画面不跟随重绘

QVTKRenderWindowInteractor内部已经处理Qt resize事件,一般不会; 如果手动修改数据,需要手动触发渲染:

m_vtkWidget->GetRenderWindow()->Render();

坑3:程序退出崩溃

  1. Qt窗口销毁顺序问题;不要在析构函数手动delete vtk对象,交给vtkSmartPointer管理。
  2. 不要在VTK回调里面直接操作Qt UI控件。

坑4:找不到QVTKRenderWindowInteractor头文件

  • VTK编译没有打开VTK_MODULE_ENABLE_VTK_GUISupportQt
  • CMakeLists没有链接VTK::GUISupportQt

六、Qt信号槽 + VTK观察者回调打通(工程最重要)

VTK的AddObserver回调跑在VTK内部,不可以直接操作Qt界面控件,会线程/事件循环冲突。 标准工程写法:VTK回调里面,emit Qt信号,把数据抛到Qt主线程槽函数去更新UI。

示例:监听鼠标点击事件,回调发Qt信号

// mainwindow.h 增加信号
signals:
    void sigVtkMouseClick(double x,double y,double z);
// 自定义vtkCommand
class MyVtkCallback : public vtkCommand
{
public:
    static MyVtkCallback* New(){ return new MyVtkCallback; }
    void SetQObject(QObject* obj){ m_qObj = obj; }
    virtual void Execute(vtkObject* caller, unsigned long eventId, void* vtkNotUsed(callData)) override
    {
        // vtk回调内部,不操作UI,只发射信号
        emit m_qObj->sigVtkMouseClick(1,2,3);
    }
private:
    QObject* m_qObj{nullptr};
};
// MainWindow构造函数绑定
auto cb = vtkSmartPointer<MyVtkCallback>::New();
cb->SetQObject(this);
m_vtkWidget->GetInteractor()->AddObserver(vtkCommand::LeftButtonPressEvent, cb);

// 信号槽绑定,UI更新放在槽函数
connect(this,&MainWindow::sigVtkMouseClick,this,[](double x,double y,double z){
    qDebug()<<"点击坐标"<<x<<y<<z;
});

关键原则:VTK回调只做数据采集,UI更新交给Qt槽函数。

七、多视图(四视图,机械软件经典)

多视图就是多个QVTKRenderWindowInteractor控件,放到不同的QWidget。 每个控件各自拥有一套独立vtkRenderer、独立相机。

注意:多个渲染窗口共享同一套vtkActor是可以的,Actor可以加到多个Renderer,不用重复拷贝PolyData,节省内存。


课后练习任务(动手写)

  1. 把上面示例跑通,Qt窗口里面显示模型。
  2. 增加右下角方向坐标轴vtkOrientationMarkerWidget。
  3. 实现:VTK鼠标左键按下,回调发出Qt信号,界面打印日志。

接下来我们再讲:Update()、Modified()更新机制,以及内存坑。

你跑这个示例的时候遇到报错,可以直接贴出来。

posted @ 2026-08-24 16:11  Drunker•L  阅读(31)  评论(0)    收藏  举报