Перейти к основному содержанию

Improve Basic Viewer Extension

Около 3 мин

Improve Basic Viewer Extension

In this article, the viewer extension from the previous exampleopen in new window will be improved.

Registering an extension with parameters

In order for the extension to be embedded in the viewer, it must be registered. Extensions are usually registered after initializing the Render Manager.

In the previous example, the extension was registered without passing parameters:

renderManager.init("viewer");
...
renderManager.extMan.addExtension(HowtoBasicExtension)
...

In our demo application, this happens in the Viewer.vue file in the views folder as in the previous example:

[tangl-demo-playground]
  [src]
    ...
    [views]
      Viewer.vue
    ... 

Passing the hideToolbar property as a parameter and setting false by default :

...
  renderManager.extMan.addExtension(HowtoBasicExtension {hideToolbar: false})
...
   

The created argument is passed to the constructor and now you can control the state of the passed argument when registering an extension:

export class HowtoBasicExtension extends ExtensionBase {
 static getName = (): string => "howto-basic"; //contains the name of the extension
 isActive: boolean = false;

 constructor(viewerName: string, args: { hideToolbar: boolean }) {
 super(viewerName);
  ...
 }
...

Hiding sharedToolbar

sharedToolbar It is created using the hook that was make in the previous example:

...
sharedToolbarRender(container: HTMLElement) {
...
}

This panel can be hidden using the property Extension Base.hideToolbar, even if the hook exists.

Let's set the value of this field through the argument that we created above.

export class HowtoBasicExtension extends ExtensionBase {
 static getName = (): string => "howto-basic"; //contains the name of the extension
 isActive: boolean = false;
 
 this.hideToolbar = false; //define new field for toolbar visibility

