Skip to content

第13章 嵌入实体(Embedded Entity):在网格中强制生成共形节点


13.1 学习目标

  • 理解嵌入实体(Embedded Entity)的概念:将低维实体"植入"高维实体内部,强制在嵌入位置生成共形节点
  • 掌握 mesh::embed() 的三种嵌入模式:嵌入点(0D)到面/体、嵌入线(1D)到面/体、嵌入面(2D)到体
  • 学会使用 mesh::removeEmbedded() 移除嵌入关系,以及 mesh::getEmbedded() 查询已有嵌入关系
  • 理解 Embed 与 Fragment 的本质区别:Embed 保留独立实体身份,Fragment 会切分几何
  • 能够将嵌入技术应用于 CAE 场景:加强筋(Rib)、裂缝(Crack)、分层界面(Interface)、集中力施加点

13.2 核心概念说明

13.2.1 默认网格的顺应性限制

Gmsh 生成网格时,有一个默认规则:只有当低维实体位于高维实体的边界上时,网格才保证共形(conformal)。换句话说:

  • 点只有在曲线端点、面/体边界上,才会被网格节点穿过
  • 曲线只有在面/体边界上,网格边才会沿其分布
  • 面只有在体边界上,网格面才会与其贴合

如果一条曲线悬浮在面的内部(比如面中央的一道加强筋),默认情况下 Gmsh 在生成该面的 2D 网格时不会考虑这条曲线的存在——网格会直接"穿过"它,线上的节点和面的节点不是共形的。这在 CAE 前处理中是不可接受的:我们要求加强筋、裂缝、材料界面等内部几何特征必须产生共形节点,才能在后续求解器中正确传递位移/力。

13.2.2 嵌入实体(Embedded Entity)的解决思路

嵌入实体(Embedded Entity) 是 Gmsh 专门为此设计的机制。它的核心语义是:

将低维实体 d(dim)"植入"到高维实体 D(inDim)中,要求在对 D 进行网格划分时,在低维实体 d 的几何位置上强制生成共形节点

嵌入关系由 mesh::embed() 函数建立:

gmsh::model::mesh::embed(dim, {tags}, inDim, inTag);

参数含义: - dim:被嵌入实体的维度,可以是 0(点)、1(曲线)或 2(面) - tags:被嵌入实体的标签列表 - inDim:宿主(父)实体的维度,必须是 2(面)或 3(体),且必须严格大于 dim - inTag:宿主实体的标签

允许的嵌入组合:

被嵌入实体 (dim) 宿主实体 (inDim) 典型 CAE 应用
0D (点) 2D (面) 面内集中力/集中质量施加点
0D (点) 3D (体) 体内点荷载、监测点
1D (曲线) 2D (面) 面内加强筋、裂缝线、带线
1D (曲线) 3D (体) 体内加强筋、管线、预应力筋
2D (曲面) 3D (体) 材料分界面、层合板界面、断层

13.2.3 嵌入后网格的行为

嵌入后,Gmsh 在生成宿主实体的网格时会强制满足以下约束:

  1. 嵌入点(0D):宿主实体的网格节点中必须有一个正好落在该点坐标上
  2. 嵌入线(1D):宿主实体的网格边必须沿该曲线分布,即曲线本身成为网格边的集合,曲线上生成的 1D 节点同时是宿主实体网格的节点
  3. 嵌入面(2D):宿主 3D 体网格的面必须贴合该曲面,曲面上生成的 2D 单元同时是 3D 体网格的面

简而言之:嵌入后,低维实体的网格完整地成为高维实体网格的一部分。

13.2.4 Embed 与 Fragment 的区别

这是一个重要的概念区分,两者都涉及低维与高维实体的交互:

机制 操作性质 对几何的影响 实体身份 适用场景
Embed 网格层面约束(不改变几何拓扑) 几何模型不变,仅在网格生成时强制共形 保留独立标签 在已有大区域内植入加强筋、裂缝等内部特征
Fragment 几何层面操作(改变几何拓扑) 用低维实体去"切"高维实体,产生新的分割几何 产生大量新实体(切割碎片) 需要将模型彻底分割为独立子域(如多体装配)

