Skip to content

第24章 后处理视图(View):List-based 与 Model-based 数据导入


24.1 学习目标

  • 理解 Gmsh 后处理的两大数据类型:基于列表的视图(List-based View)基于模型的视图(Model-based View),以及各自的适用场景
  • 掌握 view::add() 创建视图、view::addListData() 用展平数组导入自定义坐标+场值数据,理解元素形状字符串("ST"、"VL"、"SQ"等)的命名规则
  • 掌握 view::addHomogeneousModelData()view::addModelData() 将求解器输出绑定到模型网格节点/单元
  • 理解 step(时间步)参数在多步动画和跨模型切换中的作用
  • 学会 view::addListDataString() 添加文字标注、view::probe() 探针采样、高阶场的自适应可视化配置

24.2 核心概念说明

24.2.1 View 与 Plugin 的分工

后处理(Post-Processing)由两个组件协作完成:

View (视图) ─── 本章内容 ─── 数据的容器
├── List-based: 独立于任何模型与网格,自包含坐标+场值
└── Model-based: 绑定到特定模型实体与网格节点
Plugin (插件) ─── 见第20章 ─── 对 View 进行运算与变换

View 存储标量场(Scalar Field)、矢量场(Vector Field)或张量场(Tensor Field)的数据及渲染选项。GUI 可同时显示多个视图,每个独立控制可见性、时间步和显示模式。

24.2.2 两种视图对比

特性 List-based Model-based
数据来源 用户自定义坐标+场值 绑定模型网格节点/单元
独立性 完全独立,无需模型 必须依赖已有模型
数据结构 逐元素展平列表 逐节点/逐单元数组
多步支持 定长,各步结构相同 每步可绑定不同模型
典型用途 传感器读数、解析解可视化 求解器输出(应力/位移/温度)
文件格式 .pos .msh

24.2.3 元素形状字符串速查

addListData() 第二个参数为两字符字符串:首字符=场类型,次字符=元素形状。

场类型(首字符)S=标量(1分量)、V=矢量(3分量)、T=张量(9分量)

元素形状(次字符)P=点(1节点)、L=线(2)、T=三角形(3)、Q=四边形(4)、S=四面体(4)、H=六面体(8)、I=三棱柱(6)、Y=金字塔(5)

示例:"ST"=三角形标量场(每单元3值),"VL"=线矢量场(每节点3分量,共6值)。

24.2.4 Model-based 的三种数据类型

dataType 含义 场连续性
"NodeData" 节点数据,每节点一个值 连续场(节点间插值)
"ElementData" 单元数据,每单元一个常数值 分片常值场
"ElementNodeData" 单元节点数据,每单元的每节点一个值 间断场(单元边界可跳变)

24.3 List-based 视图编程(基于 x3.cpp)

24.3.1 创建视图并导入三角形标量场

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

int main(int argc, char **argv)
{
  gmsh::initialize(argc, argv);

  // 创建视图: view::add(name) 返回视图的整数 tag
  int t1 = gmsh::view::add("A list-based view");

List-based 数据组织方式:每个元素的坐标(先全部 x、再全部 y、最后全部 z),随后追加各时间步的场值。以下构建两个一阶三角形(各3节点),并填充10个时间步的标量数据:

  // 三角形1和三角形2的坐标(x集合 + y集合 + z集合,各3个值)
  std::vector<double> triangle1 = {
    0., 1., 1.,  0., 0., 1.,  0., 0., 0.};
  std::vector<double> triangle2 = {
    0., 1., 0.,  0., 1., 1.,  0., 0., 0.};

  // 为每个三角形追加 10 个时间步的场值(每步3个标量值)
  for(int step = 0; step < 10; step++) {
    triangle1.insert(triangle1.end(), {10., 11. - step, 12.});
    triangle2.insert(triangle2.end(), {11., 12., 13. + step});
  }

  // 拼接两个三角形数据并导入
  std::vector<double> triangles(triangle1);
  triangles.insert(triangles.end(), triangle2.begin(), triangle2.end());
  // 参数: viewTag, "ST"=标量三角形, 2个元素, 展平数据
  gmsh::view::addListData(t1, "ST", 2, triangles);

总数据量 = 233(坐标) + 1023(场值) = 78 个 double。addListData 根据元素类型和数量自动推断数据长度。

24.3.2 导入线段矢量场

矢量场每节点需3个分量 (vx, vy, vz)。以一条线段("VL")为例:

  // 线段: 2节点坐标(x集合 + y集合 + z集合)
  std::vector<double> line = {0., 1.,  1.2, 1.2,  0., 0.};
  for(int step = 0; step < 10; step++)
    // 每步: 2节点 x 3分量 = 6个值
    line.insert(line.end(), {10. + step, 0., 0., 10. + step, 0., 0.});
  gmsh::view::addListData(t1, "VL", 1, line);

24.3.3 添加文字标注与选项配置

  // 2D 标注(屏幕坐标,原点=窗口中心,正y朝上)和 3D 标注(世界坐标)
  gmsh::view::addListDataString(t1, {20., -20.}, {"Created with Gmsh"});
  gmsh::view::addListDataString(t1, {0.5, 1.5, 0.},
    {"A multi-step list-based view"},
    {"Align", "Center", "Font", "Helvetica"});

  // 视图显示选项
  gmsh::view::option::setNumber(t1, "TimeStep", 5);       // 显示第5步
  gmsh::view::option::setNumber(t1, "IntervalsType", 3);  // 3=连续色谱

  // 探针采样: 在 (0.9, 0.1, 0) 获取插值后的场值
  std::vector<double> val;
  double dist;
  gmsh::view::probe(t1, 0.9, 0.1, 0, val, dist);
  std::cout << "Value at (0.9, 0.1, 0):";
  for(auto v : val) std::cout << " " << v;
  std::cout << std::endl;

常用视图选项:"TimeStep"(当前步)、"IntervalsType"(1=等值线,2=填充,3=连续色谱)、"NbIso"(等值线数量)、"RangeType"(1=按步,2=跨步,3=自定义)、"ShowScale"(显示色条)。

24.3.4 高阶场与自适应可视化

高阶场需通过 setInterpolationMatrices() 定义基函数的系数矩阵 c 和指数矩阵 e:

  int t2 = gmsh::view::add("Second order quad");
  // 4节点四边形的坐标 + 9个场值(支持二阶9节点插值)
  std::vector<double> quad = {
    0.,1.,1.,0.,  -1.2,-1.2,-0.2,-0.2,  0.,0.,0.,0.};
  quad.insert(quad.end(), {1.,1.,1.,1., 3.,3.,3.,3., -3.});

  // 基函数: f[i](u,v,w) = sum_j c[i][j] * u^e[j][0] * v^e[j][1] * w^e[j][2]
  gmsh::view::setInterpolationMatrices(
    t2, "Quadrangle", 9,
    { /* 9x9 系数矩阵 c, 行优先 */ },
    { /* 9x3 指数矩阵 e, 行优先 */ });

  gmsh::view::addListData(t2, "SQ", 1, quad);

  // 启用自适应可视化网格(AMR驱动渲染)
  gmsh::view::option::setNumber(t2, "AdaptVisualizationGrid", 1);
  gmsh::view::option::setNumber(t2, "TargetError", 1e-2);
  gmsh::view::option::setNumber(t2, "MaxRecursionLevel", 5);

Gmsh 根据 TargetError 自动细分单元直到插值误差达标或达到 MaxRecursionLevel。细分后的数据可通过 view::getListData() 取回(设置 returnAdaptive=true)。

24.3.5 保存与启动

  gmsh::view::write(t1, "x3.pos");  // 保存为 .pos 文件

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

24.4 Model-based 视图编程(基于 x4.cpp)

24.4.1 创建模型与手动网格

Model-based 视图必须绑定到模型。首先创建模型并手动构建网格:

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

int main(int argc, char **argv)
{
  gmsh::initialize(argc, argv);

  gmsh::model::add("simple model");
  int surf = gmsh::model::addDiscreteEntity(2);  // 离散面,无几何描述

  // 添加4个节点: {标签}, 展平坐标(xyz xyz xyz xyz)
  gmsh::model::mesh::addNodes(2, surf, {1,2,3,4},
    {0.,0.,0.,  1.,0.,0.,  1.,1.,0.,  0.,1.,0.});
  // 添加2个三角形(element type=2): {单元标签}, {连通性}
  gmsh::model::mesh::addElementsByType(surf, 2, {1,2}, {1,2,3,  1,3,4});

addDiscreteEntity(2) 创建维度2的离散实体,无需 OCC 几何,纯粹由用户定义的网格填充,适用于只需后处理的"裸网格"场景。

24.4.2 添加 NodeData(连续场)

  int t1 = gmsh::view::add("Continuous");
  for(int step = 0; step < 10; step++) {
    gmsh::view::addHomogeneousModelData(
      t1, step, "simple model", "NodeData",
      {1, 2, 3, 4},                          // 节点标签列表
      {10., 10., 12. + step, 13. + step});   // 逐节点场值(4个)
  }

addHomogeneousModelData() 参数:viewTag、step(0-based时间步)、modelName(须与 model::add() 一致)、dataType、tags 列表、data 展平数组。"NodeData" 产生连续场,Gmsh 在节点间线性插值。

24.4.3 添加 ElementNodeData(间断场)

  int t2 = gmsh::view::add("Discontinuous");
  for(int step = 0; step < 10; step++) {
    gmsh::view::addHomogeneousModelData(
      t2, step, "simple model", "ElementNodeData",
      {1, 2},                                // 单元标签列表
      {10., 10., 12. + step,                 // 单元1的3个节点值
       14., 15., 13. + step});               // 单元2的3个节点值
  }

对于2个三角形(各3节点),每步需 2*3=6 个值。"ElementNodeData" 容许单元边界上值不连续(如节点3处单元1值为12+step、单元2值为14),呈现颜色跳变。"ElementData" 更简单:每单元仅一个常量值。

24.4.4 跨模型多步动画

Model-based 视图的核心优势是不同时间步可绑定到不同模型,支持网格重划分后的无缝可视化:

  // 创建第二个模型: OCC 立方体 + 自动四面体剖分
  gmsh::model::add("another model");
  gmsh::model::occ::addBox(0, 0, 0, 1, 1, 1);
  gmsh::model::occ::synchronize();
  gmsh::model::mesh::generate(3);  // 三维网格

  // 获取新模型的所有节点
  std::vector<std::size_t> nodes;
  std::vector<double> coord, coordParam;
  gmsh::model::mesh::getNodes(nodes, coord, coordParam);

  // 在同一个视图 t1 中添加 step 11-19,绑定到新模型
  for(int step = 11; step < 20; step++) {
    std::vector<double> val;
    for(std::size_t i = 0; i < coord.size(); i += 3)
      val.push_back(step * coord[i]);  // 场值 = step * x
    gmsh::view::addHomogeneousModelData(t1, step, "another model",
                                        "NodeData", nodes, val);
  }

视图 t1 的 step 0-9 基于粗网格(4节点2三角形),step 11-19 基于细网格(OCC立方体)。在 GUI 中播放动画时自动切换。这对动态裂纹扩展等需要网格重划分的时变求解器尤为重要。

24.4.5 addModelData() 处理非均匀数据

当需要混合不同类型元素时(如三角形+四边形混合网格),应使用更通用的 addModelData()

gmsh::view::addModelData(viewTag, step, modelName, dataType,
  {tag1, tag2, ...},    // 实体标签列表
  data,                 // 展平数据 (double*)
  numComponents,        // 每实体分量数(如标量=1,矢量=3)
  numEntities);         // 实体总数
  // 可选参数 partitionSize[]: 指定每个实体各自的数据长度

它通过 partitionSize 数组支持每个实体拥有不同的节点/值数量,适用于混合网格场景。

24.4.6 保存

  gmsh::view::write(t1, "x4_t1.msh");
  gmsh::view::write(t2, "x4_t2.msh");
  // 跨模型视图会自动生成多个文件

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

若设置 gmsh::option::setNumber("PostProcessing.SaveMesh", 1)write() 将同时保存网格几何;否则仅保存场值。


24.5 完整可运行代码

示例一:List-based 视图

// ch24_list_based.cpp
#include <set>
#include <iostream>
#include <gmsh.h>

int main(int argc, char **argv)
{
  gmsh::initialize(argc, argv);

  // ---- 1. 三角形标量场 (10步) ----
  int t1 = gmsh::view::add("List-based triangles + lines");
  std::vector<double> tri1 = {0.,1.,1., 0.,0.,1., 0.,0.,0.};
  std::vector<double> tri2 = {0.,1.,0., 0.,1.,1., 0.,0.,0.};
  for(int s = 0; s < 10; s++) {
    tri1.insert(tri1.end(), {10., 11.-s, 12.});
    tri2.insert(tri2.end(), {11., 12., 13.+s});
  }
  tri1.insert(tri1.end(), tri2.begin(), tri2.end());
  gmsh::view::addListData(t1, "ST", 2, tri1);

  // ---- 2. 线段矢量场 (10步) ----
  std::vector<double> line = {0.,1., 1.2,1.2, 0.,0.};
  for(int s = 0; s < 10; s++)
    line.insert(line.end(), {10.+s,0.,0., 10.+s,0.,0.});
  gmsh::view::addListData(t1, "VL", 1, line);

  // ---- 3. 文字标注 ----
  gmsh::view::addListDataString(t1, {20.,-20.}, {"Created with Gmsh"});
  gmsh::view::addListDataString(t1, {0.5,1.5,0.},
    {"A multi-step list-based view"},
    {"Align","Center","Font","Helvetica"});

  // ---- 4. 选项与探针 ----
  gmsh::view::option::setNumber(t1, "TimeStep", 5);
  gmsh::view::option::setNumber(t1, "IntervalsType", 3);
  std::vector<double> val;
  double dist;
  gmsh::view::probe(t1, 0.9, 0.1, 0, val, dist);
  std::cout << "Probe at (0.9,0.1,0):";
  for(auto v : val) std::cout << " " << v;
  std::cout << std::endl;

  // ---- 5. 二阶四边形 + 自适应可视化 ----
  int t2 = gmsh::view::add("Second order quad");
  std::vector<double> quad = {
    0.,1.,1.,0., -1.2,-1.2,-0.2,-0.2, 0.,0.,0.,0.};
  quad.insert(quad.end(), {1.,1.,1.,1., 3.,3.,3.,3., -3.});

  gmsh::view::setInterpolationMatrices(
    t2, "Quadrangle", 9,
    {0,0,0.25,0,0,-0.25,-0.25,0,0.25, 0,0,0.25,0,0,-0.25,0.25,0,-0.25,
     0,0,0.25,0,0,0.25,0,0,0.25,     -0.25,0,-0.25,0,0,-0.5,0.5,0,0.5,
     0,-0.5,0,0,0.5,-0.5,0,0.5,-0.5, 0,0.5,0,-0.5,0,0,0,0,-0.5,
     0.5,0,-0.5,0,0.5,0,0,0.5,-0.5,  0,0.5,0,0,1,-1,1,-1,0,0,0,0,0},
    {0,0,0,2,0,0,2,2,0, 0,2,0,1,0,0,2,1,0,1,
     2,0,0,1,0,1,1,0});
  gmsh::view::addListData(t2, "SQ", 1, quad);

  gmsh::view::option::setNumber(t2, "AdaptVisualizationGrid", 1);
  gmsh::view::option::setNumber(t2, "TargetError", 1e-2);
  gmsh::view::option::setNumber(t2, "MaxRecursionLevel", 5);

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

示例二:Model-based 视图

// ch24_model_based.cpp
#include <set>
#include <gmsh.h>

int main(int argc, char **argv)
{
  gmsh::initialize(argc, argv);

  // ---- 1. 构建手动网格(离散面 + 4节点 + 2个三角形) ----
  gmsh::model::add("simple model");
  int surf = gmsh::model::addDiscreteEntity(2);
  gmsh::model::mesh::addNodes(2, surf, {1,2,3,4},
    {0.,0.,0., 1.,0.,0., 1.,1.,0., 0.,1.,0.});
  gmsh::model::mesh::addElementsByType(surf, 2, {1,2}, {1,2,3, 1,3,4});

  // ---- 2. NodeData(连续场) ----
  int t1 = gmsh::view::add("Continuous NodeData");
  for(int s = 0; s < 10; s++)
    gmsh::view::addHomogeneousModelData(
      t1, s, "simple model", "NodeData",
      {1,2,3,4}, {10., 10., 12.+s, 13.+s});

  // ---- 3. ElementNodeData(间断场) ----
  int t2 = gmsh::view::add("Discontinuous ElementNodeData");
  for(int s = 0; s < 10; s++)
    gmsh::view::addHomogeneousModelData(
      t2, s, "simple model", "ElementNodeData",
      {1,2}, {10., 10., 12.+s, 14., 15., 13.+s});

  // ---- 4. 第二个模型(OCC立方体)跨模型追加步 ----
  gmsh::model::add("another model");
  gmsh::model::occ::addBox(0,0,0, 1,1,1);
  gmsh::model::occ::synchronize();
  gmsh::model::mesh::generate(3);

  std::vector<std::size_t> nodes;
  std::vector<double> coords, params;
  gmsh::model::mesh::getNodes(nodes, coords, params);
  for(int s = 11; s < 20; s++) {
    std::vector<double> v;
    for(std::size_t i = 0; i < coords.size(); i += 3)
      v.push_back(s * coords[i]);
    gmsh::view::addHomogeneousModelData(t1, s, "another model",
                                        "NodeData", nodes, v);
  }

  // ---- 5. 保存 ----
  gmsh::view::write(t1, "ch24_t1.msh");
  gmsh::view::write(t2, "ch24_t2.msh");

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

24.6 关键 API 速查表

视图创建与管理

API 说明
int gmsh::view::add(name) 创建新视图,返回 tag
void gmsh::view::remove(tag) 删除指定视图
void gmsh::view::write(tag, filename) 保存视图(list-based为.pos,model-based为.msh)
void gmsh::view::probe(tag, x, y, z, val, dist) 在(x,y,z)处采样场值

List-based 数据导入

API 说明
void gmsh::view::addListData(tag, type, numElems, data) 导入展平列表数据
void gmsh::view::addListDataString(tag, coords, strings, options) 添加2D/3D文字标注
void gmsh::view::setInterpolationMatrices(tag, type, d, c, e) 设置高阶场插值矩阵
void gmsh::view::getListData(tag, type, numElems, data) 读取视图中的列表数据

Model-based 数据导入

API 说明
void gmsh::view::addHomogeneousModelData(tag, step, model, type, tags, data) 同构数据导入(简化版)
void gmsh::view::addModelData(tag, step, model, type, tags, data, comp, nEnts) 通用数据导入(支持非均匀数据)
void gmsh::view::getModelData(tag, step, model, type, tags, data) 取回模型绑定数据

视图选项(view::option 命名空间)

API 说明
void gmsh::view::option::setNumber(tag, option, value) 设置数值选项
void gmsh::view::option::getNumber(tag, option, value) 获取数值选项
void gmsh::view::option::setString(tag, option, value) 设置字符串选项

常用视图选项名称

选项名 类型 含义
"TimeStep" Number 当前显示时间步
"NbTimeStep" Number 总时间步数(只读)
"IntervalsType" Number 1=等值线, 2=填充, 3=连续色谱
"NbIso" Number 等值线/填充数量
"RangeType" Number 1=按步, 2=跨步, 3=自定义
"CustomMin" / "CustomMax" Number 自定义色条范围
"ShowScale" Number 0=隐藏色条, 1=显示
"ArrowSizeMax" / "ArrowSizeMin" Number 矢量箭头尺寸范围
"AdaptVisualizationGrid" Number 0=关闭, 1=开启自适应可视化
"TargetError" Number 自适应细分误差阈值
"MaxRecursionLevel" Number 自适应细分最大层级

24.7 小结

  • List-based 视图:数据与网格解耦,通过 view::addListData() 直接提供坐标和场值。元素类型由两字符标识符决定(如 "ST"、"VL")。适合外部数据、传感器读数、解析解的可视化。
  • Model-based 视图:数据绑定到模型网格实体,三种数据类型覆盖有限元结果输出:"NodeData"(连续插值云图)、"ElementData"(分片常值)、"ElementNodeData"(单元间间断场)。跨模型步功能使网格重划分动画成为可能。
  • 高阶可视化:通过 setInterpolationMatrices() 定义基函数,配合 AdaptVisualizationGrid 等选项实现 AMR 驱动的自适应渲染。
  • 视图管理view::option 控制显示参数,view::probe() 支持任意点采样,view::write() 支持持久化输出(.pos / .msh)。