Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CAD2D 使用说明

本文根据 CAD2D 当前实现及偏心环形区域混合网格的实际开发过程整理,供后续新增二维网格程序时参考。这里记录的是当前代码的真实行为,其中部分接口依赖函数指针和调用顺序,使用前应特别注意相应约束。

1. 总体思路

CAD2D 将网格生成分为三层:

  1. 用参数化 edge 函数描述几何边和网格拓扑。
  2. RectRegion 生成一个结构化四边形区域,或用 MeshRegion 导入/构造一个普通区域。
  3. MeshRegions 合并多个区域、定义物理边界,并输出 Nektar++ XML 或 Gmsh GEO。

常用对象之间的关系如下:

edge function / LineEdge / CompositEdge
                  |
                  v
              RectRegion  ----+
                               |
Gmsh MSH ----> MeshRegion -----+----> MeshRegions
                                      |       |
                                      |       +--> outXml + outCOMPO
                                      +----------> outOuterRegion/outInnerRegion

对于“物面结构化边界层 + 中间非结构网格”问题,推荐采用两阶段流程:

生成边界层
  -> 从边界层的实际外边界节点输出 core.geo
  -> Gmsh 生成 MSH2 核心网格并尽量重组为四边形
  -> loadFromMsh 导入核心网格
  -> 检查接口节点完全一致
  -> AddRegion 合并
  -> 定义物理边界并输出 XML

2. 核心数据结构

2.1 MeshRegion

MeshRegion 是纯网格拓扑容器,主要成员为:

std::vector<std::vector<double>> m_pts; // 点坐标
std::vector<std::vector<int>>    m_edges; // 每条边的两个点编号
std::vector<std::vector<int>>    m_cells; // 每个单元包含的边编号
std::set<int>                    m_bndPts; // 当前认为是边界的点

最容易误解的是:m_cells 保存的是边编号,不是点编号。三角形包含 3 条边,四边形包含 4 条边。

辅助索引和边界数据包括:

  • m_edgesIndex:无方向点对 std::set<int> 到边编号的映射。
  • m_unSharedPtsextractBoundaryPoints() 提取出的多条不相连边界环。
  • m_unSharedPtsSet:上述边界环对应的点集合。
  • m_tolerance:点合并、边界匹配使用的几何容差。

改变点、边或单元后,通常需要:

region.ResetBndPts();
region.rebuildEdgesIndex();

ResetBndPts() 会根据“仅被一个单元使用的边”重新提取真实边界;rebuildEdgesIndex() 会重建无方向边索引。

2.2 MeshRegions

MeshRegions 继承自 MeshRegion,增加:

  • 多区域合并 AddRegion()
  • 物理边界与曲边定义;
  • Nektar++ XML 输出;
  • Gmsh GEO 边界输出。

它既是最终组合网格,也可以临时保存一组边界层区域。

3. edge 函数的约定

CAD2D 中连续边函数的约定是:

std::vector<double> edge(double s); // s in [-1, 1]

返回值至少包含 xy 两个分量。参数端点为:

  • s = -1:边起点;
  • s = 1:边终点。

当前接口通过 void * 保存函数地址,再强制转换为普通函数指针。因此应使用全局函数、命名空间函数或 static 函数。不要直接传入捕获 lambda 或普通非静态成员函数。

典型直线/圆弧函数为:

std::vector<double> wallEdge(double s) {
    const double theta = 0.5 * (1.0 - s) * theta0
                       + 0.5 * (1.0 + s) * theta1;
    return {cx + radius * std::cos(theta),
            cy + radius * std::sin(theta)};
}

4. LineEdge:控制一条参数边上的分布

LineEdge::Evaluate(s) 在两个端点之间插值,并通过不同 refine type 改变参数分布。常用构造方式为:

LineEdge edge(p0, p1, N, refineType, h0, h1);

其中 p0p1double *,其生命周期必须长于 LineEdge。建议像现有代码一样使用全局数组或其他稳定存储,不能传入很快失效的局部临时数组。

主要分布类型如下:

类型 作用
UNIFORM 均匀分布
EXPREFINE0/1 在参数起点/终点指数加密
SINREFINE0/1/2 单端或双端正弦加密
QUDREFINE0/1 单端二次加密
BOUNDARYLAYER0/1/2/4 显式指定端部边界层层数、首层和增长率
SMALLSTRETCH0/1 根据两端尺寸和增长率自动计算分段数

