Correct usage of ShapeAnalysis_FreeBounds::ConnectEdgesToWires?

The following code demonstrates a method to extract the edges and wires from a section through a shape. It shows that the curves returned from the section are BSplines with 101 poles. The example code shows how to simplify these BSplines to 4 pole approximations and create replacement edges from these new simplified curves. The example code then attempts to rebuild the wires from the collection of edges (with some of the edges simplified).

Issue: The ShapeAnalysis_FreeBounds::ConnectEdgesToWires method does not construct the wires properly. Can anyone spot the error in this code? BTW, I have tried changing the tolerance value in the call to ConnectEdgeToWires and this makes no difference.

I have tried many may variations of this code. I have tried using a BRepBuilderAPI_MakeWire and adding the edges one at a time and this fails with a disconnected wire error. However, inspection of the verticies of the edges show that there are conincident verticies in the previously added and current wire at the time of failure -- yes I checked the tolerance setting. It may be that there is some magic in edge creation I do not yet understand?

I have included the output from the trace statements in the code at the bottom of the post. I have also attached a jpeg of the sectioned shape so you can grasp the shape quickly.

Even if no one can help with the wire creation error, I hope someone will find the demonstrated technique for finding the wires and ordered edges in a section useful. I could not find any complete or useful examples in the forum or documentation.

//Create a simple shape using a face extrusion.
//First construct the wire
TColgp_Array1OfPnt aPoles (1,4);
aPoles(1) = gp_Pnt(0,10,0);
aPoles(2) = gp_Pnt(3,12,0);
aPoles(3) = gp_Pnt(7,12,0);
aPoles(4) = gp_Pnt(10,10,0);
Handle(Geom_BezierCurve) aCurve = new Geom_BezierCurve(aPoles);
BRepBuilderAPI_MakeWire MainShape_Wire;
MainShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(0,0,0),gp_Pnt(0,10,0)));
MainShape_Wire.Add(BRepBuilderAPI_MakeEdge(aCurve));
MainShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(10,10,0),gp_Pnt(10,0,0)));
MainShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(10,0,0),gp_Pnt(0,0,0)));
//Construct a Face from the wrie
TopoDS_Face MainShape_Face = BRepBuilderAPI_MakeFace(MainShape_Wire);
//Construct a thick solid by extruding the face
TopoDS_Shape MainShape_Shape = BRepPrimAPI_MakePrism(MainShape_Face,gp_Vec(0,0,1));

//Make a second shape and "cut" into the first to create a more interesting shape.
//Note that this shape is similar to the first shape -- just smaller.
TColgp_Array1OfPnt aPoles2 (1,4);
aPoles2(1) = gp_Pnt(3,8,0);
aPoles2(2) = gp_Pnt(5,9,0);
aPoles2(3) = gp_Pnt(6,9,0);
aPoles2(4) = gp_Pnt(7,8,0);
Handle(Geom_BezierCurve) aCurve2 = new Geom_BezierCurve(aPoles2);
BRepBuilderAPI_MakeWire InnerShape_Wire;
InnerShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(3,3,0),gp_Pnt(3,8,0)));
InnerShape_Wire.Add(BRepBuilderAPI_MakeEdge(aCurve2));
InnerShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(7,8,0),gp_Pnt(7,3,0)));
InnerShape_Wire.Add(BRepBuilderAPI_MakeEdge(gp_Pnt(7,3,0),gp_Pnt(3,3,0)));
//Construct a Face from the wrie
TopoDS_Face InnerShape_Face = BRepBuilderAPI_MakeFace(InnerShape_Wire);
//Construct a thick solid by extruding the face
TopoDS_Shape InnerShape_Shape = BRepPrimAPI_MakePrism(InnerShape_Face,gp_Vec(0,0,1));

//Cut the MainShape with the InnerShape
BRepAlgoAPI_Cut aCutOp(MainShape_Shape,InnerShape_Shape);
if(!aCutOp.IsDone())
{ //Always check if your boolean operation succeeded.
TRACE("Cut failure\n");
return;
}
MainShape_Shape = aCutOp.Shape();

