Operators in Envision Web
Overview
In order for a user to interact with the CEETRON Envision Viewer, your application will need to respond to various input events from devices such as the mouse, the keyboard, or a touch screen. While it is possible to handle user input yourself using DOM event listeners directly, CEETRON Envision for Web provides the concept of “Operators” in order to simplify and enhance this process. Operators hook into the browser event loop and fire predefined actions in response to user inputs - it is essentially a class of bundled functionality that will be triggered by captured input events. Operators are a way to handle user input in a more structured way - they encapsulate the logic of handling user input in a single place, and to make it easier to switch between different modes of interaction.

The OperatorStack Class
Each Viewer in a CEETRON Envision CloudSession will typically contain one or more Views for rendering. When a View is created, it composes an OperatorStack object that is owned and associated only with that View. Therefore, any Operators on that particular OperatorStack are only active for that View.
This OperatorStack is responsible for adding, removing, reordering, and referencing any Operators on the View. To achieve this, the OperatorStack contains standard stack behavioral functions, such as push, pop, set, etc. As its name implies, the OperatorStack will call any Operator logic in a ‘last in, first called’ order. In other words, the Operator at the top of the stack will be the first to receive and respond to the input event. Depending on how the Operator is implemented or defined, the event may or may not propagate down the stack to other Operators.
The OperatorStack will function on any object that is derived from the abstract Operator base class. Once an operator is pushed to the stack, it will be registered and can be retrieved using the unique name identifier of the Operator, regardless of whether the operator is active on the stack or not.
The Operator Class
The Operator class is an abstract class that contains optional functions with names that correspond to DOM event listeners. These functions can be implemented to perform desired actions when the corresponding event is triggered in the View.
- The functions for events that can currently be implemented by an Operator derived class are:
- onMouseDown
- onMouseUp
- onClick
- onDblClick
- onMouseMove
- onWheel
- onKeyDown
- onKeyUp
- onTouchStart
- onTouchEnd
- onTouchMove
- onPointerDown
- onPointerUp
- onPointerMove
When an Operator is instantiated, it needs a unique name identifier. A unique name ensures that the operator can be easily identified and managed within the OperatorStack. For standard built-in operators in Envision, you can reference this unique name using the StandardOperator enum.
The optional operator event handler methods are required to return an OperatorEventStatus indicating whether the particular event should propagate down the stack, or not. If the event is designated to propagate, the OperatorStack will call the next operator in the stack using the same event. If the event is not designated to propagate, the OperatorStack will not call any other operators in the stack for that event.
An instance of a given Operator can only be attached to one or zero Views. You can reference an Operators owning View with the ownerView property.
Basic Examples
Here is the simpliest class implementation of an Operator in Envision. This Operator simply takes the mouse coordinates of the Viewer element and logs the values to the console on each new onmousemove event.
export class EventLogOperator extends cee.Operator {
constructor(name: string) {
super(name);
}
onMouseMove(event: MouseEvent) {
console.log(` offsetX: ${event.offsetX}, offsetY: ${event.offsetY}`);
return cee.OperatorEventStatus.PROPAGATE;
}
}

Of course, we suspect your Operators will be a bit more complex and useful than this, but this simple example demonstrates the basic principle and simplicity of creating an Operator.
Once we have our operator class created, we can instantiate it, and push it to the desired views operator stack.
const opStack = myView.operators;
const operator = new EventLogOperator("LogOperator");
opStack.push(operator);
Any event detected by this View will now pass that event along to the implemented operator functions.
Notice we have chosen to let the onmousemove event propagate in this simple operator. This allows the same event to be used by another operator further down the stack that may have an onMouseMove function implemented. If we had chosen to not allow the event to propagate, no other onMouseMove functions implemented in other operators on the OperatorStack would be called.
Let’s build another simple operator, but now one that uses some other CEETRON APIs in order to control our scene and interrogate the model. In this operator, we will show the specified scalar result on a RemoteModel when holding the left mouse button down.
export class ScalarPeekOperator extends cee.Operator {
private m_scalarId: number = -1;
private m_hitRemoteModel: cee.ug.RemoteModel = null;
constructor(name: string) {
super(name);
}
get scalarId(): number {
return this.m_scalarId;
}
set scalarId(id: number) {
this.m_scalarId = id;
}
onMouseDown(event: MouseEvent) {
const ray = this.ownerView.ownerViewer.rayFromCssCoordinate(this.ownerView, event.offsetX, event.offsetY);
for (let model of this.ownerView.getModelArray()) {
if (model instanceof cee.ug.RemoteModel) {
this.m_hitRemoteModel = model;
break;
}
}
if (this.m_hitRemoteModel) {
this.m_hitRemoteModel.rayIntersect(this.m_hitRemoteModel.currentFrameIndex, ray, (intersectResult) => {
if (intersectResult.hitItem.anythingHit) {
intersectResult.model.modelSpec.fringesResultId = this.m_scalarId;
}
})
}
return cee.OperatorEventStatus.PROPAGATE;
};
onMouseUp() {
if (this.m_hitRemoteModel) {
this.m_hitRemoteModel.modelSpec.fringesResultId = -1;
this.m_hitRemoteModel = null;
}
return cee.OperatorEventStatus.PROPAGATE;
}
}

