Skip to content

第6章 网格控制进阶:Transfinite 分布与结构化网格


6.1 学习目标

  • 掌握 Transfinite Curve(跨界曲线)的节点分布控制:均匀分布与等比级数(Progression)分布
  • 理解 Transfinite Surface(跨界曲面)的工作原理,学会指定角点列表与三角剖分策略
  • 掌握 setRecombine 将三角形合并为四边形的用法,实现全四边形网格
  • 学会使用 geo::remove 删除已有实体并重建几何拓扑
  • 了解椭圆光顺(Elliptic Smoothing)对结构化网格质量的提升作用

6.2 核心概念说明

6.2.1 什么是 Transfinite 网格

在前几章中,网格节点的分布由网格尺寸场(mesh size field)自动决定。对于需要精确控制边界节点位置和数量的场景(例如 CFD 边界层、接触面配对、周期性边界),自动分布往往不能满足要求。

Transfinite 网格(Transfinite Mesh)是一类结构化网格约束,它允许用户:

  1. 精确指定每条边上的节点数(通过 setTransfiniteCurve
  2. 控制节点分布规律——均匀分布或等比级数分布
  3. 在曲面的参数平面内生成结构化网格(通过 setTransfiniteSurface
  4. 将三角形合并为四边形以获得全四边形网格(通过 setRecombine

6.2.2 Transfinite Curve 的节点分布

setTransfiniteCurve 有三种基本用法:

用法 效果
setTransfiniteCurve(tag, nNodes) 边上均匀放置 nNodes 个节点(含两端点)
setTransfiniteCurve(tag, nNodes, "Progression", ratio) 等比级数分布,ratio 控制疏密方向与程度
setTransfiniteCurve(tag, nNodes, "Bump", ratio) 中点附近聚集(ratio > 1)或端点附近聚集(ratio < 1)

Progression 的疏密规律(设 ratio 的绝对值为 r):

  • r > 1:节点向曲线终点聚集,相邻段长度递增
  • r < -1:节点向曲线起点聚集(反向等比)
  • 0 < r < 1-1 < r < 0:反向分布,即节点向相反方向聚集

ratio = 1.2 为例:终点段长度 = 起点段长度 x 1.2^(n-1),节点从起点到终点逐渐变疏。

6.2.3 Transfinite Surface 与角点

setTransfiniteSurface 在曲面的参数平面内使用跨界插值(transfinite interpolation)连接各边上的节点,形成结构化四边形网格:

  • 四边形面:Gmsh 可自动识别四个角点,无需手动指定
  • 多边形面(5条及以上边):必须手动指定角点列表,Gmsh 将面划分为若干个四边形子区域
  • 三角形面:支持 "Left""Right""Alternate" 三种三角剖分策略

6.2.4 Recombine 与四边形网格

setRecombine 作用于面或体,将其内部的三角形合并为四边形(或棱柱)。对于 Transfinite Surface,通常搭配 setRecombine 使用,以生成纯四边形网格。

适用条件:面上的三角形数量为偶数,才能全部合并为四边形。大多数 Transfinite 网格自动满足此条件。


6.3 C++ 代码逐段讲解

6.3.1 基础几何:构建矩形面

从 t1 教程的矩形面出发——一个 0.1 x 0.3 的矩形,位于 z=0 平面:

#include <set>
#include <gmsh.h>

int main(int argc, char **argv) {
  gmsh::initialize();
  gmsh::model::add("t6");

  double lc = 1e-2;
  gmsh::model::geo::addPoint(0, 0, 0, lc, 1);
  gmsh::model::geo::addPoint(.1, 0, 0, lc, 2);
  gmsh::model::geo::addPoint(.1, .3, 0, lc, 3);
  gmsh::model::geo::addPoint(0, .3, 0, lc, 4);
  gmsh::model::geo::addLine(1, 2, 1);
  gmsh::model::geo::addLine(3, 2, 2);   // 注意方向:从点3到点2
  gmsh::model::geo::addLine(3, 4, 3);
  gmsh::model::geo::addLine(4, 1, 4);
  gmsh::model::geo::addCurveLoop({4, 1, -2, 3}, 1);
  gmsh::model::geo::addPlaneSurface({1}, 1);

曲线环 {4, 1, -2, 3} 中,曲线2因定义方向(3->2)与环路方向相反,故取负值。曲线4(4->1)和曲线3(3->4)方向构成闭环:4->1 (线4) -> 1->2 (线1) -> 2->3 (线2反向即3->2) -> 3->4 (线3)。

6.3.2 删除与重建:geo::remove 的使用

接下来删除矩形面本身和左侧边(曲线4),并用三段新线替换左侧边,形成更复杂的多边形面:

  // 删除面1(dim=2, tag=1)和曲线4(dim=1, tag=4)
  gmsh::model::geo::remove({{2, 1}, {1, 4}});

geo::remove 接受一个 vector<pair<int,int>>,每个 {dim, tag} 指定要删除的实体。删除面同时会解除面与其边界曲线的关联,但曲线本身不会自动删除——需要显式指定。

  // 在左侧添加两个新点,将原来的一条线替换为三段线
  int p1 = gmsh::model::geo::addPoint(-0.05, 0.05, 0, lc);
  int p2 = gmsh::model::geo::addPoint(-0.05, 0.1, 0, lc);
  int l1 = gmsh::model::geo::addLine(1, p1);
  int l2 = gmsh::model::geo::addLine(p1, p2);
  int l3 = gmsh::model::geo::addLine(p2, 4);

左侧新轮廓由三段线组成:从点1到p1、p1到p2、p2到点4。注意 addPoint 省略了 tag 参数时,Gmsh 自动分配标签。

  // 用新的边界重建面和曲线环
  gmsh::model::geo::addCurveLoop({2, -1, l1, l2, l3, -3}, 2);
  gmsh::model::geo::addPlaneSurface({-2}, 1);

新曲线环的序列为:线2 -> 线1反向 -> l1 -> l2 -> l3 -> 线3反向。面标签设为1(覆盖之前删除的面)。曲线环标签前的负号 {-2} 表示反转法向(当 Gmsh 自动判断的法向与期望相反时使用)。

6.3.3 Transfinite Curve:节点数控制

setTransfiniteCurve 的基本形式指定曲线上的均匀节点数:

  // 在曲线2上强制放置20个均匀节点(含端点)
  gmsh::model::geo::mesh::setTransfiniteCurve(2, 20);

对于由多条曲线首尾相连组成的"复合边"(如 l1 + l2 + l3 构成左侧边),需要分别为各段设置节点数。共享端点不会重复计算,因此节点总数 = sum(各段节点数) - (共享点数):

  // l1=6节点, l2=6节点, l3=10节点 → 总共 6+6+10-2(两个共享点p1,p2) = 20节点
  gmsh::model::geo::mesh::setTransfiniteCurve(l1, 6);
  gmsh::model::geo::mesh::setTransfiniteCurve(l2, 6);
  gmsh::model::geo::mesh::setTransfiniteCurve(l3, 10);

注意:左右两侧边均为 20 个节点,这使得对面(曲线1和曲线3的对应关系)在 Transfinite 插值时形成一致的行数。

6.3.4 Transfinite Curve:等比级数分布

对于上下两条长边(曲线1和曲线3),使用等比级数分布将节点向几何特征区域聚集:

  // 曲线1:30个节点,等比级数 -1.2 → 节点向起点(左侧)聚集
  // 负号表示反向:ratio=-1.2 即节点向曲线起点方向聚集
  gmsh::model::geo::mesh::setTransfiniteCurve(1, 30, "Progression", -1.2);

  // 曲线3:30个节点,等比级数 1.2 → 节点向终点聚集
  gmsh::model::geo::mesh::setTransfiniteCurve(3, 30, "Progression", 1.2);
曲线 ratio 效果
曲线1 -1.2 节点向起点(点1方向)聚集,比值
曲线3 1.2 节点向终点聚集,比值 r=1.2

曲线1与曲线3形成对称的疏密分布,这对于需要局部细化的仿真场景(如裂纹尖端、接触区域)非常有用。

6.3.5 Transfinite Surface:多边界面与角点指定

由于当前面的边界由 6 条曲线组成(线2, 线1, l1, l2, l3, 线3),属于多边形面,必须手动指定角点:

  // 指定四个角点:点1(左下), 点2(右下), 点3(右上), 点4(左上)
  gmsh::model::geo::mesh::setTransfiniteSurface(1, "Left", {1, 2, 3, 4});

角点列表的顺序决定了结构化网格的分块方向。"Left" 指定了三角形剖分的策略(将四边形沿对角线剖分为两个三角形时,对角线向左倾斜)。

三角剖分策略(仅对非四边形的面有效):

策略 描述
"Left" 对角线向左倾斜(默认)
"Right" 对角线向右倾斜
"Alternate" / "AlternateLeft" 交替方向剖分(首排向左)
"AlternateRight" 交替方向剖分(首排向右)

6.3.6 Recombine:三角形合并为四边形

  // 将面1的三角形单元合并为四边形
  gmsh::model::geo::mesh::setRecombine(2, 1);

setRecombine 的第一个参数是维度(dim),2 表示对面进行重组。这里的"重组"含义是:将网格生成器产生的三角形对合并为四边形。对于 Transfinite 网格,三角形数量天然为偶数,因此重组总是成功的。

6.3.7 自动角点检测:四边形面示例

当面的边界仅有 4 条(或 3 条)曲线时,Gmsh 可以自动识别角点,无需手动指定:

  // 创建一个简单四边形面,演示自动角点检测
  gmsh::model::geo::addPoint(0.2, 0.2, 0, 1.0, 7);
  gmsh::model::geo::addPoint(0.2, 0.1, 0, 1.0, 8);
  gmsh::model::geo::addPoint(0.25, 0.2, 0, 1.0, 9);
  gmsh::model::geo::addPoint(0.3, 0.1, 0, 1.0, 10);
  gmsh::model::geo::addLine(8, 10, 10);
  gmsh::model::geo::addLine(10, 9, 11);
  gmsh::model::geo::addLine(9, 7, 12);
  gmsh::model::geo::addLine(7, 8, 13);
  gmsh::model::geo::addCurveLoop({10, 11, 12, 13}, 14);
  gmsh::model::geo::addPlaneSurface({14}, 15);

  // 四条边上各放 10 个节点
  for (int i = 10; i <= 13; i++)
    gmsh::model::geo::mesh::setTransfiniteCurve(i, 10);

  // 无需指定角点——Gmsh 自动识别四边形面的四个角
  gmsh::model::geo::mesh::setTransfiniteSurface(15);

若想尝试不同的三角剖分策略,可将上句替换为:

  // gmsh::model::geo::mesh::setTransfiniteSurface(15, "Alternate");

6.3.8 椭圆光顺与网格生成

最后,通过椭圆光顺(elliptic smoother)对网格进行优化,使单元更加规则:

  // 应用椭圆光顺,迭代 100 步
  gmsh::option::setNumber("Mesh.Smoothing", 100);

  gmsh::model::geo::synchronize();
  gmsh::model::mesh::generate(2);
  gmsh::write("t6.msh");

"Mesh.Smoothing" 的值表示光顺迭代步数。100 步对于大多数情况已经足够使结构化网格趋于正交。该选项对自由网格(非 Transfinite)同样有效,但对结构化网格的提升最为显著。

提示:对于高曲率曲面或大变形网格,过度的光顺可能导致边界附近的单元质量下降。此时可适当减少迭代步数(如 10-50)。


6.4 完整可运行代码

#include <set>
#include <gmsh.h>

int main(int argc, char **argv) {
  gmsh::initialize();
  gmsh::model::add("t6");

  double lc = 1e-2;
  // ===== 基础矩形面 =====
  gmsh::model::geo::addPoint(0, 0, 0, lc, 1);
  gmsh::model::geo::addPoint(.1, 0, 0, lc, 2);
  gmsh::model::geo::addPoint(.1, .3, 0, lc, 3);
  gmsh::model::geo::addPoint(0, .3, 0, lc, 4);
  gmsh::model::geo::addLine(1, 2, 1);
  gmsh::model::geo::addLine(3, 2, 2);
  gmsh::model::geo::addLine(3, 4, 3);
  gmsh::model::geo::addLine(4, 1, 4);
  gmsh::model::geo::addCurveLoop({4, 1, -2, 3}, 1);
  gmsh::model::geo::addPlaneSurface({1}, 1);

  // ===== 删除并重建左侧边界 =====
  gmsh::model::geo::remove({{2, 1}, {1, 4}});

  int p1 = gmsh::model::geo::addPoint(-0.05, 0.05, 0, lc);
  int p2 = gmsh::model::geo::addPoint(-0.05, 0.1, 0, lc);
  int l1 = gmsh::model::geo::addLine(1, p1);
  int l2 = gmsh::model::geo::addLine(p1, p2);
  int l3 = gmsh::model::geo::addLine(p2, 4);

  gmsh::model::geo::addCurveLoop({2, -1, l1, l2, l3, -3}, 2);
  gmsh::model::geo::addPlaneSurface({-2}, 1);

  // ===== Transfinite Curve:节点分布 =====
  // 右侧边:20个均匀节点
  gmsh::model::geo::mesh::setTransfiniteCurve(2, 20);

  // 左侧三段线:合计20节点
  gmsh::model::geo::mesh::setTransfiniteCurve(l1, 6);
  gmsh::model::geo::mesh::setTransfiniteCurve(l2, 6);
  gmsh::model::geo::mesh::setTransfiniteCurve(l3, 10);

  // 上下边:30节点等比级数分布
  gmsh::model::geo::mesh::setTransfiniteCurve(1, 30, "Progression", -1.2);
  gmsh::model::geo::mesh::setTransfiniteCurve(3, 30, "Progression", 1.2);

  // ===== Transfinite Surface:指定角点 =====
  gmsh::model::geo::mesh::setTransfiniteSurface(1, "Left", {1, 2, 3, 4});

  // ===== Recombine:合并三角形为四边形 =====
  gmsh::model::geo::mesh::setRecombine(2, 1);

  // ===== 第二个面:自动角点检测 =====
  gmsh::model::geo::addPoint(0.2, 0.2, 0, 1.0, 7);
  gmsh::model::geo::addPoint(0.2, 0.1, 0, 1.0, 8);
  gmsh::model::geo::addPoint(0.25, 0.2, 0, 1.0, 9);
  gmsh::model::geo::addPoint(0.3, 0.1, 0, 1.0, 10);
  gmsh::model::geo::addLine(8, 10, 10);
  gmsh::model::geo::addLine(10, 9, 11);
  gmsh::model::geo::addLine(9, 7, 12);
  gmsh::model::geo::addLine(7, 8, 13);
  gmsh::model::geo::addCurveLoop({10, 11, 12, 13}, 14);
  gmsh::model::geo::addPlaneSurface({14}, 15);

  for (int i = 10; i <= 13; i++)
    gmsh::model::geo::mesh::setTransfiniteCurve(i, 10);
  gmsh::model::geo::mesh::setTransfiniteSurface(15);

  // ===== 椭圆光顺 =====
  gmsh::option::setNumber("Mesh.Smoothing", 100);

  // ===== 网格生成与输出 =====
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::generate(2);
  gmsh::write("t6.msh");

  // 启动 GUI(传 -nopopup 可跳过)
  std::set<std::string> args(argv, argv + argc);
  if (!args.count("-nopopup")) gmsh::fltk::run();

  gmsh::finalize();
  return 0;
}

编译与运行

g++ -o t6 t6.cpp -lgmsh -I/path/to/gmsh/include -L/path/to/gmsh/lib
./t6

运行后将看到两个面:主面(多边形面经四边形重组,节点向左侧聚集)和右上方的四边形面(均匀结构化网格)。若不传 -nopopup,Gmsh GUI 将自动弹出显示结果。


6.5 关键 API 速查表

实体删除

API 签名 说明
geo::remove ({items}, recursive) 删除实体列表,recursive 默认 false

注意geo::remove 第一个参数也是 vector<pair<int,int>> 格式。删除面时关联的曲线环也会解除关联。

Transfinite 曲线

API 签名 说明
mesh::setTransfiniteCurve (tag, nNodes) 均匀分布 nNodes 个节点
mesh::setTransfiniteCurve (tag, nNodes, "Progression", ratio) 等比级数分布
mesh::setTransfiniteCurve (tag, nNodes, "Bump", ratio) 鼓包分布

Transfinite 曲面

API 签名 说明
mesh::setTransfiniteSurface (tag) 自动检测角点(仅限 3/4 边面)
mesh::setTransfiniteSurface (tag, orientation, {cornerPts...}) 手动指定角点列表,orientation 可选 "Left" / "Right" / "Alternate"

四边形重组与光顺

API 签名 说明
mesh::setRecombine (dim, tag) 将三角形合并为四边形(dim=2 对面)
mesh::setRecombine (dim, tag, angle) 指定最大重组角度(度,默认 45)
option::setNumber ("Mesh.Smoothing", n) 椭圆光顺迭代步数

几何构建(本章涉及的复用 API)

API 说明
geo::addPoint(x, y, z, meshSize, [tag]) 添加点,tag 可省略由系统自动分配
geo::addLine(startTag, endTag, [tag]) 创建直线
geo::addCurveLoop({curveTags}, [tag]) 创建曲线环
geo::addPlaneSurface({loopTags}, [tag]) 创建平面面

6.6 常见问题与注意事项

  1. 角点数量与面边数不匹配setTransfiniteSurface 的角点数必须等于面的边界曲线环中的曲线数量。当边界曲线数与角点数不一致时,Gmsh 报错且不生成网格。

  2. Transfinite 网格的适用面:不是所有面都适合 Transfinite 剖分。面必须是拓扑四边形(或三角形)。对于复杂形状,需要使用 geo::remove 拆解为多个四边形子面。

  3. 复合边上的节点共享:当多条曲线首尾相连构成一条边时,共享端点处的节点只计一次。计算总节点数时应扣除共享点数量。

  4. Recombine 的前提条件setRecombine 要求面上的三角形数量为偶数。对于 Transfinite Surface,此条件自动满足。对于自由网格,建议先检查三角形数量。

  5. Progression 的符号ratio < 0 时分布方向反转,但绝对值仍然控制疏密程度。判断聚集方向的关键是牢记"正值向终点聚集,负值向起点聚集"。

  6. 光顺与边界节点:椭圆光顺默认不移动边界节点。如需调整边界节点位置,需额外设置 Mesh.SmoothingBoundary = 1

  7. 混合使用setTransfiniteCurvesetTransfiniteSurface 不排斥其他网格约束(如 setSize)。在 Transfinite 曲面上,节点分布由 Transfinite 约束覆盖;自由面上,由尺寸场控制。


下一章预告:第7章将介绍后处理功能——如何操作 View(视图)存储和可视化仿真结果,包括标量场、矢量场和张量场的可视化。