Skip to content

第23章 离散实体(Discrete Entity)与混合模型:地形网格实战


23.1 学习目标

  • 理解离散实体(Discrete Entity)的概念:完全由网格定义的几何体(无 CAD 参数化曲面)
  • 掌握 addDiscreteEntity(dim, tag, {boundaryTags}) 创建离散点/线/面/体,以及带符号边界标签的方向约定
  • 学会程序化注入网格数据:mesh::addNodes() 写入节点坐标 + mesh::addElementsByType() 注入单元拓扑
  • 掌握常用单元类型编号:15=点(1节点), 1=线段(2节点), 2=三角形(3节点), 3=四边形(4节点), 4=四面体(4节点), 5=六面体(8节点) 等
  • 理解 mesh::reclassifyNodes() 将节点从聚合实体重新分类到正确的边界实体(边/点)
  • 理解 mesh::createGeometry() 从网格生成几何参数化,使离散面可重新划分网格
  • 掌握混合模型(Hybrid Model):将离散面与内置 CAD 实体组合为完整计算域(仅 geo 模块支持,OpenCASCADE 不支持)
  • 完整案例:从 NxN 高度数据点程序化构建三角面离散曲面并组合 CAD 下卧层体

23.2 核心概念说明

23.2.1 离散实体 vs CAD 实体

Gmsh 中有两类几何实体:

特性 CAD 实体(Built-in / OCC) 离散实体(Discrete Entity)
几何表示 参数化曲面/曲线方程(NURBS) 完全由网格数据定义
创建方式 geo::addPoint/Line/Surfaceocc::addBox addDiscreteEntity(dim, tag, boundaryTags)
网格划分 自动生成网格 网格需程序化注入
可重划网格 天然支持 需先调用 createGeometry() 生成参数化
与 CAD 实体混合 可以(geo 模块) 可以(作为 CAD 实体的边界)

离散实体的本质:它是一种"只存储网格、无解析几何"的实体——类似 STL 文件的三角形面片不包含底层 NURBS 曲面。Gmsh 的离散实体机制让你可以直接操作这样的纯网格几何,并与其他 CAD 实体组合。

23.2.2 单元类型编号速览

Gmsh 使用整型编号标识单元类型,常用值如下:

编号 名称 维度 节点数 典型用途
15 Point 0 1 离散点
1 Line (2-node) 1 2 离散边 / 2D 边界
2 Triangle (3-node) 2 3 离散面 / 地形表面
3 Quadrangle (4-node) 2 4 结构网格面
4 Tetrahedron (4-node) 3 4 三维体网格
5 Hexahedron (8-node) 3 8 结构体网格
6 Prism (6-node) 3 6 三棱柱(楔形)
7 Pyramid (5-node) 3 5 金字塔过渡单元
8 Line3 (3-node) 1 3 二阶线
9 Triangle6 (6-node) 2 6 二阶三角形

23.2.3 混合模型原理

混合模型(Hybrid Model)是指同一计算域中同时包含离散实体和 CAD 实体,两者边界相互联结形成完整 B-rep(Boundary Representation,边界表示)。

典型案例 -- 地形网格:

┌──────────────────────────────┐
│  离散面 (Discrete Surface)   │  ← 程序化注入的三角网格(地形)
│  标签: (2, 1)                │
├──────┬──────┬──────┬──────┤
│ 侧面3│ 侧面4│ 侧面5│ 侧面6│  ← CAD 面(geo::addPlaneSurface)
├──────┴──────┴──────┴──────┤
│        底面 (s1)            │  ← CAD 面
├──────────────────────────────┤
│  体 v1 = {s1, s3, s4,       │
│           s5, s6, 离散面1}   │  ← 混合体:5个CAD面 + 1个离散面
└──────────────────────────────┘

关键约束: - 只有 gmsh::model::geo 模块(内置 CAD 内核)支持混合模型 - gmsh::model::occ 模块(OpenCASCADE)不支持——其面边界必须全是 OCC 曲线 - 混合时 CAD 曲线的端点可以直接连接到离散点标签

23.2.4 边界方向约定与工作流

addDiscreteEntity 对于二维面,边界曲线标签通过带符号的列表指定:

