OCCT 的 Boolean Operations Component 不是只有传统的并、交、差。官方把它分成四类:General Fuse Operator、Boolean Operator、Section Operator、Splitter Operator。其中 General Fuse 是 Boolean、Section、Splitter 的基础算法。(Open CASCADE)
下面统一用一个 box 和一个 cylinder 来说明结果:
TopoDS_Solid makeBox()
{
gp_Pnt min(0, 0, 0);
gp_Pnt max(100, 80, 30);
BRepPrimAPI_MakeBox maker(min, max);
return maker.Solid();
}
TopoDS_Solid makeCylinder()
{
gp_Ax2 ax2(
gp_Pnt(50, 40, 0),
gp_Dir(gp_Dir::D::Z),
gp_Dir(gp_Dir::D::X)
);
BRepPrimAPI_MakeCylinder maker(ax2, 10, 50);
return maker.Solid();
}
这个例子中:
box:
X: 0 ~ 100
Y: 0 ~ 80
Z: 0 ~ 30
cylinder:
圆心轴线位置: X = 50, Y = 40
方向: Z
半径: 10
高度: 50
所以圆柱穿过 box,并且在 box 顶部上方还多出一段。
| 操作 | 含义 | 常用 API | 结果类型 |
|---|---|---|---|
Fuse | 并集 | BRepAlgoAPI_Fuse | 通常是 TopoDS_Shape / Solid / Compound |
Cut | 差集 | BRepAlgoAPI_Cut | 通常是 TopoDS_Shape / Solid |
Common | 交集 | BRepAlgoAPI_Common | 两组对象的公共部分 |
Section | 求交线 / 截交边 | BRepAlgoAPI_Section | 通常是 Vertex / Edge / Compound of Edges |
Splitter | 切分 Object,但不删除 Object 材料 | BRepAlgoAPI_Splitter | 被切开的 Object parts |
General Fuse | 所有输入互相切分,保留全部 split parts | BOPAlgo_Builder / BRepAlgoAPI_BuilderAlgo | Compound |
CellsBuilder | 在 General Fuse 的 cell 中按规则选择结果 | BOPAlgo_CellsBuilder | 自定义组合结果 |
OCCT 的 BRepAlgoAPI 包中直接列出了常见高级 API:BRepAlgoAPI_Common、BRepAlgoAPI_Cut、BRepAlgoAPI_Fuse、BRepAlgoAPI_Section、BRepAlgoAPI_Splitter 等。(Open CASCADE)
OCCT 官方把传统 Boolean Operator 定义为两组对象之间的操作:
Objects = 第一组对象
Tools = 第二组对象
传统 Boolean Operator 包含三种:FUSE、COMMON、CUT。它们分别表示两组对象的并集、交集和差集。OCCT 还提到一个 Cut21,可以理解成反向差集,也就是 Tools - Objects。(Open CASCADE)
Fuse(A, B) = A ∪ B
对于我们的例子:
Fuse(box, cylinder)
结果是 box 和 cylinder 的合并体。因为 cylinder 上方超出 box,所以最终结果会包含 box 以及圆柱伸出 box 顶部的部分。
常用代码:
TopoDS_Shape runFuse()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
BRepAlgoAPI_Fuse fuse(box, cylinder);
fuse.Build();
if (fuse.HasErrors()) {
fuse.DumpErrors(std::cerr);
return {};
}
return fuse.Shape();
}
也可以使用 SetArguments() 和 SetTools():
TopoDS_Shape runFuse2()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
TopTools_ListOfShape objects;
objects.Append(box);
TopTools_ListOfShape tools;
tools.Append(cylinder);
BRepAlgoAPI_Fuse fuse;
fuse.SetArguments(objects);
fuse.SetTools(tools);
fuse.Build();
if (fuse.HasErrors()) {
fuse.DumpErrors(std::cerr);
return {};
}
return fuse.Shape();
}
BRepAlgoAPI_Fuse 是 Boolean Union,也就是并集操作。(Open CASCADE)
Cut(A, B) = A - B
对于我们的例子:
Cut(box, cylinder)
结果是 box 被 cylinder 挖掉。因为圆柱穿过 box,所以结果类似一个带圆柱孔的长方体。
代码:
TopoDS_Shape runCut()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
BRepAlgoAPI_Cut cut(box, cylinder);
cut.Build();
if (cut.HasErrors()) {
cut.DumpErrors(std::cerr);
return {};
}
return cut.Shape();
}
BRepAlgoAPI_Cut 是 Boolean Subtraction,也就是从 arguments 中减去 tools。(Open CASCADE)
注意:Cut(box, cylinder) 和 Cut(cylinder, box) 完全不同。
Cut(box, cylinder) = box - cylinder
Cut(cylinder, box) = cylinder - box
OCCT 中的 Cut21 可以理解为反向 Cut,即 Cut(G2, G1)。(Open CASCADE)
Common(A, B) = A ∩ B
对于我们的例子:
Common(box, cylinder)
结果是 box 和 cylinder 重叠的部分,也就是位于 box 内部的那段圆柱体:
半径: 10
高度: 30
位置: box 内部
代码:
TopoDS_Shape runCommon()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
BRepAlgoAPI_Common common(box, cylinder);
common.Build();
if (common.HasErrors()) {
common.DumpErrors(std::cerr);
return {};
}
return common.Shape();
}
BRepAlgoAPI_Common 是 Boolean Intersection,即两组对象的交集。(Open CASCADE)
Section 不是求公共体积,而是求相交产生的边和点。
Section(A, B) = A 和 B 的交线 / 交点
对于我们的例子:
Section(box, cylinder)
因为 cylinder 完全在 box 的 XY 范围内,并沿 Z 方向穿过 box,所以主要交线出现在:
box 底面 z = 0 与 cylinder 侧面的交线:一个圆
box 顶面 z = 30 与 cylinder 侧面的交线:一个圆
所以结果通常是两个圆形 edge / wire 的 compound,而不是 solid。
代码:
TopoDS_Shape runSection()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
BRepAlgoAPI_Section section(box, cylinder);
section.Build();
if (section.HasErrors()) {
section.DumpErrors(std::cerr);
return {};
}
return section.Shape();
}
更推荐显式写成 arguments 和 tools:
TopoDS_Shape runSection2()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
TopTools_ListOfShape objects;
objects.Append(box);
TopTools_ListOfShape tools;
tools.Append(cylinder);
BRepAlgoAPI_Section section;
section.SetArguments(objects);
section.SetTools(tools);
section.Build();
if (section.HasErrors()) {
section.DumpErrors(std::cerr);
return {};
}
return section.Shape();
}
BRepAlgoAPI_Section 的结果由 vertices 和 edges 组成,文档中也说明它用于构造 arguments 和 tools 之间的 Section 操作。(Open CASCADE)
Section 和 Common 很容易混淆:
Common(box, cylinder)
得到公共体积,是一个实体。
Section(box, cylinder)
得到相交边线,是 edge / wire / compound。
Splitter 的语义是:
用 Tools 去切分 Objects
结果只包含 Objects 被切分后的 parts
不包含只属于 Tools 的 parts
OCCT 文档对 BRepAlgoAPI_Splitter 的描述是:Objects 是将被切分的形状,Tools 是用来切分 Objects 的形状;结果只包含 Objects 组的 split parts,不包含 Tools 组的 split parts。(Open CASCADE)
对于我们的例子:
Splitter(box, cylinder)
结果不是 box - cylinder,而是:
box 被 cylinder 的面切开
box 内部圆柱区域仍然属于结果
box 外部剩余区域也属于结果
可以理解成 box 被分成了两个部分:
1. box 中 cylinder 占据的圆柱区域
2. box 中 cylinder 外面的剩余区域
代码:
TopoDS_Shape runSplitter()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
TopTools_ListOfShape objects;
objects.Append(box);
TopTools_ListOfShape tools;
tools.Append(cylinder);
BRepAlgoAPI_Splitter splitter;
splitter.SetArguments(objects);
splitter.SetTools(tools);
splitter.Build();
if (splitter.HasErrors()) {
splitter.DumpErrors(std::cerr);
return {};
}
return splitter.Shape();
}
对比一下:
Cut(box, cylinder)
删除 box 中被 cylinder 占据的区域。
Splitter(box, cylinder)
不删除,只是把 box 按 cylinder 分割成多个 part。
General Fuse 可以理解成 Boolean 操作的“原材料”。它不直接表达并、交、差,而是把所有输入 shape 互相求交并切分,生成全部 split parts。OCCT 文档中给出的两对象关系可以理解为:General Fuse 结果包含 A-only、B-only、A∩B 三类 split parts;Common、Cut、Fuse 都可以从这些 split parts 中组合出来。(Open CASCADE)
对于我们的例子:
GeneralFuse(box, cylinder)
会得到类似这些部分:
1. box 中不属于 cylinder 的部分
2. box 和 cylinder 重叠的圆柱段
3. cylinder 超出 box 顶部的部分
常用低层 API 是:
BOPAlgo_Builder
示意代码:
TopoDS_Shape runGeneralFuse()
{
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
TopTools_ListOfShape args;
args.Append(box);
args.Append(cylinder);
BOPAlgo_Builder builder;
builder.SetArguments(args);
builder.Perform();
if (builder.HasErrors()) {
builder.DumpErrors(std::cerr);
return {};
}
return builder.Shape();
}
BRepAlgoAPI_BuilderAlgo 是 General Fuse algorithm 的 API 层基类;BRepAlgoAPI_Splitter 也是基于 General Fuse 的算法。(Open CASCADE)
CellsBuilder 适合处理更复杂的布尔表达式,比如:
(A ∪ B) - C
A ∩ B ∩ C
A - (B ∪ C)
它的思路是:先通过 General Fuse 把输入 shape 切分成很多 cell,然后你自己选择哪些 cell 进入结果。OCCT 官方介绍中说,Cells Builder 可用于在同一批 shape 上执行多个标准 Boolean 操作,或者构造自定义布尔表达式;它通过一次 intersection 获得 split parts,然后按需要选择 cell。(Open CASCADE)
概念上:
General Fuse:
给你所有切分后的 cell
CellsBuilder:
让你挑选需要的 cell,并组成最终结果
在 OCCT Boolean API 里容易混淆的是:
SetArguments(...)
SetTools(...)
对于 Cut、Fuse、Common、Splitter 来说,可以这样理解:
Arguments ≈ Objects
Tools = Tools
即:
algo.SetArguments(objects);
algo.SetTools(tools);
对于 Cut:
Result = Objects - Tools
对于 Fuse:
Result = Objects ∪ Tools
对于 Common:
Result = Objects ∩ Tools
对于 Splitter:
Result = Objects 被 Tools 切分后的 parts
OCCT 的 BRepAlgoAPI_BooleanOperation 文档说明,Boolean operation 的参数被分成 Objects 和 Tools 两组,并且支持 FUSE、COMMON、CUT、SECTION 等操作类型。(Open CASCADE)
日常写 Boolean 操作时,可以统一写成下面这种风格:
TopoDS_Shape runBoolean()
{
TopoDS_Shape object = makeBox();
TopoDS_Shape tool = makeCylinder();
TopTools_ListOfShape objects;
objects.Append(object);
TopTools_ListOfShape tools;
tools.Append(tool);
BRepAlgoAPI_Cut algo; // 可换成 Fuse / Common / Section / Splitter
algo.SetArguments(objects);
algo.SetTools(tools);
algo.Build();
if (algo.HasErrors()) {
algo.DumpErrors(std::cerr);
return {};
}
algo.DumpWarnings(std::cerr);
return algo.Shape();
}
这里的:
BRepAlgoAPI_Cut algo;
可以替换为:
BRepAlgoAPI_Fuse algo;
BRepAlgoAPI_Common algo;
BRepAlgoAPI_Section algo;
BRepAlgoAPI_Splitter algo;
TopoDS_Shape #除了你明确知道结果一定是 solid,否则 Boolean 结果建议先用:
TopoDS_Shape
例如:
TopoDS_Shape result = section.Shape();
不要直接假设:
TopoDS_Solid result = section.Shape(); // 不推荐
因为 Section 的结果通常是 edges,不是 solid。
Section 结果需要遍历 edge #Section 返回的通常是一个 compound,里面包含若干 edge。可以这样取出:
TopoDS_Shape sectionShape = runSection();
for (TopExp_Explorer exp(sectionShape, TopAbs_EDGE); exp.More(); exp.Next()) {
TopoDS_Edge edge = TopoDS::Edge(exp.Current());
Standard_Real first = 0.0;
Standard_Real last = 0.0;
Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, first, last);
if (!curve.IsNull()) {
std::cout << "Edge curve range: "
<< first << " ~ " << last << std::endl;
}
}
错误写法:
const TopoDS_Solid& makeBox()
{
TopoDS_Solid box = BRepPrimAPI_MakeBox(100, 80, 30).Solid();
return box; // 错误:返回局部变量引用
}
正确写法:
TopoDS_Solid makeBox()
{
TopoDS_Solid box = BRepPrimAPI_MakeBox(100, 80, 30).Solid();
return box;
}
调用处也建议按值接收:
TopoDS_Solid box = makeBox();
TopoDS_Solid cylinder = makeCylinder();
Fuse:
合并,A ∪ B
Cut:
减去,A - B
Common:
只保留重叠体积,A ∩ B
Section:
只要相交线 / 相交点,不要体积
Splitter:
用 Tool 切分 Object,但 Object 的材料不删除
General Fuse:
所有输入互相切分,保留所有 split parts
CellsBuilder:
从 split cells 中自己挑选要保留的部分
最常用的五个 API 是:
BRepAlgoAPI_Fuse
BRepAlgoAPI_Cut
BRepAlgoAPI_Common
BRepAlgoAPI_Section
BRepAlgoAPI_Splitter
更底层或更灵活的算法是:
BOPAlgo_Builder // General Fuse
BOPAlgo_CellsBuilder // 自定义 cell 组合 / 复杂布尔表达式
(完)