1. 前言
occt中,最常用的布尔工具是BRepAlgoAPI_Fuse、BRepAlgoAPI_Cut和BRepAlgoAPI_Common,即所谓的并集、差集、交集,它们都属于标准的正则化布尔操作。
所有的布尔操作,都属于General Fuse Algorithm体系,也就是BRepAlgoAPI_BuilderAlgo,表示通用构建器:子类BRepAlgoAPI_BooleanOperation代表标准布尔,由它派生的即是上述三个工具以及BRepAlgoAPI_Section(交线提取器);其它的子类还有BRepAlgoAPI_Splitter,表示通用分割器。
在GFA体系中,cell是一个绕不开的概念:所有输入形状经过求交分割后,产生的每一个最小不可再分的拓扑单元就是一个 Cell:
- 3D Cell = 最小实体块(Solid)
- 2D Cell = 最小面片(Face)
- 1D Cell = 最小线段(Edge)
- 0D Cell = 顶点(Vertex)
BRepAlgoAPI_Splitter就是Cell的生产工具,BRepAlgoAPI_BuilderAlgo及其子类就是融合Cell的工具。
2. 底层BOPAlgo算法工具
GFA 的真正引擎,所有 API 类最终都委托到这里执行。本文内容暂时不直接涉及这些底层对象。
| 工具类 | 职责 | 关键特性 |
|---|---|---|
BOPAlgo_PaveFiller | 求交计算器 | 计算所有输入形状之间的交点、交线、交面;构建 Pave 数据结构;是整个 GFA 最耗时的步骤 |
BOPAlgo_Builder | 通用构建器 | 基于 PaveFiller 结果,按规则分类 Cells 并合并;支持正则化与非正则化模式 |
BOPAlgo_BOP | 标准布尔操作算法 | Builder 的特化版,内置 Fuse/Cut/Common 的固定选择逻辑 |
BOPAlgo_CellsBuilder | 自定义重组引擎 | 跳过自动选择,允许用户按包含/排除规则任意选取 Cells 并组装 |
BOPAlgo_Section | 纯截面算法 | 仅提取低维交集(边/点),不执行构建流程 |
BOPAlgo_RemoveFaces | 小面清理器 | 消除布尔运算产生的退化小面/窄条面 |
BOPAlgo_MakerVolume | 实体生成器 | 从一组面/壳自动缝合成封闭实体(常用于修复导入模型) |
BOPAlgo_ArgumentAnalyzer | 输入预检器 | 在运算前检查输入的合法性(自交、间隙、退化等),可自动修复部分问题 |
3. 布尔基础概念
TODO
4. occt的标准布尔的高级工具
occt中标准布尔,可以分为BRepAlgoAPI_Fuse、BRepAlgoAPI_Cut、BRepAlgoAPI_Common和BRepAlgoAPI_Section四种,前三个主要针对的都是同维度的流形体。它们都是BRepAlgoAPI_BuilderAlgo的子类,所以它们也具备设置BuilderAlgo所支持的一些参数。本小结主要关注它们自身的能力和使用要点。
4.1 BRepAlgoAPI_Fuse工具
布尔并集;支持“多-多”组合:SetArguments和SetTools;要求模型是同维度的流形体;
voidSafeFuse(constTopoDS_Shape&shapeA,constTopoDS_Shape&shapeB,TopoDS_Shape&outResult){BRepAlgoAPI_Fuse fuseOp;// ✅ 使用 SetArguments/SetTools 而非构造函数传参TopTools_ListOfShape args,tools;args.Append(shapeA);tools.Append(shapeB);fuseOp.SetArguments(args);fuseOp.SetTools(tools);// ✅ 关键参数配置fuseOp.SetRunParallel(Standard_True);// 多线程加速fuseOp.SetFuzzyValue(1.0e-4);// 模糊容差,修复微小间隙fuseOp.SetGlue(BOPAlgo_GlueShift);// 装配体贴合面优化fuseOp.SetToFillHistory(Standard_True);// 启用历史追溯// 执行运算fuseOp.Build();// ✅ 结构化错误检查(替代旧版 ErrorStatus 枚举)if(!fuseOp.IsDone()||fuseOp.HasErrors()){constHandle(BOPAlgo_Report)&report=fuseOp.GetReport();std::cerr<<"Fuse failed with "<<report->GetAlerts(Message_Error).Extent()<<" errors:"<<std::endl;report->Dump(std::cerr);return;}// 警告信息(非致命,但值得关注)if(fuseOp.HasWarnings()){constHandle(BOPAlgo_Report)&report=fuseOp.GetReport();std::cout<<"Fuse completed with warnings:"<<std::endl;report->Dump(std::cout);}outResult=fuseOp.Shape();}4.2 BRepAlgoAPI_Cut工具
布尔差集;支持“多-多”组合:SetArguments和SetTools;要求模型是同纬度的流形体;
4.3 BRepAlgoAPI_Common工具
布尔交集;支持“多-多”组合:SetArguments和SetTools;要求模型是同纬度的流形体;
4.4 BRepAlgoAPI_Section工具
仅提取两个输入对象相交的部分,其结果是一个由交线(TopoDS_Edge)和交点(TopoDS_Vertex)组成的复合体(TopoDS_Compound)。
除了两个Shape之间的操作,它还支持两个Geom_Surface之间的求交:
BRepAlgoAPI_Section(constocc::handle<Geom_Surface>&Sf1,constocc::handle<Geom_Surface>&Sf2,constboolPerformNow=true)Section的另一个核心在于,可以计算交线在两个曲面上的PCurve,有一些局部边界工具需要依赖此,例如SplitShape。这里的PCurve被附加在了交线(TopoDS_Edge)上,可以通过BRep_Tool::CurveOnSurface来获取。
voidComputePCurveOn1(constboolB);voidComputePCurveOn2(constboolB);staticocc::handle<Geom2d_Curve>CurveOnSurface(constTopoDS_Edge&E,constTopoDS_Face&F,double&First,double&Last,bool*theIsStored=nullptr)还有就是,Section支持追溯父级,当从 Section 的结果中拿到一条交线(Edge)时,可以使用HasAncestorFaceOn1或HasAncestorFaceOn2方法来反向查询:这条交线是由第一个输入形状的哪个面(Face)和第二个输入形状的哪个面相交生成的。
boolHasAncestorFaceOn1(constTopoDS_Shape&E,TopoDS_Shape&F)const;boolHasAncestorFaceOn2(constTopoDS_Shape&E,TopoDS_Shape&F)const;4.5 BRepAlgoAPI_BooleanOperation
BRepAlgoAPI_BooleanOperation是上述BRepAlgoAPI_Fuse、BRepAlgoAPI_Cut、BRepAlgoAPI_Common和BRepAlgoAPI_Section的共同基类,支持通过枚举参数替代上述中的任一个。
voidSetOperation(constBOPAlgo_Operation theBOP);enumBOPAlgo_Operation{BOPAlgo_COMMON,BOPAlgo_FUSE,BOPAlgo_CUT,BOPAlgo_CUT21,BOPAlgo_SECTION,BOPAlgo_UNKNOWN}5. occt的布尔扩展工具
布尔扩展,主要指的就是能实现General Fuse效果的工具,类似于Cell布尔、非流行布尔的概念。在occt中,“General Fuse”在底层工具中有一个统一的概念:BOPAlgo,而到了上层工具中,主要有BRepAlgoAPI_BuilderAlgo、BRepAlgoAPI_Splitter两个。
5.1 BRepAlgoAPI_BuilderAlgo工具
BRepAlgoAPI_BuilderAlgo是底层BOPAlgo_Builder的 BRep 层简易封装,是通用相交、分割构建器。它用相交拓扑切割所有原始形体,仅输出全部被分割后的碎片拓扑,不会主动进行合并。
输出的Shape往往是Compound,也就是碎片集合,其中碎片的类型可以是solid、face等任意。其中,碎片之间的关系,是通过共用TShape体现的,例如:两个贴面的box做BuilderAlgo,所得结果是一个包含两个solid的Compound对象,但是它下面只有11个去重面,因为有两个face是相同的、仅Orientation方向不同。
BRepAlgoAPI_BuilderAlgo();voidSetArguments(constNCollection_List<TopoDS_Shape>&theLS);voidBuild(constMessage_ProgressRange&theRange=Message_ProgressRange())override;constTopoDS_Shape&Shape()override;BRepAlgoAPI_BuilderAlgo支持一些参数控制。
voidSetNonDestructive(constbooltheFlag);voidSetGlue(constBOPAlgo_GlueEnum theGlue);voidSetCheckInverted(constbooltheCheck);voidSimplifyResult(constbooltheUnifyEdges=true,constbooltheUnifyFaces=true,constdoubletheAngularTol=Precision::Angular());voidSetToFillHistory(constbooltheHistFlag);NonDestructive:这是一个关于输入形状保护的开关。默认行为False:算法为了构建结果,可能会“复用”输入形状内部的拓扑结构,并且可能会改变其数据。注意,只是入参是否还可以继续使用,不代表是否存在“子拓扑原位复用”。Glue:粘合/缝合模式,用于处理相切但无体积干涉的情况。注意,前提是用户已知无体积干涉的情况,相当于跳过了求交环节,直接构建碎片。CheckInverted:检查是否存在面的方向发生了内翻的情况。Simplify:自动对结果简化,主要是消除缝合线,接口内部会一并处理History的,也可用自行使用ShapeUpgrade_UnifySameDomain来简化模型。FillHistory:默认是会记录History的,除非主动关闭了;
BRepAlgoAPI_BuilderAlgo是所有API级别布尔工具的基类,所以这些控制参数也同样适用于它们。下面以几个典型场景介绍一下Builder的碎片情况。
5.1.1 两体相交
两个存在相交干涉的box的BRepAlgoAPI_BuilderAlgo碎片:3个solid,去重后18个Face。
5.1.2 体面相交
一个face在一个Box的内部,它们的BRepAlgoAPI_BuilderAlgo碎片有两个:box多了一个空内腔shell,face还是原来的face。也就相当于,face将自己压印在box的内部。
一个face插入一个Box,它俩的BRepAlgoAPI_BuilderAlgo碎片有三个:一个box和两个face。也就是原来的face被分割成了两个,box也是多个一个内腔shell,且内腔shell与外壳shell还共享一个Edge,并且box外壳交界的那个face,会有一个内环wire。
5.2 BRepAlgoAPI_Splitter工具
BRepAlgoAPI_Splitter是BRepAlgoAPI_BuilderAlgo的子类,它是专门用Tool来分割指定目标对象的,它只关心目标对象被分割后的碎片。相当于,从BRepAlgoAPI_BuilderAlgo的碎片集合中,挑选出来源于指定对象的。
BRepAlgoAPI_Splitter();voidSetArguments(constNCollection_List<TopoDS_Shape>&theLS);voidSetTools(constNCollection_List<TopoDS_Shape>&theLS);两个存在相交干涉的box的BRepAlgoAPI_BuilderAlgo碎片:2个solid,去重后12个Face
6. BOPAlgo算法工具
BOPAlgo_Builder是通用相交切割器,BOPAlgo_BOP是标准布尔的算子基类(Fuse/Cut/Common/Section父类),BOPAlgo_CellsBuilder是单元提取器。整体使用逻辑就是先分割成碎片单元,然后再自定义提取。
6.1 BOPAlgo_CellsBuilder
BOPAlgo_CellsBuilder是BOPAlgo_Builder的子类,在Builder分割出所有Cell碎片的基础上,增加了提取特定Cell的能力。BOPAlgo_Builder的能力完全可以参考BRepAlgoAPI_BuilderAlgo,下面就仅补充一下CellBuilder的提取能力。
CellBuilder支持材料概念。
constTopoDS_Shape&GetAllParts()const;//相当于直接获取BRepAlgoAPI_BuilderAlgo的结果voidAddAllToResult(constinttheMaterial=0,constbooltheUpdate=false);//将所有cell加入结果集//如果theMaterial非0,则将所有cell都定义成指定的材料;“0”表示无材料属性。//如果theUpdate为true,则主动调用RemoveInternalBoundaries消除相邻同材料cell的内部边界voidRemoveAllFromResult();//清空结果集voidAddToResult(constNCollection_List<TopoDS_Shape>&theLSToTake,constNCollection_List<TopoDS_Shape>&theLSToAvoid,constinttheMaterial=0,constbooltheUpdate=false);//取Take中的公共的、且不是来自于Void中的任一个的cell,加入结果集//To be taken into result the part must be IN for all shapes from the list <theLSToTake> and must be OUT of all shapes from the list <theLSToAvoid>voidRemoveFromResult(constNCollection_List<TopoDS_Shape>&theLSToTake,constNCollection_List<TopoDS_Shape>&theLSToAvoid);voidRemoveInternalBoundaries();//融合相邻的相同材料的单元voidMakeContainers();//构建容器,对已经选入结果的分片单元,按维度自动封装成符合 OCCT 拓扑层级的强类型容器除了碎片单元(VERTEX、EDGE、FACE或SOLID),还有一个“容器”的概念,类型有Wire:WIRES for EDGE,Shell:SHELLS for FACES,CompSolid:COMPSOLIDS for SOLIDS,其实就是对碎片进行归类。
默认情况,结果Shape是一个Compound,下一级就是碎片,结构形如:Compound { Solid1, Solid2, Face1, Face2, Edge1 … }。
如果构建了容器(MakeContainers),那么Compound下一级是容器,容器再一级才是碎片,结构形如:Compound { CompSolid1, CompSolid2, Shell1, Shell2, Wire1… }。注意,只能满足共享face的Solid才能进入同一个CompSolid,否则就每个独立装入一个CompSolid,Face和Edge也是类似的道理。
CellBuilder也具备融合能力,主要体现在RemoveInternalBoundaries接口。
6.1.1 AddAllToResult 分析
源码如下。其中,重点关注theMaterial的取值影响。若theMaterial取值为0,则表示不为cell定义材料,并不是材料取为0。这对RemoveInternalBoundaries影响很大,结合RemoveInternalBoundaries的源码可以看出,它仅处理定义了材料属性的cell。
voidBOPAlgo_CellsBuilder::AddAllToResult(constStandard_Integer theMaterial,constStandard_Boolean theUpdate){myShapeMaterial.Clear();myMaterials.Clear();myMapModified.Clear();//myShape=myAllParts;//if(theMaterial!=0){TopTools_ListOfShape*pLSM=myMaterials.Bound(theMaterial,TopTools_ListOfShape());//TopoDS_IteratoraIt(myAllParts);for(;aIt.More();aIt.Next()){constTopoDS_Shape&aPart=aIt.Value();myShapeMaterial.Bind(aPart,theMaterial);pLSM->Append(aPart);}}//if(!theUpdate){PrepareHistory(Message_ProgressRange());}else{RemoveInternalBoundaries();}}voidBOPAlgo_CellsBuilder::RemoveInternalBoundaries(){if(myMaterials.IsEmpty()){return;}......}6.1.2 RemoveInternalBoundaries 分析
关于RemoveInternalBoundaries的效果,删除【相同 material 标记、同维度两个 Cell 之间互相配对的共享边界】;不同 material、不同维度、外边界完全不动。目前观察到的现象是,需要区分Sheet和Solid两种。
Sheet并集时,可以去除Face间的内部Edge,效果如下图所示。
提醒一些:针对此场景,SimplifyResult是无效的,虽然是共面的平面,但可能Face底层并未共享同一个Surface对象。
Solid并集时,只能消除Solid之间的共享Face,却无法去除共面Face间的公共Edge。这也很好理解,RemoveInternalBoundaries处理的对象是cell,而针对Solid Cell,关注的是共享Face,不关注共享Edge的问题。可以考虑使用UnifySameDomain来清理内部Edge。
标准的Fuse工具,默认不会消除Face间的碎片边界,却默认自动消除solid碎片间的边界,效果如下图所示。