HOOPS Web Viewer selection

This section provides an overview and samples of the different APIs available for selection in the HOOPS Web Viewer.

Overview

From a low-level perspective, there are currently three types of selection that can occur in the Web Viewer: face, line, and part. Face and line selections are usually created from a click or touch action, whereas a part-level selection is the result of an API call.

Each type of selection generates a <a href=”../api_ref/typedoc/classes/communicator.selection.selectionitem.html”>SelectionItem</a> that contains details about the selection and the ID of the associated part. When any event with a <a href=”../api_ref/typedoc/enums/communicator.selectiontype.html”>SelectionType</a> occurs, a <a href=”../api_ref/typedoc/classes/communicator.event.nodeselectionevent.html”>NodeSelectionEvent</a> will be passed to all registered selection callbacks (see <a href=”../api_ref/typedoc/interfaces/communicator.callbackmap.html#selectionarray”>selectionArray</a> in the <a href=”../api_ref/typedoc/interfaces/communicator.callbackmap.html”>callback docs</a>).

A common way to implement selection in the Web Viewer is to simply use the <a href=”../api_ref/typedoc/classes/communicator.operator.selectionoperator.html”>SelectionOperator</a>. By default, the <a href=”../api_ref/typedoc/classes/communicator.operator.selectionoperator.html”>SelectionOperator</a> is enabled in the Web Viewer’s operator stack. The operator stack also includes any other currently active operators and is accessible via the <a href=”../api_ref/typedoc/classes/communicator.operatormanager.html”>OperatorManager</a> object.

The default behavior of the <a href=”../api_ref/typedoc/classes/communicator.operator.selectionoperator.html”>SelectionOperator</a> is to highlight the part associated with a selection.

A <a href=”../api_ref/typedoc/modules/communicator.html#partid”>PartId</a> can be retrieved from the selection event data using the <a href=”../api_ref/typedoc/classes/communicator.selection.selectionitem.html#getnodeid”>getNodeId()</a> function.

<div class="line">var partIds = [];</div>
<div class="line">myViewer.selectionManager.each(function (selectionItem) {</div>
<div class="line"> partIds.push(selectionItem.getNodeId());</div>
<div class="line">});</div>

It’s important to note that in each of these selection event data objects, the <a href=”../api_ref/typedoc/modules/communicator.html#partid”>PartId</a> is retrieved by calling the <a href=”../api_ref/typedoc/classes/communicator.selection.selectionitem.html#getnodeid”>getNodeId()</a> function (a PartId is a type of NodeId - see the <a href=”../overview/faqs.html#PartIds”>FAQs</a> for more info).

There are a handful of different types of selection events, each with their own data objects (e.g., line, face, point, and part selection objects). To find out more about each, please see the API Reference for the <a href=”../api_ref/typedoc/modules/communicator.selection.html”>Selection</a> class and the <a href=”../api_ref/typedoc/enums/communicator.selectiontype.html”>SelectionType</a> class.

With the selection information in hand, it’s possible to display additional information or attributes for the selected part. How you access attribute data depends on how you’ve converted your CAD model to the StreamCache format. Effectively, there are two options for accessing part attributes:
  1. embedding attributes in a CAD file, or 2) retrieving attributes from an external source.

For the first option, during CAD file conversion you may choose to include attributes alongside the 3D model data in the converted file, in which case the properties will be available from the selection object. To enable import of attributes in during conversion, see the <a href=”../api_ref/data_import/converter-command-line-options.html#read_attributes”>read_attributes</a> flag in the <a href=”../api_ref/data_import/converter-command-line-options.html”>Command-Line Options</a> docs.

In most cases, however, the attributes will be stored in an external system and will need to be retrieved. After querying your external system for information based on the ID, the result can then be displayed in an HTML Element elsewhere on the page.

The selection example in the Communicator package illustrates this concept by displaying the properties of a selected part in a table below other selection information retrieved from the event:

../_images/example_selection.png

Selection example showing part data, available in the %Communicator package

Simple programmatic selection

Select a single node

The following snippet shows how to use the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>selectionManager</a> class to select a specific node in the model:

        const selectionManager = hwv.selectionManager;

        selectionManager.setNodeElementSelectionColor(Communicator.Color.green());
        selectionManager.setNodeElementSelectionHighlightMode(
            Communicator.SelectionHighlightMode.HighlightAndOutline,
        );
        selectionManager.setNodeElementSelectionOutlineColor(
            Communicator.Color.createFromFloat(0.0, 1.0, 0.0),
        );

        selectionManager.selectNode(10, Communicator.SelectionMode.Toggle);

