HOOPS Exchange Integration


HOOPS Exchange is Tech Soft 3D's CAD data integration library and is the recommended way to get model data from a CAD file into Visualize. Through Exchange, you can access assembly information, visualization data, and PMI annotations for inspection or manipulation within the context of a Visualize scene graph. A detailed introduction to HOOPS Exchange can be found here.

Any file that can be loaded by the HOOPS Exchange libraries can be loaded into HOOPS Visualize. The interface described in this section is the way to connect Visualize with HOOPS Exchange, and is supported on Windows, Linux, OSX, iOS, and Android.

Important note for C# programmers

The Exchange integration has a few limitations when using C#. Functions which expect C++ pointers cannot be called directly from C#. Instead, an intermediate step using CLI (Common Language Infrastructure) is required. The Visualize solution provides the cc_exchange_lib_vc1* project which demonstrates how the C++/CLI interface is set up, allowing the C# programmer to access specific Exchange functionality that is otherwise only available in C++.

The project can be found at Applications > Demo > cc_exchange_lib_vc1*, and when compiled, generates the CLI interface in the form of a DLL. The DLL needs to be added as a reference to the C# project that invokes it. You will also find a sister project called Applications > Demo > cc_exchange_vc1*. This project is a demonstration on how you would invoke the CLI to call from your C# application through to C++ using the interface generated by cc_exchange_lib_vc1*.

Important note for Android developers

The Exchange libraries do not ship with a x86_64 binary. As a result, Visualize can't build the Android sprocket for x86_64. To get around this, you can edit your local.properties file and add the following line:

ndk.abis=armeabi-v7a,arm64-v8a,x86

...to compile for all supported architectures, or just:

ndk.abis=x86

...if you just want to target simulators.

Prerequisites

Steps for integrating HOOPS Visualize with Exchange are delineated below:

Step 1: Install Exchange

Exchange can be downloaded from the Tech Soft 3D Developer Zone. If you don't already have Exchange installed, install it now. You do not have to generate a separate key to use the Exchange component. The key will be embedded with your Visualize license key. Note the Exchange installation directory.

Step 2: Set up the environment

The Exchange DLLs must be in the system path so that they can be located when your program is executing. For example, assuming your platform is 64-bit Windows, and the installation is at C:\Program Files\HOOPS_ExchangePublish, the path should be set as C:\Program Files\HOOPS_ExchangePublish\bin\win64;.

Step 3: Include the libraries in your project

The Visualize-Exchange integration library must be included as a dependency. C++ users will need to link to hps_sprk_exchange.lib. C# users need to add a Reference to the hps_cs_sprk_exchange.dll located in the bin directory. (If you're working in the WPF Sandbox, the Copy Local property for the Reference to hps_cs_sprk_exchange.dll should be set to False).

Alternatively, you may specify the library directory by calling HPS::World::SetExchangeLibraryDirectory.

Step 4: Include the header file in your source (C++ only)

Your source files that use the Visualize-Exchange integration must include sprk_exchange.h.

Step 5: Initialize the DLLs (optional)

This step is only necessary if you need to call the HOOPS Exchange API directly in order to access functionality not provided by the Visualize-Exchange integration. If this is your intention, you must make an explicit call to A3DSDKLoadLibrary. See Runtime initializations of the HOOPS Exchange Programming Guide. You should not call any other HOOPS Exchange initialization functions.

At this point, HOOPS Exchange is ready to use in your application.

File loading

The view hierarchy that was introduced in section 3.1 is utilized when loading Exchange models. Specifically, Visualize encapsulates Exchange models HPS::Model objects as part of the loading process. It can also create a HPS::View using the Exchange model's camera (where supported by the file format). In any case, the end goal is to create a view hierarchy. There are multiple ways to do this, as demonstrated in the code snippet, below:

try
{
notifier.Wait();
HPS::CADModel modelFile = notifier.GetCADModel();
// get the View and attach it to the Canvas
HPS::View myView = modelFile.ActivateDefaultCapture();
myCanvas.AttachViewAsLayout(myView);
// alternatively, if you already have a View object, you can simply
// get the Model and attach it directly:
HPS::Model model = modelFile.GetModel();
myView.AttachModel(model);
}
catch (const HPS::IOException& ioe)
{
// handle error
}
try
{
notifier = HPS.Exchange.File.Import(filename, new HPS.Exchange.ImportOptionsKit());
notifier.Wait();
HPS.CADModel modelFile = notifier.GetCADModel();
// get the View and attach it to the Canvas
HPS.View myView = modelFile.ActivateDefaultCapture();
myCanvas.AttachViewAsLayout(myView);
// alternatively, if you already have a View object, you can simply
// get the Model and attach it directly:
model = modelFile.GetModel();
myView.AttachModel(model);
}
{
// handle error
}



This snippet demonstrates the importance of calling Wait() on your notifier object. Because all notifier classes do their I/O in a separate thread, the potential exists to use the object before it is done loading, especially if you try to interact with it immediately after you call Import(). Calling Wait() ensures the I/O operation is complete before continuing. If you do not use Wait(), you could end up with unexpected behavior.

In addition to waiting, the HPS::Exchange::ImportNotifier also can provide you with useful information about the status of the import. You can even use the notifier to cancel the import, if needed. All of the possible return values for the status are listed in the HPS::IOResult reference manual page.

// get the status of the import
HPS::IOResult result = notifier.Status();
if (result == HPS::IOResult::Success)
{
// file loading successfully
}
// you can cancel the import by calling:
notifier.Cancel();
// get the status of the import
HPS.IOResult result = notifier.Status();
if (result == HPS.IOResult.Success)
{
// file loading successfully
}
// you can cancel the import by calling:
notifier.Cancel();



