Skip to content

第5章 三维网格控制:尺寸场、体孔洞与网格算法


5.1 学习目标

  • 理解网格特征长度(Characteristic Length, lc)的分级控制策略:不同几何点设置不同 lc 值,实现局部网格加密
  • 掌握 Mesh.MeshSizeFactor 全局缩放因子的用法,理解其与命令行参数 -clscale 的等价关系
  • 学会通过 addSurfaceLoop + addVolume 构建含孔洞的三维实体(Volume with Holes)
  • 了解三维网格算法选择机制:全局选项 Mesh.Algorithm 与逐面设置 mesh::setAlgorithm()
  • 掌握高阶网格(High-Order Mesh)与选择性可见性网格划分(MeshOnlyVisible)的配置方法

5.2 核心概念说明

5.2.1 分级网格尺寸控制

在前几章中,所有几何点通常使用同一个 lc 值。在实际 CAE 前处理中,我们经常需要对不同区域设置不同的网格密度——例如应力集中区使用细网格,远场区域使用粗网格。Gmsh 通过在定义点时指定不同的 lc 参数实现这一目标:

// 点 1:密集网格(lc 小)
gmsh::model::geo::addPoint(0.5, 0.5, 0.5, lcar2 /*=0.0005*/, 1);
// 点 2:稀疏网格(lc 大)
gmsh::model::geo::addPoint(0.5, 0.5, 0,   lcar1 /*=0.1*/,   2);

lc 值表示该点附近的期望网格边长。值越小,网格越密集。Gmsh 的网格生成器会在各点之间平滑过渡网格尺寸。

5.2.2 MeshSizeFactor 全局缩放

当需要整体调整网格密度(例如做收敛性研究)而不修改每个点的 lc 值时,可以使用全局缩放因子:

gmsh::option::setNumber("Mesh.MeshSizeFactor", 0.1);

这会将所有点的 lc 乘以 0.1 后送入网格生成器。等效的命令行参数为:

./t5.exe -clscale 0.2

缩放因子 clscale 取值为 1 时保持原始尺寸,取值越小网格越密。例如 t5 教程中: - clscale=1:约 3000 节点、14000 四面体 - clscale=0.2:约 231000 节点、1360000 四面体

5.2.3 从 SurfaceLoop 到 Volume

