OCCT 中 STEP 模型网格化算法详解:BRepMesh 原理、参数与实践

Ryan Lu Lv4

前言

STEP(ISO 10303)是 CAD 领域最通用的模型交换格式,它保存的是精确的边界表示(B-rep, Boundary Representation)——解析曲面、NURBS、精确的拓扑关系。但在实际工程中,GPU 渲染、3D 打印(STL)、CAE 前处理等下游应用需要的不是精确曲面,而是三角网格(Triangle Mesh)。把 B-rep 转换成三角网格的过程就叫做网格化 / 镶嵌(Tessellation / Meshing)

OpenCASCADE Technology(OCCT)内置了成熟的网格化组件 BRepMesh,核心类是 BRepMesh_IncrementalMesh。本文将系统讲解:

  • BRepMesh 的两阶段算法原理(边离散化 + 面三角化),以及它如何保证水密性;
  • IMeshTools_Parameters每个参数的含义与默认值
  • 从读取 STEP 到导出 STL 的完整工程代码
  • 不同场景下的调参实践与常见问题排查。

一、快速上手:最小可用代码(TL;DR)

如果只想立刻把 STEP 变成 STL,下面 20 行就是全部(OCCT 7.6+):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <STEPControl_Reader.hxx>
#include <IFSelect_ReturnStatus.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <IMeshTools_Parameters.hxx>
#include <RWStl.hxx>

// 1. 读取 STEP 文件
STEPControl_Reader reader;
if (reader.ReadFile("model.step") != IFSelect_RetDone) return 1;
reader.TransferRoots();
TopoDS_Shape shape = reader.OneShape();

// 2. 配置网格化参数
IMeshTools_Parameters params;
params.Deflection = 0.1; // 弦高误差(线性偏差,与模型同单位)
params.Angle = 0.5; // 角度误差(弧度)

// 3. 执行网格化
BRepMesh_IncrementalMesh mesher(shape, params);

// 4. 导出 STL
RWStl::WriteBinary(shape, "model.stl");

几个必须记住的结论:

  • 必须先网格化再导出RWStl 只写出已有的三角剖分,不会自动网格化,跳过第 3 步会得到一个空 STL;
  • 核心参数只有两个Deflection(弦高误差,控制曲面逼近精度)和 Angle(角度误差,控制折线平滑度);
  • Deflection 与模型单位一致(STEP 通常是毫米),经验值取模型包围盒对角线的 0.1%(即 对角线 × 0.001);
  • 想换参数重新网格化,先调用 BRepTools::Clean(shape) 清除旧网格,否则增量式算法会复用旧结果。

二、为什么需要网格化:从 B-rep 到三角网格

特性B-rep(STEP 中的表示)三角网格
几何精度精确(解析曲面 / NURBS)近似(平面三角形逼近,误差可控)
拓扑信息完整(Solid/Shell/Face/Edge/Vertex)通常无(STL 就是”三角形汤”)
数据量大(精度越高三角形越多)
典型消费者CAD 建模内核GPU 渲染管线、切片软件、部分 CAE 前处理
求交/布尔运算精确、可靠只能近似

网格化的本质是一个有损压缩问题:用一组平面三角形去逼近光滑曲面,同时把逼近误差控制在给定阈值内。误差阈值选多大、三角形放多少,就是 BRepMesh 全部参数要回答的问题。

三、BRepMesh 网格化算法原理(BRepMesh_IncrementalMesh)

3.1 总体流程

BRepMesh 的网格化分为两个串行阶段

  1. 边离散化(Edge Discretization):把每条 TopoDS_Edge 的三维曲线离散成一条折线(Poly_Polygon3D);
  2. 面三角化(Face Triangulation):以各条边的离散点为边界约束,在每个面的 UV 参数域内做三角剖分,再映射回三维空间,得到挂在每个 TopoDS_Face 上的 Poly_Triangulation

两个阶段都由同样的两类误差约束控制:线性偏差(Deflection)角度偏差(Angle)

3.2 第一阶段:边离散化

对一条参数曲线 ,算法要找一组采样点 ,使得折线 同时满足两个条件:

条件一:弦高误差(Chordal / Linear Deflection)。曲线上任意一点到对应弦(线段)的最大距离不超过 Deflection

条件二:角度误差(Angular Deflection)。相邻两段弦的切向夹角不超过 Angle(弧度):

