5. Advanced Operator Concepts
In this section, we discuss how to create a more advanced multi-stage operator that consumes various mouse events, and requires a more complex interaction. We also demonstrate how to manage markup data.
- Multi-stage Operator Design
- Creating and Displaying Markup
- Measurement Units
Creating the Measure Operator Class
We start once again with an empty JS document called MeasureOperator.js and then derive a new operator from the base operator interface. To review, we will implement our custom operator by completing the same three steps from the previous section:
- Create the custom operator class, which will be extended from Communicator.Operator.Operator
- Add an event handler to the event of choice (in this case, onMouseDown)
- Register the custom operator with the Web Viewer
Let's create an operator that will measure the distance between two points. Create a new file called MeasureOperator.js with the following code:
Include MeasureOperator.js in your HTML page as seen here:
Handling the On Mouse Down Event
Next, let's start filling in the onMouseDown event. Our operator should measure the distance between two points on a model. Both of these points will be chosen based on mouse clicks of the user.
The snippet of code below looks very similar to the code we wrote for the previous operator. Now, instead of highlighting the entity the user has clicked on, we are determining the selection position from the selectionItem returned by pickFromPoint(). As a first step, we are going to print the position to the console via console.log(...).
Registering the New Operator
Let's add this new operator to our menu and register it in gettingstarted.html:
Add the code below to the top of the _initEvents function in the Menu.js file.
Finally, update the conditionals in customOperatorSelect.onclick() inside the Menu.js file.
Creating a Markup Element
One way to mark the selected position in the Web Viewer component is to add a markup element. In order to do this, we need to create a new variable holding the markup element in the constructor. Update the constructor in MeasureOperator.js with the code below:
When the user picks a point on the model, we will create a new markup element and register it with the MarkupManager. Update the code of onMouseDown in MeasureOperator.js to the following (we will implement the DistanceMarkup class shortly):
Markup elements in the Web Viewer are rendered as SVG elements on top of the 3D window. Therefore, they will always be drawn in front of the geometry. Additionally, they must be manually transformed on every camera change if they are meant to be anchored to specific points on the model.
We know that if the user has clicked on the model but no markup element exists yet, we need to create a new markup element that will hold the geometry for this markup operation. This element needs to be registered with the MarkupManager (we will define that object next). If it already exists, we know that the first point has already been defined and we need to set the second point of the operation to finish the markup.
Defining the Markup Class
Now, let's look at how we define our markup object class. Add the following class to the MeasureOperator.js file:
You will notice we are defining two points, which are the start and endpoints of the measurement. The first point is already set in the constructor when the markup object is getting created. The finalize method indicates the operator has completed, and the markup is not being edited anymore.
Implementing the Draw Method
Now let's look at the draw method:
For now, we just want to draw two points at the "click" locations. To do this we pick the appropriate markup shape element (in this case: circle).
Markup is always defined in 2D space so we need to project the 3D points that are returned in the pickFromPoint() function into 2D space. This is accomplished by the projectPoint() function which takes a 3D position and translates it into a 2D position based on the current camera.
This point is then be used to define the center of the circle. In this case, we choose a fixed radius of 2. This is enough to draw a circle with otherwise default attributes via the drawCircle() function. If we have already defined the second point, we can draw this point as well.
For more details on the markup capabilities of the HOOPS Web Viewer component, please refer to the Programming Guide.
Running the Application
Let's run our code and see the result. After selecting the operator, two individual clicks will select the start and endpoint of the measurement. After that, the operator starts over and a new measurement is created.
You will notice that the operator "snaps" to each visible edge within a given proximity. This behavior can be controlled either by configuring the pickFromPoint function with the PickConfig class or by modifying the pick tolerance via SelectionManager.setPickTolerance.
Displaying Line and Distance
To improve our operator, we can draw a line between the two points, as well as indicate the disctance between the points as text. Update draw with the following:
Now, on the first click, a circle will be drawn to the first point. On the second click, another circle will be drawn with a line spanning the distance. Text markup will should appear at the calculated midpoint of the line to show the distance.
Drawing the Markup on Mouse Move
To make the operator more interactive, it would be nice if the markup would be drawn while the user moves the mouse and not only after the second click. For that, we need to also handle the mouse move events in the operator:
As you can see, we handle selection similar to the onMouseDown event, fill out the second point and then force a refresh of the markup which normally only gets redrawn if the camera changes.
Different Style Before Finalizing
Also, let's draw the line a bit differently if the markup is not yet finalized. Modify the code in the draw() function from the MeasureOperator.js file to the following:
Handling Model Units
One issue we haven't addressed is the unit of measurement. Without retrieving the so-called "unit multiplier" from a model, the distance we calculated is unitless.
The measurement units for a model are usually created by the author of the CAD model in the original CAD application. In some cases, units can also vary per model tree node for different subassemblies, so they have to be retrieved on a per-node basis.
Change this._activeIndication = new DistanceMarkup(this._hwv, position); in the MeasureOperator::onMouseDown() function to the line below:
We need to add this unit value as an additional parameter to the DistanceMarkup constructor:
Then, apply the units to the text by modifying the end of the draw() function:
We have now given you an overview of the basic building blocks for writing a more advanced operator similar to our internal measurement or markup operators. For more information please see the Programming Guide.
All of the UI code for HOOPS Web Viewer (including all operators) is available as readable source code as part of the HOOPS Communicator package. The UI file is a great learning resource to understand some of the more advanced concepts of the HOOPS Web Viewer API. It can be found in the package in the following location: web-viewer/src/js/web-viewer-ui.js.