// 边界 = {1, 2, -3, -4} 的含义:
//   +1: 沿曲线1正方向(起点→终点)   +2: 沿曲线2正方向
//   -3: 沿曲线3负方向(终点→起点)   -4: 沿曲线4负方向

符号确保边界曲线形成一致的逆时针(CCW)环绕方向。Gmsh B-rep 约定:二维面外边界为逆时针,内边界(孔洞)为顺时针。

完整工作流:

程序化生成节点坐标
      │
      ▼
addDiscreteEntity(0/1/2, ...)    ← 创建离散点、线、面(拓扑骨架)
      │
      ▼
mesh::addNodes(2, 1, ...)        ← 节点集中注入离散面
      │
      ▼
mesh::addElementsByType(...)      ← 注入点、线、三角单元
      │
      ▼
mesh::reclassifyNodes()          ← 节点从面重分类到正确的边/点
      │
      ▼
mesh::createGeometry()           ← 从网格生成参数化(可选,但推荐)
      │
      ▼
geo::addPoint/Line/Surface...    ← 创建 CAD 下卧层(混合模型)
      │
      ▼
mesh::generate(3)                ← 三维网格生成

23.3 C++ 代码逐段讲解

23.3.1 头文件、初始化与数据准备

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

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

  int N = 100;  // N x N 控制点网格

  // lambda: 二维索引 (i,j) → 唯一节点标签 (从1开始)
  auto tag = [N](std::size_t i, std::size_t j) { return (N + 1) * i + j + 1; };

  std::vector<double> coords;            // {x0,y0,z0, x1,y1,z1, ...}
  std::vector<std::size_t> nodes;        // 节点标签(与 coords 对应)
  std::vector<std::size_t> tris;         // 三角形连接(每3个标签1个三角)
  std::vector<std::size_t> lin[4];       // 4条边界上的线段连接
  std::size_t pnt[4] = {tag(0,0), tag(N,0), tag(N,N), tag(0,N)};  // 角点

解释tag(i,j) 将二维控制点索引扁平化为一维节点标签。N=100 定义了 101x101 的节点网格。lin[4]lin[0]=底边(j=0)、lin[1]=右边(i=N)、lin[2]=顶边(j=N)、lin[3]=左边(i=0)。pnt[4] 为 4 个角点标签。

  for(std::size_t i = 0; i < N + 1; i++) {
    for(std::size_t j = 0; j < N + 1; j++) {
      nodes.push_back(tag(i, j));
      coords.insert(coords.end(), {
          (double)i / N,                         // x ∈ [0, 1]
          (double)j / N,                         // y ∈ [0, 1]
          0.05 * sin(10 * (double)(i + j) / N)   // z = 起伏高度
      });

解释:遍历 101x101 控制点网格。X/Y 均匀分布,Z 由正弦函数生成模拟地形起伏(振幅 0.05)。实际工程中可替换为 DEM(数字高程模型)数据。

      // 每个单元格被对角线切分为 2 个三角形
      if(i > 0 && j > 0) {
        tris.insert(tris.end(),
                    {tag(i-1,j-1), tag(i,j-1), tag(i-1,j)});  // 左下三角
        tris.insert(tris.end(),
                    {tag(i,j-1), tag(i,j), tag(i-1,j)});      // 右上三角
      }
      // 边界线段:左右边界 (i==0 或 i==N) 和上下边界 (j==0 或 j==N)
      if((i == 0 || i == N) && j > 0) {
        std::size_t s = (i == 0) ? 3 : 1;
        lin[s].insert(lin[s].end(), {tag(i, j - 1), tag(i, j)});
      }
      if((j == 0 || j == N) && i > 0) {
        std::size_t s = (j == 0) ? 0 : 2;
        lin[s].insert(lin[s].end(), {tag(i - 1, j), tag(i, j)});
      }
    }
  }

解释:每个控制网格单元 [i-1,i] x [j-1,j] 沿对角线 (i,j-1)-(i-1,j) 分为两个三角形。所有三角形的顶点顺序一致保持 CCW。边界线段在 i==0(右)、i==N(左)、j==0(底)、j==N(顶) 四条边上生成,后续作为离散曲线的网格数据注入。