Here we create an accessor to get/set the scalarID we want to show, and then implement the onMouseDown and onMouseUp optional operator functions. In onMouseDown, we determine if there is a RemoteModel in the view to operate on (this example simply takes the first one added to the scene and does not account for multiple RemoteModels in the scene). We then cast a ray into the scene to determine if that ray intersects the RemoteModel. If so, it colors the model as fringes with the specified scalarId (if the id is invalid, the viewer will log an exception - you can implement more robust handling if desired). In onMouseUp, we remove the coloring of the fringes.
The “Hot Key” Operator
The OperatorStack also has a special field, known as a ‘hot key’ operator. This is a special designation you can give to one (and only one) Operator on the stack. It gives special priority to the Operator and allows for quick access to an Operator. If a hot key operator is assigned, it can be immediately accessed by holding the Alt/Opt key.
This optional key mapping can directly access an operator on the stack without needing to worry about what other operators are currently on the stack. The hot key operator will take precedence over the stack, and fire its operator logic, and not propagate its events down the stack. This can be particularly useful if you wish to quickly maintain access to another global operator, such as navigation, but also allows for quick pairings of operators that may work frequently together.
In the example image below, you can see we use the Box Operator, which by default does not propagate its navigation events. However, we can quickly still navigate the scene by holding the Alt/Opt key to access the Navigation Operator, effectively bypassing the local box navigation.

Operators Examples and Example App
We have developed several operators that can serve as the basis or inspiration for other operators you may want to use in your application. You can find these operators in the Examples/Operators directory of your CEETRON Envision Web package. In here, you will see a SampleOperators folder containing classes for the various operators we have developed. Some Operators are only designed to work with particular modules, such as RemoteModel or GeometryModel. Depending on your application, you can expand these operators to handle different model types, though this will require more handling and implementation for the various models in your scene. We have decided to keep them separate and focused to a particular module for simplicity.
- You will find Operator samples for:
- Geometry Previews (such as visualizing a plane or box)
- Picking Operations (for parts or nodes in a scene)
- Transforms (Translations/Rotations)
You may also notice that you can abstract common functionality into abstract classes, and inherit from those. For example, if you want to make separate RemoteModel and ConstantRemoteModel picking operations, you can handle the common picking logic in the parent class, and then leave the module specific implementation to the child classes.
Child classes can also extend the capability of a fully functional operator. In the case of the UgCutPlanePreviewOperator, we take the base functionality of the PlanePreviewOperator (which will simply visualize a plane), and then perform feature extractions using that PlanePreview to make a cutting plane. In this example, we can now visualize the location of a cutting plane on the client side, before directing the server to perform the actual cutting plane and feature extraction.
We encourage you to use as much or as little of these samples in your application, as you see fit. We hope that these samples will serve as inspiration for your own operators and give insight on how to take advantage of the Operators feature.
Disabling Operators in Favor of Legacy Viewer UI
While we recommend the use of operators to handle your Viewer UI, if you are not ready to make the transition yet, you can disable the use of operators. If you wish to disable the Operators feature in favor of the legacy Viewer UI, you can do so by setting the ‘useOperators’ property of the ViewerOptions to false. It should be noted that this legacy UI is deprecated and will be removed in future versions of CEETRON Envision Web, so we recommend exploring the use of Operators as soon as possible.
Transitioning from the Legacy Viewer UI functions to Operators
- navigation: Properties to control navigation behavior found in view.navigation are now managed with the built-in NavigationOperator. As opposed to the legacy Navigation class, the NavigationOperator will not automatically be instanced and pushed to the OperatorStack when the Viewer is created, and it is the developers responsibility to do so. If you do not wish to extend the NavigationOperator for your own purposes, you can simply push the NavigationOperator to the OperatorStack to have the same navigation behavior as the legacy Navigation class:
myView.operators.push(new cee.NavigationOperator());
Once instanced and pushed to the stack, you can retrieve the NavigationOperator with the following code:
const navigationOperator = myView.operators.get(cee.StandardOperator.NavigationOperator);
- picking: The ug.PickHelper and Viewer.setPickHandler functionality has been deprecated, but can easily be used in the same way with Operators. In the Examples/Common/Operators directory of the package, you can find the refactored operator variations of these functions. In order to make use of these operators, you can instantiate them and push them to the OperatorStack to have them respond to mouse picking events.