Open CASCADE notes

A blog about the Open Source 3D modeling kernel: notes from its former developer and project manager

People on the Open CASCADE forum are focused on technical stuff asking for help in particular algorithms usage. I'm often curious what their problem domains are. I have witnessed OpenCASCADE being used in so different areas (medicine, ship building, CAM/CNC, geology and what not) and every time these looked impressive. So, where do *you* apply Open CASCADE ?

If you want to promote your project here, insert a link, an image etc just go ahead and advertise ;-). Be proud to present your work to other readers!

Eager to see your comments ...
Share
Tweet
Pin
Share
13 comments
Continued...

Back references
As you likely noticed using OpenCASCADE or analyzing the diagram in Part1, shapes refer to their sub-shapes and not the other way round. This is understandable as the same (sub-)shape can belong to multiple parent shapes. For instance, any shared edge will belong to at least two faces.
However it is sometimes needed to trace parent shape back from a child. To do this use TopExp::MapShapesAndAncestors().

TopTools_IndexedDataMapOfShapeListOfShape anEFsMap;
TopExp::MapShapesAndAncestors (myShape, TopAbs_EDGE, TopAbs_FACE, anEFsMap);

The code above fills out a map of parent faces for each edge in myShape. If myShape is a solid box, each edge will map to 2 faces. If you explore the same box into faces and try to fill out edge's ancestors in context of each face, then obviously the map will contain a single face for each edge – that very face you are currently in.

Adaptors
Some Open CASCADE algorithms can work on objects representing a curve. However they provide an API that does not accept Geom_Curve but rather Adaptor3d_Curve. For instance, Extrema does so what enables its use in intersection, projection and other algorithms both on geometrical curves (Geom_Curve) and topological edges (TopoDS_Edge). Other examples – calculation of lengths, or surface areas. This approach is known as Adapter pattern.
GeomAdaptor3d_Curve subclasses Adaptor3d_Curve to ‘adapt' Geom_Curve, BRepAdaptor_Curve to TopoDS_Edge, BRepAdaptor_CompCurve to TopoDS_Wire. There are similar classes for 2D curves and surfaces.

So you could write the following to measure lengths of a curve and an edge:

Handle(Geom_Curve) aCurve = ...;
Standard_Real aCurveLen = GCPoints_AbscissaPoints::Length (GeomAdaptor_Curve (aCurve));

TopoDS_Edge anEdge = ...;
Standard_Real anEdgeLen = GCPoints_AbscissaPoints::Length (BRepAdaptor_Curve (anEdge));

Conclusion
So, this has been a long story about fundamental concepts of Open CASCADE. Hope you are now more familiar with them and understand what geometry and topology are. As a first step, make yourself correctly use the terms – curve or edge, surface or face, point or vertex. This will help you and people reading your questions clearly distinguish if you mean geometry or topology. Once you have started using correct definitions and keeping in mind their distinctions, part of your problems may simply go away.

That's it! Full up with this elephant ;-) ?
Share
Tweet
Pin
Share
23 comments
Continued...

Other topology types
So far we considered vertex, edge, and face – those which have connection with geometry. The rest – wire, shell, solid, compsolid and compound – do not connect with geometry directly and are just containers for other topological entities:
- wire consists of edge(s);
- shell – of face(s);
- solid – of shell(s);
- compsolid – of solid(s) sharing common face(s);
- compound – of any arbitrary type (including compound).

A minor note on solids. A solid is expected to contain a single shell that describes its external boundary. If there are two or more shells, it's considered to be a solid with voids but Open CASCADE can be not too robust to work with such bodies. So beware.



Iteration over children
The are two ways to iterate over child subshapes.
1. Direct children can be retrieved using TopoDS_Iterator.
The following function will traverse through entire shape structure:
void TraverseShape (const TopoDS_Shape& theShape)
{
TopoDS_Iterator anIt (theShape);
for (; anIt.More(); anIt.Next()) {
const TopoDS_Shape& aChild = anIt.Value();
TraverseShape (aChild);
}
}
TopoDS_Iterator has two flags specifying whether to take into account location and orientation of a parent shape when extracting a child. If the location flag is on then any child is returned as if it were a standalone shape and placed exactly at its location in 3D space (i.e. the user would see an extracted edge right where it is displayed in the context of its parent wire). If the orientation flag is on, then returned child orientation will be a product of a parent and own child orientation (e.g. two reversed or forward will give forward, reversed and forward in any combination will give reversed).
If flags are off, then a child shape is returned with its own location and orientation as stored inside (recall diagram 2 in Part1). By default both flags are on.

2. Children of a particular subtype
If you want to retrieve all edges of your shape you can do the following:
TopExp_Explorer anExp (theShape, TopAbs_EDGE);
for (; anExp.More(); anExp.Next()) {
const TopoDS_Edge& anEdge = TopoDS::Edge (anExp.Current());
//do something with anEdge
}