23.3.2 创建离散实体拓扑

  // 4 个离散点(维度 0,标签 1-4,无边界)
  for(int i = 0; i < 4; i++) gmsh::model::addDiscreteEntity(0, i + 1);

  // 设置角点模型坐标(使 GUI 显示 & CAD 连接可用)
  gmsh::model::setCoordinates(1, 0, 0, coords[3 * tag(0, 0) - 1]);
  gmsh::model::setCoordinates(2, 1, 0, coords[3 * tag(N, 0) - 1]);
  gmsh::model::setCoordinates(3, 1, 1, coords[3 * tag(N, N) - 1]);
  gmsh::model::setCoordinates(4, 0, 1, coords[3 * tag(0, N) - 1]);

解释addDiscreteEntity(0, tag) 创建离散点。标签 1-4 分别对应四个角点 (0,0)、(1,0)、(1,1)、(0,1)。setCoordinates() 设置模型坐标——离散点没有默认坐标,必须显式设置才能用于 GUI 显示和与 CAD 实体的连接。坐标索引 3*tag-1 为 Z 分量(因为 coords 中每个节点 3 个 double,从索引 0 开始)。

  // 4 条离散曲线(维度 1,标签 1-4,以角点为端点)
  for(int i = 0; i < 4; i++)
    gmsh::model::addDiscreteEntity(1, i + 1, {i + 1, (i < 3) ? (i + 2) : 1});

  // 1 张离散面(维度 2,标签 1,4条曲线为边界,CCW 定向)
  gmsh::model::addDiscreteEntity(2, 1, {1, 2, -3, -4});

解释addDiscreteEntity(1, tag, {startPoint, endPoint}) 创建离散曲线——曲线1(点1→点2,底边),曲线2(点2→点3,右边),曲线3(点3→点4,顶边),曲线4(点4→点1,左边)。离散面的边界 {1, 2, -3, -4} 中,正号表示沿曲线正方向,负号表示逆方向,形成逆时针环绕。

23.3.3 注入网格数据

  // 集中注入所有节点到离散面(后续由 reclassifyNodes 纠正归属)
  gmsh::model::mesh::addNodes(2, 1, nodes, coords);

  // 注入点单元(类型15, 1节点)、线段单元(类型1, 2节点)、三角形单元(类型2, 3节点)
  for(int i = 0; i < 4; i++) {
    gmsh::model::mesh::addElementsByType(i + 1, 15, {}, {pnt[i]});
    gmsh::model::mesh::addElementsByType(i + 1, 1, {}, lin[i]);
  }
  gmsh::model::mesh::addElementsByType(1, 2, {}, tris);

解释mesh::addNodes(dim, tag, nodeTags, coords) 将节点数据绑定到实体——这里为方便起见将所有节点(内部+边界+角点)全部注入离散面 (2,1)mesh::addElementsByType(entityTag, elemType, elemTags, nodeTags) 注入单元:elemTags={} 表示自动编号单元标签,nodeTags 为连续排列的节点连接列表。

23.3.4 节点重分类与几何参数化

  // 重分类节点:根据单元归属,将节点从面重新分配到正确的子实体
  gmsh::model::mesh::reclassifyNodes();

  // 从网格生成参数化几何:使离散面可重划网格
  gmsh::model::mesh::createGeometry();

解释reclassifyNodes() 是离散实体工作流的关键。它将节点扫描所有单元的所属实体——如果某节点的全部单元都在同一条边上,该节点被重新归类到那条边;若全部单元都在同一角点上,则归类到那个点。如果跳过此步骤,后续对离散曲线的 getNodes() 查询将返回空。

createGeometry() 从离散实体的网格构建参数化几何:对曲线拟合 B-spline,对面构建 2D 参数域到 3D 空间的映射。执行后离散面可像普通 CAD 面一样调用 mesh::setSize() + mesh::generate() 重划网格。典型应用:STL 导入 → 离散实体 → 参数化 → 重新用四边形或优化三角形剖分。注意:对质量差的网格(极度畸变、自交),此函数可能失败。