//Take a section through the middle of the shape on the XY plane.
Standard_Real nMidThickness = 0.5;
BRepAlgoAPI_Section aSect(MainShape_Shape, gp_Pln(gp_Pnt(0,0,nMidThickness),gp::DZ()),Standard_False);
aSect.ComputePCurveOn1(Standard_True);
aSect.ComputePCurveOn2(Standard_True);
aSect.Approximation(Standard_True);
aSect.Build();

//Create and display interactive shape for the new TopoDS_Shape created by section
Handle(AIS_Shape) MainShape_AISShape = new AIS_Shape(aSect.Shape());
myAISContext->SetColor(MainShape_AISShape,Quantity_NOC_AZURE);
myAISContext->SetMaterial(MainShape_AISShape,Graphic3d_NOM_PLASTIC);
myAISContext->SetDisplayMode(MainShape_AISShape,1);
myAISContext->Display(MainShape_AISShape);

//Collect all the edges of the section. These are not returned in any particular order.
Handle(TopTools_HSequenceOfShape) Edges = new TopTools_HSequenceOfShape();
for (TopExp_Explorer Ex(aSect.Shape(),TopAbs_EDGE); Ex.More(); Ex.Next())
Edges->Append(TopoDS::Edge(Ex.Current()));

//Example Operation 1: Generate wires from the edges and determined the nature of the underlying curves.
Handle(TopTools_HSequenceOfShape) Wires = new TopTools_HSequenceOfShape(); //Will hold the wires found
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(Edges,Precision::Confusion(),Standard_True,Wires); //Wonderful tool
TRACE("Original Edges\n");
for(int w=1; wLength();w++)
{ //For each wire
int e = 0;
for(BRepTools_WireExplorer aWireExp(TopoDS::Wire(Wires->Value(w))); aWireExp.More(); aWireExp.Next())
{ //For each edge in wire. Note that BRepTools_WireExplorer returns edges in order!
TopoDS_Edge aEdge = TopoDS::Edge(aWireExp.Current());
//Analysis of Edge
Standard_Real First, Last;
Handle(Geom_Curve) curve = BRep_Tool::Curve(aEdge,First,Last); //Extract the curve from the edge
GeomAdaptor_Curve aAdaptedCurve(curve);
GeomAbs_CurveType curveType = aAdaptedCurve.GetType();
gp_Pnt pnt1, pnt2;
aAdaptedCurve.D0(First,pnt1);
aAdaptedCurve.D0(Last,pnt2);
int nPoles = 2;
if (curveType == GeomAbs_BezierCurve || curveType == GeomAbs_BSplineCurve)
nPoles = aAdaptedCurve.NbPoles();
TRACE("%ld\t%ld\t%ld\t%ld\t%ld\t%f\t%f\t%f\t%f\t%f\t%f\n",w,e,curveType,aEdge.Orientation(),nPoles,
pnt1.X(),pnt1.Y(),pnt1.Z(),pnt2.X(),pnt2.Y(),pnt2.Z());
e++;
}
}