After the model is loaded using Exchange, it can be manipulated like any other Visualize model. The components of the model are translated from their native format into the Visualize entities as discussed in the data mapping section.

IMPORTANT: When a CAD model is imported, memory is allocated which must eventually be released. Memory is allocated by Exchange to load Exchange entities, and additionally by Visualize to create the scene graph. The way the data was imported will determine how memory cleanup should be handled.


If you imported a CAD file using HPS::Exchange::File::Import with a path to a file, then the HPS::CADModel assumes "ownership" of the model file. In this case, calling Delete on the CADModel object is sufficient to delete all memory associated with the model.


However, if you read the model a different way, then later imported it to Visualize using the A3DAsmModelFile pointer (as opposed to providing a file path), then the HPS::CADModel is *not* considered the file owner. During cleanup, HPS::Component::Delete() should still be called, but doing so will not automatically free the underlying Exchange data. In this case, you also need to call the Exchange API directly and free the memory using A3DAsmModelFileDelete.


For more information, please see the API Reference entry for HPS::Component::Delete().

Options for file loading

When loading a file, you can choose beforehand whether certain elements will be loaded. Default import options will be used for any import settings that are not set on the HPS::Stream::ImportOptionsKit passed to the HPS::Exchange::File::Import function. These defaults can be seen via HPS::ImportOptionsKit::GetDefault(). The options specified during import will determine how long the import takes, the amount of memory used, and the size of exported files. Additionally, if data is not loaded during import, it will not be available afterwards without reimporting the data. It is important to keep these things in mind when selecting the import options. For example, if you have a file that contains PMI data, but know you will not be needing it, you can disable PMI as an import option:

importOptions.SetPMI(false);
HPS.Exchange.ImportOptionsKit importOptions = new HPS.Exchange.ImportOptionsKit();
importOptions.SetPMI(false);



Additional options that can be controlled during import are B-rep data, solids, surfaces, and others.

Importing multiple models

Multiple CAD models may be imported into the same scene graph. To do this, set the destination of the model using HPS::Exchange::ImportOptionsKit::SetLocation or HPS::Exchange::ModelFileImportOptionsKit::SetLocation functions. These functions allow you to provide a HPS::ComponentPath with an optional HPS::MatrixKit. HPS::ComponentPath describes the path within the component hierarchy where the new data should be inserted, scaled by the provided matrix.

If a location is not set, HOOPS Visualize will simply create a new HPS::CADModel. If the location is set, Visualize will create new HPS::Components and add them to the existing component hierarchy.

Configurations

Configurations are an important option that can be utilized for certain file formats. A configuration is a sub-model within a larger model - sometimes these files are also referred to as "multi-model" or "multi-entry" files. Only CATIAV4, SolidWorks, and IDEAS files support configurations. Some files can contain multiple configurations. If you do not specify a configuration to load, the default configuration will be loaded, if possible. Multi-model CATIA V4 files do not have a default configuration, so attempting to load such a file without specifying a configuration will cause the import to fail.

// get a list of all configurations in the file
HPS::Exchange::ConfigurationArray configArray = HPS::Exchange::File::GetConfigurations(filename);
for (size_t i = 0; i < configArray.size(); i++)
{
// get name of configuration
HPS::UTF8 configName = configArray[i].GetName();
// set configuration
importOptionsKit.SetConfiguration(configName);
// configurations can be nested - you can get a list of subconfigurations
HPS::Exchange::ConfigurationArray subconfigArray = configArray[0].GetSubconfigurations();
}
// at this point, the file can be loaded using the ImportOptionsKit
// get a list of all configurations in the file
HPS.Exchange.Configuration[] configArray = HPS.Exchange.File.GetConfigurations(filename);
for (int i = 0; i < configArray.Length; i++)
{
// get name of configuration
String configName = configArray[i].GetName();
// set configuration
HPS.Exchange.ImportOptionsKit importOptionsKit = new HPS.Exchange.ImportOptionsKit();
importOptionsKit.SetConfiguration(configName);
// configurations can be nested - you can get a list of subconfigurations
HPS.Exchange.Configuration[] subconfigArray = configArray[0].GetSubconfigurations();
}
// at this point, the file can be loaded using the ImportOptionsKit



Importing models with very large transformations

In certain cases, models which contain very large transformations or geometry coordinates relative to their size may not be presented correctly due to floating point precision errors. This issue stems from a loss of precision which happens when double precision data present in a model is converted to the single precision HPS database. When using single precision data, adding a small number to a much larger one will result in a lot of precision being lost from the result. The effect is especially pronounced in federated models, which causes the apparent position of relative geometry to be incorrect. Another common indication that a model is suffering from this problem is the model appearing to shake or vibrate while the camera is moved around the scene. This section of the Programming Guide provides one possible solution to fix this issue.

There are two main ways to approach this problem, and the solution depends on what is causing the model to be placed far away from the origin. Both of these approaches involve changing the contents of the scene graph, and as such should be performed before enabling static model computation.

Models placed far from the origin by a large translation

Models can contain one or more large translations which place the model very far away from the origin. If the model's bounding is much smaller than the size of the translation, adding the (comparatively) very small geometry size to the (comparatively) very large model translation will result in a loss of precision, responsible for rendering artifacts.

If the model in question is being imported through the Exchange sprocket, you can opt to have Visualize automatically solve this issue for you by enabling the SetLargeTranslationExtraction setting found in the HPS::Exchange::ImportOptionsKit class. When this setting is enabled, translations that are much larger than the model size are ignored, and a user option is inserted where they would have been found. This allows the original transformation to be queried, if needed, without compromising visual fidelity.

The translation is stored as user data at the index you specify, and the data has the format "extracted translation = (%f, %f, %f)".

