第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 在生成宿主实体的网格时会强制满足以下约束:
- 嵌入点(0D):宿主实体的网格节点中必须有一个正好落在该点坐标上
- 嵌入线(1D):宿主实体的网格边必须沿该曲线分布,即曲线本身成为网格边的集合,曲线上生成的 1D 节点同时是宿主实体网格的节点
- 嵌入面(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 5。embed(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 常见问题与注意事项
-
必须在 embed 前同步:每次使用 GEO 内核新增几何实体后,调用
embed()之前必须执行geo::synchronize()。若忘了同步,嵌入关系将不会生效,网格中不会出现预期的共形节点。 -
嵌入实体不应交叉或位于边界上:嵌入的低维实体之间不应相互交叉,也不应是宿主实体边界的一部分。如果位于边界上,默认的网格生成逻辑已经保证共形,无需额外嵌入。
-
嵌入实体的独立性:Embed 保留被嵌入实体的独立标签。这意味着可以在嵌入实体上定义 Physical Group,从而在输出网格中识别出属于加强筋/裂缝/界面的单元集合。
-
OpenCASCADE 捷径:使用
occ::fragment()时,低维实体会自动嵌入到高维实体中(如果不在边界上),相当于同时完成了 Boolean 分割和 Embed。如果已经使用 fragment 操作,通常不需要再手动调用embed()。 -
网格质量考量:嵌入实体会在宿主网格中强制引入额外的节点约束(特别是嵌入线和面的情况),可能导致局部网格质量下降。如果发现嵌入线附近的单元扭曲严重,可以适当降低该区域的
lc(目标单元尺寸),或对嵌入线使用setTransfiniteCurve()指定节点数。 -
移除嵌入关系:
removeEmbedded()仅移除嵌入关系本身,不删除几何实体。如果需要彻底删除嵌入的几何实体,应当在移除嵌入关系后使用geo::remove()或gmsh::model::removeEntities()。 -
多级嵌入不支持:Gmsh 不支持嵌套嵌入(即先将曲线嵌入面,再将此面嵌入体,期望曲线也随之进入体)。每条嵌入关系直接作用于一对"被嵌入实体-宿主"。如果需要线最终出现在体内,应直接调用
embed(1, {线标签}, 3, {体标签})。 -
gui 中查看嵌入效果:生成网格后,在 Gmsh GUI 中通过
Tools -> Options -> Mesh -> Visibility可以按几何实体过滤显示网格元素,从而观察被嵌入实体上的网格是否正确地嵌入了宿主网格中。