HOOPS Exchange Integration

Using the HOOPS Exchange HIO Component

The HOOPS Exchange HIO component (HIO Exchange) supports both import and export capabilities. For import, it utilizes the capabilities of HOOPS Exchange to access assembly, tessellation, and PMI information from many MCAD formats and bring it into the HOOPS/3DGS scene-graph. A full list of the supported HOOPS Exchange formats is detailed here. Before reviewing the specifics of this component, developers should become familiar with the general HIO architecture and capabilities, covered in this section of the HOOPS/MVO Programming Guide.

For export, the component supports exporting the HOOPS/3DGS scene graph information to IGES, STEP, Parasolid, and STL. (The scene-graph can also be exported to the PRC format, which is the native 3D representation used within PDF. Exporting to a 3D PDF requires a license to the HOOPS Publish product.)

The HIO Exchange component is delivered in the form of a .hio file, which gets dynamically loaded by the HOOPS/MVO file import logic. To ensure your application can access the hio_exchange component, perform the following:

Windows:

  • Create a hio_plugins directory in your application’s working directory.

  • Copy the hioexchange.hio file found in <hoops>/bin/<platform>/hio_plugins/hio_exchange to the hio_plugins directory created in the previous step.

Linux:

  • Copy the hioexchange.hio file found in <hoops>/bin/<platform> into your application’s working directory.

Additionally, ensure the HOOPS Exchange DLLs are in your environment path. During start-up, when HOOPS/MVO finds the HIO Exchange component and HOOPS Exchange DLLs, it will perform the following steps:

  1. Create the appropriate input and output handler.

  2. Register the handler and its associated file extensions with the HIOManager.

Once the hio_exchange component is successfully loaded, your application will be able to import any of the files supported by HOOPS Exchange. To utilize the extensive HOOPS Exchange API services, you must compile against HOOPS Exchange directly.

Importing Examples

Reading a file involves setting up the HInputHandlerOptions structure with a valid HOOPS Exchange license key and HBaseView object, and then loading the model into the desired HOOPS Visualize scene-graph segment by calling HBaseModel::Read. The following shows how you would import a CATIA file:

#include "HIOManager.h"

HFileInputResult result = InputOK;
HInputHandlerOptions input_options;

// set up the input options with a HOOPS Exchange license key and an HBaseView object
// NOTE:  if the license key has already been included via an initial call to HC_Define_System_Options at startup, it does not need to be included here
input_options.m_license = const char* licensekey = "<license_key_string>";
input_options.m_pHBaseView = my_view;

// open up the desired HOOPS/MVO model segment and read the file
HC_Open_Segment_By_Key(<my model key>);
        result = HBaseModel::Read("c:\\temp\\myFile.CATProduct", view, true, &input_options);
HC_Close_Segment();

/*
After reading, a handle to the PRC data is accessible via the HInputHandlerOptions structure, and you can directly query assembly,
topology and PMI information using the HOOPS Exchange API. This PRC data handle would also be used to export to a 3D-PDF if there
is an active license for HOOPS Publish. Refer to the HOOPS Publish HIO connector docs for more information.
*/

m_pPRCAsmModelFile = input_options.m_pPRCAsmModelFile;

Important: If you’re loading DWG files, the above method will not work if you have HIO_DWG compiled because Visualize defaults to using HIO_DWG as the importer for DWG files. To force Exchange to handle import of a DWG file, use the HInputHandler method instead, with “dwg_with_prc” (or for DXF files, use “dxf_with_prc”) as the parameter for \ref HIOManager::GetInputHandler() "GetInputHandler()":

HInputHandler * dwg_handler = HDB::GetHIOManager()->GetInputHandler("dwg_with_prc");

if (dwg_handler != nullptr)
{
        HInputHandlerOptions opts;
        char texture_directory[] = "/path/to/textures";
        opts.m_pExtendedData = (void *)texture_directory;

        // import DWG file into model segment
        HFileIOResult result = dwg_handler->FileInputByKey("filename.dwg", m_pHView->GetModelKey(), &opts);
        if (result == HIO_OK)
        {
                // file loaded OK
        }
        else {
                // handle error
        }
}
else {
        // handle error
}

