Skip to content

第2章 几何基础:点、线、面、曲线环与物理组

2.1 学习目标

  • 理解 Gmsh 几何实体的层次结构:Point(点) → Line(线) → CurveLoop(曲线环) → PlaneSurface(平面)
  • 掌握 lc(目标网格尺寸)的作用及其对局部网格密度的控制
  • 理解 Gmsh 的 tag(标签)机制:每个几何维度的实体标签独立
  • 学会使用物理组(Physical Group)对几何实体进行分组,用于边界条件指定和材料属性分配
  • 掌握 synchronize() 的作用——将 CAD 数据同步到 Gmsh 内部模型

2.2 核心概念说明

2.2.1 几何实体的层次结构

Gmsh 的几何构造遵循严格的层次关系,如下图所示:

Point (0D)
  └── Line / Spline / Circle (1D)         —— 以两个 Point 为端点
        └── CurveLoop                    —— 有序、有向的曲线闭合列表
              └── PlaneSurface (2D)       —— 由一个或多个 CurveLoop 围成
                    └── Volume (3D)       —— 由一组 Surface 围成

本章聚焦于从 Point 到 PlaneSurface 的完整链路。这套层级关系在 Gmsh 中是强制性的:你不能跳过 CurveLoop 直接由 Line 定义 Surface,也不能跳过 Line 直接由 Point 定义 Surface。

2.2.2 为什么需要 CurveLoop?

CurveLoop(曲线环)是 Gmsh 中连接"边"与"面"的桥梁。它不仅仅是一个曲线集合,更是一个有序、有向的序列:

  • 有序:曲线的顺序决定了它们首尾相连的拓扑关系。
  • 有向:每条曲线前的符号(正号或负号)表示该曲线的走向。正号表示使用曲线的自然方向(从起点到终点),负号表示反向。

例如,假设一个矩形由 4 条线组成,其中曲线 2 的自然方向与环的遍历方向相反,那么 CurveLoop 中就会写成 -2

2.2.3 Tag 机制

在 Gmsh 中,每个几何实体都由一个正整数标签(tag)唯一标识。关键规则是:

  • 同一维度内的标签必须唯一。例如,不能有两个 Point 都使用标签 1
  • 不同维度之间的标签可以重复。Point 1 和 Line 1 可以同时存在,互不冲突。
  • 标签可以手动指定,也可以由 Gmsh 自动生成。手动指定时,标签作为函数的最后一个可选参数传入;省略时,Gmsh 自动分配并返回新标签。

2.2.4 物理组的作用

物理组(Physical Group)将多个几何实体分组,为后续的有限元分析提供语义信息:

  • 边界条件指定:例如将矩形底部的一系列曲线归入物理组 "bottom",在求解器中为该组施加固定约束。
  • 材料属性分配:例如将不同区域的表面归入不同的物理组("steel", "aluminum")。
  • 选择性导出:如果定义了物理组,Gmsh 默认只导出属于至少一个物理组的网格单元。未分组的几何实体不会被导出到网格文件。

物理组也按维度区分:0D 物理组(物理点)、1D 物理组(物理曲线)、2D 物理组(物理曲面)、3D 物理组(物理体积)。

2.3 C++ 代码逐段讲解

本节以一个完整的矩形面(10 cm × 30 cm)为例,逐步讲解每个 API 的用法。

2.3.1 初始化与模型创建

#include <gmsh.h>