用通俗的比喻来说:Embed 好比在一整块蛋糕里插入牙签(蛋糕本身还是一个整体,但牙签位置被标记);Fragment 好比用刀把蛋糕切成几块(蛋糕变成了各自独立的多个小块)。

重要提示:在 OpenCASCADE 内核中使用 fragment() 时,如果低维实体不位于高维实体的边界上,它们会被自动嵌入到高维实体中——这是一个有用的捷径。

13.2.5 关于同步(Synchronize)

在调用 mesh::embed() 之前,必须先调用 geo::synchronize()(或 occ::synchronize(),取决于所用的 CAD 内核)。这是因为嵌入操作发生在 Gmsh 内核模型层面,需要 CAD 模块的数据已同步到模型中。同样,如果使用 GEO 内核定义了新的几何实体,也需要在 embed() 之前重新同步。


13.3 C++ 代码逐段讲解

以下代码综合演示了四种嵌入场景:在面内嵌入点、在面内嵌入线、在体内嵌入点、在体内嵌入线和面。

13.3.1 基础几何与面内嵌入点

// ----------------------------------------------------------------------------
// 步骤 1: 建立基础几何——一个 0.1 × 0.3 的矩形面
// ----------------------------------------------------------------------------
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);

四个角点构成矩形,通过 CurveLoop 围出平面区域 Surface 1。这是所有后续嵌入操作的"基础宿主面"。

// ----------------------------------------------------------------------------
// 步骤 2: 放大网格尺寸并定义面内嵌入点
// ----------------------------------------------------------------------------
lc *= 4;
gmsh::model::geo::mesh::setSize({{0, 1}, {0, 2}, {0, 3}, {0, 4}}, lc);

// 在面内定义一个独立点(不属于面的任何边界曲线)
gmsh::model::geo::addPoint(0.02, 0.02, 0., lc, 5);

// ★ 关键:embed 之前必须同步
gmsh::model::geo::synchronize();

// 将点 5 嵌入到 Surface 1 中
gmsh::model::mesh::embed(0, {5}, 2, 1);

这段代码的要点: - 首先将全局网格尺寸放大 4 倍(从 1e-2 变为 4e-2),使网格更粗,便于观察嵌入效果 - addPoint(0.02, 0.02, 0., lc, 5) 在面内部定义一个孤立点(坐标(0.02, 0.02, 0),标签 5),此点不在面的任何边界曲线上 - synchronize() 必须在 embed() 前调用,确保点 5 在 Gmsh 内核模型中注册 - embed(0, {5}, 2, 1) 声明:将维度 0 的实体{5}嵌入到维度 2 的实体 1(即 Surface 1)中

嵌入后,该面生成 2D 网格时会在坐标(0.02, 0.02, 0)处强制放置一个节点。这在 CAE 中常用于施加点荷载或设置监测点。

13.3.2 面内嵌入线

// ----------------------------------------------------------------------------
// 步骤 3: 在面内定义并嵌入一条曲线
// ----------------------------------------------------------------------------
gmsh::model::geo::addPoint(0.02, 0.12, 0., lc, 6);
gmsh::model::geo::addPoint(0.04, 0.18, 0., lc, 7);
gmsh::model::geo::addLine(6, 7, 5);      // 定义一条内部线段

gmsh::model::geo::synchronize();
gmsh::model::mesh::embed(1, {5}, 2, 1);  // 将 Line 5 嵌入到 Surface 1 中

这里先在面内定义一条直线段(从点 6 到点 7),标签为 Line 5embed(1, {5}, 2, 1) 将维度 1 的 Line 5 嵌入到维度 2 的 Surface 1

嵌入后,该线段的网格将完整地成为面网格的一部分:线上的每个 1D 节点同时也是面的 2D 节点,沿线的网格边同时是面网格中三角形的边。这在 CAE 中可用于模拟面内加强筋或指定缝合线。

13.3.3 向体内嵌入点和线