23.3.5 构建 CAD 下卧层(混合模型)

  // 仅 geo 模块支持混合模型,OpenCASCADE 不支持

  // 在地形面下方创建 4 个 CAD 点(底面角点,z = -0.5)
  int p1 = gmsh::model::geo::addPoint(0, 0, -0.5);
  int p2 = gmsh::model::geo::addPoint(1, 0, -0.5);
  int p3 = gmsh::model::geo::addPoint(1, 1, -0.5);
  int p4 = gmsh::model::geo::addPoint(0, 1, -0.5);

  // 底面 4 条边 + 4 条垂直边(连接底面 CAD 点到地表离散角点)
  int c1  = gmsh::model::geo::addLine(p1, p2);
  int c2  = gmsh::model::geo::addLine(p2, p3);
  int c3  = gmsh::model::geo::addLine(p3, p4);
  int c4  = gmsh::model::geo::addLine(p4, p1);
  int c10 = gmsh::model::geo::addLine(p1, 1);   // CAD点p1 → 离散点1
  int c11 = gmsh::model::geo::addLine(p2, 2);   // CAD点p2 → 离散点2
  int c12 = gmsh::model::geo::addLine(p3, 3);   // CAD点p3 → 离散点3
  int c13 = gmsh::model::geo::addLine(p4, 4);   // CAD点p4 → 离散点4

解释geo::addLine(startPoint, endPoint) 创建 CAD 直线。垂直边 c10-c13 的第二个参数是离散点标签(1-4)——这展示了混合模型的核心机制:CAD 曲线以离散点为端点。

  // 底面
  int ll1 = gmsh::model::geo::addCurveLoop({c1, c2, c3, c4});
  int s1  = gmsh::model::geo::addPlaneSurface({ll1});

  // 4 个侧面:每条环 = 1底边 + 2垂直边 + 1离散曲线
  int ll3 = gmsh::model::geo::addCurveLoop({c1, c11, -1, -c10});
  int s3  = gmsh::model::geo::addPlaneSurface({ll3});
  int ll4 = gmsh::model::geo::addCurveLoop({c2, c12, -2, -c11});
  int s4  = gmsh::model::geo::addPlaneSurface({ll4});
  int ll5 = gmsh::model::geo::addCurveLoop({c3, c13, 3, -c12});
  int s5  = gmsh::model::geo::addPlaneSurface({ll5});
  int ll6 = gmsh::model::geo::addCurveLoop({c4, c10, 4, -c13});
  int s6  = gmsh::model::geo::addPlaneSurface({ll6});

解释:以前侧面 s3 的曲线环 {c1, c11, -1, -c10} 为例:+c1=底边 p1→p2,+c11=垂直边 p2→2,-1=离散曲线1反向(点2→点1,沿地表),-c10=垂直边 p1→1 反向(点1→p1)。离散曲线1正方向是点1→点2,但侧面需要从点2回到点1,因此取负。

  // 6 张面围成封闭空间 → 体
  int sl1 = gmsh::model::geo::addSurfaceLoop({s1, s3, s4, s5, s6, 1});
  int v1  = gmsh::model::geo::addVolume({sl1});
  gmsh::model::geo::synchronize();

解释addSurfaceLoop({faces}) 的面列表中,1 是离散面标签。它与 5 个 CAD 面共同构成封闭面环。geo::synchronize() 将所有 geo 模块实体同步到全局模型,并将 CAD 实体与离散实体的拓扑关联起来。

23.3.6 网格剖分策略

  bool transfinite     = false;   // 全六面体结构网格
  bool transfiniteAuto = false;   // 自动 Transfinite

  if(transfinite) {
    int NN = 30;
    std::vector<std::pair<int, int>> tmp;
    gmsh::model::getEntities(tmp, 1);
    for(auto c : tmp) gmsh::model::mesh::setTransfiniteCurve(c.second, NN);
    gmsh::model::getEntities(tmp, 2);
    for(auto s : tmp) {
      gmsh::model::mesh::setTransfiniteSurface(s.second);
      gmsh::model::mesh::setRecombine(s.first, s.second);
      gmsh::model::mesh::setSmoothing(s.first, s.second, 100);
    }
    gmsh::model::mesh::setTransfiniteVolume(v1);
  }
  else if(transfiniteAuto) {
    gmsh::option::setNumber("Mesh.MeshSizeMin", 0.5);
    gmsh::option::setNumber("Mesh.MeshSizeMax", 0.5);
    gmsh::model::mesh::setTransfiniteAutomatic();
  }
  else {
    gmsh::option::setNumber("Mesh.MeshSizeMin", 0.05);
    gmsh::option::setNumber("Mesh.MeshSizeMax", 0.05);
  }

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