两个条件是同时生效、取更严格者的:曲率大的区域由 Angle 主导加密,缓弯但弦长过大的区域由 Deflection 主导加密。实现上,OCCT 使用 GCPnts 包中的切向偏差类算法(如 GCPnts_TangentialDeflectionGCPnts_QuasiUniformDeflection)在曲线上自适应取点。

3.3 第二阶段:面三角化

边离散完成后,每个面的处理流程是:

  1. 收集边界约束:把构成该面各 Wire 的边离散点(含方向信息)作为约束多边形;
  2. 在 UV 参数域做约束 Delaunay 三角化(Constrained Delaunay Triangulation):OCCT 在面的二维参数空间 中插入边界点并构建满足 Delaunay 空圆性质的三角剖分,边界边作为不可翻转的约束边;
  3. 插入内部点(可选):若 InternalVerticesMode = true(默认),在面内部按误差要求补充采样点,避免大平面出现狭长三角形;
  4. 表面偏差控制(可选):若 ControlSurfaceDeflection = true(默认),算法检查每个三角形与真实曲面的距离,超过 Deflection 的区域会插入新点加密;
  5. 映射回三维:每个 UV 节点经曲面求值 得到三维坐标,存入 Poly_Triangulation(同时保留 UV 坐标,供纹理或参数域查询使用)。

3.4 水密性(Watertight)为什么天然成立

B-rep 中相邻两个面共享同一条 TopoDS_Edge(同一 TShape)。由于每条边只在第一阶段离散一次,两个面在公共边界上拿到的离散点完全一致,三角化后边界上的节点天然一一对应,不会出现裂缝(T-junction / crack)。这是 BRepMesh 相对”逐面独立网格化”方案的核心优势,也是导出的 STL 能直接用于 3D 打印切片的原因。

3.5 增量式(Incremental)的含义

网格化结果缓存在拓扑数据结构上(TopoDS_TFace)。再次调用 BRepMesh_IncrementalMesh 时,如果已有网格满足当前精度要求,算法会直接跳过该面——这对交互式应用很友好,但也带来一个经典坑:改了参数重新网格化却”没效果”。解决办法是先清理:

1
2
3
4
#include <BRepTools.hxx>

BRepTools::Clean(shape); // 清除已有三角化数据
BRepMesh_IncrementalMesh mesher(shape, params); // 这次才会真正重新网格化

四、核心参数详解(IMeshTools_Parameters)

OCCT 7.6 起推荐使用 IMeshTools_Parameters 统一传参(旧式构造函数 BRepMesh_IncrementalMesh(shape, deflection, isRelative, angle, isInParallel) 仍然可用,内部就是转发到它)。各字段含义与默认值如下:

参数类型默认值含义
DeflectionReal0.001弦高误差上限,与模型同单位。网格精度的主开关
AngleReal0.5 rad(≈28.6°)相邻线段/三角形法向夹角上限,控制平滑度
AngleInteriorReal-1(= 用 Angle面内部区域的角度误差上限,可与边界分开设置
DeflectionInteriorReal-1(= 用 Deflection面内部区域的弦高误差上限,可与边界分开设置
RelativeBooleanfalse为 true 时各边按自身长度 × Deflection 计算误差(相对模式)
InParallelBooleanfalse多线程并行网格化,装配体大模型建议开启
MinSizeRealPrecision::Confusion()网格单元最小尺寸下限,防止数值退化
InternalVerticesModeBooleantrue是否在面内部插入采样点(提高三角形质量)
ControlSurfaceDeflectionBooleantrue是否按曲面真实偏差自适应加密面内网格
CleanModelBooleantrue清理模型已有网格数据的策略开关
AdjustMinSizeBooleanfalse是否允许按面的尺寸自动放宽 MinSize
AllowQualityDecreaseBooleanfalse困难区域允许适度放宽误差以完成网格化
MeshAlgoInteger-1(默认内核)面三角化算法选择:-1 默认 / 0 Watson / 1 Delaunay

下面对最重要的几个参数展开说明。

4.1 Deflection:弦高误差(精度主开关)

  • 意义:三角形(或弦)与真实曲面之间允许的最大距离。
  • 量纲:与模型单位一致(STEP 一般是 mm)。Deflection = 0.1 对毫米模型就是 0.1 mm。
  • 经验公式Deflection ≈ 包围盒对角线 × 0.001(高质量可视化可到 0.0001,预览可放宽到 0.005)。
  • 副作用:Deflection 减半,曲面区域三角形数量大约变为 4 倍(面积平方关系),时间与内存同步上涨。不要无脑设成机器精度。

4.2 Angle:角度误差(平滑度开关)

  • 意义:相邻离散段方向变化的上限(弧度)。对圆/圆柱等恒曲率几何体起决定性作用。
  • 直觉换算:一段圆弧被离散后,每段对应的圆心角不超过 Angle。默认值 0.5 rad ≈ 28.6°,意味着整圆最少被离散成约 13 段——这就是”圆柱看起来像多边形”的直接原因。
  • 常用取值:渲染 0.5,精细可视化 0.25,出口级质量 0.1(整圆约 63 段)。

4.3 Relative:相对误差模式

Relative = true 时,每条边按 边长 × Deflection 确定自己的误差阈值。效果是大特征粗、小特征相对细,适合尺寸跨度极大的装配体。但注意此时 Deflection 不再具有绝对单位含义,调试直觉会差一些,单零件一般保持 false。

4.4 InternalVerticesMode 与 ControlSurfaceDeflection

  • InternalVerticesMode = true(默认)会在面内部补点,显著改善大曲面上的三角形形状(避免狭长三角形),对后续做变形、纹理贴图、FEA 前处理都有益;纯渲染且追求最少三角数时可以关掉。
  • ControlSurfaceDeflection = true(默认)让算法主动校验”三角形到曲面”的真实偏差并加密,保证 Deflection 在面内部(而不只是边界附近)也成立。关闭后速度略快但精度承诺变弱,一般不建议关。

4.5 AngleInterior / DeflectionInterior:内外差别化精度

默认值 -1 表示与全局 Deflection / Angle 相同。把它们设成比全局值更大,可以实现”边界密、内部疏”的网格——边界决定零件轮廓和装配配合,内部大平缓区域没必要同等密度,这是降低三角数的高级手段。

4.6 InParallel:多线程

InParallel = true 后面级网格化按面并行。对几十上百个面的装配体提速明显;单个简单零件收益有限。OCCT 的并行基于其自身的 OSD_Parallel,不需要额外依赖。

五、完整工程示例:STEP → 网格化 → STL

下面是一个可直接编译的完整程序,包含:STEP 读取、按包围盒自动计算 Deflection、清理旧网格、并行网格化、网格规模统计、二进制 STL 导出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <STEPControl_Reader.hxx>
#include <IFSelect_ReturnStatus.hxx>
#include <IMeshTools_Parameters.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRepTools.hxx>
#include <BRepBndLib.hxx>
#include <Bnd_Box.hxx>
#include <BRep_Tool.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopLoc_Location.hxx>
#include <Poly_Triangulation.hxx>
#include <RWStl.hxx>
#include <OSD_Timer.hxx>
#include <iostream>

int main(int argc, char** argv)
{
if (argc < 2) {
std::cerr << "用法: " << argv[0] << " model.step [out.stl]\n";
return 1;
}

// ---- 1. 读取 STEP ----
STEPControl_Reader reader;
if (reader.ReadFile(argv[1]) != IFSelect_RetDone) {
std::cerr << "无法读取 STEP 文件\n";
return 1;
}
reader.TransferRoots();
TopoDS_Shape shape = reader.OneShape();

// ---- 2. 按模型尺寸自动计算 Deflection(包围盒对角线的 0.1%)----
Bnd_Box bbox;
BRepBndLib::Add(shape, bbox);
const double diag = bbox.CornerMin().Distance(bbox.CornerMax());
const double deflection = diag * 1e-3;

// ---- 3. 清除可能存在的旧网格,保证按新参数重新网格化 ----
BRepTools::Clean(shape);

// ---- 4. 配置并执行网格化 ----
IMeshTools_Parameters params;
params.Deflection = deflection;
params.Angle = 0.5;
params.InParallel = Standard_True; // 多线程加速

OSD_Timer timer;
timer.Start();
BRepMesh_IncrementalMesh mesher(shape, params);
timer.Stop();

// ---- 5. 统计网格规模 ----
Standard_Integer nbNodes = 0, nbTriangles = 0;
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
TopLoc_Location loc;
const Handle(Poly_Triangulation)& tri =
BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
if (!tri.IsNull()) {
nbNodes += tri->NbNodes();
nbTriangles += tri->NbTriangles();
}
}
std::cout << "包围盒对角线: " << diag << "\n"
<< "Deflection: " << deflection << "\n"
<< "节点数: " << nbNodes << "\n"
<< "三角形数: " << nbTriangles << "\n"
<< "耗时: " << timer.ElapsedTime() << " s\n";

// ---- 6. 导出二进制 STL ----
const char* out = (argc > 2) ? argv[2] : "out.stl";
if (!RWStl::WriteBinary(shape, out)) {
std::cerr << "STL 导出失败\n";
return 1;
}
return 0;
}

对应的 CMakeLists.txt(链接库按依赖关系列出):

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required(VERSION 3.10)
project(step_meshing CXX)
set(CMAKE_CXX_STANDARD 17)

find_package(OpenCASCADE REQUIRED)

add_executable(step_meshing main.cpp)
target_include_directories(step_meshing PRIVATE ${OpenCASCADE_INCLUDE_DIR})
target_link_libraries(step_meshing PRIVATE
TKSTEP TKXSBase TKMesh TKTopAlgo TKBRep TKG3d TKG2d
TKGeomBase TKGeomAlgo TKMath TKernel TKSTL)

六、参数选择实践指南

场景Deflection(相对包围盒对角线)Angle其他建议
实时渲染 / 网页预览0.1% ~ 0.5%0.5可关闭 InternalVerticesMode 减少三角数
高质量可视化 / 截图0.01% ~ 0.05%0.25
3D 打印 / STL 导出0.05% 且不大于打印层精度(如 0.05 mm)0.25 ~ 0.1误差小于喷嘴/层厚精度即可,再小只是浪费
CAE 前处理0.01% 或按物理公差0.1保持 InternalVerticesMode = true
大型装配体Relative = true 或分级设置0.5开启 InParallel

几条实用经验:

  1. 先用默认值跑一遍Deflection=0.001Angle=0.5),看三角数和视觉效果再向需要的方向调;
  2. Deflection 与用途对齐,而不是与机器精度对齐:3D 打印超过喷嘴精度、渲染超过亚像素,都没有意义;
  3. 圆柱/孔类特征”变多边形”先调 Angle 再调 Deflection——恒曲率几何由角度误差主导;
  4. 三角数暴涨时优先检查是否有微小碎面(STEP 转换常见问题),必要时用 ShapeUpgrade_UnifySameDomain 先做模型清理。

