在Qt中集成VTK
1. 下载和安装VTK
- 下载源码包, 保存到工作目录中. 比如, 我自己的目录为
c:/libs/vtk
. - 解压缩vtk的源码包. 默认解压缩当当前目录后是
VTK-9.3.0
, 我打算将这个目录用于构建后的版本目录, 因此将它改一下名字,VTK-9.3.0-src
- 建立两个同级的目录,
VTK-9.3.0-build
和VTK-9.3.0
, 第一个是构建时的工作目录, 这样cmake的所有生成文件都放在这里, 不会破坏源代码目录VTK-9.3.0-src
. 而VTK-9.3.0
用于安装VTK的目录. 将这些目录都和安装目录隔离开, 避免万一构建出了什么问题后清理起来麻烦.
1.1 修改bug
VTK9.3有一个bug, 会导致Debug模式下编译失败. 我们先修改一下.
打开Common\Core\vtkConstantImplicitBackend.h
,
1 | VTK_ABI_NAMESPACE_BEGIN |
我们需要将VTKCOMMONCORE_EXPORT
给删掉. 不然会重复导出符号.
1.2 配置CMake
启动CMAKE, 指定源目录为
C:/libs/vtk/VTK=9.3.0-src
, build目录为c:/libs/vtk/VTK-9.3.0-build
.按下
Configure
按钮. 此时, 也可以将Grouped
和Advanced
两个复选框勾上. 此时会弹出对话框, 选择工具. 在里面选择Visual Studio 17 2022
和x64
.
Qt6 编译使用的是Visual Studio 16 2019
, 可能选择它是更好的做法. 我的电脑里面只有2022, 因此只能选择这个. 不过, 我们可以为Visual Studio 2022安装2019的生成工具使用. 如下所示:
确认之后, CMAKE会进行配置分析. 取决于你的电脑, 这是一个很漫长的过程, 等完成后, 要进行下面的修改:
- 注意选中
BUILD_SHARED_LIBS
, 这个一定要选中 (默认是选中的, 要检查一下不要弄没了). - 寻找
VTK_LEGACY_REMOVE
, 选中它会禁用deprecated api. 我们会使用最新的API接口, 不会盲目抄网上的老东西, 所以这个最好是选中它. - 把
VTK_MODULE_ENABLE_VTK_ioss
和VTK_MODULE_ENABLE_VTK_IOIOSS
都设置为NO
, 以前的版本会编译失败. 我们反正用不到, 也懒得花上一个多小时去尝试了, 就设置为NO
了. 这个有啥用我还不知道. - 寻找
CMAKE_INSTALL_PREFIX
, 把它设置为我们前面创建的目录C:/libs/vtk/VTK-9.3.0
. 这个一定要慎重, 一旦确定了就不要以后再乱动了. - 最后, 寻找
Qt
, 将VTK_GROUP_ENABLE_QT
给勾选上, 然后将其他几个都设置为WANT
. 我因为不用QML, 就没有选中Quick
的这一项. 注意的是, Qt6的新版本的目录结构有变化, 和VTK不怎么兼容, 如果选择了quick, 需要自己去修改几个目录的位置.
- 注意选中
选择了之后, 就可以再次按下
Configure
按钮了. 这个时候还会有错误. 我们要如下修改:- 设置
Qt5_DIR
和Qt6_DIR
. 对于Qt6, 默认位置在C:\Qt\6.7.0\msvc2019_64\lib\cmake\Qt6
下面. Qt5也一样. - 确认一下
VTK_QTVERSION
的值, 因为这台电脑只装了Qt6, 我也只会在Qt6上面用VTK, 我就设置为6了.
- 设置
再次按下
Configure
按钮, 结果如下图所示. 红色的是给你确认的. 因为这台电脑上没有装vulkan, 所以会有警告. 我们不用它, 所以不用关心. 等以后需要的时候再重新安装Vulkan SDK就是了. 然后确认一下没问题了. 就可以按下Generate
按钮生成VC工程了.按下
Generate
, 会在build目录中生成VTK.sln
文件. 然后我们可以关闭CMAKE了.
1.3 编译VLK和安装
用Visual Studio打开VTK.sln
工程. 选择ALL BUILD
项目, 打开工程属性页:
- 选择
所有配置
, 然后选择常规/C++标准
, 将其改为C++17
或更新的版本. - 如果担心和Qt的版本不一致, 可以修改
平台工具集
, 改为2019的v142
版本. 据说这个版本不一致会影响plugin, 但是我基本上不拖拉控件, 所以也就不想改了.
然后, 激活Release
, 并开始Build. 这个过程很漫长, 我的11代i5的笔记本, 估计会跑上1个多小时.
编译完成后, 选择INSTALL
项目, 选择仅生成INSTALL
.
然后, 将配置切换到Debug, 并重新做一轮. 编译和安装就完成了.
修改完毕之后, 注意将VTK_DIR
放到环境变量里面去.
2. 在Qt的qmake项目中使用VTK
使用qmake,无法利用cmake,使用起来很麻烦,需要人工将库一个个地添加到工程里面。例如:
修改.pro
文件, 在文件按末尾增加vtk库:
1 | INCLUDEPATH += /path/to/VTK/include/vtk-<version> |
这种做法太麻烦,尝试一次就不会再有做第二次的兴趣了。除非是在已有的qmake工程中增加VTK,否则还是不主张使用。
3. 在Qt的cmake项目中使用VTK
3.1 创建一个纯Qt项目的CMake文件
首先注意一点的是,比较新的QtCreator自动创建的widget项目的cmake文件是有问题的,而且里面有很多做Windows用不到的东西,如果不跨平台,只做Windows上位机程序,还是按照它的文档手工写文件比较好,做出来的cmake文件更干净一些。
下面是一个支持GUI的CMakeLists.txt
文件:
1 | # GUI和Console都需要的部分 |
3.2 添加VTK
然后, 要支持VTK,我们只需要将下面几行加到里面去:
1 | # 添加对VTK的引用. 也可以使用COMPONENTS指定具体的组件. 对新手来说, 这个有点困难. |
这样,CMake工程文件就完成了。
接下来,我们还需要找个地方实现auto_init
。这个活动需要在使用::New()
的前面进行,个人建议就放到main.cpp
里面。这样不管是Console还是GUI程序,都能利用它。比如:
1 | ... |
3.3 其他改进
接下来,我们可以考虑利用预编译头文件。这一点上,cmake做的比qmake,比VC都差了很多。尤其是VTK的头文件多如牛毛,好像也没有一个如OpenCV的opencv.hpp
这样的打包头文件,用起来很不友好。个人的建议是把你自己经常用到的头文件都定义在一起。
比如,下面我定义了一个变量PCH_FILES
,里面包含了经常用到的头文件,然后用target_precompile_headers
来声明它。如果是多子工程的项目,可以将PCH_FILES
定义到顶层文件中,每个子目录项目的cmakelists文件中使用target_precompile_headers
来引用它。
1 | set(PCH_FILES |
4. Qt中使用VTK编程
4.1 控制台程序
既然是控制台,就无所谓Qt不Qt的了,使用的是vtkRenderWindow
,用什么开发工具和库都是一样的。当然,如果只是看图,也可以直接使用ImageView2
。
4.2 GUI程序
但是,使用Qt的价值在于我们要编写GUI程序,我们要使用Qt提供的一系列的Widget。VTK的历史上,先后给出了好多个用于Qt的Widget组件:
QVTKWidget
QVTKWidget2
QVTKOpenGLWidget
QVTKOpenGLNativeWidget
QVTKOpenGLStereoWidget
对于我们来说,如果你使用的是Qt5.4以上的版本,VTK的版本是8.2以上,那么前面的都不需要考虑,只需要记住最后两个就好了。根据VTK的文档,建议一般情况下使用QVTKOpenGLNativeWidget
,特殊场景下可以使用SteroWidget
这个。
以前的版本,VTK还会编译出一个dll,你可以将它放到Qt的plugin目录里面,然后就可以在QtCreator的设计节目中看到一个QVTKWidget
了。这个新的VTK版本也没有了。所以新的用户也不要纠结怎么找不到了。QVTKOpenGLNativeWidget
是QOpenGLWidget
的派生类。现在我们先拖一个QWidget
控件,然后把它提升到QVTKOpenGLNativeWidget
就是了。
下面是一个典型的工作流程。假定ui->qvtkWidget
是我们在ui文件中定义的一个QVTKOpenGLNativeWidget
组件,那么这样使用:
1 | // 创建source |
和Console程序的主要区别在于:
- 使用
vtkGenericOpenGLRenderWindow
代替了vtkRenderWindow
- 不需要
vtkRenderWindowInteractor
的创建和关联的操作了。不然会出错的。
VTK的资料有些是过时的——几乎所有的开源的东西的文档都有这个问题,像Qt这么高质量的文档实在是凤毛麟角了——更好的做法是直接看API和Example。Qt相关的例子在VTK/Examples/GUI/Qt
下面。
另外,vtkEventQtSlotConnect
类用于连接vtkObject
的事件到QObject
的slot
上面。
1 | connections = vtkEventQtSlotConnect::New(); |
在Widget中使用vtkImageView2
下面是经过测试的一种做法. 其中, ui->vtkWidget
是在界面中创建的QVTKOpenGLNativeWidget
, 其他的都放在函数中了:
1 | void ImageForm::onLoadImage() |
另外,我们也可以使用vtkImageView2
。下面是外网上面的两种思路。不过,就我而言,我更喜欢使用vtkImageActor
来自己实现操作。
这是在VTK论坛上这个问题的答复. 其中, qvtk
是一个QVTKOpenGLWidget
, 它应该和QVTKOpenGLNativeWidget
一样的:
1 | imageViewer = vtkSmartPointer<vtkImageViewer2>::New(); |
这是网上的另一种做法:
1 | //VTK 在QT中的渲染窗口部件(QT与VTK 在此被链接) |