第10章 复合实体(Compound):跨面一致网格划分
10.1 学习目标
- 理解复合实体(Compound)的概念:将多个几何实体合并为一个逻辑整体进行网格划分,实现跨边界的一致节点匹配
- 掌握
mesh::setCompound()的三个用法层面:Compound Curve(线复合)、Compound Surface(面复合)、Compound Volume(体复合) - 理解 Gmsh 处理 Compound 的内部五步流程:独立剖分、离散融合、重参数化、统一剖分、可选回分类
- 学会区分 Compound 与 Physical Group 的本质差异:前者改变网格拓扑,后者仅做逻辑分组
- 掌握
Mesh.CompoundClassify和Mesh.CompoundMeshSizeFactor两个关键选项的用途
10.2 核心概念说明
10.2.1 什么是复合实体(Compound Entity)
在实际 CAE 前处理中,经常遇到以下情况:
- 导入的 CAD 模型(如 STEP 文件)带有大量细碎曲面(如倒角面、Logo 印刻面),各面之间被内部边线分隔,如果直接剖分会产生大量不必要的小单元
- 手动分区(partition)建模后,需要将相邻面"缝合"起来,使网格在接缝处节点一一对应
- 复杂曲面被分割成多个子面分别描述,但网格阶段希望视为一整张曲面来划分
Gmsh 的 复合实体(Compound Entity) 机制正是为解决这类问题而设计。它的核心思想是:将多个同维度的几何实体(线、面或体)声明为一个逻辑整体,在网格生成时跨过内部边界进行划分,最终输出一个统一的离散网格。
Compound 和 Physical Group 是两个容易混淆的概念,它们的区别是根本性的:
| 概念 | 对网格的影响 | 内部边界 | 典型用途 |
|---|---|---|---|
| Compound | 改变网格拓扑——内部边界消失,节点在接缝处自动匹配 | 被忽略 | 缝合细碎曲面、跨面一致网格 |
| Physical Group | 不改变网格拓扑——仅给元素打标签,内部边界仍存在 | 被保留 | 施加边界条件、材料属性分区 |
简而言之:Compound 改变"网格怎么画"(how to mesh),Physical Group 改变"网格怎么用"(how to use)。
10.2.2 Gmsh 处理 Compound 的五步流程
当调用 setCompound() 后执行 mesh::generate(),Gmsh 内部按以下五个步骤完成网格生成:
步骤 1: 独立剖分 (Individual Meshing)
对 Compound 中的每个底层几何实体单独生成初始网格。
此阶段的网格尺寸由 Mesh.CompoundMeshSizeFactor 控制(默认 0.5,
即取全局尺寸的 0.5 倍,使初始网格比最终期望密度更细)。
步骤 2: 离散融合 (Discrete Fusion)
将所有独立网格合并为一个离散实体(discrete entity),
消除内部边界。
步骤 3: 重参数化 (Reparametrization)
在融合后的离散实体上计算分段线性映射(离散参数化),
为后续网格划分提供统一的参数空间。
步骤 4: 统一剖分 (Compound Meshing)
基于步骤 3 的参数化结果,在离散实体上重新划分网格。
此时内部边界已不存在,网格可以自由跨越原始实体的接缝。
步骤 5: 可选回分类 (Optional Reclassification)
根据 Mesh.CompoundClassify 选项决定网格单元和节点的归属:
- = 1(默认):将网格元素回分类到原始几何实体上
- = 0:所有元素留在新生成的离散实体上
重要限制:步骤 3 的重参数化要求融合后的形状"足够简单"(amenable to reparametrization)。如果形状过于复杂、狭长或扭曲,重参数化可能失败。此时应参考 t13.cpp 的做法——先对整体做初次剖分后通过 classifySurfaces 将其拆分为可参数化的子面片(patches),再使用 Compound 进行二次剖分。
10.2.3 Compound 的三种维度
| 维度 | 名称 | 含义 | API 调用示例 |
|---|---|---|---|
| 1 | Compound Curve | 将多条线段/样条视为一条连续曲线 | setCompound(1, {2, 3, 4}) |
| 2 | Compound Surface | 将多个面视为一个连续曲面 | setCompound(2, {1, 5, 10}) |
| 3 | Compound Volume | 将多个体视为一个连续体域 | setCompound(3, {1, 2, 3}) |
Compound Curve 用于消除线段连接点处的网格节点强制匹配(即网格可以跨点分布)。Compound Surface 用于消除面间公共边处的节点强制匹配,使网格可以自由跨面划分。Compound Volume 同理用于 3D 体域。
10.3 C++ 代码逐段讲解
本示例构造三块相邻曲面,它们共享内部曲线作为边界。不使用 Compound 时,网格在内部边界处必须节点对齐(但不做一致性保证);使用 Compound 后,三块曲面被视为一整张曲面,网格自由跨过内部曲线,自动保证跨面节点一一对应。
10.3.1 初始化与几何点定义
#include <set>
#include <gmsh.h>
int main(int argc, char **argv) {
gmsh::initialize();
gmsh::model::add("ch10_compound");
double lc = 0.1;
// 定义 9 个几何点。点 5,6,7,8,9 用于构建内部细分曲线
gmsh::model::geo::addPoint(0.0, 0.0, 0.0, lc, 1);
gmsh::model::geo::addPoint(1.0, 0.0, 0.0, lc, 2);
gmsh::model::geo::addPoint(1.0, 1.0, 0.5, lc, 3);
gmsh::model::geo::addPoint(0.0, 1.0, 0.4, lc, 4);
gmsh::model::geo::addPoint(0.3, 0.2, 0.0, lc, 5);
gmsh::model::geo::addPoint(0.0, 0.01, 0.01, lc, 6);
gmsh::model::geo::addPoint(0.0, 0.02, 0.02, lc, 7);
gmsh::model::geo::addPoint(1.0, 0.05, 0.02, lc, 8);
gmsh::model::geo::addPoint(1.0, 0.32, 0.02, lc, 9);
这里定义了 9 个点在三维空间中,构成了一个略带翘曲的非平面区域。点 5 是内部顶点,点 6-9 是边界上的细分点,用于创建内部曲线。
10.3.2 曲线和曲面定义
// 外边界曲线
gmsh::model::geo::addLine(1, 2, 1); // 底部边界:点1->点2
gmsh::model::geo::addLine(2, 8, 2); // 点2->点8(底部边界右段)
gmsh::model::geo::addLine(8, 9, 3); // 点8->点9
gmsh::model::geo::addLine(9, 3, 4); // 点9->点3(右侧边界上段)
gmsh::model::geo::addLine(3, 4, 5); // 顶部边界
gmsh::model::geo::addLine(4, 7, 6); // 点4->点7(左侧边界上段)
gmsh::model::geo::addLine(7, 6, 7); // 点7->点6
gmsh::model::geo::addLine(6, 1, 8); // 点6->点1(底部边界左段)
// 内部曲线:穿过面域的样条曲线和直线
gmsh::model::geo::addSpline({7, 5, 9}, 9); // 样条 9:点7->5->9
gmsh::model::geo::addLine(6, 8, 10); // 直线 10:点6->8
曲线 9(样条)和 10(直线)是两条内部曲线——它们将整个区域分割为三个子面。如果不使用 Compound,这两条曲线会强制网格在沿线处产生节点对齐。
// 子面 1:由曲线 {5, 6, 9, 4} 围成(顶部区域)
gmsh::model::geo::addCurveLoop({5, 6, 9, 4}, 11);
gmsh::model::geo::addSurfaceFilling({11}, 1);
// 子面 5:由曲线 {-9, 3, 10, 7} 围成(中间区域)
// 注意符号:-9 表示取曲线 9 的反向
gmsh::model::geo::addCurveLoop({-9, 3, 10, 7}, 13);
gmsh::model::geo::addSurfaceFilling({13}, 5);
// 子面 10:由曲线 {-10, 2, 1, 8} 围成(底部区域)
gmsh::model::geo::addCurveLoop({-10, 2, 1, 8}, 15);
gmsh::model::geo::addSurfaceFilling({15}, 10);
gmsh::model::geo::synchronize();
这里使用 addSurfaceFilling()(填充曲面)而非 addPlaneSurface()(平面曲面),因为这些面不在同一平面上(点 3 和点 4 的 Z 坐标分别为 0.5 和 0.4)。synchronize() 将 GEO 模块的几何数据同步到 Gmsh 内核模型。
10.3.3 设置 Compound 约束
这是本章的核心——通过 setCompound() 声明复合实体:
// === Compound Curve (维度=1): 将曲线 2,3,4 合并为一条逻辑曲线 ===
// 效果:网格可以跨越点 8 和点 9,不会在这些中间点处强制放置节点
gmsh::model::mesh::setCompound(1, {2, 3, 4});
// === Compound Curve (维度=1): 将曲线 6,7,8 合并为一条逻辑曲线 ===
// 效果:网格可以跨越点 7 和点 6
gmsh::model::mesh::setCompound(1, {6, 7, 8});
// === Compound Surface (维度=2): 将面 1,5,10 合并为一张逻辑曲面 ===
// 效果:网格可以自由跨越内部曲线 9 和 10,
// 在接缝处的节点自动一一对应(conformal matching)
gmsh::model::mesh::setCompound(2, {1, 5, 10});
setCompound() 签名:
void gmsh::model::mesh::setCompound(const int dim,
const std::vector<int> & tags);
dim:实体维度(1=曲线,2=曲面,3=体)tags:要合并的实体标签列表
三条 Compound 约束的效果叠加:外侧边界被合并为连续曲线(消除中间点 6,7,8,9 的节点强制放置),三个子面被合并为一整张曲面(消除内部曲线 9 和 10 的分割效应)。最终 Gmsh 将这三块区域当作一张完整的曲面来剖分。
10.3.4 网格生成、Physical Group 与后处理
gmsh::model::mesh::generate(2);
gmsh::write("ch10_result.msh");
生成 2D 网格后输出为 .msh 文件。观察输出的网格即可验证:内部曲线 9 和 10 处不再有节点强制对齐的痕迹,网格单元可以自由跨越原来的面边界。
如果在使用 Compound 的同时还需要对原始实体施加边界条件,可以仍然通过 addPhysicalGroup 基于原始实体标签操作:
// 即使使用了 Compound,仍可基于原始面标签定义 Physical Group
gmsh::model::addPhysicalGroup(2, {1, 5, 10}, 100, "compound_surface");
gmsh::model::addPhysicalGroup(1, {1, 5}, 200, "outer_boundary");
注意:当 Mesh.CompoundClassify = 1(默认值)时,网格元素会被回分类到原始几何实体上,因此 Physical Group 可以正常工作。
10.4 完整可运行代码
// ============================================================================
// 第 10 章完整示例:Gmsh 复合实体(Compound)跨面一致网格划分
// 编译: g++ -o ch10 ch10.cpp -I/path/to/gmsh/include -L/path/to/gmsh/lib -lgmsh
// ============================================================================
#include <set>
#include <gmsh.h>
int main(int argc, char **argv) {
gmsh::initialize(argc, argv);
gmsh::model::add("ch10_compound");
double lc = 0.1;
// =================== 1. 定义几何点 ===================
gmsh::model::geo::addPoint(0.0, 0.0, 0.0, lc, 1);
gmsh::model::geo::addPoint(1.0, 0.0, 0.0, lc, 2);
gmsh::model::geo::addPoint(1.0, 1.0, 0.5, lc, 3);
gmsh::model::geo::addPoint(0.0, 1.0, 0.4, lc, 4);
gmsh::model::geo::addPoint(0.3, 0.2, 0.0, lc, 5);
gmsh::model::geo::addPoint(0.0, 0.01, 0.01, lc, 6);
gmsh::model::geo::addPoint(0.0, 0.02, 0.02, lc, 7);
gmsh::model::geo::addPoint(1.0, 0.05, 0.02, lc, 8);
gmsh::model::geo::addPoint(1.0, 0.32, 0.02, lc, 9);
// =================== 2. 定义曲线 ===================
gmsh::model::geo::addLine(1, 2, 1);
gmsh::model::geo::addLine(2, 8, 2);
gmsh::model::geo::addLine(8, 9, 3);
gmsh::model::geo::addLine(9, 3, 4);
gmsh::model::geo::addLine(3, 4, 5);
gmsh::model::geo::addLine(4, 7, 6);
gmsh::model::geo::addLine(7, 6, 7);
gmsh::model::geo::addLine(6, 1, 8);
gmsh::model::geo::addSpline({7, 5, 9}, 9); // 内部样条曲线
gmsh::model::geo::addLine(6, 8, 10); // 内部直线
// =================== 3. 定义三个子面 ===================
// 子面 1(顶部),由曲线 5,6,9,4 围成
gmsh::model::geo::addCurveLoop({5, 6, 9, 4}, 11);
gmsh::model::geo::addSurfaceFilling({11}, 1);
// 子面 5(中部),由曲线 -9,3,10,7 围成(-9 表示曲线 9 反向)
gmsh::model::geo::addCurveLoop({-9, 3, 10, 7}, 13);
gmsh::model::geo::addSurfaceFilling({13}, 5);
// 子面 10(底部),由曲线 -10,2,1,8 围成
gmsh::model::geo::addCurveLoop({-10, 2, 1, 8}, 15);
gmsh::model::geo::addSurfaceFilling({15}, 10);
gmsh::model::geo::synchronize();
// =================== 4. 设置 Compound 约束 ===================
// Compound Curve: 将边界曲线合并,消除中间点 8,9 处的节点强制放置
gmsh::model::mesh::setCompound(1, {2, 3, 4});
// Compound Curve: 消除中间点 7,6 处的节点强制放置
gmsh::model::mesh::setCompound(1, {6, 7, 8});
// Compound Surface: 将三张子面合并为一整张曲面剖分
// 网格可自由跨越内部曲线 9 和 10,接缝处自动节点匹配
gmsh::model::mesh::setCompound(2, {1, 5, 10});
// =================== 5. 选项设置 ===================
// CompoundClassify = 1 (默认): 网格元素回分类到原始几何实体
gmsh::option::setNumber("Mesh.CompoundClassify", 1);
// CompoundMeshSizeFactor = 0.5 (默认): 初始剖分网格尺寸为全局 lc 的 0.5 倍
gmsh::option::setNumber("Mesh.CompoundMeshSizeFactor", 0.5);
// =================== 6. 生成网格并输出 ===================
gmsh::model::mesh::generate(2);
gmsh::write("ch10_result.msh");
// =================== 7. 定义 Physical Group(基于原始面标签) ===================
gmsh::model::addPhysicalGroup(2, {1, 5, 10}, 100, "compound_surface");
gmsh::model::addPhysicalGroup(1, {1, 5}, 200, "outer_boundary");
// =================== 8. 显示 GUI ===================
std::set<std::string> args(argv, argv + argc);
if (!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
编译运行:
g++ -o ch10 ch10.cpp -I/path/to/gmsh/include -L/path/to/gmsh/lib -lgmsh
./ch10 # 打开 GUI 查看跨面网格效果
./ch10 -nopopup # 仅生成 ch10_result.msh
10.5 关键 API 速查表
Compound 相关 API
| 函数 | 签名 | 说明 |
|---|---|---|
mesh::setCompound |
setCompound(int dim, vector<int> tags) |
将 dim 维度的 tags 实体合并为复合实体。dim=1 为曲线,dim=2 为曲面,dim=3 为体 |
geo::addSurfaceFilling |
addSurfaceFilling(vector<int> wireTags, int tag) |
由闭合线框创建填充曲面(适用于非平面曲面),wireTags 中的负数表示取曲线反向 |
Compound 相关选项
| 选项名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Mesh.CompoundClassify |
integer | 1 | 网格元素在 Compound 上的归属方式:0=留在新离散实体上,1=回分类到原始几何实体 |
Mesh.CompoundMeshSizeFactor |
float | 0.5 | Compound 初始独立剖分阶段的网格尺寸缩放因子。默认 0.5 意味着初始网格为全局 lc 的一半,以提供足够细的离散用于重参数化 |
相关几何 API(用于构造多面域)
| 函数 | 说明 |
|---|---|
geo::addCurveLoop({c1, c2, ...}, tag) |
由有序曲线列表创建闭合线框(CurveLoop),负数表示反向。线框是构成面的边界 |
geo::addPlaneSurface({cl1, ...}, tag) |
由线框创建平面曲面。仅适用于所有点共面的情况 |
geo::addSurfaceFilling({cl1, ...}, tag) |
由线框创建填充曲面。适用于任意空间曲面(含翘曲/非平面),本章示例因其非平面特性必须使用此函数 |
geo::addSpline({p1, p2, ...}, tag) |
通过控制点创建样条曲线 |
geo::synchronize() |
将 GEO 模块的几何数据同步到 Gmsh 内核模型。在任何网格操作之前必须调用 |
Physical Group vs Compound 对比
| 操作 | API 调用 | 对网格的影响 |
|---|---|---|
| Physical Group | addPhysicalGroup(dim, {tags}, pgTag) |
仅标记元素,不改变网格拓扑 |
| Compound | mesh::setCompound(dim, {tags}) |
合并实体,改变网格拓扑(消除内部边界) |
10.6 常见问题与注意事项
-
Compound 的实体必须同维度:
setCompound(2, {1, 5, 10})将所有面放在一起,不能混入曲线或体。如果需要跨维度合并,应分别调用不同维度的setCompound。 -
重参数化失败的应对:如果形状过于复杂导致网格生成失败(表现为 Gmsh 报错或生成的网格有明显空洞),说明融合后的形状无法做有效的离散重参数化。解决方法:
- 调整初始几何分区,使每个子面更"规则"(接近矩形或圆盘拓扑)
- 降低
Mesh.CompoundMeshSizeFactor(如设为 0.3),使初始网格更细 -
参考
t13.cpp的思路:先用classifySurfaces+createGeometry将复杂面拆分为可参数化的子面片,再使用 Compound -
CompoundClassify 与高阶网格的兼容性:当
Mesh.CompoundClassify = 1时,回分类操作与高阶网格(2 阶及以上)不兼容。如果使用高阶单元,应将此选项设为 0。 -
Compound 与 Transfinite(结构化)网格:
setCompound的 Compound Surface 可以与 Transfinite 网格配合使用——先声明 Compound,再对合并后的实体施加setTransfiniteCurve和setTransfiniteSurface。这在需要跨面结构化网格时非常有用。 -
Compound 不能替代 Boolean 运算:Compound 是在网格层面对实体进行逻辑合并,并不改变底层的几何模型。如果需要对几何体做布尔并/交/差运算,应使用 OpenCASCADE 内核(
gmsh::model::occ),而非 GEO 内核的 Compound。 -
Compound Mesh Size Factor 的调优:如果最终网格在 Compound 内部边界处出现质量问题,可以尝试将
Mesh.CompoundMeshSizeFactor减小(如 0.3 或 0.2),使初始独立剖分更细,为重参数化提供更精确的离散基础。但注意过小的值会增加步骤 1 的内存和时间开销。 -
多组 Compound 的独立性:可以定义多个 Compound,例如分三组分别调用
setCompound(2, {...})。各组之间仍然保持各自的边界。同一实体不能同时属于多个 Compound。 -
查看 Compound 效果:生成网格后,在 Gmsh GUI 中可以通过
Tools -> Options -> Mesh -> Visibility切换显示不同实体上的网格,观察网格是否平滑跨越了原始的内部边界。