# Exchange-Parasolid

Designed to facilitate CAD conversion and geometric modeling in a single interface, the Exchange-Parasolid Sprocket is useful for developers who wish to load files using HOOPS Exchange and then interact with the geometry in the scene using the Parasolid modeling kernel.

The Exchange-Parasolid Sprocket is available on Windows, Linux, and MacOS platforms, and you must have a license for both Exchange and Parasolid in order to use the sprocket.

HOOPS Visualize Desktop, Exchange, and Parasolid interact with each other in the following manner:

1. HOOPS Visualize Desktop displays the model on screen, builds a Component structure, provides the user access to Parasolid and Exchange data, and keeps the Parasolid data, Exchange data, Component structure, and visual rendition synchronized.
2. Exchange loads and translates the model and manages Views, PMI, and the assembly structure.
3. Parasolid tessellates the model and performs modeling operations.

Throughout this guide, we sometimes refer to “PRC” – this refers to the intermediate CAD data format used by Exchange during file conversion. In HOOPS Visualize Desktop, PRC also forms the basis of the Component structure used in the Exchange-Parasolid Sprocket. For more information on PRC, see the HOOPS Exchange documentation or the PRC specification itself.

## Prerequisites

To satisfy the prerequisites, users should complete the prerequisite steps for both the Exchange and Parasolid sprockets.

## File Loading and Translation

When using the Exchange-Parasolid Sprocket, the contents of each Exchange Representation Item is translated to Parasolid. The translation process takes place automatically as part of the import and is configurable by the user. Since the geometry being inserted into the scene will be tessellated by Parasolid, Parasolid tessellation settings also need to be provided.

Since HOOPS Visualize Desktop’s Parasolid integration supports convergent modeling, even files without B-rep can be loaded and translated to Parasolid, if the format is supported by Exchange.