(This method can be used for all file types, not just DWG.)

Tessellation Options The following example demonstrates how to use the HTessellationSettings class to customize level-of-detail (LOD) and other tessellation settings when using HOOPS Exchange to import a CAD file and generate tessellation:

HTessellationSettings tessellation;
HInputHandlerOptions options;
options.m_pHBaseView = my_view;
options.m_pExtendedData = &tessellation;

HInputHandler * handler = HDB::GetHIOManager()->GetInputHandler(my_CAD_file_extension);
if (handler == nullptr)
{
        // something went wrong
}

HFileInputResult result;

HC_Open_Segment_By_Key(my_view->GetModelKey()); {
        HC_KEY predefined_key = HC_Open_Segment("predefined");
        {
                HC_Insert_Distant_Light(-1, 0, 1.5);
                tessellation.m_TessLod = HTessellationSettings::HTessellationLOD::TessLODExtraLow;
                result = handler->FileInputByKey(your_filename, predefined_key, &options);
                if (result != HIO_OK)
                {
                        // something went wrong
                }
        } HC_Close_Segment();

        HC_KEY custom_key = HC_Open_Segment("custom");
        {
                HC_Insert_Distant_Light(-1, 0, 1.5);
                options.m_pPRCAsmModelFile = nullptr;
                tessellation.m_TessLod = HTessellationSettings::HTessellationLOD::TessLODUserDefined;
                tessellation.m_dChordHeightRatio = 50;
                tessellation.m_dAngleToleranceDeg = 40;
                tessellation.m_bUseHeightInsteadOfRatio = false;
                result = handler->FileInputByKey(filename, custom_key, &options);
                if (result != HIO_OK)
                {
                        // something went wrong
                }
        } HC_Close_Segment();
} HC_Close_Segment();

This example shows two separate methods for generating tessellation, each storing the desired tessellation settings in the ref HInputHandlerOptions::m_pExtendedData field that will be passed as part of the options object to ref FileInputByKey().

The first approach simply assigns the HTessellationSettings::m_TessLod option to the predefined setting HTessellationSettings::HTessellationLOD::TessLODExtraLow.

The second example demonstrates how to directly manipulate tessellation settings to better customize results using the option HTessellationSettings::HTessellationLOD::TessLODUserDefined, which allows users to define their own custom values for tessellation-specific settings such as HTessellationSettings::m_dChordHeightRatio.

Obtaining and Selecting Configurations

Several of the CAD file formats supported by HOOPS Exchange have a concept of multiple model ‘configurations’ which can be stored in the file. Each configuration may have a list of ‘views’, described below. HOOPS Exchange is able to access the list of configurations at load time, and this info can in turn be accessed in HIO Exchange by defining a load-time callback using HInputHandlerOptions::m_pPRCMultiModelSelectionCallback. This method will provide your callback with the number of configurations, and let you pass back the configuration that you’ve selected for loading. (You would typically provide the list of configurations in a GUI pulldown dialog, and use the configuration selected by the user as the ‘out_config_selection’).

The callback is specified in the HInputHandlerOptions used to control file input:

input_options.m_pPRCMultiModelSelectionCallback = MultiModelSelectionCallback;

Controlling Import of Hidden Objects

If HInputHandlerOptions::m_bPRCImportHiddenObjects is set to true, representation items marked as ‘hidden’ will be imported but have their visibility turned off. If set to false, representation items marked as hidden will be skipped and not imported

Exporting Example

You can write files in formats supported by HOOPS Exchange from your HOOPS 3DF-based application using code similar to this:

#include "HIOManager.h"

HFileOutputResult result = OutputFail;
HOutputHandlerOptions output_options;

/* Let's assume that m_pPRCAsmModelFile is a pointer to a PRC model that was either constructed by your application code, or returned from HOOPS Exchange. We pass in the handle to the PRC data via the
HOutputHandlerOptions::m_pExtendedData attribute.*/
output_options.m_pExtendedData = m_pPRCAsmModelFile;

result = pSolidModel->WriteWithOptions("c:\\temp\\myFile.iges", my_view, &output_options);

Supported Data