解释:三种网格策略: - 自由四面体(默认):通用,任意几何可剖 - 手动 Transfinite:所有曲线统一节点数 → 全六面体,适用于地下层结构网格 - 自动 TransfinitesetTransfiniteAutomatic() 由尺寸约束反推每条边的节点数,自动应用 Transfinite + Recombine

mesh::generate(3) 生成三维网格。因离散面已参数化,它将被作为计算域顶面重新剖分。

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

  gmsh::finalize();
  return 0;
}

23.4 完整可运行代码

// =============================================================================
//  Chapter 23: Discrete Entities & Hybrid Models — Terrain Meshing
//
//  功能: 演示离散实体完整工作流 + 混合模型地形网格
//  编译: g++ -o ch23_terrain ch23_terrain.cpp -lgmsh -std=c++11
//  运行: ./ch23_terrain (GUI)  或  ./ch23_terrain -nopopup (批处理)
// =============================================================================

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

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

  // ===== 1. 数据准备:N x N 地形控制点 =====
  int N = 100;

  auto tag = [N](std::size_t i, std::size_t j) {
    return (N + 1) * i + j + 1;
  };

  std::vector<double> coords;
  std::vector<std::size_t> nodes;
  std::vector<std::size_t> tris;
  std::vector<std::size_t> lin[4];
  std::size_t pnt[4] = {tag(0,0), tag(N,0), tag(N,N), tag(0,N)};

  for(std::size_t i = 0; i < N + 1; i++) {
    for(std::size_t j = 0; j < N + 1; j++) {
      nodes.push_back(tag(i, j));
      coords.insert(coords.end(), {
          (double)i / N,
          (double)j / N,
          0.05 * sin(10 * (double)(i + j) / N)
      });
      if(i > 0 && j > 0) {
        tris.insert(tris.end(),
                    {tag(i-1,j-1), tag(i,j-1), tag(i-1,j)});
        tris.insert(tris.end(),
                    {tag(i,j-1), tag(i,j), tag(i-1,j)});
      }
      if((i == 0 || i == N) && j > 0) {
        std::size_t s = (i == 0) ? 3 : 1;
        lin[s].insert(lin[s].end(), {tag(i, j - 1), tag(i, j)});
      }
      if((j == 0 || j == N) && i > 0) {
        std::size_t s = (j == 0) ? 0 : 2;
        lin[s].insert(lin[s].end(), {tag(i - 1, j), tag(i, j)});
      }
    }
  }

  // ===== 2. 创建离散实体拓扑 =====
  for(int i = 0; i < 4; i++)
    gmsh::model::addDiscreteEntity(0, i + 1);

  gmsh::model::setCoordinates(1, 0, 0, coords[3 * tag(0, 0) - 1]);
  gmsh::model::setCoordinates(2, 1, 0, coords[3 * tag(N, 0) - 1]);
  gmsh::model::setCoordinates(3, 1, 1, coords[3 * tag(N, N) - 1]);
  gmsh::model::setCoordinates(4, 0, 1, coords[3 * tag(0, N) - 1]);

  for(int i = 0; i < 4; i++)
    gmsh::model::addDiscreteEntity(1, i + 1,
                                   {i + 1, (i < 3) ? (i + 2) : 1});

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

  // ===== 3. 注入网格数据 =====
  gmsh::model::mesh::addNodes(2, 1, nodes, coords);

  for(int i = 0; i < 4; i++) {
    gmsh::model::mesh::addElementsByType(i + 1, 15, {}, {pnt[i]});
    gmsh::model::mesh::addElementsByType(i + 1, 1, {}, lin[i]);
  }
  gmsh::model::mesh::addElementsByType(1, 2, {}, tris);

  // ===== 4. 重分类节点 & 生成几何参数化 =====
  gmsh::model::mesh::reclassifyNodes();
  gmsh::model::mesh::createGeometry();

  // ===== 5. 构建 CAD 下卧层(混合模型) =====
  int p1 = gmsh::model::geo::addPoint(0, 0, -0.5);
  int p2 = gmsh::model::geo::addPoint(1, 0, -0.5);
  int p3 = gmsh::model::geo::addPoint(1, 1, -0.5);
  int p4 = gmsh::model::geo::addPoint(0, 1, -0.5);

  int c1  = gmsh::model::geo::addLine(p1, p2);
  int c2  = gmsh::model::geo::addLine(p2, p3);
  int c3  = gmsh::model::geo::addLine(p3, p4);
  int c4  = gmsh::model::geo::addLine(p4, p1);
  int c10 = gmsh::model::geo::addLine(p1, 1);
  int c11 = gmsh::model::geo::addLine(p2, 2);
  int c12 = gmsh::model::geo::addLine(p3, 3);
  int c13 = gmsh::model::geo::addLine(p4, 4);

  int ll1 = gmsh::model::geo::addCurveLoop({c1, c2, c3, c4});
  int s1  = gmsh::model::geo::addPlaneSurface({ll1});
  int ll3 = gmsh::model::geo::addCurveLoop({c1, c11, -1, -c10});
  int s3  = gmsh::model::geo::addPlaneSurface({ll3});
  int ll4 = gmsh::model::geo::addCurveLoop({c2, c12, -2, -c11});
  int s4  = gmsh::model::geo::addPlaneSurface({ll4});
  int ll5 = gmsh::model::geo::addCurveLoop({c3, c13, 3, -c12});
  int s5  = gmsh::model::geo::addPlaneSurface({ll5});
  int ll6 = gmsh::model::geo::addCurveLoop({c4, c10, 4, -c13});
  int s6  = gmsh::model::geo::addPlaneSurface({ll6});

  int sl1 = gmsh::model::geo::addSurfaceLoop({s1, s3, s4, s5, s6, 1});
  int v1  = gmsh::model::geo::addVolume({sl1});
  gmsh::model::geo::synchronize();

  // ===== 6. 网格剖分 =====
  bool transfinite     = false;
  bool transfiniteAuto = false;

  if(transfinite) {
    int NN = 30;
    std::vector<std::pair<int, int>> tmp;
    gmsh::model::getEntities(tmp, 1);
    for(auto c : tmp)
      gmsh::model::mesh::setTransfiniteCurve(c.second, NN);
    gmsh::model::getEntities(tmp, 2);
    for(auto s : tmp) {
      gmsh::model::mesh::setTransfiniteSurface(s.second);
      gmsh::model::mesh::setRecombine(s.first, s.second);
      gmsh::model::mesh::setSmoothing(s.first, s.second, 100);
    }
    gmsh::model::mesh::setTransfiniteVolume(v1);
  }
  else if(transfiniteAuto) {
    gmsh::option::setNumber("Mesh.MeshSizeMin", 0.5);
    gmsh::option::setNumber("Mesh.MeshSizeMax", 0.5);
    gmsh::model::mesh::setTransfiniteAutomatic();
  }
  else {
    gmsh::option::setNumber("Mesh.MeshSizeMin", 0.05);
    gmsh::option::setNumber("Mesh.MeshSizeMax", 0.05);
  }

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

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

  gmsh::finalize();
  return 0;
}