在三维建模中,Gmsh 使用层级结构构建实体:

  1. 曲面(Surface):由曲线环(Curve Loop)定义,可以是平面(addPlaneSurface)或非平面(addSurfaceFilling
  2. 曲面环(Surface Loop):一组闭合的曲面集合,形成"壳"(Shell)
  3. (Volume):由曲面环定义的三维实体

构建含孔洞的体的关键规则: - addVolume 的参数是一个曲面环标签列表 - 第一个曲面环定义体的外边界(exterior boundary) - 后续曲面环定义体内的孔洞(holes)

// shells[0] 是外边界,shells[1..5] 是 5 个球面孔洞
int ve = gmsh::model::geo::addVolume(shells);

5.2.4 非平曲面与球面孔洞

对于球面等非平曲面,Gmsh 内置几何内核提供 addSurfaceFilling 函数。当边界曲线是以同一点为圆心的圆弧(Circle Arc)时,该函数自动生成球面片(spherical patch);否则使用超限插值(transfinite interpolation)。

每个球面孔洞由 8 个球面三角形片(对应球面八面体剖分)构成,通过 8 个曲面环和 12 条圆弧定义。

5.2.5 三维网格算法概览

Gmsh 支持多种三维网格生成算法,通过 Mesh.Algorithm 选项全局设置:

算法名称 适用维度 特点
1 MeshAdapt 2D 自适应网格,稳定性好
5 Delaunay 2D 经典 Delaunay 三角剖分
6 Frontal-Delaunay 2D 前沿推进 + Delaunay,质量更优
7 BAMG 2D 各向异性,需要额外安装
8 Delaunay for Quads 2D 四边形 Delaunay
9 Packing of Parallelograms 2D 平行四边形填充

此外,可以对特定面单独设置算法:

// 对 dim=2, tag=33 的面使用 MeshAdapt(算法1)
gmsh::model::mesh::setAlgorithm(2, 33, 1);

5.2.6 高阶网格

为生成曲线网格(curvilinear mesh)以更好地贴合几何曲率,可以启用高阶单元:

gmsh::option::setNumber("Mesh.ElementOrder", 2);       // 二阶单元
gmsh::option::setNumber("Mesh.HighOrderOptimize", 2);   // 高阶优化(保证有效性)

HighOrderOptimize 使用基于 Johnen 等人(2013)和 Toulorge 等人(2013)的方法,对曲线单元进行几何有效性优化和去纠缠(untangling)。

5.2.7 选择性可见性网格划分

有时我们只希望对模型的某一部分进行网格划分。Gmsh 支持隐藏实体并仅对可见部分生成网格:

// 隐藏所有实体
std::vector<std::pair<int, int>> entities;
gmsh::model::getEntities(entities);
gmsh::model::setVisibility(entities, false);
// 仅显示体 tag=5
gmsh::model::setVisibility({{3, 5}}, true, true);
// 仅对可见部分生成网格
gmsh::option::setNumber("Mesh.MeshOnlyVisible", 1);

5.3 C++ 代码逐段讲解

5.3.1 球面孔洞辅助函数 cheeseHole

这是本章最核心的函数,它创建一个球面孔洞并返回其曲面环和体标签。我们将逐步拆解其实现。

void cheeseHole(double x, double y, double z, double r, double lc,
                std::vector<int> &shells, std::vector<int> &volumes)
{

参数说明: - (x, y, z):球心坐标 - r:球半径 - lc:该孔洞周边的网格特征长度 - shells:输出参数,添加该孔洞的曲面环标签 - volumes:输出参数,添加该孔洞的体标签

步骤一:定义球面上的 7 个关键点

  int p1 = gmsh::model::geo::addPoint(x,     y,     z,     lc);  // 球心
  int p2 = gmsh::model::geo::addPoint(x + r, y,     z,     lc);  // +x 方向
  int p3 = gmsh::model::geo::addPoint(x,     y + r, z,     lc);  // +y 方向
  int p4 = gmsh::model::geo::addPoint(x,     y,     z + r, lc);  // +z 方向
  int p5 = gmsh::model::geo::addPoint(x - r, y,     z,     lc);  // -x 方向
  int p6 = gmsh::model::geo::addPoint(x,     y - r, z,     lc);  // -y 方向
  int p7 = gmsh::model::geo::addPoint(x,     y,     z - r, lc);  // -z 方向

这 7 个点构成球面的八面体近似框架:p1 是球心(用作所有圆弧的中心),p2 到 p7 是球面上的 6 个顶点(分别位于 6 个坐标轴方向上)。

步骤二:创建球面上的 12 条大圆弧

每条圆弧的圆心都是 p1(球心),使得这些圆弧位于球面上:

  int c1  = gmsh::model::geo::addCircleArc(p2, p1, p7);
  int c2  = gmsh::model::geo::addCircleArc(p7, p1, p5);
  int c3  = gmsh::model::geo::addCircleArc(p5, p1, p4);
  int c4  = gmsh::model::geo::addCircleArc(p4, p1, p2);
  int c5  = gmsh::model::geo::addCircleArc(p2, p1, p3);
  int c6  = gmsh::model::geo::addCircleArc(p3, p1, p5);
  int c7  = gmsh::model::geo::addCircleArc(p5, p1, p6);
  int c8  = gmsh::model::geo::addCircleArc(p6, p1, p2);
  int c9  = gmsh::model::geo::addCircleArc(p7, p1, p3);
  int c10 = gmsh::model::geo::addCircleArc(p3, p1, p4);
  int c11 = gmsh::model::geo::addCircleArc(p4, p1, p6);
  int c12 = gmsh::model::geo::addCircleArc(p6, p1, p7);

这 12 条大圆弧将球面划分为 8 个球面三角形区域(上下左右前后各四分之一)。每段圆弧角度为 90 度,符合 addCircleArc 的 Pi 限制。

步骤三:构建 8 个曲线环与球面片

  int l1 = gmsh::model::geo::addCurveLoop({c5, c10, c4});
  int l2 = gmsh::model::geo::addCurveLoop({c9, -c5, c1});
  int l3 = gmsh::model::geo::addCurveLoop({c12, -c8, -c1});
  int l4 = gmsh::model::geo::addCurveLoop({c8, -c4, c11});
  int l5 = gmsh::model::geo::addCurveLoop({-c10, c6, c3});
  int l6 = gmsh::model::geo::addCurveLoop({-c11, -c3, c7});
  int l7 = gmsh::model::geo::addCurveLoop({-c2, -c7, -c12});
  int l8 = gmsh::model::geo::addCurveLoop({-c6, -c9, c2});

每个曲线环由 3 条圆弧构成(球面三角形边界)。负号用于调整方向,确保每个环的法向量一致。

  int s1 = gmsh::model::geo::addSurfaceFilling({l1});
  int s2 = gmsh::model::geo::addSurfaceFilling({l2});
  int s3 = gmsh::model::geo::addSurfaceFilling({l3});
  int s4 = gmsh::model::geo::addSurfaceFilling({l4});
  int s5 = gmsh::model::geo::addSurfaceFilling({l5});
  int s6 = gmsh::model::geo::addSurfaceFilling({l6});
  int s7 = gmsh::model::geo::addSurfaceFilling({l7});
  int s8 = gmsh::model::geo::addSurfaceFilling({l8});

由于所有边界圆弧共享同一个圆心 p1,addSurfaceFilling 自动识别并生成球面片,而非平面超限插值。

步骤四:组合曲面环与体,输出结果

  int sl = gmsh::model::geo::addSurfaceLoop({s1, s2, s3, s4, s5, s6, s7, s8});
  int v  = gmsh::model::geo::addVolume({sl});
  shells.push_back(sl);
  volumes.push_back(v);
}

8 个球面片构成一个封闭壳体(曲面环),再由它定义孔洞体。调用者通过 shellsvolumes 获取生成的标签。

5.3.2 构建截断立方体主体

截断立方体(truncated cube)是一个将角切掉的立方体,共 14 个顶点、21 条边、9 个平面面:

  // 截断立方体的 14 个顶点
  gmsh::model::geo::addPoint(0.5, 0.5, 0.5, lcar2, 1);  // 切角点(密集)
  gmsh::model::geo::addPoint(0.5, 0.5, 0,   lcar1, 2);
  gmsh::model::geo::addPoint(0,   0.5, 0.5, lcar1, 3);
  // ... 其余点省略,使用 lcar1

注意点 1 使用了较小的 lcar2(=0.0005),使其周边网格显著加密,而其余点使用较大的 lcar1(=0.1)。这种不均匀的尺寸分布在生成的网格中表现为:从点 1 向外逐渐过渡,尺寸从极小增加到正常。

外边界曲面环由 9 个平面面构成:

  int sl = gmsh::model::geo::addSurfaceLoop(
      {35, 31, 29, 37, 33, 23, 39, 25, 27});
  shells.push_back(sl);

5.3.3 在体内创建 5 个球面孔洞

使用循环在截断立方体内部依次放置 5 个球面孔洞:

  double x = 0, y = 0.75, z = 0, r = 0.09;
  for (int t = 1; t <= 5; t++) {
    x += 0.166;
    z += 0.166;
    cheeseHole(x, y, z, r, lcar3, shells, volumes);
    gmsh::model::geo::addPhysicalGroup(3, {volumes.back()}, t);
    std::printf("Hole %d (center = {%g,%g,%g}, radius = %g) has number %d!\n",
                t, x, y, z, r, volumes.back());
  }

每个孔洞创建后,立即将其体标签加入物理组(dim=3),物理组标签 t 用于在求解器中区分不同的孔洞。

5.3.4 定义含孔洞的最终实体

  int ve = gmsh::model::geo::addVolume(shells);
  gmsh::model::geo::synchronize();

  gmsh::model::addPhysicalGroup(3, {ve}, 10);

shells 向量中,第一个元素是截断立方体的外表面曲面环,后续 5 个元素是球面孔洞的曲面环。addVolume 将第一个作为外边界,其余作为内孔洞,生成"瑞士奶酪"状的实体。

物理组标签 10 用于标识去除孔洞后的立方体区域(dim=3)。

5.3.5 配置网格生成选项

全局算法设置

  gmsh::option::setNumber("Mesh.Algorithm", 6);  // Frontal-Delaunay

逐面设置:对面 tag=33 单独使用 MeshAdapt 算法:

  gmsh::model::mesh::setAlgorithm(2, 33, 1);

高阶网格(可选,注释掉的代码):

  // gmsh::option::setNumber("Mesh.ElementOrder", 2);
  // gmsh::option::setNumber("Mesh.HighOrderOptimize", 2);

选择性可见性网格划分(可选,注释掉的代码):

  // std::vector<std::pair<int, int>> ent;
  // gmsh::model::getEntities(ent);
  // gmsh::model::setVisibility(ent, false);
  // gmsh::model::setVisibility({{3, 5}}, true, true);
  // gmsh::option::setNumber("Mesh.MeshOnlyVisible", 1);

5.3.6 网格生成与输出

  gmsh::model::mesh::generate(3);
  gmsh::write("t5.msh");

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

  gmsh::finalize();
  return 0;

5.4 完整可运行代码

// ch05_full.cpp — 截断立方体 + 5 个球面孔洞完整示例
#include <set>
#include <cstdio>
#include <gmsh.h>

// 创建球面孔洞辅助函数
void cheeseHole(double x, double y, double z, double r, double lc,
                std::vector<int> &shells, std::vector<int> &volumes)
{
  namespace factory = gmsh::model::geo;

  // 球面上 7 个关键点(球心 + 6 个极点)
  int p1 = factory::addPoint(x,     y,     z,     lc);
  int p2 = factory::addPoint(x + r, y,     z,     lc);
  int p3 = factory::addPoint(x,     y + r, z,     lc);
  int p4 = factory::addPoint(x,     y,     z + r, lc);
  int p5 = factory::addPoint(x - r, y,     z,     lc);
  int p6 = factory::addPoint(x,     y - r, z,     lc);
  int p7 = factory::addPoint(x,     y,     z - r, lc);

  // 12 条大圆弧(圆心均为 p1)
  int c1  = factory::addCircleArc(p2, p1, p7);
  int c2  = factory::addCircleArc(p7, p1, p5);
  int c3  = factory::addCircleArc(p5, p1, p4);
  int c4  = factory::addCircleArc(p4, p1, p2);
  int c5  = factory::addCircleArc(p2, p1, p3);
  int c6  = factory::addCircleArc(p3, p1, p5);
  int c7  = factory::addCircleArc(p5, p1, p6);
  int c8  = factory::addCircleArc(p6, p1, p2);
  int c9  = factory::addCircleArc(p7, p1, p3);
  int c10 = factory::addCircleArc(p3, p1, p4);
  int c11 = factory::addCircleArc(p4, p1, p6);
  int c12 = factory::addCircleArc(p6, p1, p7);

  // 8 个曲线环 + 8 个球面片
  int l1 = factory::addCurveLoop({c5, c10, c4});
  int l2 = factory::addCurveLoop({c9, -c5, c1});
  int l3 = factory::addCurveLoop({c12, -c8, -c1});
  int l4 = factory::addCurveLoop({c8, -c4, c11});
  int l5 = factory::addCurveLoop({-c10, c6, c3});
  int l6 = factory::addCurveLoop({-c11, -c3, c7});
  int l7 = factory::addCurveLoop({-c2, -c7, -c12});
  int l8 = factory::addCurveLoop({-c6, -c9, c2});

  int s1 = factory::addSurfaceFilling({l1});
  int s2 = factory::addSurfaceFilling({l2});
  int s3 = factory::addSurfaceFilling({l3});
  int s4 = factory::addSurfaceFilling({l4});
  int s5 = factory::addSurfaceFilling({l5});
  int s6 = factory::addSurfaceFilling({l6});
  int s7 = factory::addSurfaceFilling({l7});
  int s8 = factory::addSurfaceFilling({l8});

  int sl = factory::addSurfaceLoop({s1, s2, s3, s4, s5, s6, s7, s8});
  int v  = factory::addVolume({sl});

  shells.push_back(sl);
  volumes.push_back(v);
}

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

  namespace factory = gmsh::model::geo;

  double lcar1 = 0.1;     // 稀疏区域 lc
  double lcar2 = 0.0005;  // 点 1 附近密集 lc
  double lcar3 = 0.055;   // 孔洞周边 lc

  // ---- 如果想全局缩放,取消此注释 ----
  // gmsh::option::setNumber("Mesh.MeshSizeFactor", 0.1);

  // ---- 截断立方体的 14 个顶点 ----
  factory::addPoint(0.5, 0.5, 0.5, lcar2, 1);
  factory::addPoint(0.5, 0.5, 0,   lcar1, 2);
  factory::addPoint(0,   0.5, 0.5, lcar1, 3);
  factory::addPoint(0,   0,   0.5, lcar1, 4);
  factory::addPoint(0.5, 0,   0.5, lcar1, 5);
  factory::addPoint(0.5, 0,   0,   lcar1, 6);
  factory::addPoint(0,   0.5, 0,   lcar1, 7);
  factory::addPoint(0,   1,   0,   lcar1, 8);
  factory::addPoint(1,   1,   0,   lcar1, 9);
  factory::addPoint(0,   0,   1,   lcar1, 10);
  factory::addPoint(0,   1,   1,   lcar1, 11);
  factory::addPoint(1,   1,   1,   lcar1, 12);
  factory::addPoint(1,   0,   1,   lcar1, 13);
  factory::addPoint(1,   0,   0,   lcar1, 14);

  // ---- 21 条直线边 ----
  factory::addLine(8, 9, 1);    factory::addLine(9, 12, 2);
  factory::addLine(12, 11, 3);  factory::addLine(11, 8, 4);
  factory::addLine(9, 14, 5);   factory::addLine(14, 13, 6);
  factory::addLine(13, 12, 7);  factory::addLine(11, 10, 8);
  factory::addLine(10, 13, 9);  factory::addLine(10, 4, 10);
  factory::addLine(4, 5, 11);   factory::addLine(5, 6, 12);
  factory::addLine(6, 2, 13);   factory::addLine(2, 1, 14);
  factory::addLine(1, 3, 15);   factory::addLine(3, 7, 16);
  factory::addLine(7, 2, 17);   factory::addLine(3, 4, 18);
  factory::addLine(5, 1, 19);   factory::addLine(7, 8, 20);
  factory::addLine(6, 14, 21);

  // ---- 9 个曲线环 + 9 个平面面 ----
  factory::addCurveLoop({-11, -19, -15, -18}, 22);
  factory::addPlaneSurface({22}, 23);
  factory::addCurveLoop({16, 17, 14, 15}, 24);
  factory::addPlaneSurface({24}, 25);
  factory::addCurveLoop({-17, 20, 1, 5, -21, 13}, 26);
  factory::addPlaneSurface({26}, 27);
  factory::addCurveLoop({-4, -1, -2, -3}, 28);
  factory::addPlaneSurface({28}, 29);
  factory::addCurveLoop({-7, 2, -5, -6}, 30);
  factory::addPlaneSurface({30}, 31);
  factory::addCurveLoop({6, -9, 10, 11, 12, 21}, 32);
  factory::addPlaneSurface({32}, 33);
  factory::addCurveLoop({7, 3, 8, 9}, 34);
  factory::addPlaneSurface({34}, 35);
  factory::addCurveLoop({-10, 18, -16, -20, 4, -8}, 36);
  factory::addPlaneSurface({36}, 37);
  factory::addCurveLoop({-14, -13, -12, 19}, 38);
  factory::addPlaneSurface({38}, 39);

  std::vector<int> shells, volumes;

  // 外边界曲面环
  int sl = factory::addSurfaceLoop(
      {35, 31, 29, 37, 33, 23, 39, 25, 27});
  shells.push_back(sl);

  // ---- 创建 5 个球面孔洞 ----
  double x = 0, y = 0.75, z = 0, r = 0.09;
  for (int t = 1; t <= 5; t++) {
    x += 0.166;
    z += 0.166;
    cheeseHole(x, y, z, r, lcar3, shells, volumes);
    factory::addPhysicalGroup(3, {volumes.back()}, t);
    std::printf("Hole %d (center = {%g,%g,%g}, radius = %g) has number %d!\n",
                t, x, y, z, r, volumes.back());
  }

  // 含孔洞的体:第一个曲面环是外边界,后续的是孔洞
  int ve = factory::addVolume(shells);
  factory::synchronize();

  // 物理组:体 ve 标识去除孔洞后的立方体区域
  gmsh::model::addPhysicalGroup(3, {ve}, 10);

  // ---- 网格算法配置 ----
  gmsh::option::setNumber("Mesh.Algorithm", 6);  // Frontal-Delaunay (2D)
  gmsh::model::mesh::setAlgorithm(2, 33, 1);      // 面 33 单独使用 MeshAdapt

  // ---- 高阶网格(可选,取消注释启用) ----
  // gmsh::option::setNumber("Mesh.ElementOrder", 2);
  // gmsh::option::setNumber("Mesh.HighOrderOptimize", 2);

  // ---- 选择性可见性网格划分(可选,取消注释启用) ----
  // std::vector<std::pair<int, int>> ent;
  // gmsh::model::getEntities(ent);
  // gmsh::model::setVisibility(ent, false);
  // gmsh::model::setVisibility({{3, 5}}, true, true);
  // gmsh::option::setNumber("Mesh.MeshOnlyVisible", 1);

  // ---- 生成三维网格 ----
  gmsh::model::mesh::generate(3);
  gmsh::write("ch05_result.msh");

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

  gmsh::finalize();
  return 0;
}