// ----------------------------------------------------------------------------
// 步骤 4: 将面拉伸为体,并向体内嵌入点和曲线
// ----------------------------------------------------------------------------
// 沿 z 轴拉伸 Surface 1 生成 Volume 1
std::vector<std::pair<int, int>> ext;
gmsh::model::geo::extrude({{2, 1}}, 0, 0, 0.1, ext);

// 向体内嵌入一个点
int p = gmsh::model::geo::addPoint(0.07, 0.15, 0.025, lc);

gmsh::model::geo::synchronize();
gmsh::model::mesh::embed(0, {p}, 3, 1);   // 将点 p 嵌入到 Volume 1 中

extrude({{2, 1}}, 0, 0, 0.1, ext) 将维度 2 的 Surface 1 沿 z 轴正方向拉伸 0.1 个单位,生成 Volume 1(体标签 1)。注意此处使用自动标签风格: - addPoint(...) 返回值 p 是 Gmsh 自动分配的点标签(比不指定标签参数时的返回值) - embed(0, {p}, 3, 1) 将该点嵌入到体积 1 中

// 向体内嵌入一条曲线
gmsh::model::geo::addPoint(0.025, 0.15, 0.025, lc, p + 1);
int l = gmsh::model::geo::addLine(7, p + 1);

gmsh::model::geo::synchronize();
gmsh::model::mesh::embed(1, {l}, 3, 1);   // 将 Line l 嵌入到 Volume 1 中

这里在体内定义了一条线段,从面内嵌入点 7(已在步骤 3 中定义)到新点 p+1。embed(1, {l}, 3, 1) 将这条线段嵌入到体中。生成 3D 网格时,该线段上的 1D 节点将成为 3D 四面体网格的顶点,沿线的边将构成四面体的棱。

这在 CAE 中用于在 3D 体内定义埋管(如锚杆、预应力索)或裂缝前缘。

13.3.4 向体内嵌入面

// ----------------------------------------------------------------------------
// 步骤 5: 在体内定义并嵌入一个曲面
// ----------------------------------------------------------------------------
gmsh::model::geo::addPoint(0.02, 0.12, 0.05, lc, p + 2);
gmsh::model::geo::addPoint(0.04, 0.12, 0.05, lc, p + 3);
gmsh::model::geo::addPoint(0.04, 0.18, 0.05, lc, p + 4);
gmsh::model::geo::addPoint(0.02, 0.18, 0.05, lc, p + 5);

// 定义该面的四条边界线
gmsh::model::geo::addLine(p + 2, p + 3, l + 1);
gmsh::model::geo::addLine(p + 3, p + 4, l + 2);
gmsh::model::geo::addLine(p + 4, p + 5, l + 3);
gmsh::model::geo::addLine(p + 5, p + 2, l + 4);

// 由线框创建曲面
int ll = gmsh::model::geo::addCurveLoop({l + 1, l + 2, l + 3, l + 4});
int s  = gmsh::model::geo::addPlaneSurface({ll});

gmsh::model::geo::synchronize();
gmsh::model::mesh::embed(2, {s}, 3, 1);   // 将 Surface s 嵌入到 Volume 1 中

步骤 5 在 z = 0.05 平面(体积 z 范围 0~0.1 的中间高度)定义了一个矩形小曲面,并通过 embed(2, {s}, 3, 1) 将其嵌入到 Volume 1 中。这是唯一允许的 2D→3D 嵌入组合。

嵌入后,该曲面的 2D 网格面片将成为 3D 四面体网格的内部面——四面体不会"穿透"它,而是沿其分界。这在 CAE 中用于模拟材料分层界面、节理面或断层。

13.3.5 生成网格并输出

// ----------------------------------------------------------------------------
// 步骤 6: 生成 3D 网格并保存
// ----------------------------------------------------------------------------
gmsh::model::mesh::generate(3);
gmsh::write("t15.msh");

// 如果命令行没有 "-nopopup" 参数,启动 GUI 查看结果
std::set<std::string> args(argv, argv + argc);
if (!args.count("-nopopup")) gmsh::fltk::run();

