Skip to content

第10章 复合实体(Compound):跨面一致网格划分


10.1 学习目标

  • 理解复合实体(Compound)的概念:将多个几何实体合并为一个逻辑整体进行网格划分,实现跨边界的一致节点匹配
  • 掌握 mesh::setCompound() 的三个用法层面:Compound Curve(线复合)、Compound Surface(面复合)、Compound Volume(体复合)
  • 理解 Gmsh 处理 Compound 的内部五步流程:独立剖分、离散融合、重参数化、统一剖分、可选回分类
  • 学会区分 Compound 与 Physical Group 的本质差异:前者改变网格拓扑,后者仅做逻辑分组
  • 掌握 Mesh.CompoundClassifyMesh.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 常见问题与注意事项

  1. Compound 的实体必须同维度setCompound(2, {1, 5, 10}) 将所有面放在一起,不能混入曲线或体。如果需要跨维度合并,应分别调用不同维度的 setCompound

  2. 重参数化失败的应对:如果形状过于复杂导致网格生成失败(表现为 Gmsh 报错或生成的网格有明显空洞),说明融合后的形状无法做有效的离散重参数化。解决方法:

  3. 调整初始几何分区,使每个子面更"规则"(接近矩形或圆盘拓扑)
  4. 降低 Mesh.CompoundMeshSizeFactor(如设为 0.3),使初始网格更细
  5. 参考 t13.cpp 的思路:先用 classifySurfaces + createGeometry 将复杂面拆分为可参数化的子面片,再使用 Compound

  6. CompoundClassify 与高阶网格的兼容性:当 Mesh.CompoundClassify = 1 时,回分类操作与高阶网格(2 阶及以上)不兼容。如果使用高阶单元,应将此选项设为 0。

  7. Compound 与 Transfinite(结构化)网格setCompound 的 Compound Surface 可以与 Transfinite 网格配合使用——先声明 Compound,再对合并后的实体施加 setTransfiniteCurvesetTransfiniteSurface。这在需要跨面结构化网格时非常有用。

  8. Compound 不能替代 Boolean 运算:Compound 是在网格层面对实体进行逻辑合并,并不改变底层的几何模型。如果需要对几何体做布尔并/交/差运算,应使用 OpenCASCADE 内核(gmsh::model::occ),而非 GEO 内核的 Compound。

  9. Compound Mesh Size Factor 的调优:如果最终网格在 Compound 内部边界处出现质量问题,可以尝试将 Mesh.CompoundMeshSizeFactor 减小(如 0.3 或 0.2),使初始独立剖分更细,为重参数化提供更精确的离散基础。但注意过小的值会增加步骤 1 的内存和时间开销。

  10. 多组 Compound 的独立性:可以定义多个 Compound,例如分三组分别调用 setCompound(2, {...})。各组之间仍然保持各自的边界。同一实体不能同时属于多个 Compound。

  11. 查看 Compound 效果:生成网格后,在 Gmsh GUI 中可以通过 Tools -> Options -> Mesh -> Visibility 切换显示不同实体上的网格,观察网格是否平滑跨越了原始的内部边界。