If the model was imported through other means, you can add some code to your application that will perform equivalent steps.

  1. After the model has been loaded, search it for translations.

  2. Compute the bounding of the segment where the translation is found.

  3. Decide if the translation is so much larger than the bounding which would cause visual artifacts. A translation which is several orders of magnitude larger than the bounding is a good candidate.

  4. If this is the first translation to be considered too large to render, we should save it away to use it as a baseline. Other translations that will be removed need to take it into account, so that the relative positioning of different geometry will remain untouched.
float cutoff = 10000;
Vector baseline_translation = Vector::Zero();
SearchResults search_results;
model.GetSegmentKey().Find(Search::Type::ModellingMatrix, Search::Space::SubsegmentsAndIncludes, search_results);
SearchResultsIterator it = search_results.GetIterator();
while (it.IsValid())
{
// found items will be the segments that contains transformations
SegmentKey segment_with_transform(it.GetItem());
FloatArray transform;
ModellingMatrixControl modelling_matrix_control = segment_with_transform.GetModellingMatrixControl();
modelling_matrix_control.ShowElements(transform);
// extract the translation from the transformation
Vector translation(transform[12], transform[13], transform[14]);
BoundingKit bounding_kit;
if (segment_with_transform.ShowBounding(bounding_kit))
{
SimpleSphere sphere;
SimpleCuboid cuboid;
bounding_kit.ShowVolume(sphere, cuboid);
if (translation.x / sphere.radius > cutoff ||
translation.y / sphere.radius > cutoff ||
translation.z / sphere.radius > cutoff)
{
// This translation seems too large given the model's bounding.
if (baseline_translation == Vector::Zero())
{
// This is the first translation to be removed for this model. Store it in case more translations need to be removed
baseline_translation = translation;
modelling_matrix_control.Translate(-translation);
}
else
{
// This is not the first translation to be removed.
// Take the baseline translation into account so that relative positioning is maintained.
Vector delta(translation - baseline_translation);
modelling_matrix_control.Translate(-translation.x + delta.x, -translation.y + delta.y, -translation.z + delta.z);
}
}
}
it.Next();
}
float cutoff = 10000;
Vector baseline_translation = Vector.Zero();
SearchResults search_results;
model.GetSegmentKey().Find(Search.Type.ModellingMatrix, Search.Space.SubsegmentsAndIncludes, out search_results);
SearchResultsIterator it = search_results.GetIterator();
while (it.IsValid())
{
// found items will be the segments that contains transformations
SegmentKey segment_with_transform = new SegmentKey(it.GetItem());
float[] transform = new float[16];
ModellingMatrixControl modelling_matrix_control = segment_with_transform.GetModellingMatrixControl();
modelling_matrix_control.ShowElements(out transform);
// extract the translation from the transformation
Vector translation = new Vector(transform[12], transform[13], transform[14]);
BoundingKit bounding_kit;
if (segment_with_transform.ShowBounding(out bounding_kit))
{
SimpleSphere sphere;
SimpleCuboid cuboid;
bounding_kit.ShowVolume(out sphere, out cuboid);
if (translation.x / sphere.radius > cutoff ||
translation.y / sphere.radius > cutoff ||
translation.z / sphere.radius > cutoff)
{
// This translation seems too large given the model's bounding.
if (baseline_translation == Vector.Zero())
{
// This is the first translation to be removed for this model. Store it in case more translations need to be removed
baseline_translation = translation;
modelling_matrix_control.Translate(-translation);
}
else
{
// This is not the first translation to be removed.
// Take the baseline translation into account so that relative positioning is maintained.
Vector delta = new Vector(translation -baseline_translation);
modelling_matrix_control.Translate(-translation.x + delta.x, -translation.y + delta.y, -translation.z + delta.z);
}
}
}
it.Next();
}



Models that contain geometry defined to be very far away from the origin

It is also possible for a model to be placed very far away from the origin, not because or large translations, but because the geometry itself is defined that way. Just like the previous case, this is only a problem if the location of the model is very far away when compared to the size of the model - a model with a bounding box of 10 located millions of units away from the origin will cause problems, while a much larger model will render without precision issues.

This issue is more costly to rectify than the previous case, since it involves changing each piece of geometry in the model, rather than only changing only the translations that are too large compared to the model's size. As such, the possibility of precision loss due to large translations should be considered first.

This issue can be rectified as follows:

  1. After the model has been imported, compute its bounding.
  2. If the bounding radius is much smaller than the bounding center, then the geometry data should be edited to place it closer to the origin.
  3. Calculate the vector that would translate the bounding's center to the origin.
  4. Subtract this vector from the vertices of the geometry in the scene. When doing this, rotations that are found between the model segment and the segment where geometry is being edited need to be taken into account.
// This snippet assumes that the possibility of very large translations has already been taken into account
float cutoff = 10000;
Vector translation_vector = Vector::Zero();
BoundingKit bounding_kit;
if (model.GetSegmentKey().ShowBounding(bounding_kit))
{
SimpleSphere sphere;
SimpleCuboid cuboid;
bounding_kit.ShowVolume(sphere, cuboid);
if (sphere.center.x / sphere.radius > cutoff ||
sphere.center.y / sphere.radius > cutoff ||
sphere.center.z / sphere.radius > cutoff)
{
// The model is very far away from the origin
translation_vector = Vector(-sphere.center);
}
}
if (translation_vector != Vector::Zero())
{
// edit geometry vertices taking into account any rotations
// for example, for a shell:
ShellKey shell;
PointArray points;
shell.ShowPoints(points);
for (Point & one_point : points)
one_point += translation_vector;
shell.EditPointsByReplacement(0, points);
}
// This snippet assumes that the possibility of very large translations has already been taken into account
float cutoff = 10000;
Vector translation_vector = Vector.Zero();
BoundingKit bounding_kit;
if (model.GetSegmentKey().ShowBounding(out bounding_kit))
{
SimpleSphere sphere;
SimpleCuboid cuboid;
bounding_kit.ShowVolume(out sphere, out cuboid);
if (sphere.center.x / sphere.radius > cutoff ||
sphere.center.y / sphere.radius > cutoff ||
sphere.center.z / sphere.radius > cutoff)
{
// The model is very far away from the origin
translation_vector = new Vector(sphere.center * -1);
}
}
if (translation_vector != Vector.Zero())
{
// edit geometry vertices taking into account any rotations
// for example, for a shell:
ShellKey shell = new ShellKey();
Point[] points;
shell.ShowPoints(out points);
for (int i = 0; i < points.Length; i++)
points[i] += translation_vector;
shell.EditPointsByReplacement(0, points);
}