This is a basic example of loading and translating a file with default options:

        // load and translate a file using the default options
        try {
            ExchangeParasolid::ImportNotifier notifier = HPS::ExchangeParasolid::File::Import(
                filename, // the file to import
                Exchange::ImportOptionsKit::GetDefault().SetHiddenObjects(false), // options for reading the file with Exchange
                Exchange::TranslationOptionsKit::GetDefault(), // options for translating the file to Parasolid
                Parasolid::FacetTessellationKit::GetDefault(), // options for Parasolid facet tessellation
                Parasolid::LineTessellationKit::GetDefault()); // options for Parasolid line tessellation
            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
        }

    // load and translate a file using the default options
    try
    {
        ExchangeParasolid.ImportNotifier notifier = HPS.ExchangeParasolid.File.Import(
            filename,                                       //the file to import
            Exchange.ImportOptionsKit.GetDefault().SetHiddenObjects(false),       //options for reading the file with Exchange
            Exchange.TranslationOptionsKit.GetDefault(),  //options for translating the file to Parasolid
            Parasolid.FacetTessellationKit.GetDefault(),  //options for Parasolid facet tessellation
            Parasolid.LineTessellationKit.GetDefault());  //options for Parasolid line tessellation
        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 (HPS.IOException)
    {
        // handle error
    }

The usual caveats described in the other Sprocket guides still apply here:

- The user can call `HPS::IONotifier::Wait()` on the notifier to wait until after the import has completed. The import happens on a separate thread, and it is not guaranteed to be completed until after `HPS::IONotifier::Wait()` returns or until the `HPS::IONotifier::Status()` call returns something other than `HPS::Event::Status::InProgress`.
- The import can be canceled by calling `HPS::IONotifier::Cancel()` on the notifier.
- The import status can be queried while the import is in progress.

### Options for File Loading

The Options for file loading section in the Exchange Sprocket Programming Guide also applies to the Exchange-Parasolid Sprocket. As the sample above shows there are several options kit which need to be passed to the `HPS::ExchangeParasolid::File::Import()` function call:

1. An `HPS::Exchange::ImportOptionsKit`. This kit determines how the file is read by Exchange. The options specified in the Exchange Sprocket sections still apply (for example, calling `HPS::Exchange::ImportOptionsKit::SetPMI()` on this kit will result in PMIs not being imported).
2. An `HPS::Exchange::TranslationOptionsKit`. This kit determines how the file is translated into Parasolid, and optionally performs some functions such as sewing and healing. It’s worth noting that one of the options allows users to use multiple processes to translate the data, speeding up the process. See the function `HPS::Exchange::TranslationOptionsKit::SetMultiProcessCount` for more information.
3. A `HPS::Parasolid::FacetTessellationKit` and a `HPS::Parasolid::LineTessellationKit`. These kits determine how Parasolid will tessellate facets and lines respectively. The description of these kits is the same as the one found in the Import options section of the Parasolid programming guide. It is worth noting that if you don’t need to fine tune all the tessellation options, you can just call the `HPS::Parasolid::FacetTessellationKit::SetTessellationLevel()` function from the `HPS::Parasolid::FacetTessellationKit`, and use the default `HPS::Parasolid::LineTessellationKit`. This way the two are guaranteed to be compatible. (When these kits are not compatible, it can result in lines that do not exactly lie on faces or lines that are tessellated more coarsely than the faces they belong to.)

## Importing Multiple Models

Multiple models can be imported into the same scene using the same functionality described in Importing multiple models in the Exchange Programming Guide.

Note: When importing multiple models, the Parasolid entities from all models in the same scene are placed in the same Parasolid partition.

### Component Structure of Exchange-Parasolid Files

- The top of the component structure for Exchange-Parasolid files will be an `HPS::Exchange::CADModel`.
- From there the component structure will continue as expected for an Exchange file: the CADModel will contain Product Occurrence components, which will in turn contain either other Product Occurrence components or a Part Definition component. Part Definition components will contain Representation Item components.
- When importing a model through Exchange-Parasolid, components under the Representation Item level will all be Parasolid components. For example, Representation Item components will contain Parasolid Topological Body components.
- The component structure will still contain Views and Filters if the original CAD file contained them.

### Sample Component Structure of an Exchange-Parasolid File

The following graphic demonstrates a hypothetical Component structure for an Exchange-Parasolid file:

A sample of a Component structure in a hypothetical CAD model imported into the Exchange-Parasolid Sprocket.

The most important aspect of this structure is that the Exchange representation item will always contain a single Parasolid topological body (and not a Parasolid assembly or other data). In the next section, we’ll look at a sample that shows how to use the function `HPS::Component::GetSubcomponents()` to retrieve these elements.

## Accessing Parasolid and Exchange Data

By using the the HOOPS Visualize Desktop Component structure, it’s possible to gain access to Exchange or Parasolid entities directly. You can then operate on these entities using the native Exchange and Parasolid APIs.

The first step in this process is to find the component we are interested in. Each component can query its parent and children component through its `HPS::Component::GetOwners()` and `HPS::Component::GetSubcomponents()` functions. It can also query all its descendants of a particular type by calling `HPS::Component::GetAllSubcomponents()`.

The type of a component can be found using the `HPS::Component::GetComponentType()` function, and the result can be checked against the `HPS::Component::ComponentType` enumeration. The `HPS::Component` class also contains the `HPS::Component::HasComponentType()` function, which allows us to check against a bitmask. For example, the function can be used to determine whether a component is an Exchange or a Parasolid component, or whether the component represents any of the topological items supported by Exchange or Parasolid.

Once an interesting component has been located, we can access its underlying data. For Exchange components, we create an `HPS::Exchange::Component` from the Component object, and call `HPS::Exchange::Component::GetExchangeEntity()`. For Parasolid components, we create an `HPS::Parasolid::Component` from the component object, and call `HPS::Parasolid::Component::GetParasolidEntity()`.

        // Query the CADModel for its children components
        ComponentArray subcomponents = cad_model.GetSubcomponents();
        if (!subcomponents.empty()) {
            // Check that the component type of the first child component is Exchange Product Occurrence
            Component::ComponentType component_type = subcomponents[0].GetComponentType();
            if (component_type == Component::ComponentType::ExchangeProductOccurrence) {
                // Since we know this component is an Exchange component, we can cast it to
                // an Exchange::Component object and access its data
                Exchange::Component exchange_component(subcomponents[0]);
                A3DEntity* entity = exchange_component.GetExchangeEntity();

                // We can now use the Exchange native API to interact with it
                A3DAsmProductOccurrenceData pocc_data;
                A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, pocc_data);
                A3DAsmProductOccurrenceGet(entity, &pocc_data);
            }
        }

        // Now let's look for all subcomponents of the CADModel which are Parasolid Topological Faces
        subcomponents = cad_model.GetAllSubcomponents(Component::ComponentType::ParasolidTopoFace);
        if (!subcomponents.empty()) {
            // Check that the component type of the first Parasolid Topo Face we found is indeed
            // a Parasolid component by checking its type mask
            if (subcomponents[0].HasComponentType(Component::ComponentType::ParasolidComponentMask)) {
                // Since we know this component is a Parasolid component, we can cast it to
                // a Parasolid::Component object and access its data

                Parasolid::Component parasolid_component(subcomponents[0]);
                PK_ENTITY_t entity = parasolid_component.GetParasolidEntity();

                // We can now use the Parasolid native API to interact with it
                PK_SURF_t surface;
                PK_FACE_ask_surf(entity, &surface);
            }
        }

        // Query the CADModel for its children components
        HPS.Component[] subcomponents = cad_model.GetSubcomponents();
        if (!subcomponents.empty())
        {
            // Check that the component type of the first child component is Exchange Product Occurrence
            Component.ComponentType component_type = subcomponents[0].GetComponentType();
            if (component_type == Component.ComponentType.ExchangeProductOccurrence)
            {
                // Since we know this component is an Exchange component, we can set it to
                // an Exchange.Component object and access its data
                Exchange.Component exchange_component = new Exchange.Component(subcomponents[0]);
                IntPtr entity = exchange_component.GetExchangeEntity();

                // We can now use the Exchange native API to interact with it
                HPS.Component pocc_data = cad_model.GetSubcomponents()[0];
            }
        }

        // Now let's look for all subcomponents of the CADModel which are Parasolid Topological Faces
        subcomponents = cad_model.GetAllSubcomponents(Component.ComponentType.ParasolidTopoFace);
        if (!subcomponents.empty())
        {
            // Check that the component type of the first Parasolid Topo Face we found is indeed
            // a Parasolid component by checking its type mask
            if (subcomponents[0].HasComponentType(Component.ComponentType.ParasolidComponentMask))
            {
                // Since we know this component is a Parasolid component, we can cast it to
                // a Parasolid.Component object and access its data

                Parasolid.Component parasolid_component = new Parasolid.Component(subcomponents[0]);
                PK.ENTITY_t entity = parasolid_component.GetParasolidEntity();

                // We can now use the Parasolid native API to interact with it
                PK.SURF_t surface;
                PK.FACE.ask_surf(entity, &surface);
            }
        }

## Updating the Model after Parasolid Modeling Operations

After a modelling operation takes place in Parasolid, HOOPS Visualize Desktop will have to be notified about which Parasolid bodies have changed, so that they can be retessellated and have their component structure updated to reflect their new status.

This can be done through the `HPS::ExchangeParasolid::File::Tessellate()` function or the `HPS::Parasolid::Component::Tessellate()` function.

Note: Currently the `HPS::Parasolid::Component::Tessellate()` and `HPS::ExchangeParasolid::File::Tessellate()` functions update HOOPS Visualize Desktop to reflect the changes made in Parasolid. This function does NOT however update the PRC representation of the model held by Exchange. Therefore, after calling either of these Tessellate functions, in order to export an accurate representation of the model, use the Parasolid API (e.g., `PK_PART_transmit`) and reload the model into HOOPS Visualize Desktop from disk.

