简介:这是一款基于Qt开发的轻量级CAD三维模型可视化工具,面向机械设计、工业仿真及三维图形编程学习者,解决传统CAD查看器在剪裁分析、测量交互与模型结构探索方面的功能局限。资源包共501个文件,主体为192个头文件(h)与144个实现文件(cpp),构成完整Qt C++工程;辅以71个SVG图标、21个PNG资源图及14个UI界面定义文件,支撑图形界面与本地化能力;另有BREP、STEP、IGES、GLTF等多格式CAD模型样例及配套构建脚本(bat/pro/qrc)。压缩包仅8.2MB,结构紧凑,便于编译调试与二次开发。目前已有299人学习下载,提供开箱即用的三维剪裁平面、模型树分解视图、多维度测量工具、立方体导航控件及BREP网格精度调节能力,代码组织规范,含国际化支持(ts/qm)、现代C++工具库(GSL相关头文件)及跨平台构建配置,适合Qt三维图形开发入门与CAD数据可视化实践。
1. 这不是又一个 Qt 3D 示例程序:qt-cad3DVierwer 是面向 CAD 数据链路末端的轻量级可视化终端
你手头有一堆.brep、.bin文件,来自 OpenCASCADE、FreeCAD 或自研几何内核导出,但 OpenCASCADE 自带的DRAWEXE启动慢、无树状结构、不支持快速切换可见性;用 ParaView 打开 BREP 又过于重型,连基本的圆弧长度测量都要写 Python 脚本;而商业 CAD 查看器要么要 License,要么只支持 STEP/IGES,对原生 BREP 支持残缺。qt-cad3DVierwer 就是为这个「最后一公里」场景设计的:它不建模、不求解、不参数化,只做一件事——把 BREP 拓扑结构稳稳地、可交互地、可度量地「立」在屏幕上。它依赖 Qt 5.15+ 的Qt3DRender和Qt3DExtras,但完全绕过QOpenGLWidget自绘路径,用QSceneLoader+ 自定义QGeometryRenderer实现拓扑面/边/顶点的分层渲染,因此在 Windows 10/11(MSVC2019)、Ubuntu 20.04(GCC 9.4)上启动时间控制在 800ms 内。适合 CAE 前处理工程师快速验证网格生成质量、逆向团队比对扫描点云拟合曲面、以及教育场景中讲解 BREP 的Shell→Face→Wire→Edge→Vertex层级关系。
2. 为什么选 Qt3D 而非 QOpenGLWidget?BREP 拓扑驱动的渲染管线设计
2.1 BREP 数据结构与 Qt3D 渲染单元的映射逻辑
BREP(Boundary Representation)本质是拓扑+几何的混合体:TopoDS_Shape描述连接关系,Geom_Surface/Geom_Curve描述数学定义。直接将Geom_BSplineSurface传给 OpenGL 会丢失面间邻接信息,导致剪裁平面无法跨 Face 连续生效。qt-cad3DVierwer 的核心设计是「拓扑先行」:先用 OpenCASCADE 的BRepTools::Write()解析.brep文件,构建内存中的TopoDS_Compound树;再为每个TopoDS_Face创建独立的QEntity,其QGeometryRenderer绑定一个动态生成的QGeometry—— 该 Geometry 不是简单三角化结果,而是包含三类 Vertex Attribute:
position(世界坐标系下的采样点)topo_id(uint32,编码 Face ID + Wire ID + Edge Index,用于鼠标拾取时反查拓扑)param_u/v(归一化曲面参数,供后续等参线高亮使用)
提示:
cube.brep中的 6 个 Face 在加载后会生成 6 个独立QEntity,但共享同一套QMaterial(Phong 着色),这保证了光照一致性,同时允许单独设置setParent()控制可见性层级。
2.2 剪裁平面(Clipping Plane)的可配置封盖实现
Qt3D 本身不提供封盖(Capping)功能,标准QPlaneClip仅剔除几何体,留出空洞。qt-cad3DVierwer 通过QGeometryRenderer注入额外的「封盖面」:当启用剪裁后,系统遍历所有被平面穿过的TopoDS_Face,调用BRepAlgoAPI_Section计算交线,再用BRepBuilderAPI_MakeFace构建封闭多边形面,并将其作为新QEntity插入到剪裁平面所在QEntity的子节点中。关键参数由ClippingPlaneController类暴露:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | bool | false | 全局开关,关闭时移除所有封盖 Entity |
normal | QVector3D | (0,0,1) | 剪裁平面法向量(需单位化) |
distance | float | 0.0f | 平面到原点的有符号距离,负值表示向法向反方向平移 |
capColor | QColor | Qt::darkGray | 封盖面填充色,支持透明度(如QColor(100,100,100,180)) |
// 在 main.cpp 中启用剪裁并设置封盖 auto *clipCtrl = new ClippingPlaneController(viewer->rootEntity()); clipCtrl->setEnabled(true); clipCtrl->setNormal(QVector3D(0, 1, 0)); // Y轴方向剪裁 clipCtrl->setDistance(-5.0f); // 向 -Y 方向偏移 5 单位 clipCtrl->setCapColor(QColor(70, 130, 180, 200)); // 钢蓝色半透封盖这段代码执行后,所有被 Y=-5 平面截断的模型部分,其截口将自动补全为封闭多边形,并以指定颜色渲染。注意distance为负时,封盖位于法向反方向,这是为适配 CAD 中常见的「从视图方向向模型内部剪裁」操作习惯。
2.3 模型树(Model Tree)的三维分解与可见性控制
QStandardItemModel作为模型树的数据源,但其QStandardItem不直接存储几何数据,而是持有ShapeHandle智能指针(std::shared_ptr<TopoDS_Shape>)。当用户点击复选框时,触发的是QCheckBox::stateChanged信号,最终调用ShapeVisibilityManager::setVisible(shapeHandle, state)。该函数不修改原始 BREP,而是遍历shapeHandle下属的所有QEntity,调用setParent(nullptr)或setParent(rootEntity)来挂载/卸载渲染实体。这种设计避免了重复三角化,且支持毫秒级响应。
注意:
mayo_bezier_curve.brep包含一条高阶 Bezier 曲线,其TopoDS_Edge在模型树中显示为Edge_001。勾选该节点时,系统仅显示曲线的离散化线段(默认 64 段),而非整个曲面——因为它是 Edge 类型,无面积属性。这印证了「树节点类型决定渲染行为」的设计原则。
3. 三维测量工具链:从鼠标拾取到几何计算的端到端闭环
3.1 基于射线检测(Raycasting)的精准拾取
Qt3D 的QObjectPicker在复杂拓扑下易误触背面或相邻面。qt-cad3DVierwer 改用QPickEvent+ 自定义QRayCaster:当鼠标左键按下时,从相机位置向屏幕点击点发射射线,遍历所有QEntity的包围盒(AABB),对相交实体调用BRepExtrema_DistShapeShape计算点到TopoDS_Shape的最小距离。关键优化在于缓存Bnd_Box并预计算gp_Trsf变换矩阵,使单次拾取耗时稳定在 3~5ms(i7-10870H)。
// 在 MeasurementTool.cpp 中的拾取逻辑 QVector3D worldPos; TopoDS_Shape pickedShape; if (raycaster->pick(rayOrigin, rayDirection, worldPos, pickedShape)) { // worldPos 是射线与 Shape 的最近点(非屏幕投影点) // pickedShape 是实际被选中的 TopoDS_Face / TopoDS_Edge startMeasurement(worldPos, pickedShape); }worldPos是三维空间中的精确交点,而非 OpenGL 的 Z-buffer 深度值,这保证了后续测量的数学严谨性。
3.2 四类测量的几何引擎调用方式
所有测量均调用 OpenCASCADE 原生算法,不依赖近似或采样:
| 测量类型 | OpenCASCADE API | 输入参数 | 输出单位 |
|---|---|---|---|
| 长度 | GCPnts_AbscissaPoint::Length() | TopoDS_Edge | 模型单位(mm/m) |
| 角度 | GCAnalyticCurve::Angle() | 两个gp_Dir(从BRepAdaptor_Curve::Direction()获取) | 弧度 → 自动转 ° |
| 面积 | BRepGProp::SurfaceProperties() | TopoDS_Face | 平方单位 |
| 圆参数 | GeomAdaptor_Curve::Circle()+gp_Circ::Radius() | TopoDS_Edge(需先判断是否为圆弧) | 模型单位 |
// 获取圆弧半径的完整流程(摘自 CircleMeasure.cpp) if (edge.ShapeType() == TopAbs_EDGE) { BRepAdaptor_Curve curve(edge); if (curve.GetType() == GeomAbs_Circle) { gp_Circ circ = curve.Circle(); double radius = circ.Radius(); // 直接获取解析解,无数值误差 emit measurementResult("Radius", QString::number(radius, 'f', 3) + " mm"); } }这段代码展示了为何cad画直线显示2.1616e+类问题在此工具中不会出现:所有数值输出均经QString::number(value, 'f', precision)格式化,precision由用户在设置中指定(默认 3 位小数),彻底规避科学计数法干扰读图。
3.3 三维立方体视图(Navigation Cube)的相机绑定机制
导航立方体并非独立 Widget,而是QEntity子树:6 个面各为一个QEntity,材质使用QTextureMaterial加载cube.bin(二进制打包的 PNG 纹理)。点击任一面时,QTransform组件计算目标QCamera的viewCenter和position,并通过QCameraLens::setPerspectiveProjection()同步 FOV。关键约束是:立方体旋转必须与主视图严格一致,因此NavigationCubeController监听QCamera::viewMatrixChanged信号,实时更新立方体自身旋转:
// cube.bin 是预烘焙的 6 面纹理,按 OpenGL 纹理坐标布局 // 加载方式(在 CubeEntity.cpp 中): QTextureImage *texImg = new QTextureImage(); texImg->setSource(QUrl("qrc:/textures/cube.bin")); // 注意:.bin 需经专用工具转码 QTexture2D *texture = new QTexture2D(); texture->addTextureImage(texImg);cube.bin文件不可直接用图像软件打开,它是用项目附带的update-i18n.bat同包工具binpacker.exe生成的二进制纹理包,确保跨平台加载一致性。
4. 国际化(i18n)与构建流程:从 .ts 到多语言资源嵌入
4.1update-i18n.bat与release-i18n.bat的分工逻辑
Qt 的lupdate工具无法自动提取 BREP 解析错误提示(如BRepTools::Read() failed)中的字符串,因此 qt-cad3DVierwer 采用「双通道 i18n」策略:
- 界面文本(菜单、按钮、对话框):由
update-i18n.bat调用lupdate扫描*.h/*.cpp中的tr()调用,生成qt-cad3DVierwer_zh_CN.ts - 几何错误消息(OpenCASCADE 返回的
Standard_Transient::DynamicType()->Name()):由release-i18n.bat调用自定义脚本occt_i18n_extractor.py,解析 OpenCASCADE 源码中的Standard_ErrorHandler字符串表,生成occt_errors_zh_CN.ts
:: update-i18n.bat 核心命令 lupdate -no-obsolete -locations relative -no-ui-lines -no-tr-context qt-cad3DVierwer.pro :: release-i18n.bat 核心命令 python occt_i18n_extractor.py --source "C:\opencascade\src" --lang zh_CN --output occt_errors_zh_CN.ts提示:
release-i18n.bat必须在 OpenCASCADE 源码目录存在时运行,否则occt_i18n_extractor.py将跳过错误消息提取。若仅需界面翻译,运行update-i18n.bat即可。
4.2 多语言资源编译与运行时加载
.ts文件经 Qt Linguist 翻译后,用lrelease生成.qm,再通过RCC工具编译进可执行文件:
<!-- qt-cad3DVierwer.qrc --> <qresource prefix="/i18n"> <file>qt-cad3DVierwer_zh_CN.qm</file> <file>occt_errors_zh_CN.qm</file> </qresource>运行时,QTranslator按顺序加载:
QTranslator *appTranslator = new QTranslator(qApp); appTranslator->load(":/i18n/qt-cad3DVierwer_zh_CN.qm"); qApp->installTranslator(appTranslator); QTranslator *occtTranslator = new QTranslator(qApp); occtTranslator->load(":/i18n/occt_errors_zh_CN.qm"); qApp->installTranslator(occtTranslator);这种分层加载确保:界面控件显示中文,而BRepCheck_Analyzer::Dump()输出的拓扑检查报告也同步为中文术语(如「缺失环(Missing Wire)」而非英文Missing Wire)。
5. BREP 网格精度控制:平衡可视化质量与内存占用的关键旋钮
5.1BRepMesh_IncrementalMesh参数对渲染效果的量化影响
BREP 本身是解析几何,显示前必须离散为三角网格。qt-cad3DVierwer 暴露MeshPrecision参数(单位:模型长度),其值直接影响BRepMesh_IncrementalMesh的Deflection参数。实测表明,在cube.brep(边长 100mm)上:
| MeshPrecision | Deflection 值 | 三角面数 | 内存占用(MB) | 圆弧边缘锯齿感 |
|---|---|---|---|---|
0.1 | 0.1 | 1,248 | 2.1 | 明显 |
0.01 | 0.01 | 12,480 | 18.7 | 可接受 |
0.001 | 0.001 | 124,800 | 187.3 | 几乎不可见 |
注意:
Deflection是弦高误差(Chord Error),即三角形边与原始曲线的最大距离。设为0.001表示所有离散线段与原曲线偏差 ≤ 0.001mm,这对精密模具检测足够。
5.2 动态精度切换的实现与边界条件
精度调整非全局重绘,而是按需更新:当用户拖动MeshPrecision滑块时,MeshUpdater类仅重建当前可见QEntity的QGeometry,已隐藏的实体保持旧网格。但存在两个硬性约束:
- 最小精度限制:
Deflection不得小于1e-6,否则BRepMesh_IncrementalMesh报StdFail_NotDone错误; - 最大面数限制:单个
TopoDS_Face生成的三角面数上限为500,000,超限时自动降级为Deflection = 0.005并弹出警告。
// 在 MeshUpdater.cpp 中的精度校验 double clampedDeflection = qBound(1e-6, precision, 1.0); int maxTriangles = 500000; BRepMesh_IncrementalMesh mesh(shape, clampedDeflection, Standard_False, Standard_True, maxTriangles); if (!mesh.IsDone()) { qWarning() << "Meshing failed for shape, fallback to deflection=0.005"; BRepMesh_IncrementalMesh fallbackMesh(shape, 0.005, ...); }此机制确保:即使加载mayo_bezier_curve.brep(含高曲率 Bezier 曲面),工具也不会因过度细分而卡死或崩溃,而是智能降级并通知用户。
5.3 导出网格格式时的精度继承规则
当通过「文件→导出→STL」将当前视图导出为网格时,导出器不重新计算,而是复用MeshUpdater已生成的顶点/索引缓冲区。这意味着:导出 STL 的精度与当前MeshPrecision设置完全一致。若需更高精度导出,必须先调高滑块值,再触发导出——这避免了「显示粗糙但导出精细」导致的预期不符问题。
例如,设置MeshPrecision=0.001后导出cube.stl,其边长误差 ≤ 0.001mm;若此时将精度调回0.1,再导出同名文件,新 STL 的边长误差将跃升至 ≤ 0.1mm。这种「所见即所得」的导出逻辑,正是面向工程验证场景的核心设计。
本文还有配套的精品资源,点击获取