1. 问题现象与背景
1.1 从一次“卡死”说起
上个月我在处理一批STEP格式的CAD模型,目的是给CAE前处理做几何简化,把那些从原始设计软件里导出来的碎面、重合边清理掉。程序跑了一整夜,第二天早上到公司一看,8核的编译机CPU占用率100%,但输出目录里只有一半的结果文件。一开始我以为是模型太大导致计算慢,后来手动跑到那个卡住的模型——一个只有两千多个面的小零件——程序直接无响应,Ctrl+C都杀不掉,只能强杀进程。
经过逐行注释和二分定位,问题出在ShapeUpgrade_UnifySameDomain这个调用上。这个类是OpenCASCADE里做“同域统一”的标准工具,我对它并不陌生,但从来没想过它会死循环。后来查资料、做实验、反反复复调整参数,前后折腾了两三天才彻底解决。今天把这套完整的定位思路和处理方案写下来,给还在坑里的朋友一个参考。
先说结论:ShapeUpgrade_UnifySameDomain在默认参数下确实存在因特定几何缺陷导致无法收敛的情况,处理不当就会让程序陷入死循环。这既不是OpenCASCADE完全没修过的bug,也不是简单“换个版本”就能绕开的问题,需要从输入数据、参数配置、调用方式三个层面同时下手。
1.2 这个类到底在做什么
ShapeUpgrade_UnifySameDomain是OpenCASCADE的模型修复工具集ShapeUpgrade下的一个重要类。它的核心功能是:
- 合并共面的相邻面(unifyFaces),把那些本来在一个平面上却被切割成多个碎片的face合成一个;
- 移除重合边(unifyEdges),把同一条几何边上的多个edge段合并,或者去掉被重复定义的边;
- 处理退化边(degenerate edge),比如圆锥顶点处收缩成点的边。
实际应用中这个类非常有用。CAD软件导出的模型经常带大量碎面,每个碎面之间有内部边。有限元网格划分、CFD前处理、碰撞检测简化、轻量化可视化等环节都希望几何尽量干净,所以做一次同域统一是很有必要的。在OpenCASCADE 6.9.0之后这个类才被引入,之后成了模型清理管线的标配工具。
但问题恰恰出在“太常用”上。很多人在自己的代码里一行ShapeUpgrade_UnifySameDomain aUnifier(theShape); TopoDS_Shape result = aUnifier.Shape();就完事了,没做过输入检查,也没考虑过极端几何。一旦模型里有某些特殊瑕疵,这个类就可能永远“转”不出来。
2. ShapeUpgrade_UnifySameDomain技术拆解:算法原理与死循环风险
2.1 内部算法流程与真实代价
要理解死循环,先得知道这个类内部是怎么工作的。ShapeUpgrade_UnifySameDomain并不仅仅是简单的几何遍历和合并,它内部会构建BOP(Boolean Operations)数据结构的扩展版本,也就是BOPAlgo系列的那套东西。
当unifyFaces开启时,算法会做以下几件事:
- 收集模型中的所有face,按几何类型(平面、圆柱面、球面、Bezier曲面等)和位置关系进行分组;
- 对每一组共面或同几何的face,计算它们的边界edge之间的拓扑关系;
- 通过求交、比较容差、判断方向等几何操作,确定哪些edge可以合并、哪些face可以融合;
- 删除内部edge,重建face的线框和曲面数据结构;
- 最后把新shape与原始shape做一次映射关系更新,保证历史记录可用。
这一步走的是完整的BOP流程,非常重。计算过程中会大量调用求交器(IntTools)、曲面逼近(ApproxSurface)和容差校验。
这里要强调一个很多人忽略的点:unifyFaces=true时的计算量远远大于unifyEdges=true。因为面合并要对整个face的边界做重新求交和拓扑重建,而不仅仅是比较两条边是否重合。实测下来,同一个模型,仅开启unifyEdges的耗时可能只要几十毫秒,但开启unifyFaces后可能直接涨到几秒甚至几十秒。而一旦几何数据存在局部退化,这个时间就不是“涨”的问题了,是“永远不结束”的问题。
2.2 最容易触发死循环的几何特征
踩过坑之后,我把我手头能复现死循环的模型都过了一遍,发现它们都有几个共同特征。
第一类是退化边。什么意思?就是一条边的两个顶点在几何位置上完全重合,或者距离小于模型精度。最典型的例子是圆锥面的顶点处,曲面收缩成一个点,拓扑上却可能定义了一条极短的边上,这种边本身没有实际几何意义,但对算法来说是合法的edge。ShapeUpgrade_UnifySameDomain内部在处理这类退化边时,如果同时要合并相邻face,就可能在一个“判断这条边是否应该删除”的循环里反复横跳。
第二类是极度细长的face。当一个face的长宽比达到几千甚至上万,比如一个宽度0.001毫米、长度100毫米的窄条面,算法在计算边界edge的共线性和重合度时,容差判断会变得极不稳定。因为它内部默认的精度是Precision::Confusion(),通常是1e-7。细长面会导致几何计算的中间结果落在容差边界附近,一旦判断结果在“是”与“否”之间抖动,循环就无法收敛。
第三类是自相交或间隙过小的几何组合。有些模型虽然通过了CAD软件的导出检查,但面与面之间的间隙只有1e-9量级,小于默认容差但又不是真正的重合。这种情况下,BOP的求交器会反复重试,每次重试都发现“差点就重合了”,然后再次尝试,进入死循环。
注意:不要以为只有导入的STEP模型才有这些问题。用OpenCASCADE自己的建模API构造模型,如果中间做过布尔运算、倒角、延伸之类的操作,也可能产生退化几何。
2.3 已知版本表现与社区反馈
在OpenCASCADE社区里,关于ShapeUpgrade_UnifySameDomain死循环或卡死的讨论并不少。从6.9.0到后面的6.9.1、7.4.0、7.5.0、7.6.0、7.7.0,都有人报告不同场景下的卡死问题。有些修复在后续版本中生效,但很多case是换了新版本依旧存在。
我自己的测试环境是7.5.0和7.7.0都有复现。说明这个问题不是某个特定版本的临时bug,而是算法设计本身在某些几何输入下无法收敛。把这个锅完全甩给API是不合适的,使用者必须自己做好防御性编程。
3. 问题定位:从现象到根因的排查过程
3.1 第一步:锁定参数组合
遇到死循环第一时间不要怀疑算法,先怀疑自己。我在定位过程中做的第一步是用二分法确定是哪个参数导致的。
ShapeUpgrade_UnifySameDomain的构造函数是:
ShapeUpgrade_UnifySameDomain(const TopoDS_Shape& aShape, const Standard_Boolean unifyEdges = Standard_True, const Standard_Boolean unifyFaces = Standard_True);还有一个常用的方法:
void SetAllowInternalEdges(const Standard_Boolean theMode); void SetKeepShapes(const TopoDS_Shape& theShape);我分别测了三种组合:
| 参数组合 | 结果 |
|---|---|
| unifyEdges=false, unifyFaces=false | 所有模型都能通过,不死循环 |
| unifyEdges=true, unifyFaces=false | 大部分模型通过,个别模型耗时明显增加但能结束 |
| unifyEdges=true, unifyFaces=true | 特定模型死循环 |
这组实验很清晰:问题集中在unifyFaces=true的分支上。进一步测试发现,如果只开unifyFaces=true而关闭unifyEdges,死循环问题依旧存在,只是触发概率略低。说明核心风险点是面统一这个分支。
3.2 第二步:用DRAW快速复现
直接改C++代码做实验太慢了,每次编译都要几分钟。我很快改用OpenCASCADE自带的DRAW Test Harness来复现。DRAW是OpenCASCADE的交互式命令行环境,可以用Tcl脚本直接调用大部分核心API,非常适合做这种问题复现和参数验证。
复现脚本大概长这样:
# 加载模型 pload XDE ReadStep model.step result # 设置unifySameDomain命令的参数 # 语法: unifysamedomain result [unifyEdges] [unifyFaces] [allowInternalEdges] unifysamedomain result 1 1 0在DRAW里跑这个命令,模型会卡住不动,命令行失去响应。但DRAW的好处是它是单进程的,卡住了可以直接Ctrl+C杀掉重来,不用等C++重新编译。而且DRAW还提供了一些辅助命令检查模型,比如:
# 检查shape的有效性 checkshape result # 统计face/edge数量 tolerance result通过checkshape我发现,出问题的模型都报了大量SelfIntersection和BadOrientation的警告。这给我提了个醒:死循环不是凭空发生的,模型本身就带有某些“预置缺陷”,只是平时这些缺陷不影响显示,也不影响简单的拓扑遍历,但一旦进入BOP的求交流程,就会触发异常分支。
提示:遇到UnifySameDomain相关的问题,强烈建议先在DRAW里复现,不要一上来就改C++代码。DRAW的Tcl脚本可以让你在几秒内试完所有参数组合,效率高得多。
3.3 第三步:根因归纳
综合DRAW里做的切分实验和源码层面的分析,我把死循环的根因归纳为三类:
第一类是几何精度冲突。模型的最小几何特征小于Precision::Confusion(),导致算法在判断“两条边是否重合”“两个面是否共面”时结果不稳定。这不是ShapeUpgrade_UnifySameDomain独有的问题,整个BOP大家族都有类似毛病。
第二类是退化元素导致的循环不收敛。我追踪了部分源码的执行路径,发现问题往往出现在处理列表迭代的时候:当某个face被判定为“可以合并”后,算法会把它从待处理列表里移除,但如果有退化元素干扰了移除逻辑,列表永远“处理不完”,循环就出不去了。
第三类是复杂曲面上的数据不一致。当face的几何曲面与边界边的3D曲线不一致时,算法会尝试通过重新逼近来修正。逼近过程需要多次迭代,如果每次迭代的误差都无法收敛到设定阈值,就会一直逼近下去,表现为死循环。
这三种情况往往是叠加的:退化边带来精度冲突,精度冲突导致数据不一致,数据不一致让迭代不收敛。所以单纯靠调一个参数很难根治,必须系统地处理。
4. 解决方案:四层防护体系
4.1 方案一:输入预处理,把脏数据挡在门外
既然模型自带问题,那就先在模型上做清理,别让ShapeUpgrade_UnifySameDomain直接面对脏几何。
我最终采用的预处理流程是:
- 先跑一遍
ShapeFix_Shape,把基本的拓扑问题修复掉; - 手工清除退化边(长度小于
Precision::Confusion()的边); - 对极小面(面积小于阈值的face)做特殊标记,后续统一处理;
- 用
BRepCheck_Analyzer检查修复结果,不合格的模型直接走替代方案,不再进入UnifySameDomain流程。
代码如下:
#include <ShapeFix_Shape.hxx> #include <BRepCheck_Analyzer.hxx> #include <TopExp_Explorer.hxx> #include <BRep_Tool.hxx> #include <TopoDS.hxx> #include <TopoDS_Edge.hxx> #include <BRepAdaptor_Curve.hxx> #include <Precision.hxx> TopoDS_Shape PreprocessShape(const TopoDS_Shape& inputShape) { // Step 1: 基本修复 Handle(ShapeFix_Shape) aFixer = new ShapeFix_Shape(inputShape); aFixer->SetPrecision(Precision::Confusion()); aFixer->SetMaxTolerance(1.0); aFixer->Perform(); TopoDS_Shape fixedShape = aFixer->Shape(); // Step 2: 遍历并标记退化边 int removeCount = 0; for (TopExp_Explorer exp(fixedShape, TopAbs_EDGE); exp.More(); exp.Next()) { const TopoDS_Edge& edge = TopoDS::Edge(exp.Current()); BRepAdaptor_Curve curve(edge); if (curve.FirstParameter() != curve.LastParameter()) { Standard_Real len = BRepAdaptor_Curve(edge).LastParameter() - BRepAdaptor_Curve(edge).FirstParameter(); if (len < Precision::Confusion()) { // 记录这条边需要移除 removeCount++; } } } // 注意:这里只做了检测和计数,实际项目中需要结合ShapeFix_Wire等工具 // 把退化边从所属wire中移除,再重建face return fixedShape; }这里有个细节要注意:ShapeFix_Shape不是万能的。它擅长修复的是wire不闭合、edge方向不一致、face法向翻转这类拓扑问题。对于极细长、自相交这种几何层面的问题,它很多时候也无能为力。所以预处理之后仍然要接BRepCheck_Analyzer做验证。
4.2 方案二:合理的参数组合,降低收敛难度
ShapeUpgrade_UnifySameDomain不是参数越多越强,默认参数也不是万能药。关键参数有这几个:
unifyEdges:是否合并重合边;unifyFaces:是否合并共面面;SetAllowInternalEdges(Standard_False):合并后是否允许内部边保留。
经过反复测试,我的建议是:除非明确需要做面合并,否则把 unifyFaces 关掉。如果只是清理重叠边、消除微小边,只开unifyEdges=true就够了,速度快不说,基本不会死循环。
如果业务上确实需要合并共面面,那不要一次性处理整个模型,而是分步骤处理。
TopoDS_Shape UnifyShapeSafely(const TopoDS_Shape& inputShape) { // 第一步:先合并边,不做face合并 ShapeUpgrade_UnifySameDomain edgeUnifier(inputShape, Standard_True, Standard_False); edgeUnifier.SetAllowInternalEdges(Standard_True); TopoDS_Shape afterEdgeUnify = edgeUnifier.Shape(); // 第二步:在边合并结果基础上做face合并 ShapeUpgrade_UnifySameDomain faceUnifier(afterEdgeUnify, Standard_False, Standard_True); faceUnifier.SetAllowInternalEdges(Standard_False); return faceUnifier.Shape(); }分步操作的最大好处是:每一步的输入输出都是可控的,即使第二步死循环,第一步的结果也已经拿到了。同时因为第一步已经清理了重合边,第二步面对的数据会干净很多,收敛概率大增。
SetAllowInternalEdges的参数选择也很微妙。允许内部边存在,算法就不需要把共面面完全融合,计算量大幅降低;不允许内部边存在,则要做完整的拓扑重建,死循环风险成倍增加。我的经验是:如果对最终结果要求不极端,建议保持Standard_True。
// 推荐的低风险配置 ShapeUpgrade_UnifySameDomain unifier(shape, Standard_True, Standard_True); unifier.SetAllowInternalEdges(Standard_True); // 宁可保留内部边,也要保证程序不卡死4.3 方案三:超时保护,给死循环一个“熔断开关”
无论前面怎么做防御,都无法100%保证不触发死循环。几何数据走到BOP里,很多时候是不可预知的。所以必须从工程架构上做最后一层保护:线程超时。
思路很简单:把ShapeUpgrade_UnifySameDomain的调用放到一个后台线程里,主线程等待一个超时时间。如果超时了还不返回,就放弃这个模型的这次清理,记录下来走降级路径。
#include <future> #include <thread> #include <chrono> std::future<TopoDS_Shape> RunUnifyAsync(const TopoDS_Shape& shape) { return std::async(std::launch::async, [shape]() { ShapeUpgrade_UnifySameDomain unifier(shape, Standard_True, Standard_True); unifier.SetAllowInternalEdges(Standard_True); return unifier.Shape(); }); } bool TryUnifyWithTimeout(const TopoDS_Shape& inputShape, TopoDS_Shape& outputShape, std::chrono::seconds timeout) { std::future<TopoDS_Shape> future = RunUnifyAsync(inputShape); std::future_status status = future.wait_for(timeout); if (status == std::future_status::ready) { outputShape = future.get(); return true; } // 超时,放弃当前线程(detach策略下线程会继续跑,但不再影响主流程) return false; }注意:这里有个现实问题。用std::async启的线程,如果超时时直接放弃等待,后台线程还在继续跑,CPU占用率不会立刻降下来。这在长时间批量处理中是个隐患,后台会堆积越来越多的僵尸线程。
更严谨的做法是用std::thread配合显式的线程管理,或者用专门的线程池把超时任务杀掉。但在Windows和Linux上“强杀线程”本身就是个危险动作,OpenCASCADE内部有大量堆内存分配,杀掉一半的线程容易造成内存泄漏。
所以更加实用的方案是:超时后把整个进程退出,然后重新拉起一个新的worker进程处理下一个模型。进程间的隔离让死循环任务无法拖垮整个批量处理流程。
// 伪代码示意:进程级隔离 int ProcessOneModel(const std::string& stepFile) { pid_t pid = fork(); if (pid == 0) { // 子进程:执行UnifySameDomain TopoDS_Shape shape = ReadStep(stepFile); ShapeUpgrade_UnifySameDomain unifier(shape, true, true); TopoDS_Shape result = unifier.Shape(); WriteResult(result); exit(0); } else { // 父进程:超时检测 int status; alarm(30); // 30秒超时 waitpid(pid, &status, 0); if (WIFSIGNALED(status)) { // 子进程超时被杀,记录异常,继续下一个 LogError(stepFile, "timeout"); return -1; } return 0; } }4.4 方案四:替代方案,绕开UnifySameDomain
有些场景下,其实不需要ShapeUpgrade_UnifySameDomain这么重的工具。如果目的只是去除重合边、清理多余面,可以考虑这些替代思路:
- 用
BRepAlgoAPI_Fuse把同面的面做一个union,效果和unifyFaces=true类似,但可控性更强; - 用
ShapeFix_Wire手动清理每条wire里的退化边; - 用
BRepBuilderAPI_Sewing做缝补,能解决一部分重合边问题; - 对纯平面模型,可以自己遍历face,判断几何平面是否相同,手动合并。
这些方法胜在可控,但代码量会多一些。我的建议是优先级排序:超时保护 > 分步Unify > 预清理 + Unify > 替代方案。把替代方案作为最后的兜底比较好。
5. 工程落地与性能影响分析
5.1 实际批量处理的效果
用了上面这些方案之后,我重新跑了一遍之前卡死的模型集,总共20782个STEP文件。在8核16线程的机器上,批量处理的耗时分布如下:
| 配置 | 总耗时 | 失败/超时数 | 平均单模型耗时 |
|---|---|---|---|
| 原始方案(直接Unify,无保护) | 卡死,未完成 | 12个卡死 | 不稳定 |
| 仅开启unifyEdges | 约3小时 | 0 | 约0.5秒 |
| unifyEdges + unifyFaces(分步) | 约6小时 | 2个超时 | 约1.1秒 |
| 完整方案(预清理 + 分步 + 超时) | 约6.5小时 | 0 | 约1.2秒 |
完整方案里,打开超时保护之后彻底杜绝了卡死。非常有意思的是,原来卡死的12个模型,在预清理之后有10个能正常跑完,只有2个模型还需要走超时降级。说明大部分情况下的死循环确实可以靠输入清理来解决。
5.2 结果质量评估
光跑得快还不够,结果质量必须验证。我主要做了三方面的检查:
- 连通性与拓扑有效性:用
BRepCheck_Analyzer检查分步处理后的shape,确保没有破面、缝隙、错误方向的面; - 体积保持:对实体模型,比较处理前后的体积。同一实体在清理前后体积差一般在0.1%以内,超过这个值说明有面被错误合并了;
- 边界保持:对于需要保留特定边界的场景(比如螺栓孔、定位槽),用
SetKeepShapes把这些特征保护起来,处理完再验证特征是否存在。
SetKeepShapes是一个很好用的方法。它可以接收一个TopoDS_Shape表示需要保留的子形状。我在处理带孔模型时,会先把孔的面或边提取出来,传入这个方法。
// 提取孔边并保护 TopTools_ListOfShape keepShapes; for (TopExp_Explorer exp(shape, TopAbs_EDGE); exp.More(); exp.Next()) { const TopoDS_Edge& edge = TopoDS::Edge(exp.Current()); // 判断是否为圆弧边(孔边大多数是圆弧) BRepAdaptor_Curve curve(edge); if (curve.GetType() == GeomAbs_Circle) { keepShapes.Append(edge); } } ShapeUpgrade_UnifySameDomain unifier(shape, Standard_True, Standard_True); unifier.SetKeepShapes(keepShapes); TopoDS_Shape result = unifier.Shape();用了这个保护之后,孔特征在面合并过程中不会被算法“顺手”抹掉。这个细节对于有装配关系或定位特征的零件来说特别重要。
5.3 对OpenCASCADE版本的选型建议
我的经验是,OpenCASCADE版本选择对这个问题的影响不容忽视。7.6.0和7.7.0在BOP和ShapeUpgrade这块都有不少修复,但并没有彻底解决所有死循环问题。官方在7.8.0之后把更多精力放到了BOP重写上,相关的稳定性质疑反而在增多。
对于生产环境,我倾向于推荐长期维护的LTS风格版本,配合前文说的防御性编程。而不是盲目追求最新版本,毕竟OpenCASCADE的API兼容性虽然好,但新版本经常引入新的几何处理逻辑,需要重新做回归测试。
如果你们项目用的是自定义编译的OpenCASCADE,建议把ShapeUpgrade_UnifySameDomain用到的模块(ShapeUpgrade、BOPAlgo、IntTools等)单独拉出来,用自己的回归模型集做一轮压力测试,比官方单元测试要靠谱得多。
6. 常见问题与避坑经验补充
6.1 问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 程序直接卡死,CPU 100% | unifyFaces=true 时遭遇退化边或精度冲突 | 预清理 + 分步Unify + 超时保护 |
| 程序不卡但耗时暴涨 | 大量细微面触发了面合并的求交计算 | 提高容差,设置合理面合并范围 |
| 处理结果出现丢面、破洞 | SetAllowInternalEdges设置不当,导致内部边被强行删除 | 尝试改为 true |
| 部分特征被无故删除 | 没有设置SetKeepShapes保护边界 | 提取关键特征边/面并传入 |
| Debug版本正常,Release版本卡死 | 编译器优化暴露浮点判断问题 | 在Release下跑回归测试,注意容差一致性 |
| 批量处理时堆积大量线程 | 超时后std::async线程未回收 | 考虑进程级隔离,或自定义线程池 |
| 模型换电脑后出现不同表现 | 不同平台浮点结果有细微差异 | 统一编译选项,使用固定容差配置 |
6.2 独家避坑技巧
第一个技巧:批量处理前先用DRAW做全量预筛。不要直接拿着C++程序跑几千个模型,先用脚本化Tcl把每个模型过一遍unifysamedomain,设置较短的执行时间,把可疑模型全部筛出来。这个预筛步骤可以节省大量无效计算时间。
第二个技巧:统一容差策略。ShapeUpgrade_UnifySameDomain内部用的是默认容差,但你可以在预处理阶段把模型的容差统一到一个合理的量级。比如从STEP读进来的面,tolerance可能分布在1e-6到1e-3之间,不够统一。建议先跑一遍ShapeFix_Shape把所有face的tolerance都归一到1e-4量级,再进入Unify。
第三个技巧:不要在一个大shape上直接Unify。如果一个装配体有几十个独立零件,先把装配体拆成零件,逐个Unify之后再拼回去。因为装配体内部不同零件的间隙、干涉情况非常复杂,整体Unify很容易触发边界判断的死循环。拆开了之后,单个零件的几何往往简单得多,死循环概率大幅下降。
第四个技巧:开启内存控制开关。在长时间批处理场景下,每次调用ShapeUpgrade_UnifySameDomain都可能分配大量内存,如果不控制,内存碎片会越来越多。建议在main函数里定期检查进程内存占用,超过阈值就重启进程,这是最简单的内存释放方案。
6.3 在DRAW中做周期性回归
我在项目里放了一个小的回归脚本,每次升级OpenCASCADE版本之后跑一遍,确保新版本没有引入新的回归问题。脚本核心就几行:
# 回归测试脚本: 遍历模型目录,逐个测试UnifySameDomain foreach modelFile [glob /data/models/*.step] { puts "Testing $modelFile" catch { ReadStep $modelFile s unifysamedomain s 1 0 checkshape s } msg puts "Result: $msg" }通过这个脚本,我在一次从7.5.0升级到7.6.0时,提前发现了某个类型的模型在新版本上行为不一致的回归问题。建议所有重度使用OpenCASCADE的团队都建立自己专属的回归模型集,模型数量不用多,100个左右能覆盖典型场景就够了。
6.4 关于社区和文档资源
如果你在OpenCASCADE的文档和示例代码里找不到答案,有几个方向可以深挖:
- OpenCASCADE源码里的
samples目录,特别是Tcl示例脚本,往往有意外惊喜; - STEP Models Repository社区有大量带问题的真实模型,可以拿来测试你的防御逻辑;
- OpenCASCADE论坛上搜
UnifySameDomain和hang或infinite loop,能翻到一些老帖,虽然不一定有最终答案,但描述的问题症状很值得参考。
对于这类偏底层、偏冷门的问题,指望现成答案不现实,核心还是自己掌握一套定位问题的方法论。
7. 最后的几点体会
现在我的代码库里,任何一次ShapeUpgrade_UnifySameDomain的调用都必然伴随三层防护:DRAW预检、分步调用、进程级超时。每次处理模型前还会先跑一遍BRepCheck_Analyzer,不合格的直接送进修复管线。这套流程虽然看起来冗余,但正式跑了几个月,再也没出现过卡死。
我个人的感受是,OpenCASCADE是一个功能非常强大但“脾气”很大的库,尤其是BOP系列算法,对输入数据的质量极其敏感。你永远要假设用户给的模型可能不完美、可能有极端情况,然后在调用层做好防守。所谓“稳定地处理不稳定输入”,这本身就是CAD内核二次开发工程师的核心能力之一。