The following code sample demonstrates how to add a fillet to an edge of a model and retessellate it using the native Parasolid API:

        // Load a model, fillet an edge using parasolid, and retessellate it to see the result

        // Load a model and translate it to Parasolid
        Exchange::ImportOptionsKit exchange_import = Exchange::ImportOptionsKit::GetDefault();
        exchange_import.SetBRepMode(Exchange::BRepMode::BRepAndTessellation);
        Exchange::TranslationOptionsKit translation_options = Exchange::TranslationOptionsKit::GetDefault();
        ExchangeParasolid::ImportNotifier notifier = ExchangeParasolid::File::Import(filename,
                                                                                     exchange_import,
                                                                                     translation_options,
                                                                                     Parasolid::FacetTessellationKit::GetDefault(),
                                                                                     Parasolid::LineTessellationKit::GetDefault());

        notifier.Wait();
        if (notifier.Status() != IOResult::Success) {
            // handle error
        }

        View view = notifier.GetCADModel().ActivateDefaultCapture();
        myCanvas.AttachViewAsLayout(view);

        // find the edges, and pick the first one
        CADModel cad_model = notifier.GetCADModel();
        ComponentArray edge_components = cad_model.GetAllSubcomponents(Component::ComponentType::ParasolidTopoEdge);
        if (edge_components.empty()) {
            // handle the error
        }

        Parasolid::Component para_edge_component(edge_components[0]);
        PK_EDGE_t edge_entity = para_edge_component.GetParasolidEntity();

        // find the body which owns this edge, by traversing the parents of the edge, until when we find a body
        HPS::Component owner = para_edge_component.GetOwners()[0];
        while (owner.GetComponentType() != HPS::Component::ComponentType::ParasolidTopoBody)
            owner = owner.GetOwners()[0];

        // set the blends on the edge
        int number_of_edges = 0;
        PK_EDGE_t* edges = nullptr;
        PK_EDGE_set_blend_constant_o_t blend_options;
        PK_EDGE_set_blend_constant_o_m(blend_options);
        PK_ERROR_t error = PK_EDGE_set_blend_constant(1, &edge_entity, 0.005, &blend_options, &number_of_edges, &edges);

        if (error != PK_ERROR_no_errors) {
            // handle the error
        }

        // commit the blends on the body which owns this edge
        int number_of_blend_faces = 0;
        PK_FACE_t* created_blend_faces = nullptr;
        PK_FACE_array_t* underlying_topology = nullptr;
        int* replaced_topologies = nullptr;
        PK_blend_fault_t status = PK_blend_fault_no_fault_c;
        PK_EDGE_t faulty_edge = PK_ENTITY_null;
        PK_ENTITY_t topology_associated_with_fault = PK_ENTITY_null;
        PK_BODY_fix_blends_o_t fix_blend_options;
        PK_BODY_fix_blends_o_m(fix_blend_options);
        HPS::Parasolid::Component para_component(owner);
        error = PK_BODY_fix_blends(para_component.GetParasolidEntity(),
                                   &fix_blend_options,
                                   &number_of_blend_faces,
                                   &created_blend_faces,
                                   &underlying_topology,
                                   &replaced_topologies,
                                   &status,
                                   &faulty_edge,
                                   &topology_associated_with_fault);

        if (error != PK_ERROR_no_errors || (status != PK_blend_fault_no_fault_c && status != PK_blend_fault_repaired_c)) {
            // handle the error
        }

        // retessellate the body to update the component structure and see the result on screen
        HPS::Parasolid::FacetTessellationKit facet_tessellation = HPS::Parasolid::FacetTessellationKit::GetDefault();
        para_component.Tessellate(facet_tessellation, HPS::Parasolid::LineTessellationKit::GetDefault());
        myCanvas.Update();

        //Load a model, fillet an edge using parasolid, and retessellate it to see the result

        //Load a model and translate it to Parasolid
        Exchange.ImportOptionsKit exchange_import = Exchange.ImportOptionsKit.GetDefault();
        exchange_import.SetBRepMode(Exchange.BRepMode.BRepAndTessellation);
        Exchange.TranslationOptionsKit translation_options = Exchange.TranslationOptionsKit.GetDefault();
        ExchangeParasolid.ImportNotifier notifier = ExchangeParasolid.File.Import(
            filename, exchange_import, translation_options,
            Parasolid.FacetTessellationKit.GetDefault(), Parasolid.LineTessellationKit.GetDefault());

        notifier.Wait();
        if (notifier.Status() != IOResult.Success)
        {
            //handle error
        }

        View myView = notifier.GetCADModel().ActivateDefaultCapture();
        myCanvas.AttachViewAsLayout(myView);

        HPS.CADModel modelFile = notifier.GetCADModel();
        HPS.Model model = modelFile.GetModel();
        myView.AttachModel(model);

        //find the edges, and pick the first one
        CADModel cad_model = notifier.GetCADModel();
        HPS.Component[] edge_components = cad_model.GetAllSubcomponents(Component.ComponentType.ParasolidTopoEdge);
        if (edge_components.empty())
        {
            //handle the error
        }

        Parasolid.Component para_edge_component = new Parasolid.Component(edge_components[0]);
        PK.EDGE_t[] edge_entity = new PK.EDGE_t[1];
        edge_entity[0] = para_edge_component.GetParasolidEntity();

        //find the body which owns this edge, by traversing the parents of the edge, until when we find a body
        HPS.Component owner = para_edge_component.GetOwners()[0];
        while (owner.GetComponentType() != HPS.Component.ComponentType.ParasolidTopoBody)
            owner = owner.GetOwners()[0];

        //set the blends on the edge
        int number_of_edges = 0;
        PK.EDGE_t* edges = null;
        PK.EDGE.set_blend_constant_o_t blend_options = new PK.EDGE.set_blend_constant_o_t(true);

        PK.ERROR.code_t error = PK.EDGE.set_blend_constant(1, edge_entity, 0.005, &blend_options, &number_of_edges, &edges);

        if (error != PK.ERROR.code_t.no_errors)
        {
            //handle the error
        }

        //commit the blends on the body which owns this edge
        int number_of_blend_faces = 0;
        PK.FACE_t* created_blend_faces = null;
        PK.FACE.array_t* underlying_topology = null;
        int* replaced_topologies = null;
        PK.blend_fault_t status = PK.blend_fault_t.no_fault_c;
        PK.EDGE_t faulty_edge = PK.EDGE_t.@null;
        PK.ENTITY_t topology_associated_with_fault = PK.EDGE_t.@null;
        PK.BODY.fix_blends_o_v fix_blend_options = new PK.BODY.fix_blends_o_v(true);
        HPS.Parasolid.Component para_component = new HPS.Parasolid.Component(owner);
        error = PK.BODY.fix_blends(para_component.GetParasolidEntity(), &fix_blend_options, &number_of_blend_faces, &created_blend_faces, &underlying_topology, &replaced_topologies, &status, &faulty_edge, &topology_associated_with_fault);

        if (error != PK.ERROR.code_t.no_errors || (status != PK.blend_fault_t.no_fault_c && status != PK.blend_fault_t.repaired_c))
        {
            //handle the error
        }

        //retessellate the body to update the component structure and see the result on screen
        HPS.Parasolid.FacetTessellationKit facet_tessellation = HPS.Parasolid.FacetTessellationKit.GetDefault();
        para_component.Tessellate(facet_tessellation, HPS.Parasolid.LineTessellationKit.GetDefault());
        myCanvas.Update();

