第16章 OpenCASCADE 内核与构造实体几何(CSG)
16.1 学习目标
- 理解 Gmsh 两种几何内核的本质区别:built-in 内核(自底向上)与 OpenCASCADE 内核(自顶向下,B-rep)
- 掌握使用 OpenCASCADE(occ)命名空间直接创建基本体:
addBox、addSphere、addCylinder、addCone、addTorus - 学会 CSG(Construction Solid Geometry)布尔运算:
cut(差)、fuse(并)、intersect(交)、fragment(共形布尔分片) - 理解
cut与fragment的核心差异:删减 vs 共形保留,以及各自适用的仿真场景 - 掌握布尔运算后的实体查找技巧:
getEntities、getBoundary、getEntitiesInBoundingBox、getClosestEntities - 学会通过
mesh::setSize对实体列表批量设置网格尺寸,替代点级lc约束 - 使用
gmsh::logger记录和检索运行日志
16.2 核心概念说明
16.2.1 Gmsh 的两种几何内核
Gmsh 提供两套几乎完全对应的几何建模 API,位于不同的命名空间下:
| 特性 | built-in 内核(gmsh::model::geo) |
OpenCASCADE 内核(gmsh::model::occ) |
|---|---|---|
| 建模方式 | 自底向上(bottom-up):点 -> 线 -> 面 -> 体 | 自顶向下(top-down):直接创建体,通过布尔运算组合 |
| 几何表示 | 边界表示(B-rep),但构造过程需逐级建立拓扑 | 完整的 B-rep 表示,直接提供高级实体 API |
| 实体创建 | addPoint -> addLine -> addCurveLoop -> addPlaneSurface -> addSurfaceLoop -> addVolume |
addBox / addSphere / addCylinder / addCone / addTorus 一步创建 |
| 布尔运算 | 通过 geo::translate + 手动布尔操作间接实现 |
原生支持:cut / fuse / intersect / fragment |
| CAD 导入 | 不支持 | 支持 STEP、IGES 等工业标准格式 |
| 适用场景 | 简单参数化几何、纯程序化建模 | 复杂 CAD 模型、CSG 组合、多材料界面、CAD 导入后处理 |
| 复杂度 | 简单几何代码量大,复杂几何代码量急剧膨胀 | 简单几何极简,复杂几何通过布尔运算可组合 |
选择建议:对于 CAE 前处理中的绝大多数场景,优先选择 OpenCASCADE 内核。built-in 内核主要用于参数化研究的简单模型或对拓扑有精确控制需求的场景。
16.2.2 构造实体几何(CSG)与布尔运算
CSG(Constructive Solid Geometry) 是一种通过基本体(primitive)之间的布尔运算来构建复杂几何的方法论。其核心操作包括:
- 并运算(Union / Fuse):A + B,保留两个体的全部材料
- 差运算(Cut / Difference):A - B,从 A 中减去 B 占据的空间
- 交运算(Intersection):A 交 B,只保留 A 和 B 共同占据的空间
- 共形布尔分片(Fragment):A 与 B 在相交处被分割成独立实体,所有交界面被"缝合"为共享面——这是多材料网格的关键操作
Gmsh 中四个操作的 API 原型完全一致:
void occ::cut( objectDimTags, toolDimTags, out, outMap, tag, removeObject=true, removeTool=true);
void occ::fuse( objectDimTags, toolDimTags, out, outMap, tag, removeObject=true, removeTool=true);
void occ::intersect(objectDimTags, toolDimTags, out, outMap, tag, removeObject=true, removeTool=true);
void occ::fragment(objectDimTags, toolDimTags, out, outMap, tag, removeObject=true, removeTool=true);
参数说明:
- objectDimTags:目标实体列表(主操作对象),类型 std::vector<std::pair<int,int>>
- toolDimTags:工具实体列表(用于布尔操作),类型同上
- out:输出参数,返回生成的所有实体(与输入实体同维度)
- outMap:输出参数,返回每个输入实体与其子实体的映射关系(父-子关系表)
- tag:可为结果实体指定标签,传 -1 表示自动分配
- removeObject:是否删除目标原始实体(默认 true)
- removeTool:是否删除工具原始实体(默认 true)
16.2.3 fragment vs cut:多材料建模的关键区别
这是初学者最容易混淆的地方。考虑一个立方体中嵌入一个球体:
使用 cut 的结果:
立方体 - 球体 = 带球形空腔的立方体
球体被完全移除,得到一个内部有空腔的单一体。适用于需要挖孔的场景(如螺丝穿过板)。
使用 fragment 的结果:
立方体 + 球体 = 立方体在球体处被分割为多个实体(立方体外部 + 球体 + 立方体内部被球占据的部分消失)
所有实体在交界面处被共形分割,交界面成为共享面(而不是重复面)。这确保了在生成网格时,不同材料的网格在界面上节点一一对应,对于复合材料、生物力学、地质建模等多材料仿真至关重要。
cut 示意图: fragment 示意图:
┌─────────┐ ┌─────────┐
│ cube │ │cube_out │
│ │ │ ┌────┤
│ cavity │<-- 空腔 │ │ball│
│ │ │ └────┤
└─────────┘ └─────────┘
单一体 多个共形实体
16.2.4 布尔运算后的实体标签管理
OpenCASCADE 的布尔运算总是创建新实体。当 removeObject 或 removeTool 设为 true 时,原始实体会被删除。Gmsh 的行为受以下选项控制:
Geometry.OCCBooleanPreserveNumbering(默认true):当布尔操作导致实体的简单修改时(如简单切割一个立方体),Gmsh 尝试将新实体赋予与原始实体相同的标签。这使得在物理组定义时可以"预测"某些标签。
然而,这个机制并不可靠——对于复杂操作(如 fragment),标签会重新分配。稳妥做法:通过 out 和 outMap 返回值动态获取布尔运算后的实体标签。
16.2.5 与第5章内容的对应关系
本章使用 OpenCASCADE 内核重现了第5章中 built-in 内核构建的"立方体减去八分之一 + 五个球形夹杂"模型。第5章通过手动构建点、线、面、体实现了该模型,代码量极大;本章通过 CSG 布尔运算仅用少量 API 调用即完成相同几何。这是两种内核在代码效率上最直观的对比。
16.3 C++ 代码逐段讲解
16.3.1 初始化与日志启动
#include <set>
#include <iostream>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("t16");
// 启动日志记录:所有 Gmsh 内部消息都会被日志系统捕获
// 可用于后续排查问题或获取布尔运算的详细信息
gmsh::logger::start();
gmsh::logger::start() 启动日志记录器,此后所有 Gmsh 内部消息(信息、警告、错误)都会被捕获到内存缓冲区中。配合本章末的 gmsh::logger::get() 可以检索日志内容——这在调试复杂布尔操作时非常有价值。
16.3.2 使用 occ 内核创建基本体
// 创建两个立方体(分别赋予标签 1 和 2)
// addBox(x, y, z, dx, dy, dz, tag)
// (x,y,z) 为立方体左下角坐标,(dx,dy,dz) 为各方向延伸长度
try {
gmsh::model::occ::addBox(0, 0, 0, 1, 1, 1, 1); // 标签1: 单位立方体
gmsh::model::occ::addBox(0, 0, 0, 0.5, 0.5, 0.5, 2); // 标签2: 1/8角落小立方体
} catch(...) {
gmsh::logger::write("Could not create OpenCASCADE shapes: bye!");
return 0;
}
occ::addBox(x, y, z, dx, dy, dz, tag) 一步创建六面体。这与 built-in 内核中需要依次创建 8 个点、12 条线、6 个面、1 个体形成了鲜明对比。
其他常用基本体 API:
| API | 参数 | 说明 |
|---|---|---|
addBox(x,y,z,dx,dy,dz,tag) |
角点+长宽高 | 轴对齐长方体 |
addSphere(xc,yc,zc,r,tag) |
球心+半径 | 球体 |
addSphere(xc,yc,zc,r,angle1,angle2,angle3,tag) |
球心+半径+三个角度 | 球面片 |
addCylinder(xc,yc,zc,dx,dy,dz,r,tag) |
底面圆心+轴向+半径 | 圆柱体 |
addCone(xc,yc,zc,dx,dy,dz,r1,r2,tag) |
底面圆心+轴向+两半径 | 圆台/圆锥 |
addTorus(xc,yc,zc,r1,r2,tag) |
中心+主半径+管半径 | 圆环体 |
addWedge(x,y,z,dx,dy,dz,tag) |
角点+长宽高 | 楔形体 |
16.3.3 布尔差运算(cut):挖去八分之一角
// 布尔差运算: 立方体1 - 小立方体2 = 去掉一角的立方体
// ov 用于接收产生的体积实体
// ovv 用于接收父-子关系映射
std::vector<std::pair<int, int>> ov; // 输出体积列表
std::vector<std::vector<std::pair<int, int>>> ovv; // 每个输入实体的子实体列表
gmsh::model::occ::cut({{3, 1}}, // objectDimTags: 体积3, 标签1(主对象)
{{3, 2}}, // toolDimTags: 体积3, 标签2(工具)
ov, ovv, 3); // out, outMap, tag=-3(指定结果标签)
要点拆解:
- std::pair<int,int> 的格式为 {维度, 标签},如 {3, 1} 表示"3D 实体,标签为 1"(即一个体积)。用 dimTag 描述实体是 Gmsh 的统一约定。
- 由于 removeObject 和 removeTool 默认为 true,此操作后标签 1 和 2 的原始立方体被删除,标签 3 成为新的带缺角的 L 型立方体。
- ov 将包含 1 个元素:{3, 3},即体积 3。
- ovv 包含 2 个子列表,分别对应标签 1 和标签 2 的原始实体产生的子实体。
16.3.4 创建球形夹杂并使用 fragment 共形布点
// 在 L 型立方体的对角线上均匀放置 5 个球体
double x = 0, y = 0.75, z = 0, r = 0.09;
std::vector<std::pair<int, int>> holes; // 存储 5 个球体的 dimTag
for(int t = 1; t <= 5; t++) {
x += 0.166;
z += 0.166;
gmsh::model::occ::addSphere(x, y, z, r, 3 + t); // 标签 4~8
holes.push_back({3, 3 + t}); // {维度3, 标签4} ... {维度3, 标签8}
}
// =============== 关键决策点 ===============
// 如果这里用 cut({{3,3}}, holes, ov, ovv),结果将是:
// 一个 L 型立方体,内部有 5 个球形空腔(空洞)
//
// 但我们需要的是:5 个球体作为独立的材料夹杂保留,且
// 球体表面网格与立方体网格共形(节点一一对应)
//
// 因此使用 fragment:
gmsh::model::occ::fragment({{3, 3}}, // object: L 型立方体
holes, // tools: 5 个球体
ov, ovv); // 输出参数
为什么不用 cut:cut 会删除球体,立方体上留下空洞——这适用于模拟螺钉穿过板的情况。而这里要模拟的是复合材料中的球形夹杂(inclusion),球体材料与基体不同,二者在界面上需要共形网格。fragment 将球体与立方体在相交处分割,交界面被合并为共享面,保证网格节点一一对应。
16.3.5 分析 fragment 的输出
// ov 包含 fragment 产生的所有体积实体(与输入同维度)
gmsh::logger::write("fragment produced volumes:");
for(auto e : ov)
gmsh::logger::write("(" + std::to_string(e.first) + "," +
std::to_string(e.second) + ")");
// ovv 包含父-子关系:ovv[i] 是第 i 个输入实体产生的子实体列表
// ovv[0] = 原始体积3(立方体)的子实体
// ovv[1] = holes[0](球体4)的子实体
// ovv[2] = holes[1](球体5)的子实体 ... 以此类推
gmsh::logger::write("before/after volume relations:");
std::vector<std::pair<int, int>> in(1, std::pair<int, int>(3, 3));
in.insert(in.end(), holes.begin(), holes.end());
for(std::size_t i = 0; i < in.size(); i++) {
std::string s = "parent (" + std::to_string(in[i].first) + "," +
std::to_string(in[i].second) + ") -> child";
for(std::size_t j = 0; j < ovv[i].size(); j++) {
s += " (" + std::to_string(ovv[i][j].first) + "," +
std::to_string(ovv[i][j].second) + ")";
}
gmsh::logger::write(s);
}
gmsh::logger::write(msg) 将自定义消息写入日志缓冲区。这对于查看布尔运算后的实体标签变化非常有用——你不需要在 GUI 中手工查找,日志会精确告诉你每个父子关系。
16.3.6 同步与物理组定义
// 将 OpenCASCADE 内核中的几何同步到 Gmsh 模型中
// 在所有 occ 操作完成后、物理组定义/网格生成前,必须调用此函数
gmsh::model::occ::synchronize();
synchronize() 的作用:OpenCASCADE 内核维护自己的几何数据库,synchronize() 将 occ 内核中的所有实体"拉取"到 Gmsh 的通用模型数据库中。此后才能进行物理组定义、网格生成等操作。这是一个容易遗漏但至关重要的步骤。
// 定义物理组
// 由于 OCCBooleanPreserveNumbering 选项的作用,
// 5 个球体的标签在 fragment 后被保留(4, 5, 6, 7, 8)
for(int i = 1; i <= 5; i++)
gmsh::model::addPhysicalGroup(3, {3 + i}, i); // 物理组标签 1~5
// 立方体的标签在 fragment 后会改变,需从 ov 中动态获取
// ov[0] 是 fragment 产生的第一个体积(原立方体的残体)
gmsh::model::addPhysicalGroup(3, {ov[0].second}, 10); // 物理组标签 10
这里体现了动态获取布尔输出标签的重要性:5 个球体标签得以保留(因为 OCCBooleanPreserveNumbering 默认开启),但立方体标签 不会 保留——必须从 ov 中获取。
16.3.7 实体查找技巧(一):getClosestEntities 找面
// 目标:找到模型的所有边界面中,
// 距离点 (1, 1, 0.5) 最近的 2 个面 → 即顶部面和右侧面
gmsh::model::getEntities(ov, 3); // 获取所有体积实体
std::vector<std::pair<int, int>> ov2;
gmsh::model::getBoundary(ov, ov2); // 获取这些体积的所有边界面
std::vector<double> dist, coord;
// 在所有边界面中,找到距离点(1,1,0.5)最近的 2 个面
gmsh::model::occ::getClosestEntities(1, 1, 0.5, // 查询点坐标
ov2, // 候选实体列表
ov, // 输出:最近实体 dimTag
dist, // 输出:对应的距离
coord, // 输出:实体上的最近点坐标
2); // 返回数量 N=2
// ov 现在包含 2 个 dimTag:{2, surfaceTag}, {2, surfaceTag}
gmsh::model::addPhysicalGroup(2, {ov[0].second, ov[1].second}, 100,
"Top & right surfaces");
occ::getClosestEntities(x, y, z, candidates, out, dist, coord, N) 是一个强大的几何查询工具,它对候选实体列表中的每个实体计算到查询点的最近距离,返回距离最小的 N 个实体。在上述代码中,我们首先通过 getBoundary 获取边界面的全集,然后通过 getClosestEntities 筛选出距离 (1,1,0.5) 最近的两个面——这正是顶部面(z=1)和右侧面(x=1)。
16.3.8 实体查找技巧(二):getEntitiesInBoundingBox 找点
double eps = 1e-3;
// 在包围盒中搜索:中心 (0.5, 0.5, 0.5),半边长 eps
// 这将找到 L 形立方体的凹角点(原小立方体被切位置的对角点)
gmsh::model::getEntitiesInBoundingBox(
0.5 - eps, 0.5 - eps, 0.5 - eps, // xmin, ymin, zmin
0.5 + eps, 0.5 + eps, 0.5 + eps, // xmax, ymax, zmax
ov, 0); // out: 存放找到的实体;dim=0 表示只找 0 维实体(点)
getEntitiesInBoundingBox(xmin, ymin, zmin, xmax, ymax, zmax, out, dim) 在指定的轴对齐包围盒内查找指定维度的实体。这里用于精确定位一个几何特征点(L 形拐角处)——该点附近需要较高的网格密度以捕捉几何细节。
16.3.9 网格尺寸设置与生成
double lcar1 = .1; // 全局默认尺寸
double lcar2 = .0005; // 拐角点极细尺寸
double lcar3 = .055; // 球体表面中等尺寸
// 为所有点设置默认尺寸
gmsh::model::getEntities(ov, 0); // 获取所有 0D 实体(点)
gmsh::model::mesh::setSize(ov, lcar1); // 统一设为 0.1
// 覆盖球体上的点:使用更细的尺寸
gmsh::model::getBoundary(holes, ov, false, false, true);
// ↑ ↑ ↑ ↑ ↑
// 输入 输出 合并 定向 仅返回低维
// 参数说明:
// combined=false — 不合并相邻实体
// oriented=false — 不需要方向信息
// recursive=true — 递归获取所有低维边界(包括本维度及以下)
gmsh::model::mesh::setSize(ov, lcar3); // 球体点尺寸 0.055
// 拐角点特殊加密
gmsh::model::getEntitiesInBoundingBox(0.5 - eps, 0.5 - eps, 0.5 - eps,
0.5 + eps, 0.5 + eps, 0.5 + eps,
ov, 0);
gmsh::model::mesh::setSize(ov, lcar2); // 拐角点尺寸 0.0005
gmsh::model::mesh::generate(3);
gmsh::write("t16.msh");
mesh::setSize(entities, size) 是 mesh::setSize(dimTags, size) 的重载形式,对实体列表中的所有实体施加统一的网格尺寸约束。这与第4章中通过点级 lc 参数控制尺寸本质相同,但 mesh::setSize 更加灵活——你可以对任意实体列表(点、线、面)批量设置,适用于布尔运算后不知道点标签的场景。
getBoundary 的 recursive=true 参数允许从体积递归获取所有边界点,省去了先获取面再获取线再获取点的繁琐过程。
16.3.10 日志检索与 GUI 启动
// 检索并输出日志
std::vector<std::string> log;
gmsh::logger::get(log);
std::cout << "Logger has recorded " << log.size() << " lines" << std::endl;
gmsh::logger::stop();
// 启动 GUI(可通过 -nopopup 参数禁止)
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
日志系统 API:
- gmsh::logger::start() — 开始记录
- gmsh::logger::write(msg) — 写入一条消息
- gmsh::logger::get(log) — 获取所有已记录的消息
- gmsh::logger::stop() — 停止记录
16.4 完整可运行代码
// ch16_occ_csg.cpp — OpenCASCADE 内核与 CSG 布尔运算
// 编译: g++ -o ch16_occ_csg ch16_occ_csg.cpp -lgmsh
#include <set>
#include <iostream>
#include <gmsh.h>
int main(int argc, char **argv)
{
gmsh::initialize(argc, argv);
gmsh::model::add("ch16");
// ---- 1. 启动日志 ----
gmsh::logger::start();
// ---- 2. 创建基本体(OpenCASCADE 一步完成) ----
try {
// addBox(x, y, z, dx, dy, dz, tag)
gmsh::model::occ::addBox(0, 0, 0, 1, 1, 1, 1); // 标签1: 单位立方体
gmsh::model::occ::addBox(0, 0, 0, 0.5, 0.5, 0.5, 2); // 标签2: 1/8角小立方体
} catch(...) {
gmsh::logger::write("Could not create OpenCASCADE shapes!");
return 0;
}
// ---- 3. 布尔差:挖去八分之一角 ----
std::vector<std::pair<int, int>> ov;
std::vector<std::vector<std::pair<int, int>>> ovv;
// cut: 立方体1 - 小立方体2, 结果标签设为3
gmsh::model::occ::cut({{3, 1}}, {{3, 2}}, ov, ovv, 3);
// ---- 4. 创建 5 个球体(球形夹杂) ----
double x = 0, y = 0.75, z = 0, r = 0.09;
std::vector<std::pair<int, int>> holes;
for(int t = 1; t <= 5; t++) {
x += 0.166;
z += 0.166;
gmsh::model::occ::addSphere(x, y, z, r, 3 + t);
holes.push_back({3, 3 + t});
}
// ---- 5. 共形布尔分片:保留球体 + 保证共形界面 ----
gmsh::model::occ::fragment({{3, 3}}, holes, ov, ovv);
// 打印 fragment 的输出信息
gmsh::logger::write("fragment produced volumes:");
for(auto e : ov)
gmsh::logger::write(" (" + std::to_string(e.first) + ","
+ std::to_string(e.second) + ")");
// ---- 6. 同步几何 ----
gmsh::model::occ::synchronize();
// ---- 7. 定义物理组 ----
// 球体标签 4~8 在 fragment 后保留(OCCBooleanPreserveNumbering)
for(int i = 1; i <= 5; i++)
gmsh::model::addPhysicalGroup(3, {3 + i}, i);
// 立方体标签变化,从 ov[0] 动态获取
gmsh::model::addPhysicalGroup(3, {ov[0].second}, 10);
// ---- 8. 实体查找:找到顶部面和右侧面 ----
gmsh::model::getEntities(ov, 3); // 所有体积
std::vector<std::pair<int, int>> ov2;
gmsh::model::getBoundary(ov, ov2); // 所有边界面
std::vector<double> dist, coord;
gmsh::model::occ::getClosestEntities(1, 1, 0.5, // 查询点
ov2, // 候选集合
ov, // 输出: 最近2个面
dist, coord, 2);
gmsh::model::addPhysicalGroup(2, {ov[0].second, ov[1].second},
100, "Top & right surfaces");
// ---- 9. 设置网格尺寸 ----
double lcar1 = .1; // 全局默认
double lcar2 = .0005; // 拐角加密
double lcar3 = .055; // 球体表面
// 所有点默认尺寸
gmsh::model::getEntities(ov, 0);
gmsh::model::mesh::setSize(ov, lcar1);
// 球体上的点用中等尺寸覆盖
gmsh::model::getBoundary(holes, ov, false, false, true);
gmsh::model::mesh::setSize(ov, lcar3);
// 拐角点特殊加密(包围盒查找)
double eps = 1e-3;
gmsh::model::getEntitiesInBoundingBox(
0.5 - eps, 0.5 - eps, 0.5 - eps,
0.5 + eps, 0.5 + eps, 0.5 + eps, ov, 0);
gmsh::model::mesh::setSize(ov, lcar2);
// ---- 10. 生成网格 ----
gmsh::model::mesh::generate(3);
gmsh::write("ch16_occ_csg.msh");
// ---- 11. 检索日志 ----
std::vector<std::string> log;
gmsh::logger::get(log);
std::cout << "Logger has recorded " << log.size() << " lines" << std::endl;
gmsh::logger::stop();
// ---- 12. GUI ----
std::set<std::string> args(argv, argv + argc);
if(!args.count("-nopopup")) gmsh::fltk::run();
gmsh::finalize();
return 0;
}
16.5 关键 API 速查表
16.5.1 基本体创建
| API | 签名 | 说明 |
|---|---|---|
occ::addBox |
(x,y,z,dx,dy,dz,tag) |
轴对齐长方体 |
occ::addSphere |
(xc,yc,zc,r,tag) |
球体 |
occ::addCylinder |
(xc,yc,zc,dx,dy,dz,r,tag) |
圆柱体(任意轴向) |
occ::addCone |
(xc,yc,zc,dx,dy,dz,r1,r2,tag) |
圆台/圆锥(r2=0) |
occ::addTorus |
(xc,yc,zc,r1,r2,tag) |
圆环体(r1 主半径, r2 管半径) |
occ::addWedge |
(x,y,z,dx,dy,dz,tag) |
楔形体 |
occ::addRectangle |
(x,y,z,dx,dy,tag) |
二维矩形面 |
occ::addDisk |
(xc,yc,zc,rx,ry,tag) |
二维圆/椭圆盘 |
occ::addPlaneSurface |
(wireTags,tag) |
由线框生成填充面 |
16.5.2 布尔运算
| API | 签名 | 说明 |
|---|---|---|
occ::cut |
(objDimTags,toolDimTags,out,outMap,tag,removeObj,removeTool) |
差运算 A - B |
occ::fuse |
(objDimTags,toolDimTags,out,outMap,tag,removeObj,removeTool) |
并运算 A + B |
occ::intersect |
(objDimTags,toolDimTags,out,outMap,tag,removeObj,removeTool) |
交运算 A 交 B |
occ::fragment |
(objDimTags,toolDimTags,out,outMap,tag,removeObj,removeTool) |
共形布尔分片 |
16.5.3 实体查询
| API | 说明 |
|---|---|
getEntities(out, dim) |
获取指定维度的所有实体 |
getBoundary(in, out, combined, oriented, recursive) |
获取边界实体。recursive=true 递归获取所有低维边界 |
getEntitiesInBoundingBox(xmin,ymin,zmin,xmax,ymax,zmax,out,dim) |
包围盒查找指定维度实体 |
occ::getClosestEntities(x,y,z,candidates,out,dist,coord,N) |
在候选列表中找距离(x,y,z)最近的 N 个实体 |
16.5.4 网格尺寸
| API | 说明 |
|---|---|
mesh::setSize(dimTags, size) |
对实体列表设置统一网格尺寸 |
mesh::setSizeAtParametricPoints(dim, tag, parametricCoord, sizes) |
对指定实体的参数化坐标设置变尺寸(与 Transfinite 配合) |
16.5.5 日志
| API | 说明 |
|---|---|
logger::start() |
开始记录日志 |
logger::write(msg) |
写入一条日志消息 |
logger::get(log) |
获取所有日志条目 |
logger::stop() |
停止日志记录 |
16.6 注意事项
-
必须 synchronize:在所有 OCC 操作完成后、模型查询或网格生成前,必须调用
occ::synchronize()。忘记此调用将导致物理组定义找不到实体或网格生成失败。 -
标签不稳定的问题:不要假设布尔运算后的实体标签。始终通过
out参数或实体查询函数动态获取标签。OCCBooleanPreserveNumbering选项提供的标签保留行为不是普遍保证的。 -
fragment 的性能考虑:
fragment需要在所有交界面处进行精确的面分割和缝合,计算成本高于cut或fuse。仅在确实需要共形界面(多材料)时使用fragment,简单减操作使用cut。 -
实体维度一致性:
{3, tag}中的维度号必须与实体的实际维度匹配。不能将 2D 面传入期望 3D 体积的布尔运算参数中。 -
removeObject/removeTool 的默认值:注意默认均为
true,意味着原始实体会被删除。如需保留原始实体(如作为后续操作的参考),显式传入false。 -
try-catch 保护:OCC 操作可能因为自相交几何、精度问题等原因抛出异常。对于关键操作使用
try-catch包裹,避免程序直接崩溃。 -
扩展阅读:
t18.cpp、t19.cpp、t20.cpp提供了更多 OCC 高级用法示例(螺纹、倒角、STEP 导入等),建议在掌握本章内容后继续学习。
16.7 与 built-in 内核的代码量对比
以下是使用两种内核创建相同模型(L 形立方体 + 5 个球形夹杂)的代码对比:
| 操作 | built-in 内核(第5章风格) | OpenCASCADE 内核(本章) |
|---|---|---|
| 创建立方体 | 8 点 + 12 线 + 6 面 + 1 体 ≈ 30+ 行 | addBox(...) 1 行 |
| 挖去一角 | 需手动构建边界再拼接实体 | cut(...) 1 行 |
| 5 个球体 | 无法直接创建(需逐面构建或导入) | addSphere(...) 循环 5 行 |
| 共形界面 | 极度繁琐(手动构建接触面拓扑) | fragment(...) 1 行 |
| 总计 | 100+ 行 | 约 50 行 |
这个对比清楚地展示了 OpenCASCADE 内核在复杂几何建模中的压倒性优势。对于 CAE 前处理工程师而言,掌握 OCC 内核和 CSG 方法是进入复杂模型处理的门槛技能。