 constructor(viewerName: string, args: { hideToolbar: boolean }) {
 super(viewerName);
 this.hideToolbar = args.hideToolbar; // set field from arguments 
 }
...

Add extension command system support

What is the extension command mode for?

Various extensions will take over the focus of the user's attention when performing any actions, excluding conflicts with other extensions.

The extension manager has the concept of an active command - the one that is currently selected. There can be only one active command, or none.

Using the example of the ruler and coordinates tools, it can be seen that there can only be one active command. These two tools will not work at the same time:

Set the active command by extension

Let's set the active command when clicking on the button on the panel. to do this, modify the click event handle:

sharedToolbarRender(container: HTMLElement) {
 const renderManager = this.getRenderManager();
 const button = document.createElement("button");
 button.innerHTML = "Click Me!";
 button.addEventListener("click", () => {
			this.isActive = !this.isActive;
			if (this.isActive) {
                //the current command extension for the renderManager is
                // set with the name of this object
				renderManager.extMan.setCurrentCommandExtension(this.getName())
                //the selection lock is set for the renderManager
				renderManager.setSelectionLock(true);
			} else {
                //release the selection lock for renderManager
				renderManager.setSelectionLock(false);
                //end the current command extension for renderManager
				renderManager.extMan.finishCurrentCommandExtension();
			}
		});
...

In the example above, the two main new features are setCurrentCommandExtension and finishCurrentCommandExtension. Using the first one, we install the extension command, and then, using the second one, we interrupt its operation by clicking.

The reaction of the extension to the change of the active command from the outside (by another extension)

To react to the change of the active command from the outside, we need to add the commandChanged hook.

In this hook we need to take actions to deactivate our extension correctly:

commandChanged() {
    const renderManager = this.getRenderManager();
    this.isActive = false;
    renderManager.setSelectionLock(false);
}

In these examples we also used the RenderManager.setSelectionLock function. This function is optional and serves only for additional convenience when clicking on model elements.

Creating additional HTML interface

In this example, a functionality will be added that colors the created HTML element in the same color as the model element is colored when you click on it.

In order to access the container and store the resulting color somewhere, we will create a variable colorDiv for convenience:

export class HowtoBasicExtension2 extends ExtensionBase {
...
 colorDiv = document.createElement("div");
...
 }
...

In order to create additional HTML interface elements, we need to add a uiRender hook, in which an element is added on top of the canvas. Element's styles are also added into this hook:

...
uiRender(container: HTMLElement) {
 this.colorDiv.innerHTML = "Hello!"
 this.colorDiv.style.display = "none"
 this.colorDiv.style.position = "relative"
 this.colorDiv.style.top = "80vh"
 this.colorDiv.style.margin = "15px"
 this.colorDiv.style.backgroundColor = "gray"
 this.colorDiv.style.color = "white"
 this.colorDiv.style.padding = "5px"

 container.appendChild(this.colorDiv);
}
...

We get the color that is randomly generated by clicking on the model element and color our HTML element in the same color. This happens in the onClick hook:

onClick(){
  ...
  this.colorDiv.innerHTML = randomColor.getHexString()
  this.colorDiv.style.backgroundColor = "#" + randomColor.getHexString()
  ...
}

At the end, we will add the display/hiding of this element only by clicking on the "Click Me!" button. The default element will be hidden until the "Click Me!" button is pressed:

uiRender(container: HTMLElement) {
  ... 
  this.colorDiv.style.display = "none"
  ...
}

sharedToolbarRender(container: HTMLElement) {
  ...   
  button.addEventListener("click", () => {
     this.isActive = !this.isActive;
     if (this.isActive) {
        ...
        this.colorDiv.style.display = "block"
     } else {
      ...
      this.colorDiv.style.display = "none"
     }
   });
  ...
}

commandChanged() {
  ...
  this.colorDiv.style.display = "none"
}

The finished example code looks like this:

import {ExtensionBase, ElementState, RenderEvents} from 'tangl-viewer';
import {Color} from "three";

export class HowtoBasicExtension2 extends ExtensionBase {
   static getName = (): string => "howto-basic-2"; //contains the name of the extension
   isActive: boolean = false;
  
   colorDiv = document.createElement("div");
  
   constructor(viewerName: string, args: { hideToolbar: boolean }) {
   super(viewerName);
  
   this.hideToolbar = args.hideToolbar;
   }
  
   uiRender(container: HTMLElement) {
     const div = document.createElement("div");
  
     this.colorDiv.innerHTML = "Hello!"
     this.colorDiv.style.display = "none"
     this.colorDiv.style.position = "relative"
     this.colorDiv.style.top = "80vh"
     this.colorDiv.style.margin = "15px"
     this.colorDiv.style.backgroundColor = "gray"
     this.colorDiv.style.color = "white"
     this.colorDiv.style.padding = "5px"
  
     container.appendChild(this.colorDiv);
   }
  
   sharedToolbarRender(container: HTMLElement) {
     const renderManager = this.getRenderManager();
     const button = document.createElement("button");
     button.innerHTML = "Click Me!";
     button.addEventListener("click", () => {
       this.isActive = !this.isActive;
       if (this.isActive) {
         renderManager.extMan.setCurrentCommandExtension(this.getName())
         renderManager.setSelectionLock(true);
         this.colorDiv.style.display = "block"
    
       } else {
         renderManager.setSelectionLock(false);
         renderManager.extMan.finishCurrentCommandExtension();
         this.colorDiv.style.display = "none"
       }
  
     });
  
     container.appendChild(button);
   }
  
   commandChanged() {
     const renderManager = this.getRenderManager();
     this.isActive = false;
     renderManager.setSelectionLock(false);
     this.colorDiv.style.display = "none"
   }
  
   onClick() {
     if (!this.isActive) return;
    
     const renderManager = this.getRenderManager();
     if (renderManager.hoveredElNum == -1 || !this.isActive) return;
     const randomColor = new Color(0xffffff).setHex(Math.random() * 0xffffff);
     renderManager.sceneManager.tools.setElementsColor([renderManager.hoveredElNum], randomColor.getHex())
     renderManager.sceneManager.tools.setElementsState([renderManager.hoveredElNum], ElementState.Normal)
     renderManager.requestUpdate();
    
     this.colorDiv.innerHTML = randomColor.getHexString()
     this.colorDiv.style.backgroundColor = "#" + randomColor.getHexString()
   }
  
   added(): void {
     const renderManager = this.getRenderManager();
     renderManager?.addEventListener(RenderEvents.Click, () => this.onClick())
   }
  
   deleted(): void {
   }
}