HOOPS Exchange first maps all incoming CAD data to the neutral ‘PRC’ format, which in turn is then mapped to the HOOPS/3DGS scene-graph. Here are some key notes on supported graphical primitives and attributes:

  • All analytics (polycylinders, NURBS surfaces, spheres, ellipses, circles) will be turned into shells via \ref ::Generate_Shell_From_Geometry. Furthermore, all curves will be turned into polylines.

  • Color interpolation is supported via textures. However, color interpolation will always be turned on with such models.

  • Images and simple diffuse textures are supported. However, any texture that has an automatic texture map will appear untextured. Environment maps are supported but secondary diffuse textures and bump maps are not.

  • Subwindows are not currently supported.

  • Heuristics and rendering options are not currently supported. This means that “mask transform” and “screen range” settings are ignored.

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

Views

A view consists of a group of information in the original CAD file, and displays a particular state of the model. This typically includes a camera, set of cutting planes, lights, PMI, background color, and ‘linked items’ which are references to model objects along with specific visibility and position information.

The HIO Exchange module provides support for querying the view information from the PRC data, and mapping it to HOOPS/MVO MarkupManager layers. Each view is associated with a markup ‘layer’. Refer to the Markup Manager Programming Guide for more details on how to query the list of MarkupManager ‘layers’ and open (activate) a layer.

The view information stored in the MarkupManager layers should not be edited, as it is quite specific to the particular CAD-View. If you wish to have additional markups, they should be added to newly created MarkupManager layers.

Mapping Between HOOPS/3DGS and HOOPS Exchange Entities

It is important for developers to be able to map between objects within their HOOPS/3DGS database and the corresponding entities within HOOPS Exchange. Recall that HOOPS Exchange maps CAD data to the PRC data format.

The HIOConnector class supports this data mapping functionality. When you call HIOConnector::GetKernelEntity and pass a HOOPS key, the HIOConnector will return a pointer to the corresponding PRC entity. Similarly, if you provide a pointer to a PRC entity when calling HIOConnector::GetHoopsEntity, the HIOConnector will return the key(s) of object(s) within the HOOPS/3DGS database which are used to represent the PRC entity.

Here is some sample code which sets up and utilizes the HIOConnector capabilities. Note that we create a new HIOConnector object and pass its handle into the HInputHandlerOptions class. After input is complete, the HIOConnector class will encapsulate the mapping information. Example:

    #include "HIOManager.h"

    HFileInputResult result = InputOK;
    HInputHandlerOptions input_options;
    HIOConnector *connector = new HIOConnector;

    // setup the input options with a HOOPS Exchange license key and an HBaseView object
    input_options.m_license = const char* licensekey = "<license_key_string>";
    input_options.m_pHBaseView = my_view;
input_options.m_pConnector = connector;

    // open up the desired HOOPS/MVO model segment and read the file
    HC_Open_Segment_By_Key(<my model key>);
    result = HBaseModel::Read("c:\\temp\\myFile.CATProduct", view, true, &input_options);
    HC_Close_Segment();

    my_view->FitSegment(modelkey);
    my_view->Update();

    HC_Open_Segment_By_Key(<my model key>);
    HC_Begin_Contents_Search("...", "shells");
    char temp[256];
    HC_KEY key;
    while (HC_Find_Contents(temp, &key))
    {
            void *prc_entity = connector->GetKernelEntity(key);
            HC_KEY newkey = connector->GetHoopsEntity(prc_entity);

            if (newkey == key)
                    AfxMessageBox(_T("Same Roundtripped Key"));
    }
    HC_End_Contents_Search();
    HC_Close_Segment();

Once a PRC entity is obtained via the HIOConnector::GetKernelEntity method, we can use various HOOPS Exchange methods to further query the PRC data, including traversing up the PRC topology:

// Get PRC topology entity from the HOOPS key via HIO_Connector:
A3DEntity* pEntity = (A3DEntity *)connector->GetKernelEntity(hoops_key);

// Get "OwnersManager" for PRC entity
A3DTopoItemOwnersManagerGet(pEntity, m_pTopologyOwnersManager));

