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
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.
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
Include MeasureOperator.js in your HTML page as seen in the following code:
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 in the previous operator. Now, instead of highlighting the entity the user has clicked on, we are just determining the selection position from the selectionItem returned by pickFromPoint(). As a first step we are going to just 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:
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.
Go ahead and run this code with your browser develop tools open to the console. You will notice that the returned position is in 3d world space. If you need the 2D position of the pick location within the window you can use projectPoint() on the view object (see also Selection and Picking).
Creating a Markup Element
One way to mark the selected position in the Web Viewer component is to add a markup element. We do this with the code below. First, we need to create a new variable holding the markup element in the constructor. Update the constructor in MeasureOperator.js with the code below:
Next, we need to create a new markup element and register it with the MarkupManager if the user has picked a point on the model. Update the code of onMouseDown in MeasureOperator.js to the following (we will implement the DistanceMarkup class shortly):
Markups in the Web Viewer component are generally rendered as SVG elements on top of the 3D WebGL window. That means that they must be manually transformed on every camera change if they are meant to be anchored to the 3D Model. They are also not "hidden surface removed" meaning that a markup element will always appear in-front of the 3D geometry.
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 then 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:
The finalize method indicates the operator has completed, and the markup is not being edited anymore. You will notice we are defining two points, which are the start and endpoints of the line. The first point is already set in the constructor when the markup object is getting created.
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 can 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, and on 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 still getting finalized. Modify the code in the draw() function from the MeasureOperator.js file.
Handling Model Units
One issue that 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 CAD application. In some cases they can also even 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 class' onMouseDown() function to the line below:
We need to add this unit value as an additional parameter to the DistanceMarkup constructor:
And then apply the units when we draw the text by modifying the end of the draw() function:
The Programming Guide provides more information on how model units are handled in the HOOPS Web Viewer component.
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 our distribution. 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.