Skip to content

第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 拉伸函数的返回值

所有拉伸函数(extruderevolvetwist)返回的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之后追加变换操作(translaterotatecopy
  • 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 常见问题与注意事项

  1. synchronize() 的时机:在调用getValue()查询实体坐标之前,必须先调用geo::synchronize()。几何构建过程中不需要频繁同步。

  2. 旋转角度限制:内置geo内核的单次旋转角度必须小于Pi。若需要更大角度,分段旋转或切换到OpenCASCADE内核。

  3. copy() vs 直接变换copy()复制实体并返回新标签,原实体保持不变。直接translate()rotate()等会修改实体本身。构建对称或重复结构时,先用copy()再变换是推荐模式。

  4. 表面环的面朝向addSurfaceLoop()要求所有参与面的法向量一致朝外(或一致朝内)。如果某面朝向不正确,在列表中对该面的标签取负值即可。

  5. 拉伸的Layers参数{8, 2}表示第一层8个单元、第二层2个单元;{0.5, 1}表示第一层结束于总高度的一半、第二层结束于总高度。两者长度必须相等。

  6. 返回值结构extrude()/revolve()/twist()返回的向量中,ov[0]是顶面,ov[1]是新体,ov[2..n]是侧面。这个约定在所有拉伸函数中保持一致。


下一章预告:第4章将介绍OpenCASCADE几何内核的使用,包括布尔运算、倒角圆角、以及更复杂的CAD特征建模。