注意:这里的 BOUNDARYLAYER*LineEdge 的一维点分布类型,不是 RectRegion::MeshGen() 使用的 eBoundaryLayer* 二维网格生成方法,两者不要混淆。

带显式边界层参数的构造函数为:

LineEdge(p0, p1, N, refineType,
         h0, q0, NBlayers0,
         h1, q1, NBlayers1);

5. CompositEdge:将多段 edge 合成一条逻辑边

当一条物面由多段函数组成,但希望用一个 RectRegion 连续生成时,可以使用 CompositEdge

LineEdge partCount0(a0, a1, n0, UNIFORM, 0.0, 0.0);
LineEdge partCount1(a1, a2, n1, UNIFORM, 0.0, 0.0);

CompositEdge wall;

void initialiseWall() {
    static bool initialised = false;
    if (initialised) return;
    wall.addEdge(partCount0, reinterpret_cast<void *>(wallPart0));
    wall.addEdge(partCount1, reinterpret_cast<void *>(wallPart1));
    initialised = true;
}

std::vector<double> completeWall(double s) {
    return wall.Evaluate(s);
}

CompositEdgeLineEdge 的主要作用是提供每一段的单元数 m_N;实际几何坐标由传入的 edge 函数计算。各段连接点必须在容差内重合。

总切向单元数为:

wall.m_N

不要重复调用 addEdge(),否则段和单元数会重复累加。使用一次性初始化函数是最稳妥的方式。

6. RectRegion:结构化四边形区域

6.1 四条边模式

普通四边区域可以用四个连续 edge 函数构造:

std::vector<void *> edges = {
    reinterpret_cast<void *>(edge0),
    reinterpret_cast<void *>(edge1),
    reinterpret_cast<void *>(edge2),
    reinterpret_cast<void *>(edge3)
};

RectRegion block(edges, "block", true, tolerance);
block.MeshGen(M, N, eIsoparametric);

也可以传入四组离散边界点。Medge0/edge2 方向的单元数,Nedge1/edge3 方向的单元数。

网格方法包括:

方法 行为
eIsoparametric 四边 Coons 型等参插值
eStraightLine 对向边节点连成直线并求交点
eBoundaryLayer0 edge0 生长,使用相邻离散点估计内部法向
eBoundaryLayer1 edge0 生长,使用连续 edge 的数值导数计算法向

若自动连通性检查判断了错误的边方向,可以用:

std::vector<int> directions = {1, -1, 1, -1};
block.SetEdgesDirec(directions);

6.2 标准边界层模式

生成物面边界层时,真正需要定义的只有:

  1. edge0:物面切向曲线;
  2. edge1:法向累计生长距离。

edge2edge3 在边界层算法中不会被使用,可以置空。此时必须将 connectivityCheck 设置为 false

std::vector<void *> edges = {
    reinterpret_cast<void *>(wallEdge),
    reinterpret_cast<void *>(normalDistribution),
    nullptr,
    nullptr
};

RectRegion layer(edges, "WallBL", false, tolerance);
layer.MeshGen(nWallCells, nNormalCells, eBoundaryLayer1);

法向分布函数只使用返回值的第一个分量:

std::vector<double> normalDistribution(double s) {
    int layer = std::lround(0.5 * (s + 1.0) * nNormalCells);
    return {cumulativeDistance(layer), 0.0};
}

这里必须返回从物面开始的累计距离:第 0 个节点为 0,最后一个节点为总边界层厚度。

6.3 法向方向

边界层法向由 edge0 的切向导数 (dx, dy) 计算:

normal = (-dy, dx) / |d(edge0)/ds|

因此网格总是向 edge0 参数正方向的左侧生长。改变物面函数的参数方向即可改变生长方向。

对于环形流体域:

  • 内圆物面应顺时针参数化,使左法向指向圆外、进入流体;
  • 外圆物面应逆时针参数化,使左法向指向圆内、进入流体。

如果方向错误,通常会出现边界层长进固体、区域重叠或负 Jacobian。

6.4 边界层层数和总厚度

库中已有两种做法:

  1. MeshTool::setRadiusMesh()setRadiusLayers()radiusEdge()
  2. 自己实现累计距离 edge。