七、常见问题 FAQ

Q1:为什么导出的 STL 文件是空的(0 字节或只有文件头)?

RWStl::WriteBinary / WriteAscii 只负责把已经存在的三角剖分写出来,本身不做网格化。读取 STEP 后必须先调用 BRepMesh_IncrementalMesh,否则 shape 上没有任何三角形可写。

Q2:调小 Deflection 重新网格化,结果完全没有变化?

BRepMesh 是增量式算法:已有网格若满足新参数要求就会被保留。换参数前显式清理:

1
2
BRepTools::Clean(shape);
BRepMesh_IncrementalMesh mesher(shape, newParams);

Q3:相邻面之间出现裂缝怎么办?

理论上不会——共享边只离散一次,网格天然水密(见 3.4 节)。如果真的看到裂缝,通常是这两种情况:模型本身有自由边 / 缝隙(STEP 转换质量差,可用 ShapeFix_Shape 修复);或者裂缝出现在下游软件里——STL 不存拓扑,每个面的顶点各自存储,下游若不做顶点焊接会显示硬边/缝隙,这属于格式特性而非网格错误。

Q4:网格太密,怎么减少三角数?

按优先级:增大 Deflection → 增大 Angle → 设置更大的 DeflectionInterior / AngleInterior 让面内部稀疏 → 关闭 InternalVerticesMode。BRepMesh 本身不做网格简化(decimation),需要后处理可导出后用 MeshLab 等工具。

Q5:圆柱显示成明显的多边形,是网格化错了吗?

不是错误,是默认参数的视觉表现:Angle = 0.5 rad 意味着整圆最少约 13 段。把 Angle 调到 0.1~0.25 即可。另外注意 OCCT 的显示网格与导出网格是同一份数据,在交互查看器里看到的粗糙程度就反映了真实网格。

Q6:BRepMesh 的网格能直接用于有限元分析吗?

不能,定位不同:BRepMesh 做的是表面三角化(2D mesh),目标是几何逼近精度;FEA 需要的是体网格(四面体/六面体),且对单元长宽比、雅可比等有质量要求。OCCT 官方提供了 Netgen 等第三方体网格器的集成接口,或使用专业前处理软件。