generate(3) 生成最高到 3D 的网格。由于存在嵌入关系,Gmsh 会在各维度宿主实体剖分时自动考虑所有嵌入约束,确保嵌入位置产生共形节点。


13.4 完整可运行代码

// ============================================================================
// 第 13 章完整示例:Gmsh 嵌入实体(Embedded Entity)
// 编译: g++ -o ch13 ch13.cpp -I/path/to/gmsh/include -L/path/to/gmsh/lib -lgmsh
// 运行: ./ch13            # 打开 GUI 查看嵌入效果
//       ./ch13 -nopopup   # 仅生成 t15.msh
// ============================================================================

#include <set>
#include <gmsh.h>

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

  // =================== 1. 基础几何:0.1 × 0.3 矩形面 ===================
  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);

  // 放大尺寸以产生较粗网格
  lc *= 4;
  gmsh::model::geo::mesh::setSize({{0, 1}, {0, 2}, {0, 3}, {0, 4}}, lc);

  // =================== 2. 面内嵌入点(0D → 2D) ===================
  gmsh::model::geo::addPoint(0.02, 0.02, 0., lc, 5);
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::embed(0, {5}, 2, 1);

  // =================== 3. 面内嵌入线(1D → 2D) ===================
  gmsh::model::geo::addPoint(0.02, 0.12, 0., lc, 6);
  gmsh::model::geo::addPoint(0.04, 0.18, 0., lc, 7);
  gmsh::model::geo::addLine(6, 7, 5);
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::embed(1, {5}, 2, 1);

  // =================== 4. 拉伸得到体,体内嵌入点和线(0D/1D → 3D) ===================
  std::vector<std::pair<int, int>> ext;
  gmsh::model::geo::extrude({{2, 1}}, 0, 0, 0.1, ext);

  int p = gmsh::model::geo::addPoint(0.07, 0.15, 0.025, lc);
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::embed(0, {p}, 3, 1);   // 体内嵌入点

  gmsh::model::geo::addPoint(0.025, 0.15, 0.025, lc, p + 1);
  int l = gmsh::model::geo::addLine(7, p + 1);
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::embed(1, {l}, 3, 1);   // 体内嵌入线

  // =================== 5. 体内嵌入面(2D → 3D) ===================
  gmsh::model::geo::addPoint(0.02, 0.12, 0.05, lc, p + 2);
  gmsh::model::geo::addPoint(0.04, 0.12, 0.05, lc, p + 3);
  gmsh::model::geo::addPoint(0.04, 0.18, 0.05, lc, p + 4);
  gmsh::model::geo::addPoint(0.02, 0.18, 0.05, lc, p + 5);

  gmsh::model::geo::addLine(p + 2, p + 3, l + 1);
  gmsh::model::geo::addLine(p + 3, p + 4, l + 2);
  gmsh::model::geo::addLine(p + 4, p + 5, l + 3);
  gmsh::model::geo::addLine(p + 5, p + 2, l + 4);

  int ll = gmsh::model::geo::addCurveLoop({l + 1, l + 2, l + 3, l + 4});
  int s  = gmsh::model::geo::addPlaneSurface({ll});
  gmsh::model::geo::synchronize();
  gmsh::model::mesh::embed(2, {s}, 3, 1);   // 体内嵌入面

  // =================== 6. 网格生成与输出 ===================
  gmsh::model::mesh::generate(3);
  gmsh::write("ch13_result.msh");

  // =================== 7. 可选:查看嵌入关系 ===================
  gmsh::vectorpair embedded;
  gmsh::model::mesh::getEmbedded(3, 1, embedded);
  // 输出所有嵌入在 Volume 1 中的实体列表(维度, 标签)
  // printf("Entities embedded in Volume 1:\n");
  // for (auto &p : embedded) printf("  dim=%d, tag=%d\n", p.first, p.second);

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

  gmsh::finalize();
  return 0;
}

编译运行:

g++ -o ch13 ch13.cpp -I/path/to/gmsh/include -L/path/to/gmsh/lib -lgmsh
./ch13              # 打开 GUI,可观察嵌入点/线/面处的共形网格
./ch13 -nopopup     # 仅生成 ch13_result.msh

