第4章:基于Python的网格生成实战

网格生成这件事,说实话,是CFD分析里最磨人的环节之一。我见过太多人花了两周调求解器参数,结果网格质量不行,算出来的东西根本没法看。我自己刚入行那会儿也吃过这个亏——辛辛苦苦算了一周,后处理一看,升力系数曲线跟锯齿似的,最后发现是网格在翼型尾缘那里扭曲得太厉害。

这一章咱们就聊聊怎么用Python搞定翼型网格。我会用Gmsh这个工具,它开源、轻量、Python接口也还算顺手。你想想看,如果能用脚本一键生成网格,那可比在GUI里点鼠标高效多了。

核心要点:网格质量直接决定CFD结果的可靠性。边界层网格尤其关键——它捕捉的是壁面附近的速度梯度,搞不好整个阻力计算就废了。

4.1 准备工作:安装与导入

先装好Gmsh的Python接口。我个人习惯用pip安装:

pip install gmsh

导入的时候注意,Gmsh的API设计得有点特别——它用面向过程的方式操作几何和网格。我刚开始用的时候也觉得别扭,但用顺手了其实挺直观。

import gmsh
import numpy as np

# 初始化
gmsh.initialize()
gmsh.model.add("airfoil_mesh")

小技巧:每次运行完记得调用 gmsh.finalize() 释放资源。我刚开始经常忘,结果内存泄漏搞得电脑卡死。

4.2 翼型几何定义

咱们用NACA0012对称翼型做例子。它的坐标可以用解析公式生成:

def naca0012_coords(n_points=200):
    """生成NACA0012翼型坐标"""
    beta = np.linspace(0, np.pi, n_points)
    x = 0.5 * (1 - np.cos(beta))  # 余弦分布,前缘后缘加密
    
    # NACA 4位数字翼型厚度分布
    t = 0.12  # 最大厚度12%
    yt = 5 * t * (0.2969*np.sqrt(x) - 0.1260*x - 0.3516*x**2 + 0.2843*x**3 - 0.1015*x**4)
    
    # 对称翼型,上下表面
    x_upper = x
    y_upper = yt
    x_lower = x
    y_lower = -yt
    
    return x_upper, y_upper, x_lower, y_lower

这里有个坑——为什么用余弦分布而不是均匀分布?因为翼型前缘曲率大,需要更多网格点来捕捉几何特征。均匀分布的话,前缘会变得很粗糙,算出来的压力分布会失真。我在做某型风机叶片优化时吃过这个亏,后来才改过来的。

4.3 在Gmsh中创建几何

有了坐标点,接下来把它们导入Gmsh。这里要注意顺序——必须按逆时针方向闭合曲线:

def create_airfoil_geometry(x_upper, y_upper, x_lower, y_lower):
    """在Gmsh中创建翼型几何"""
    # 添加上表面点
    upper_points = []
    for i in range(len(x_upper)):
        pid = gmsh.model.geo.addPoint(x_upper[i], y_upper[i], 0, 0.001)
        upper_points.append(pid)
    
    # 添加下表面点(注意顺序:从尾缘到前缘)
    lower_points = []
    for i in range(len(x_lower)-1, -1, -1):
        pid = gmsh.model.geo.addPoint(x_lower[i], y_lower[i], 0, 0.001)
        lower_points.append(pid)
    
    # 创建样条曲线
    upper_spline = gmsh.model.geo.addSpline(upper_points)
    lower_spline = gmsh.model.geo.addSpline(lower_points)
    
    # 创建闭合曲线环
    curve_loop = gmsh.model.geo.addCurveLoop([upper_spline, lower_spline])
    
    return curve_loop, upper_spline, lower_spline

注意:点坐标的容差设置很关键。我设的是0.001,如果翼型尺寸很小(比如弦长0.1m),这个值可能太大,会导致几何失真。建议根据弦长动态调整。

4.4 远场边界设定

远场边界一般取翼型弦长的10-20倍。太小了会干扰流场,太大了浪费计算资源。我一般取15倍弦长:

def create_farfield(chord_length=1.0, farfield_ratio=15):
    """创建远场边界"""
    radius = chord_length * farfield_ratio
    
    # 创建远场圆
    center = gmsh.model.geo.addPoint(0, 0, 0)
    p1 = gmsh.model.geo.addPoint(radius, 0, 0)
    p2 = gmsh.model.geo.addPoint(0, radius, 0)
    p3 = gmsh.model.geo.addPoint(-radius, 0, 0)
    p4 = gmsh.model.geo.addPoint(0, -radius, 0)
    
    # 创建圆弧
    arc1 = gmsh.model.geo.addCircleArc(p1, center, p2)
    arc2 = gmsh.model.geo.addCircleArc(p2, center, p3)
    arc3 = gmsh.model.geo.addCircleArc(p3, center, p4)
    arc4 = gmsh.model.geo.addCircleArc(p4, center, p1)
    
    farfield_loop = gmsh.model.geo.addCurveLoop([arc1, arc2, arc3, arc4])
    
    return farfield_loop