Loading multiple models with large coordinates into the same scene

When multiple models are to be loaded into the same scene, the strategies discussed above need to be repeated for each model. All transformations across models need to maintain their relative effect with each other, therefore, we need to apply the same transformations to each model.

Example:

A two-story house is divided into two models, each representing one floor. The first floor is loaded, and a very large translation of 10 million units along the X axis is removed, as to avoid precision issues.

The second floor is then loaded, and we notice that this model, apart from having a 10 million units translation along the X axis, also contains a 50 unit translation along the Y axis. If this was the first model being loaded, we would remove the translation along both axis, but since it is the second model loaded into the same scene, the previously removed translation needs to be removed here as well.

The result will be that while the translation along the X axis will be removed, the translation along the Y axis needs to be maintained, so that the second floor model will be placed correctly above the first floor model.

If you are loading multiple models using the Exchange Sprocket, this process is automated by Visualize, if the following conditions are met:

  1. Each part of the model should be imported with the HPS::Exchange::ImportOptionsKit::SetLargeTranslationExtraction setting enabled.
  2. Models other than the first one should be added to the scene using the HPS::Exchange::ImportOptionsKit::SetLocation method.
  3. The second argument of SetLargeTranslationExtraction, denoting the user data index where extracted translations are saved should be the same between imports of models that belong in the same scene.

Incremental loading

Loading a large, complex assembly can take a significant amount of time and memory. When working with large assemblies, sometimes it does not make sense to load the whole assembly if you are only interested in a single part. To solve this problem, parts of CAD models may be loaded individually. Incremental loading only works with certain formats - see "Limitations" at the end of this section for details.

To perform an incremental load, set the import mode to "incremental" before loading:

importOptions.SetMode(HPS::Exchange::ImportMode::Incremental);
// perform an import with the incremental import mode
HPS::Exchange::ImportNotifier notifier = HPS::Exchange::File::Import(filename, importOptions);
notifier.Wait();
HPS.Exchange.ImportOptionsKit importOptions = new HPS.Exchange.ImportOptionsKit();
importOptions.SetMode(HPS.Exchange.ImportMode.Incremental);
// perform an import with the incremental import mode
HPS.Exchange.ImportNotifier notifier = HPS.Exchange.File.Import(filename, importOptions);
notifier.Wait();



When this mode is enabled, only the structure of the CAD file will be imported. This means that no geometry is tessellated or displayed. Instead, you get the assembly tree structure with which you can the traverse the hierarchy and selectively load a part on demand. You do this by calling HPS::Exchange::ImportOptionsKit::SetIncrementalComponentPath (or SetIncrementalComponentPaths) with the components you wish to load:

// get the CADModel that contains the structure of the file
HPS::Exchange::CADModel cadModel = notifier.GetCADModel();
// construct the appropriate path
HPS::Component rootProductOccurrence = cadModel.GetSubcomponents()[0];
HPS::Component componentToLoad = rootProductOccurrence.GetSubcomponents()[0];
HPS::Component pathComponents[] = { componentToLoad, rootProductOccurrence, cadModel };
HPS::ComponentPath pathToLoad(3, pathComponents);
// set this as the incremental component path to load
importOptions.SetIncrementalComponentPath(pathToLoad);
// set other import options as appropriate
// now options related to representation items, like Solids or Surfaces, will be respected
// perform an import with this component path - only the specified component path will be imported
notifier = HPS::Exchange::File::Import(filename, importOptions);
notifier.Wait();
// get the CADModel that contains the structure of the file
HPS.Exchange.CADModel cadModel = notifier.GetCADModel();
// construct the appropriate path
HPS.Component rootProductOccurrence = cadModel.GetSubcomponents()[0];
HPS.Component componentToLoad = rootProductOccurrence.GetSubcomponents()[0];
HPS.Component [] pathComponents = { componentToLoad, rootProductOccurrence, cadModel };
HPS.ComponentPath pathToLoad = new ComponentPath(pathComponents);
// set this as the incremental component path to load
importOptions.SetIncrementalComponentPath(pathToLoad);
// set other import options as appropriate
// now options related to representation items, like Solids or Surfaces, will be respected
// perform an import with this component path - only the specified component path will be imported
notifier = HPS.Exchange.File.Import(filename, importOptions);
notifier.Wait();



At this point, the CADModel contains the loaded component path, along with the other product occurrences that have not been loaded. One can load another component path by repeating the same steps, or one can specify multiple ComponentPath objects to load simultaneously via the Exchange::ImportOptionsKit::SetIncrementalComponentPaths function. In any case, the load functionality happens at the level of a product occurrence, so that is what the leaf component in the incremental ComponentPath should be.

Unloading product occurrences and querying load status

Another piece of the integration with incremental load is the ability to query a product occurrence for its load status and the ability to unload it. To this end, there is a new Exchange::ProductOccurrence class that provides an Unload and GetLoadStatus function.

