Technical note
OCCT 常用模型修复接口梳理:ShapeFix、Sewing 与 Filling
整理 CAD/CAE 几何修复中常见的 OCCT 接口,包括 BRepCheck、ShapeFix、BRepBuilderAPI_Sewing、ShapeAnalysis_FreeBounds 和 BRepFill_Filling,并记录它们在工程使用中的边界。
背景:模型修复不能只靠一个 API
做 CAD/CAE 几何修复时,很容易有一个误区:看到模型导入后存在自由边、开口壳、坏 wire 或局部面缝隙,就希望找到一个“万能修复接口”,把 shape 丢进去,然后得到一个干净、闭合、可网格化的模型。
实际工程里基本不会这么简单。
OCCT 确实提供了不少修复相关工具,比如 BRepCheck_Analyzer、ShapeFix_Shape、ShapeFix_Face、ShapeFix_Wire、BRepBuilderAPI_Sewing、ShapeAnalysis_FreeBounds、BRepFill_Filling 等。
但这些接口解决的问题层次并不一样:
- 有的适合做基础有效性检查;
- 有的适合做轻量修复;
- 有的适合修 face 或 wire;
- 有的适合把已有面缝起来;
- 有的适合根据边界补一张新面。
如果把它们都简单归类为“修复 API”,代码很容易变成一串黑盒调用:
checkShape();
fixShape();
sewShape();
fillShape();
checkAgain();
流程看起来完整,但几个关键问题其实没有回答:
每一步到底修了什么?
有没有改变原始拓扑语义?
失败后要不要回滚?
修复结果能不能进入后续网格流程?
用户怎么知道哪里被修过?
所以这篇文章不想写成 API 手册,而是从工程使用角度梳理一下:这些 OCCT 修复接口分别适合解决什么问题,以及它们的边界在哪里。
BRepCheck_Analyzer:先判断问题是否真的存在
在真正修复之前,我一般会先做检查,而不是直接修。
OCCT 里常见的基础检查工具是 BRepCheck_Analyzer。它可以用来判断一个 TopoDS_Shape 在 OCCT 的基础规则下是否有效。
典型用法大致是:
BRepCheck_Analyzer analyzer(shape);
if (!analyzer.IsValid()) {
// shape 存在基础有效性问题
}
这一步看起来很普通,但在工程里很重要。因为很多修复流程的第一个问题不是“怎么修”,而是“这个问题是否真的存在”。
有些模型并不是一个理想 solid,但对当前流程可能已经够用。例如只用于显示、测量或局部选择时,它不一定需要被强制修成闭合实体。
反过来,也有一些模型能通过 BRepCheck_Analyzer,但后续在布尔、网格、共形处理或接触识别时仍然会暴露问题。
所以我更倾向于把 BRepCheck_Analyzer 理解成基础体检工具,而不是完整质量评估工具。
它适合回答的问题是:
这个 shape 在 OCCT 基础规则下是否有效;
某些 face、wire、edge 是否存在明显拓扑问题;
修复前后,基础有效性有没有变差。
它不适合回答的问题是:
这个模型是否适合 CAE 网格;
两个面之间是否存在近接触风险;
局部小缝隙是否会影响后续共形;
模型语义是否被修复流程改变。
这也是后来做几何修复模块时,我更倾向于把“检查”单独作为一层,而不是在每个修复函数里随手塞一个 IsValid() 就结束。
ShapeFix_Shape:适合作为轻量修复入口
ShapeFix_Shape 是很多人接触 OCCT 修复时最先用到的接口。它的优势是入口统一,可以对一个 shape 做一轮基础修复。
典型流程大概是:
Handle(ShapeFix_Shape) fixer = new ShapeFix_Shape(shape);
fixer->SetPrecision(precision);
fixer->SetMaxTolerance(maxTolerance);
fixer->Perform();
TopoDS_Shape fixedShape = fixer->Shape();
在工程里,ShapeFix_Shape 比较适合处理一些轻量、常规、风险较低的问题,例如:
wire 连接关系整理;
部分 face / wire 的基础修复;
容差范围内的小问题规整;
导入后 shape 的基础预处理。
但它不应该承担“复杂模型修复”的全部职责。
原因很简单:ShapeFix_Shape 不理解你的业务目标。
同一个模型,在不同场景下修复目标可能完全不同:
只是显示出来;
参与布尔运算;
生成表面网格;
生成体网格;
保持接触面共形;
保持原始导入语义。
这些目标不同,能接受的修复程度也不同。
有些场景希望尽量闭合模型,有些场景更在意保持原始拓扑;有些场景可以接受删除细小边,有些场景里小边可能就是边界条件或真实结构特征。
所以我现在更倾向于把 ShapeFix_Shape 看成一个“基础清理工具”,而不是最终修复策略。
比较稳妥的使用方式是:
轻量修复可以用 ShapeFix_Shape;
复杂修复不要只依赖 ShapeFix_Shape;
修复前后必须有检查;
失败时要能保留原始 shape;
不要在用户不知情的情况下做高风险拓扑修改。
这也是为什么我不建议在模型导入阶段默认启用太激进的修复流程。导入阶段可以做保守处理,但不应该替用户决定哪些几何特征应该被修改或删除。
ShapeFix_Face:面级修复适合局部处理
如果问题集中在 face 层面,ShapeFix_Face 会比直接对整个 shape 做全局修复更可控。
例如某个 face 的边界 wire 存在连接问题、闭合问题或局部自交问题,直接全局修复可能影响整个 shape;而面级修复可以把影响范围限制在一个更小的局部区域内。
简化后的流程类似:
TopoDS_Face face = ...;
Handle(ShapeFix_Face) faceFixer = new ShapeFix_Face(face);
faceFixer->SetPrecision(precision);
faceFixer->SetMaxTolerance(maxTolerance);
Handle(ShapeFix_Wire) wireTool = faceFixer->FixWireTool();
wireTool->FixClosed(Standard_True);
wireTool->FixConnected(Standard_True);
faceFixer->Perform();
TopoDS_Face fixedFace = faceFixer->Face();
这里有一个工程判断:面级修复不是为了“把所有面都过一遍”,而是为了处理已经定位到的局部问题。
如果一个 shape 有几千、几万个 face,直接逐面修复不一定划算。很多 face 本来没有问题,强行处理反而会带来额外成本和不确定性。
更合理的方式是:
先检测问题;
定位到可疑 face 或边界;
对局部 face 做修复;
再把结果放回局部区域;
最后做整体或局部验证。
ShapeFix_Face 本身只是工具,真正难的是决定:
什么时候用它?
对哪一块用?
修完怎么验收?
修失败后要不要回滚?
这些判断不应该散落在每个修复函数里,而应该进入更上层的策略层。
ShapeFix_Wire:wire 修复通常是补面和缝合前的准备
很多几何修复问题最后都会落到 wire 上。
例如:
面边界没有闭合;
几条边首尾没有连好;
边方向不一致;
边界上有很短的小边;
wire 存在局部自交。
这些问题如果不先处理,后面的 sewing、filling、make face 都可能失败。
ShapeFix_Wire 适合处理 wire 层面的连接和规整问题。它通常不会单独成为最终修复结果,而是作为后续操作的准备步骤。
例如,从几条边构造一个修补面之前,一般不能直接把边丢给 BRepFill_Filling 或 BRepBuilderAPI_MakeFace。更稳妥的做法是先判断这些边能否形成合理边界,再构造 wire,并在必要时做 wire 级修复。
一个典型思路是:
收集边;
判断边是否首尾可连接;
构造 wire;
修复 wire 的连接和闭合问题;
再尝试 make face 或 filling;
最后验证生成面是否有效。
在 CAD/CAE 工程里,wire 问题经常不是最终问题,而是更上层问题的表现。
比如一个 open shell 的自由边,表面看是 free edge,但本质上可能是:
两个面没有拓扑连接;
局部确实缺了一块面;
导入容差导致边界没有合并;
模型本来就是开放曲面;
该位置是用户故意保留的开口。
所以看到 wire 问题时,不要急着直接修 wire。先判断它属于哪一类问题,通常更重要。
ShapeFix_Wireframe:处理线框缺陷时要注意副作用
ShapeFix_Wireframe 常用来处理更偏线框层面的缺陷,例如小边、wire gap 等。
示意用法大致是:
Handle(ShapeFix_Wireframe) wireframeFixer = new ShapeFix_Wireframe(shape);
wireframeFixer->SetPrecision(precision);
wireframeFixer->SetMaxTolerance(maxTolerance);
wireframeFixer->ModeDropSmallEdges() = Standard_True;
wireframeFixer->FixSmallEdges();
wireframeFixer->FixWireGaps();
TopoDS_Shape fixedShape = wireframeFixer->Shape();
这类工具在工程里很有用,但也要谨慎。
例如删除小边这件事,从修复角度看可能是合理的;但从建模语义看,小边可能代表真实结构特征。对于 CAD/CAE 模型来说,小倒角、小缝、小台阶有时候不是噪声,而是物理结构的一部分。
因此线框修复不能只看“模型更干净了没有”,还要看:
是否误删了真实特征;
是否影响后续边界条件选择;
是否改变了用户能看到和能选中的拓扑;
是否影响后续网格尺寸和局部加密。
这也是为什么我不太赞成在导入阶段默认启用太激进的线框修复。
线框修复可以做,但它应该出现在可控的修复流程里,而不是在导入时静默执行。
BRepBuilderAPI_Sewing:连接已有面,不是自动补面
BRepBuilderAPI_Sewing 是几何修复里非常常用的接口。
它的作用可以简单理解为:在给定容差内,把相邻面之间能够连接的边界缝合起来。
简化用法是:
BRepBuilderAPI_Sewing sewing(tolerance);
sewing.Add(shape);
sewing.Perform();
TopoDS_Shape sewedShape = sewing.SewedShape();
它适合处理的问题包括:
多个 face 拓扑上没有连接;
shell 存在可缝合的开口;
相邻面边界几何上接近,但拓扑上没有共享;
导入后 face 集合需要组成 shell。
但这里有一个很重要的边界:
Sewing 是连接已有面,不是凭空补一张面。
如果模型缺了一块面,只剩周围一圈自由边,BRepBuilderAPI_Sewing 不会自动理解并生成缺失面。它可能让已有面之间连接得更好,但它不是补面算法。
所以在工程里,Sewing 适合作为这些流程的一部分:
导入后 face 集合的轻量拓扑连接;
局部 patch 修复后的重新缝合;
补面之后,把新面和周围旧面连接起来;
面级修复之后,重新固化局部拓扑关系。
Sewing 最大的问题通常来自容差。
容差太小,原本应该连接的面缝不上;容差太大,不该连接的边界可能被错误合并。这个问题在 CAD/CAE 里尤其敏感,因为错误缝合可能会改变后续网格拓扑,甚至影响边界条件、接触关系和材料区域。
所以我现在更倾向于把 Sewing 当成一个需要上下文的操作:
它修的是哪些面?
输入是不是局部 patch?
容差怎么来?
修复前自由边有多少?
修复后自由边是否下降?
shape validity 有没有变差?
失败时是否需要回滚?
只有这些信息都在,Sewing 才是一个工程可控的修复步骤。
ShapeAnalysis_FreeBounds:自由边是很多修复流程的入口
在处理 open shell、面缝隙、补面问题时,自由边通常是最重要的线索之一。
自由边可以理解为:某条 edge 只被一个 face 使用,说明它处在边界上。如果一个 shell 本来应该闭合,却存在大量自由边,就说明局部拓扑没有闭起来。
OCCT 中可以通过拓扑映射自己统计自由边,也可以结合 ShapeAnalysis_FreeBounds 把自由边连接成 wire。
一个常见流程是:
先统计 edge 被多少个 face 使用;
找出只属于一个 face 的 edge;
把这些 free edge 尝试连接成 wire;
判断 wire 是否能形成闭合边界;
再决定是 sewing、补面,还是放弃自动处理。
ShapeAnalysis_FreeBounds 比较适合做自由边整理和边界识别。它不是修复接口本身,但它经常是修复流程的前置步骤。
这里也有一个工程上的注意点:
free edge 只是现象,不是结论。
一个 free edge 可能意味着:
两个面只是拓扑没连上;
局部确实缺了一张面;
导入容差导致边界没匹配;
模型本来就是开放曲面;
该位置是用户故意保留的开口。
如果不区分这些情况,直接对 free edge 做补面或 sewing,很容易修错。
所以更稳妥的做法是:先把 free edge 聚成局部边界,再结合所在 face、邻域、模型类型和后续用途做判断。
BRepFill_Filling:从边界构造修补面
当模型确实存在缺面时,Sewing 通常不够用。这时需要考虑补面。
OCCT 中常见的补面思路之一是使用 BRepFill_Filling,根据边界约束构造一张填充面。它适合处理一些局部孔洞或非严格平面的边界补面问题。
从工程流程看,补面一般不是一个单独 API 调用,而是一串步骤:
找到问题边界;
判断边界是否可以组成 wire;
检查边界是否闭合;
根据边界生成填充曲面;
把曲面转成 face;
将新 face 与周围面 sewing;
验证自由边是否减少;
验证 shape 是否仍然有效。
如果边界比较简单、共面性较好,有时也可以用 BRepBuilderAPI_MakeFace 直接从 wire 创建面。
但对于非共面边界,或者边界需要生成一张过渡曲面时,BRepFill_Filling 会更合适。
不过补面比 Sewing 风险更高。
原因是 Sewing 更多是在已有拓扑之间建立连接,而 Filling 是在模型中新增几何。新增一张面之后,必须考虑它是否符合原始设计意图。
尤其在 CAE 模型里,补出来的面可能会改变封闭区域、体域识别、网格拓扑和边界条件。
所以补面不适合在导入阶段默认执行。它更适合放在交互式修复或专门修复命令里,让用户能看到问题位置、确认修复范围,并在修复后检查结果。
补面流程中最容易出问题的地方包括:
边界没有真正闭合;
边方向不一致;
边界自交;
边界不共面;
生成面质量不好;
新面和旧面 sewing 后仍然存在自由边;
补面导致 shape 类型或拓扑结构发生非预期变化。
所以补面一定要有验收逻辑,不能只看 API 是否返回成功。
为什么这些接口需要再包一层工程策略
如果只看 OCCT API,本身已经有检查、修复、缝合、补面工具。那为什么工程里还需要再包一层?
核心原因是:
OCCT 提供的是工具,不是业务决策。
例如同样是 free edge,可能有几种处理方式:
如果只是拓扑没连接,可以尝试 sewing;
如果是 wire gap,可以尝试 wireframe fix;
如果确实缺面,可以尝试 filling;
如果是开放曲面,不应该强行闭合;
如果会影响后续共形网格,需要更谨慎地处理接触关系。
这些判断不是某一个 API 能自动完成的。
工程修复模块真正要解决的是:
问题检测;
问题分类;
策略选择;
修复执行;
结果验证;
失败回滚;
修复报告。
这也是后来我更倾向于把修复流程拆成几层:
检查层:判断 shape 是否存在问题;
分析层:把问题归类为 free edge、face gap、open boundary 等;
策略层:决定使用 ShapeFix、Sewing、Filling 还是局部编辑;
执行层:调用 OCCT 接口或自定义局部修复;
验证层:检查修复前后变化;
报告层:告诉用户修了什么、有没有风险。
这样做的好处是,代码不会变成一串黑盒 API 调用。后续如果某个模型修复失败,也更容易判断问题出在哪里:是检测不准、策略选错、容差不合适,还是某个 API 本身解决不了这个问题。
导入阶段应该轻一点
模型导入阶段和专门修复阶段应该分开。
导入阶段的目标是稳定读入模型,尽量保留原始几何和拓扑语义。它可以做一些轻量、保守、可预期的处理,比如基础检查、单位处理、简单 shape fix、必要的拓扑整理。
但导入阶段不应该默认做强修复。
原因有三个。
第一,强修复可能改变模型语义。比如删除小边、合并边界、补面、扩大容差 sewing,这些操作都可能让模型变得“更干净”,但不一定更正确。
第二,导入阶段缺少用户上下文。系统不知道用户接下来是要显示、建模、布尔、网格,还是设置边界条件。不同用途对修复的接受程度不同。
第三,复杂修复需要解释和回滚。用户应该知道哪里被修了、用了什么策略、修复前后自由边是否减少、shape 是否仍然有效。如果这些都藏在导入流程里,后续出问题很难排查。
所以更稳妥的边界是:
导入阶段:做轻量修复和问题记录;
修复模块:做检测、分类、策略选择和可解释修复;
交互命令:处理局部补面、面缝合、近接触等高风险操作。
这种边界看起来麻烦,但对于 CAD/CAE 系统来说更可控。
修复结果不能只看是否生成成功
几何修复里还有一个常见误区:只要 API 返回成功,就认为修好了。
实际上并不是。
一个修复结果至少要看几类指标:
shape 是否为空;
BRepCheck_Analyzer 是否通过;
自由边数量是否下降;
问题区域是否减少;
shape 类型是否发生非预期变化;
修复后的拓扑是否还能被后续流程识别;
是否影响用户可见的边、面和选择对象。
尤其是 free edge 和 face gap 修复,不能只看生成了一个新的 shape。因为新的 shape 可能确实生成成功,但自由边没有减少,甚至引入了更多边界问题。
补面也是一样。生成一张 face 不代表孔洞被正确修复。还要看这张 face 是否和周围面缝上了,边界是否还开放,局部拓扑是否有效。
所以工程上应该把修复结果写成报告,而不是只返回一个 bool。
一个更有用的修复结果应该包含:
是否成功;
使用了什么策略;
修复前问题数量;
修复后问题数量;
是否有回滚;
是否存在仍未解决的问题;
是否需要用户进一步确认。
这类信息对于交互式修复尤其重要。用户不只是想知道“修好了没有”,还想知道“修了哪里,怎么修的,会不会影响模型”。
小结:OCCT 提供工具,但工程修复需要边界
OCCT 的修复接口很强,但它们更像一组工具箱,而不是完整的修复系统。
对于 CAD/CAE 场景来说,模型修复真正困难的地方往往不在于调用 ShapeFix、Sewing 或 Filling,而在于:
什么时候调用;
对哪里调用;
用多大容差;
修复后怎么验收;
失败时怎么回滚;
是否应该让用户确认。
如果把这些接口放到一个修复系统里,我现在更倾向于这样理解它们的分工:
BRepCheck_Analyzer:
基础体检工具,用于判断 OCCT 层面的 shape 有效性。
ShapeFix_Shape:
轻量修复入口,适合基础清理,不适合承担复杂决策。
ShapeFix_Face / ShapeFix_Wire:
面和 wire 层面的局部修复工具,适合配合问题定位使用。
ShapeFix_Wireframe:
线框缺陷处理工具,对小边和 wire gap 有用,但要注意误删真实特征。
BRepBuilderAPI_Sewing:
连接已有面,适合面集合缝合和局部 patch 后重新连接,不是补面算法。
ShapeAnalysis_FreeBounds:
自由边和边界识别工具,常作为 open shell、gap 和补面流程的入口。
BRepFill_Filling:
从边界生成修补面,适合局部缺面修复,但风险高,需要验证和用户确认。
这也是我后来对几何修复模块的一个基本判断:
导入阶段要克制;
基础修复要保守;
复杂修复要可解释;
局部修复要可回滚;
面向网格的修复还要考虑后续共形和稳定性。
从这个角度看,ShapeFix、Sewing 和 Filling 不是几何修复的全部,而是修复系统中的几个基础执行器。
真正能让修复流程稳定下来的,是前面的检测、分类、策略选择,以及后面的验证和报告。