为什么用圆形而不是矩形?因为圆形远场边界对来流方向不敏感,不管攻角怎么变,边界条件施加起来都方便。矩形的话,你得根据来流方向调整边界位置,麻烦得很。

4.5 边界层网格设置

这才是重头戏。边界层网格的核心思路是:壁面附近用很薄的棱柱层,然后逐渐过渡到三角形网格。我一般用以下参数:

参数 推荐值 说明
第一层高度 1e-5 * 弦长 保证y+ ≈ 1
增长因子 1.15 - 1.2 太大会导致网格过渡突兀
边界层层数 15 - 25 根据雷诺数调整
总厚度 0.05 * 弦长 覆盖边界层厚度

在Gmsh中设置边界层网格,需要用field来控制网格尺寸:

def setup_boundary_layer():
    """设置边界层网格"""
    # 创建边界层场
    bl_field = gmsh.model.mesh.field.add("BoundaryLayer")
    gmsh.model.mesh.field.setNumber(bl_field, "Size", 1e-5)  # 第一层高度
    gmsh.model.mesh.field.setNumber(bl_field, "Ratio", 1.15)  # 增长因子
    gmsh.model.mesh.field.setNumber(bl_field, "Thickness", 0.05)  # 总厚度
    gmsh.model.mesh.field.setNumber(bl_field, "Quads", 1)  # 使用四边形
    
    # 指定边界层附着在翼型表面
    gmsh.model.mesh.field.setNumbers(bl_field, "CurvesList", [upper_spline, lower_spline])
    
    return bl_field

经验之谈:第一层高度怎么算?用这个公式:y_wall = y+ * mu / (rho * u_tau)。其中u_tau是摩擦速度,大概估算为来流速度的5%。如果雷诺数1e6,弦长1m,第一层高度大概在1e-5量级。

4.6 网格生成与质量控制

所有设置完成后,执行网格生成:

def generate_mesh():
    """生成网格"""
    # 设置全局网格尺寸
    gmsh.model.mesh.setSize(gmsh.model.getEntities(0), 0.01)
    
    # 生成二维网格
    gmsh.model.mesh.generate(2)
    
    # 优化网格质量
    gmsh.model.mesh.optimize("Netgen")
    
    # 输出网格
    gmsh.write("airfoil.msh")
    
    # 可视化
    gmsh.fltk.run()

网格生成后一定要检查质量。我常用的指标:

  • 最小角度:三角形单元应大于20°,四边形大于45°
  • 长宽比:边界层内可以到1000,但主流区最好小于10
  • 正交性:壁面附近网格线应尽量垂直于壁面

避坑指南:我曾经遇到过一个案例,网格生成时没报错,但算到一半就发散。后来发现是翼型尾缘处有几个单元扭曲得厉害,长宽比超过5000。从那以后我养成了习惯——每次生成网格后先跑一遍质量检查脚本。

4.7 完整流程整合

把上面的函数串起来,就是一个完整的网格生成脚本。我个人习惯把它封装成一个类,方便复用:

class AirfoilMeshGenerator:
    def __init__(self, chord=1.0, farfield_ratio=15):
        self.chord = chord
        self.farfield_ratio = farfield_ratio
        gmsh.initialize()
        
    def generate(self):
        # 1. 生成翼型坐标
        x_up, y_up, x_low, y_low = naca0012_coords()
        
        # 2. 创建几何
        curve_loop, up, low = create_airfoil_geometry(x_up, y_up, x_low, y_low)
        ff_loop = create_farfield(self.chord, self.farfield_ratio)
        
        # 3. 创建面
        surface = gmsh.model.geo.addPlaneSurface([ff_loop, curve_loop])
        
        # 4. 设置边界层
        bl_field = setup_boundary_layer()
        
        # 5. 生成网格
        gmsh.model.mesh.generate(2)
        
        return gmsh.model

你看,整个流程其实就五步:几何定义、远场设定、边界层配置、网格生成、质量检查。每一步都有讲究,但用脚本实现后,改参数、跑批量都非常方便。

翼型网格生成流程 步骤1:几何定义 NACA0012坐标生成 步骤2:远场设定 15倍弦长圆形边界 步骤3:边界层配置 第一层高度/增长因子 步骤4:网格生成 三角形/四边形混合 步骤5:质量检查 最小角度/长宽比 步骤6:输出与可视化 .msh文件 / FLTK窗口 质量不合格?返回调整参数 关键参数速查 • 第一层高度:1e-5 × 弦长 • 增长因子:1.15 ~ 1.20 • 边界层层数:15 ~ 25 • 远场半径:15 × 弦长 • 最小角度:三角形 > 20° • 长宽比:主流区 < 10

嗯,网格生成这块儿就聊到这儿。记住一个原则:网格质量永远比数量重要。一万个高质量的网格,比十万个烂网格强得多。你花在网格上的时间,最终都会在计算结果里体现出来。

专注资料整理