13.5 关键 API 速查表

嵌入(Embed)相关 API

函数 签名 说明
mesh::embed embed(int dim, vector<int> tags, int inDim, int inTag) dim 维度、标签为 tags 的实体嵌入到 inDim 维度、标签为 inTag 的宿主实体中。dim 必须严格小于 inDim,且 inDim 只能是 2 或 3
mesh::removeEmbedded removeEmbedded(vectorpair dimTags, int dim = -1) dimTags 指定的宿主实体中移除嵌入关系。若 dim >= 0,仅移除该维度的嵌入实体(如 dim=0 只移除嵌入点)。不删除几何实体本身
mesh::getEmbedded getEmbedded(int dim, int tag, vectorpair &dimTags) 查询宿主实体 (dim, tag) 中的所有已嵌入实体,结果存入 dimTags(每个元素为一对 (dim, tag))

嵌入前必须调用的前置 API

函数 说明
geo::synchronize() 将 GEO 内核的几何数据同步到 Gmsh 模型。每次定义新的几何实体后、调用 embed() 之前都必须调用
occ::synchronize() 同上,适用于 OpenCASCADE 内核

相关几何 API

函数 说明
geo::addPoint(x, y, z, meshSize, tag) 创建点。若 tag 省略则返回自动分配的标签
geo::addLine(p1, p2, tag) 由两端点创建直线段,返回线的标签
geo::addCurveLoop({c1, c2, ...}, tag) 由有序曲线列表创建闭合线框,负数表示反向
geo::addPlaneSurface({cl1, ...}, tag) 由线框创建平面曲面
geo::extrude({dim, tag}, dx, dy, dz, out) 将实体沿方向向量 (dx, dy, dz) 拉伸。返回结果存入 out 向量
mesh::setSize 设置特定几何点的参考网格尺寸

13.6 常见问题与注意事项

  1. 必须在 embed 前同步:每次使用 GEO 内核新增几何实体后,调用 embed() 之前必须执行 geo::synchronize()。若忘了同步,嵌入关系将不会生效,网格中不会出现预期的共形节点。

  2. 嵌入实体不应交叉或位于边界上:嵌入的低维实体之间不应相互交叉,也不应是宿主实体边界的一部分。如果位于边界上,默认的网格生成逻辑已经保证共形,无需额外嵌入。

  3. 嵌入实体的独立性:Embed 保留被嵌入实体的独立标签。这意味着可以在嵌入实体上定义 Physical Group,从而在输出网格中识别出属于加强筋/裂缝/界面的单元集合。

  4. OpenCASCADE 捷径:使用 occ::fragment() 时,低维实体会自动嵌入到高维实体中(如果不在边界上),相当于同时完成了 Boolean 分割和 Embed。如果已经使用 fragment 操作,通常不需要再手动调用 embed()

  5. 网格质量考量:嵌入实体会在宿主网格中强制引入额外的节点约束(特别是嵌入线和面的情况),可能导致局部网格质量下降。如果发现嵌入线附近的单元扭曲严重,可以适当降低该区域的 lc(目标单元尺寸),或对嵌入线使用 setTransfiniteCurve() 指定节点数。

  6. 移除嵌入关系removeEmbedded() 仅移除嵌入关系本身,不删除几何实体。如果需要彻底删除嵌入的几何实体,应当在移除嵌入关系后使用 geo::remove()gmsh::model::removeEntities()

  7. 多级嵌入不支持:Gmsh 不支持嵌套嵌入(即先将曲线嵌入面,再将此面嵌入体,期望曲线也随之进入体)。每条嵌入关系直接作用于一对"被嵌入实体-宿主"。如果需要线最终出现在体内,应直接调用 embed(1, {线标签}, 3, {体标签})

  8. gui 中查看嵌入效果:生成网格后,在 Gmsh GUI 中通过 Tools -> Options -> Mesh -> Visibility 可以按几何实体过滤显示网格元素,从而观察被嵌入实体上的网格是否正确地嵌入了宿主网格中。