Unloading a particular product occurrence from Exchange simply requires invoking the Unload function on an Exchange::ProductOccurrence:

// the product occurrence can only be constructed from a Component whose
// component type is Component::ComponentType::ExchangeProductOccurrence
HPS::Exchange::ProductOccurrence productOccurrence(someComponent);
// unload data from Exchange and remove corresponding Component objects
productOccurrence.Unload(HPS::Exchange::UnloadMode::ExchangeAndVisualization);
// or, equivalently
productOccurrence.Unload();
// alternatively, to just unload Exchange data but leave components and visualization
productOccurrence.Unload(HPS::Exchange::UnloadMode::ExchangeOnly);
// the product occurrence can only be constructed from a Component whose component type is Component.ComponentType.ExchangeProductOccurrence
HPS.Exchange.ProductOccurrence productOccurrence = new HPS.Exchange.ProductOccurrence(someComponent);
// unload data from Exchange and remove corresponding Component objects
productOccurrence.Unload(HPS.Exchange.UnloadMode.ExchangeAndVisualization);
// or, equivalently
productOccurrence.Unload();
// alternatively, to just unload Exchange data but leave components and visualization
productOccurrence.Unload(HPS.Exchange.UnloadMode.ExchangeOnly);



Whether or not the unload removes Component objects depends on the Exchange::UnloadMode value passed to Exchange::ProductOccurrence::Unload. Passing Exchange::UnloadMode::ExchangeAndVisualization will result in Exchange data being unloaded and components being deleted, while passing Exchange::UnloadMode::ExchangeOnly will only unload the data from Exchange, but leave the component objects and their associated visualization data in memory.

An "Exchange only" unload is meant to allow memory savings by removing the Exchange data, but allow the visualization to remain. Some functionality may be lost with this type of unload, since it will generally no longer be possible to query the associated Exchange entity for things like its name or physical properties. Additionally, either form of unload will modify the underlying Exchange data, so operations performed on those underlying Exchange entities will similarly have unloaded entities, which will affect what gets exported for the various CAD exports or 3D PDF export.

It is possible to query the current load status of a product occurrence via Exchange::ProductOccurrence::GetLoadStatus. This can be used to determine if a product occurrence has not been loaded, has been loaded, has only visualization data, or is partially loaded (i.e., a product occurrence contains other product occurrences, some of which are loaded, some of which are not). This allows one to display special icons for the various load states in a model browser, or to determine what kind of load functionality to allow for a particular product occurrence, for example.

Limitations to incremental loading

This functionality is restricted to SolidWorks, NX (Unigraphics), Creo (Pro/E), and CATIA V5 files. The API won't allow this functionality to be used for files that are not of those formats. Additionally, while native CATIA V5 assemblies will work with incremental load, V5 assemblies that contain or wrap V4 assemblies will not work. If incremental loading is attempted with an unsupported format, an exception will be thrown.

The ability to load and unload product occurrences in an assembly is only available if the file is originally loaded in incremental mode. The API does not allow the use of those functions for CADModel objects which were not loaded incrementally.

Incremental load will not work with parts in an assembly for which there is no corresponding file with BRep data. For instance, you can have a SolidWorks assembly and only a subset of the parts it references as files on disk. If you perform a complete import of the assembly (calling Exchange::ImportOptionsKit::SetMode with Exchange::ImportMode::Complete), geometry will still be inserted for those parts which don't have the corresponding part file if Exchange is able to extract the tessellation from the SolidWorks assembly. If you perform an incremental import of such an assembly (calling Exchange::ImportOptionsKit::SetMode with Exchange::ImportMode::Incremental), it will not be possible to load those parts for which there is no part file on disk (trying to do so will simply result in nothing getting loaded for that part). Rather, it will only be possible to load parts for which there is a part file. Similarly, using incremental load with a CATIA V5 assembly will only work for components corresponding to CATPart or CATProduct files. Any components referring only to CGR files cannot be loaded incrementally.

Handling views, metadata, and filters

Handling views

In the first code snippet on this page, notice that ActivateDefaultCapture returns a HPS::View object. This is the same HPS::View discussed in the view hierarchy section. Some model formats include a view of a particular state of the model, and ActivateDefaultCapture will load that view. The view may include a camera, toggle visibility for cutting planes, PMI, and geometry with the scene. Additionally, it may apply transformations to some geometry, for example, create an exploded view of an assembly.

Certain model formats allow multiple views to be pre-defined, and you may be interested in loading some view other than the default. The code below demonstrates how to do this.

CaptureArray captureArray = modelFile.GetAllCaptures();
for (size_t i = 0; i < captureArray.size(); i++)
{
HPS::Capture capture = captureArray[i];
HPS::StringMetadata metadata = capture.GetMetadata("Name");
HPS::UTF8 viewName = metadata.GetValue();
if (viewName == "<some interesting view>")
{
capture.Activate();
break; // exit loop
}
}
HPS.Capture[] captureArray = modelFile.GetAllCaptures();
for (int i = 0; i < captureArray.Length; i++)
{
Capture capture = captureArray[i];
HPS.StringMetadata metadata = new HPS.StringMetadata(capture.GetMetadata("Name"));
String viewName = metadata.GetValue();
if (viewName == "<some interesting view>")
{
capture.Activate();
break;// exit loop
}
}


Handling metadata

Metadata is additional non-geometric information that is associated with an HPS::Exchange::Component. Each piece of metadata is a name-value pair, and the values can be strings, integers, unsigned integers, doubles, Booleans, or times. Any HPS::Exchange::Component can have metadata associated with it. During the import process, metadata is generally only imported if the HPS::Exchange::ImportOptionsKit::SetAttributes is set to true. However, some metadata, such as faces, certain IDs, layers, and materials, are always imported even if HPS::ImportOptionsKit::SetAttributes is set to false. A list of that metadata is as follows:

Metadata nameTypeDescription
"Name"StringName of this component.
"PersistentID"non-zero unsigned int
"NonPersistentID"non-zero unsigned int
"Layer"unsigned intLayer number where this component is located.
"Filename"StringFile name of this model.
"FileFormat"StringFile format for this model.
"IsAnnotationCapture"BooleanOnly available for components of type capture. Value is true if the capture is an annotation capture, false otherwise.
"Units"StringThe units of the model. Only available from the HPS::Exchange::CADModel.
"IsDefaultSheet"BooleanWhether the sheet is the default sheet for a drawing. Only available for components of type HPS::Component::ComponentType::ExchangeDrawingSheet.

Metadata can be embedded with a HPS::Exchange::Component in many different forms. The following example demonstrates how to get metadata associated with a HPS::Exchange::Component:

// get all metadata associated with a component
MetadataArray metadataArray = modelFile.GetAllMetadata();
double someDouble;
int someInteger;
unsigned int someUnsignedInt;
for (size_t i = 0; i < metadataArray.size(); i++)
{
HPS::Metadata metadata = metadataArray[i];
// metadata can be of many different types
// you must test for type of metadata
if (metadata.Type() == HPS::Type::StringMetadata)
{
HPS::StringMetadata sm(metadata);
HPS::UTF8 metadataName = sm.GetName();
HPS::UTF8 someString = sm.GetValue();
}
else if (metadata.Type() == HPS::Type::DoubleMetadata)
{
HPS::DoubleMetadata dm(metadata);
HPS::UTF8 metadataName = dm.GetName();
someDouble = dm.GetValue();
}
else if (metadata.Type() == HPS::Type::IntegerMetadata)
{
HPS::IntegerMetadata im(metadata);
HPS::UTF8 metadataName = im.GetName();
someInteger = im.GetValue();
}
else if (metadata.Type() == HPS::Type::UnsignedIntegerMetadata)
{
HPS::UTF8 metadataName = uim.GetName();
someUnsignedInt = uim.GetValue();
}
else if (metadata.Type() == HPS::Type::TimeMetadata)
{
HPS::TimeMetadata tm(metadata);
HPS::UTF8 metadataName = tm.GetName();
someUnsignedInt = tm.GetValue();
// as a convenience, TimeMetadata can also be provided as a String
HPS::UTF8 timeString = tm.GetValueAsString();
}
}
// get all metadata associated with a component
HPS.Metadata[] metadataArray = modelFile.GetAllMetadata();
for (int i = 0; i < metadataArray.Length; i++)
{
HPS.Metadata metadata = metadataArray[i];
// metadata can be of many different types
// you must test for type of metadata
if (metadata.Type() == HPS.Type.StringMetadata)
{
HPS.StringMetadata sm = new HPS.StringMetadata(metadata);
String metadataName = sm.GetName();
String someString = sm.GetValue();
}
else if (metadata.Type() == HPS.Type.DoubleMetadata)
{
HPS.DoubleMetadata dm = new HPS.DoubleMetadata(metadata);
String metadataName = dm.GetName();
double someDouble = dm.GetValue();
}
else if (metadata.Type() == HPS.Type.IntegerMetadata)
{
HPS.IntegerMetadata im = new HPS.IntegerMetadata(metadata);
String metadataName = im.GetName();
int someInteger = im.GetValue();
}
else if (metadata.Type() == HPS.Type.UnsignedIntegerMetadata)
{
String metadataName = uim.GetName();
uint someUnsignedInt = uim.GetValue();
}
else if (metadata.Type() == HPS.Type.TimeMetadata)
{
HPS.TimeMetadata tm = new HPS.TimeMetadata(metadata);
String metadataName = tm.GetName();
uint someUnsignedInt = tm.GetValue();
// as a convenience, TimeMetadata can also be provided as a String
String timeString = tm.GetValueAsString();
}
}


Activating filters

Filters are found in some CAD files and provide the capability to show or hide certain pieces of geometry or objects that lie in certain layers. Filters can be toggled once a CAD file has been imported via the Visualize-Exchange API. Filters may also be toggled when captures are activated. Note that a filter can be attached to any component of the model. You may use HPS::CADModel::GetAllFilters to get a list of all HPS::Filter objects in the HPS::CADModel. The following code example shows how to activate a filter on a view:

HPS::CADModel cadModel = notifier.GetCADModel();
HPS::View myFirstView = cadModel.GetAllCaptures()[0].Activate();
// get list of filters
HPS::FilterArray filters = cadModel.GetAllFilters();
filters[0].Activate(myFirstView); // activate first filter
CADModel cadModel = notifier.GetCADModel();
HPS.View myFirstView = cadModel.GetAllCaptures()[0].Activate();
// get list of filters
HPS.Filter[] filters = cadModel.GetAllFilters();
filters[0].Activate(myFirstView); // activate first filter


More than one filter may be active at a particular time. To activate or deactivate a filter, you first need to get a reference to it. You can get a list of all the active filters on a HPS::View by using this logic:

HPS::FilterArray filterArray = modelFile.GetActiveFilters(myFirstView);
for (size_t i = 0; i < filterArray.size(); i++)
{
HPS::Filter filter = filterArray[i];
// deactivates this filter
filter.Deactivate(myFirstView);
}
HPS.Filter[] filterArray = modelFile.GetActiveFilters(myFirstView);
for (int i = 0; i < filterArray.Length; i++)
{
HPS.Filter filter = filterArray[i];
// deactivates this filter
filter.Deactivate(myFirstView);
}