After specifying a few properties about our selection appearance, we can use the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#selectnode”>selectNode()</a> function of the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>selectionManager</a> to specify a <a href=”../api_ref/typedoc/modules/communicator.html#nodeid”>NodeId</a> from our model, which will be highlighted when the function is executed.

Pick from point selection

Another simple and common selection method is to pick an entity from a 2D point on the canvas:

        var view = hwv.view;
        const selectionManager = hwv.selectionManager;

        var config = new Communicator.PickConfig();
        config.allowFaces = true;
        config.allowLines = true;
        config.allowPoints = true;

        var node;
        view.pickFromPoint(new Communicator.Point2(800, 500), config).then(function (
            selectionItem,
        ) {
            node = selectionItem.getNodeId();
            selectionManager.add(selectionItem);
        });

In this snippet, we initialize and populate an object for a <a href=”../api_ref/typedoc/classes/communicator.pickconfig.html”>PickConfig</a> object to store a variety of selection options that will be passed to the selection function.

In our call to <a href=”../api_ref/typedoc/classes/communicator.view.html#pickfrompoint”>pickFromPoint()</a>, we’ve passed an arbitrary 2D coordinate on the canvas as a parameter. Using this canvas location, the function will heuristically determine which entity (point, face, or line) to select. Selection results can be customized by modifying the options in the <a href=”../api_ref/typedoc/classes/communicator.pickconfig.html”>PickConfig</a> object.

The behavior of this method will be very similar to the default behavior of the <a href=”../api_ref/typedoc/classes/communicator.operator.selectionoperator.html”>SelectionOperator</a>, which calls <a href=”../api_ref/typedoc/classes/communicator.view.html#pickfrompoint”>pickFromPoint()</a> for mouse clicks.

Area and volume selection

Please note: Cutting planes, overlay geometry, and backface culling will be ignored for all of the methods described in this section.

Select by area

The <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginscreenselectbyarea”>beginScreenSelectByArea()</a> method in the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>SelectionManager</a> class offers an easy way to select objects within a two-dimensional area of the screen. Simply define the bounds of your selection with two points of the screen, and pass that information along with a configuration object to the function:

        var view = hwv.view;
        var model = hwv.model;
        var selectionManager = hwv.selectionManager;

        var k = 0.5;
        var screenMin = new Communicator.Point2(-k, -k);
        var screenMax = new Communicator.Point2(+k, +k);

        var pixelDim = view.getCanvasSize();

        var pixelMin = new Communicator.Point2(
            Math.floor(((screenMin.x + 1) / 2) * pixelDim.x),
            Math.floor(((screenMin.y + 1) / 2) * pixelDim.y),
        );

        var pixelMax = new Communicator.Point2(
            Math.floor(((screenMax.x + 1) / 2) * pixelDim.x - 1),
            Math.floor(((screenMax.y + 1) / 2) * pixelDim.y - 1),
        );

        var config = new Communicator.IncrementalPickConfig();
        config.mustBeFullyContained = true;

        selectionManager
            .beginScreenSelectByArea(pixelMin, pixelMax, config)
            .then(function (handle) {
                var loop = function (stillProcessing) {
                    if (!stillProcessing) {
                        return Promise.resolve(null);
                    }
                    return selectionManager.advanceIncrementalSelection(handle).then(loop);
                };
                return loop(true).then(function () {
                    return selectionManager.endIncrementalSelection(handle);
                });
            })
            .then(function () {
                return true;
            });

In this snippet, we initialize and populate an object for <a href=”../api_ref/typedoc/classes/communicator.incrementalpickconfig.html”>IncrementalPickConfig</a> to store selection options that will be passed to the selection function. This configuration is simply the default settings, with the exception of the setting <a href=”../api_ref/typedoc/classes/communicator.incrementalpickconfig.html#mustbefullycontained”>mustBeFullyContained</a>, which we’ve set to true to specify that no objects that are partially outside of the selection area should be contained within our selection results.

The function <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginscreenselectbyarea”>beginScreenSelectByArea()</a> returns a promise, which resolves into another function that recursively iterates through the model’s entities by calling <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#advanceincrementalselection”>advanceIncrementalSelection()</a> until there are no more parts to select, at which point it returns false to stop the iteration.

The result of this process is that our <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>selectionManager</a> instance now contains an array of <a href=”../api_ref/typedoc/interfaces/communicator.selection.nodeselectionitem.html”>NodeSelectionItems</a> that can be accessed via methods in the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>SelectionManager</a> class, such as <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#getresults”>getResults()</a>.

Ray drill selection