Here is the output when applying this code to a simple cube; the edge on the left is where the fillet has been applied:

A sample of a Component structure in a hypothetical CAD model imported into the Exchange-Parasolid Sprocket.

## Creating Geometry in Memory Using Parasolid and Adding It to the Scene

Note: This is the Exchange-Parasolid equivalent of the Entity Creation section of the Parasolid Programming Guide.

Sometimes you may want to create a model from scratch instead of loading one, or other times you may want to create some geometry from scratch and then add it to an existing model you’ve already loaded. In either case, you’ll need a way to take what you’ve created in memory using Parasolid and add it to the scene.

The `HPS::ExchangeParasolid::File::AddEntity()` function allows you to not only update the HOOPS Visualize Desktop component structure but also to create new PRC data to update the Exchange structure. (Please see the `HPS::ExchangeParasolid::File::AddEntity()` for details about the limitations of the `HPS::ExchangeParasolid::File::AddEntity()` function.)

Below is a sample of how to add a new Parasolid body to the model PartWithPMI.CATPart, located in the samples/data directory. Here’s the model before adding the new body:

Model before addition of new Parasolid body.

This code snippet demonstrates loading a model via Exchange, creating a Parasolid body in memory (a red cube), and using the `HPS::ExchangeParasolid::File::AddEntity()` function to add the Parasolid body to the Exchange CAD model:

        /*** create a parasolid body and add it at the top level of an existing Exchange model ***/

        Exchange::ImportOptionsKit import_kit = Exchange::ImportOptionsKit::GetDefault();
        import_kit.SetPMI(false); // omitting PMI for better visibility of new body

        Exchange::ImportNotifier notifier = Exchange::File::Import(my_cad_file, import_kit);
        notifier.Wait();

        HPS::CADModel cad_model = notifier.GetCADModel();
        Exchange::CADModel exchange_cad_model(cad_model);
        View view1 = cad_model.ActivateDefaultCapture();

        // create a Parasolid body (a cube)
        PK_BODY_t solid_body = PK_ENTITY_null;
        PK_BODY_create_solid_block(0.05f, 0.05f, 0.05f, nullptr, &solid_body); // parasolid dimensions in meters

        // make the cube red
        PK_ATTDEF_t system_color;
        PK_ATTDEF_find("SDL/TYSA_COLOUR_2", &system_color);

        PK_ATTRIB_t color_attribute;
        PK_ATTRIB_create_empty(solid_body, system_color, &color_attribute);

        double color[] = {1, 0, 0};
        PK_ATTRIB_set_doubles(color_attribute, 0, 3, color);

        // apply a translation matrix to the cube we're adding
        MatrixKit translation;
        translation.Translate(100.0f, 0.0f, 50.0f);

        // add cube to existing CAD Model
        HPS::Parasolid::Component added_component = ExchangeParasolid::File::AddEntity(exchange_cad_model, solid_body, translation);

        // retrieve the Parasolid entity for inspection
        PK_ENTITY_t entity = added_component.GetParasolidEntity();
        PK_CLASS_t entity_type;
        PK_ENTITY_ask_class(entity, &entity_type);

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_add_body.cs
       :start-after: //! [parasolid_add_body]
       :end-before: //! [parasolid_add_body]

And here’s the final output, with the red cube inserted into the CAD model:

Model after addition of new Parasolid body (in red).

## Limitations

- When importing files that do not contain B-rep information, colors will not be imported.
- Currently the `HPS::Parasolid::Component::Tessellate()` and `HPS::ExchangeParasolid::File::Tessellate()` functions update HOOPS Visualize Desktop to reflect the changes made in Parasolid. This function does NOT however update the PRC representation of the model held by Exchange. Therefore, after calling either of these Tessellate functions, in order to export an accurate representation of the model, use the Parasolid API (e.g., `PK_PART_transmit11`) and reload the model into HOOPS Visualize Desktop from disk.

## Miscellaneous

- The HOOPS Parasolid Demo Viewer uses Exchange-Parasolid and can be used to sample basic modeling operations.

## Synchronizing HOOPS Visualize Desktop and the Native Parasolid API

This chart shows which HOOPS Visualize Desktop functions must be called after a specific native Parasolid operation:

Native Parasolid operation | Related HOOPS Visualize Desktop call | Updates PRC?
--- | --- | ---
Adds new Body to the Parasolid partition | `HPS::ExchangeParasolid::File::AddEntity()` | YES
Modifies existing body | `HPS::ExchangeParasolid::File::Tessellate()` | NO
Deletes Body | `HPS::CADModel::Delete()` | YES

## Boolean Operations

HOOPS Visualize Desktop supports the subtraction boolean operation via the native Parasolid API. To perform a subtraction in a scene, we’ll load two CAD models into a scene, defining one of them as a target (the 3D model we’ll be subtracting from) and the other as a tool (the 3D shape that will define the bounds of the subtraction operation). In this example, we’ll load an engine model as our target and then load a cylinder to serve as the tool for the subtraction.

Please note, this is a very basic sample boolean subtraction operator, merely a starting point from which to build a more robust boolean operator leveraging Parasolid. Specifically, this operator implementation does not address the case in which a boolean operation would result in multiple bodies (although the comments in the operator code explain how to address that eventuality).

CAD model of an engine that will be used as the target for a boolean operation

CAD model of a cylinder that will be used as the tool for a boolean operation