TopExp_Explorer has an additional parameter that specifies which parent type you want to skip. For example, if you want to retrieve only floating edges (i.e. not belonging to any face – refer to Part3), you can do the following:
TopExp_Explorer aFloatingEdgeExp (theShape; TopAbs_EDGE, TopAbs_FACE);


More on location and orientation
As already described in the previous chapters, an individual location of a geometrically bounded entity (vertex, edge, face) defines a displacement relative to its underlying geometry. Location of any topology entity (regardless if it has geometric binding or not) also defines a displacement relative to its children. For instance, if a wire has a location consisting of a translation part along the {0, 0, 10} vector then it just means that all its edges are actually translated along the Z axis by 10 units.

The same works for orientation. Parent orientation affects its children orientation when extracting them from inside the shape. There is one important exception however – which is about edge orientation within a face. If you recall Part4 we discussed there that face material lies on the left of forward edge pcurves and on the right of the reversed edge pcurves. This reversed or forward orientation of an edge must be calculated *excluding* own face orientation. That is, if you need to use edge pcurves you should rather use:

TopExp_Explorer aFaceExp (myFace.Oriented (TopAbs_FORWARD), TopAbs_EDGE);
for (; aFaceExp.More(); aFaceExp.Next()) {
const TopoDS_Edge& anEdge = TopoDS::Edge (aFaceExp.Current());
}
This exception becomes understandable if you try to understand the details. Let's construct a face bottom-up:

Handle(Geom_Surface) aSurf = new Geom_Plane (gp::XOY());

//anti-clockwise circles if too look from surface normal
Handle(Geom_Curve) anExtC = new Geom_Circle (gp::XOY(), 10.);
Handle(Geom_Curve) anIntC = new Geom_Circle (gp::XOY(), 5.);
TopoDS_Edge anExtE = BRepBuilderAPI_MakeEdge (anExtC);
TopoDS_Edge anIntE = BRepBuilderAPI_MakeEdge (anExtC);
TopoDS_Wire anExtW = BRepBuilderAPI_MakeWire (anExtE);
TopoDS_Wire anIntW = BRepBuilderAPI_MakeWire (anIntE);
BRep_Builder aB;
TopoDS_Face aFace;
aB.MakeFace (aFace, aSurf, Precision::Confusion());
aB.Update (aFace, aSurf);
aB.Add (aFace, anExtW);
aB.Add (aFace, anIntW.Reversed()); //material should lie on the right of the inner wire

aFace has a forward orientation (default). Let's explore its edges and pcurves. Though we did not explicitly add them, recall (see Part 3) that for planes they can be computed on the fly:

void TraversePCurves (const TopoDS_Face& theFace)
{
TopExp_Explorer anExp (theFace, TopAbs_EDGE);
for (; anExp.More(); anExp.Next()) {
const TopoDS_Edge& anEdge = TopoDS::Edge (anExp.Current());
Standard_Real aF, aL;
Handle(Geom2d_Curve) aPCurve = BRep_Tool::CurveOnSurface (anEdge, theFace, aF, aL);
}
}

Returned pcurves will be as shown below (material will be on the left of red and on the right of blue).



Everything is correct. Now imagine we reverse the face and explore again:

TopoDS_Face aRFace = TopoDS::Face (aFace.Reversed());
TraversePCurves (aRFace);

What will we see ? All the edges will be extracted with opposite orientations and respectively their pcurves will mean that material is beyond an external wire and within an internal wire. This is clearly wrong, though original aRFace is perfectly correct. Recall Part4 that a face orientation just shows face logical orientation regarding its underlying surface. In our case aRFace will be just aFace with a normal {0, 0, -1}.

Thus, the only way out is to do the following:
TopExp_Explorer anExp (theFace.Oriented (TopAbs_FORWARD), TopAbs_EDGE);

This will ensure that edges will show orientation regarding surface (not face!) normal. I made exact same comment in Part 4. Open CASCADE algorithms take care of this particular case, make sure so do you.

Hope orientation is now also a well swallowed bit of an elephant we have been eating in this series. I think we are almost done with it. There are a few tiny bones to pick, and unless you are still hungry we will finish it in the last post to follow. Bon appetite ;-)

To be continued...
Share
Tweet
Pin
Share
10 comments
Continued...

Though the next topology type after the edge considered in the previous post is a wire, let's jump to the face which is the last topology entity that binds with geometry. We'll consider a wire as well as the rest of topology types in the future posts. Frankly, I was not originally going to speak of them but several folks on the blog asked to.

Face
A face is a topology entity that describes a boundary unit of the 3D body. A face is described with its underlying surface and one or more wires. For instance a solid cylinder consists of 3 faces – bottom and top, and lateral. Each of respective underlying surfaces is infinite (Geom_Plane and Geom_CylindricalSurface), while each face bounds its surface with a wire – two of them consists of a single edge lying on Geom_Circle and the lateral face consists of 4 edges – 2 are shared with top and bottom faces, and remaining two represent a seam edge (see previous post on edges), i.e. the face contains it twice in its wire (with different orientations).