5.5 关键 API 速查表

尺寸控制

函数 / 选项 说明
factory::addPoint(x, y, z, lc, tag) 定义点及其局部网格特征长度
gmsh::option::setNumber("Mesh.MeshSizeFactor", factor) 全局尺寸缩放因子
命令行 -clscale <factor> 等效于 Mesh.MeshSizeFactor

曲面与体构建

函数 说明
factory::addCircleArc(start, center, end, tag) 三点圆弧(弧度 < Pi)
factory::addCurveLoop({curveTags}, tag) 由曲线列表构成闭合环
factory::addPlaneSurface({loopTags}, tag) 平面面:第一个环=外边界,其余=孔洞
factory::addSurfaceFilling({loopTag}, tag) 非平曲面填充:圆弧共中心则生成球面片
factory::addSurfaceLoop({surfaceTags}, tag) 由一组曲面构成封闭壳体
factory::addVolume({surfaceLoopTags}, tag) 定义体:第一个环=外边界,其余=内孔洞

网格算法

函数 / 选项 说明
gmsh::option::setNumber("Mesh.Algorithm", algo) 全局设置网格算法(1/5/6/7/8/9)
gmsh::model::mesh::setAlgorithm(dim, tag, algo) 对指定实体单独设置算法
gmsh::option::setNumber("Mesh.ElementOrder", N) 设置单元阶次(1=线性, 2=二阶)
gmsh::option::setNumber("Mesh.HighOrderOptimize", opt) 高阶曲线单元优化策略
gmsh::model::mesh::generate(3) 生成三维网格