int main(int argc, char **argv)
{
    gmsh::initialize();          // 初始化 Gmsh
    gmsh::model::add("t1");      // 创建名为 "t1" 的新模型
  • gmsh::initialize() 必须在调用任何 Gmsh API 之前执行,它会初始化内部数据结构、注册 CAD 内核和 GUI 模块。
  • gmsh::model::add("t1") 创建一个名为 "t1" 的模型。如果不调用该函数,Gmsh 会在需要时自动创建一个匿名模型,但显式命名有助于调试和多模型管理。

2.3.2 定义目标网格尺寸

    double lc = 1e-2;  // 目标网格尺寸 = 0.01 m = 1 cm

lc(characteristic length,特征长度)是 Gmsh 中控制网格密度的核心参数。它的含义是:在几何实体附近,期望的网格单元边长约为 lc

  • lc 越小,网格越密,计算精度越高但计算量越大。
  • lc 定义在每个 Point 上。Gmsh 会在几何体内通过插值的方式确定任意位置的网格尺寸。
  • 如果不指定 lc,Gmsh 会基于模型整体尺寸使用一个默认的均匀粗糙网格。

更高级的网格尺寸控制方法(如尺寸场、背景网格)将在第5章、第7章和第8章中介绍。

2.3.3 创建点(Point)

    gmsh::model::geo::addPoint(0,    0,   0, lc, 1);   // 点1: (0, 0, 0)
    gmsh::model::geo::addPoint(0.1,  0,   0, lc, 2);   // 点2: (0.1, 0, 0)
    gmsh::model::geo::addPoint(0.1,  0.3, 0, lc, 3);   // 点3: (0.1, 0.3, 0)
    int p4 = gmsh::model::geo::addPoint(0, 0.3, 0, lc); // 点4: (0, 0.3, 0),自动分配标签

addPoint 的函数签名(位于 gmsh::model::geo 命名空间下):

addPoint(x, y, z, meshSize, tag)
参数 含义
x, y, z 点的三维坐标(double 类型)
meshSize 该点附近的目标网格尺寸(可选,默认使用全局粗网格)
tag 点的标签(可选,正整数;省略则自动分配)

返回值:点的标签(int)。

关键观察:前三个点手动指定了标签(1, 2, 3),而第四个点让 Gmsh 自动分配标签(返回值 p4)。两种方式都可以使用,但在构建复杂几何时,手动指定标签能使代码更清晰、更易于后续引用。

2.3.4 创建线段(Line)

    gmsh::model::geo::addLine(1, 2, 1);   // 线1: 点1 → 点2(底边)
    gmsh::model::geo::addLine(3, 2, 2);   // 线2: 点3 → 点2(右边,从上到下)
    gmsh::model::geo::addLine(3, p4, 3);  // 线3: 点3 → 点4(顶边,从右到左)
    gmsh::model::geo::addLine(4, 1, p4);  // 线4: 点4 → 点1(左边,从上到下)

addLine 的函数签名:

addLine(startPointTag, endPointTag, tag)
参数 含义
startPointTag 线段起点的标签
endPointTag 线段终点的标签
tag 线段的标签(可选,省略则自动分配)

关键观察:线的标签(1, 2, 3, p4)和点的标签(1, 2, 3, p4)存在重复。线 1 的标签是 1,点 1 的标签也是 1——这完全合法,因为点和线属于不同的几何维度。

矩形四条边的定义方向如下图所示(点坐标单位为米):

   (0, 0.3) p4 ●──────────────● (0.1, 0.3) 点3
                ↖ Line 3 (3→4) ↗
                │              │
       Line 4   │              │ Line 2
      (4→1)     │              │ (3→2)
                │              │
                ↓              │
    (0, 0)  点1 ●──────────────→● (0.1, 0)   点2
                   Line 1 (1→2)

2.3.5 创建曲线环(CurveLoop)

    gmsh::model::geo::addCurveLoop({4, 1, -2, 3}, 1);

addCurveLoop 的函数签名:

addCurveLoop({curveTags with signs}, tag)

第一个参数是一个整数向量(使用 C++ 初始化列表 {...}),每条曲线的标签可带符号:

  • 正号(如 4):按曲线的自然方向(从起点到终点)使用。
  • 负号(如 -2):按曲线的反方向使用。

让我们分析这个 CurveLoop 的构成:

顺序 曲线标签 方向 含义
1 4 (Line 4) 正向:点4→点1 左边,从上到下
2 1 (Line 1) 正向:点1→点2 底边,从左到右
3 -2 (Line 2) 反向:点2→点3 右边,从下到上
4 3 (Line 3) 正向:点3→点4 顶边,从右到左

为什么要将 Line 2 取反?因为 Line 2 的自然方向是点 3 → 点 2(从上到下),而环的遍历方向要求从点 2 到点 3(从下到上)。取反后,-2 等价于从点 2 走到点 3,与环的方向一致。

最终形成一个逆时针的闭合环路:点4 → 点1 → 点2 → 点3 → 点4。

2.3.6 创建平面(PlaneSurface)

    gmsh::model::geo::addPlaneSurface({1}, 1);

addPlaneSurface 的函数签名:

addPlaneSurface({curveLoopTags}, tag)

第一个参数是一个整数向量,包含围成该平面的 CurveLoop 标签(不带符号):

  • 第一个 CurveLoop 定义外边界(外部轮廓)。
  • 后续的 CurveLoop 定义内边界(孔洞)。本章示例只有一个外边界,多孔示例见第4章。

2.3.7 同步 CAD 数据

    gmsh::model::geo::synchronize();

synchronize() 将内置 CAD 内核中的几何数据同步到 Gmsh 的内部模型数据结构中。这是一个必须调用的函数——在同步之前,由 gmsh::model::geo::* 创建的实体不能被网格生成器或其他非 CAD 模块使用。

关于调用时机:

  • 可以在每个 CAD 命令后立即同步,但不推荐——synchronize() 涉及不可忽略的计算量。
  • 最佳实践:在完成一批几何创建操作后,集中调用一次 synchronize()
  • 如果在同步后又创建了新几何实体,需要再次调用 synchronize()

2.3.8 定义物理组

    gmsh::model::addPhysicalGroup(1, {1, 2, 4}, 5);
    gmsh::model::addPhysicalGroup(2, {1}, -1, "My surface");

addPhysicalGroup 的函数签名:

addPhysicalGroup(dim, {entityTags}, tag, name)
参数 含义
dim 几何维度(0=点, 1=线, 2=面, 3=体)
entityTags 要分组的几何实体标签列表
tag 物理组标签(可选,-1 表示自动分配)
name 物理组名称(可选,如 "bottom", "steel"

第一条语句创建了一个 1D 物理组(标签 5),将曲线 1(底边)、2(右边)、4(左边)归为一组。这个组可以用于施加边界条件(如固定位移、强制对流等)。注意曲线 3(顶边)没有被包含在内,因此它不会被导出(在导出网格时,顶边的网格单元不会出现)。

第二条语句创建了一个 2D 物理组(标签由 Gmsh 自动分配,名称为 "My surface"),包含表面 1。这在有限元分析中可用于指定该区域的材料属性。

重要:如果不定义任何物理组,Gmsh 会导出所有网格元素。一旦定义了至少一个物理组,默认行为变为只导出属于物理组的元素。可以通过 gmsh::option::setNumber("Mesh.SaveAll", 1) 强制导出所有元素。

2.3.9 生成网格

    gmsh::model::mesh::generate(2);  // 生成2D网格

mesh::generate(dim) 的参数为网格维度:

  • 1:仅生成 1D 网格(线上的线段单元)。
  • 2:生成 2D 网格(面上的三角形/四边形单元),同时会自动生成所需的 1D 网格。
  • 3:生成 3D 网格(体上的四面体/六面体单元),同时会自动生成所需的 1D 和 2D 网格。

本例生成 2D 网格,Gmsh 会先在四条曲线上生成 1D 线段单元,再在矩形平面上生成 2D 三角形单元。

2.3.10 保存网格文件

    gmsh::write("t1.msh");

write(filename) 将网格保存到文件。通过文件扩展名可以指定格式:

扩展名 格式
.msh Gmsh 原生网格格式(最新版)
.msh2 / .msh4 指定 Gmsh 格式版本
.unv I-Deas Universal 格式
.stl STL 三角面片格式
.vtk VTK 格式

也可以使用 gmsh::option::setNumber("Mesh.MshFileVersion", x) 显式指定 MSH 格式版本。

2.3.11 GUI 可视化(可选)

    std::set<std::string> args(argv, argv + argc);
    if(!args.count("-nopopup")) gmsh::fltk::run();  // GUI 显示

    gmsh::finalize();  // 清理 Gmsh 资源
    return 0;
}

gmsh::fltk::run() 启动内置的 FLTK 图形界面,用于交互式查看几何和网格。参数 -nopopup 用于批处理模式(例如在 CI/CD 流水线或脚本中)跳过 GUI。

gmsh::finalize() 应在程序结束时调用,释放 Gmsh 占用的所有资源。

2.4 完整可运行代码

// ch02_rectangle.cpp —— 矩形面几何定义与网格生成
#include <set>
#include <gmsh.h>

int main(int argc, char **argv)
{
    // === 1. 初始化 ===
    gmsh::initialize();
    gmsh::model::add("ch02");

    // === 2. 目标网格尺寸 ===
    double lc = 1e-2;  // 1 cm

    // === 3. 创建四个角点 ===
    gmsh::model::geo::addPoint(0,   0,   0, lc, 1);
    gmsh::model::geo::addPoint(0.1, 0,   0, lc, 2);
    gmsh::model::geo::addPoint(0.1, 0.3, 0, lc, 3);
    int p4 = gmsh::model::geo::addPoint(0, 0.3, 0, lc);   // 自动标签

    // === 4. 创建四条边 ===
    gmsh::model::geo::addLine(1, 2,  1);   // 底边: 点1→点2
    gmsh::model::geo::addLine(3, 2,  2);   // 右边: 点3→点2
    gmsh::model::geo::addLine(3, p4, 3);   // 顶边: 点3→点4
    gmsh::model::geo::addLine(4, 1,  p4);  // 左边: 点4→点1

    // === 5. 曲线环(逆时针) ===
    // 曲线2自然方向是3→2,但环需要2→3,因此取反
    gmsh::model::geo::addCurveLoop({4, 1, -2, 3}, 1);

    // === 6. 平面 ===
    gmsh::model::geo::addPlaneSurface({1}, 1);

    // === 7. 同步到 Gmsh 模型 ===
    gmsh::model::geo::synchronize();

    // === 8. 物理组 ===
    gmsh::model::addPhysicalGroup(1, {1, 2, 4}, 5);       // 底+右+左
    gmsh::model::addPhysicalGroup(2, {1}, -1, "My surface");

    // === 9. 生成2D网格 ===
    gmsh::model::mesh::generate(2);

    // === 10. 保存 ===
    gmsh::write("ch02_rectangle.msh");

    // === 11. GUI(可选) ===
    std::set<std::string> args(argv, argv + argc);
    if(!args.count("-nopopup")) gmsh::fltk::run();

    gmsh::finalize();
    return 0;
}

编译与运行(假设 Gmsh SDK 已配置,参见第1章):

g++ -o ch02_rectangle ch02_rectangle.cpp -lgmsh
./ch02_rectangle              # 启动 GUI
./ch02_rectangle -nopopup     # 批处理模式,仅生成 ch02_rectangle.msh

2.5 关键 API 速查表

API 函数 所属命名空间 参数说明 返回值
initialize() gmsh 无参数 void
model::add(name) gmsh name: 模型名称(字符串) void
geo::addPoint(x, y, z, meshSize, tag) gmsh::model x,y,z: 坐标; meshSize: 局部网格尺寸(可选); tag: 点标签(可选) int (标签)
geo::addLine(p1, p2, tag) gmsh::model p1,p2: 起止点标签; tag: 线标签(可选) int (标签)
geo::addCurveLoop(tags, tag) gmsh::model tags: 带符号的曲线标签向量; tag: 环标签(可选) int (标签)
geo::addPlaneSurface(tags, tag) gmsh::model tags: 曲线环标签向量; tag: 面标签(可选) int (标签)
geo::synchronize() gmsh::model 无参数 void
addPhysicalGroup(dim, tags, tag, name) gmsh::model dim: 维度; tags: 实体标签向量; tag: 物理组标签(可选); name: 名称(可选) int (标签)
mesh::generate(dim) gmsh::model dim: 网格维度(1/2/3) void
write(filename) gmsh filename: 输出文件路径 void
fltk::run() gmsh 无参数 void
finalize() gmsh 无参数 void
option::setNumber(name, value) gmsh name: 选项名; value: 数值 void

2.6 常见问题与要点回顾

  1. 忘记调用 synchronize():这是最常见的错误。如果在同步前尝试生成网格,网格生成器将看不到 CAD 实体。

  2. CurveLoop 中的符号错误:如果曲线方向与环的遍历方向不匹配,CurveLoop 将无法闭合,导致 addPlaneSurface 失败。画出草图并在代码中标注每条线的方向是一种良好的习惯。

  3. 标签冲突:同一维度内标签必须唯一。当手动指定标签与自动分配标签混用时,需确保手动标签不会与自动标签冲突。建议要么全手动指定,要么全自动分配。

  4. 物理组导出行为:物理组一旦定义,只有属于物理组的元素会被导出。如果发现网格文件缺少某些边界,检查是否忘记将这些边界加入物理组,或设置 Mesh.SaveAll = 1

  5. lc 的选取lc 应根据分析精度需求和计算资源综合选取。作为经验法则,对于线弹性分析,在特征尺寸方向上有 5-10 个单元通常能获得合理的精度。例如,若矩形宽度为 0.1 m,lc = 0.01 意味着宽度方向上约有 10 个单元。