OpenFOAM二次开发教程(18):网格与算例流水线自动化——blockMesh/snappyHexMesh/checkMesh
OpenFOAM二次开发教程18网格与算例流水线自动化——blockMesh/snappyHexMesh/checkMesh版本与事实声明blockMesh与checkMesh是官方 Quickstart 中的标准前处理步骤Quickstart 示例给出blockMesh并提示可用runApplication封装运行与日志。snappyHexMesh是官方网格工具其分阶段配置如castellatedMesh/snap/addLayers与具体键名以官方文档与你本机安装版本的官方教程算例为准。topoSet是官方工具类官方 Doxygen 记载topoSet为抽象基类用于集合操作常用于生成 cellZone/faceZone 供porosityForce第 13 篇等使用。renumberMesh是官方网格工具用于重排编号第 15 篇。网格质量判据的具体阈值随问题类型变化本文给出的阈值仅作工程惯例示例最终阈值应由你的问题与文献决定。一句话结论网格流水线的自动化 “参数化blockMeshDict 分阶段snappyHexMeshcastellatedMesh→snap→addLayerscheckMesh质量门禁 topoSet生成区域 renumberMesh重排”其中**质量门禁是整条流水线的灵魂——没有量化的通过/失败判据自动化只会更快地产生垃圾网格**。〇、本篇要解决的认知问题Q1为什么网格前处理最值得自动化它的手工成本到底在哪里Q2blockMeshDict怎么参数化生成哪些量应该由脚本算出来而不是手写Q3snappyHexMesh的三个阶段各做什么为什么必须分阶段调试Q4checkMesh的哪些输出可以当门禁判据阈值怎么定Q5一条完整流水线应该包含哪些步骤怎么保证同参数必然同网格一、机制解析1.1 网格前处理为什么是自动化的最高价值区对比三个环节的成本环节手工特征自动化收益前处理网格参数多、试错多、每次几何改动都要重做极高一次脚本化永久复用求解提交即可参数少中主要靠并行第 14 篇后处理已有函数对象第 12、17 篇高但已有成熟路径更关键的是一条科学纪律做网格无关性研究时你需要同一套几何、同一套加密策略下的多套网格只在分辨率上不同。如果网格靠手工点你无法保证只改了分辨率——而只改一个变量是所有对比实验的前提。手工网格会让你的网格无关性研究失去说服力。1.2 blockMeshDict 参数化把能算出来的交给脚本blockMeshDict里最容易出错的是坐标与单元数的相互依赖顶点坐标必须与块定义、单元数、加密比例一致。手工维护这套一致性是灾难。参数化的核心思路定义少数物理量几何尺寸、目标分辨率由脚本算出字典量顶点坐标列表、单元数物理输入人决定 字典输出脚本算 Lx, Ly, Lz ────► vertices (8 个坐标) Nx, Ny, Nz分辨率 ────► blocks 中的 (Nx Ny Nz) grading加密比例 ────► simpleGrading (…) patch 名称与位置 ────► boundary 的 faces 索引必须由脚本算、不要手写的三类量顶点坐标只要几何尺寸变了8 个坐标全变手改必错。单元数与加密比例网格无关性研究要改的就是它们——必须是输入而不是埋在字典里的常量。边界面索引hex (0 1 2 3 4 5 6 7)的顶点编号与faces ( (0 3 2 1) ... )的拓扑关系极易手写错且错了之后checkMesh才会告诉你。用脚本按命名约定生成把风险从人转移到代码。最佳实践把几何与分辨率定义成一个小的参数表YAML/JSON/Python dict由生成脚本产出blockMeshDict。这样改分辨率就是改一个数字换几何就是换一组尺寸——第 19 篇的 DOE 扫描直接复用这套参数表。1.3 snappyHexMesh 分阶段为什么必须一步一步来snappyHexMesh的配置通常分为若干阶段如castellatedMesh、snap、addLayers。每个阶段可以用开关控制是否执行例如只想做前两阶段时关闭addLayers。三个阶段的职责阶段示例名做什么出问题时的表现castellatedMesh从背景网格出发按几何表面切割出阶梯状笛卡尔网格表面未捕捉 → 几何被磨平或丢失细节snap把阶梯状网格顶点吸附到几何表面吸附失败 → 表面凹凸不平/出现负体积addLayers在壁面附近加边界层拉伸单元加层失败 → 边界层缺失或质量骤降为什么必须分阶段调试本篇最实用的建议先只开castellatedMesh确认几何被正确切割看表面位置、看是否丢特征。再加snap确认表面平滑且无负体积。最后加addLayers边界层是最容易让质量崩掉的一步单独调它。一次性打开全部阶段出问题时你无法定位是哪一步坏了——这是snappyHexMesh使用者最常见的效率损失。1.4 checkMesh 质量门禁把看着还行变成数字通过checkMesh是网格质量的官方检查工具通过时输出Mesh OK。它是流水线的门禁核心因为它把网格行不行从主观判断变成客观输出。可作门禁的典型质量指标阈值按问题类型定以下为工程惯例示例非标准规定指标含义糟糕时的后果惯例参考阈值示例非正交性non-orthogonality面法向与单元中心连线的夹角离散误差大、不收敛平均值 70、最大值 80常用工程上限偏斜度skewness面中心相对插值点的偏移精度下降、稳定性差最大值 4严格场景 2~3长宽比aspect ratio单元形状拉伸程度收敛慢、各向异性误差视问题而定边界层网格可较大负体积/负面积单元网格退化直接不可用必须为 0最小/最大体积比单元尺度跨度线性系统病态视问题而定门禁设计三原则区分硬失败与警告负体积类必须零容忍硬失败非正交性可设警告阈值与失败阈值两级。阈值必须写进脚本、可审计不要凭记忆判断这次网格行不行。门禁失败要能定位checkMesh会指出问题单元的位置/数量脚本应把这部分输出保留下来不要只保留通过/失败。1.5 完整流水线的步骤与同参数必然同网格一条规范的网格流水线① 参数表几何 分辨率 加密策略 │ ▼ ② 生成 blockMeshDict参数化脚本 │ ▼ ③ blockMesh背景网格 │ ▼ ④ 分阶段 snappyHexMeshcastellated → snap → layers │ ▼ ⑤ topoSet生成需要的 cellZone / faceZone │ ▼ ⑥ renumberMesh重排编号第 15 篇注意编号变化会影响按编号索引的脚本 │ ▼ ⑦ checkMesh门禁→ 通过则归档失败则中止并输出诊断同参数必然同网格的保证唯一方法是每一步都由脚本执行、所有中间产物与日志归档。只要有任何一步是手工点选/手工编辑这个保证就失效。这也是可复现研究的最低要求。二、完整代码与逐行剖析代码 2-1参数化的blockMeshDict生成器Python# -*- coding: utf-8 -*- make_blockmesh.py —— 由参数表生成 blockMeshDict单块结构化网格 用法python make_blockmesh.py 输出目录 [Lx Ly Lz Nx Ny Nz] 设计把“物理量”作为输入把“字典量”作为输出杜绝手工维护一致性。 importsysfrompathlibimportPathimportjson# 参数表几何尺寸m 目标分辨率单元数 加密策略PARAMS{Lx:0.1,Ly:0.1,Lz:0.01,# 几何尺寸示例值Nx:20,Ny:20,Nz:1,# 分辨率示例值grading:(1.0,1.0,1.0),# 各方向加密比例1均匀scale:1.0,# 全局缩放}TEMPLATE/*--------------------------------*- C -*----------------------------------*\\ | | | | \\\\ / F ield | OpenFOAM: The Open Source CFD Toolbox | | \\\\ / O peration | | | \\\\ / A nd | | | \\\\/ M anipulation | | \\*---------------------------------------------------------------------------*/ FoamFile {{ version 2.0; format ascii; class dictionary; object blockMeshDict; }} scale {scale}; vertices ( (0 0 0) ({Lx} 0 0) ({Lx} {Ly} 0) (0 {Ly} 0) (0 0 {Lz}) ({Lx} 0 {Lz}) ({Lx} {Ly} {Lz}) (0 {Ly} {Lz}) ); blocks ( // 顶点 0..7 顺序固定单元数由参数给出grading 由参数给出 hex (0 1 2 3 4 5 6 7) ({Nx} {Ny} {Nz}) simpleGrading ({gx} {gy} {gz}) ); edges ( ); boundary ( movingWall {{ type wall; faces ( (3 7 6 2) ); // 顶面示例顶盖驱动 }} fixedWalls {{ type wall; faces ( (0 4 7 3) (2 6 5 1) (1 5 4 0) ); // 其余三个侧面 }} frontAndBack {{ type empty; // 二维算例前后设 empty faces ( (0 3 2 1) (4 5 6 7) ); }} ); mergePatchPairs ( ); // ************************************************************************* // defbuild(p:dict)-str:gx,gy,gzp[grading]returnTEMPLATE.format(scalep[scale],Lxp[Lx],Lyp[Ly],Lzp[Lz],Nxp[Nx],Nyp[Ny],Nzp[Nz],gxgx,gygy,gzgz,)defmain()-None:outdirPath(sys.argv[1]iflen(sys.argv)1else.)paramsdict(PARAMS)iflen(sys.argv)8:params.update(dict(zip([Lx,Ly,Lz,Nx,Ny,Nz],[float(v)forvinsys.argv[2:8]])))sysdiroutdir/systemsysdir.mkdir(parentsTrue,exist_okTrue)(sysdir/blockMeshDict).write_text(build(params),encodingutf-8)# 同时落一份参数快照保证“同参数必然同网格”可被核查(outdir/mesh_params.json).write_text(json.dumps(params,ensure_asciiFalse,indent2),encodingutf-8)print(f[OK] 已生成{sysdir/blockMeshDict}与 mesh_params.json)if__name____main__:main()逐行剖析PARAMS把几何尺寸、分辨率、加密比例集中成一张表这是参数化的核心——改分辨率就是改一个数字不必碰字典文本。TEMPLATE.format(...)生成字典顶点坐标由Lx/Ly/Lz算出杜绝手改 8 个坐标时的不一致。hex (0 1 2 3 4 5 6 7)与faces的顶点编号保持固定的拓扑约定这是单块的标准写法拓扑关系不随尺寸变化因此可以安全地写死在模板里而坐标与单元数随参数变所以必须由脚本算。同时输出mesh_params.json这是同参数必然同网格的可核查凭据——你随时能用这个快照重新生成完全相同的网格可复现纪律。frontAndBack用type empty二维算例的必要设置第 04 篇的纪律在这里被模板化永远不会忘。脚本支持命令行覆盖参数为第 19 篇的自动化扫描预留接口。代码 2-2完整网格流水线脚本含质量门禁#!/bin/sh# mesh_pipeline.sh —— 几何 → 网格 → 区域 → 重排 → 质量门禁全自动# 用法sh mesh_pipeline.sh 算例路径set-eucase${1:?用法:sh mesh_pipeline.sh 算例路径}cd$caseLOGlog.mesh_pipelinesay(){echo[$(date%H:%M:%S)]$*|tee-a$LOG;}say ① 生成背景网格blockMeshblockMeshlog.blockMesh21||{sayFAIL blockMesh;exit1;}say ② 分阶段 snappyHexMesh # 关键分阶段执行。请在你的 system/snappyHexMeshDict 中通过阶段开关控制# 先只开 castellatedMesh再加 snap最后加 addLayers见正文 §一.3。# 是否生成本步取决于算例是否使用 snappyHexMesh纯 blockMesh 算例可跳过。if[-fsystem/snappyHexMeshDict];thensnappyHexMesh-overwritelog.snappyHexMesh21\||{sayFAIL snappyHexMesh提示请分阶段调试先只开 castellatedMesh;exit1;}elsesay (无 snappyHexMeshDict跳过)fisay ③ 生成需要的区域topoSet可选if[-fsystem/topoSetDict];thentopoSetlog.topoSet21||sayWARN topoSet 异常见 log.topoSetelsesay (无 topoSetDict跳过若第 13 篇的 porosityForce 需要 cellZone 则必须生成)fisay ④ 重排编号renumberMesh改善矩阵带宽renumberMesh-overwritelog.renumberMesh21||sayWARN renumberMesh 异常say ⑤ 质量门禁checkMeshcheckMeshlog.checkMesh21||true# checkMesh 的退出码语义以官方为准这里以输出内容判定ifgrep-qMesh OKlog.checkMesh;thensayPASS checkMesh: Mesh OKelsesayFAIL checkMesh 未报 Mesh OK诊断摘要grep-iEFailed|negative|non-orthogonality|skewness|aspectlog.checkMesh|head-n20|seds/^/ /|tee-a$LOGexit1fisay ⑥ 归档参数快照可复现凭据ls-lmesh_params.json2/dev/null|seds/^/ /|tee-a$LOG||say (无参数快照建议补充)say全流程通过。逐行剖析say()用tee -a同时输出到屏幕与日志流水线必须留痕否则失败时无法复盘。② 用-overwrite让snappyHexMesh直接覆盖网格并明确提示请分阶段调试——把 §一.3 的方法论写进脚本的失败提示里。③ 判断topoSetDict是否存在再执行“按需执行”。并用注释把第 13 篇porosityForce需要 cellZone的依赖关系点出来——跨篇依赖显式化。⑤以grep -q Mesh OK作为门禁判据checkMesh的通过标志并在失败时抽取非正交、偏斜、负体积等关键诊断行“失败要能定位”§一.4 原则 3。注释明确checkMesh 的退出码语义以官方为准故这里以输出内容判定不臆造退出码行为。⑥ 检查参数快照是否存在把可复现凭据作为流水线的一部分而不是可选项。代码 2-3网格无关性研究脚本同几何不同分辨率#!/bin/sh# mesh_independence.sh —— 同几何、只改分辨率生成 N 套网格并核对质量# 用法sh mesh_independence.sh 算例模板目录 分辨率列表如 10 20 40set-eutpl${1:?用法:sh mesh_independence.sh 模板算例 \10 20 40\}res_list${2:?缺少分辨率列表}fornin$res_list;dodmesh_N${n}rm-rf$d;cp-r$tpl$d# 用参数化生成器重写 blockMeshDict只改分辨率几何不变python make_blockmesh.py$d0.10.10.01$n$n1/dev/null(cd$dshmesh_pipeline.sh./dev/null21)\echo[OK]$d网格通过质量门禁\||echo[FAIL]$d网格未通过门禁见$d/log.checkMeshdoneechoecho下一步对这些算例使用完全相同的 fvSchemes/fvSolution 运行echo 再用第 17 篇的聚合脚本绘制“监测量 vs 分辨率”曲线echo 最后两套网格结果差异进入容差即认为达到网格无关。逐行剖析每个分辨率复制一份模板算例再改分辨率只改一个变量的纪律§一.1。复用代码 2-1 的生成器与代码 2-2 的流水线体现流水线可组合而不是每个脚本重写一遍。输出下一步指引网格生成只是网格无关性研究的前半段后半段是同设置运行 对比曲线。把链路写在脚本里避免使用者忘了后半段——这也是分阶段的工程意识。三、常见报错与排查报错 3-1blockMesh报顶点或面定义错误如 face 使用了未定义的顶点。现象建网格失败。根因vertices数量不足或boundary的faces引用了不存在的顶点编号常见于手改字典时。解法用参数化生成器代码 2-1产出字典不要手改顶点编号核对hex (0 1 2 3 4 5 6 7)的顶点顺序约定与faces的编号是否在同一套约定内。报错 3-2snappyHexMesh后表面凹凸不平或出现负体积。现象几何表面质量差、checkMesh报负体积。根因snap阶段参数不当吸附过猛/迭代不足或几何表面本身有缺陷未封闭/自交/尺度与网格不匹配。解法分阶段调试——先只做castellatedMesh确认切割正确再加snap调参最后才开addLayers§一.3同时检查几何文件是否有效可用外部工具检查 STL 的面片与封闭性。报错 3-3addLayers一开质量就崩。现象加边界层后非正交性/偏斜度暴涨或出现负体积。根因边界层厚度超过局部单元尺寸首层厚度×层数 背景网格尺寸或几何曲率大处层不适用。解法检查首层厚度 × 层数是否与背景网格加密匹配对曲率大/狭窄处考虑局部禁用加层逐段先小范围验证再全域应用。报错 3-4checkMesh报Mesh OK但求解效果很差。现象门禁通过但结果不对。根因Mesh OK只代表没有致命缺陷不代表质量适合你的问题例如非正交性很高但仍 “OK”。解法把非正交性、偏斜度等具体数值纳入门禁不只依赖 “Mesh OK”根据问题类型设定更严的阈值必要时加密或改用更适合的网格类型。报错 3-5改了分辨率后结果变化很大无法判断是否网格无关。现象两套网格结果差异明显。根因可能不是网格问题而是没有只改一个变量——例如同时改了fvSchemes、时间步或湍流设置。解法用代码 2-3 的流程严格保证只改分辨率运行阶段使用完全相同的fvSchemes/fvSolution用参数快照mesh_params.json核对两套网格确实只有分辨率不同。四、动手练习练习 1参数化生成用代码 2-1 生成一个blockMeshDict并在其上运行blockMeshcheckMesh。判定checkMesh报Mesh OKmesh_params.json与字典内容一致改一个参数重新生成确认只有对应部分变化。练习 2分阶段 snappy对一个含几何的算例先只开castellatedMesh运行再逐步加snap、addLayers。判定能记录每一步的表面质量/质量指标变化能指出哪一步是质量恶化的主因。练习 3质量门禁用代码 2-2 的流水线跑一个算例其中故意把分辨率设得极低或加密比例设得极端触发门禁失败。判定脚本中止并输出非正交/偏斜等诊断行能据此定位问题。练习 4网格无关性用代码 2-3 生成三套分辨率网格。判定三套网格都通过质量门禁mesh_params.json显示只有分辨率不同能说出后半段同设置运行 对比曲线的完整步骤。练习 5思考题无标准答案你的团队要在 CI 里对每个提交做网格回归。设计该门禁的判据清单。验证要点(a) 是否区分硬失败负体积与警告阈值非正交、偏斜(b) 是否把阈值写进脚本并可审计© 是否保留诊断输出(d) 是否归档参数快照以保证可复现(e) 是否考虑renumberMesh会改变编号从而影响依赖编号的后续脚本第 15 篇。五、小结与下一篇预告本篇把网格前处理从手艺变成了工程参数化几何与分辨率作为输入顶点坐标与单元数由脚本算、分阶段snappyHexMeshcastellatedMesh→snap→addLayers一次只调一个阶段、质量门禁以checkMesh的Mesh OK为底线把非正交性/偏斜度等具体数值纳入判据并区分硬失败与警告、按需生成区域topoSet为第 13 篇的porosityForce等提供 cellZone、重排编号renumberMesh同时记住它改变编号、可复现凭据mesh_params.json快照。第 19 篇《参数扫描与 DOE 自动化》将把本篇的网格流水线、第 16 篇的 Python 编排与第 17 篇的结果聚合接成一条完整的实验生产线定义变量-目标契约、设计全因子/拉丁超立方抽样、断点续跑与收敛状态机、结果长表与报表——那是把二次开发能力转化为科研/工程产出的一步。本篇认知问题回显FAQQ1为什么网格前处理最值得自动化A三条原因。第一是成本结构网格前处理参数多、试错多几何一改就要重做而求解只需提交、后处理已有成熟路径因此前处理的自动化收益最高。第二是科研纪律做网格无关性研究需要同一几何、同一加密策略下的多套网格若靠手工点选无法保证只改了分辨率而只改一个变量是所有对比实验的前提。第三是可复现性只有每一步都由脚本执行并归档中间产物与日志才能保证同参数必然同网格这是可复现研究的最低要求。Q2blockMeshDict 怎么参数化哪些量应由脚本算A把物理量作为输入几何尺寸 Lx/Ly/Lz、目标分辨率 Nx/Ny/Nz、加密比例 grading、缩放 scale由脚本算出字典量。必须由脚本算的三类量顶点坐标几何尺寸一变8 个坐标全变单元数与加密比例网格无关性研究要改的就是它们必须是输入而非埋在字典里的常量边界面索引hex 顶点顺序与 faces 编号的拓扑关系极易手写错错了要等 checkMesh 才暴露。单块网格的拓扑约定顶点编号与面编号的相对关系不随尺寸变化可以安全地写死在模板里而坐标与单元数必须由参数生成。同时应输出参数快照文件作为可复现凭据。Q3snappyHexMesh 的三个阶段各做什么为什么要分阶段调试A阶段通常为 castellatedMesh从背景网格按几何表面切割出阶梯状笛卡尔网格若表面未捕捉会导致几何被磨平或丢失细节、snap把阶梯状网格顶点吸附到几何表面吸附失败会导致表面凹凸不平或出现负体积、addLayers在壁面附近加边界层加层失败会导致边界层缺失或质量骤降。必须分阶段调试的原因是每个阶段都可能引入质量恶化一次性打开全部阶段时无法定位是哪一步坏了正确做法是先只开 castellatedMesh 确认切割正确再加 snap 确认表面平滑无负体积最后单独调 addLayers。每阶段可用开关控制是否执行具体键名以官方文档与本机官方教程算例为准。Q4checkMesh 的哪些输出可以当门禁判据阈值怎么定A通过标志是输出 Mesh OK这是底线而非充分条件。可作门禁的指标包括非正交性工程惯例上限常取平均值小于约 70、最大值小于约 80、偏斜度最大值常取小于 4严格场景小于 2~3、长宽比、负体积或负面积单元必须为 0属硬失败、最小最大体积比。阈值不应照搬需按问题类型与文献确定并且必须写进脚本、可审计。门禁设计三原则区分硬失败与警告两级阈值阈值写进脚本而非凭记忆判断失败时保留 checkMesh 的诊断输出问题单元数量与位置以便定位。注意 Mesh OK 只代表没有致命缺陷不代表质量适合你的问题。Q5一条完整网格流水线包含哪些步骤如何保证同参数必然同网格A步骤为参数表几何、分辨率、加密策略→ 参数化生成 blockMeshDict → blockMesh背景网格→ 分阶段 snappyHexMeshcastellated → snap → layers→ topoSet生成所需 cellZone/faceZone→ renumberMesh重排编号以改善矩阵带宽但注意它会改变编号从而影响按编号索引的脚本→ checkMesh 质量门禁通过则归档失败则中止并输出诊断→ 归档参数快照。保证同参数必然同网格的唯一方法是每一步都由脚本执行、所有中间产物与日志归档只要有任何一步是手工点选或手工编辑这个保证就失效。在 CI 中还应区分硬失败与警告阈值、保留诊断输出、并归档参数快照。