// Use OwnersManager to retrieve the item's "owner"
A3DTopoItemOwner* pTopoItemEdgeOwner = NULL;
A3DTopoItemOwnersGet(m_pTopologyOwnersManager, pEntity, pTopoItemEdgeOwner);

Tessellation Settings for HIO_Exchange. In the HOOPS/MVO component, a class called HTessellationSettings can be used in conjunction with HIO Exchange to customize a variety of output settings for tessellation, such as triangle size. Please see the API Reference for full details.

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 when being inserted in the Visualize 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. A 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 addresses how to recognize and fix this issue.

The first way to address this issue is to use methods that do not cause precision loss. 3DF supports double precision and provides versions of its core functions that preserve precision. These functions are clearly marked with a D in their name (for example, HC_DInsert_Shell is the double precision version of HC_Insert_Shell). One limitation of this approach is that the MVO libraries operate with single precision data. This means in order to interact with the scene, you would need to create a version of the operators you are interested in using which use double precision function calls. When importing a model through MVO, the HInputHandlerOption::m_attributePrecisionMode can be set to a variety of different values to have importers automatically switch to inserting double precision geometry when deemed necessary.

Aside from this method, there are two additional ways to approach this issue. The correct choice 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 any static model settings.

Models Placed Far From the Origin Through 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 HIO Exchange module, you can opt to have Visualize automatically solve this issue for you, by enabling the HInputHandlerOption::m_bPRCExtractLargeTranslations setting. When this setting is enabled, translations that are much larger than the model size are stripped out, and a user option is inserted where they would have been found instead, so that the original transformation can always be queried, if needed, without compromising visual fidelity.

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.

Example:

float cutoff = 10000;
float baseline_translation[3];
bool baseline_translation_valid = false;

HC_Open_Segment_By_Key(model_key);
HC_Begin_Contents_Search("....", "modelling matrix");
HC_KEY segment_with_transform;

while (HC_Find_Contents(nullptr, &segment_with_transform))
{
        // found items will be the segments that contains transformations
        float transform[16];
        HC_Open_Segment_By_Key(segment_with_transform);
        HC_Show_Modelling_Matrix(transform);
        HC_Close_Segment();

        // extract the translation from the transformation
        float translation[3] = { transform[12], transform[13] , transform[14] };

        HPoint center;
        float radius;
        if (HC_Filter_Circumsphere_By_Key(segment_with_transform, "", &center, &radius))
        {
                if (translation[0] / radius > cutoff ||
                        translation[1] / radius > cutoff ||
                        translation[2] / radius > cutoff)
                {
                        // This translation seems too large given the model's bounding.
                        HC_Open_Segment_By_Key(segment_with_transform);
                        if (!baseline_translation_valid)
                        {
                                // This is the first translation to be removed for this model. Store it in case more translations need to be removed
                                baseline_translation_valid = true;
                                baseline_translation[0] = translation[0];
                                baseline_translation[1] = translation[1];
                                baseline_translation[2] = translation[2];
                                HC_Translate_Object(-translation[0], translation[1], translation[2]);
                        }
                        else
                        {
                                // This is not the first translation to be removed.
                                // Take the baseline translation into account so that relative positioning is maintained.
                                float delta[3] = { translation[0] - baseline_translation[0], translation[1] - baseline_translation[1] , translation[2] - baseline_translation[2] };
                                HC_Translate_Object(-baseline_translation[0] + delta[0], baseline_translation[1] + delta[1], baseline_translation[2] + delta[2]);

                        }
                        HC_Close_Segment();
                }
        }
}

HC_End_Contents_Search();
HC_Close_Segment();

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 above one, 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.

Example:

// 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, in a shell:
        ShellKey shell;
        PointArray points;
        shell.ShowPoints(points);
        for (Point & one_point : points)
                one_point += 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 HIO Exchange module, this process can be automated by Visualize, if the following conditions are met:

  • Each part of the model should be imported with the HInputHandlerOptions::m_bPRCExtractLargeTranslation setting enabled.

  • Models other than the first one should set the HInputHandlerOptions::m_PRCReferenceSegmentKey variable to point to the segment where the imported model will be included. If the second model is being imported directly into the segment where the first model is found, then that segment key should be used.