//Example Operation2: Create a new shape from edges extracted from the MainShape_Shape and simplify each
//BSpline curve found down to a 4 pole curve where possible.
Handle(TopTools_HSequenceOfShape) NewEdges = new TopTools_HSequenceOfShape(); //Will hold the new Edges for new shape
TRACE("Simplified Edges\n");
for(int w=1; wLength();w++)
{ //For each wire
int e = 0;
for(BRepTools_WireExplorer aWireExp(TopoDS::Wire(Wires->Value(w))); aWireExp.More(); aWireExp.Next())
{ //For each edge in wire. Note that BRepTools_WireExplorer returns edges in order!
TopoDS_Edge aEdge = TopoDS::Edge(aWireExp.Current());
//Analysis of Edge
int aOrientation = aEdge.Orientation();
Standard_Real First, Last;
Handle(Geom_Curve) curve = BRep_Tool::Curve(aEdge,First,Last); //Extract the curve from the edge
GeomAdaptor_Curve aAdaptedCurve(curve);
GeomAbs_CurveType curveType = aAdaptedCurve.GetType();
gp_Pnt pnt1, pnt2;
aAdaptedCurve.D0(First,pnt1);
aAdaptedCurve.D0(Last,pnt2);
int nPoles = 2;
if (curveType == GeomAbs_BezierCurve || curveType == GeomAbs_BSplineCurve)
nPoles = aAdaptedCurve.NbPoles();
if(curveType == GeomAbs_BSplineCurve && nPoles > 4)
{ //Attempt to simplify the curve
Handle(Geom_BSplineCurve) aBSplineCurve = aAdaptedCurve.BSpline();
GeomConvert_ApproxCurve aApprox(aBSplineCurve,Precision::Confusion(),GeomAbs_C1,1,2);
aBSplineCurve = aApprox.Curve();
if(aBSplineCurve->NbPoles() == 2)
{ //Just replace with a simple line
aEdge = BRepBuilderAPI_MakeEdge(pnt1,pnt2);
if(aOrientation != aEdge.Orientation())
aEdge.Reverse();
aOrientation = aEdge.Orientation();
nPoles = 2;

}
else if(aBSplineCurve->NbPoles() == 4)
{ //We succeeded in creating a simplified curve. Now create an edge that uses the new curve.
aEdge = BRepBuilderAPI_MakeEdge(aBSplineCurve);
if(aOrientation != aEdge.Orientation())
aEdge.Reverse();
aOrientation = aEdge.Orientation();
//Analysis of new Edge
Handle(Geom_Curve) curve = BRep_Tool::Curve(aEdge,First,Last); //Extract the curve from the edge
GeomAdaptor_Curve adaptedCurve(curve);
GeomAbs_CurveType curveType = adaptedCurve.GetType();
gp_Pnt pnt1, pnt2;
adaptedCurve.D0(First,pnt1);
adaptedCurve.D0(Last,pnt2);
nPoles = adaptedCurve.NbPoles();
}
else
{ //The curve simplification failed. Sometimes it returns a curve with 3 poles!
//We will just use the original curve.
}
}
NewEdges->Append(aEdge); //Add edge to list of edges we will try and create new wires from.
TRACE("%ld\t%ld\t%ld\t%ld\t%ld\t%f\t%f\t%f\t%f\t%f\t%f\n",w,e,curveType,aOrientation,nPoles,
pnt1.X(),pnt1.Y(),pnt1.Z(),pnt2.X(),pnt2.Y(),pnt2.Z());
e++;
}
}
//Now try and create wires from NewEdges
Handle(TopTools_HSequenceOfShape) NewWires = new TopTools_HSequenceOfShape(); //Will hold the wires found
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(NewEdges,Precision::Confusion(),Standard_True,NewWires); //Wonderful tool
TRACE("Reconstituted Wire Edges\n");
for(int w=1; wLength();w++)
{ //For each wire
int e = 0;
for(BRepTools_WireExplorer aWireExp(TopoDS::Wire(NewWires->Value(w))); aWireExp.More(); aWireExp.Next())
{ //For each edge in wire. Note that BRepTools_WireExplorer returns edges in order!
TopoDS_Edge aEdge = TopoDS::Edge(aWireExp.Current());
//Analysis of Edge
Standard_Real First, Last;
Handle(Geom_Curve) curve = BRep_Tool::Curve(aEdge,First,Last); //Extract the curve from the edge
GeomAdaptor_Curve aAdaptedCurve(curve);
GeomAbs_CurveType curveType = aAdaptedCurve.GetType();
gp_Pnt pnt1, pnt2;
aAdaptedCurve.D0(First,pnt1);
aAdaptedCurve.D0(Last,pnt2);
int nPoles = 2;
if (curveType == GeomAbs_BezierCurve || curveType == GeomAbs_BSplineCurve)
nPoles = aAdaptedCurve.NbPoles();
TRACE("%ld\t%ld\t%ld\t%ld\t%ld\t%f\t%f\t%f\t%f\t%f\t%f\n",w,e,curveType,aEdge.Orientation(),nPoles,
pnt1.X(),pnt1.Y(),pnt1.Z(),pnt2.X(),pnt2.Y(),pnt2.Z());
e++;
}
}