Exporting files

All file formats that Exchange can export are supported via the Visualize-Exchange API (ExportACIS, ExportIGES, ExportJT, ExportParasolid, ExportPRC, ExportSTEP, ExportSTL, ExportU3D, and ExportVRML). All export formats have their own Export*OptionsKit (except for VRML which has no options).

Writing a file through Exchange is an operation similar to reading:

try
{
}
catch (const HPS::IOException& ioe)
{
// handle error
}
try
{
HPS.Exchange.File.ExportJT(modelFile, filename, new HPS.Exchange.ExportJTOptionsKit());
}
{
// handle error
}


Exchange can import many more file types than it can export. A list of supported file formats is located here (authentication required).

Exporting Visualize scene graph data with an Exchange model

HOOPS Visualize does not use B-rep (boundary representation) calculations when rendering. All models are tessellated. However, models will be exported with B-rep if the original Exchange model was loaded with B-rep. If your application adds Visualize scene graph data to the scene, and you have a need to export that data along with the Exchange data, the export process is more complex.

Exporting Visualize data with Exchange data is possible using the ExportPRC function. As the name implies, PRC is the only format in which this operation is permitted. The general sequence is explained below:

  • Create a HPS::KeyPathArray containing the key paths to the Visualize geometry you want to be part of the export.
  • Set that key path on an HPS::ExportPRCOptionsKit using the SetAdditionalKeyPaths function.
  • Call HPS::Exchange::File::ExportPRC, passing it the HPS::CADModel, the output file name, and the HPS::ExportPRCOptionsKit containing the extra key paths.

There exist overloads of the ExportPRC function which do not accept a HPS::CADModel. These overloads will export everything which is pointed to by the key paths you pass to them, but in those cases you will only get tessellation - B-rep is not included.

If you need to export Exchange and Visualize data to a non-PRC file format, we recommend you first export to PRC, then use Exchange to translate the file to the format of your choice.

Event handling

During the import process, Exchange will trigger events when it detects a problem or has information to give you. For example, if Exchange couldn't find a referenced subcomponent in an assembly, or if there was some sort of import failure, a warning or error could be triggered. It is the responsibility of your application to catch these events and respond appropriately.

The Visualize-Exchange integration will capture all events and wrap them into one of three types: HPS::InformationEvent, HPS::ErrorEvent, or HPS::WarningEvent. Each of these types derive from HPS::Event, thus, handling them is identical to handling any other type of Visualize event (event handling is discussed here).

WarningEvent and InformationEvent contain both a code and a message field that help to determine what caused the event.

Data mapping

It is important for developers to be able to map objects within their Visualize database to the corresponding entities as loaded by HOOPS Exchange. Exchange first maps all incoming CAD data to the industry standard 'PRC' format, which in turn is then mapped to the Visualize scene graph. Visualize interfaces to each Exchange entity through an HPS::Exchange::Component object. Using a HPS::Exchange::Component, you can query the underlying PRC data for model information. Exchange objects are called entities and Visualize objects are components. Here is an example of some basic Exchange interactions:

// get Visualize-Exchange component interface object for corresponding Visualize key
HPS::Component component = modelFile.GetComponentFromKey(hpsKey);
// test if the object type is a product occurrence
// [see Component reference manual page for other Exchange types]
{
// do something with this object
}
// gets the native Exchange entity that corresponds to the component
const A3DEntity* entity = ((HPS::Exchange::Component) component).GetExchangeEntity();
// gets the keys associated with the Component
KeyArray keys = component.GetKeys();
// at this point, you can use the returned entity to query model information using the Exchange API
// get HPS-Exchange component interface object for corresponding HPS key
HPS.Component component = modelFile.GetComponentFromKey(hpsKey);
// test if the object type is a product occurrence
// [see table below for other Exchange types]
if (component.GetComponentType() == HPS.Component.ComponentType.ExchangeProductOccurrence)
{
// do something with this object
}
// gets the native Exchange entity that corresponds to the component
HPS.Exchange.Component myExchangeComponent = new Exchange.Component(cadModel.GetSubcomponents()[0]);
// by saving a reference to the Exchange entity, you don't have to create a new Exchange object each time you need to access it
IntPtr exchangeEntity = ((HPS.Exchange.Component) myExchangeComponent).GetExchangeEntity();
// gets the keys associated with the Component
HPS.Key[] keys = component.GetKeys();


At the end of the code snippet above, Exchange is integrated, and you can use then use the entity references to manipulate model data using the Exchange API.

Using type masks

Commonly, it is necessary to determine whether a component contains a certain type of entity. If you are only looking for a specific type, you can use HPS::Component::GetComponentType to test against the returned type. But some generalized entities can take many forms. For example, PMI can exist as plain text, rich text, or GD&T, among other things. Rather than checking against all types of PMI, you can use a PMI mask with the HasComponentType function. This operation will tell you whether the component contains any type of PMI.

Type masks also exist for topology and representation items. A usage example is as follows:

if (component.HasComponentType(HPS.Component.ComponentType.ExchangePMIMask))
{
// it is a piece of PMI
}
if (component.HasComponentType(HPS.Component.ComponentType.ExchangeTopologyMask))
{
// it is topology
}
if (component.HasComponentType(HPS.Component.ComponentType.ExchangeRepresentationItemMask))
{
// it is a representation item
}


Keeping HOOPS Visualize synchronized with HOOPS Exchange

When you make changes to a Visualize scene graph which represents a HOOPS Exchange model, those changes affect the Visualize scene graph only. Likewise, if you modify a model's PRC data using the HOOPS Exchange API, the corresponding Visualize scene graph is unchanged. To ensure the PRC data and the Visualize scene graph are synchronized, further action is necessary.

