webvis initialization

In this tutorial, we will explore various methods for initializing webvis contexts and viewers to suit your specific needs. You may want to integrate a webvis viewer directly with your HTML canvas element or utilize one of the pre-built webvis web components. If your project involves a frontend framework such as Vue, React, or Angular, this tutorial will provide valuable guidance on maximizing the potential of webvis in conjunction with these popular frameworks.

webvis Basics

In this tutorial, the crucial distinction to understand is between the context and viewer. A context represents an entity that encompasses the state of a space, including its resources (such as models, clip planes, drawings), user interaction data, and session member connectivity. In most scenarios, an application will host a single context, since users typically engage with one space at a time.

On the other hand, the viewer is responsible for rendering the space’s contents based on a camera’s pose, as determined by the viewer’s view matrix. Multiple viewers can be created from the same context, enabling the visualization of various perspectives or distinct renderings of views.

webvis API

Creating a context directly from the global webvis object and generating viewers from contexts represent the most direct method of interacting with webvis. However, you might prefer to employ our comprehensive, ready-to-use web components. Regardless, gaining a deeper understanding of context and viewer creation can prove advantageous, as these foundational elements underpin our web components and facilitate the development of highly customized applications.

Creating a context through the webvis API is done asynchronously by calling the webvis.requestContext() function. The method takes a name and possibly a settings object and returns a new context if a context with this name does not already exist in your application. If a context already exists, this function will return undefined and warn application developers in the development console. The following example creates two contexts and adds a model to each of them:

<script type="module">
    const myContext = await webvis.requestContext("my_context");
    const mySecondContext = await webvis.requestContext("my_context_2");

    myContext.add("urn:x-i3d:shape:box"); // Adds a box to the first context
    mySecondContext.add("urn:x-i3d:shape:box"); // Adds a box to the second context
</script>

The contexts maintain their state independently from each other. Previously created contexts can be retrieved by calling webvis.getContext() and webvis.getContexts(). getContext() can be used to retrieve a context identified by name (e.g. webvis.getContext("myContext")). Without specifying a name the first context created by your application will be returned.

<script type="module">
    webvis.getContext("my_context"); // Will return the previously created context "my_context"
    webvis.getContext("my_context_2"); // Will return the previously created context "my_context_2"
    webvis.getContext() // Will return the first created context of your application, here "my_context"
    webvis.getContexts() // Will return an array with both contexts in creation order
</script>

A viewer can be created using the ContextAPI.createViewer method:

<canvas></canvas>
<script type="module">
    const context = await webvis.requestContext("default_context");
    const node = context.add("urn:x-i3d:shape:box");
    context.setProperty(node, webvis.Property.ENABLED, true);

    const canvas = document.querySelector("canvas");
    context.createViewer("my-viewer", canvas);
</script>

In this playground, two viewers are created connected to the same context. Notice, how selecting the box in one viewer is also reflected in the other viewer as selection is a context-wide concept.

webvis UI

You can easily incorporate webvis into your application using the provided web components, making the integration process a hassle-free experience. All web components derived from WebVisElement (e.g. <webvis-viewer>) take optional attributes viewer and context. If context is specified and a context with this name already exists, the element will attach to the context. If context is specified and a context with this name does not already exists, it is created. If viewer is specified, it will change the name of the viewer to be created on the context.

The following example illustrates how to create two <webvis-viewer> elements connected to the same context with explicit names for context and viewers:

Lifetime

In numerous instances, your application may not consistently display 3D content, making user experience and performance critical when transitioning between different sections of your application. To comprehend the performance impact of various actions on the Document Object Model (DOM) nodes containing webvis-related content, it is essential to emphasize two main points:

  • The webvis context retains its state throughout your entire application, and its lifecycle concludes only upon page navigation.

  • A viewer depends on its canvas element to preserve its rendered state. As long as the canvas element is not discarded, no resource-intensive re-rendering of the space occurs.

Lifetime when handling canvas elements

When employing canvases directly to achieve greater control over your application’s appearance and user experience, you can detach them from the document when not in use and reattach them when you wish to revisit the 3D content.

The subsequent example illustrates the re-attachment of a canvas:

Lifetime when working with webvis components

Utilizing webvis components also enables you to manage the viewer’s lifetime effectively. As mentioned earlier, <webvis-viewer> and other components derived from WebVisElement create their own viewer under a default name. Viewers created this way are destroyed once detached from the DOM. However, viewers defined through a viewer attribute persist and can be re-attached to the DOM. Additionally, removing the DOM element of a named viewer will not eliminate the viewer’s rendered state. When a DOM element with the same viewer attribute is re-inserted into the DOM, the previously rendered canvas reappears.

