最开始接触OCC的时候,我以为所谓“Qt引入occ库”,就是在.pro文件里加两行INCLUDEPATH和LIBS,然后跑通一个官方Example就完了。真上手才发现,OCC这套东西的体量和普通第三方库完全不是一个量级——光是它的CMake配置、运行期资源路径、动态库依赖,就能让一个“Hello World”拖上两三天。这篇是Qt+OpenCascade开发笔记的第二篇,核心任务很明确:把OpenCascade引入到Qt工程中,搭一个能复用的工程模板,最后发布一个能在干净Windows机器上运行的Demo。如果你也卡在“官方Demo能编译、自己的工程却跑不起来”这个阶段,这篇笔记应该能帮你省掉不少弯路。
我把Second篇的目标拆成四件事:一是建立可复用的Qt+OCC工程骨架,二是打通OCC的CMake依赖配置,三是在Qt窗口里渲染一个真实的OCC三维场景,四是产出可分发、双击就能跑的Demo。四件事对应“引入occ库、搭建模板、发布Demo”三个关键词,也是后续继续做建模、测量、导入导出功能的地基。
1. 动手之前先想清楚:这篇笔记要打通哪几个环节
1.1 这一篇与上一篇的衔接
系列的第一篇解决的是“环境有没有”的问题——OCC源码怎么编译、安装目录长什么样、Qt用什么版本。如果你已经拿到了一个能用的OCC安装包(或者自己编译的install目录),那么第二篇的起点就在这里。
但很多人的真实体验是:OCC官方提供的Examples项目能编译,一换成自己的Qt工程就各种报错。找不到头文件、链接不到TK库、运行时告诉你缺DLL、3D窗口黑屏……这些问题和OCC本身的关系不大,更多是“接入方式”没捋顺。所以这一篇不写复杂的建模算法,只做“接入”这一件事。
我这里默认你具备的基础是:C++语法过关、能创建一个空的Qt Widgets工程、理解CMake基本语法。至于OCC内部BRep数据结构、拓扑几何那套理论,这一篇完全不需要。
1.2 目标拆解与结果定义
在动手之前,先给“成功”下一个可验证的定义,不然容易陷入“好像编译过了但又不知道算什么水平”的迷茫状态。我认为这篇要做到以下四点才算结束:
- 点开生成的exe,Qt主窗口能正常显示;
- 主窗口内嵌一个OCC三维视口,能看到坐标轴和场景背景;
- 点击界面按钮,能够在三维空间里生成一个基本体并显示出来;
- 把exe连同依赖拷贝到一台没有开发环境的机器上,双击能运行,三维场景不黑屏。
严格来说,第四点是“发布Demo”的验收标准,也是很多人最后放弃的地方。我会在最后一节展开讲动态库收集和资源路径的坑。
1.3 什么情况下你需要这篇笔记
如果你是刚接触OCC,Qt也只会拖控件拉界面,这篇笔记能帮你把一个“最小可用工程”跑起来,后续再往里面填充业务代码。如果你是要给团队搭脚手架,这篇笔记里的目录结构、CMake配置和发布脚本可以直接抄走。如果你已经在OCC的官方示例里转了一圈、被自定义编译折腾得想放弃,那么重点看第3节和第5节,那两步通常是最劝退的。
2. 版本与路径双核对:OCC库接入前的最后一道检查
2.1 一套稳妥的版本组合
OCC的Windows二进制包对编译器和Qt版本比较敏感。版本选错了,后面所有步骤都会变得很别扭。这里总结一下我实测过、以及社区里比较常见的组合:
| OCC版本 | 配套编译器 | 常见Qt搭配 | 说明 |
|---|---|---|---|
| 7.5.x | VS2017/2019 64位 | Qt 5.12 ~ 5.15 | 老教程多,资料最全,但部分接口在后续版本已调整 |
| 7.6.x | VS2019 64位 | Qt 5.15.2 | 我主力测试的组合,稳定性不错 |
| 7.7.x | VS2019/2022 64位 | Qt 5.15 / Qt 6.2+ | 新功能多,官方示例更新得也快 |
| 7.8.x | VS2019/2022 64位 | Qt 6.x | 较新的版本,建议配合较新的Qt |
如果完全没有历史包袱,我建议直接选“OCC 7.6 + Qt 5.15.2 + VS2019 64位”这套,原因很简单:网上绝大多数报错帖都基于这个组合,真出问题你能搜到答案。如果你必须用Qt 6,也不是不行,但OCC在Qt 6的OpenGL上下文管理上有一些细节差异,第一次跑通会比Qt 5多花点时间。
还有一个容易忽略的地方:OCC的二进制包通常只提供64位版本。工程里的“解决方案配置”要切换到“Release x64”,项目和OCC的位数必须一致,否则链接阶段大概率报一堆无法解析的外部符号。
2.2 检查OCC安装目录与CMake配置文件
拿到OCC安装包或自己编译完的install目录后,不要急着写代码,先确认几个关键目录存在:
- include目录:存放OCC所有头文件;
- lib目录:存放TK开头的链接库(.lib),调试和发布通常会分开子目录;
- bin目录:存放TK开头的运行库(.dll),同样分Debug和Release;
- cmake目录:存放OpenCASCADEConfig.cmake、OpenCASCADEConfigVersion.cmake等文件,这是CMake find_package能找到OCC的关键。
如果你是自己编译的OCC,一定要做install这一步,否则头文件和cmake配置不会规整地集中到一个目录,后续CMake配置会极其痛苦。在Windows上可以用一条命令快速确认:
dir /s /b D:\thirdparty\OCC730\cmake\OpenCASCADEConfig.cmake如果提示找不到文件,说明你手上的OCC没有生成CMake配置,CMake接入这条路走不通,需要回看第一篇重新配置安装步骤。注意,这一步不该省,因为它决定了后面“find_package(OpenCASCADE REQUIRED)”能不能被识别。
另外建议在操作系统的Path环境变量里临时加上OCC的bin目录,至少加Release版本的bin。这样做不是为了让你依赖它,而是为了首次运行时不至于一上来就被“找不到TK*.dll”干扰排错。等把发布流程摸清楚后,你反而要刻意抹掉这个依赖,改成把DLL打包到exe旁边。
2.3 Debug和Release二选一,别混
Windows上的OCC预编译包大多只有Release版本。很多人习惯Qt工程用Debug调试,结果就出现了“Debug的exe + Release的OCC库”混搭,调试正常、一打包就崩,或者干脆链接时就报LNK2038运行时库不匹配。
我的建议很直接:初期把整个工程跑在Release模式下,调试输出用qDebug和日志文件来替代断点。流程跑通、模板稳定了,再决定要不要自己编译OCC的Debug库。这样做能隔离掉一大批“版本混用”导致的问题。这个决策在你后面做发布版时,能省掉最诡异的那类Bug。
3. CMake把OCC请进Qt工程:关键配置逐行拆解
3.1 为什么工程模板选CMake而不是qmake
OCC官方提供的工程示例两种都有,但如果你要做的是一个会长期演进的工程模板,我强烈建议CMake。原因不复杂:
- OCC本身就是用CMake构建的,它对CMake原生的find_package支持最完善;
- Qt官方从Qt 6开始全面转向CMake,未来你再升级Qt,qmake的.pro工程迁移成本很高;
- CMake的target概念能把“当前工程依赖了哪些OCC组件”表达得清清楚楚,后面加模块时不容易乱。
qmake当然也能接OCC,但对第三方库的查找和跨平台处理比较绕,模板化的意义不大。
3.2 最小CMakeLists逐段拆解
下面这份CMakeLists是我模板的起点,注释已经写进去了,逐段说一下哪些是必须的、哪些是可选的。
cmake_minimum_required(VERSION 3.16) project(OccQtTemplate LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 查找OCC,按需声明组件 find_package(OpenCASCADE REQUIRED COMPONENTS FoundationClasses ModelingData ModelingAlgorithms Visualization ) # 查找Qt find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ) add_executable(OccQtTemplate WIN32 src/main.cpp src/MainWindow.ui src/MainWindow.h src/MainWindow.cpp src/OccWidget.h src/OccWidget.cpp ) target_include_directories(OccQtTemplate PRIVATE ${OpenCASCADE_INCLUDE_DIRS} ) target_link_libraries(OccQtTemplate PRIVATE Qt5::Widgets Qt5::OpenGL ${OpenCASCADE_LIBRARIES} )第一段里面的AUTOMOC、AUTOUIC、AUTORCC必须要开,否则Qt的Q_OBJECT头文件不会自动生成moc,.ui也不会转成可用的C++代码。这三行是Qt CMake工程的标配,不加的话你会在链接阶段看到大量“未定义的vtable”这类报错。
然后是find_package(OpenCASCADE REQUIRED COMPONENTS ...)。COMPONENTS是按需分批拉取OCC库的最佳方式,不需要把所有TK库全部链进来。我选的这四个组件对应如下功能:
- FoundationClasses:OCC最底层的基础库,容器、数学、内存管理都在这;
- ModelingData:几何和拓扑数据结构,TopoDS_Shape、BRep这些核心类型在这里;
- ModelingAlgorithms:建模算法库,比如用BRepPrimAPI_MakeBox创建基本体;
- Visualization:AIS、V3d、OpenGL显示这一整套,没有它你的Qt窗口里不会有三维场景。
如果你的业务将来要读取STEP/IGES文件,再加DataExchange组件就够了。这里先保持最小集,编译速度快,依赖也清晰。
最关键的坑往往出现在find_package找不到OCC的时候。正常情况下,OCC安装包里的cmake目录提供了OpenCASCADEConfig.cmake,CMake搜索“包路径”时能找到它。如果报错“Could not find a package configuration file”,你要做的是在配置CMake时指定搜索路径:
cmake -S . -B build \ -DCMAKE_PREFIX_PATH="D:/thirdparty/OCC730;D:/thirdparty/Qt/5.15.2/msvc2019_64" \ -DCMAKE_BUILD_TYPE=ReleaseCMAKE_PREFIX_PATH可以一次性写多个路径,用分号隔开。你也可以只用-DOpenCASCADE_DIR=D:/thirdparty/OCC730/cmake来单独指给OCC,但建议统一用PREFIX_PATH,因为接下来Qt也需要同样的方式找到。
3.3 首次编译后确认OCC真的被链接进来了
这一步看起来多余,但值得做,因为它能把“环境问题”和“代码问题”快速分开。在main.cpp里加一段打印OCC版本号的代码:
#include <Standard_Version.hxx> #include <QDebug> int main(int argc, char *argv[]) { QApplication app(argc, argv); qDebug() << "OpenCASCADE version:" << OCC_VERSION_COMPLETE; // ... }编译成功后,右键exe属性看依赖,或者用Dependencies工具打开exe,确认TK*.dll都指向OCC的bin目录。如果这一步就提示缺DLL,多半是Path环境变量没配好,按2.2节处理即可。确认主程序能加载OCC库,再往Qt窗口里塞三维控件,后面排错时就能排除“库没链上”这个变量。
4. 从能编译到能当模板:工程目录与最小Demo设计
4.1 模板目录结构设计
工程模板的价值在于“可复用”。OCC项目一旦做大了,会涉及建模、显示、数据导入导出、测量等多种功能,如果不按业务模块拆目录,几个月后会非常痛苦。
我建议的模板结构如下:
OccQtTemplate/ ├── CMakeLists.txt ├── CMakePresets.json ├── cmake/ │ └── OCCUtils.cmake ├── src/ │ ├── main.cpp │ ├── MainWindow.h │ ├── MainWindow.cpp │ ├── MainWindow.ui │ ├── OccWidget.h │ ├── OccWidget.cpp │ └── CadActions.cpp ├── resources/ │ └── icons/ ├── scripts/ │ ├── copy_occt_dlls.bat │ └── deploy_demo.bat └── README.md注意几个设计点:
- src下不再以文件类型堆砌,而是按照“应用壳”“三维显示控件”“建模操作”的职责拆分。后续加功能时,每个模块的代码量会自然落在自己的文件里,而不是把所有东西塞进MainWindow。
- 第三方库路径不写死在CMakeLists里,而是放在CMakePresets.json中。换机器时只需要改这一个文件,不需要动构建脚本。
- scripts目录放和打包、拷贝DLL相关的批处理脚本。脚本和代码分开,避免仓库里出现一堆.bat和CMakeLists混在一起的情况。
这里想特别强调一下第三点:OCC的DLL数量很多,如果你手动拷贝容易漏。我在模板里放了一个copy_occt_dlls.bat的雏形,每台机器第一次配置工程时执行一次,把OCC的bin/Release下的TK*.dll复制到本地build目录下的bin里。后面再编译时就不用关心DLL路径了。
4.2 三维显示控件的最小骨架:把OCC场景塞进QWidget
“在Qt里显示OCC三维场景”是这一篇的核心技术点,也是网上帖子里最容易给出过时写法的部分。早期的做法是拿QWidget的winId()当窗口句柄传给OCC,但这种方式在OCC 7.5之后的版本上会越来越别扭。现在更规范的路子是:让OCC渲染进Qt自己的OpenGL上下文,也就是用QOpenGLWidget作为承载控件。
头文件骨架大概长这样:
#ifndef OCCWIDGET_H #define OCCWIDGET_H #include <QOpenGLWidget> #include <QOpenGLFunctions> #include <AIS_InteractiveContext.hxx> #include <V3d_View.hxx> #include <V3d_Viewer.hxx> class OccWidget : public QOpenGLWidget { Q_OBJECT public: explicit OccWidget(QWidget *parent = nullptr); void displayShape(const TopoDS_Shape &shape); void fitAll(); void dumpSnapshot(const QString &filePath); protected: void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; private: Handle(V3d_Viewer) m_viewer; Handle(V3d_View) m_view; Handle(AIS_InteractiveContext) m_context; }; #endif // OCCWIDGET_HinitializeGL里做的事可以概括成三步:创建图形驱动和显示连接、创建Viewer和View、创建AIS交互上下文并把View与Qt的OpenGL上下文绑定。paintGL里的核心只有一行:m_view->Redraw()。鼠标事件则负责把鼠标操作转成View的旋转、平移和缩放,OCC 7.5之后有AIS_ViewController可以统一处理,但这个类的封装细节比较多,我会放在系列的下一篇里专门展开。
这一篇先把骨架立起来,保证你能在Qt窗口内看到三维场景。具体到“坐标轴怎么显示”“背景色怎么调”,其实都是V3d_Viewer和AIS_InteractiveContext的配置问题,调试起来比较直观,遇到问题也好查文档。
4.3 Demo功能路径与验收标准
这个模板Demo我定为三步:初始化三维场景;点击“创建立方体”按钮生成一个参数化box;点击“适配视图”把视角自动对准模型。如果再加一个“导出截图”按钮,就能覆盖OCC最常见的交互和输出需求。
创建基本体的核心代码非常精简,放到模板的CadActions.cpp里:
#include <BRepPrimAPI_MakeBox.hxx> #include <AIS_Shape.hxx> void CadActions::createBox(Handle(AIS_InteractiveContext) ctx) { TopoDS_Shape box = BRepPrimAPI_MakeBox(gp_Pnt(-50, -40, -20), gp_Pnt(50, 40, 20)).Shape(); Handle(AIS_Shape) aisShape = new AIS_Shape(box); ctx->Display(aisShape, Standard_True); }这里有个接口细节:OCC不同小版本的AIS_InteractiveContext::Display重载签名有区别。7.5以上推荐用Display(shape, Standard_True)这种两个参数的写法,意思是“显示并自动触发视图更新”。如果你在别人的老代码里看到Display(shape, AIS_Shaded, 0, Standard_True)这种四个参数的写法,编译报错时直接改成新重载就行。
写到这里,模板就有了一个明确可运行的最小闭环:Qt窗口显示、OCC场景初始化、按钮触发建模、视图适配。这四件事串通后,你接下来要做的任何OCC功能,都会在这个闭环上长出来。
5. 发布Demo:离开开发机之前必须处理的四件事
5.1 动态库收集:windeployqt之外的额外动作
很多人以为发布就是点一个Release编译,然后把exe拷出去。Qt工程可以直接用windeployqt收集Qt运行库和插件,但OCC这种第三方库不是Qt的,windeployqt不会帮你处理。所以发布OCC Demo,必须分两步收集DLL。
第一步,用windeployqt处理Qt依赖。假设你已经构建好了OccQtTemplate.exe:
windeployqt --release --dir dist\demo build\Release\OccQtTemplate.exe第二步,把OCC的DLL拷贝进发布目录。最省心的方法是直接把OCC bin目录下的Release DLL整目录拷过去:
xcopy /Y /E D:\thirdparty\OCC730\win64\bin\*.dll dist\demo\这样会带进去一堆你可能用不到的库,体积确实会大一些,但第一次发布“跑起来”比“体积小”更重要。等确定Demo稳定后,再用Dependencies工具查看exe实际依赖的TK库列表,精简拷贝。
这一步最大的坑是:你自己开发机上运行正常,但发布目录少了OCC的DLL,目标机器一运行就弹“找不到TK*.dll”。排查时优先用Dependencies打开发布目录下的exe,看红色标记的缺失项,比盲目百度报错快得多。
5.2 资源目录与CSF环境变量:黑屏和空场景的常见元凶
DLL都齐了之后,另一个容易踩的坑是环境变量。很多人发布时只拷DLL,结果程序能打开、按钮也能点,但三维视口要么黑屏、要么背景色异常。这种问题十有八九是OCC的运行时资源目录没找到。
OCC在初始化渲染环境时,需要读取着色器、资源定义等文件,它通过一组CSF_开头的环境变量来定位。这些变量在开发机上由OCC自带的env.bat设置,所以你在IDE里跑没问题;到了干净机器上,环境变量不存在,程序只能用内置默认值,表现就是显示异常。
我在模板的main.cpp里做了一个实用处理:程序启动时读取exe同目录下的config.ini,从里面拼出资源路径并用qputenv写入环境变量。关键示意如下:
QString rootPath = QCoreApplication::applicationDirPath(); qputenv("CSF_ShadersFolder", (rootPath + "/occ_data/Shaders").toLocal8Bit()); qputenv("CSF_ResourcesFolder", (rootPath + "/occ_data/src").toLocal8Bit());注意,这些变量名在不同OCC版本里可能不一样,千万不要照抄网上的变量名,要以你自己安装目录里env.bat或custom.bat中实际设置的变量名为准。正确做法是:打开OCC安装目录下的env.bat,把里面所有CSF_开头的变量对出来,然后把你需要的文件复制到发布目录的occ_data子目录中,再在程序里设置成相对路径。
5.3 新机器验收与黑屏排查顺序
发布完成后,一定要在一台“干净的机器”上验收。不用真的开虚拟机,找一台没有装过Qt和OCC的电脑就行。按照下面的顺序排查,基本能覆盖绝大多数问题:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 双击exe直接报缺少DLL | Qt或OCC的DLL没拷全 | 用Dependencies打开exe,按红色缺失项补齐 |
| 报缺少VCRUNTIME140.dll | 目标机器没有VC运行库 | 安装vcredist_x64.exe,或在工程里静态链接运行库 |
| 程序能开但3D视口黑屏 | CSF_*环境变量没设置,或显卡OpenGL版本太旧 | 检查occ_data路径,升级显卡驱动 |
| 按钮点击后程序退出 | Debug/Release混用 | 全部切Release模式重新编译并重新打包 |
| 界面正常但鼠标操作没反应 | 三维控件事件未正确转发 | 检查OccWidget的鼠标事件是否调用了View的旋转/平移接口 |
黑屏的排查尤其要提醒:OCC渲染需要OpenGL 2.1以上能力。如果目标机器是虚拟机且禁用了3D加速,或者远控软件用的是软件渲染,都可能出现黑屏或雪花。这种情况下可以先更新显卡驱动,仍然不行就检查程序启动日志里OCC初始化OpenGL上下文是否失败。
5.4 模板里值得长期保留的发布记录
最后一点属于长期经验。我见过太多人把OCC工程跑通一次,三个月后再发布时就忘了当时用的OCC版本、Qt版本和拷贝了哪些资源目录,到处翻聊天记录。为了避免这种状况,我在README.md里放了一个发布记录表:
| 项目 | 记录值 |
|---|---|
| OCC版本 | 7.6.0 |
| Qt版本 | 5.15.2 MSVC2019 64位 |
| 构建类型 | Release x64 |
| windeployqt版本 | 5.15.2 |
| 是否包含occ_data资源目录 | 是 |
每完成一次发布,更新一行。以后无论是你自己还是接手的人,都能快速定位“当时是怎么跑起来的”。这个习惯比任何代码技巧都实用。
在我自己的实际操作里,最深刻的体会是:Qt引入occ库这件事,难点不在OCC的API,而在“依赖管理”。只要把版本、路径、CMake配置、DLL拷贝、资源路径这五件事在模板层面固化下来,后续开发就会顺畅很多。希望这篇笔记能帮你把最耗精力的接入阶段一次走通,后面就能专注在建模和交互功能本身上了。