第一种的典型流程为:

setRadiusMesh(firstHeight, growth, maxHeight);
int n = findNlayers(firstHeight, growth, totalThickness, maxHeight);
setRadiusLayers(n);

// edge1 使用 radiusEdge
region.MeshGen(nTangential, n, eBoundaryLayer1);

需要注意:findNlayers() 返回累计厚度首次达到目标值时的层数,因此最后得到的总厚度可能略大于指定值。radiusEdge() 还依赖一个全局状态,不适合并发使用;每次改变层数前都要调用 setRadiusLayers()

如果总厚度必须精确,推荐像当前环形网格一样:

  1. 根据首层、目标增长率、最大层高和总厚度计算层数;
  2. 调整有效增长率或归一化累计距离;
  3. 强制最后一个累计距离等于指定总厚度。

7. 区域合并

使用 MeshRegions::AddRegion() 合并区域:

MeshRegions combined("Combined", tolerance);
combined.AddRegion(region0);
combined.AddRegion(region1);
combined.ResetBndPts();
combined.rebuildEdgesIndex();

实现上的重要约束:

  • 只有源区域中标记为边界点的点,才有机会与目标区域已有边界点合并;
  • 点匹配使用 0.5 * (|dx| + |dy|) < tolerance
  • 匹配后的公共边会根据无方向端点集合合并;
  • 核心区和边界层接口必须拥有完全相同的节点坐标和分段拓扑。

因此不要分别对同一接口做两套近似采样。推荐用已经生成的边界层接口节点直接输出核心区几何。

在完成一组区域合并后,再添加另一个大区域之前,先执行:

combined.ResetBndPts();
combined.rebuildEdgesIndex();

这样 m_bndPts 才代表当前组合网格的真实外露边界。

8. 与 Gmsh 组合生成混合网格

8.1 输出核心区 GEO

MeshRegions 的底层 outGeo() 是私有函数,通常通过以下公开函数间接调用:

outOuterRegion(filename, outerLoop, selectorCenter, selectorRadius, exclude);
outInnerRegion(filename, breakPoints, selectorCenter, selectorRadius);

outOuterRegion() 中的 selectorRadius 实际使用的是下面的 L1 距离判据,而不是严格的欧氏半径:

|x - center.x| + |y - center.y| < selectorRadius

使用它选择复杂边界时,应检查最终 .geo 文件中的边界环是否正确。对于需要完全可控的多环几何,也可直接使用 util::OutGeo(),或在上层显式整理边界点。

偏心环形区域的推荐做法是:

  1. 从外壁边界层中找出其核心接口节点,作为 GEO 的外环;
  2. 从内壁边界层中排除物面边界,保留核心接口作为孔洞;
  3. 调用 outOuterRegion() 写出平面区域;
  4. 追加 Gmsh 网格尺寸和重组参数。

关键点是使用边界层对象中的实际节点,而不是重新调用解析 edge 函数采样。

8.2 防止 Gmsh 改变接口节点

GEO 输出会把接口相邻节点逐一连接成独立的 Line。为了防止 Gmsh 在每条接口线上插入新节点,可追加:

Transfinite Line {firstLine:lastLine} = 2;

这里的 2 表示每条线只有两个端点。若 Gmsh 改变接口离散,后续 AddRegion() 无法形成共形接口。

典型核心区设置为:

Mesh.CharacteristicLengthMin = coreMeshSize;
Mesh.CharacteristicLengthMax = coreMeshSize;
Mesh.Algorithm = 5;
Mesh.RecombinationAlgorithm = 1;
Mesh.MshFileVersion = 2.2;

outGeo() 已经写入 Recombine Surface。Blossom 重组允许保留少量未合并的三角形,通常比强制得到全四边形更稳健。

8.3 Gmsh 版本和格式

当前 loadFromMsh() 是面向传统 ASCII MSH 的简单解析器。建议明确使用 MSH2:

gmsh core.geo -2 -algo del2d -format msh2 -o core.msh

解析器只导入:

  • Gmsh 类型 2:三角形;
  • Gmsh 类型 3:四边形。

线单元及其他类型会被忽略。不要依赖二进制 MSH、MSH4 实体块或高阶单元格式。