The full code for importing two CAD models into a scene is here, and, for the most part, we’re just using a standard approach for importing files in HOOPS Visualize Desktop. However, please note that we’re using a `HPS::Exchange::ImportOptionsKit::SetLocation()` to import our second model (the tool) into the same Component tree as the first file.

In addition, please note that in terms of positioning, our models have been designed to overlap each other in World Space to facilitate the subtraction operation. If you want to use two models that weren’t necessarily designed for this purpose, there are a variety of approaches for positioning them on top of each other to perform the boolean operation. For example, using the Handles Operator is an easy way to manually super-impose one model on top of another.

Once loaded, our two CAD models will look like this, with the cylinder piercing the engine model on the left:

Two intersecting CAD models loaded via the Exchange sprocket

### Defining a Custom Operator

Perhaps the simplest method for implementing a boolean operation in HOOPS Visualize Desktop is to define a custom operator that handles a series of mouse clicks for defining the target and tool. The full source code for the class is available at the bottom of the page, but for now we’ll just focus on the `OnMouseUp` method, since that’s where the bulk of the logic is contained. (More info about custom operators is available in this section.)

We’ll call our custom operator class `TestBooleanOperator`, and like all custom operators, it inherits the `HPS::Operator` class. In this example, we’ll overload the `HPS::Operator::OnMouseUp` method, and in the overloaded method we’ll place the logic for selecting the target and tool for the boolean subtraction operation:

            bool OnMouseUp(HPS::MouseState const& in_state) override
            {
                if (!IsMouseTriggered(in_state) && in_state.GetLocation() == click_location) {
                    HPS::SelectionResults results;
                    size_t count =
                        canvas.GetWindowKey().GetSelectionControl().SelectByPoint(click_location, selection_options, results);
                    if (count == 0)
                        return false;

                    HPS::SelectionResultsIterator s_it = results.GetIterator();
                    while (s_it.IsValid()) {
                        HPS::KeyPath selected_path;
                        s_it.GetItem().ShowPath(selected_path);
                        HPS::ComponentPath component_path = cad_model.GetComponentPath(selected_path);

                        HPS::ComponentArray components = component_path.GetComponents();
                        for (auto it = components.rbegin(), e = components.rend(); it != e; ++it) {
                            if (it->GetComponentType() == HPS::Component::ComponentType::ParasolidTopoBody) {
                                HPS::Parasolid::Component para_component(*it);
                                auto highlight_control = canvas.GetWindowKey().GetHighlightControl();

                                PK_ENTITY_t selected_parasolid_entity = para_component.GetParasolidEntity();

                                if (target == selected_parasolid_entity) {
                                    // deselect target
                                    highlight_control.Unhighlight(selected_path, target_highlight);
                                    canvas.Update();
                                    target = PK_ENTITY_null;
                                    target_component.Reset();
                                    target_path.Reset();
                                    return true;
                                }
                                else if (tool == selected_parasolid_entity) {
                                    // deselect tool
                                    highlight_control.Unhighlight(selected_path, tool_highlight);
                                    canvas.Update();
                                    tool = PK_ENTITY_null;
                                    tool_component.Reset();
                                    tool_path.Reset();
                                    return true;
                                }
                                else if (target == PK_ENTITY_null) {
                                    target = selected_parasolid_entity;
                                    target_component = *it;
                                    target_path = selected_path;
                                    highlight_control.Highlight(selected_path, target_highlight);
                                    canvas.Update();
                                    return true;
                                }
                                else {
                                    tool = selected_parasolid_entity;
                                    tool_component = *it;
                                    tool_path = selected_path;
                                    highlight_control.Highlight(selected_path, tool_highlight);
                                    canvas.Update();
                                    return true;
                                }
                            }
                        }
                        s_it.Next();
                    }
                    return true;
                }
                return false;
            }

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_selection_logic]
       :end-before: //! [00760_exchange_parasolid_boolean_selection_logic]

The Parasolid Body upon which the user clicks first will be selected as the target, and the second body selected by the user will be the tool for the boolean operation:

The target is selected and highlighted in green

The tool is selected and highlighted in orange

### Instantiating the Custom Operator

With our custom operator class defined, in our application code (provided in full below) we can now instantiate our custom operator and push it onto the operator stack in the `HPS::Operator::Priority::Default` priority queue:

        TestBooleanOperator* boolean_operator = new TestBooleanOperator(canvas, cadmodel);
        view.GetOperatorControl().Push(boolean_operator, HPS::Operator::Priority::Default);

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_instantiate]
       :end-before: //! [00760_exchange_parasolid_boolean_instantiate]

This instance of `TestBooleanOperator` is now available in the operator stack, and we’ll learn how to retrieve it in the next section. (For more details about the operator stack, please see this section.)

Once our `TestBooleanOperator` instance has been pushed to the operator stack, HOOPS Visualize Desktop takes ownership of its memory, so do not call `delete` on this instance, as it will be automatically freed by HOOPS Visualize Desktop.

### Retrieving the Operator From the Stack

Suppose that after we’ve pushed our original instance of `TestBooleanOperator` onto the operator stack, we want to access its member methods &ndash; in this case, specifically the `ApplyBoolean()` method.

In order to retrieve our custom operator from the `Default` priority operator stack, we can use the following approach:

        OperatorPtr myOpPointer;
        myCanvas.GetFrontView().GetOperatorControl().ShowTop(myOpPointer);
        if (myOpPointer->GetName() == "TestBooleanOperator") {
            auto op = std::static_pointer_cast<TestBooleanOperator>(myOpPointer);
            op->ApplyBoolean();
            myCanvas.UpdateWithNotifier().Wait();
        }

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_retrieve]
       :end-before: //! [00760_exchange_parasolid_boolean_retrieve]

We can retrieve a pointer to the operator by calling `HPS::OperatorControl::ShowTop`, passing a newly instantiated `HPS::OperatorPtr` as an out parameter to be populated by the function.

At this point, however, in order to gain access to the methods in our derived `TestBooleanOperator` class we’ll need to create a new pointer to our derived type and use a `static_pointer_cast` to transform our `HPS::OperatorPtr` to a shared pointer of `TestBooleanOperator` type.

Now that we have a `TestBooleanOperator` pointer, we can access its methods and call the `ApplyBoolean()` function, which will call Parasolid’s boolean subtraction function, removing from the target any overlapping volume, as seen in the image below:

Final result of our boolean subtraction operation

After the data has been changed in Parasolid, HOOPS Visualize Desktop needs to be updated. This update can be accomplished by using the three functions: `HPS::Parasolid::Component::Tessellate` (for geometry that was modified, like the target), `HPS::Component::Delete` (for geometry that was deleted, like the tool), and `ExchangeParasolid::File::AddEntity` (for geometry that was created, which would happen in the case where a boolean operation splits an entity in two, for example).

The source code :ref`below <boolean_full_source` demonstrates `HPS::Parasolid::Component::Tessellate` and `HPS::Component::Delete`, and this section demonstrates how to use the `HPS::ExchangeParasolid::File::AddEntity` function.

## Full Source for Custom Boolean Operator

### TestBooleanOperator derived class

        class TestBooleanOperator: public HPS::Operator {
          public:
            TestBooleanOperator(Canvas const& in_canvas, CADModel const& in_cad_model):
                canvas(in_canvas), cad_model(in_cad_model), target(PK_ENTITY_null), tool(PK_ENTITY_null)
            {
            }

            virtual ~TestBooleanOperator() {}

            HPS::UTF8 GetName() const override { return "TestBooleanOperator"; }

            bool OnMouseDown(HPS::MouseState const& in_state) override
            {
                if (IsMouseTriggered(in_state)) {
                    click_location = in_state.GetLocation();
                }
                return false;
            }

            //! [00760_exchange_parasolid_boolean_selection_logic]
            bool OnMouseUp(HPS::MouseState const& in_state) override
            {
                if (!IsMouseTriggered(in_state) && in_state.GetLocation() == click_location) {
                    HPS::SelectionResults results;
                    size_t count =
                        canvas.GetWindowKey().GetSelectionControl().SelectByPoint(click_location, selection_options, results);
                    if (count == 0)
                        return false;

                    HPS::SelectionResultsIterator s_it = results.GetIterator();
                    while (s_it.IsValid()) {
                        HPS::KeyPath selected_path;
                        s_it.GetItem().ShowPath(selected_path);
                        HPS::ComponentPath component_path = cad_model.GetComponentPath(selected_path);

                        HPS::ComponentArray components = component_path.GetComponents();
                        for (auto it = components.rbegin(), e = components.rend(); it != e; ++it) {
                            if (it->GetComponentType() == HPS::Component::ComponentType::ParasolidTopoBody) {
                                HPS::Parasolid::Component para_component(*it);
                                auto highlight_control = canvas.GetWindowKey().GetHighlightControl();

                                PK_ENTITY_t selected_parasolid_entity = para_component.GetParasolidEntity();

                                if (target == selected_parasolid_entity) {
                                    // deselect target
                                    highlight_control.Unhighlight(selected_path, target_highlight);
                                    canvas.Update();
                                    target = PK_ENTITY_null;
                                    target_component.Reset();
                                    target_path.Reset();
                                    return true;
                                }
                                else if (tool == selected_parasolid_entity) {
                                    // deselect tool
                                    highlight_control.Unhighlight(selected_path, tool_highlight);
                                    canvas.Update();
                                    tool = PK_ENTITY_null;
                                    tool_component.Reset();
                                    tool_path.Reset();
                                    return true;
                                }
                                else if (target == PK_ENTITY_null) {
                                    target = selected_parasolid_entity;
                                    target_component = *it;
                                    target_path = selected_path;
                                    highlight_control.Highlight(selected_path, target_highlight);
                                    canvas.Update();
                                    return true;
                                }
                                else {
                                    tool = selected_parasolid_entity;
                                    tool_component = *it;
                                    tool_path = selected_path;
                                    highlight_control.Highlight(selected_path, tool_highlight);
                                    canvas.Update();
                                    return true;
                                }
                            }
                        }
                        s_it.Next();
                    }
                    return true;
                }
                return false;
            }

            //! [00760_exchange_parasolid_boolean_selection_logic]
            void OnViewAttached(HPS::View const& in_attached_view) override
            {
                attached_view = GetAttachedView();

                HPS::SprocketPath sprocket_path(canvas, canvas.GetAttachedLayout(), attached_view, attached_view.GetAttachedModel());

                selection_options.SetAlgorithm(HPS::Selection::Algorithm::Visual)
                    .SetLevel(HPS::Selection::Level::Entity)
                    .SetScope(sprocket_path.GetKeyPath());

                // Create target style
                target_style_source = HPS::Database::CreateRootSegment();
                HPS::MaterialKit highlight_material;
                highlight_material.SetDiffuse(HPS::RGBAColor(0, 0.8f, 0.0f, 0.3f));
                highlight_material.SetSpecular(HPS::RGBColor(1.0f, 0.847f, 0.0f));
                target_style_source.GetMaterialMappingControl()
                    .SetFaceMaterial(highlight_material)
                    .SetEdgeColor(HPS::RGBColor(0, 0.8f, 0))
                    .SetTextColor(HPS::RGBColor(0, 0.8f, 0))
                    .SetLineColor(HPS::RGBColor(0, 0.8f, 0));

                HPS::PortfolioKey portfolio = attached_view.GetPortfolioKey();
                portfolio.DefineNamedStyle("BooleanOperator_TargetStyle", target_style_source);
                target_highlight.SetOverlay(HPS::Drawing::Overlay::None).SetStyleName("BooleanOperator_TargetStyle");

                // Create tool style
                tool_style_source = HPS::Database::CreateRootSegment();
                highlight_material.SetDiffuse(HPS::RGBAColor(1.0f, 0.647f, 0.0f, 1.0f));
                tool_style_source.GetMaterialMappingControl()
                    .SetFaceMaterial(highlight_material)
                    .SetEdgeColor(HPS::RGBColor(1.0f, 0.510f, 0.3f))
                    .SetTextColor(HPS::RGBColor(1.0f, 0.510f, 0.3f))
                    .SetLineColor(HPS::RGBColor(1.0f, 0.510f, 0.3f));

                portfolio.DefineNamedStyle("BooleanOperator_ToolStyle", tool_style_source);
                tool_highlight.SetOverlay(HPS::Drawing::Overlay::None).SetStyleName("BooleanOperator_ToolStyle");
            }

            void OnViewDetached(HPS::View const& in_detached_view) override
            {
                // check to make sure the canvas still exists
                if (canvas.Type() != HPS::Type::None) {
                    // cleanup

                    auto highlight_control = canvas.GetWindowKey().GetHighlightControl();
                    highlight_control.Unhighlight(target_highlight);
                    highlight_control.Unhighlight(tool_highlight);

                    auto portfolio_key = attached_view.GetPortfolioKey();
                    portfolio_key.UndefineNamedStyle("BooleanOperator_TargetStyle");
                    portfolio_key.UndefineNamedStyle("BooleanOperator_ToolStyle");

                    target_style_source.Delete();
                    tool_style_source.Delete();

                    canvas.Update();
                }
            }

            bool ApplyBoolean()
            {
                canvas.GetWindowKey().GetHighlightControl().UnhighlightEverything();

                // Now that we have all the data we need for the boolean target and tool, we move on to performing
                // the boolean operation itself
                // Step 1: Transform the tool, so that it is in the same frame of reference as the target
                MatrixKit target_matrix;
                target_path.ShowNetModellingMatrix(target_matrix);
                MatrixKit tool_matrix;
                tool_path.ShowNetModellingMatrix(tool_matrix);
                MatrixKit transform_matrix = target_matrix.Invert() * tool_matrix;
                if (transform_matrix != MatrixKit()) {
                    // The matrix has to be specified in meters.
                    // The imported model uses millimeters as its units, so we will need to scale the matrix accordingly
                    double scale = 0.001;

                    // Recreate the matrix into Parasolid
                    float* elements = transform_matrix.data;
                    PK_TRANSF_sf_t transform_data;
                    transform_data.matrix[0][0] = elements[0];
                    transform_data.matrix[1][0] = elements[1];
                    transform_data.matrix[2][0] = elements[2];
                    transform_data.matrix[3][0] = elements[3];
                    transform_data.matrix[0][1] = elements[4];
                    transform_data.matrix[1][1] = elements[5];
                    transform_data.matrix[2][1] = elements[6];
                    transform_data.matrix[3][1] = elements[7];
                    transform_data.matrix[0][2] = elements[8];
                    transform_data.matrix[1][2] = elements[9];
                    transform_data.matrix[2][2] = elements[10];
                    transform_data.matrix[3][2] = elements[11];
                    transform_data.matrix[0][3] = elements[12] * scale;
                    transform_data.matrix[1][3] = elements[13] * scale;
                    transform_data.matrix[2][3] = elements[14] * scale;
                    transform_data.matrix[3][3] = elements[15];
                    PK_TRANSF_t transform;
                    PK_TRANSF_create(&transform_data, &transform);

                    // Transform the tool using it
                    PK_BODY_transform_o_t transform_options;
                    PK_BODY_transform_o_m(transform_options);
                    PK_TOPOL_track_r_t tracking;
                    PK_TOPOL_local_r_t results;
                    PK_ERROR_t error = PK_BODY_transform_2(tool, transform, 0.001, &transform_options, &tracking, &results);

                    // Check that the transformation completed successfully
                    if (error != PK_ERROR_none)
                        return false; // something went wrong
                    else if (results.status != PK_local_status_ok_c && results.status != PK_local_status_nocheck_c)
                        return false; // something went wrong
                }

                // Step 2: Determine whether both entities are faceted, both have b-rep data available, or if they are mixed.
                // Both entities need to be either faceted or both need to have b-rep data available.
                // In the case where they are not the same, we convert the entity with b-rep to a faceted entity
                bool target_faceted = IsEntityFaceted(target);
                bool tool_faceted = IsEntityFaceted(tool);

                // Step 3: If one of the bodies is faceted and the other isn't we need to convert the non-faceted one to
                // a faceted body as well.
                PK_BODY_t facet_body = PK_ENTITY_null;
                PK_TOPOL_track_r_t tracking;
                PK_ERROR_t error = PK_ERROR_none;
                if (tool_faceted != target_faceted) {
                    PK_BODY_t classical_body = PK_ENTITY_null;
                    if (!tool_faceted)
                        classical_body = tool;
                    else if (!target_faceted)
                        classical_body = target;

                    PK_BODY_make_facet_body_o_t make_facet_options;
                    PK_BODY_make_facet_body_o_m(make_facet_options);
                    PK_TOPOL_track_r_t redundant_topol;
                    error = PK_BODY_make_facet_body(
                        classical_body, PK_ENTITY_null, &make_facet_options, &facet_body, &tracking, &redundant_topol);

                    if (error != PK_ERROR_none)
                        return false; // something went wrong
                }

                // Step 4. Perform the boolean operation
                PK_BODY_boolean_o_t boolean_options;
                PK_BODY_boolean_o_m(boolean_options);
                boolean_options.function = PK_boolean_subtract_c;
                PK_boolean_r_t boolean_results;
                PK_BODY_t tool_to_use = tool;
                PK_BODY_t target_to_use = target;
                if (facet_body != PK_ENTITY_null && !tool_faceted)
                    tool_to_use = facet_body;
                else if (facet_body != PK_ENTITY_null && !target_faceted)
                    target_to_use = facet_body;
                error = PK_BODY_boolean_2(target_to_use, 1, &tool_to_use, &boolean_options, &tracking, &boolean_results);
                if (error == PK_ERROR_none && boolean_results.result == PK_boolean_result_success_c && boolean_results.n_bodies > 0) {
                    // Step 5. Show the result:
                    for (int i = 0; i < boolean_results.n_bodies; ++i) {
                        // In this case there will only be a single resulting body from the boolean,
                        // the modified target entity.
                        // We re-tessellate it to sync the Parasolid changes with the Visualize
                        // representation of the part
                        if (boolean_results.bodies[i] != target)
                            return false; // something went wrong

                        HPS::Parasolid::Component parasolid_target_component(target_component);
                        parasolid_target_component.Tessellate(HPS::Parasolid::FacetTessellationKit::GetDefault(),
                                                              HPS::Parasolid::LineTessellationKit::GetDefault());

                        // In the case where we have multiple bodies resulting from a boolean operation
                        // (for example if a boolean results in the target body being cut in half), we
                        // will need to Add the bodies which were not previously in the scene to it, using
                        // the ExchangeParasolid::File::AddEntity function
                    }

                    // Delete the Visualize component associated with the tool, since the boolean
                    // operation deleted its associated Parasolid body
                    tool_component.Delete();
                    return true;
                }
                else
                    return false; // something went wrong
            }

          private:
            HPS::Canvas canvas;
            HPS::CADModel cad_model;
            HPS::View attached_view;

            PK_BODY_t target;
            HPS::Component target_component;
            HPS::KeyPath target_path;
            PK_BODY_t tool;
            HPS::Component tool_component;
            HPS::KeyPath tool_path;

            HPS::WindowPoint click_location;
            HPS::SelectionOptionsKit selection_options;
            HPS::SegmentKey target_style_source;
            HPS::HighlightOptionsKit target_highlight;
            HPS::SegmentKey tool_style_source;
            HPS::HighlightOptionsKit tool_highlight;

            bool IsEntityFaceted(PK_BODY_t body)
            {
                int number_of_faces = 0;
                PK_FACE_t* faces = nullptr;
                PK_ERROR_t error = PK_BODY_ask_faces(body, &number_of_faces, &faces);
                if (error != PK_ERROR_none || number_of_faces == 0) {
                    // something went wrong
                    return false;
                }

                PK_SURF_t surface;
                error = PK_FACE_ask_surf(faces[0], &surface);
                if (error != PK_ERROR_none) {
                    // something went wrong
                    return false;
                }

                PK_GEOM_ask_geom_category_o_t geom_options;
                PK_GEOM_ask_geom_category_o_m(geom_options);
                PK_GEOM_category_t category;
                error = PK_GEOM_ask_geom_category(surface, &geom_options, &category);
                if (error != PK_ERROR_none) {
                    // something went wrong
                    return false;
                }

                if (category == PK_GEOM_category_facet_c)
                    return true;
                else if (category == PK_GEOM_category_classic_c)
                    return false;

                return false;
            }
        };
    } // namespace

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_operator_class]
       :end-before: //! [00760_exchange_parasolid_boolean_operator_class]

