第3章 变换与拉伸几何
3.1 学习目标
- 掌握Gmsh中几何变换(transformation)的基本操作:平移、旋转、缩放、镜像
- 理解实体列表的(dim, tag)表示法,以及
copy()与直接变换的区别 - 学会通过表面环(surface loop)构建实体体积(volume)
- 掌握
extrude()函数:从2D面拉伸为3D体,并控制层数与网格类型 - 理解
revolve()旋转拉伸和twist()扭转拉伸的用法与适用场景
3.2 核心概念
3.2.1 几何变换(Geometric Transformation)
在第2章中,我们通过逐点定义的方式构建了几何体。当需要生成规则排列的重复结构时,这种方式显得繁琐且容易出错。Gmsh提供的几何变换工具可以直接对已有实体进行操作,大幅简化复杂几何的构建过程。
所有几何变换函数的第一个参数都是实体列表,表示为一个std::vector<std::pair<int, int>>,其中每个(dim, tag)对唯一标识一个实体:
- dim = 0:点
- dim = 1:线
- dim = 2:面
- dim = 3:体
3.2.2 拉伸(Extrusion)
拉伸是将低维实体沿某个方向扩展为高维实体的操作: - 沿向量(dx, dy, dz)拉伸线得到面 - 沿向量(dx, dy, dz)拉伸面得到体
在网格划分阶段,拉伸不仅可以生成几何体,还可以同时控制拉伸方向的网格分布——通过指定Layers参数来设置层数、每层细分数量以及每层高度。
3.2.3 表面环与体(Surface Loop and Volume)
Gmsh中构建体的逻辑与构建面完全类似: - 曲线环(Curve Loop)由一系列首尾相连的曲线组成,围成一个面 - 表面环(Surface Loop)由一系列闭合的面组成,围成一个体
3.3 基础回顾:来自第2章的模板
本章的所有代码都建立在第2章创建的矩形面基础之上——一个位于z=0平面的矩形面(标签为1),由四个角点(0,0,0)、(0.1,0,0)、(0.1,0.3,0)、(0,0.3,0)围成,四条边的标签分别为1、2、3、4。代码片段如下(片段A):
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::synchronize();
gmsh::model::addPhysicalGroup(1, {1, 2, 4}, 5);
gmsh::model::addPhysicalGroup(2, {1}, -1, "My surface");
本章后续所有代码片段都从片段A之后继续编写,不再重复上述部分。完整代码见3.7节。
3.4 Part A:几何变换
3.4.1 translate() —— 平移
translate()函数将指定实体沿向量(dx, dy, dz)平移。以下代码添加新点5并将其向左平移0.02:
// 添加点5,然后平移到新位置
gmsh::model::geo::addPoint(0, .4, 0, lc, 5);
gmsh::model::geo::translate({{0, 5}}, -0.02, 0, 0);
这里{{0, 5}}表示维度为0、标签为5的点。平移后,点5的x坐标从0变为-0.02。
注意:Gmsh中坐标没有单位——数值本身不代表米或毫米,用户需要自行赋予物理含义。如果你在做毫米级的CAE模型,可以把1.0当作1mm。
3.4.2 rotate() —— 绕轴旋转
rotate()的完整签名为:
rotate({entities}, x, y, z, ax, ay, az, angle);
其中(x, y, z)是旋转轴上的一点,(ax, ay, az)是旋转轴的方向向量,angle是旋转角(弧度制)。
// 将点5绕z轴旋转 -Pi/4,旋转中心为(0, 0.3, 0)
gmsh::model::geo::rotate({{0, 5}}, 0, 0.3, 0, 0, 0, 1, -M_PI / 4);
这条语句将点5绕经过(0, 0.3, 0)且方向为(0, 0, 1)(即z轴)的旋转轴旋转-45度。
API速记:旋转角度为正时按右手定则绕轴正方向旋转。若旋转角度大于Pi,请使用OpenCASCADE几何内核(第4章介绍),内置geo内核仅支持角度小于Pi的旋转。
3.4.3 copy() —— 复制实体
copy()是一个非常重要的函数。与直接变换不同,它复制一份实体,返回新实体的标签,而原实体保持不变。
// 复制点3并沿y轴平移0.05
std::vector<std::pair<int, int>> ov;
gmsh::model::geo::copy({{0, 3}}, ov);
gmsh::model::geo::translate(ov, 0, 0.05, 0);
// ov[0].second 就是新点的标签,可以用它来创建线
gmsh::model::geo::addLine(3, ov[0].second, 7);
gmsh::model::geo::addLine(ov[0].second, 5, 8);
gmsh::model::geo::addCurveLoop({5, -8, -7, 3}, 10);
gmsh::model::geo::addPlaneSurface({10}, 11);
这段代码的工作流是:
1. copy()复制点3,结果写入ov(out vector,输出向量)
2. translate()将副本沿y轴平移0.05
3. 使用ov[0].second获取新点标签,连接新线
4. 用新线围成面11
关键点:
copy()的返回值ov是一个vector<pair<int,int>>,每个元素的.first是维度,.second是标签。始终使用.second获取新标签。
3.4.4 批量复制与平移面
copy()可以一次复制多个实体,translate()也能一次变换多个实体:
// 同时复制面1和面11,然后沿x轴平移0.12
gmsh::model::geo::copy({{2, 1}, {2, 11}}, ov);
gmsh::model::geo::translate(ov, 0.12, 0, 0);
printf("New surfaces '%d' and '%d'\n", ov[0].second, ov[1].second);
输出向量ov中元素的顺序与输入实体的顺序一致:ov[0]对应面1的副本,ov[1]对应面11的副本。
3.4.5 scale() 与 symmetrize()
虽然本章的示例代码中没有直接使用,但这两个函数同样常用:
scale() —— 缩放(以某点为中心对实体进行缩放任缩):
// scale({entities}, cx, cy, cz, sx, sy, sz)
// (cx, cy, cz) 是缩放中心,(sx, sy, sz) 是各方向缩放因子
gmsh::model::geo::scale({{0, 1}}, 0, 0, 0, 2.0, 2.0, 1.0);
// 以原点为中心,点1的x、y坐标放大2倍,z不变
symmetrize() —— 镜像(关于平面 ax+by+cz+d=0 进行镜像):
// symmetrize({entities}, a, b, c, d)
// 关于平面 ax+by+cz+d=0 做镜像
gmsh::model::geo::symmetrize({{2, 1}}, 1, 0, 0, 0);
// 关于平面 x=0 (即yz平面) 镜像面1
3.4.6 变换操作总结
| 函数 | 作用 | 原实体是否保留 |
|---|---|---|
translate() |
平移实体 | 实体本身移动 |
rotate() |
绕轴旋转 | 实体本身旋转 |
scale() |
缩放 | 实体本身缩放 |
symmetrize() |
镜像 | 实体本身镜像 |
copy() + 变换 |
复制后变换 | 原实体保留,返回新实体标签 |
最佳实践:在构建复杂几何时,通常先创建基础几何,然后用
copy()+ 变换组合来"繁衍"出整个结构。这样代码更简洁,且便于参数化调整。
3.5 构建体积:Surface Loop 与 Volume
体积(volume)是Gmsh中的第四种基本实体类型(前三种为点、线、面)。构建体积的方式与构建面完全对应:
面 = 曲线环(Curve Loop) → 由闭合曲线序列定义
体 = 表面环(Surface Loop) → 由闭合面序列定义
以下代码手动构建一个六面体(由6个面围成):
// 在z=0.12平面创建四个新点
gmsh::model::geo::addPoint(0., 0.3, 0.12, lc, 100);
gmsh::model::geo::addPoint(0.1, 0.3, 0.12, lc, 101);
gmsh::model::geo::addPoint(0.1, 0.35, 0.12, lc, 102);
// 通过API查询点5的坐标,用于创建点103
gmsh::model::geo::synchronize();
std::vector<double> xyz;
gmsh::model::getValue(0, 5, {}, xyz);
gmsh::model::geo::addPoint(xyz[0], xyz[1], 0.12, lc, 103);
说明:
gmsh::model::getValue(0, 5, {}, xyz)查询点5的坐标。synchronize()的调用确保几何数据已同步到Gmsh内部模型,这是调用getValue()的前提条件。
接下来连接各点形成侧面、顶面,最终包围成一个体:
// 连接竖直方向的棱线
gmsh::model::geo::addLine(4, 100, 110); gmsh::model::geo::addLine(3, 101, 111);
gmsh::model::geo::addLine(6, 102, 112); gmsh::model::geo::addLine(5, 103, 113);
// 连接顶面四边
gmsh::model::geo::addLine(103, 100, 114); gmsh::model::geo::addLine(100, 101, 115);
gmsh::model::geo::addLine(101, 102, 116); gmsh::model::geo::addLine(102, 103, 117);
// 构建四个侧面
gmsh::model::geo::addCurveLoop({115, -111, 3, 110}, 118);
gmsh::model::geo::addPlaneSurface({118}, 119);
gmsh::model::geo::addCurveLoop({111, 116, -112, -7}, 120);
gmsh::model::geo::addPlaneSurface({120}, 121);
gmsh::model::geo::addCurveLoop({112, 117, -113, -8}, 122);
gmsh::model::geo::addPlaneSurface({122}, 123);
gmsh::model::geo::addCurveLoop({114, -110, 5, 113}, 124);
gmsh::model::geo::addPlaneSurface({124}, 125);
// 构建顶面
gmsh::model::geo::addCurveLoop({115, 116, 117, 114}, 126);
gmsh::model::geo::addPlaneSurface({126}, 127);
// 将6个面包围成体:顶面+四个侧面+底面(原有的面11)
gmsh::model::geo::addSurfaceLoop({127, 119, 121, 123, 125, 11}, 128);
gmsh::model::geo::addVolume({128}, 129);
重要规则:addSurfaceLoop()中面的朝向必须一致(全部指向体外或全部指向体内)。Gmsh会根据面的实际朝向自动判断。如果某面的朝向不正确(即面的法向量指向体内而非体外),该面在表面环中的标签应取负值。
3.6 Part B:拉伸与网格控制
3.6.1 extrude() 基本用法 —— 几何拉伸
手动构建体的过程非常繁琐。extrude()函数可以一步完成:沿指定方向拉伸面,自动生成所需的全部点、线、面,并返回新实体的标签。
// 将之前复制的面(ov[1])沿z轴拉伸0.12,生成体
std::vector<std::pair<int, int>> ov2;
gmsh::model::geo::extrude({ov[1]}, 0, 0, 0.12, ov2);
这条语句将ov[1](面11的副本)沿z轴正方向拉伸0.12,创建的新体标签存储在ov2中。
3.6.2 extrude() 高级用法 —— 带层控制的网格拉伸
extrude()最强大的功能是可以同时控制拉伸方向的网格分布。以下代码创建了带分层控制的拉伸:
double h = 0.1;
std::vector<std::pair<int, int>> ov;
// 拉伸面1,总高度h,分2层:
// 第1层:8个细分,高度到总高的0.5(即0.05)
// 第2层:2个细分,高度到总高的1.0(即0.1)
gmsh::model::geo::extrude({{2, 1}}, 0, 0, h, ov,
{8, 2}, // 每层的单元数量
{0.5, 1}); // 每层结束时的归一化高度
参数解读:
| 参数 | 含义 |
|---|---|
{{2, 1}} |
被拉伸的实体列表(面1) |
0, 0, h |
拉伸向量(0, 0, 0.1) |
ov |
输出:返回新创建的实体标签列表 |
{8, 2} |
第1层8个单元,第2层2个单元 |
{0.5, 1} |
第1层结束于总高度的0.5处,第2层结束于总高度的1.0处 |
注意:高度值使用归一化坐标:
0.5表示总高度的一半(即0.05),1.0表示总高度(即0.1)。每层的实际高度 = 该层结束高度 - 上一层结束高度。因此第1层实际高度为0.05,第2层也为0.05。
3.6.3 拉伸函数的返回值
所有拉伸函数(extrude、revolve、twist)返回的ov列表具有固定的结构:
| 索引 | 含义 |
|---|---|
ov[0] |
拉伸产生的"顶面"(top surface) |
ov[1] |
拉伸产生的"体"(new volume) |
ov[2], ov[3], ... |
拉伸产生的各"侧面"(lateral surfaces) |
这个结构可以用于后续操作,例如引用新生成的体来定义物理组。
3.6.4 revolve() —— 旋转拉伸
revolve()沿圆弧路径拉伸实体,适用于生成旋转对称结构:
// 将面28绕轴旋转 -Pi/2 生成体
// 轴点:(-0.1, 0, 0.1),轴方向:(0, 1, 0)
// 分7个单元,并重组为棱柱(prism)
gmsh::model::geo::revolve({{2, 28}}, -0.1, 0, 0.1, 0, 1, 0, -M_PI / 2, ov,
{7});
参数说明:前三个参数(-0.1, 0, 0.1)是旋转轴上的一点,接着三个参数(0, 1, 0)是旋转轴方向(这里绕y轴旋转),-M_PI/2是旋转角度。{7}表示沿旋转方向划分为7个单元。
限制:使用内置geo内核时,单次旋转角度必须小于Pi(180度)。若要实现完整一周的旋转,需要至少分3次调用。OpenCASCADE内核没有此限制。
3.6.5 twist() —— 扭转拉伸
twist()将平移和旋转组合在一起,实现"扭转"效果:
// 获取ONELAB参数定义的角度值
std::vector<double> angle;
gmsh::onelab::getNumber("Parameters/Twisting angle", angle);
// 扭转拉伸面50:沿x方向平移-0.2,同时绕x轴旋转指定角度
gmsh::model::geo::twist({{2, 50}}, // 被拉伸的面
0, 0.15, 0.25, // 旋转轴点
-2 * h, 0, 0, // 平移向量
1, 0, 0, // 旋转轴方向(x轴)
angle[0] * M_PI / 180., // 旋转角(度转弧度)
ov, // 输出
{10}, // 10个细分
{}, // 无额外高度控制
true); // 重组为棱柱
twist()可以理解为:实体在平移的同时绕指定轴旋转。这在工程中有很多应用,例如螺纹、螺旋叶片、扭转梁等。
参数详细说明:
- 轴点 (0, 0.15, 0.25) 和轴方向 (1, 0, 0) 定义旋转轴
- 平移向量 (-2*h, 0, 0) 即 (-0.2, 0, 0)
- 总旋转角 = 预设角度 x Pi/180
- 最后一个参数 true 表示将生成的四面体重组(recombine)为棱柱
3.6.6 物理组定义与网格生成
完成几何构建后,将体加入物理组并生成网格:
// 同步几何数据
gmsh::model::geo::synchronize();
// 将体129和由extrude生成的体加入物理组"1",命名为"The volume"
gmsh::model::addPhysicalGroup(3, {129, 130}, 1, "The volume");
// 生成3D网格
gmsh::model::mesh::generate(3);
gmsh::write("t2.msh");
3.6.7 拉伸网格 vs 自由网格
理解"拉伸网格(extruded mesh)"与"自由网格(free mesh)"的差异对于网格质量控制非常重要:
| 特性 | 拉伸网格 | 自由网格 |
|---|---|---|
| 生成方式 | 由2D网格沿指定方向扩展 | 在3D域内自动填充 |
| 单元类型 | 棱柱(prism)/六面体(hex) | 四面体(tetra)为主 |
| 方向性 | 具有明确的层状结构 | 各向同性 |
| 适用场景 | 薄壁结构、流体边界层 | 任意复杂几何 |
| 网格质量控制 | 通过层数和高度参数 | 通过尺寸场控制 |
拉伸网格特别适合于: - CFD边界层网格:在壁面附近使用高度渐变的层,第一层极薄以捕捉粘性效应 - 薄壁结构:如钣金件,厚度方向使用少量单元 - 复合材料层合板:每层代表一个铺层
3.7 完整可运行代码
将本章各节代码片段按顺序拼接(片段A + 3.4.1~3.4.4 + 3.5 + 3.6.1),得到的完整程序即为官方教程的 t2.cpp。关键拼接要点:
- 片段A(3.3节) 提供矩形面和物理组
- 3.4.1~3.4.4节 在片段A之后追加变换操作(
translate、rotate、copy) - 3.5节 在z=0.12平面手动构建六面体体129
- 3.6.1节 用
extrude()将面拉伸为体130 - 最后统一
synchronize()、添加物理组、生成3D网格
完整源码见官方文件 gmsh-4.15.2-source/tutorials/c++/t2.cpp,或参考本章各节代码片段的自然拼接。
3.8 拉伸网格完整示例(t3.cpp精华)
以下代码展示带层控制的extrude、绕轴旋转的revolve和扭转的twist的完整用法。该程序在片段A创建的矩形面基础上,依次执行三次拉伸。
核心逻辑如下(完整源码见官方文件 gmsh-4.15.2-source/tutorials/c++/t3.cpp):
// --- 片段A:基础矩形面(同3.3节) ---
// ... addPoint ×4, addLine ×4, addCurveLoop, addPlaneSurface ...
double h = 0.1;
// --- 带层控制的网格拉伸:2层,第一层8单元,第二层2单元 ---
std::vector<std::pair<int, int>> ov;
gmsh::model::geo::extrude({{2, 1}}, 0, 0, h, ov,
{8, 2}, {0.5, 1});
// 结果:ov[0]为顶面28,ov[1]为体1
// --- 旋转拉伸面28:绕y轴旋转-Pi/2,7个细分 ---
gmsh::model::geo::revolve({{2, 28}}, -0.1, 0, 0.1, 0, 1, 0, -M_PI / 2, ov,
{7});
// 结果:ov[0]为顶面50,ov[1]为体2
// --- 扭转拉伸面50:沿x平移-0.2 + 绕x轴旋转90度,重组为棱柱 ---
gmsh::model::geo::twist({{2, 50}}, 0, 0.15, 0.25, -2 * h, 0, 0, 1, 0, 0,
90.0 * M_PI / 180., ov, {10}, {}, true);
gmsh::model::geo::synchronize();
gmsh::model::addPhysicalGroup(3, {1, 2, ov[1].second}, 101);
gmsh::model::mesh::generate(3);
gmsh::write("t3.msh");
3.9 关键API速查表
3.9.1 几何变换
| API | 签名 | 说明 |
|---|---|---|
geo::translate |
(vector<pair<int,int>>, dx, dy, dz) |
平移实体 |
geo::rotate |
(vector<pair<int,int>>, x, y, z, ax, ay, az, angle) |
绕轴旋转,角度为弧度 |
geo::scale |
(vector<pair<int,int>>, cx, cy, cz, sx, sy, sz) |
以(cx,cy,cz)为中心缩放 |
geo::symmetrize |
(vector<pair<int,int>>, a, b, c, d) |
关于平面ax+by+cz+d=0镜像 |
geo::copy |
(vector<pair<int,int>>, &out) |
复制实体,返回新标签 |
3.9.2 拉伸操作
| API | 签名 | 说明 |
|---|---|---|
geo::extrude |
(entities, dx, dy, dz, &out, [layers], [heights]) |
沿向量拉伸,可选层控制 |
geo::revolve |
(entities, px, py, pz, ax, ay, az, angle, &out, [layers]) |
绕轴旋转拉伸 |
geo::twist |
(entities, px, py, pz, dx, dy, dz, ax, ay, az, angle, &out, [layers], [heights], [recombine]) |
平移+旋转组合拉伸 |
3.9.3 体构建
| API | 签名 | 说明 |
|---|---|---|
geo::addSurfaceLoop |
({surfaceTags}, tag) |
由面序列定义表面环 |
geo::addVolume |
({surfaceLoopTags}, tag) |
由表面环定义体 |
3.9.4 数据查询与选项
| API | 签名 | 说明 |
|---|---|---|
model::getValue |
(dim, tag, {}, &out) |
查询实体坐标(需先synchronize) |
geo::mesh::setSize |
(entities, size) |
批量设置点的网格尺寸 |
geo::synchronize |
() |
同步geo内核数据到Gmsh模型 |
option::setNumber |
(name, val) |
设置数值型选项 |
option::setColor |
(name, r, g, b) |
设置颜色选项 |
3.10 常见问题与注意事项
-
synchronize() 的时机:在调用
getValue()查询实体坐标之前,必须先调用geo::synchronize()。几何构建过程中不需要频繁同步。 -
旋转角度限制:内置geo内核的单次旋转角度必须小于Pi。若需要更大角度,分段旋转或切换到OpenCASCADE内核。
-
copy() vs 直接变换:
copy()复制实体并返回新标签,原实体保持不变。直接translate()、rotate()等会修改实体本身。构建对称或重复结构时,先用copy()再变换是推荐模式。 -
表面环的面朝向:
addSurfaceLoop()要求所有参与面的法向量一致朝外(或一致朝内)。如果某面朝向不正确,在列表中对该面的标签取负值即可。 -
拉伸的Layers参数:
{8, 2}表示第一层8个单元、第二层2个单元;{0.5, 1}表示第一层结束于总高度的一半、第二层结束于总高度。两者长度必须相等。 -
返回值结构:
extrude()/revolve()/twist()返回的向量中,ov[0]是顶面,ov[1]是新体,ov[2..n]是侧面。这个约定在所有拉伸函数中保持一致。
下一章预告:第4章将介绍OpenCASCADE几何内核的使用,包括布尔运算、倒角圆角、以及更复杂的CAD特征建模。