8.4 导入时的最大四边形内角

const double maxAngle = 120.0 / 180.0 * std::acos(-1.0);
MeshRegions core("Core", tolerance);
int status = core.loadFromMsh("core.msh", maxAngle);

该参数使用弧度。对于四边形,如果最大内角大于阈值,导入时会沿对角线拆成两个三角形。当前实现只有在阈值大于 90° 时才启用此拆分逻辑。

该参数只检查并拆分导入的四边形,不会检查已有三角形的最大内角,也不是通用网格质量优化器。

8.5 合并前检查接口

导入核心网格后,建议至少检查:

core.m_bndPts.size() == expectedInterfacePointCount

并对每一个核心边界点调用边界层组合网格的:

boundaryLayers.pointIsExist(core.m_pts[id], matchedId)

全部匹配后再执行 AddRegion(core)。这样可以在输出非共形网格前尽早失败。

9. 定义物理边界和曲边

9.1 按解析 edge 定义

已知边界函数和边数时,使用:

combined.defineBoundary(
    reinterpret_cast<void *>(wallEdge),
    nEdges,
    boundaryId,
    nCurvedPoints,
    direction);

它会按相同参数位置计算每条边的两个端点,并在当前边界中查找对应 edge。

  • nEdges 必须与真实边界分段数一致;
  • nCurvedPoints > 2 时,会为每条边输出 PolyEvenlySpaced 曲边点;
  • direction < 0 可反转解析 edge 的参数方向;
  • 定义边界前要保证 m_bndPtsm_edgesIndex 已更新。

对于由 CompositEdge 表示的边界,既可以用完整 composite edge 和 composite.m_N 定义,也可以逐段调用并使用同一个边界 ID。

9.2 按位置条件定义

也可以先按转角拆分外露边界,再用条件函数选择:

std::map<int, void *> conditions;
conditions[0] = reinterpret_cast<void *>(isWall);
conditions[1] = reinterpret_cast<void *>(isFarField);
combined.defineBoundary(conditions, splitAngle);

条件函数签名为:

bool condition(std::vector<double> &point);

一个条件命中某一边界段上的任意一点后,整段会被分配给该边界 ID。条件应能唯一识别目标边界段。

10. 输出 Nektar++ XML

完整输出必须按下面的顺序执行:

combined.CheckMesh();
combined.outXml("mesh.xml");
combined.outCOMPO("mesh.xml", {0});

outXml() 写入:

  • VERTEX
  • EDGE
  • 三角形/四边形 ELEMENT
  • CURVED

但它不会关闭整个 XML 文档。outCOMPO() 以追加方式写入:

  • 边界 composite;
  • 单元 composite;
  • DOMAIN
  • 可选的 EXPANSIONS
  • XML 结束标签。

因此不能只调用 outXml()。编译时定义 OUTPUTEXP 才会输出默认 expansion 配置。

outCOMPO() 会先将四边形单元排在三角形单元之前,以便生成连续的 Q[...]T[...] composite 范围。

传给 outCOMPO() 的整数列表用于进一步切分单元 composite;简单单区域网格通常传 {0}

11. 网格检查

11.1 CheckMesh()

combined.CheckMesh();

输出包括:

  • isolated points;
  • isolated edges;
  • negative Jacobi elements。

其中仅被一个单元使用的边就是外露边界,所以 isolated edges 的数量通常等于物理边界边数,不要求为零。真正需要重点关注的是:

  • isolated points 应为 0;
  • negative Jacobi elements 应为 0;
  • isolated edges 应与预期的所有边界分段数一致。

loadFromMsh()loadFromXml() 内部会调用 FixMesh() 修正负方向单元,但最终合并后仍应再次检查。

11.2 推荐的外部检查

xmllint --noout mesh.xml
rg -c '<Q ' mesh.xml
rg -c '<T ' mesh.xml

还应检查 Gmsh 日志中的:

  • 重组后的四边形和三角形数量;
  • invalid quads;
  • 最小质量;
  • 是否出现接口线重新剖分相关错误。

12. 推荐的程序组织

对于具体算例,推荐保持下面的职责划分:

params.h
  几何尺寸、独立边界分段数、首层高度、增长率、总厚度、core mesh size

edgefunctions.h
  几何 edge、LineEdge、CompositEdge、法向累计距离、派生层数