myView->SetComputedMode (Standard_False);
myView->FitAll();
myView->ZFitAll();
myView->Update();

//TRACE OUTPUT
Original Edges
1 0 0 0 2 10.000000 10.000000 0.500000 10.000000 0.000000 0.500000
1 1 0 0 2 10.000000 0.000000 0.500000 0.000000 0.000000 0.500000
1 2 0 0 2 0.000000 0.000000 0.500000 0.000000 10.000000 0.500000
1 3 6 1 101 10.000000 10.000000 0.500000 0.000000 10.000000 0.500000
2 0 0 0 2 7.000000 3.000000 0.500000 3.000000 3.000000 0.500000
2 1 0 0 2 3.000000 3.000000 0.500000 3.000000 8.000000 0.500000
2 2 6 1 101 7.000000 8.000000 0.500000 3.000000 8.000000 0.500000
2 3 0 0 2 7.000000 8.000000 0.500000 7.000000 3.000000 0.500000
Simplified Edges
1 0 0 0 2 10.000000 10.000000 0.500000 10.000000 0.000000 0.500000
1 1 0 0 2 10.000000 0.000000 0.500000 0.000000 0.000000 0.500000
1 2 0 0 2 0.000000 0.000000 0.500000 0.000000 10.000000 0.500000
1 3 6 1 4 10.000000 10.000000 0.500000 0.000000 10.000000 0.500000
2 0 0 0 2 7.000000 3.000000 0.500000 3.000000 3.000000 0.500000
2 1 0 0 2 3.000000 3.000000 0.500000 3.000000 8.000000 0.500000
2 2 6 1 4 7.000000 8.000000 0.500000 3.000000 8.000000 0.500000
2 3 0 0 2 7.000000 8.000000 0.500000 7.000000 3.000000 0.500000
Reconstituted Wire Edges
1 0 0 0 2 10.000000 10.000000 0.500000 10.000000 0.000000 0.500000
1 1 0 0 2 10.000000 0.000000 0.500000 0.000000 0.000000 0.500000
1 2 0 0 2 0.000000 0.000000 0.500000 0.000000 10.000000 0.500000
2 0 6 1 4 10.000000 10.000000 0.500000 0.000000 10.000000 0.500000
3 0 0 0 2 7.000000 8.000000 0.500000 7.000000 3.000000 0.500000
3 1 0 0 2 7.000000 3.000000 0.500000 3.000000 3.000000 0.500000
3 2 0 0 2 3.000000 3.000000 0.500000 3.000000 8.000000 0.500000
4 0 6 1 4 7.000000 8.000000 0.500000 3.000000 8.000000 0.500000

Attachments: 
tmay's picture

Found the problem with my call to ConnectEdgesToWires. The third argument should be Standard_False. This poorly documented argument has the signature "const Standard_Boolean shared". Another member function (SplitWires) documents it's argument "shared" this way:

If is True extraction is performed only when
edges share the same vertex.
If is False connection is performed only when
ends of the edges are at distance less than .

This documentation from SplitWires seems confused as to what the function is doing? eg: extraction versus connection. However, it does allow us to "guess" the meaning of the shared argment in ConnectEdgesToWires.

My "guess" is that the documentation for the argument "shared" in ConnectEdgesToWires would read:

If is True connection is performed only when
edges share the same vertex.
If is False connection is performed only when
ends of the edges are at distance less than or
when edges share the same vertex.

In any case, the shared argument to ConnectEdgesToWires being set to false makes a lot of sense in my example code. When we build new edges after simplifying a curve it is clear that they do no longer share the same vertex. By passing Standard+False as the value of the "shared" argument we are asking the routine to discover connections based on proximity of independant vertices in addition to shared verticies.

As usual, the devil is in the details.

Cheers and Happy Holidays,
Tim