23.5 关键 API 速查表

23.5.1 离散实体创建与管理

API 说明
gmsh::model::addDiscreteEntity(dim, tag) 创建无边界离散实体(用于 dim=0 的点)
gmsh::model::addDiscreteEntity(dim, tag, boundaryTags) 创建带边界的离散实体:线(dim=1,端点列表)、面(dim=2,带符号曲线列表)、体(dim=3,面列表)
gmsh::model::setCoordinates(tag, x, y, z) 设置离散点的模型坐标(用于 GUI 显示和 CAD 连接)
gmsh::model::getType(dim, tag, type) 获取实体类型字符串(如 "Discrete Surface"

23.5.2 网格数据注入

API 说明
gmsh::model::mesh::addNodes(dim, tag, nodeTags, coords) 向实体注入节点;coords{x0,y0,z0, x1,y1,z1,...} 交错格式
gmsh::model::mesh::addElementsByType(entityTag, elemType, elemTags, nodeTags) 向实体注入同类型单元;elemTags={} 表示自动编号
gmsh::model::mesh::addElements(entityTag, elemTypes, elemTags, nodeTags) 向实体注入混合类型单元
gmsh::model::mesh::reclassifyNodes() 将节点从聚合实体重分类到正确的边界实体(基于单元归属)
gmsh::model::mesh::createGeometry() 从离散实体网格生成参数化几何(使离散面可重划网格)

23.5.3 常用单元类型编号

编号 名称 维度 节点数 编号 名称 维度 节点数
15 Point 0 1 1 Line (2-node) 1 2
8 Line3 (3-node) 1 3 2 Triangle (3-node) 2 3
9 Triangle6 (6-node) 2 6 3 Quadrangle (4-node) 2 4
4 Tetrahedron (4-node) 3 4 5 Hexahedron (8-node) 3 8
6 Prism (6-node) 3 6 7 Pyramid (5-node) 3 5

23.5.4 混合模型相关 geo API

API 说明
gmsh::model::geo::addPoint(x, y, z) 创建 CAD 点,返回标签(端点可连接离散点)
gmsh::model::geo::addLine(startTag, endTag) 创建 CAD 直线,端点可为离散点标签
gmsh::model::geo::addCurveLoop(curveTags) 创建闭合曲线环(有向边界)
gmsh::model::geo::addPlaneSurface(wireTags) 以闭合环为边界创建平面面
gmsh::model::geo::addSurfaceLoop(faceTags) 以封闭面集合创建面环
gmsh::model::geo::addVolume(surfaceLoopTags) 以封闭面环创建体
gmsh::model::geo::synchronize() 同步 geo 模块实体到 Gmsh 全局模型

23.5.5 Transfinite 网格控制

API 说明
gmsh::model::mesh::setTransfiniteCurve(tag, nPoints) 设置曲线结构网格点数
gmsh::model::mesh::setTransfiniteSurface(tag) 对面应用 Transfinite 剖分
gmsh::model::mesh::setTransfiniteVolume(tag) 对体应用 Transfinite 剖分
gmsh::model::mesh::setRecombine(dim, tag) 对面/体启用四边形/六面体重组
gmsh::model::mesh::setTransfiniteAutomatic() 自动 Transfinite(由尺寸约束推断分割数,全重组)

23.6 注意事项与最佳实践

  1. geo 模块支持混合模型gmsh::model::occ(OpenCASCADE)不支持其边界实体为离散实体。如果已通过 OCC 构建几何,需转用 geo 模块重新构建或仅用 geo 完成混合部分。

  2. reclassifyNodes() 不可省略:跳过此步骤,所有节点将保留在首次 addNodes() 指定的实体上,后续对离散曲线/点的节点查询返回空,createGeometry() 可能产生不正确结果。

  3. createGeometry() 的质量依赖:参数化几何的质量取决于输入网格质量。极度狭长或反转的三角形可能导致参数化失败。建议调用前对网格进行质量检查。

  4. 边界标签的符号约定:二维面边界中正号=沿曲线正方向,负号=逆方向。错误符号导致面法向反转,影响体定义和网格生成。CCW 环绕是基本原则。

  5. 离散实体与分区实体的区别:两者都存储为 "Discrete *" 类型,但分区实体(mesh::partition() 生成)额外携带分区索引和父实体信息(通过 getPartitions()/getParent() 查询)。

  6. 节点和单元标签全局唯一addNodes()addElementsByType() 中的标签必须在整个模型中唯一。

  7. 从外部文件导入网格的替代方案:除程序化注入外,可通过 gmsh::merge("file.stl") 导入 STL,然后调用 mesh::classifySurfaces(angle, boundary, curveAngle) 自动创建离散实体和拓扑。

  8. 地形数据真实来源:实际工程中地形数据通常来自 GIS/DEM 文件,只需将 coords 的 Z 分量替换为真实海拔,其余流程完全一致。