第17章 高级实体建模:断面扫掠、圆角、管道与曲率自适应网格
17.1 学习目标
- 掌握通过多个截面曲线创建过渡实体/面(断面扫掠,Thrusection / Loft / Skin)的方法,理解直纹面(ruled surface)与光滑过渡(smooth)两种插值方式的区别
- 学会对体的棱边施加圆角(Fillet),包括提取实体边界边、为每条边设定独立半径
- 掌握沿任意空间曲线路径扫掠截面生成管道(Pipe)的技术,理解不同标架(trihedron)类型对扫掠扭转行为的影响
- 理解曲率自适应网格(Curvature-adaptive mesh)的原理:根据几何曲率自动决定网格尺寸,曲率大的区域单元更密
- 熟悉本章涉及的 OCC(OpenCASCADE)辅助操作:
addWire(开放线框)、addSpline(样条曲线)、getBoundary(边界提取)、copy/translate等
17.2 核心概念说明
17.2.1 断面扫掠(Thrusection)
断面扫掠(Thrusection)是一种通过多个平行或非平行截面曲线来创建过渡实体或曲面的建模技术,在 CAD 领域也被称为 Loft 或 Skin。Gmsh 通过 OCC 内核提供 addThruSections() 函数实现这一功能。
截面1 (wire1) ──┐
截面2 (wire2) ──┼── 插值/放样 ──→ 过渡体/面
截面3 (wire3) ──┘
工作原理:给定一组由闭合或开放的线框(wire)构成的截面,OCC 在这些截面之间插值生成过渡曲面,再将曲面缝合为实体(如果 makeSolid=true)。
直纹面(Ruled)vs 光滑过渡(Smooth):
- makeRuled=true:在相邻截面之间用直纹面(ruled surface)连接,即用直线段直接连接对应点列。外观呈棱角分明的多边形过渡,适合规则几何体。
- makeRuled=false(默认):在相邻截面之间用光滑曲面连接,即通过 B样条/Bézier 插值生成连续过渡。外观平滑,适合有机形状。
高次曲面与连续性控制:addThruSections() 还提供了 maxDegree(最大曲面次数)和 continuity(连续性级别,如 "C0", "G1", "C1", "G2", "C2", "C3", "CN")等高级参数,用于控制曲面精度。
17.2.2 圆角(Fillet)
圆角(Fillet)是指将实体上的尖锐棱边替换为圆弧过渡面的操作。它对提升零件的疲劳寿命、消除应力集中、改善铸造/注塑工艺性至关重要。
在 Gmsh OCC 中,fillet() 函数接受:
1. 体标签列表:对哪些体施加圆角
2. 边标签列表:对哪些棱边倒圆角
3. 半径列表:每条边的圆角半径。可以是单个半径(适用于所有边)、每条边一个半径,或每条边两个半径(起点半径和终点半径不同,变半径圆角)
自动获取所有边界边:通过 getBoundary() 串联调用可以自动提取体的所有边界边,避免手动指定:
体(3D) → getBoundary → 面(2D) → getBoundary → 边(1D) → 收集边的标签
17.2.3 管道(Pipe)
管道(Pipe)是将一个截面(如圆盘)沿一条空间曲线路径扫掠生成实体的操作。它是通用扫掠(sweep)的一种特例。
Gmsh OCC 实现:addPipe() 接收一个截面实体(dimTags)和一条引导路径线框(wireTag),沿路径扫掠生成管道。
标架(Trihedron)类型控制截面在扫掠过程中如何相对于路径扭转:
| 标架类型 | 说明 |
|---|---|
DiscreteTrihedron |
离散标架(默认),最小扭转,数值稳定 |
Frenet |
Frenet 标架(Frenet Frame),基于曲线的切向、法向、副法向定义局部坐标系 |
CorrectedFrenet |
修正 Frenet 标架,减少 Frenet 在拐点处的翻转问题 |
Fixed |
固定方向,截面不随路径旋转 |
ConstantNormal |
常法向,保持截面法向恒定 |
Darboux |
Darboux 标架,结合曲面信息 |
17.2.4 曲率自适应网格
在 CAE 前处理中,一个常见需求是:几何曲率大的地方网格更密,以保证有限元分析精度。Gmsh 提供了一个全局选项来实现这一目标:
gmsh::option::setNumber("Mesh.MeshSizeFromCurvature", N);
其中 N 的含义是:曲线每经过 $2\pi$ 弧度的弯曲时,该段弧上最少要布置 N 个单元。举例:
- $N=20$:一个完整圆($2\pi$ 弧度)上至少 20 个单元
- $N=50$:一个完整圆上至少 50 个单元(网格更密,弧面几何分辨率更高)
- $N=5$:一个完整圆上至少 5 个单元(近似多边形,适用于曲率平缓的区域)
配合使用:通常将 Mesh.MeshSizeFromCurvature 与 Mesh.MeshSizeMin(最小尺寸限制)、Mesh.MeshSizeMax(最大尺寸限制)配合使用,避免曲率驱动产生过小或过大的单元。
与 API 关系:曲率自适应网格是通过全局选项控制的,而非通过 mesh::field API。设置该选项后,Gmsh 在网格划分时自动查询各几何实体的曲率并计算相应的局部网格尺寸。
17.3 C++ 代码逐段讲解
17.3.1 头文件与初始化
#include <set>
#include <cmath>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch17_advanced_solids");
引入 <set> 用于存储命令行参数集合(判断 GUI 模式),<cmath> 提供 M_PI、cos、sin 等数学常量与函数。<gmsh.h> 是 Gmsh C++ API 的唯一入口头文件。gmsh::model::add() 为当前模型命名,该名称会出现在 GUI 标题栏和导出的文件中。
17.3.2 断面扫掠 — 光滑过渡模式
gmsh::vectorpair out;
// 创建第一个截面:大圆 (中心原点,半径0.5)
gmsh::model::occ::addCircle(0, 0, 0, 0.5, 1);
gmsh::model::occ::addCurveLoop({1}, 1);
// 创建第二个截面:偏置小圆 (z=1处,半径0.1)
gmsh::model::occ::addCircle(0.1, 0.05, 1, 0.1, 2);
gmsh::model::occ::addCurveLoop({2}, 2);
// 创建第三个截面:偏置中圆 (z=2处,半径0.3)
gmsh::model::occ::addCircle(-0.1, -0.1, 2, 0.3, 3);
gmsh::model::occ::addCurveLoop({3}, 3);
// 断面扫掠:光滑插值,生成实体
gmsh::model::occ::addThruSections({1, 2, 3}, out, 1,
true, // makeSolid: 生成实体
false); // makeRuled: 光滑过渡
gmsh::model::occ::synchronize();
gmsh::model::occ::addCircle(x, y, z, r, tag) 在指定位置以给定半径创建一个圆(闭合曲线)。圆心在 (x, y),圆所在平面与 XY 平面平行(z 为法向高度)。标签 tag 正数时显式指定,OCC 内部使用与 geo 不同的编号空间。
gmsh::model::occ::addCurveLoop({curveTag}, tag) 将闭合曲线转换为曲线环。曲线环是面的边界定义单元。即使只有一条曲线,也需要显式创建 curve loop。
gmsh::model::occ::addThruSections({wireTags}, out, tag, makeSolid, makeRuled):
- {1, 2, 3}:三个截面 wire 的标签列表,按空间顺序排列
- out:输出参数,接收生成实体(dim, tag 对)的列表
- tag=1:显式指定新实体的标签
- makeSolid=true:生成体(3D);false 则生成面(2D)
- makeRuled=false:使用光滑插值过渡
此段代码生成的形状类似一个花瓶:底部大圆(截面1)、腰部小圆(截面2)、顶部中圆(截面3),它们之间通过光滑曲面连接。
17.3.3 断面扫掠 — 直纹面模式
// 第二组截面:使用直纹面 (ruled) 插值
gmsh::model::occ::addCircle(2 + 0, 0, 0, 0.5, 11);
gmsh::model::occ::addCurveLoop({11}, 11);
gmsh::model::occ::addCircle(2 + 0.1, 0.05, 1, 0.1, 12);
gmsh::model::occ::addCurveLoop({12}, 12);
gmsh::model::occ::addCircle(2 - 0.1, -0.1, 2, 0.3, 13);
gmsh::model::occ::addCurveLoop({13}, 13);
gmsh::model::occ::addThruSections({11, 12, 13}, out, 11,
true, // makeSolid: 生成实体
true); // makeRuled: 直纹面
gmsh::model::occ::synchronize();
第二组截面放在 x=2 偏移位置(2 + 0),与第一组形成对比。截面的几何参数与第一组相同,唯一区别是 makeRuled=true。
当 makeRuled=true 时,相邻截面之间的连接曲面由直线段直接连接对应点生成,外观呈棱角分明的锥形/棱柱形过渡。这对于机械零件(如锥齿轮坯、多边形过渡接头)非常有用。
在 GUI 中同时观察两组结果,可以直观对比光滑与直纹两种模式的差异。
17.3.4 实体圆角
// 复制第一个体,平移到 x=4 处
gmsh::vectorpair vol1 = {{3, 1}};
gmsh::model::occ::copy(vol1, out);
gmsh::model::occ::translate(out, 4, 0, 0);
gmsh::model::occ::synchronize();
// 提取体的所有边界边
gmsh::vectorpair faces;
gmsh::model::getBoundary(out, faces, false); // 体→面 边界
gmsh::vectorpair edges;
gmsh::model::getBoundary(faces, edges, false); // 面→边 边界
// 收集边的绝对标签(getBoundary 默认返回带符号标签)
std::vector<int> edgeTags;
for (auto &e : edges)
edgeTags.push_back(std::abs(e.second));
// 用半径 0.1 对所有边倒圆角
gmsh::model::occ::fillet({out[0].second}, // 体标签
edgeTags, // 边标签
{0.1}, // 半径(统一半径)
out); // 输出实体
gmsh::model::occ::synchronize();
本段展示了在实体上施加圆角的完整工作流程。
-
复制并平移:
occ::copy()+occ::translate()将原始体复制一份并平移到 x=4。使用复制体做圆角操作,避免影响原始实体。 -
提取边界边:
gmsh::model::getBoundary(inDimTags, outDimTags, combined, oriented, recursive)返回实体的边界实体。 - 第一层:体 → 面(
faces) - 第二层:面 → 边(
edges) combined=false表示不合并重复边界(当输入多个实体时)-
返回的标签可能带符号(表示方向),因此用
std::abs()取绝对值 -
调用 fillet:
occ::fillet(volumeTags, curveTags, radii, outDimTags, removeVolume) {out[0].second}是体的标签(单元素列表,表示只对一个体操作)edgeTags是从边界提取获得的所有边{0.1}是单个半径值,表示对所有边使用统一半径 0.1removeVolume=true(默认):删除原体,只保留圆角后的新体
变半径圆角:如果 radii 的长度是 curveTags 的两倍,则每条边可以指定起点和终点两个半径,实现渐变半径圆角。
17.3.5 管道 — 沿样条曲线扫掠
// 定义螺旋样条路径
double nturns = 1; // 螺旋圈数
int npts = 20; // 控制点数量
double r = 1; // 螺旋半径
double h = 1; // 总高度
std::vector<int> splinePts;
for (int i = 0; i < npts; i++) {
double theta = i * 2 * M_PI * nturns / npts;
gmsh::model::occ::addPoint(
r * cos(theta), r * sin(theta), i * h / npts,
1, // 网格尺寸(此处不关键)
1000 + i // 点标签
);
splinePts.push_back(1000 + i);
}
gmsh::model::occ::addSpline(splinePts, 1000);
首先创建一条螺旋样条曲线作为扫掠路径。nturns 控制螺旋的圈数,npts 控制离散精度,r 和 h 控制螺旋半径和高度。occ::addSpline(pointTags, splineTag) 通过这些控制点插值生成一条三次 B样条曲线。
// 将样条曲线包装为开放的线框(wire)
gmsh::model::occ::addWire({1000}, 1000);
occ::addWire({curveTags}, wireTag) 将一组曲线包装为线框(wire)。Wire 与 CurveLoop 的区别:
- CurveLoop:闭合的曲线环,用于定义面的边界
- Wire:开放的曲线链,用于作为扫掠路径等
管道扫掠要求引导路径为 wire 类型。
// 定义截面:圆盘(disk),半径 0.2
gmsh::model::occ::addDisk(1, 0, 0, 0.2, 0.2, 1000);
// 旋转圆盘使其法向与螺旋起点切线方向对齐
gmsh::model::occ::rotate({{2, 1000}}, 0, 0, 0, 1, 0, 0, M_PI / 2);
// 沿螺旋线扫掠圆盘生成管道
gmsh::vectorpair pipeOut;
gmsh::model::occ::addPipe({{2, 1000}}, 1000, pipeOut, "DiscreteTrihedron");
occ::addDisk(xc, yc, zc, rx, ry, tag) 创建一个椭圆/圆盘面。rx=ry=0.2 表示圆盘,半径为 0.2。occ::rotate(dimTags, x, y, z, ax, ay, az, angle) 绕通过点 (x,y,z)、方向 (ax,ay,az) 的轴旋转指定角度。此处绕 X 轴旋转 $\pi/2$ 使圆盘法向从 Z 轴转为 Y 轴方向(与螺旋起点切线匹配)。
occ::addPipe(dimTags, wireTag, out, trihedron) 是核心扫掠 API:
- {{2, 1000}}:截面实体(维度2 = 面,标签1000 = 圆盘)
- 1000:引导路径 wire 的标签
- pipeOut:输出,接收生成的管道实体
- "DiscreteTrihedron":标架类型(可替换为 "Frenet"、"Fixed" 等)
// 删除源截面,增加子边显示数量以获得更平滑的几何渲染
gmsh::model::occ::remove({{2, 1000}});
gmsh::option::setNumber("Geometry.NumSubEdges", 1000);
gmsh::model::occ::synchronize();
occ::remove() 删除不再需要的源截面。Geometry.NumSubEdges=1000 提高曲面在 GUI 中的渲染精度(增加每条边的分割段数),这只是视觉效果,不影响网格质量。对螺旋管道这类高曲率几何,提高该值可避免渲染成多边形。
17.3.6 曲率自适应网格与网格生成
// 激活曲率自适应网格尺寸计算
// 参数 20:每 2*PI 弧度至少 20 个单元
gmsh::option::setNumber("Mesh.MeshSizeFromCurvature", 20);
// 设置网格尺寸的上下限
gmsh::option::setNumber("Mesh.MeshSizeMin", 0.001);
gmsh::option::setNumber("Mesh.MeshSizeMax", 0.3);
gmsh::model::mesh::generate(3);
gmsh::write("ch17_advanced.msh");
曲率自适应网格通过三个全局选项控制:
| 选项 | 含义 | 本示例设置 |
|---|---|---|
Mesh.MeshSizeFromCurvature |
每 $2\pi$ 弧度最少单元数 | 20 |
Mesh.MeshSizeMin |
网格尺寸下限(防止曲率极大处单元过小) | 0.001 |
Mesh.MeshSizeMax |
网格尺寸上限(防止曲率极小处单元过大) | 0.3 |
设置后,Gmsh 在网格划分时自动遍历所有几何实体,用其曲率半径和角度参数计算局部目标单元尺寸。例如,圆角半径 0.1 处,由于曲率大,网格会自动加密;而在平直区域,网格可以较粗。
注意:曲率自适应是在网格划分阶段生效的,而非在几何建模阶段。它不影响 OCC 几何实体本身,只影响最终生成的网格节点分布。
17.3.7 GUI 启动与清理
std::set<std::string> args(argv, argv + argc);
if (!args.count("-nopopup"))
gmsh::fltk::run();
gmsh::finalize();
return 0;
}
标准结尾:检查命令行是否包含 -nopopup,若无则启动 FLTK 图形界面供交互查看。gmsh::finalize() 释放 Gmsh 资源。
17.4 完整可运行代码
// =============================================================================
// Gmsh C++ 教程 第17章 — 高级实体建模
// 断面扫掠(ThruSection) / 圆角(Fillet) / 管道(Pipe) / 曲率自适应网格
// =============================================================================
#include <set>
#include <cmath>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch17_advanced_solids");
gmsh::vectorpair out;
// ===== 17.4.1 断面扫掠:光滑过渡 (makeRuled=false) =====
gmsh::model::occ::addCircle(0, 0, 0, 0.5, 1);
gmsh::model::occ::addCurveLoop({1}, 1);
gmsh::model::occ::addCircle(0.1, 0.05, 1, 0.1, 2);
gmsh::model::occ::addCurveLoop({2}, 2);
gmsh::model::occ::addCircle(-0.1, -0.1, 2, 0.3, 3);
gmsh::model::occ::addCurveLoop({3}, 3);
gmsh::model::occ::addThruSections({1, 2, 3}, out, 1, true, false);
// ===== 17.4.2 断面扫掠:直纹面模式 (makeRuled=true) =====
gmsh::model::occ::addCircle(2 + 0, 0, 0, 0.5, 11);
gmsh::model::occ::addCurveLoop({11}, 11);
gmsh::model::occ::addCircle(2 + 0.1, 0.05, 1, 0.1, 12);
gmsh::model::occ::addCurveLoop({12}, 12);
gmsh::model::occ::addCircle(2 - 0.1, -0.1, 2, 0.3, 13);
gmsh::model::occ::addCurveLoop({13}, 13);
gmsh::model::occ::addThruSections({11, 12, 13}, out, 11, true, true);
// ===== 17.4.3 实体圆角 =====
// 复制第一个体,平移到 x=4
gmsh::model::occ::copy({{3, 1}}, out);
gmsh::model::occ::translate(out, 4, 0, 0);
gmsh::model::occ::synchronize();
// 提取体的所有边界边
gmsh::vectorpair faces, edges;
gmsh::model::getBoundary(out, faces, false);
gmsh::model::getBoundary(faces, edges, false);
std::vector<int> edgeTags;
for (auto &e : edges) edgeTags.push_back(std::abs(e.second));
// 对所有边施加统一半径 0.1 的圆角
gmsh::model::occ::fillet({out[0].second}, edgeTags, {0.1}, out);
gmsh::model::occ::synchronize();
// ===== 17.4.4 管道:沿螺旋样条扫掠圆盘 =====
double nturns = 1, r = 1, h = 1;
int npts = 20;
std::vector<int> pts;
for (int i = 0; i < npts; i++) {
double theta = i * 2 * M_PI * nturns / npts;
gmsh::model::occ::addPoint(
r * cos(theta), r * sin(theta), i * h / npts, 1, 1000 + i);
pts.push_back(1000 + i);
}
gmsh::model::occ::addSpline(pts, 1000);
gmsh::model::occ::addWire({1000}, 1000);
gmsh::model::occ::addDisk(1, 0, 0, 0.2, 0.2, 1000);
gmsh::model::occ::rotate({{2, 1000}}, 0, 0, 0, 1, 0, 0, M_PI / 2);
gmsh::vectorpair pipeOut;
gmsh::model::occ::addPipe({{2, 1000}}, 1000, pipeOut, "DiscreteTrihedron");
gmsh::model::occ::remove({{2, 1000}});
// 提高曲面渲染精度(仅视觉效果)
gmsh::option::setNumber("Geometry.NumSubEdges", 1000);
gmsh::model::occ::synchronize();
// ===== 17.4.5 曲率自适应网格 =====
gmsh::option::setNumber("Mesh.MeshSizeFromCurvature", 20);
gmsh::option::setNumber("Mesh.MeshSizeMin", 0.001);
gmsh::option::setNumber("Mesh.MeshSizeMax", 0.3);
gmsh::model::mesh::generate(3);
gmsh::write("ch17_advanced.msh");
// ===== 17.4.6 GUI 与清理 =====
std::set<std::string> args(argv, argv + argc);
if (!args.count("-nopopup"))
gmsh::fltk::run();
gmsh::finalize();
return 0;
}
17.5 关键 API 速查表
| API | 函数签名关键部分 | 说明 |
|---|---|---|
occ::addThruSections |
(wireTags, outDimTags, tag, makeSolid, makeRuled, maxDegree, continuity, parametrization, smoothing) |
通过多个截面 wire 创建过渡体/面。makeRuled=true 直纹面,false 光滑过渡。maxDegree 控制曲面最大次数,continuity 控制连续性级别 |
occ::fillet |
(volumeTags, curveTags, radii, outDimTags, removeVolume) |
对体的棱边倒圆角。radii 可为单一值、每边一值或每边两值(变半径) |
occ::fillet2D |
(edgeTag1, edgeTag2, radius, tag, pointTag, reverse) |
二维圆角:在两条边之间创建指定半径的圆角边 |
occ::addPipe |
(dimTags, wireTag, outDimTags, trihedron) |
沿 wire 扫掠实体。trihedron 控制扭转行为("DiscreteTrihedron"/"Frenet"/"Fixed"等) |
occ::addSpline |
(pointTags, tag) |
通过控制点创建 B样条曲线 |
occ::addWire |
({curveTags}, tag) |
将开放曲线包装为线框(wire),用于扫掠路径等 |
occ::addDisk |
(xc, yc, zc, rx, ry, tag) |
创建椭圆盘面(rx=ry 时为圆盘) |
occ::addCircle |
(x, y, z, r, tag, angle, zAxis) |
创建圆(闭合曲线),圆心 (x,y),平面法向为 Z 轴 |
occ::addCurveLoop |
({curveTags}, tag) |
将闭合曲线包装为曲线环(curve loop),作为面的边界 |
occ::copy |
(dimTags, outDimTags) |
复制实体 |
occ::translate |
(dimTags, dx, dy, dz) |
平移实体 |
occ::rotate |
(dimTags, x, y, z, ax, ay, az, angle) |
绕轴旋转实体 |
occ::remove |
(dimTags) |
删除 OCC 实体 |
model::getBoundary |
(dimTags, outDimTags, combined, oriented, recursive) |
获取实体的边界实体。recursive=true 递归至维度 0(点) |
option::setNumber |
(name, value) |
设置全局数值选项(如 Mesh.MeshSizeFromCurvature) |
occ::chamfer |
(volumeTags, curveTags, surfaceTags, distances, outDimTags, removeVolume) |
倒角(Chamfer),与 fillet 类似但用直线面代替圆弧面 |
17.6 注意事项
-
OCC 标签隔离:
occ::命名空间下的函数使用独立的标签编号空间。一个标签可同时存在于 OCC 和 geo(内置内核)中,两者互不影响。使用occ::synchronize()会将 OCC 实体同步到 Gmsh 模型中。 -
synchronize 时机:OCC 建模操作(addCircle, addWire, fillet, addPipe 等)在调用
occ::synchronize()后才会真正写入模型数据库。在对几何进行网格相关操作前务必先同步。 -
getBoundary 标签符号:
getBoundary()默认返回不带方向的标签(oriented=false),但可能因内部逻辑返回负值。在需要标签向量(如传给 fillet)时务必使用std::abs()取绝对值。 -
addThruSections 的 wire 顺序:wireTags 列表的顺序决定插值路径。倒序会改变过渡方向。
-
曲率自适应与尺寸场的配合:
Mesh.MeshSizeFromCurvature与第7章、第8章介绍的mesh::field(尺寸场)可以同时使用。当两者都激活时,Gmsh 取两者中更小的尺寸(取 min),以确保不违反任一约束。 -
addPipe 的截面要求:截面必须为面实体(dim=2),引导路径必须为 wire 实体(dim=1)。截面在扫掠前需通过
rotate或translate调整到路径起点处的正确朝向。 -
fillet 半径限制:圆角半径不能超过相邻面的几何尺寸,否则 OCC 会报错。如果某条边的两侧面宽度不足以容纳指定半径的圆角,需要减小半径或重新设计几何。
-
编译与运行:本章代码需链接 Gmsh 库。编译命令示例(Linux/macOS):
bash g++ -o ch17 ch17.cpp -lgmsh -std=c++11运行时可加-nopopup跳过 GUI 直接生成.msh文件。