Setting up the Viewers
Summary
In this chapter, we will walk through setting up two web viewers – one to display the full assembly of our configured model and one to give a preview view of a selected component.
Concepts
- Declaring multiple HOOPS Web Viewer components
- Setting callbacks via component events
- Configuring additional Web Viewer options
Create the Main Entry Point
To get started, we will create a new JS file titled app.js and place it in the src/js folder. The app.js file will contain our main application logic, as well as manage the setup and handling of our two WebViewer objects.
To better organize our code, we will be breaking up our code and using the external module approach for this development. We will be using the ES6 class syntax to encapsulate functionality. Let us start by making the main class in our app.js file. This is where we will instantiate our viewers and build our application through composition from other objects we will create later. Right now, we will just leave the class and constructor blank and return to it.
class main {
constructor() {
}
} // End main class
Registering the Web Viewer Components
This tutorial uses the Web Components UI kit (@ts3d-hoops/web-viewer-components) - the same one introduced in the Building A Basic Application tutorial. If you haven’t been through that tutorial yet, its Connecting to the HOOPS Web Viewer Component section is a useful primer on the <hoops-web-viewer> element and the <hoops-service-registry> / <hoops-web-viewer-context-manager> wrapper elements it needs.
Registering the components is a matter of importing the package once, for its side effect. Since we have provided the CSS file for you, you can import both it and the component package at the top of the app.js file. Your app.js file should now look something like this:
import '../css/tutorial-configurator.css';
import '@ts3d-hoops/web-viewer-components';
// Application logic will begin once DOM content is loaded
window.onload = () => {
const app = new main();
};
class main {
constructor() {
}
} // End main class
At this point, you are welcome to run npm run dev in the command line to start our Vite dev server and serve the frontend framework that we have provided. You can leave the development server running, and it will update your frontend as we write more code. You should see something like this before writing any additional JavaScript:
Adding the Viewers to the Page
Because we have provided the HTML and styling, we should see the container elements present that we can attach our HOOPS Web Viewers to. Unlike a plain-<div>-and-new WebViewer(...) setup, the <hoops-web-viewer> custom element is the rendering surface, wrapped in the same <hoops-service-registry> / <hoops-web-viewer-context-manager> plumbing used throughout the Basic App tutorial. Since we have two independent viewers on this page, each gets its own pair of wrapper elements:
<div id="component-preview" class="comp-container">
<hoops-service-registry>
<hoops-web-viewer-context-manager>
<hoops-web-viewer id="comp-viewer" enginePath="./engine"></hoops-web-viewer>
</hoops-web-viewer-context-manager>
</hoops-service-registry>
<div id="component-preview-label" class="comp-label">Component Preview</div>
</div>
<div id="canvas" class="comp-container">
<hoops-service-registry>
<hoops-web-viewer-context-manager>
<hoops-web-viewer id="viewer" enginePath="./engine"></hoops-web-viewer>
</hoops-web-viewer-context-manager>
</hoops-service-registry>
<div id="viewer-label" class="comp-label">Full Assembly Preview</div>
</div>
Note
<hoops-service-registry> registers its services into a single, page-wide registry the first time it connects to the DOM, so using it twice like this (once per viewer) is harmless - it’s just simplest to reason about, since each viewer’s markup is then self-contained. This tutorial doesn’t use any of the toolbar/panel components that consume those services, so it isn’t something you need to worry about either way here.
We can see that the <hoops-web-viewer> ids we are interested in are still called viewer and comp-viewer - they now live on the <hoops-web-viewer> elements themselves rather than on plain <div> tags. We will use these ids when connecting to our viewers.
Note
The enginePath attribute tells HOOPS Web Viewer where to find the graphics engine files (engine.esm.wasm and related assets) at run time. It must point to a directory your web server or bundler actually serves - for example, copy the contents of @ts3d-hoops/sc-engine from your HOOPS Visualize Web installation into an engine folder alongside your bundled output.
Note
Unlike the old <div>-and-new WebViewer(...) setup, nothing sizes either viewer for you at runtime - <hoops-web-viewer> is sized entirely by ordinary CSS, the same way as in the Building A Basic Application tutorial. The provided tutorial-configurator.css already includes rules for this:
#canvas hoops-web-viewer,
#component-preview hoops-web-viewer {
display: block;
width: 100%;
height: 100%;
}
If you look back at your web application, you will see nothing has changed yet – we haven’t wired up any application logic to either component. Don’t worry, we haven’t loaded a model into either viewer yet either; we will instantiate both viewers empty since we do not know ahead of time which model the user will want to preview.
Connecting to the WebViewer Components and Setting Callbacks
The next step is to get references to both <hoops-web-viewer> elements and subscribe to their events. If you are unfamiliar with the component’s event-based callback style, or need a refresher, please review the callbacks section of the Building A Basic Application tutorial: every entry in the old CallbackMap is re-dispatched as a same-named DOM CustomEvent prefixed with hwv (so sceneReady becomes hwvSceneReady), and its detail object always includes hwv, the underlying WebViewer instance.
class main {
constructor() {
// Get references to both HOOPS Web Viewer components
this._viewerEl = document.getElementById("viewer");
this._compViewerEl = document.getElementById("comp-viewer");
} // End main constructor
} // End main class
We are interested in the hwvSceneReady event on both viewers. As discussed in the Building A Basic Application tutorial, this event fires as soon as each Web Viewer is active and can be interacted with; any functions on a Web Viewer object should only be called after its own hwvSceneReady event has fired.
this._viewerEl.addEventListener("hwvSceneReady", ({ detail: { hwv } }) => {
this._viewer = hwv;
// Set background color for viewers
// Enable nav cube and axis triad
// Enable backfaces
});
this._viewerEl.addEventListener("hwvSelectionArray", ({ detail: { selectionEvents } }) => {
// Reserved for later use
});
We can retrieve the navigation cube and axis triad via the View object on the WebViewer instance. We will retrieve these objects, enable them, and move the navigation cube from its default position to the lower right corner of the scene.
// Additional viewer options
this._viewer.view.getAxisTriad().enable();
this._viewer.view.getNavCube().enable();
this._viewer.view.getNavCube().setAnchor(OverlayAnchor.LowerRightCorner);
In addition, we will spice up our viewer by adding a colored background consistent with our UI theme. This is as simple as calling setBackgroundColor on the View object. You can specify a single background color, or make a gradient from top to bottom by providing two colors. We will choose to make the gradient.
// Background color for viewers
this._viewer.view.setBackgroundColor(new Color(33, 33, 33), new Color(175, 175, 175));
Lastly, we will be working with many independent models from multiple sources. It is impossible to say whether our model data vertices were constructed appropriately (hopefully they were), but to ensure the rendering is complete, we will enable rendering of the backfaces. This ensures that any culling based on the orientation of vertices will not result in missing facets in our model.
this._viewer.view.setBackfacesVisible(true);
With this, our hwvSceneReady listener for the main viewer should be complete.
this._viewerEl.addEventListener("hwvSceneReady", ({ detail: { hwv } }) => {
this._viewer = hwv;
// Background color for viewers
this._viewer.view.setBackgroundColor(new Color(33, 33, 33), new Color(175, 175, 175));
// Additional viewer options
this._viewer.view.setBackfacesVisible(true);
this._viewer.view.getAxisTriad().enable();
this._viewer.view.getNavCube().enable();
this._viewer.view.getNavCube().setAnchor(OverlayAnchor.LowerRightCorner);
});
Don’t forget the import for Color and OverlayAnchor, alongside the CSS/component-registration imports at the top of app.js:
import { Color, OverlayAnchor } from '@ts3d-hoops/web-viewer';
We can perform the same desired operations on the component viewer, though we do not need the nav cube and axes in this viewer.
this._compViewerEl.addEventListener("hwvSceneReady", ({ detail: { hwv } }) => {
this._compViewer = hwv;
// Background color for viewers
this._compViewer.view.setBackgroundColor(new Color(33, 33, 33), new Color(175, 175, 175));
this._compViewer.view.setBackfacesVisible(true);
});
We will use the hwvSelectionArray event later to add additional functionality to our scene.