Topology and Geometry in Open CASCADE. Part 4


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.

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).

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().

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.

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.

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...


  1. It explains so clearly. Thank you so much for your great job.

  2. One question about the location.

    If the face is a part of a TopoDS_Shape can the TopoDS::Location::Transformation be different from the one saved in BRep_TFace?


  3. BRep_TFace location (returned by BRep_Tool::xxx (const TopoDS_Face&, TopLoc_Location&) is an independent instance of one stored in TopoDS_Shape. Final location is a product of both:
    TopLoc_Location L = F.Location() * TF->Location();
    I don’t remember why it was introduced into BRep_TFace (if OCC folks have a clue, please post here). My possible guess is to enable explicit sharing (and thus to save some memory) but I am not sure.

    1. Note the difference in returned Location for BRep_Tool::Tolerance and BRepTool::Surface:

      const Handle(Poly_Triangulation)&
      BRep_Tool::Triangulation(const TopoDS_Face& F, TopLoc_Location& L)
      L = F.Location();
      return (*((Handle(BRep_TFace)*)&F.TShape()))->Triangulation();

      const Handle(Geom_Surface)& BRep_Tool::Surface(const TopoDS_Face& F, TopLoc_Location& L)
      Handle(BRep_TFace)& TF = *((Handle(BRep_TFace)*) &F.TShape());
      L = F.Location() * TF->Location();
      return TF->Surface();

  4. Hi, When i use boolean fuse two adjacent boxes(adjacent faces) , then why the common edge is visible. Using the old BRepAlgo_Fuse API , this is not the case. are there any flag/settings to remove this common edge ?
    thanks -PG

  5. PG, maybe BRepFeat_Gluer is the tool you are looking for. You can use it to glue solids on common faces. LocOpe_FindEdges will help you find common edges, that you can then bind from your gluer.
    You can check for pythonOCC examples, under Level1/TopologyLocalOperations/topology_local_operations.py

  6. hi,
    thanks very much for posts.
    i understood that surfaces can be parabolic just like the first picture bottom-up.
    is it true?
    did i understand right?
    and parabolic surfaces should be represented with mathmatics functions.
    then which member of geom_surface in http://opencascade.sourcearchive.com/documentation/6.3.0.dfsg.1-1/classGeom__Surface-members.html
    holds the function data?

  7. hi again,
    i think value() function do the job.

  8. Thank you, your blog is very helpful. Still, I have one issue I hope someone can help me with.
    Suppose I'm writing a C++ Maya plugin to import STEP files as NURBS surfaces (as an example). With OpenCASCADE, I am able to read the file, and to convert all geometry to BSpline Surfaces. The problem is that the surfaces don't take the face's wires into account. I need to get the BSpline surfaces that *do* follow the wires correctly. I know that internal wires (e.g. holes) cannot exist in such a surface, but then it should simply be split into multiple surfaces.
    So far I am unable to figure this out, and it's the only thing holding me back from continuing my work. And the OpenCASCADE forum is not very active unfortunately. So I hope someone reads this and can help me out.
    Thank you :).
    And again, thanks for a very useful blog!

  9. Hi Nathaniel,

    Thanks for the question.
    I've been following your questions on this thread
    My guess is that you probably confuse two things - a NURBS form and a geometry-vs-topology concept.
    The first one could be relatively easy. If you really need all of your geometrical curves and surfaces be in the NURBS form then just apply BRepBuilderAPI_NurbsConvert:

    TopoDS_Shape anInputShape = ...;
    BRepBuilderAPI_NurbsConvert aConverter;
    aConverter.Perform (anInputShape);
    if (aConverter.IsDone()) {
    TopoDS_Shape anOutputShape = aConverter.Shape();

    There can be certain issues with this conversion (degree, number of spans, proximity to the original geometry, etc) but it does do the job to get you NURBS.

    The second, geometry-vs-topology. Either you fail to recognize the difference or I am missing your final intention. Or if Maya plugin does not allow you to specify arbitrary boundaries on your NURBS surface, your task is hardly feasible in a general case until a lot of work to be done on your own.

    For the sake of simplicity, imagine you import from the STEP file a single face lying on a single NURBS surface. Let that face contain an external boundary composed of 15-25 edges. Let the face also have 5 internal wires, each containing 3-5 edges. Just take a piece of paper and draw anything like that - I am having hard times to attach images here at blogspot.com, sorry :-(.

    What is your expectation of the result ? If Maya does not allow you to feed a face (i.e. surface + boundaries) and you are only left with a possibility to feed multiple NURBS surfaces (each with rectangular UxV domain and grid of UxV knots) then you are doomed to split this topology into numerous patches yourself. OCC (and likely any other geometric modeling kernel) would not allow you to automatically do this. And you would have to do this yourself. That would be nightmare and hardly ever feasibly in a general case unless you have some very specific constraints for the geometry you deal with.

    Hope this at least gives some clues on your case.
    First off, check the Maya API to see if you can really feed faces (not just surfaces).

    Good luck !

    1. See https://www.opencascade.com/system/files/forum/20180122_122046.jpg

    2. Hi Roman,

      Thank you very much for the detailed explanation.
      I am starting to understand the difference between topology and geometry. And yes, ideally, I would like to split a complex topology like that into a number of patches: https://imgur.com/a/9eXjm

      One thing I don't understand is why it wouldn't be possible to split the topology into smaller patches with OCC. I noticed that there are a number of splitting classes. The degrees and continuity might make it difficult I think.

      Another thing, OCC is able to triangulate topology. I don't know how that works exactly, but I was hoping that there would be a way to create similar results using nurbs patches.

      I would understand if OCC has no functionality for this, maybe it's simply not one of its use-cases. In that case I'll have to look around for an alternative for this final step, though I'd prefer to do it all in OCC of course.
      And of course I would really like to avoid having to do this manually.

      Alternatively, it would be possible too on my end to work with (rational) Bezier surfaces. But I expect OCC cannot split the topology in that case either.

      Either way, thanks for the help, it already helped me understand this better :)


    3. Hi Nathaniel,

      Glad this helped ;-).
      Thanks for attaching the picture. Yeah, I see where you are leading to. Now imagine that your external wire contains twice as many edges. How would that affect your expected result ?

      Anyway, no splitting in OCC would do this job to the best of my knowledge.

      As for triangulation, that's a different story. Triangulation is essentially an approximation of a 3D model with the help of planar triangles. There is always a trade-off between precision of such approximation and number of triangles (read memory footprint).

      So you could certainly apply triangulation and convert each triangle into a NURBS surface of degree 1 (both U and V) with one degenerated side. But that would be a hardly feasible model in any CAD/3D application - the size will be enormously large and data quality is extremely poor. Nothing beyond visualization I bet. If Maya does not accept faces but does accept meshes then you could feed it with results of triangulation indeed but not with the degree 1 NURBS surfaces.

      My 2 cents of course.

      P.S. Yep, Bezier won't make any good comparing to NURBS in this scenario. Effectively Bezier is just a single span of a NURBS.

    4. Hi Roman,

      I'm going to have to be a bit more creative to solve my problem, but it's very nice to have some certainty (even if that means that what I'm trying is not very doable).

      Just for clarity: I'm not actually writing a Maya plugin; Maya can import STEP (sort of, I think it's not perfect). My project is similar in the sense that I can't use trimmed nurbs/brep. But now at least I am starting to understand the difference and the problem of obtaining simple nurbs patches from trimmed nurbs.

      And don't worry, I'm not going to make a nurbs patch for each triangle. I think my machine would explode

  10. Hi Nathaniel,

    Thanks for the update!
    OK. Whatever you end up with it would be great to hear back from you, just for the sake of own education and sharing knowledge.

    Good luck!

    1. Hi Roman,

      One more thought actually. Maybe you could tell me if it makes sense.

      So say I have only (non-planar) nurbs faces with no holes, so only a single closed boundary for the face. I think it should be possible to at least force this into "simple" nurbs patches. I understand the edges and surface would have to be (converted to) consistent degrees and types. And some shapes will be hard to deal with automatically (e.g. complex concave boundary). But I think for these faces it should be possible and doable, even if it has to be done (partially) by hand. Or am I missing something?

    2. Hi Nathaniel,

      Let's imagine quite a simple example - a single "convex" boundary (as we are in 3D, not in 2D, convex is kind of conditional).

      Even in that case, what you would need to do is to (automatically) split the face domain into quadrangle-like shapes. The latter are required as you need a rectangular grid of knots in 2D UxV space. Hope the referenced image - https://ibb.co/dGF5Jb - is of some help.
      Internal rectangles defined by knot iso-lines which are completely inside the face domain should be easy - they map just "as is". For those at boundaries you will have decide how to distribute your sample points. In such case those patches will not be fully equal to your original surface anyway. They will deviate from your original surface and you will certainly have gaps (cracks) between adjacent NURBS patches. So your model will no longer be watertight.

      To overcome that, even in a simplified example you will need a more sophisticated approach - e.g. splitting the entire face domain with a dense grid of isolines. In that case, I would look at ShapeUpgrade_FaceDivide and feed its ShapeUpgrade_SplitSurface tool with required U/V values. But even then there will be a lot of corner cases - e.g. tiny edges due to splitting with dense grid, etc.
      Something really challenging...

      Hope this helps somehow anyway.