第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)是一类结构化网格约束,它允许用户:
- 精确指定每条边上的节点数(通过
setTransfiniteCurve) - 控制节点分布规律——均匀分布或等比级数分布
- 在曲面的参数平面内生成结构化网格(通过
setTransfiniteSurface) - 将三角形合并为四边形以获得全四边形网格(通过
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 常见问题与注意事项
-
角点数量与面边数不匹配:
setTransfiniteSurface的角点数必须等于面的边界曲线环中的曲线数量。当边界曲线数与角点数不一致时,Gmsh 报错且不生成网格。 -
Transfinite 网格的适用面:不是所有面都适合 Transfinite 剖分。面必须是拓扑四边形(或三角形)。对于复杂形状,需要使用
geo::remove拆解为多个四边形子面。 -
复合边上的节点共享:当多条曲线首尾相连构成一条边时,共享端点处的节点只计一次。计算总节点数时应扣除共享点数量。
-
Recombine 的前提条件:
setRecombine要求面上的三角形数量为偶数。对于 Transfinite Surface,此条件自动满足。对于自由网格,建议先检查三角形数量。 -
Progression 的符号:
ratio < 0时分布方向反转,但绝对值仍然控制疏密程度。判断聚集方向的关键是牢记"正值向终点聚集,负值向起点聚集"。 -
光顺与边界节点:椭圆光顺默认不移动边界节点。如需调整边界节点位置,需额外设置
Mesh.SmoothingBoundary = 1。 -
混合使用:
setTransfiniteCurve和setTransfiniteSurface不排斥其他网格约束(如setSize)。在 Transfinite 曲面上,节点分布由 Transfinite 约束覆盖;自由面上,由尺寸场控制。
下一章预告:第7章将介绍后处理功能——如何操作 View(视图)存储和可视化仿真结果,包括标量场、矢量场和张量场的可视化。