## Scene Setup and Operator Instantiation

    bool setUpScene(Canvas& canvas, char const* importFileTarget, char const* importFileTool)
    {
        Exchange::ImportOptionsKit kit = Exchange::ImportOptionsKit::GetDefault();
        kit.SetBRepMode(Exchange::BRepMode::BRepAndTessellation);

        // load the first file
        ExchangeParasolid::ImportNotifier notifier = ExchangeParasolid::File::Import(importFileTarget,
                                                                                     kit,
                                                                                     Exchange::TranslationOptionsKit::GetDefault(),
                                                                                     Parasolid::FacetTessellationKit::GetDefault(),
                                                                                     Parasolid::LineTessellationKit::GetDefault());
        notifier.Wait();

        if (notifier.Status() != IOResult::Success)
            return false; // something went wrong

        CADModel cadmodel = notifier.GetCADModel();

        // load the second file by adding it to the scene
        ComponentArray location(1, cadmodel);
        kit.SetLocation(ComponentPath(location));
        notifier = ExchangeParasolid::File::Import(importFileTool,
                                                   kit,
                                                   Exchange::TranslationOptionsKit::GetDefault(),
                                                   Parasolid::FacetTessellationKit::GetDefault(),
                                                   Parasolid::LineTessellationKit::GetDefault());
        notifier.Wait();

        if (notifier.Status() != IOResult::Success)
            return false; // something went wrong

        // attach the view to the canvas, and setup a nice camera
        View view = cadmodel.ActivateDefaultCapture();
        canvas.AttachViewAsLayout(view);

        HPS::CameraKit camera_kit;
        camera_kit.SetPosition(HPS::Point(-116.158989f, -2172.083008f, 921.859985f))
            .SetTarget(HPS::Point(-0.000153f, -89.000000f, 117.000000f))
            .SetUpVector(HPS::Vector(-0.004887f, -0.360171f, -0.932874f))
            .SetField(894.474365f, 894.474365f)
            .SetNearLimit(0.000000f)
            .SetProjection(HPS::Camera::Projection::Orthographic);
        view.GetSegmentKey().SetCamera(camera_kit);
        view.GetSegmentKey().InsertDistantLight(Vector(1, 0, 0));
        canvas.Update();

        //! [00760_exchange_parasolid_boolean_instantiate]
        TestBooleanOperator* boolean_operator = new TestBooleanOperator(canvas, cadmodel);
        view.GetOperatorControl().Push(boolean_operator, HPS::Operator::Priority::Default);
        //! [00760_exchange_parasolid_boolean_instantiate]

        return true;
    }

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_setup]
       :end-before: //! [00760_exchange_parasolid_boolean_setup]