Surface
Let's briefly recall what a surface is. If you had a course of mathematical analysis in your higher school you likely remember this by heart, if not you might want to read some articles to educate yourself. First part of this one on wikipedia give simple examples of parametric surfaces.

A surface maps its 2D parametric space {U,V} into a 3D space object (though still two-dimensional). Compare it with molding when a planar steel sheet is transformed into something curved. Parametric space can be bounded or unbounded, or (un)bounded in one direction only. For instance, a plane's parametric space is unbounded, while NURBS is bounded, and a cylindrical surface is bounded in U (from 0 to 2*PI) and is unbounded in V (-infinity, + infinity).
Geom_Surface::Value() returns a 3D point (X, Y, Z) from a point in a parametric space (U, V). For instance any location on Earth is described by latitude (V) and longitude (U), but can be viewed as 3D point in the Universe (if Earth center had some origin defined).

Let's recall that an edge must have both a 3D curve and a pcurve in its face surface space. Open CASCADE requires that face boundaries (wires) represent closed contours in 3D and 2D space. Therefore a face cylinder's lateral face is described as we considered above.

Not all modeling kernels impose such restrictions. I remember a cylinder coming from Unigraphics (via STEP) which lateral face was described with 2 wires, each consisting of a circular edge. We had a few discussions with my colleagues from the Data Exchange team on how to recognize such cases and how to convert them into Open CASCADE valid wire. As a result, Shape Healing's ShapeFix_Face has been extended with FixMissingSeam().

Orientation
Face orientation shows how face normal is aligned with its surface normal. If orientation is TopAbs_FORWARD then normals match, if TopAbs_REVERSED then they are opposite to each other.
Face normal shows where body material is – it lies ‘behind' the face. In a correct solid body all face normals go ‘outward' (see below):



Material on the face is defined by orientation of its edges. The side is defined by a cross product of a surface (not face!) normal and edge derivative. Edge derivative equals its 3D curve derivative if edge is forward and is opposite, if reversed. Perhaps easier understanding can be got if to consider edge pcurves: if an edge is forward then material is on the left, if reversed, material is on the right. This may sound complicated but once you get it, it will be simpler ;-). We'll speak more of orientation in a dedicated chapter.



Tolerance
Geometric meaning of a face tolerance is a thickness of a pie surrounding a surface.



A face tolerance is used by modeling algorithms significantly more rarely than edge's or vertex' and is often retained at default level (Precision::Confusion()).
By convention, Open CASCADE requires that the following condition is respected:
Face tolerance <= Edge tolerance <= Vertex tolerance, where an Edge lies on a Face, and a Vertex – on an Edge.

Triangulation
In addition to underlying surface, a face may contains a tessellated representation (composed of triangles). It is computed, for example, when a face is displayed in shading mode. Visualization algorithms internally call BRepMesh::Mesh() which calculates and adds triangulation for every face.

Additional location
Unlike an edge or a vertex, a face has an additional location (TopLoc_Location) which is a member field of BRep_TFace. So, do not forget to take it into account when using underlying surface or triangulation. Stephane has recently pointed this out on a forum.

Creating a face bottom-up and accessing its data
Like in the case of a vertex and an edge, BRep_Builder and BRep_Tool is what you need.
BRep_Builder aBuilder;
TopoDS_Face aFace;
aBuilder.MakeFace (aFace, aSurface, Precision::Confusion());
...

TopLoc_Location aLocation;
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aFace, aLocation);
gp_Pnt aPnt = aSurf->Value (aU, aV).Transformed (aLocation.Transformation());
//or to transform a surface at once
//Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aFace);
//gp_Pnt aPnt = aSurf->Value (aU, aV);

Handle(Poly_Triangulation) aTri = BRep_Tool::Triangulation (aFace, aLocation);
aPnt = aTri->Nodes.Value (i).Transformed (aLocation.Transformation());

Hope this was not too boring ;-). Just bookmark it and get back when you need it!

To be continued...
Share
Tweet
Pin
Share
17 comments
Newer Posts
Older Posts

Subscribe for the new posts

Blog Archive

  • August 2015 (2)
  • May 2014 (1)
  • November 2013 (1)
  • June 2013 (1)
  • May 2013 (1)
  • November 2012 (2)
  • November 2011 (1)
  • June 2011 (3)
  • May 2011 (2)
  • March 2011 (1)
  • February 2011 (1)
  • November 2010 (2)
  • October 2010 (2)
  • September 2010 (1)
  • August 2010 (1)
  • July 2010 (1)
  • June 2010 (1)
  • May 2010 (1)
  • April 2010 (2)
  • March 2010 (2)
  • January 2010 (2)
  • December 2009 (1)
  • November 2009 (2)
  • October 2009 (3)
  • August 2009 (2)
  • July 2009 (3)
  • June 2009 (4)
  • May 2009 (3)
  • April 2009 (2)
  • March 2009 (5)
  • February 2009 (5)
  • January 2009 (5)
  • December 2008 (11)
  • November 2008 (8)

Loading...

Followers

Created by ThemeXpose