If your changes are relatively simple, such as a deleting a PRC entity, you can call HPS::Component::Delete on the corresponding HPS::Component to update the Visualize scene graph. (For further details on the behavior of the HPS::Component::Delete function, please refer to the API Reference.)

However, if you have a need to make more substantive changes, first make the modification using the Exchange API. Next, call HPS::Exchange::Component::Reload to bring the scene graph and component hierarchy up to date. The reload should be invoked at or above the level where the changes were made, thus ensuring Visualize doesn't try to access potentially deleted PRC entities. It is important to only call Reload() for components that have been changed, as reloading can be an expensive operation for complex models. Make use of a notifier if you need to determine when the reload is complete:

HPS::Exchange::ImportNotifier importNotifier = HPS::Exchange::File::Import(filename, importOptionsKit);
importNotifier.Wait();
Exchange::CADModel myCADModel = importNotifier.GetCADModel();
// ... modify PRC via Exchange APIs
Exchange::ReloadNotifier reloadNotifier = exchangeComponent.Reload();
// can query notifier for status or progress
reloadNotifier.Wait(); // will block until the reload thread completes
myCADModel.ActivateDefaultCapture();
HPS.Exchange.ImportNotifier importNotifier = HPS.Exchange.File.Import(filename, importOptionsKit);
importNotifier.Wait();
Exchange.CADModel myCADModel = importNotifier.GetCADModel();
// ... modify PRC via Exchange APIs
Exchange.ReloadNotifier reloadNotifier = exchangeComponent.Reload();
// can query notifier for status or progress
reloadNotifier.Wait(); // will block until the reload thread completes
myCADModel.ActivateDefaultCapture();


Please note that the reload process rebuilds any prototypes and as a result, all existing Exchange pointers to the reloaded components will be invalidated.

It also is recommended that you re-activate a capture after a component reload completes. This will ensure that the visibility of components in the scene is correct, particularly for items whose visibility is conditional - for example, PMI. It is possible that a model may look correct without re-activating a capture, but to work in general, it is necessary to re-activate a capture after a reload. Be sure to keep a reference to the CAD model object to re-activate the capture (see previous sections for capture examples).

Implementing a copy/paste/cut/move workflow

HOOPS Visualize offers several convenient functions to enable you to quickly adjust the position of components in the model tree. A common use for this functionality is a copy/paste/cut/move workflow. Although possible, it can be complicated to edit the PRC structure directly using HOOPS Exchange, since the management of the PRC model, links, and the associated HPS view require important expertise in both HPS scene graph and PRC management. For this reason, a high-level API is available to implement these common operations.

  • Copy/paste workflow: The "copy/paste" workflow can be accomplished with the function HPS::Component::CopyTo(HPS::Component in_component, HPS::Component target_parent). This function creates a copy of the A3DEntity associated with the HPS::Component in_component, and adds it under the A3DEntity associated with the HPS::Component target_parent in the PRC. It will then create a new HPS::Component, associate it with the A3DEntity copied and then return this component. Following this function, you need to call HPS::Exchange::Component::Reload() from the returned component to create the subcomponent tree of this newly created component. You only need to call Reload() from this component, instead of having to call Reload() from the HPS::CADModel or the target component. For performance reasons, it is always a good idea to call Reload() from the lowest point of the tree possible. Lastly, you should check the type of the returned component to see if the function ran successfully (a component of type None means that the function failed).

  • Delete workflow: The "delete" workflow is performed using the function HPS::Component::Delete with the option HPS::Component::DeleteMode::StandardAndExchange. This function will remove the A3DEntity link to the component and clean up the link from and to that entity inside of the PRC, and then perform a standard delete on that component. This function does not require a reload to synchronize the HPS component tree and PRC entities structure.

  • Move workflow: The "move" workflow can be done by combining the copy/paste workflow and the deletion workflow: you copy/paste the copied component under the new parent, and then delete the original component. This function requires a call to HPS::Component::Reload() to properly update the HPS component tree.

Please note that the implications of using CopyTo are slightly different depending on the type of entity you are operating on. Please see the reference manual for details.

Component creation using a custom importer

HOOPS Visualize takes care of all of the import logic for supported file formats, including HOOPS Exchange. In doing so, it creates an in-memory model structure which consists of the HPS::Component objects mentioned in the previous section. If you have a need to write your own importer, you will need a way to create these components as you parse the input file. HOOPS Visualize allows you to do this using its component creation API functions.

The first step in this process is to create the root component, which is the HPS::Exchange::CADModel. This is the only object in the Exchange structure that has no owner. From this point, you can build the model structure as you parse it, using the HPS::Exchange::Factory static class to create each component.

// create the CAD model
// ... parse file
// if your entity is a product occurrence
// create the CAD model
HPS.Exchange.CADModel cadModel = HPS.Exchange.Factory.CreateCADModel();
// ... parse file
// if your entity is a product occurrence
HPS.Exchange.Component component = HPS.Exchange.Factory.CreateComponent
(cadModel, HPS.Component.ComponentType.ExchangeProductOccurrence, myEntity);


Additionally, see the reference manual entries for HPS::Component::AddKey, HPS::Component::AddOwner, and HPS::Component::AddReference, which aid with component creation in certain situations. For example, to associate a geometry key will a component, use AddKey.

Limitations

As Exchange itself is only implemented for Windows, Linux, OSX, iOS, and Android, the Visualize-Exchange integration is not applicable to other operating systems.

It is also important to note that the Visualize-Exchange integration ignores B-rep data and asks HOOPS Exchange to provide all data in tessellated form.

Further reading

A further discussion on the inner workings of Exchange is beyond the scope of this manual, however, the main source for Exchange documentation can be viewed here (authentication required).