## Operator Retrieval and Boolean Operation Execution

    void executeBooleanOperation(HPS::Canvas& myCanvas)
    {
        //! [00760_exchange_parasolid_boolean_retrieve]
        OperatorPtr myOpPointer;
        myCanvas.GetFrontView().GetOperatorControl().ShowTop(myOpPointer);
        if (myOpPointer->GetName() == "TestBooleanOperator") {
            auto op = std::static_pointer_cast<TestBooleanOperator>(myOpPointer);
            op->ApplyBoolean();
            myCanvas.UpdateWithNotifier().Wait();
        }
        //! [00760_exchange_parasolid_boolean_retrieve]
    }

    .. literalinclude:: ../../../internals/tests/docs/source/cs/00760_exchange_parasolid_boolean.cs
       :start-after: //! [00760_exchange_parasolid_boolean_execute]
       :end-before: //! [00760_exchange_parasolid_boolean_execute]

Conversation saved
HOOPSY BETA

### Hello! I'm HOOPSY

Your multilingual generative AI assistant for documentation. Ask me anything about HOOPS Visualize Desktop in your own language, or choose a sample question below to start a conversation:

What is HOOPS Visualize Desktop?
What are the latest release notes for HOOPS Visualize Desktop?
How do I start evaluating HOOPS Visualize Desktop?
How do I write my first sample application?
How can I get assistance?

HOOPSY may store conversations according to OpenAI's policy. Responses may not always be accurate.

HOOPSY is thinking...