Healing Gaps in CGM

By Renjie Xu

As we live in a less than perfect world, 3D CAD models are far from perfect. Particularly, models often times contain gaps, which can be found between adjacent vertices, edges, and faces. 

When gaps are tiny, they may not cause problems. You may not even be aware of their existence.  Good tessellators are able to produce watertight meshes and robust operators are able to carve out desired features. However, once the gap size exceeds your modeler’s tolerance, they become uncontrollable. Most of the downstream operators such as Boolean or Offset will either fail in the middle or finish with an unusable model.

So how do you go about finding those problematic gaps?  In CGM, we use the checker.  This tool reports, by default, all the gaps which have a size bigger than 100 times the model resolution. The model resolution is the minimum length CGM can represent. For example, if the distance between two points is less than this value, the two points will be treated as identical.  CGM trusts the robustness of its downstream operators and requires the operators to function well with the gaps under 100 times this size.
Once you know the model gap anomaly, you can use the CGM healing tool to repair it. This tool provides two techniques for you to choose from:

Before and After Gap Healing

Technique One

The first technique heals a gap by extending and computing the intersections of the surfaces or curves around the gap.  It works nicely for simple gaps (e.g. gaps lie on planar geometries), but might not be optimal in complicated cases. This technique will keep your model intact and will not make any shape changes. 

Technique Two

For the more complicated cases, you might want to apply this second option. This technique deforms the surfaces or edges around a gap and computes the intersections if necessary. The deformation in this approach does change the model shape. The tolerance values will need to be set to limit the maximum change. This healing tool works both globally or locally and can be used to automatically heal all the gaps in a model, or heal the gaps only in a selected area. This local technique will keep the rest areas of the model untouched (You can get the gap location info from the checker tool).

Cracks vs Gaps

There are people who claim they have models with large gaps, however those noticeable gaps on a display screen are not gaps, by definition. They are “cracks”, as we call them. We define a crack to be an open area surrounded by edges or vertices which are not topologically connected.  For example two faces can be very close to each other on two boundaries, but they may not be topologically adjacent. In other words, the model does not know the two faces are connected over the boundaries.

How to Heal Cracks 

CGM also has a tool, called the assembler, which can be used to heal cracks. This tool does not directly heal the cracks; instead it converts the cracks into gaps. It loops through all the cracks, calculates their sizes and compares them with a tolerance value. If a crack has a size less than the tolerance, the topology connection link will be established on the edges or vertices around the crack and thus the crack changes to a gap.  Now the model is repairable. 

What About This Tolerance Value?

How do you choose the value? Before you come up with a value, consider the model has a hole, which is a feature you don’t want to remove from the model. You might have already noticed a hole could be a crack too, a big crack by definition. So the tolerance value should be larger than the largest crack you want to heal and smaller than the smallest hole you want to keep.

These powerful tools are necessary for 3D geometric modeling.  3D CAD models are created in many different systems, with many different techniques and do not always produce perfect results. Having these tools help move us a little closer to the desired results.


Recently I was asked by a customer how to render a 3D visualization model translated from a CAD file by Graphical 3D InterOp (see ref 1 below.)  The customer is using HOOPS so the quick answer was to use our bridge libraries which connect our products together and sure enough there is a 3D InterOp/HOOPs bridge for precisely this purpose.  However I thought that I would really like to know how the data gets passed between the different components, especially as I had a small test application I had used a while ago and I never got the display to be as good as that generated by the bridge.  (The bridge is actually shipped as source, so the code is available if you want to take a look at it.)

The first thing I needed to understand was the format of the data being supplied by Graphical InterOp (I am just going to concentrate on the rendering information for the faces in a model.)  Each face in the model gets described by a set of collections of triangles.  The data consists of vertex positions for every vertex in the face, vertex normal at each vertex and a description of the way the vertices are connected.

Triangles are described by the indices of the vertices into the vertex position array, as a simple list of triangles or in optimized form as TriStrips or TriFans.

Triangles, TriStrips, TriFans

This is all pretty standard and the descriptive information is in the InterOp documentation, but the steps to render the triangles into HOOPS have some subtleties that I had overlooked in my early attempt.

First I knew that I wanted to make shells in HOOPS because they would support the nice shading and lighting effects with good continuity between the triangles, however in InterOp the data for the face is presented as a series of sets of Triangles, TriStrips or TriFans and to get the shading across the face I needed to combine these into a single shell for the entire face.

Second by combining these shells I actually make the process more efficient as I only present the vertex positions to the HOOPS functions once for the face instead of once for each set of triangles.

Thirdly I found that I could insert TriFans into HOOPS using the same function as TriStrips  (HC_Insert_Shell_By_Tristrips) except that I had to set the number of vertices in the TriFan data to be minus the actual number, then HOOPS would interpret the TriFan correctly.  So even though HOOPS does not support TriFans internally, it does allow shells to be defined by TriFans as input data.

Finally for shading purposes HOOPS can calculate normal vectors in a shell from the vertex positions, however InterOp is providing me with these normals from the actual model.  Using the normals from InterOp gives improved continuity at face boundaries.

With these in mind I wrote some code to get the data into the HOOPS database:

// Iterate through the triangle sets in the face
std::vector<int> face_list_indices;
SPAIopVisuPolygonIter polygon_iterator = currentFace.GetPolygonIterator();
while( polygon_iterator.Next())
  // Get the current triangle set
  SPAIopVisuPolygon current_polygon = polygon_iterator.Current();
  SPAIopVisuPolygonType polygonType( SPAIopVisu_PolygonType_Unknown );
  const int* p_polygon_indices = NULL;
  int polygon_index_count = current_polygon.GetPolygonIndices(p_polygon_indices, polygonType );
  int polygon_index;
  switch ( polygonType )
  case SPAIopVisu_PolygonType_Triangle:
    for ( polygon_index=0; polygon_index<polygon_index_count/3; polygon_index++ )
      // divide by 3 as HOOPS indexes by point (i.e. 3 floats)
  case SPAIopVisu_PolygonType_TriStrip:
    for ( polygon_index=0; polygon_index<polygon_index_count; polygon_index++ )
      // divide by 3 as HOOPS indexes by point (i.e. 3 floats)
  case SPAIopVisu_PolygonType_TriFan:
    face_list_indices.push_back(-polygon_index_count);    // Negative for TriFan
    for ( polygon_index=0; polygon_index<polygon_index_count; polygon_index++ )
      // divide by 3 as HOOPS indexes by point (i.e. 3 floats)

// Get the face points and normals
const float* p_face_vertices = NULL;
int face_vertex_count = currentFace.GetVertices( p_face_vertices );
const float* p_face_normals = NULL;
int face_normal_count = currentFace.GetNormals( p_face_normals );

// Create the shell
HC_KEY shell_key = HC_Insert_Shell_By_Tristrips (face_vertex_count, p_face_vertices,
face_list_indices.size(), face_list_indices.data(), 0, NULL);
HC_MSet_Vertex_Normals(shell_key, 0, face_normal_count, p_face_normals);

To improve the appearance I used Phong shading which gives smooth interpolation of colors over the faces with nice specular highlighting effects.  This is achieved in the Phong shading algorithm by interpolating the normal vectors across the triangles, again making use of the normal information I had supplied from InterOp.  I set this option on the main HOOPS segment that held my model.

Here is one of our demo parts that has been rendered using the code.

Spatial Rendered Part

In conclusion, I was pleased with the images I generated, the code I wrote was simple and actually more efficient in terms of data than my original attempt.  I would recommend using the bridge code as a much quicker implementation though!


Helpful Qt Tools for 3D Software Development Kits

When software developers opt for tools that are cross platform, they often turn to Qt. Qt is arguably the most popular toolkit for making applications that can run on Windows, Mac or Linux with little additional effort. Spatial’s 3D software development kits can be paired with Qt to produce enterprise quality applications that are platform agnostic.

To help developers get started in this environment, here we present a small collection of files that can be used with qmake for generating makefiles and IDE workspaces that are configured to use software components from Spatial. We’ve also included a simple project that illustrates the use of these add-ons.


Qt 5.4 is the current version available, but these scripts should work with earlier and future versions as well.  You’ll also need an installation of the Spatial software components for each platform you want to support. To take advantage of the qmake scripts provided here, install your Spatial components in a common root directory and name the product folders according to the following naming convention:

prerequisites for 3D software development kits Qt 3D ACIS, 3D InterOp, HOOPS

By using a network share as the installation location, all Spatial components for all supported platforms can be installed to the same location. The qmake system has support for prefix headers (sometimes called pre-compiled headers) which can be used dramatically decrease build times when third party software components such as ACIS are shared on a network drive.

* If you don’t install your components this way, you can specify their locations individually by setting the variables in your shell environment or in your qmake project file prior to using the pri files provided here.


In order to add support for Spatial components, edit your product’s .pro file and add the following variables:

Usage for Qt tools for 3D Software Development Kits

With this information, your environment is completely specified, and all that’s left to do is include the components you want to build into your application.

Here is an example of a .pro file that takes advantage of this build configuration:


# Specify default values for versions

# Allow local configuration file to specify SPA_ROOT
# and to override default versions as needed.
# config-local.pri should not be kept in revision control
# but created and modified by the developer in their working copy

isEmpty(SPA_ROOT):error(Edit config-local.pri to set SPA_ROOT)
!include($$SPA_ROOT/spa_pri/acis.pri):error(Unable to include acis.pri)
!include($$SPA_ROOT/spa_pri/iop.pri):error(Unable to include iop.pri)

message(A3DT: $$A3DT)
message(X3DT: $$X3DT)
message(ARCH: $$ARCH)

CONFIG*=thread console
SOURCES += main.cpp


After including any of the .pri files provided, you can access the values they define. This can be useful for automatically writing a batch file that augments your PATH environment variable, which makes dynamic loading of the libraries much easier. For example:

win32 {
  bs_a3dt = $$replace(A3DT, '/', '\\')
  cmd = echo 'set PATH=$${bs_a3dt}\\$${ARCH}$${d}\\code\\bin;^%PATH^%' > acisPath.bat
  cmd = echo 'set A3DT=$${bs_a3dt}'>> acisPath.bat
  cmd = echo 'set ARCH=$${ARCH}'>> acisPath.bat

The variables A3DT and ARCH are defined by including acis.pri. The forward slash to backslash conversion is done because all paths in the qmake environment use forward slashes, while the windows command prompt expects backslashes.

In addition to modifying your PATH variable, the batch file created by the above snippet sets A3DT and ARCH shell environment variables. This can be useful when opening and building visual studio solutions that ship with Spatial’s software components.

Link to Zip File


By pairing Spatial’s software components with the power of Qt, you can create applications quickly and easily that run on all the major platforms. By using .pri and .pro files, configuring a build environment is easy, reliable, and flexible. Even if you choose not to use Qt as your GUI toolkit, you can use qmake as a platform independent build tool for easily generating makefiles and IDE workspaces.



How To Create an Ellipsoid using 3D ACIS

The 3D analytics supported directly in 3D ACIS include: sphere, block, pyramid, cone, and torus. These shapes can be changed to more generalized shapes using simple tricks that may not be known by everyone. For instance, to create a well behaved ellipsoid, convert a sphere to a spline and perform non-uniform scaling on it, as shown by the following Scheme script:

(define ellipsoid (lambda (r1 r2 r3)

(define x (solid:sphere 0 0 0 1 ) )
(define saved_new_periodic_splitting (option:set 'new_periodic_splitting 3 ) )
    (define ellipsoid (entity:spline-convert x))
    (entity:delete x)
    (entity:scale ellipsoid r1 r2 r3)
    (option:set 'new_periodic_splitting saved_new_periodic_splitting )
(ellipsoid 0.2 0.3 0.4)

In C++, you would perform something like the following:

BODY* ellipsoid = 0;
outcome result;
check_outcome( result = api_set_int_option( "new_periodic_splitting", 3 ) );
check_outcome( result = api_solid_sphere( SPAposition( 0, 0, 0 ), 1, ellipsoid ) );
check_outcome( result = api_transform_entity( ellipsoid, scale_transf( radius_x, radius_y, radius_z ) ) );
check_outcome( result = api_change_body_trans( ellipsoid, NULL ) );

The same logic can be applied to the other 3D analytics, as well as other surfaces.


We are just starting the 3rd day of 2015 SolidWorks World. The first two days have been very well attended and with a great deal of excitement.

There has been a lot of curiosity about Spatial and our role in the Dassault Systemes ecosystem. For many, it is the first time that they have heard of us. After some explanation, many are beginning to connect the dots on how we can add value in to their company. The primary interest has been around how we can help additive manufacturing bridge the gap between precise models and non-precise data. 

Additive Manufacturing is a vital market for many of the Solidworks partners and users. Several are showing how this can be applied alongside traditional segments such as medical and dental, aerospace and automotive. The requirements for these parts are quickly becoming more and more stringent. Precise geometry is going to be necessary to help address these challenges.

In addition to what is happening with Spatial at SolidWorks World, you can see many new use models for the 3DExperience where companies are changing the way we imagine, design and manufacture. A great example is Augmented Reality. There are several companies showing how this can be used in an industrial context. One of the most impressive is what 3DExcite is showing in the DS booth. The rendering and animation from their technology, coupled with augmented reality it truly stunning.

There are going to be several new product announcements on the floor today that I am looking forward to. It is great to see that design and manufacturing is alive and thriving in the Americas. 

Twitter Facebook LinkedIn YouTube RSS