Interfacing OpenCascade with NetGen

Quaoar

Administrator
Staff member
This topic is kind of complementary to Refining Poly_Triangulation meshes as it appears that I underestimated (a lot!) the development efforts required to build up a decent refiner. Since I do not have any open budget to continue working on refinement techniques life-long, it seems it's the right time to look around and try something existing. This time, it's NetGen. Fortunately, NetGen is already interfaced in FreeCAD and that makes me think that I could reuse their sources. Let's see. I'll post here all the interesting discoveries.

If anyone has any experience with NetGen, please, post here. I'm mainly concerned with its robustness and performance.
 

blobfish

CAD community veteran
This topic is kind of complementary to Refining Poly_Triangulation meshes as it appears that I underestimated (a lot!) the development efforts required to build up a decent refiner. Since I do not have any open budget to continue working on refinement techniques life-long, it seems it's the right time to look around and try something existing. This time, it's NetGen. Fortunately, NetGen is already interfaced in FreeCAD and that makes me think that I could reuse their sources. Let's see. I'll post here all the interesting discoveries.

If anyone has any experience with NetGen, please, post here. I'm mainly concerned with its robustness and performance.
My limited experience is focused on creating uniform surface meshes to flatten for stamping die blank estimations. I found netgen to be robust but not very performant for my purposes. I grew impatience trying to tease out all the parameters and their effects. I settled on using occt meshing and the refining through either cgal or pmp.
 

Quaoar

Administrator
Staff member
My limited experience is focused on creating uniform surface meshes to flatten for stamping die blank estimations. I found netgen to be robust but not very performant for my purposes. I grew impatience trying to tease out all the parameters and their effects. I settled on using occt meshing and the refining through either cgal or pmp.
This PMP lib looks appealing. Does it preserve all the feature edges on remeshing? I need the mesher to exactly respect all edges in a CAD model as the main target is accessibility analysis (like in Fusion 360).
 

blobfish

CAD community veteran
This PMP lib looks appealing. Does it preserve all the feature edges on remeshing? I need the mesher to exactly respect all edges in a CAD model as the main target is accessibility analysis (like in Fusion 360).
No I don't think the pmp lib has any constraints for that. cgal appears to but I have never tried it.
 

Quaoar

Administrator
Staff member
My very brief experience is stated here: https://github.com/CadQuery/cadquery/issues/367#issuecomment-651328681

TL;DR Netgen was able to generate a correct surface mesh for the issue case which was not possible with OCCT. Furthemore they have a pybind11 based python interface which is great (if you use python that is).
I used C++ and made 2 attempts to plug NetGen with its different versions (the older one compilable with C++11 and another one is the current master, C++17-only).

1632419047386.png

Down the road, I faced some very strange issues, all seem to be related to memory management. For example, I saw many times heap corruption problem when I tried to allocate NetGen's mesh data structure from a heap memory instead of the local stack. The reason why I tried to cheat it was to avoid automatic destruction of the mesh data structure when it goes out of scope. Keeping dtor enabled leads to a crash in my environment, which I fail to understand:

1632419560225.png

Here is the bridge code that finally works:

C++:
bool asiAlgo_MeshGen::DoNetGen(const TopoDS_Shape&         shape,
                               Handle(Poly_Triangulation)& mesh,
                               ActAPI_ProgressEntry        progress)
{
  TopoDS_Shape sh = shape;

  // The following auto-selection is just AABB-based, nothing special.
  const double linDefl = AutoSelectLinearDeflection(shape);

  netgen::MeshingParameters ngParam;
  ngParam.minh        = linDefl;
  ngParam.maxh        = linDefl*100;
  ngParam.uselocalh   = true;
  ngParam.secondorder = false;
  ngParam.grading     = 0.3;

  netgen::OCCParameters occParam;

  nglib::Ng_Init();

  // ATTENTION: static!!! No idea why, but using local memory will crash
  // the dtor of ngMesh. Using heap memory (i.e., new netgen::Mesh) leads
  // to a heap corruption error. Therefore I use static to avoid destructor
  // which is graceless, but kind of makes it work...
  static netgen::Mesh ngMesh;

  netgen::OCCGeometry geom;
  geom.shape = sh;
  geom.BuildFMap();
  geom.BuildVisualizationMesh(linDefl);
  geom.CalcBoundingBox();
  geom.changed = 1;
  geom.PrintNrShapes();

  netgen::OCCSetLocalMeshSize (geom, ngMesh, ngParam, occParam);
  netgen::OCCFindEdges        (geom, ngMesh, ngParam);
  netgen::OCCMeshSurface      (geom, ngMesh, ngParam);

  const int nbNodes     = (int) ngMesh.GetNP();
  const int nbTriangles = (int) ngMesh.GetNSE();

  std::cout << "Num. of mesh nodes     generated: " << nbNodes << std::endl;
  std::cout << "Num. of mesh triangles generated: " << nbTriangles << std::endl;

  if ( !nbNodes || !nbTriangles )
  {
    nglib::Ng_Exit();
    return false;
  }

  // Populate the result.
  mesh = new Poly_Triangulation(nbNodes, nbTriangles, false);
  //
  for ( int i = 1; i <= nbNodes; ++i )
  {
    const netgen::MeshPoint& point = ngMesh.Point(netgen::PointIndex(i));
    mesh->ChangeNode(i).SetCoord(point[0], point[1], point[2]);
  }

  for (int i = 1; i <= nbTriangles; ++i)
  {
    const netgen::Element2d& elem = ngMesh.SurfaceElement(netgen::ElementIndex(i));
    mesh->ChangeTriangle(i).Set(elem[0], elem[1], elem[2]);
  }

  std::cout << "Mesh was generated." << std::endl;
  ngMesh.DeleteMesh(); // This thing is likely useless.

  nglib::Ng_Exit();
  return true;
}


I'll give it more tests in production soon. Does anyone know if I could grab the corresponding face IDs from the generated triangles?
 
Last edited:

blobfish

CAD community veteran
Down the road, I faced some very strange issues, all seem to be related to memory management. For example, I saw many times heap corruption problem when I tried to allocate NetGen's mesh data structure from a heap memory instead of the local stack. The reason why I tried to cheat it was to avoid automatic destruction of the mesh data structure when it goes out of scope. Keeping dtor enabled leads to a crash in my environment, which I fail to understand:

I'll give it more tests in production soon. Does anyone know if I could grab the corresponding face IDs from the generated triangles?
Your experience with netgen sounds familiar. I am guessing your code example is using the current master of netgen. I say that because the main meshing calls look different than mine. Anyway this works for me on debian bullseye using the repository version of netgen (libnglib-6.2)

If I remember correctly, netgen doesn't offer any mapping from the generated mesh back to the TopoDS_Shape. I do believe that gmsh has that built into their API.
 

Quaoar

Administrator
Staff member
Your experience with netgen sounds familiar. I am guessing your code example is using the current master of netgen. I say that because the main meshing calls look different than mine. Anyway this works for me on debian bullseye using the repository version of netgen (libnglib-6.2)

If I remember correctly, netgen doesn't offer any mapping from the generated mesh back to the TopoDS_Shape. I do believe that gmsh has that built into their API.
Yes, it's the current master. Too bad if NetGen does not keep back references, I thought this should be a "must" for a FEM generator. I can try projecting the mesh nodes back onto the visuslization mesh though and this way work it around.
 

A-U

Active CAD practitioner
Looking at the sources I found this function:

Code:
  const void Mesh :: GetSurfaceElementsOfFace (int facenr, Array<SurfaceElementIndex> & sei) const

it also looks that occgeom contains a facemap
Code:
fmap
. Full disclosure: I have not used those.
 

blobfish

CAD community veteran
I can try projecting the mesh nodes back onto the visuslization mesh though and this way work it around.
I had that thought too, but I understand your reluctance. Feels kind of like a hack. I am interested in this accessibility/undercut analysis, so I have a few questions:

1) Have you accumulated any test shapes yet?
2) Why won't an occt incremental mesh with the appropriate deflection parameters work?
3) Have you thought about the occt Hidden Line Removal algos.
 

Quaoar

Administrator
Staff member
1) Have you accumulated any test shapes yet?
In my case, it's mostly about 3-axis machining, so the test cases are quite standard mechanical parts. Here is the archive: http://quaoar.su/files/dfm-cases.zip

Some of the cases are taken from the DFMPro application. I have never used it myself (it is commercial) but took time to install it just to grab some files and the user manual that explains which accessibility checks they have. Here is this manual (precompiled HTML): http://quaoar.su/files/dfmpro.chm I found it to be quite useful, especially for non-engineers (like me).

1632807349679.png

2) Why won't an occt incremental mesh with the appropriate deflection parameters work?
Whatever parameters I passed there, it left planar faces subdivided with a very coarse mesh. That's a typical test part I use and the typical result from BRepMesh:

1632807502155.png
I tried different settings of the OCC mesher, including some hacks, such as converting all analytical shapes to splines and even inserting vertices with TopAbs_INTERNAL orientation inside the planar faces' domains. Nothing helped. The only effect of having internal vertices I've seen was the emergence of degenerated triangles and sometimes crashes of the mesher.

In contrast, here is the mesh generated by NetGen on this part:

1632807644262.png
It's fine enough, so I can have accessibility check on its triangles. For example, here's the accessibility test for the direction X+:

1632807774381.png
3) Have you thought about the occt Hidden Line Removal algos.
The last subcontracting job I did for OCC was a deep analysis of HLR algos. They work reasonably well for small models (single parts), even though the discrete version of the algo seems to be worse than the precise method. That was surprising as the discrete version is conceptually simpler, but as of now, it is quite unusable (and they will unlikely improve these things in open source). The precise version, on the other hand, seems to be an overkill for such a type of analysis. Also, given that both algos are thousands of lines of legacy code, this whole thing looks quite non-maintainable. It's just scary to ground your paid project on a codebase that you have no idea about.

The visibility check I'm implementing is, on the other hand, quite simple. The key here is the fast ray-mesh intersection where we can benefit from the BVH structures of OpenCascade.
 
Last edited:

blobfish

CAD community veteran
Whatever parameters I passed there, it left planar faces subdivided with a very coarse mesh. That's a typical test part I use and the typical result from BRepMesh:
...
I tried different settings of the OCC mesher, including some hacks, such as converting all analytical shapes to splines and even inserting vertices with TopAbs_INTERNAL orientation inside the planar faces' domains. Nothing helped. The only effect of having internal vertices I've seen was the emergence of degenerated triangles and sometimes crashes of the mesher.

In contrast, here is the mesh generated by NetGen on this part: ...
It's fine enough, so I can have accessibility check on its triangles. ...
I am sorry, I should have been more specific in my question. I am aware of the different results of the occt BRepMesh and netgen. I don't have my head into this and I am probably missing something, but it is not obvious to me why the large planar triangles of the BRepMesh don't work for your analysis. Don't waste your time bringing me up to speed if you don't want to spend the time.


The last subcontracting job I did for OCC was a deep analysis of HLR algos. They work reasonably well for small models (single parts), even though the discrete version of the algo seems to be worse than the precise method. That was surprising as the discrete version is conceptually simpler, but as of now, it is quite unusable (and they will unlikely improve these things in open source). The precise version, on the other hand, seems to be an overkill for such a type of analysis. Also, given that both algos are thousands of lines of legacy code, this whole thing looks quite non-maintainable. It's just scary to ground your paid project on a codebase that you have no idea about.

The visibility check I'm implementing is, on the other hand, quite simple. The key here is the fast ray-mesh intersection where we can benefit from the BVH structures of OpenCascade.
Good to know about the HLR algos as I have never used them myself. Thanks for sharing. I am seeing some similarities between your accessibility analysis and some of my work with OpenSceneGraph. As long as you are working with a mesh, I wonder if a graphics API might be a better fit? I have got good results for mouse pointer intersections using openscenegraph's kdtree. Raytracing? Render to texture? Just thinking out loud. I will stop wasting your time.
 

Quaoar

Administrator
Staff member
I am sorry, I should have been more specific in my question. I am aware of the different results of the occt BRepMesh and netgen. I don't have my head into this and I am probably missing something, but it is not obvious to me why the large planar triangles of the BRepMesh don't work for your analysis.
What I am doing is I'm assigning scalars with the individual triangles. I.e., currently I cast a ray from a midpoint of a triangle and check for any obstacles. With such a method, large triangles would lead to low resolution of the final scalar mapping as there will be just one hit test for one triangle.

What you say about graphics API totally makes sense and I'm pretty sure that this is how Fusion360 approaches this kind of accessibility analysis. And likely this how such problems have to solved. Can you share some more details on your approach maybe? Something to read? For me it's simply a question of competence: I have no idea how to program GPGPU (shame on me). Another obstacle (well, not really such an obstacle, but still something to take into account) is that I need to run it all on a server in headless mode.

And no worries about time and questions, really. I think that this is exactly why we are all here: sharing experiences, how-tos, best and worst practices. For me, this is a tiny community of cad devs, and I'd love to learn from you and other folks here. Just like people learn from each other at conferences in scientific communities.
 

Quaoar

Administrator
Staff member
One another pretty obvious discovery on Windows is that one should not mix up the release and debug libraries. E.g., if I compile my project in debug, it's necessary to make sure that the linked NetGen is debug as well. One easy way to achieve that is to put release and debug libs in separate folders, e.g. like this:

1632912814498.png
Mixing up debug and release will crash your app in dtors of the dynamically allocated objects, such as ngcore::Array<> for example.
 

blobfish

CAD community veteran
What I am doing is I'm assigning scalars with the individual triangles. I.e., currently I cast a ray from a midpoint of a triangle and check for any obstacles. With such a method, large triangles would lead to low resolution of the final scalar mapping as there will be just one hit test for one triangle.
I see.



What you say about graphics API totally makes sense and I'm pretty sure that this is how Fusion360 approaches this kind of accessibility analysis. And likely this how such problems have to solved. Can you share some more details on your approach maybe? Something to read? For me it's simply a question of competence: I have no idea how to program GPGPU (shame on me). Another obstacle (well, not really such an obstacle, but still something to take into account) is that I need to run it all on a server in headless mode.
All my graphics programming experience has been high level scene graphs. Coin3d, OpenSceneGraph. So I have no experience with the programmable graphics pipelines. I started to play with blender + cycles to create a baked texture map onto a mesh. Blender is freezing my laptop graphics so I am getting a new version for my next try. Another thought I had was using OSG intersector. That has a lot of question marks.
 
Top