实体可见性与物理组

函数 说明
gmsh::model::getEntities(entities) 获取所有实体列表 {dim, tag}
gmsh::model::setVisibility(entities, visible) 设置实体可见性
gmsh::model::setVisibility(entities, visible, recursive) 递归设置可见性
gmsh::option::setNumber("Mesh.MeshOnlyVisible", 1) 仅对可见实体生成网格
gmsh::model::addPhysicalGroup(dim, {tags}, tag) 将实体分组为物理组(dim=3 为体组)

注意事项

  1. lc 分级策略:在不同关键点设置不同的 lc 值,Gmsh 会在各点之间平滑过渡网格尺寸。网格尺寸从 lc 最小的点向外逐渐增大。
  2. 全局缩放Mesh.MeshSizeFactor 是在所有 lc 值计算完毕后应用的乘法因子。用于收敛性研究的网格加密而不需要重新定义几何。
  3. 圆弧角度限制:内置几何内核中,addCircleArc 的弧度必须小于 Pi。完整圆至少需要 3 段圆弧。本章球面孔洞使用 12 段,每段 90 度。
  4. 曲面填充的选择addSurfaceFilling 适用于 3 或 4 条边界的曲面;当所有边界圆弧共享同一圆心时自动生成球面片。如有更多边界曲线,可使用 OpenCASCADE 内核的 gmsh::model::occ::addSurfaceFilling
  5. 体的孔洞规则addVolume 的参数列表中,第一个曲面环定义外边界,后续曲面环定义内部孔洞。孔洞不能与外部边界相交或超出外部边界。
  6. 物理组用于区域标识:在本章中,dim=3 的物理组用于区分立方体材料区域(tag=10)和各个球面孔洞(tag=1..5),方便在求解器中施加不同属性。
  7. 选择性网格划分:设置 Mesh.MeshOnlyVisible = 1 后,generate() 仅处理当前可见的实体,这在调试或分步划分大型模型时非常有用。
  8. 编译命令(链接 Gmsh 库): g++ -o ch05_full ch05_full.cpp -lgmsh -std=c++11 使用时可通过 ./ch05_full -clscale 0.5 控制全局网格密度。