Extracting Outer and Inner Wire Loops from Faces in Open CASCADE
Paramteer Space Orientation Rules
In Open CASCADE's geometric modeling kernel, the distinction between outer and inner wire loops relies on the orientation of wires within the face's parameter domain. When traversing a wire in the positive direction, if the region to your left corresponds to the face interior while the region to your right represents the exterior, that wire constitutes an outer loop. Conversely, if the left side lies outside the face and the right side lies inside, the wire represents an inner loop (hole).
This orientation-based classification proves essential for geometric computations on topological faces, ensuring correct results during boolean operations, intersections, and trimming operations on complex 3D models.
Extraction Workflow
Obtaining outer and inner wire loops from a face involves four key stages:
1. Enumerate all wire loops: Extract every wire loop (TopoDS_Wire) belonging to the target face (TopoDS_Face).
2. Locate the outer wire: Identify which wire serves as the outer boundary. In parameter space, counterclockwise-oriented wires typically represent outer boundaries.
3. Compute signed area: Use vector cross-product methods to calculate each wire's signed area. Counterclockwise loops yield positive areas, while clockwise loops produce negative values. This metric aids in distinguishing loop types.
4. Partition the wires: Separate the outer wire from the remaining loops to isolate all inner wires.
Implementation Example
The following code demonstrates wire extraction using the BRepBuilderAPI and ShapeAnalysis components:
#include <gp_Circ.hxx>
#include <gp_Pln.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <ShapeAnalysis.hxx>
#include <TopExp.hxx>
#include <TopTools_IndexedMapOfShape.hxx>
#include <TopoDS.hxx>
#include "Viewer.h"
int main(int argc, char* argv[])
{
gp_Pln basePlane;
// Define three circular boundaries at different locations
gp_Circ outerCircle(gp::XOY(), 2.0);
outerCircle.SetLocation(gp_Pnt(5.0, 5.0, 0.0));
gp_Circ innerCircle1(gp::XOY(), 0.8);
innerCircle1.SetLocation(gp_Pnt(4.0, 5.0, 0.0));
gp_Circ innerCircle2(gp::XOY(), 0.8);
innerCircle2.SetLocation(gp_Pnt(6.0, 5.0, 0.0));
// Construct edges from circles
BRepBuilderAPI_MakeEdge edgeBuilder1(outerCircle);
BRepBuilderAPI_MakeEdge edgeBuilder2(innerCircle1);
BRepBuilderAPI_MakeEdge edgeBuilder3(innerCircle2);
// Convert edges to wire loops
BRepBuilderAPI_MakeWire wireBuilder1(edgeBuilder1.Edge());
BRepBuilderAPI_MakeWire wireBuilder2(edgeBuilder2.Edge());
BRepBuilderAPI_MakeWire wireBuilder3(edgeBuilder3.Edge());
// Initialize face with bounding rectangle
BRepBuilderAPI_MakeFace faceBuilder(basePlane, 0.0, 10.0, 0.0, 10.0);
// Add wires to face builder (reverse orientation for inner loops)
if (wireBuilder1.IsDone()) {
faceBuilder.Add(wireBuilder1.Wire());
}
if (wireBuilder2.IsDone()) {
TopoDS_Wire wire2 = wireBuilder2.Wire();
wire2.Reverse();
faceBuilder.Add(wire2);
}
if (wireBuilder3.IsDone()) {
TopoDS_Wire wire3 = wireBuilder3.Wire();
wire3.Reverse();
faceBuilder.Add(wire3);
}
// Extract outer boundary using ShapeAnalysis utility
TopoDS_Face targetFace = BRepBuilderAPI_MakeFace(faceBuilder);
TopoDS_Wire outerLoop = ShapeAnalysis::OuterWire(targetFace);
// Map all wire shapes in the face
TopTools_IndexedMapOfShape wireMap;
TopExp::MapShapes(targetFace, TopAbs_WIRE, wireMap);
// Collect inner wires by excluding the outer boundary
std::vector<TopoDS_Wire> holeLoops;
for (int i = 1; i <= wireMap.Extent(); i++) {
if (!wireMap(i).IsSame(outerLoop)) {
holeLoops.emplace_back(TopoDS::Wire(wireMap(i)));
}
}
// Visualize results
Viewer viewer(50, 50, 500, 500);
viewer << targetFace;
viewer << outerLoop;
for (const auto& hole : holeLoops) {
viewer << hole;
}
viewer.StartMessageLoop();
return 0;
}
Type Conversion Consideration
When populating a container of TopoDS_Wire objects from a shape map, direct insertion causes compilation errors:
// This produces a compile-time error
holeLoops.emplace_back(wireMap(i));
The shape map stores generic TopoDS_Shape objects that require explicit type conversion. Use the TopoDS::Wire cast function to perform the necessary type narrowing:
// Correct approach with explicit type conversion
holeLoops.emplace_back(TopoDS::Wire(wireMap(i)));
The TopoDS::Wire function performs safe down-casting from the general shape type to the wire subtype, resolving the type mismatch issue.