Q7:网格化很慢甚至卡住,怎么排查?

  • 大装配体先开 InParallel = true
  • 检查 Deflection 是否误设成了过小的绝对值(三角数随 1/d² 爆炸);
  • 某些 STEP 模型的面带有病态参数域(极端狭长的 UV 域),三角化会异常缓慢,可用 ShapeFix_Shape / ShapeUpgrade_UnifySameDomain 预处理;
  • MinSize 保持默认即可,手动改小同样会引发三角数爆炸。

Q8:除了 STL,能导出 OBJ 或 glTF 吗?

可以,OCCT 提供 RWObj(OBJ)和 RWGltf(glTF 2.0)模块。注意 glTF/OBJ 的读写基于 XCAF 文档(带颜色、名称、实例结构):用 STEPCAFControl_Reader 把 STEP 读进 TDocStd_Document,再交给 RWGltf_CafWriter 导出,网格化同样在导出前用 BRepMesh_IncrementalMesh 完成。

八、进阶:访问网格数据(Poly_Triangulation)

网格化后,每个面的三角形数据挂在 Poly_Triangulation 上,通过 BRep_Tool::Triangulation 取出。下面的代码把全部三角形收集到全局坐标系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <BRep_Tool.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopLoc_Location.hxx>
#include <Poly_Triangulation.hxx>
#include <Poly_Triangle.hxx>
#include <gp_Trsf.hxx>

for (TopExp_Explorer expFace(shape, TopAbs_FACE); expFace.More(); expFace.Next())
{
const TopoDS_Face& face = TopoDS::Face(expFace.Current());

TopLoc_Location loc;
const Handle(Poly_Triangulation)& mesh = BRep_Tool::Triangulation(face, loc);
if (mesh.IsNull())
continue;

const gp_Trsf trsf = loc.Transformation();
for (Standard_Integer i = 1; i <= mesh->NbTriangles(); ++i) // 坑 1:索引从 1 开始
{
Standard_Integer n1, n2, n3;
mesh->Triangle(i).Get(n1, n2, n3);

// 坑 2:面朝向为 REVERSED 时要翻转绕向,保证法线一致朝外
if (face.Orientation() == TopAbs_REVERSED)
std::swap(n2, n3);

// 坑 3:节点存在面的局部坐标系,需乘以 location 变换到全局
const gp_Pnt p1 = mesh->Node(n1).Transformed(trsf);
const gp_Pnt p2 = mesh->Node(n2).Transformed(trsf);
const gp_Pnt p3 = mesh->Node(n3).Transformed(trsf);
// ……写入自己的顶点缓冲 / 文件
}
}

三个容易踩的坑:

  1. 索引从 1 开始Node(i)Triangle(i) 都是 1-based(OCCT 传统),三角形返回的节点编号也是 1-based;
  2. REVERSED 面的绕向:共享同一几何面的两个拓扑面朝向可能相反,不翻转会导致一半三角形法线朝内;
  3. Location 变换:复合形状(装配体)中,几何存在实例自身的局部坐标系里,必须应用 TopLoc_Location 才能得到全局坐标。装配体里大量面共享同一份网格数据,只做一次变换即可获得正确结果,这也是 OCCT 实例化省内存的原因。

如果需要参数域坐标(如纹理贴图),可用 mesh->UVNode(i) 取出每个节点对应的

总结

OCCT 对 STEP 模型的网格化可以概括为一句话:BRepMesh_IncrementalMesh 先在 Deflection(弦高误差)和 Angle(角度误差)双重约束下离散所有边,再在面的 UV 参数域做以边离散点为约束的 Delaunay 三角化,并把结果缓存在拓扑结构上。工程上记住四件事就够了:

  1. 核心参数是 DeflectionAngle,经验起点是包围盒对角线 × 0.001 与 0.5 rad;
  2. 导出 STL/OBJ/glTF 前必须显式网格化;
  3. 换参数重新网格化前先 BRepTools::Clean
  4. 共享边只离散一次,所以网格天然水密,可直接用于 3D 打印。

参考资料

  • Title: OCCT 中 STEP 模型网格化算法详解:BRepMesh 原理、参数与实践
  • Author: Ryan Lu
  • Created at : 2026-07-17 00:00:00
  • Updated at : 2026-07-17 14:44:10
  • Link: http://ryan-hub.site/7b5ec9c29354/
  • License: This work is licensed under CC BY-NC-SA 4.0.