OCCT “拓扑环线重组算法” (Topological Wire Recombination Algorithm)
TopoDS_Shape XXX::CreateRebuildSurface(const TopTools_IndexedMapOfShape& selectedFaces){qDebug() << \"开始曲面重建(环线重组模式),面数量:\" << selectedFaces.Extent();try {// 1. 验证输入 (与之前版本相同)if (selectedFaces.IsEmpty()) { return TopoDS_Shape(); }if (selectedFaces.Extent() == 1) { return selectedFaces(1); }// 2. 检查所有面是否都是平面且共面,并获取参考平面 (与之前版本相同)gp_Pln refPlane;bool refPlaneSet = false;for (Standard_Integer i = 1; i <= selectedFaces.Extent(); i++) {const TopoDS_Shape& shape = selectedFaces(i);if (shape.IsNull() || shape.ShapeType() != TopAbs_FACE) { /* ... */ return TopoDS_Shape(); }const TopoDS_Face& face = TopoDS::Face(shape);Handle(Geom_Surface) surface = BRep_Tool::Surface(face);Handle(Geom_Plane) plane = Handle(Geom_Plane)::DownCast(surface);if (plane.IsNull()) { /* ... */ return TopoDS_Shape(); }if (!refPlaneSet) {refPlane = plane->Pln();refPlaneSet = true;}else {if (!refPlane.Axis().Direction().IsParallel(plane->Pln().Axis().Direction(), Precision::Angular()) ||Abs(refPlane.Distance(gp::Origin()) - plane->Pln().Distance(gp::Origin())) > Precision::Confusion()) {qDebug() << \"错误:选择的面不是共面的平面\";return TopoDS_Shape();}}}qDebug() << \"所有面都是共面的平面\";// ======================= 核心算法:环线重组 =======================// 3. 收集所有面上的所有环线 (Wires)TopTools_ListOfShape allWires;for (Standard_Integer i = 1; i <= selectedFaces.Extent(); ++i) {TopExp_Explorer wireExplorer(selectedFaces(i), TopAbs_WIRE);for (; wireExplorer.More(); wireExplorer.Next()) {allWires.Append(wireExplorer.Current());}}qDebug() << \"收集到总共 \" << allWires.Extent() << \" 条环线。\";// 4. 识别出最外层的环线 (Outer Wire)// 逻辑:最外层的环线不被任何其他环线所包含。TopoDS_Wire outerWire;TopTools_ListIteratorOfListOfShape candidateIter(allWires);for (; candidateIter.More(); candidateIter.Next()) {const TopoDS_Wire& candidateWire = TopoDS::Wire(candidateIter.Value());bool isOuter = true;// 获取候选环线上的一个点用于测试TopExp_Explorer vertexExplorer(candidateWire, TopAbs_VERTEX);if (!vertexExplorer.More()) {qDebug() << \"警告:环线没有顶点,无法进行包含测试,已跳过。\";continue;}gp_Pnt testPoint = BRep_Tool::Pnt(TopoDS::Vertex(vertexExplorer.Current()));// 检查这个候选环线是否被其他任何环线包含TopTools_ListIteratorOfListOfShape containerIter(allWires);for (; containerIter.More(); containerIter.Next()) {const TopoDS_Wire& containerWire = TopoDS::Wire(containerIter.Value());if (candidateWire.IsSame(containerWire)) continue; // 不和自己比较// 用 BRepClass_FaceClassifier 来判断点是否在环线内部BRepClass_FaceClassifier classifier;classifier.Perform(BRepBuilderAPI_MakeFace(refPlane, containerWire), testPoint, Precision::Confusion());if (classifier.State() == TopAbs_IN) {isOuter = false; // 如果被包含,则它不是最外层环线break;}}if (isOuter) {outerWire = candidateWire;qDebug() << \"成功识别出最外层环线。\";break; // 找到后即可退出}}if (outerWire.IsNull()) {qDebug() << \"错误:无法在所有环线中识别出最外层环线。\";return TopoDS_Shape();}// 5. 创建新的面,并添加内孔环线// 使用识别出的最外层环线创建基础面BRepBuilderAPI_MakeFace faceMaker(refPlane, outerWire);if (!faceMaker.IsDone()) {qDebug() << \"错误:使用最外层环线创建基础面失败。\";return TopoDS_Shape();}// 将除了最外层环线以外的所有其他环线作为孔(Hole)添加int holeCount = 0;TopTools_ListIteratorOfListOfShape holeIter(allWires);for (; holeIter.More(); holeIter.Next()) {const TopoDS_Wire& holeWire = TopoDS::Wire(holeIter.Value());if (holeWire.IsSame(outerWire)) continue; // 跳过最外层环线本身faceMaker.Add(holeWire);holeCount++;}qDebug() << \"已添加 \" << holeCount << \" 条内孔环线。\";// 6. 检查、验证并返回最终的面if (!faceMaker.IsDone() || faceMaker.Face().IsNull()) {qDebug() << \"错误:添加内孔后,面构建失败。\";return TopoDS_Shape();}TopoDS_Face resultFace = faceMaker.Face();BRepCheck_Analyzer analyzer(resultFace);if (!analyzer.IsValid()) {qDebug() << \"警告:重建的面可能有几何问题。\";}qDebug() << \"曲面重建成功!\";GProp_GProps resultProps;BRepGProp::SurfaceProperties(resultFace, resultProps);qDebug() << \"重建后最终面积:\" << resultProps.Mass();return resultFace;}catch (const Standard_Failure& e) {qDebug() << \"OCCT异常:\" << e.GetMessageString();return TopoDS_Shape();}catch (...) {qDebug() << \"未知异常发生在曲面重建过程中\";return TopoDS_Shape();}}
此算法的核心思想是 “分解与重构”。它完全忽略了输入面的原始组合方式(哪个是大面,哪个是小面),而是将所有输入面分解成它们最基本的拓扑元素——边界环线(Wires)。然后,它在所有这些环线中,通过拓扑分析找出唯一的“最外层”环线,并以此为基础,将所有其他环线作为内孔,重构出一个全新的、合并后的面。
它与布尔运算的根本区别在于:
- 布尔运算是“几何体”的加减法,像是在一个实体上用另一个实体进行“雕刻”。
- 本算法是“拓扑边界”的筛选与组合,像是将一堆拼图的边框碎片(环线)重新拼成一幅完整的画。
详细步骤详解
让我们一步步分解这个精妙的算法:
第1步:前置处理与环境准备
- 代码部分: 函数开头的验证和共面性检查。
- 详解: 这是算法的“安全检查”阶段。它确保所有输入的面片都在同一个数学平面上,为后续的2D拓扑分析提供了统一的几何环境(
refPlane)。没有这个统一的平面,就无法判断一个环线是否在另一个内部。
第2步:边界提取(“分解”阶段)
- 代码部分:
TopTools_ListOfShape allWires;for (Standard_Integer i = 1; i <= selectedFaces.Extent(); ++i) { TopExp_Explorer wireExplorer(selectedFaces(i), TopAbs_WIRE); for (; wireExplorer.More(); wireExplorer.Next()) { allWires.Append(wireExplorer.Current()); }} - 详解: 这是算法的第一步核心操作。它遍历用户选择的所有面,无差别地将每一个面的所有边界环线(
TopoDS_Wire)提取出来,放入一个统一的列表allWires中。 - 比喻: 想象一下,你有一张大纸(大面)和几张剪下来的小纸片(小面)。这一步相当于你忽略纸张本身,只用笔描出所有纸张的轮廓线,然后把这些轮廓线放在一张新的透明画板上。原始的纸张被丢弃,只留下了它们的边界信息。
第3步:最外层轮廓识别(“分析”与“筛选”阶段)
- 代码部分: 整个
for循环寻找outerWire的部分。 - 详解: 这是整个算法最核心、最巧妙的部分。现在我们有一堆杂乱的环线,必须找出哪一条是包围所有其他环线的最外层边界。
- “候选人”策略: 算法从环线列表
allWires中任意挑选一个candidateWire作为“最外层候选人”。 - “资格审查”: 为了验证这个候选人是否名副其实,算法会用列表中的所有其他环线
containerWire来对它进行测试。 - 测试方法:
- 从“候选人”
candidateWire上取一个点testPoint。 - 用另一个环线
containerWire临时构建一个测试面。 - 使用强大的
BRepClass_FaceClassifier工具,判断testPoint是否位于这个临时面的内部 (TopAbs_IN)。
- 从“候选人”
- 得出结论:
- 如果
testPoint在任何一个containerWire构成的面内部,就证明我们当前的“候选人”被别人包住了,它不可能是最外层的。于是审查失败 (isOuter = false),立即停止对这个候选人的测试,换下一个候选人。 - 如果一个“候选人”通过了所有其他环线的测试(即它不在任何其他环线的内部),那么它就一定是那条唯一的“最外层环线”。算法立即确认它的身份,并跳出循环。
- 如果
- “候选人”策略: 算法从环线列表
第4步:新面重构(“重构”阶段)
- 代码部分:
BRepBuilderAPI_MakeFace faceMaker(...)和faceMaker.Add(...)。 - 详解: 找到最外层环线后,构建新面就变得非常简单清晰。
- 创建外壳: 首先,使用
BRepBuilderAPI_MakeFace,以第1步中确定的公共平面refPlane和第3步中找到的outerWire作为参数,创建一个只有外边界的新面。 - 挖孔: 然后,再次遍历环线列表
allWires。这一次,将所有不是outerWire的环线,通过faceMaker.Add()方法,作为内孔(Hole)添加到正在构建的面中。OCCT的这个API会自动处理好环线的方向问题,确保它们能正确地形成孔洞。
- 创建外壳: 首先,使用
第5步:结果验证与输出
- 代码部分: 函数结尾的
BRepCheck_Analyzer和return。 - 详解: 最后,对生成的新面进行几何和拓扑有效性检查,确保没有产生无效的几何体,然后返回这个完美的、重组后的最终结果。
算法优势与特点
- 逻辑精确: 完美地实现了“将所有环线进行重组”的核心描述,过程清晰,逻辑严谨。
- 拓扑驱动: 算法的健壮性不依赖于输入的几何体是怎样的(一个大面+N个小面,还是N个重叠的面),只要它们的边界环线能在拓扑上构成内外关系,就能正确工作。
- 高鲁棒性: 相比简单的
MakeFace后Reverse(),这种方法从根本上解决了内外边界识别的问题,因此结果总是正确的,不会出现“大面被挖掉,保留小孔”的错误。 - 扩展性: 这个思想可以扩展到更复杂的场景,例如处理“岛中湖,湖中岛”这类多层嵌套的环线结构。