To demonstrate, the subsequent playground showcases an unnamed <webvis-viewer> without a viewer attribute on the left, while a named <webvis-viewer> with a viewer attribute is displayed on the right. By clicking “Destroy” and “Create” on the left viewer, you can observe the brief time needed for the viewer to re-render. In contrast, the viewer on the right retains its cached state, avoiding re-rendering and allowing for a seamless transition.

In instances where you know that users will no longer need a previously used named viewer, the viewer should be removed explicitly using the ContextAPI.removeViewer function. This is not necessary for unnamed viewers as they are automatically cleaned up once they are detached from the DOM.

Working with a Frontend Framework

Frontend frameworks like Vue, React, and Angular use a technique called “Virtual DOM” to efficiently update the DOM of a web page. The Virtual DOM is a lightweight copy of the actual DOM tree that the framework maintains in memory. When the state of the application changes, the framework calculates the difference between the previous Virtual DOM and the updated one, and then only updates the necessary parts of the actual DOM. This process is called “diffing” or “reconciliation”.

In numerous instances, applications featuring multiple views and/or routes may also involve situations where webvis components are destroyed and later re-created. When using frontend frameworks in conjunction with webvis, you can take advantage of webvis’ internal caching of viewer state.

Consider the following example of a simple React application that switches between a tab containing 3D content and a tab providing additional information:

import React from 'react';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';

function WebvisTab() {
  // This is not the most performant way for illustration purposes:
  return <webvis-viewer></webvis-viewer>;
}

function InfoTab() {
  return <p>Additional information</p>;
}

function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/3d">3D View</Link>
            </li>
            <li>
              <Link to="/info">Additional Information</Link>
            </li>
          </ul>
        </nav>

        <Switch>
          <Route path="/3d" component={WebvisTab} />
          <Route path="/info" component={InfoTab} />
        </Switch>
      </div>
    </Router>
  );
}

This example employs a <webvis-viewer> without a viewer attribute, creating a default viewer. When a user switches to the second tab (/info), the reconciliation algorithm detects that <webvis-viewer> is no longer part of the DOM and can be destroyed.

However, this creates the issue of the viewer needing to re-render the space when the user returns to the 3D view. In many cases, this is undesirable and can be easily avoided by using a named viewer instead (e.g., <webvis-viewer viewer="3dview"></webvis-viewer>). Utilizing a named viewer prompts webvis to cache the rendered view, enabling it to be re-inserted into the DOM as demonstrated in the earlier playground example.

As soon as you can be sure that a viewer is no longer needed, it is best to explicitly remove it. To demonstrate this principle we use React one more time:

function WebvisComponent({show}) {
  const myContextName = "myContext";
  // Initialize a state getting a context object from webvis if it already exists
  const [webvisContext, setWebvisContext] = useState(webvis.getContext(myContextName));

  useEffect(() => {
    if (!webvisContext) {
      // If the context does not exist yet, it is asynchronously created
      webvis.requestContext(myContextName).then(context => {
        // Add a 3D model once the context is created
        const nodeId = context.add("urn:x-i3d:examples:catia:bike");
        context.setProperty(nodeId, webvis.Property.ENABLED, true);
        setWebvisContext(context);
      });
    } else {
      // React will call this effect again as soon as webvisContext is set, now we return the cleanup callback for unmounting
      return () => {
        const viewer = webvisContext.getViewer("3dview");
        webvisContext.removeViewer(viewer);
      };
    }
  }, [webvisContext]);

  return <div id="viewer-container">
      {(webvisContext && show) ? <webvis-viewer viewer="3dview" context="myContext"></webvis-viewer> : null}
  </div>;
}

In this example, a webvis context is acquired upon mounting. If it does not exist, it will be created asynchronously. The show property allows a consuming component to display or hide the viewer. During this process, the <webvis-viewer> is destroyed and re-created. However, since a named viewer is employed, the canvas responsible for rendering the 3D space will be reused, eliminating the need for re-rendering.

The viewer is explicitly removed by calling removeViewer only when the example WebvisComponent is ultimately unmounted. This approach enables granular management of your viewers’ lifecycles.

It is worth noting that, in your app, webvis context creation will occur at a more global location, ensuring that the same context is passed down into multiple components.