mesh.cpp
  参数检查、区域生成、GEO 输出、MSH 导入、接口检查、区域合并、最终输出

参数检查至少应包括:

  • 几何半径和位置合法;
  • 边界层不重叠并留有核心区域;
  • 首层、增长率、总厚度和核心尺寸为正;
  • 分段数与 CompositEdge 的分段方式兼容;
  • Gmsh 接口总边数适合预期的重组策略;
  • 曲边点数不少于 3。

13. 一个最小边界层示例

#include "CAD2D/MeshRegions.h"
#include "CAD2D/RectRegion.h"

std::vector<double> wall(double s) {
    // 顺时针内圆:左法向指向圆外
    double theta = -std::acos(-1.0) * (s + 1.0);
    return {cx + r * std::cos(theta), cy + r * std::sin(theta)};
}

std::vector<double> growth(double s) {
    int j = std::lround(0.5 * (s + 1.0) * nLayers);
    return {cumulativeDistance(j), 0.0};
}

int main() {
    std::vector<void *> edges = {
        reinterpret_cast<void *>(wall),
        reinterpret_cast<void *>(growth),
        nullptr,
        nullptr
    };

    RectRegion bl(edges, "BL", false, 1.0e-10);
    bl.MeshGen(nWallCells, nLayers, eBoundaryLayer1);

    MeshRegions mesh("Mesh", 1.0e-10);
    mesh.AddRegion(bl);
    mesh.ResetBndPts();
    mesh.rebuildEdgesIndex();
    mesh.CheckMesh();
}

14. 其他模块

  • SplineEdge:从文件读取二维点,构造 cardinal cubic B-spline,并建立近似弧长表;Evaluate(s) 按归一化弧长参数 [-1,1] 取点。
  • Cylinder:按起止角度生成圆弧点,但不包含圆心平移。
  • BLMeshModule:边界层几何模块的抽象基类,使用字符串到数值的参数表。
  • BLEllipse:椭圆/圆柱边界层示例。
  • BLFlatPlateBLRectangle:多段物面的边界层拼接示例。
  • BLAirfoilairfoil.*:NACA 或楔形翼型及其边界层模块。
  • MeshTool:全局法向层分布、局部变厚边界层及按边界嵌套层级输出 GEO 的辅助函数。
  • util:距离、包围盒、拓扑树、GEO 输出和 XML 数字解析等工具。

15. 常见问题清单

边界层向错误方向生长

检查 edge0 的参数方向。CAD2D 使用左法向,不根据“内壁/外壁”自动判断流体侧。

RectRegion 连通性检查失败

普通四边区域检查四条边端点;必要时用 SetEdgesDirec()。只有 edge0 + edge1 的边界层区域必须关闭检查,另外两条边使用 nullptr

合并后接口仍是两套边

通常是节点坐标、节点数或容差不一致。确认 Gmsh 没有在线上插入节点,并使用边界层实际节点输出 GEO。

defineBoundary() 找不到点或边

确认:

  • nEdges 与网格真实分段数一致;
  • edge 函数端点与网格节点在 m_tolerance 内一致;
  • 已调用 ResetBndPts()rebuildEdgesIndex()
  • composite edge 已完成一次性初始化。

XML 文件不完整

必须先调用 outXml(),再对同一文件调用 outCOMPO()

Gmsh 重组四边形质量差

允许保留少量三角形,并在 loadFromMsh() 中设置合理的最大四边形内角,例如 120°。不要为了全四边形破坏接口或产生高畸变单元。

修改网格后边界数量异常

任何会改变点、边、单元或区域连接关系的操作后,重新执行:

ResetBndPts();
rebuildEdgesIndex();

16. 编译依赖

只使用核心结构化生成、合并和 XML 输出时,通常至少需要编译:

CAD2D/CompositEdge.cpp
CAD2D/LineEdge.cpp
CAD2D/MeshRegion.cpp
CAD2D/MeshRegions.cpp
CAD2D/RectRegion.cpp
CAD2D/tinyxml2.cpp
CAD2D/util.cpp

使用 radiusEdge()findNlayers() 辅助流程时还需加入 CAD2D/MeshTool.cpp。使用样条、翼型或预制边界层模块时,再加入对应源文件。

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages