As a child, I marveled at antiquity through my great grandmother’s many stories of life in the old west. Born in the 1890s—well before the horseless carriage—she’d speak of waking up before dawn to walk five miles to school each morning. After class, since she was already in town, her parents had her stop by the baker, butcher and general store before visiting the dairy to carry dinner and breakfast home. In the century since, we have all benefitted from the advent of department stores, catalog ordering, supermarkets, shopping malls, and most-recently, online shopping. This transformation, driven by the market opportunity of customer convenience and product accessibility, has been nothing short of revolutionary. Rather than organize products according to the provider’s convenience—meat from the butcher, milk at the dairy—they are offered to us collectively in a single location.

The focus on the buyer’s needs catalyzed the evolving paradigm, while innovations in technologies and business models have enabled it. For many 3D modeling solutions today, this same approach is necessary to survive and thrive in a world that rewards, and increasingly requires instant, global accessibility.

Mobile Device Usage

Nearly everyone you know can be seen using a mobile device- your boss, your parents, your children and your supermarket clerk all use these devices for much more than telephone calls. We expect that the activities and workflows that we need and want can be done on any device, anywhere. In 2014, half ( of all workloads were done in the cloud for the first time. What’s more, decision makers lead mobile adoption, with more than 90% ( using a mobile device for business use. Are your customers’ expectations any different? 

Access to Workflows

Whether you are doing proprietary internal CAD modeling, or building an OEM CAD solution, it’s likely that your customers, their customers, and users not only could benefit from a globally available mobile accessibility to their workflows 24/7, but that they expect it by default. If your users are internal, having mobile access to your workflows not only enables new possibilities to engage with your customers or iterate on your designs, but it means your team can work to deliver the best product, anytime, anywhere.

Creating the Right Architecture

CAD models are often very large and complex. Putting them on a mobile device may seem beyond mobile device capability, but the right architecture can enable workflows on even the largest 3D models. The visualization process can be done either on the device or in the cloud. Your 3D CAD model can be stored in your datacenter, in the cloud, or partially on the device. By carefully considering each of your workflows’ requirements, mobile CAD can be enabled on most consumer devices. Put the processing and complexity where the computing power is, either in the cloud or your own data center. This includes not only demanding 3D CAD modeling operations, but also visualization processing. Said simply, when your goal is mobile accessibility to your users, a successful implementation will push only what is needed to the device, thereby achieving a complete, yet responsive experience. This can range from view-only access, to full model creation and manipulation. In both of these scenarios, and the gamut between, use the cloud to enable mobile accessibility to your valuable workflows.

Protecting Your Intellectual Property

If you or your customers are dealing with 3D models, you are probably wondering how to ensure that this valuable intellectual property is protected. Many are surprised to learn that a 3D cloud system can actually be more secure than traditional implementations. Without configurable model access, you may be sharing this critical material via physical media or email. Once that model leaves your hands, you have no control of where it may go from there—and you may never know where it reaches. In a cloud implementation, you can provision access to suppliers, partners and customers not only on a time-limited basis- with an ability to see instantly access usage metrics- but also with full control to constrain access to only certain assemblies, details or operations. Grant view-only access to those who need it, without handing over your trade secrets in the process.

When implementing a cloud-based solution, you’ll also be able to choose public, private, or hybrid cloud options, thus realizing the benefits of accessibility while being able to restrict mission-critical data and operations to only authorized users, and only on networks you control and trust. By leveraging best-in-class cloud providers, you benefit from billions of dollars of investment and maintenance in security, and can integrate these seamlessly with your internal systems and firewalls.

Don’t force your customers to walk for miles to different vendors just to survive in today’s 3D modeling world. There are many paths to a successful cloud and mobile implementation and Spatial can help you navigate the options and offerings to most-effectively realize your desired result. Whether you’re looking for an OEM CAD Cloud solution utilizing visualization solutions like HOOPS, or building your own web based CAD software built on webGL, Spatial has nearly three decades of industry proven experience in 3D modeling that we’ll put to work for you. Please contact us ( ) if you’d like to learn more.

capture meta-data about ACIS APIs This article shares a simple architecture which can be used to capture meta-data about the use of ACIS APIs in your program. It can be used to record the filename, line number, arguments and outcome of each API. C language preprocessor macros are used to invoke methods on a static object called spa::CheckOutcome. The meta-data is sent to your implementation of spa::CheckOutcome::Logger interface.


Here is an example of the output produced by a simple implementation of the Logger interface:

api_start_modeller(0) from main.cpp @ line 56: OK
api_initialize_kernel() from main.cpp @ line 58: OK
api_set_file_info(0x01 | 0x02, fi) from main.cpp @ line 64: OK
api_solid_block(SPAposition(), SPAposition(), block) from main.cpp @ line 70: ERROR (width negative or zero)
api_solid_block(SPAposition(), SPAposition(1,1,1), block) from main.cpp @ line 73: OK
api_save_entity_list(fptr, true, elist) from main.cpp @ line 78: OK
api_restore_entity_list(fptr, true, elist) from main.cpp @ line 83: OK WARNING (restore data has unknown origin)
api_terminate_kernel() from main.cpp @ line 87: OK
api_stop_modeller() from main.cpp @ line 88: OK

Additionally, the spa::CheckOutcome class contains an interface that can be implemented to report and interrupt the progress of the API. The interface also provides pre-API and post-API methods that are invoked accordingly, which allow you to more easily show and hide a progress dialog box. Here is an (abbreviated) example of the output produced by a simple implementation of the Progress interface:

Restoring entity list (0%)
Restoring entity list (1%)
Restoring entity list (3%)
Restoring entity list (5%)
Restoring entity list (7%)

Restoring entity list (100%)
API call completed. result == ok

Using this architecture in your code is quite easy. Just surround each of your API calls with the checkOutcome or checkOutcomeWithProgress macros. Here are two examples of its use:

if(!checkOutcome(api_initialize_kernel())) {
std::cout << "Initialization failed: "
<< spa::CheckOutcome::getLastErrorMessage()
<< std::endl;
return -1;

if(!checkOutcomeWithProgress(api_restore_entity_list(fptr, true, elist), "Restoring entity list")) {
std::cout << "Restore failed: "
<< spa::CheckOutcome::getLastErrorMessage()
<< std::endl;

As you can see from these samples, spa::CheckOutcome provides you with easy access to the error message in the event of a failure. Additionally, the success or failure of an API call is simplified to a Boolean value by the checkOutcome macro which can be used more naturally in conditionals.

It is important to check the return value of your ACIS API calls and respond to failures appropriately. Information about the success of an API is returned in an outcome object. When an API fails, the outcome object can be queried to obtain specific information about the failure. The static method spa::CheckOutcome::getLastOutcome() can be used to obtain last outcome object for further processing when needed.

All of the capabilities described are can be easily added to your application. Just add click here to download CheckOutcome.cpp and CheckOutcome.h , then add the files to your project. To see an example implementation of the Logger and Progress interfaces, as well as how spa::CheckOutcome and the macros are used take a look at main.cpp.


Spatial's 3D InterOp requires that all translations be done on the same thread that creates the SPAISystemInitGuard. This complicates programs that may wish to create separate threads for a translation. This is often done in GUI applications that require the main thread to listen for events and handle all translation logic in a separate thread that will not impact the interactivity of the main thread. It would be highly undesirable to block all event processing for the duration of a translation, so a separate thread is often used to do the translation so that the application can still be interactive. 

Spatial has recommended that you follow the guidelines established in the document “InterOp: Important Note for Multi-threaded Applications.”  However, the example code there is specifically for file to file translations. The classes provided do not demonstrate how do to in-memory translations or provide a flexible mechanism for doing arbitrary translations on any arbitrary thread. It is far easier to spawn a new thread in an application for the translation and then close that thread when done. IOP does not support concurrent translations, so some caution must be made to not accidentally do two translations at once. 

This of course gets more and more complicated for real world applications. Often, a single code path for a translation cannot be maintained.  In one translation, you may want to only process BREP data. In another, you may want graphical data.  In yet a third, you may be interested in a file-to-file translation and not care to process in memory. This requires different code paths per translation and it is no trivial task to modify the example code into something that works for type of translation you may have. 

With the proper encapsulation, one class can manage all translations and drive them in such a way so that the conversion is done within the boundaries of one and only one SPISystemInitGuard. It will allow you to spawn and then destroy a thread in your application so that code management is easier and the translation is localized. It will allow you to implement any number of callbacks so that not only one translation code path is needed.  It can handle multiple different translations in an application with the reassurance that it abides by the restrictions of InterOp translators and does not force any of event processing threads to synchronously block for the entire duration of a translation.

To view an example of flexible code for use by any program that may invoke arbitrary threads to do translations, visit:


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(),, 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!

Twitter Facebook LinkedIn YouTube RSS