Also part of the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>SelectionManager</a> class, the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginraydrillselection”>beginRayDrillSelection()</a> function uses ray selection to drill deep into the model (as opposed to selecting the first non-occluded item, as in <a href=”../api_ref/typedoc/classes/communicator.view.html#pickfrompoint”>pickFromPoint()</a> ).

Like <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginscreenselectbyarea”>beginScreenSelectByArea()</a>, <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginraydrillselection”>beginRayDrillSelection()</a> uses an incremental process, iterating through the model’s instances and aggregating the results:

        var view = hwv.view;
        const selectionManager = hwv.selectionManager;

        var screenPos = new Communicator.Point2(0, 0);

        var pixelDim = view.getCanvasSize();

        var pixelPos = new Communicator.Point2(
            Math.floor(((screenPos.x + 1) / 2) * pixelDim.x),
            Math.floor(((screenPos.y + 1) / 2) * pixelDim.y),
        );

        var pixelBoxRadius = 3;

        var config = new Communicator.IncrementalPickConfig();
        config.allowFaces = true;
        config.allowLines = false;
        config.allowPoints = false;

        selectionManager
            .beginRayDrillSelection(pixelPos, pixelBoxRadius, config)
            .then(function (handle) {
                var loop = function (stillProcessing) {
                    if (!stillProcessing) {
                        return Promise.resolve(null);
                    }
                    return selectionManager.advanceIncrementalSelection(handle).then(loop);
                };
                return loop(true).then(function () {
                    return selectionManager.endIncrementalSelection(handle);
                });
            })
            .then(function () {
                return true;
            });

In this example, we’re setting our ray in the middle of the canvas and passing that 2D point to the <span class=”code”><a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginraydrillselection”>beginRayDrillSelection()</a> along with a handful of configuration settings in the <a href=”../api_ref/typedoc/classes/communicator.incrementalpickconfig.html”>pickConfig</a> object.

The result of this process is that our <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>selectionManager</a> instance now contains an array of <a href=”../api_ref/typedoc/interfaces/communicator.selection.nodeselectionitem.html”>NodeSelectionItems</a> that can be accessed via methods in the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>SelectionManager</a> class, such as <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#getresults”>getResults()</a>.

The pixelBoxRadius setting specifies the proximity around the ray for selecting parts (the higher the value, the more inclusive the selection).

The recursive pattern used to aggregate the selection items is effectively the same as discussed in the section above for the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginscreenselectbyarea”>beginScreenSelectByArea()</a> function.

Polyhedron selection

Below is a sample of volume selection using the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginconvexpolyhedronselection”>beginConvexPolyhedronSelection()</a> function from the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html”>SelectionManager</a> class. Here we’re defining a volume by creating a box with six planes:

        var selectionManager = hwv.selectionManager;
        var createPlane = function (px, py, pz, nx, ny, nz) {
            var p = new Communicator.Point3(px, py, pz);
            var n = new Communicator.Point3(nx, ny, nz);
            return Communicator.Plane.createFromPointAndNormal(p, n);
        };
        var config = new Communicator.IncrementalPickConfig();
        var center = new Communicator.Point3(0, 0, 0);
        var x = 300;
        var y = 200;
        var z = 150;
        var planes = [
            createPlane(+x, 0, 0, -1, 0, 0),
            createPlane(-x, 0, 0, +1, 0, 0),
            createPlane(0, +y, 0, 0, -1, 0),
            createPlane(0, -y, 0, 0, +1, 0),
            createPlane(0, 0, +z, 0, 0, -1),
            createPlane(0, 0, -z, 0, 0, +1),
        ];
        selectionManager
            .beginConvexPolyhedronSelection(planes, center, config)
            .then(function (handle) {
                var loop = function (stillProcessing) {
                    if (!stillProcessing) {
                        return Promise.resolve(null);
                    }
                    return selectionManager.advanceIncrementalSelection(handle).then(loop);
                };
                return loop(true).then(function () {
                    return selectionManager.endIncrementalSelection(handle);
                });
            })
            .then(function () {
                return true;
            });

Our first step is to define a function to create our planes. In this case, we’re using <a href=”../api_ref/typedoc/classes/communicator.plane.html#createfrompointandnormal”>createFromPointAndNormal()</a> to create our planes; hence, it’s only required that we provide a single point and a normal.

Once the planes have been defined, we simply need to pass them to <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginconvexpolyhedronselection”>beginConvexPolyhedronSelection()</a> along with the heuristicOrigin for calculating distances for ordering our results. Often, this will be the center of the volume you’ve defined, and in this case, we’re using the origin, although other values may be preferred.

The recursive pattern used to aggregate the selection items is effectively the same as discussed in the section above for the <a href=”../api_ref/typedoc/classes/communicator.selectionmanager.html#beginscreenselectbyarea”>beginScreenSelectByArea()</a> function.