第18章 STEP 导入与几何分割
18.1 学习目标
- 理解 STEP 格式在 CAD/CAM 工业中的核心地位(ISO 10303 标准),以及 Gmsh 通过 OpenCASCADE 内核导入 STEP 的完整流程
- 掌握
occ::importShapes()与gmsh::merge()的本质区别:前者可返回最高维实体标签,便于后续程序化操作 - 学会使用
Geometry.OCCTargetUnit选项在导入时自动转换几何单位(毫米/米/英寸等) - 掌握几何分割(Geometry Partitioning)的完整流程:包围盒计算 -> 创建切割面 -> 空间变换(旋转+平移) -> copy + translate 批量生成 -> fragment 共形布尔运算 -> remove 清理
- 理解
removeEntities()在模型层面的实体删除以及surf=true仅保留剖面的应用场景 - 了解几何分割在 CAE 前处理中的实际价值:大模型分区域并行网格划分、CAD 特征的区域提取
18.2 核心概念说明
18.2.1 STEP 格式与 ISO 10303
STEP(Standard for the Exchange of Product model data,产品模型数据交换标准)是 ISO 10303 系列国际标准定义的中性文件格式,是 CAD/CAM/CAE 领域最重要、最通用的几何数据交换格式。几乎所有主流 CAD 软件(CATIA、SolidWorks、NX、Creo、FreeCAD)都原生支持 STEP 导入/导出。
| 特性 | 说明 |
|---|---|
| 标准编号 | ISO 10303(常称为 STEP) |
| 常见扩展名 | .step, .stp, .stpnc |
| 几何内核 | 基于边界表示法(B-rep, Boundary Representation) |
| 数据类型 | AP203(机械零件)、AP214(汽车工业)、AP242(统一标准) |
| 内容层级 | 实体(Solid)、壳体(Shell)、面(Face)、边(Edge)、顶点(Vertex) |
在 CAE 前处理中的角色:CAD 工程师在 CAD 系统中完成几何建模后,导出为 STEP 文件;CAE 工程师在 Gmsh 中导入 STEP 进行网格生成。STEP 是连接 CAD 和 CAE 的桥梁。
18.2.2 importShapes() vs merge() 的核心区别
Gmsh 提供了两种导入外部几何的方式:
occ::importShapes("file.step", v) → 返回 (dim, tag) 列表,可直接操作
gmsh::merge("file.step") → 仅合并数据,不返回实体标签
| 特性 | occ::importShapes() |
gmsh::merge() |
|---|---|---|
| 所属模块 | gmsh::model::occ:: |
gmsh::(全局) |
| 返回值 | vector<pair<int,int>> 最高维实体标签 |
无返回值 |
| 后续操作 | 可直接用返回标签做切割/变换/分片 | 需通过 getEntities() 反查 |
| 适用场景 | 需要程序化修改几何 | 仅浏览或直接划分网格 |
为什么要用 importShapes? 本章的核心任务是几何分割——需要对导入的体做包围盒查询、切割面和体进行 fragment 布尔运算。这些操作必须知道体和面的标签。
实际代码对比:
// 方案A: importShapes —— 推荐用于几何操作场景
std::vector<std::pair<int, int>> v;
gmsh::model::occ::importShapes("part.step", v);
// v[0].first = 3, v[0].second = 1 → 导入的体(dim=3, tag=1)
// 现在可以直接用 v 做后续操作
// 方案B: merge —— 适合仅查看/直接网格化
gmsh::merge("part.step");
// 没有返回标签,需要用 getEntities() 反查所有3D实体
std::vector<std::pair<int, int>> entities;
gmsh::model::getEntities(entities, 3); // 多一步
18.2.3 Geometry.OCCTargetUnit 单位转换
CAD 系统使用不同的长度单位:机械行业多用毫米(mm),建筑行业多用米(m),芯片设计用微米(um)。OpenCASCADE 默认假设 STEP 数据的单位是毫米,但可以通过 Geometry.OCCTargetUnit 选项指定目标单位:
// 在 importShapes 之前设置目标单位
gmsh::option::setString("Geometry.OCCTargetUnit", "M"); // 转换为米
gmsh::option::setString("Geometry.OCCTargetUnit", "MM"); // 保持毫米(默认)
gmsh::option::setString("Geometry.OCCTargetUnit", "CM"); // 转换为厘米
gmsh::option::setString("Geometry.OCCTargetUnit", "UM"); // 转换为微米
重要:此选项必须在 importShapes() 或 merge() 调用之前设置,OpenCASCADE 会在导入时执行单位缩放。
18.2.4 几何分割的整体流程
本章演示的几何分割(Geometry Partitioning)是将一个完整的 CAD 体沿某个方向切成 N 片的过程,其核心流程如下:
┌──────────────┐
│ 1. 导入 STEP │ importShapes()
└──────┬───────┘
│
┌──────▼───────┐
│ 2. 获取包围盒│ getBoundingBox()
└──────┬───────┘
│
┌──────▼───────┐
│ 3. 创建切割面│ addRectangle() → rotate() → translate()
└──────┬───────┘
│
┌──────▼───────┐
│ 4. 批量生成 │ copy() + translate() (N-2次)
└──────┬───────┘
│
┌──────▼───────┐
│ 5. 共形相交 │ fragment(体, 切割面列表)
└──────┬───────┘
│
┌──────▼───────┐
│ 6. 清理残余 │ remove(多余的2D实体)
└──────┬───────┘
│
┌──────▼───────┐
│7. 可选:仅剖面│ getEntitiesInBoundingBox → removeEntities
└──────┬───────┘
│
┌──────▼───────┐
│ 8. 划分网格 │ mesh::generate()
└──────────────┘
18.3 C++ 代码逐段讲解
18.3.1 初始化与 STEP 导入
#include <set>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch18_step_partition");
// ---- 导入 STEP 文件 ----
// importShapes 返回最高维实体的 (dim, tag) 列表
std::vector<std::pair<int, int>> v;
try {
gmsh::model::occ::importShapes("../t20_data.step", v);
} catch(...) {
gmsh::logger::write("无法加载 STEP 文件,程序退出。");
gmsh::finalize();
return 0;
}
解释:occ::importShapes() 是 OpenCASCADE 模块提供的 STEP 导入函数。第一个参数是文件路径;第二个参数 v 是输出——导入后几何中最高维实体的 (dim, tag) 列表。对于含有一个完整实体的 STEP 文件,v 通常只有一项,如 {(3, 1)},表示维度为 3、标签为 1 的体。
使用 try-catch 包裹导入操作是一个好习惯:STEP 文件可能损坏、不存在,或包含 OpenCASCADE 无法识别的几何特征(如某些样条曲面类型),捕获异常后优雅退出。
18.3.2 设置单位转换(可选)
// ---- 可选:设置目标单位 ----
// 如果 STEP 文件包含英制尺寸,可用此行转换为公制毫米:
// gmsh::option::setString("Geometry.OCCTargetUnit", "MM");
//
// 如果希望输出为米(建筑/土木常用),可设置为 "M":
// gmsh::option::setString("Geometry.OCCTargetUnit", "M");
//
// 注意:此选项必须在 importShapes() 之前设置才能生效。
// 支持的字符串值: "M"(米), "MM"(毫米,默认), "CM"(厘米), "UM"(微米), "IN"(英寸)
解释:Geometry.OCCTargetUnit 控制 OpenCASCADE 导入时的单位缩放。默认值是 "MM"(毫米)。如果你的 STEP 文件是其他单位(例如英制零件以英寸为基准),设置此项后 OpenCASCADE 会自动将数值乘以换算系数。此选项对后续所有几何操作透明——设置后即可按照统一的单位体系进行包围盒计算、切割面定位等。
18.3.3 获取包围盒
// ---- 获取体的包围盒(Bounding Box) ----
double xmin, ymin, zmin, xmax, ymax, zmax;
gmsh::model::occ::getBoundingBox(v[0].first, v[0].second,
xmin, ymin, zmin,
xmax, ymax, zmax);
解释:occ::getBoundingBox(dim, tag, xmin, ymin, zmin, xmax, ymax, zmax) 返回指定实体的轴对齐包围盒(Axis-Aligned Bounding Box, AABB)。v[0] 是导入体的 (dim=3, tag=1);六个 double 引用参数用于输出包围盒的极小点和极大点坐标。
包围盒是后续所有操作的基线数据——切割面的大小、偏移量都需要基于这些坐标计算。AABB 是沿全局坐标系各轴的最小/最大值,因此对于摆放方向不规则(旋转放置)的零件,包围盒会偏大。
18.3.4 设定切割参数
// ---- 切割参数设定 ----
int N = 5; // 切割片数(切成N片,需要N-1个切割面)
std::string dir = "X"; // 切割方向:"X"、"Y" 或 "Z"
bool surf = false; // false=保留体积切片, true=仅保留剖面
double dx = (xmax - xmin);
double dy = (ymax - ymin);
double dz = (zmax - zmin);
// 切割面的尺寸:矩形在切割方向上的法向面内定义
double L = (dir == "X") ? dz : dx; // 切割面的第一轴向尺寸
double H = (dir == "Y") ? dz : dy; // 切割面的第二轴向尺寸
解释:N=5 表示将体切割成 5 片,需要 4 个切割面(分别位于总长度的 1/5、2/5、3/5、4/5 处)。
dir 指定切割方向。若选择 "X",即沿 X 轴方向切,则切割面应平行于 YZ 平面。为此,我们在 XY 平面创建一个矩形的切割面,然后将其旋转到与切割方向垂直的姿态:
- "X" 方向切:切割面初始在 XY 平面,沿 Y 轴旋转 -90 度使其平行于 YZ 平面
- "Y" 方向切:切割面初始在 XY 平面,沿 X 轴旋转 +90 度使其平行于 XZ 平面
- "Z" 方向切:切割面无需旋转,保持在 XY 平面
L 和 H 是切割面在自身平面内的两个尺寸,分别取包围盒在非切割方向上的跨度,确保切割面能完全覆盖体的截面。
surf 标志控制最终输出:false 时保留切割产生的所有体积片段(如共轭换热分析中需要分别对流体域和固体域划分网格);true 时仅保留各个切割面位置上的剖面(如用于展示内部截面或渐进式切片可视化)。
18.3.5 创建第一个切割面
// ---- 创建第一个切割面 ----
// addRectangle 在 XY 平面创建矩形面(z=zmin处)
std::vector<std::pair<int, int>> s;
s.push_back({2, gmsh::model::occ::addRectangle(xmin, ymin, zmin, L, H)});
// 将切割面旋转到与切割方向垂直的平面
if(dir == "X") {
// 绕 Y 轴旋转 -90 度:XY平面 → YZ平面
gmsh::model::occ::rotate({s[0]}, xmin, ymin, zmin, 0, 1, 0, -M_PI / 2);
}
else if(dir == "Y") {
// 绕 X 轴旋转 +90 度:XY平面 → XZ平面
gmsh::model::occ::rotate({s[0]}, xmin, ymin, zmin, 1, 0, 0, M_PI / 2);
}
// dir=="Z" 时无需旋转,保持 XY 平面即可
// 将切割面平移到第一个切割位置
double tx = (dir == "X") ? dx / N : 0;
double ty = (dir == "Y") ? dy / N : 0;
double tz = (dir == "Z") ? dz / N : 0;
gmsh::model::occ::translate({s[0]}, tx, ty, tz);
解释:这段代码展示了 OpenCASCADE 几何建模中"创建 -> 变换(旋转+平移)"的标准模式。
occ::addRectangle(x, y, z, dx, dy)在 XY 平面创建一个矩形面,返回其标签。注意这个矩形在其局部坐标中是一个平面面(Planar Face)。occ::rotate({entities}, ax, ay, az, angle)将指定实体绕通过点(xmin, ymin, zmin)、方向为(0, 1, 0)的轴旋转-PI/2弧度。第一个参数是实体标签列表。旋转轴的方向向量(ax, ay, az)自动归一化。occ::translate({entities}, dx, dy, dz)将实体平移。这里将切割面移到体内部的第一个切割位置(对于 N=5 且方向为 X,tx = dx/5)。
关键设计思想:先创建一个足够大的面,再通过旋转+平移将其放到正确的位置。这比手工计算三维空间中的精确坐标更简洁可靠。
18.3.6 批量生成切割面
// ---- 批量生成其余切割面(N-2个) ----
// 第0个切割面已是第一个切割位置,再创建第2到第N-1个(共N-1个切割面)
std::vector<std::pair<int, int>> tmp;
for(int i = 1; i < N - 1; i++) {
gmsh::model::occ::copy({s[0]}, tmp); // 复制第一个切割面
s.push_back(tmp[0]); // 加入切割面列表
gmsh::model::occ::translate({s.back()}, // 平移到第i个切割位置
i * tx, i * ty, i * tz);
}
解释:循环从 i=1 到 i=N-2(共 N-2 次迭代),与已有的第一个切割面一起组成 N-1 个切割面。
occ::copy({src}, dst)深拷贝源实体。dst输出新实体的标签列表。复制产生的实体与源实体几何完全相同,但拥有独立的标签。- 将新标签追加到
s列表,便于后续的 fragment 操作。 translate({s.back()}, i*tx, i*ty, i*tz)将新产生的切割面平移到对应的切割位置。偏移量随i递增:tx*(1),tx*(2), ... 对应于总长的 2/N, 3/N, ..., (N-1)/N。
为什么用 copy + translate 而不是 addRectangle + rotate + translate? 因为第一个切割面已经完成了 addRectangle 和 rotate 操作,copy 产生一个完全相同的几何面,省去了重复的矩形构建和角度旋转。
18.3.7 Fragment:共形布尔分片运算
// ---- Fragment 分片运算 ----
// fragment 将体与所有切割面共形相交
std::vector<std::pair<int, int>> ov; // 输出: 分片后的所有体
std::vector<std::vector<std::pair<int, int>>> ovv; // 输出: 每个工具体产生的碎片
gmsh::model::occ::fragment(v, s, ov, ovv);
解释:occ::fragment(objects, tools, ov, ovv) 是 OpenCASCADE 中最强大的布尔运算之一——共形分片(Conformal Fragmentation)。它的行为是:
- 将
objects列表中的每个实体,用tools列表中的所有实体进行切割 - 结果是
objects中的每个体被切割面切成了多个子体积 - 切割面的实体也会被保留在结果中(作为面嵌入到各子体的边界上)
与普通布尔运算(cut/fuse/intersect)不同,fragment 保证了共形性(Conformality):切割面与体相交后,切割面上的交线在相邻子体的边界上是完全一致的,不存在几何间隙或重叠。这是因为 fragment 将所有参与实体作为整体进行拓扑运算,确保交界面被共享。
结果参数:
- ov:分片后生成的所有最高维实体(体或面)
- ovv:嵌套列表,对应每个 object 被分割后产生的碎片——ovv[i] 是 v[i] 被切割后的子实体列表
以上面 5 片分割为例,ov 将包含 5 个体积碎片。(实际数量取决于 STEP 文件中体的数量——如果文件包含多个体,每个体都要被切割。)
18.3.8 清理多余的切割面残余
// ---- 清理切割面在体外部"伸出"的部分 ----
// fragment 之后,切割面上只有与体相交的部分被保留为体的边界
// 但 OpenCASCADE 可能残留一些面实体需要清理
gmsh::model::occ::getEntities(tmp, 2); // 获取所有2D实体(面)
gmsh::model::occ::remove(tmp, true); // 递归删除不在体边界上的面
解释:fragment 运算后,切割面被分割成两类: 1. 与体内部相交的部分——变成了相邻子体积的共享边界面 2. 在体外部"伸出"的部分——这些面对体的拓扑不再有意义,需要清理
occ::remove({entities}, true) 中的 true 表示递归(recursive)删除。递归删除意味着:如果一个面被删除,那么只有该面独享的边和顶点也会被一并删除(体边界上与其他面共享的边和点则保留)。
occ::synchronize() 将 OpenCASCADE 内核中的拓扑变化同步到 Gmsh 的模型数据结构中,后续的网格划分和模型查询操作才能正确反映这些变化。
18.3.9 surf=true:仅保留剖面
if(surf) {
// ---- 模式2: 仅保留剖面(丢弃体) ----
double eps = 1e-4;
std::vector<std::pair<int, int>> s_surf; // 收集剖面标签
// 在各切割位置处用包围盒搜索剖面
for(int i = 1; i < N; i++) {
// 构造搜索包围盒:每个切割位置处一个薄片状的包围盒
double xx = (dir == "X") ? xmin : xmax;
double yy = (dir == "Y") ? ymin : ymax;
double zz = (dir == "Z") ? zmin : zmax;
std::vector<std::pair<int, int>> e;
gmsh::model::getEntitiesInBoundingBox(
xmin - eps + i * tx, ymin - eps + i * ty, zmin - eps + i * tz,
xx + eps + i * tx, yy + eps + i * ty, zz + eps + i * tz,
e, 2); // 搜索维度2(面)
s_surf.insert(s_surf.end(), e.begin(), e.end());
}
解释:此段代码进入"仅保留剖面"模式——丢掉所有体积实体,只保留切割位置处体的内部截面。
核心技巧是用 getEntitiesInBoundingBox() 在各切割位置做一个空间查询:
- 对于 dir="X",搜索范围为 [xmin + i*tx, xmax + i*tx] (X 方向从最小到最大跨度,但位于切割平面位置附近),Y 和 Z 方向只取 ±eps 的薄片
- eps 设为 1e-4,保证能捕获到所有剖面上的面实体,同时避免误捕其他位置的面
- 最后一个参数 2 表示只搜索维度为 2 的实体(面)
对于 N=5 的情况,循环 i=1 到 i=4,在 4 个切割位置各搜一次,收集到所有剖面上的面。
// ---- 删除其余所有实体(保留剖面) ----
std::vector<std::pair<int, int>> dels;
gmsh::model::getEntities(dels, 2); // 所有面
// 从删除列表中移除要保留的剖面(集合差运算)
for(auto it = s_surf.begin(); it != s_surf.end(); ++it) {
auto it2 = std::find(dels.begin(), dels.end(), *it);
if(it2 != dels.end()) dels.erase(it2);
}
// 按维度从高到低依次删除:先删体,再删面,再删边,最后删点
gmsh::model::getEntities(tmp, 3);
gmsh::model::removeEntities(tmp); // 删除所有3D体
gmsh::model::removeEntities(dels); // 删除不留的面
gmsh::model::getEntities(tmp, 1);
gmsh::model::removeEntities(tmp); // 删除所有1D边
gmsh::model::getEntities(tmp, 0);
gmsh::model::removeEntities(tmp); // 删除所有0D点
}
解释:删除流程分为两个阶段:
阶段1 — 构造"白名单":先从所有2D实体中找出要保留的剖面,通过 std::find + erase 构造差集 dels = {所有面} \ {剖面}。
阶段2 — 逐维度删除:删除顺序必须从高维到低维(3D -> 2D -> 1D -> 0D)。因为低维实体可能作为高维实体的边界存在——如果先删除低维实体,高维实体的拓扑会变得不一致。removeEntities() 在模型层面(而非 OpenCASCADE 内核层面)删除实体,适用于不再需要对几何进行进一步 CAD 操作(变换、布尔等)的场景。
此时模型只剩下 N-1 个切割位置的剖面面。这些面可以直接用于二维网格划分,也可以作为可视化截面或导出为 IGES/STEP 供其他工具使用。
18.3.10 网格划分与输出
// ---- 网格划分 ----
gmsh::option::setNumber("Mesh.MeshSizeMin", 3);
gmsh::option::setNumber("Mesh.MeshSizeMax", 3);
gmsh::model::mesh::generate(3); // 生成三维网格
gmsh::write("ch18_step_partition.msh"); // 输出为 .msh 文件
// ---- 启动 GUI 查看结果 ----
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
解释:Mesh.MeshSizeMin 和 Mesh.MeshSizeMax 设为相同的值 3,表示均匀网格尺寸(各单元边长约 3),适用于演示目的。实际生产使用中,通常用第8章介绍的尺寸场(Field)来指定空间变化的网格尺寸。
mesh::generate(3) 生成三维网格。对于 surf=false 模式(保留体积切片),生成的是四面体网格;对于 surf=true 模式(仅保留剖面),虽然调用的是 generate(3),但由于模型中没有三维实体了,Gmsh 会自动退化为二维三角形网格生成。
对称切割面的网格一致性:fragment 保证了相邻子体积共享切割面,但不能保证这些共享面上的网格一致。如果需要在切割面上有节点一一对应的共形网格(例如用于域分解方法 /DDM),参见第15章的 setPeriodic 或第21章(t21.cpp/t21.geo)的 PartitionMesh。
18.4 面向 CAE 的实际应用场景
18.4.1 大模型分区域并行网格划分
对于超大 CAD 模型(如上万零部件的航空发动机、整车白车身),单次全局网格划分在内存和时间上都不可行。可采用以下策略:
- 将 STEP 模型导入 Gmsh
- 沿多个方向切割成 M x N x P 个子区域
- 对每个子区域独立(在分布式节点上)生成网格
- 利用 fragment 的共形性保证相邻子区域交界面的拓扑一致性
这比直接对整机划分能成倍降低单节点内存需求,且天然支持并行化。
18.4.2 CAD 特征的区域提取
有时只需要对零件的某个局部区域生成网格(例如只关心应力集中区域的孔洞周围区域)。通过以下流程可提取感兴趣的子区域:
- 导入 STEP
- 用包围盒定位感兴趣区域
- 创建多个切割面围成"截取窗口"
- fragment + remove 得到目标子区域
- 仅对该子区域划分精细网格
18.4.3 多材料/多物理场区域划分
在共轭换热(CHT, Conjugate Heat Transfer)分析中,固体域和流体域的界面需要共形网格。可以:
- 先后导入固体和流体的 STEP 文件到同一模型
- 用 fragment 确保界面处的拓扑一致性
- 对不同域设置不同的网格尺寸
- 导出时通过 Physical Group 区分材料区域
18.5 关键 API 速查表
| API | 说明 |
|---|---|
occ::importShapes(filename, out) |
导入 STEP/IGES/BREP 文件,out 返回最高维实体 (dim,tag) 列表 |
option::setString("Geometry.OCCTargetUnit", unit) |
设置导入单位:"M"/"MM"/"CM"/"UM"/"IN",须在 importShapes 前调用 |
occ::getBoundingBox(dim, tag, xmin, ymin, zmin, xmax, ymax, zmax) |
获取实体的轴对齐包围盒(AABB) |
occ::addRectangle(x, y, z, dx, dy) |
在 XY 平面创建矩形面,返回标签 |
occ::rotate({entities}, cx, cy, cz, ax, ay, az, angle) |
绕通过点 (cx,cy,cz)、方向为 (ax,ay,az) 的轴旋转实体 |
occ::translate({entities}, dx, dy, dz) |
平移实体 |
occ::copy({source}, dest) |
深拷贝实体,dest 输出新标签列表 |
occ::fragment(objects, tools, out, outMap) |
共形分片:用 tools 切割 objects,保证共享边界的拓扑一致性 |
occ::remove({entities}, recursive) |
删除 OpenCASCADE 实体,recursive=true 时级联删除其独享的低维边界 |
occ::synchronize() |
将 OpenCASCADE 内核变更同步到 Gmsh 模型 |
model::getEntitiesInBoundingBox(xmin,ymin,zmin,xmax,ymax,zmax, out, dim) |
空间查询:获取包围盒内的指定维度实体 |
model::removeEntities({entities}) |
在模型层面递归删除实体(不再需要 CAD 操作时使用) |
18.6 完整可运行代码
// -----------------------------------------------------------------------------
// Chapter 18: STEP Import and Geometry Partitioning
//
// 功能: 导入 STEP 文件,沿指定方向将几何体切割为 N 片,
// 支持两种模式:(1) 保留体积切片 (2) 仅保留剖面
//
// 编译 (需链接 Gmsh SDK):
// g++ -o ch18_step_partition ch18_step_partition.cpp -lgmsh
//
// 运行:
// ./ch18_step_partition (带 GUI 查看)
// ./ch18_step_partition -nopopup (仅生成 .msh 文件)
//
// 依赖文件:
// t20_data.step (Gmsh 教程提供的 STEP 示例文件)
// -----------------------------------------------------------------------------
#include <set>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch18_step_partition");
// ===== 1. 导入 STEP 文件 =====
std::vector<std::pair<int, int>> v;
try {
gmsh::model::occ::importShapes("../t20_data.step", v);
} catch(...) {
gmsh::logger::write("无法加载 STEP 文件,程序退出。");
gmsh::finalize();
return 0;
}
// ===== 2. 获取包围盒 =====
double xmin, ymin, zmin, xmax, ymax, zmax;
gmsh::model::occ::getBoundingBox(v[0].first, v[0].second,
xmin, ymin, zmin,
xmax, ymax, zmax);
// ===== 3. 切割参数 =====
int N = 5; // 切割片数
std::string dir = "X"; // 切割方向 "X" / "Y" / "Z"
bool surf = false; // false=保留体积切片, true=仅保留剖面
double dx = xmax - xmin, dy = ymax - ymin, dz = zmax - zmin;
double L = (dir == "X") ? dz : dx; // 切割面第一轴向尺寸
double H = (dir == "Y") ? dz : dy; // 切割面第二轴向尺寸
// ===== 4. 创建第一个切割面 =====
std::vector<std::pair<int, int>> s;
s.push_back({2, gmsh::model::occ::addRectangle(xmin, ymin, zmin, L, H)});
if(dir == "X")
gmsh::model::occ::rotate({s[0]}, xmin, ymin, zmin, 0, 1, 0, -M_PI / 2);
else if(dir == "Y")
gmsh::model::occ::rotate({s[0]}, xmin, ymin, zmin, 1, 0, 0, M_PI / 2);
double tx = (dir == "X") ? dx / N : 0;
double ty = (dir == "Y") ? dy / N : 0;
double tz = (dir == "Z") ? dz / N : 0;
gmsh::model::occ::translate({s[0]}, tx, ty, tz);
// ===== 5. 批量生成其余切割面 (N-2个) =====
std::vector<std::pair<int, int>> tmp;
for(int i = 1; i < N - 1; i++) {
gmsh::model::occ::copy({s[0]}, tmp);
s.push_back(tmp[0]);
gmsh::model::occ::translate({s.back()}, i * tx, i * ty, i * tz);
}
// ===== 6. Fragment 共形分片 =====
std::vector<std::pair<int, int>> ov;
std::vector<std::vector<std::pair<int, int>>> ovv;
gmsh::model::occ::fragment(v, s, ov, ovv);
// ===== 7. 清理不在体边界上的残余面 =====
gmsh::model::occ::getEntities(tmp, 2);
gmsh::model::occ::remove(tmp, true);
gmsh::model::occ::synchronize();
// ===== 8. 可选:仅保留剖面 =====
if(surf) {
double eps = 1e-4;
std::vector<std::pair<int, int>> s_surf;
for(int i = 1; i < N; i++) {
double xx = (dir == "X") ? xmin : xmax;
double yy = (dir == "Y") ? ymin : ymax;
double zz = (dir == "Z") ? zmin : zmax;
std::vector<std::pair<int, int>> e;
gmsh::model::getEntitiesInBoundingBox(
xmin - eps + i * tx, ymin - eps + i * ty, zmin - eps + i * tz,
xx + eps + i * tx, yy + eps + i * ty, zz + eps + i * tz,
e, 2);
s_surf.insert(s_surf.end(), e.begin(), e.end());
}
// 从所有面中排除要保留的剖面
std::vector<std::pair<int, int>> dels;
gmsh::model::getEntities(dels, 2);
for(auto it = s_surf.begin(); it != s_surf.end(); ++it) {
auto it2 = std::find(dels.begin(), dels.end(), *it);
if(it2 != dels.end()) dels.erase(it2);
}
// 按维度从高到低删除
gmsh::model::getEntities(tmp, 3);
gmsh::model::removeEntities(tmp); // 删除所有体
gmsh::model::removeEntities(dels); // 删除不留的面
gmsh::model::getEntities(tmp, 1);
gmsh::model::removeEntities(tmp); // 删除所有边
gmsh::model::getEntities(tmp, 0);
gmsh::model::removeEntities(tmp); // 删除所有点
}
// ===== 9. 网格划分 =====
gmsh::option::setNumber("Mesh.MeshSizeMin", 3);
gmsh::option::setNumber("Mesh.MeshSizeMax", 3);
gmsh::model::mesh::generate(3);
gmsh::write("ch18_step_partition.msh");
// ===== 10. 可选 GUI =====
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
18.7 本章小结
- STEP(ISO 10303)是 CAD/CAM/CAE 领域最通用的几何交换格式,Gmsh 通过 OpenCASCADE 内核原生支持导入。
occ::importShapes()返回导入实体的(dim, tag)列表,是后续程序化几何操作(切割、变换、布尔运算)的基础;gmsh::merge()仅负责合并数据,不返回标签。Geometry.OCCTargetUnit在导入时执行单位缩放,应在importShapes()调用前设置。- 几何分割的核心流程:
getBoundingBox获取基线尺寸 ->addRectangle+rotate+translate创建并定位切割面 ->copy+translate批量生成 ->fragment执行共形分片 ->remove清理残余。 fragment保证相邻子区域交界面的拓扑一致性(共形性),是其区别于普通布尔运算的关键。getEntitiesInBoundingBox空间查询 +removeEntities逐维度删除可实现仅保留剖面的后处理。- 几何分割的实际价值在于大模型并行网格划分、感兴趣子区域提取和多物理场区域划分。
- 本章技术与第21章(
t21.geo/ 网格分区 Mesh Partitioning)互补:本章在几何层面切割体,第21章在网格层面对已生成的网格进行分区。
18.8 注意事项
- STEP 文件质量:部分 CAD 系统导出的 STEP 文件可能含有 OpenCASCADE 无法识别的几何元素(如某些样条类型),导入时会抛出异常,应使用 try-catch 保护。
- 包围盒精度:
getBoundingBox返回的是轴对齐包围盒(AABB),对于斜放的零件可能偏大。如需更紧致的包围盒,需自行计算 OBB(Oriented Bounding Box,有向包围盒)。 - fragment 的计算复杂度:参与 fragment 的实体越多,布尔运算越慢。对于非常复杂的几何体(上千个面),建议先用
cut或intersect做局部处理,减少一次 fragment 中的实体数量。 - remove 与 removeEntities 的区别:
occ::remove操作 OpenCASCADE 内核数据,后续仍可进行 CAD 变换;model::removeEntities操作 Gmsh 模型数据,删除后不能再执行 CAD 操作。在surf=true模式下因不再需要进一步的几何操作,使用后者更直接。 - 切割面的网格一致性:fragment 保证了几何层面的共形性,但不保证网格层面上相邻子区域在共享面上的节点对齐。如需节点一一对应的共形网格,参见第15章的
setPeriodic或第21章的PartitionMesh。 - 切割方向为 "X" 或 "Y" 时:切割面的初始创建位置在
z=zmin的 XY 平面上,旋转中心在(xmin, ymin, zmin),确保旋转后切割面能够覆盖整个截面。