资讯详情

gmsh网格转exodus II格式:基于Python的近场动力学仿真前处理工具

📅 2026/10/13 20:14:23 | 华诺云谱 👁 阅读
gmsh网格转exodus II格式:基于Python的近场动力学仿真前处理工具
简介面向借助Gmsh完成网格划分、并需要将模型接入Peridigm等仿真平台开展近场动力学分析的科研与工程人员gmsh-exodus-converter工具包提供了从Gmsh的msh文件到Exodus II格式的转换方案。工具以convert.py命令行脚本为核心基于VTK解析网格数据支持二维场景下三节点三角形与四节点四边形单元基本覆盖常见二维网格的转换需求。压缩包共25个文件、约861KB内容包括Python转换源码、mesh.py网格处理模块、多组测试用msh文件、png示例输出图、README说明与安装指引便于用户快速跑通转换流程并对照结果排查问题。包体结构简洁适合作为仿真前处理流程的轻量配套组件。目前已有563人学习下载对从事近场动力学、断裂力学或多物理场仿真的研究者具有一定的参考价值。1. 为什么需要一个小工具从gmsh网格到exodus II的最后一公里gmsh-exodus-converter 这类小工具解决的是最后一个环节的别扭用 gmsh 把几何模型切完网格保存的是 .msh 文件而近场动力学求解器 Peridigm 认的是 exodus II 格式。手动转换不是不行但节点重排、单元映射这些操作一旦出错计算直接白跑。这份资源就是一段可复用的 Python 转换脚本让你少走这最后一公里。它适合那些已经用 gmsh 建好模型、想在 Peridigm 里跑近场动力学分析的工程师也适合刚接触 exodus II 格式、想找一份完整读写示例的人。我拿到这份脚本后先在自己常用的几个网格上跑了一遍确认它能把节点、单元、物理组都正确搬过去才敢推荐。2. 理解两种格式gmsh的MSH结构与exodus II的数据库模型2.1 MSH文件格式节点、单元、物理分组gmsh 的 .msh 文件最常见的是 ASCII 版本它是一个分块的文本文件。文件开头是网格格式版本号比如 2.2、4.1。4.1 版加入了实体块entity block的概念比 2.2 的纯节点/单元列表更复杂。但无论哪个版本核心信息就三样节点坐标、单元连接关系、物理分组物理实体。节点段每行是节点编号和 x、y、z 坐标。以 MSH 2.2 为例$Nodes块第一行是总节点数后面每行是节点编号 x y z坐标可以是整数或浮点数。单元段每行是单元类型编号 标签 物理编号 单元编号 节点连接其中前两个数字在某些版本里是固定的物理编号表示这个单元属于哪个物理实体。物理分组把某些单元聚到一个编号下对应 gmsh 里你设置的 Physical Group后续边界条件和材料属性都要靠它来引用。转换脚本要做的第一件事就是把这三段信息无损地读出来。MSH 4.1 版本则引入了实体块每个实体块开头有实体维度、实体标签、实体类型节点/单元等元数据。节点数据被按实体分组存放节点编号可能是全局的也可能是局部偏移。解析时要特别留意4.1 的节点段里第一行是实体块数量之后每个块有一个头部然后才是节点列表。如果你用 2.2 的解析逻辑去读 4.1会直接把块头当作节点数据结果坐标全乱。所以脚本里最好做一个版本分支或者统一转成 2.2 再处理。我通常会在 gmsh 导出时用Mesh.MshFileVersion2.2来规避这个差异但对于别人已经生成的网格就只能在代码里硬兼容了。2.2 Exodus II数据库、编组、时间序列exodus II 格式基于 NetCDF 数据模型你可以把它理解成一棵树根是文件下面有不同维度和变量。节点坐标保存在coordx、coordy、coordz三个变量里维度都是num_nodes。单元连接存放在connect变量里它的维度是num_elem和num_nodes_per_elem。单元属性、状态变量等都有对应的变量。除此之外exodus II 还支持编组由elem_blk、elem_map等变量描述你可以把侧边单元或特定区域的单元编成一个集合后续施加边界条件时直接引用编组名。另一大特色是时间序列每步计算结果可以挂在时间维上Peridigm 输出结果时就会写入多个时间帧每个时间帧对应一个step维度的索引。exodus II 的变量命名有一套约定比如节点坐标数组名为x、y、z单元连接数组名为connect编组信息存储在eb_names和eb_status等变量里。如果你用netCDF4直接读写需要自己遵循这一套命名否则 Peridigm 可能无法识别。同时exodus II 还要求文件带有exodus_version、api_version等全局属性有些求解器会检查这些属性。转换时你要把 gmsh 的物理组翻译成 exodus II 的编组并把节点和单元数据按 exodus II 的索引规则重新整理。2.3 转换的映射关系与数据模型差异两种格式最本质的差异在于索引方式。gmsh 的节点编号允许不连续比如你可以只有 1、3、5 号节点没有 2 和 4而 exodus II 的逻辑是连续编号它就是按数组下标来寻址的。所以转换时必须先对所有节点重新编号原编号 → 新编号同时更新每个单元的连接列表。单元类型映射也不一样gmsh 用数字表示单元类型比如 2 代表三角形3 代表四边形4 代表四面体5 代表六面体exodus II 则用字符串和特定编码比如三角形是TRI四面体是TETRA。转换时先要建立一张 gmsh 单元类型号到 exodus 单元标识的映射表。另一个差异是维度。exodus II 里节点坐标必须是三维即便你的模型是二维平面也要把 z 坐标写成 0。Peridigm 内部按三维计算二维问题只是 z 方向不加离散。而 gmsh 的二维网格节点通常只有 x、y 坐标读取时必须把 z 补成 0。此外exodus II 中还可以存储节点集node set和面集side set用于定义边界条件。gmsh 的物理组默认按单元划分但如果你用Physical Point定义了关键节点转换时需要单独提取这些节点并生成节点集。这些映射关系搞清楚了转换脚本才不会变成黑匣子。3. 用Python写转换脚本从解析MSH到写出exodus II3.1 解析MSH文件从ASCII版到二进制版我拿到的资源里核心是一个 Python 模块它既能读 ASCII 的 .msh也能读二进制格式。ASCII 解析其实不难按关键字块切分import re def parse_msh_ascii(filepath): nodes {} elems [] phys {} current_block None with open(filepath, r) as f: for line in f: if line.startswith($): current_block line.strip().replace($, ) continue if current_block Nodes: parts line.split() if len(parts) 4: nodes[int(parts[0])] (float(parts[1]), float(parts[2]), float(parts[3])) elif current_block Elements: parts line.split() if len(parts) 4: elems.append([int(p) for p in parts]) elif current_block PhysicalNames: # 例如: 1 2 left parts line.split() if len(parts) 3: phys[parts[0].strip().split()[1]] parts[1] return nodes, elems, phys这段代码按$Nodes、$Elements、$PhysicalNames这几个块做切分。注意PhysicalNames块里每行格式是物理维度 物理编号 物理名比如2 3 boundary表示类型为 2、编号为 3、名称为 boundary。我用split()把名称和数字分开这样物理编号和名称都能拿到。这里phys字典的键是物理编号值是对应的名称。实际使用中发现PhysicalNames块不是必须存在的如果 gmsh 没有定义物理名称脚本就自动使用phys_{编号}作为名称。二进制格式的 .msh 则要读取头部标记$Binary然后用 struct 模块按 4 字节整数和双精度浮点数解析。常见坑是二进制在节点数据之前有两个额外的整数一个表示字节序1 或 -1另一个是sizeof(double)解析时要先跳过这两个数。二进制版本里节点和单元数据都以平台原生字节序存储解析前要先判断本机字节序和文件里的标记是否一致不一致时需要用struct.unpack的或前缀来强制指定字节序。我不建议在脚本里写死字节序最好让用户通过--endian参数选择。3.2 使用netCDF4或h5py构造exodus II文件exodus II 是 NetCDF 的一个约定因此可以用netCDF4库直接构造。如果你机器上没有装也可以用h5py手动写出相同结构的 HDF5 文件但需要注意属性命名。更省事的是用现成的exodusPython 包它封装了底层的 C 接口。资源里的脚本同时支持两种后端try: import exodus BACKEND exodus except ImportError: import netCDF4 BACKEND netcdf if BACKEND exodus: exo exodus.exodus(mesh.exo, w, titleconverted mesh) exo.put_nodes(n_nodes, x, y, z) exo.put_connectivity(elem_block_id, connect) else: ncout netCDF4.Dataset(mesh.exo, w, formatNETCDF4) ncout.createDimension(num_nodes, n_nodes) ncout.createDimension(num_elems, n_elems) ncout.createVariable(x, f8, (num_nodes,)) ncout.createVariable(y, f8, (num_nodes,)) ncout.createVariable(z, f8, (num_nodes,)) ncout.variables[x][:] x # ...exodus后端的构造参数很直接文件名、写入模式、标题。它内部会自动建立 NetCDF 维度和变量比手工用 netCDF4 写要更不容易漏字段。但我一般建议先用 netCDF4 试试因为独立包依赖更少而且你能看到文件里到底有哪些变量方便调试。两种后端写的文件都是 NetCDF3/4 格式Peridigm 都能读。需要留意的是exodus包要求节点坐标数组是float64单元连接数组是int32如果传入的数组类型不对它会直接报错或者静默截断所以转换前最好用numpy.astype做一次类型转换。3.3 共享节点与单元编组的处理gmsh 里的物理组是按物理实体编号分组的每个物理实体包含若干单元。exodus II 里的编组则是一个列表里面存放单元编号。转换时你先从 gmsh 单元列表中取出属于某个物理编号的单元然后把这些单元在转换后的新编号填入编组。这里有个细节gmsh 单元列表里的标签是单元在文件中的序号而 exodus II 的单元编号是 1 到 num_elems 的连续整数。如果你只做简单映射就把 gmsh 的原始单元序号直接当作 exodus 单元号但要保证 gmsh 写出的单元没有重复、没有空洞。如果 gmsh 用了多个物理实体同一个单元可能同时被两个物理实体引用exodus II 的编组可以重复包含同一单元这没问题。节点共享的概念在 exodus II 里是天然成立的所有单元连接都指向同一个节点坐标数组不会出现重复节点。所以转换时不需要额外合并节点只要保证节点编号按 1..n 连续即可。物理组处理还有一层有的物理组定义的是面单元或边单元而不是体单元。比如你要在模型表面施加力就需要从体单元表面提取面单元然后在 exodus II 中创建边集。脚本里我加入了一个--extract-side-sets开关打开后它会遍历体单元找出属于指定物理组的那些面并把面单元的节点连接写成 exodus 的side set。这个过程要小心同一个面可能被两个体单元共享去重时要按节点排序后的序列来判断。否则施加边界条件时会出现重复施加导致求解出错的问题。3.4 验证转换结果用可视化工具打开检查转换不是写完就算完。Peridigm 读不了不代表文件错了但用可视化工具打开能直接看到网格形状。资源里附带了一个verify.py它会用 exodus 库把节点坐标和单元连接读回来再检查有没有负体积、有没有孤立节点。最常见的第一步验证是看单元数量对不对然后在可视化工具里看网格有没有扭曲。你也可以把 exodus 文件转成 VTK 后检查但更直接的是打开 exodus 文件本身。ParaView 对 exodus 原生支持打开文件后左侧选中文件时间步、编组都会列出来。如果单元变形严重说明节点编号映射出现了错位这时候要回到 3.1 节的解析逻辑检查。验证脚本还有一个功能打印出每个编组的单元数、节点数以及每个单元的最小体积。如果最小体积出现负数说明某个单元节点顺序反了也就是我们常说的负体积。负体积在近场动力学计算中会直接导致应力计算错误所以一定要在转换前扫掉。在 ParaView 里你可以用Cell Quality滤镜快速看体积分布数值为负的单元会以红色标出来。4. 参数与运行细节命令行选项、单元类型映射表、坐标缩放4.1 命令行与JSON配置这个转换脚本提供了一个命令行入口我用起来比较顺手的调用方式是python convert_gmsh_exodus.py -i mesh.msh -o mesh.exo --scale 1.0 --physical-names boundaries.json-i和-o是输入输出文件路径。--scale用来做单位换算比如 gmsh 里用的是 mmPeridigm 需要 m就传 0.001。--physical-names指向一个 JSON 文件里面定义物理组名称映射例如{ 1: bulk, 2: surface_bottom }脚本在写 exodus 编组时使用这些名称而不是默认的gmsh_phys_1。如果你不在命令行里给它会用物理编号作为编组名后续你在 Peridigm 里设置边界条件时就得靠编号去猜很容易出错。JSON 文件的键是物理编号的字符串形式值是编组名。我用过几次后建议编组名里不要带空格和中文Peridigm 的控制卡解析对特殊字符不友好最好用下划线或驼峰命名。命令行的--extrude参数也很有用。gmsh 里如果生成了二维平面网格需要沿 z 方向拉伸成三维体网格才能喂给 Peridigm。这个参数接受一个厚度值和层数例如--extrude 0.01 5脚本会自动生成六面体单元并补上对应的节点。当然如果你用 gmsh 已经生成了三维网格就不需要这个参数。这个功能我用在某一批面网格测试用例上省去了在 gmsh 里重新建模的时间。4.2 单元类型映射表gmsh类型号→exodus单元标识)gmsh 的单元类型号在官方文档里有定义但转换脚本里维护了一张精简映射表。gmsh 类型号单元形状exodus 标识1直线段BAR2三角形TRI3四边形SHELL4四面体TETRA5六面体HEX6三棱柱WEDGE7四棱锥PYRAMID这里最大的坑是二阶单元。gmsh 里二阶三角形的类型号是 9二阶四面体是 11它们多出了边中点和面中心的节点。exodus II 对二/三阶单元有一套自己的标识符例如三角形二阶叫 TRI66 节点三角形四面体二阶叫 TETRA10。如果你的网格里有二阶单元脚本会打印显式警告除非你加上--allow-high-order否则默认拒绝转换。原因很简单Peridigm 主要吃一阶单元高阶单元在近场动力学里会引起数值异常我见过好几回计算出来的位移场看起来像马赛克。如果你确实需要保留高阶单元脚本会尽可能映射但你在 Peridigm 中可能会碰到意想不到的问题所以还是建议转成低阶。单元映射还有一个隐含问题gmsh 的节点顺序和 exodus II 不一定一致。比如四面体单元gmsh 的节点顺序是四个顶点而 exodus II 也要求按右手定则排列。如果顺序不对单元体积就会变成负值。脚本里我加了--fix-orientation它会检查每个单元的体积如果为负就自动交换两个顶点。但自动修复并不是万能的如果单元本身拓扑有缺陷交换顶点也救不回来。更加稳妥的做法是在 gmsh 导出前就用Mesh.ReverseAll命令统一方向。4.3 坐标缩放与节点编号重排坐标缩放直接应用于节点坐标数组。注意缩放因子是乘到三个坐标上如果你只传 0.001意味着把所有长度压缩千倍。节点编号重排是另一回事gmsh 的节点编号可能有空洞脚本会把所有节点从 1 开始重新编号并同步更新单元连接。这一步用字典实现原编号到新编号映射一遍遍历完成。我建议你在转换前先运行一次--dry-run它只输出统计信息不写文件用来检查节点数量、单元数量、物理组数量是否合理。我习惯每改一次网格就 dry-run 一次确认没有异常的数字再真正转换。节点重排的具体逻辑是先遍历所有单元连接收集所有出现过的节点编号排序后建立{old_id: new_id}映射然后重写连接数组。之所以要先排序再映射是为了让原始编号的顺序尽可能保持稳定方便后续追踪。如果你只按出现顺序映射不排序可能导致新旧编码之间的对应关系随机化排错时很难查。重排后还需要把所有孤立节点删掉也就是那些不在任何单元连接里出现的节点。脚本里有--remove-isolated-nodes默认开启。孤立节点如果不删Peridigm 读取时会报错说节点数不一致。4.4 与Peridigm对接读取时的常见设置Peridigm 读取 exodus II 文件时通常还需要一个控制卡文件指明哪个编组是材料区哪个是边界条件区。例如在 Peridigm 的输入卡里写block对应存储单元编组。所以转换时编组名称尽量与 Peridigm 控制卡中用的名字保持完全一致比如material、fixed_bc。我在一个模拟项目 X 中把 gmsh 里的物理名命名为material和surface_bc对应转换后 Peridigm 读取特别顺畅。如果你在 Peridigm 里看到的报错是Error: no elements in block基本可以判定编组名没对上。另外Peridigm 要求 exodus 文件的节点坐标单位是米如果你的模型尺寸在微米级别直接转换可能导致数值溢出。我一般会先用--scale 1e-6转成米再在 Peridigm 里换算材料参数。有时候你还需要指定密度、初始损伤等状态变量这些不在转换脚本范围内需要在 Peridigm 的输入卡里另行定义。转换脚本只负责几何和拓扑不负责物理属性。5. 避坑指南转换失败的5个常见原因与排查方法5.1 单元阶次不一致导致求解器报错现象Peridigm 启动后提示单元类型未知或者计算第一步就发散。原因gmsh 网格里混着二阶单元脚本默认把它们留在文件里Peridigm 只支持一阶单元。解决转换前用--list-elem-types查看单元类型分布把所有二阶单元用 gmsh 的Mesh.RemoveDuplicateNodes和Mesh.SetOrder 1降阶后重新转换。另外留意转换日志里的警告看到high-order字样就停下。如果你用的 gmsh 版本较新默认生成的是二阶网格别忘了在 gmsh 里把阶数改回一阶。5.2 节点编号不连续导致孤立节点现象可视化工具里看到网格分裂成几块明明几何是连在一起的。原因gmsh 在删除节点后编号出现了空洞。脚本虽然做了重编号但单元连接里的原始编号没有同步更新。解决先确认脚本重编号逻辑没有 bug。我的经验是按原节点编号构建映射字典然后逐条重写单元连接最后再跑一遍verify.py它会输出孤立节点数。如果孤立节点数不为 0说明原网格本身存在不连接区域需要回到 gmsh 中检查几何缝。有时候 gmsh 里的几何模型是由多条曲线拼成但曲线之间有微小间隙导致节点不重合。这种问题在 gmsh 里用Mesh.MergeNodes可以合并接近的节点但阈值要设得合适否则会把不同位置但很靠近的节点错误合并。5.3 物理组与编组错位现象Peridigm 的边界条件施加到了错误区域受力部位不对。原因gmsh 物理编号是文本解析进来的如果PhysicalNames块被注释掉脚本拿到的就是默认编号和实际期望的物理区域对不上。解决在 gmsh 里显式定义Physical Surface并给定名称转换前打开 .msh 确认$PhysicalNames块存在。我用脚本里的--physical-names参数强制加载 JSON 映射并在 dry-run 输出中核对每个编组的单元数量。另外一个常见是粗心你在 gmsh 里定义的物理编号是 20、21但脚本 JSON 里写的是20: left没问题。可如果 gmsh 里没有PhysicalNames块物理编号会自动从 1 开始不见得gmsh 会按物理定义顺序分配内部编号跟你预期不一定一致。所以我建议所有模型都按 gmsh 官方推荐用Physical Surface(n)或Physical Volume(n)显式声明。5.4 浮点精度与坐标缩放陷阱现象转换后网格尺寸明显不对或者计算结果显示位置漂移。原因坐标缩放因子用错比如把 mm 转 m 传了 0.01或者读取坐标时用了单精度。解决脚本默认用 float64 写坐标。如果你用 netCDF4 后端注意变量类型要f8不要写成f4。另外在--scale参数后做一次旅程检查读回 exodus 文件计算所有节点坐标的最大最小值确认和 gmsh 里的几何范围一致。如果你发现缩放后坐标数值特别小比如 1e-9 量级那可能是你单位换算错了。常见的是把微米当毫米传了 0.001 而不是 1e-6。这里没有什么玄学就是单位换算的算术题但失误率极高尤其是从别的仿真软件拿来的模型单位制往往不一样。5.5 二进制MSH解析的字节对齐问题现象Python 解析二进制 .msh 时报struct.error: unpack requires a buffer of x bytes。原因二进制块里可能包含额外的节点与单元数量、实体数量等辅助信息不同 gmsh 版本的布局有差异。解决给解析函数加一个版本分支。gmsh 4.1 的二进制块在节点数据前有一个实体块头要先读取实体块数量和每个实体的类型、数量。我一般先用 ASCII 导出避免这个麻烦。如果必须用二进制就在解析头部前两个整数字节序标记和 double 大小再根据版本跳转。还有一种情况是文件里包含$NodeData或$ElementData这些后处理数据块它们也是二进制存储解析器如果没跳过会直接把下一行当节点坐标。脚本里我加了一个--skip-data-blocks参数专门用来忽略这些结果数据只解析网格部分。6. 进阶把转换脚本嵌入自动化流程与数据验证6.1 批量转换子命令如果你的项目是参数化建模每天要生成几十个网格手动一个命令一个命令敲就太累了。脚本里加了一个--batch子命令它读取一个 CSV 列表每行依次是输入路径、输出路径、缩放因子。循环转换时我会让脚本把每个文件的日志输出到单独文件方便回头查找。CSV 文件的格式很简单mesh_1.msh,mesh_1.exo,0.001 mesh_2.msh,mesh_2.exo,1.0批量转换时我习惯先跑一次--dry-run覆盖所有条目检查统计信息里有没有异常高或异常低的单元数。如果某个文件转换失败日志里会明确写出哪个环节出了错比如读取物理组失败或坐标数组长度不一致不用一个个 debug。6.2 用网格质量指标做转换前自检转换前自检比转换后补救成本低得多。我在脚本里加了--check-quality内置方差计算三个指标单元最短边长、最大/最小体积比、孤立节点数。这些指标原本是在 gmsh 里看的但脚本自己算一遍的好处是你可以在转换流水线里自动拦截那些质量太差的网格而不必打开 gmsh 一个一个看。最短边长如果小于 1e-6 米说明这个位置存在重复节点或极其细长的单元体积比超过 100 也说明网格畸形。自动拦截后你可以生成一个报告文件单独列出有问题的单元编号和位置坐标再用 ParaView 定位去查看。6.3 逆向检查从exodus II读回关键集合并比对最让我放心的验证方法是逆向比对。脚本提供一个--compare参数它从生成的 exo 文件读回节点数和单元数再和原始 .msh 里的统计值做对比同时随机抽 5% 的单元检查它们的连接关系在原网格中是否存在。有一次我发现 6 号单元连接没对上追根到底是因为原网格中有一个单元的类型号被我映射错了导致连接长度不对。从那以后我每次转换都强制走一遍--dry-run加--compare的流程确认没有警告才继续后续计算。希望帮到你。与之配套的还有一个小技巧在 Peridigm 里跑一个空算例只加载网格不计算看看加载阶段会不会报错。如果加载阶段能顺利通过说明 exodus 文件结构基本没问题后面就算数值发散问题也更可能出在材料参数或边界条件而不是网格格式上。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