The same component can be defined in various frontend frameworks. When using webvis with Vue.js, we recommend against using the “keep-alive” feature. Instead, you should leverage our internal caching strategy for managing the rendered canvas state. webvis supports the attribute viewer="viewername" to re-attach the cached state in our web components, eliminating the need for the “keep-alive” feature.

In Vue.js the same component might look like this:

// Import Vue
import Vue from 'vue';

// Create a Vue component
Vue.component('WebvisComponent', {
  props: ['show'],
  data: function() {
    return {
      myContextName: 'myContext',
      webvisContext: webvis.getContext('myContext'),
    };
  },
  mounted: function() {
    if (!this.webvisContext) {
      webvis.requestContext(this.myContextName).then((context) => {
        const nodeId = context.add('urn:x-i3d:examples:catia:bike');
        context.setProperty(nodeId, webvis.Property.ENABLED, true);
        this.webvisContext = context;
      });
    }
  },
  beforeDestroy: function() {
    if (this.webvisContext) {
      const viewer = this.webvisContext.getViewer('3dview');
      this.webvisContext.removeViewer(viewer);
    }
  },
  template: `
    <div id="viewer-container">
      <webvis-viewer v-if="webvisContext && show" viewer="3dview" context="myContext"></webvis-viewer>
    </div>
  `,
});

In Angular the same component would be implemented like this:

// Import necessary Angular libraries and decorators
import { Component, OnInit, OnDestroy, Input } from '@angular/core';

// Define the component's class and use the @Component decorator to configure its metadata
@Component({
  selector: 'app-webvis',
  template: `
    <div id="viewer-container">
      <webvis-viewer *ngIf="webvisContext && show" viewer="3dview" context="myContext"></webvis-viewer>
    </div>
  `,
})
export class WebvisComponent implements OnInit, OnDestroy {
  @Input() show: boolean;
  myContextName = 'myContext';
  webvisContext: webvis.ContextAPI;

  constructor() {
    this.webvisContext = webvis.getContext(this.myContextName);
  }

  ngOnInit(): void {
    if (!this.webvisContext) {
      webvis.requestContext(this.myContextName).then((context) => {
        const nodeId = context.add('urn:x-i3d:examples:catia:bike');
        context.setProperty(nodeId, webvis.Property.ENABLED, true);
        this.webvisContext = context;
      });
    }
  }

  ngOnDestroy(): void {
    if (this.webvisContext) {
      const viewer = this.webvisContext.getViewer('3dview');
      this.webvisContext.removeViewer(viewer);
    }
  }
}

And finally since this was shown as a functional component in React, this would be the equivalent class component:

import React, { Component } from 'react';

class WebvisComponent extends Component {
  constructor(props) {
    super(props);
    this.myContextName = "myContext";
    this.state = {
      webvisContext: webvis.getContext(this.myContextName),
    };
  }

  componentDidMount() {
    if (!this.state.webvisContext) {
      webvis.requestContext(this.myContextName).then((context) => {
        const nodeId = context.add("urn:x-i3d:examples:catia:bike");
        context.setProperty(nodeId, webvis.Property.ENABLED, true);
        this.setState({ webvisContext: context });
      });
    }
  }

  componentWillUnmount() {
    if (this.state.webvisContext) {
      const viewer = this.state.webvisContext.getViewer("3dview");
      this.state.webvisContext.removeViewer(viewer);
    }
  }

  render() {
    const { show } = this.props;
    const { webvisContext } = this.state;
    return (
      <div id="viewer-container">
        {webvisContext && show ? (
          <webvis-viewer viewer="3dview" context="myContext"></webvis-viewer>
        ) : null}
      </div>
    );
  }
}

TypeScript considerations when using Frontend frameworks

In many frontend frameworks you have to declare types or add a hint for the templating engine in order to use web components. In React with TypeScript you would add a type declaration similar to the following to your typings in order to use the <webvis-viewer> component:

declare namespace JSX {
  interface IntrinsicElements {
    'webvis-viewer': {
      viewer?: string;
      context?: string;
    };
  }
}

In Angular, you can use CUSTOM_ELEMENTS_SCHEMA as part of your NgModule’s schemas. Please note that this will also disable template checks you might want to keep. It is best practice to keep the CUSTOM_ELEMENTS_SCHEMA setting as close to the webvis components as possible while other parts of your application should be part of a NgModule without the CUSTOM_ELEMENTS_SCHEMA declaration:

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class MyModule {}

Examples

For further examples, please see our GitHub organization where you can find more elaborate demonstration applications:

GITHUB: CODE EXAMPLESGITHUBCODE EXAMPLES