==========
Instancing
==========

In this part, we will walk through building our own custom "instance operator". This will be a custom operator that copies geometry.

A common additive manufacturing use case may be to replicate parts on the plane for printing multiple copies. To do this visually, we can instance selected nodes and allow the user to place them on the plane with the mouse.

The *InstanceOperator.ts* file has been provided with the code for our ``InstanceOperator`` class. Our custom operator class will implement the ``Operators.OperatorBase`` interface within |HCNOW| to our specific needs. We will create member variables for where the user clicked, the current nodes we want to instance, and the Z position of the inserted nodes.

.. code-block:: ts

    constructor(hwv: WebViewer) {
        super(hwv, hwv.view);
        this._mainViewer = hwv;
        this._ptDown = Point2.zero();
        this._currentNodes = [];
        this._nodePosZ = 0;
    }

We want the user to be able to select a node, click on the plane, and insert the node where the user clicked. For this tutorial, we will use the ``onMouseDown`` and ``onMouseUp`` event callbacks in the ``Operator`` class. We use both to ensure that the user is clicking and not dragging the mouse. If the mouse down position is equal to the mouse up position, we can insert the node into the scene.

.. code-block:: ts

    onMouseDown(event: MouseInputEvent) {
        this._ptDown.assign(event.getPosition());
    }

    onMouseUp(event: MouseInputEvent) {
        const position = event.getPosition();
        
        if (position.equals(this._ptDown)) {
          const config = new PickConfig(SelectionMask.Face);
          this._mainViewer.view.pickFromPoint(position, config)
            .then((selectionItem) => {
              if (
                selectionItem.isEntitySelection() &&
                this._mainViewer.model.getNodeName(selectionItem.getNodeId()) ===
                  "printingPlane"
              ) {
                this._insertGeometry(selectionItem.getPosition());
              } 
              else {
                alert("Please select a point on the printing plane.");
              }
            });
        }
    }
    
This function will ensure that the user has picked a point on the ``PrintingPlane`` surface, and not elsewhere in world space. If the user did select the ``PrintingPlane``, we continue to insert the geometry.

The ``InstanceOperator`` class will need another function called ``_insertGeometry`` that uses the mouse click event position on the selection item to dictate where the geometry should be inserted.

To insert the geometry, we will follow the same general steps outlined in the :doc:`Building A Basic Application </tutorials/basic-app/introduction>`. tutorial. We will obtain the mesh IDs, then use those mesh IDs to set mesh instance data and create the mesh instance. 
    
.. code-block:: ts

    async _insertGeometry(position: Point2) {
        const meshIds = await this._mainViewer.model.getMeshIds(this._currentNodes);

        meshIds.forEach(async (meshId, index) => {
            const color = await this._mainViewer.model.getNodeEffectiveFaceColor(this._currentNodes[index], 0);
            
            let netMatrix = this._mainViewer.model.getNodeNetMatrix(
                this._currentNodes[index]
            );
            
            netMatrix.m[12] = position.x; // Add translation to the X-axis.
            netMatrix.m[13] = position.y; // Add translation to the Y-axis.
            netMatrix.m[14] = this._nodePosZ;

            return this._mainViewer.model.createMeshInstance(
                new MeshInstanceData(
                meshId,
                netMatrix,
                "Node " + this._currentNodes + " Instance",
                color,
                Color.black())
                );
        });
    }    
   
Lets write our last member function, setNodesToInstance. This function gathers the selection node IDs, then recursively gathers and dependent leaf nodes we may need to instance alongside it.
 
.. code-block:: ts
 
    setNodesToInstance(nodeIds: number[]) {
        this._currentNodes = this._gatherChildLeafNodes(nodeIds);
        this._mainViewer.model.getNodesBounding(this._currentNodes).then((box) => {
          this._nodePosZ = box.max.z - box.min.z;
        });
    }    
    
With this, we have the application logic to instance nodes in the scene. But we still need to instantiate the operator itself and connect it to the UI button. Instancing is handled below:

.. code-block:: ts

    const instanceOp = new InstanceOperator(this.hwv);
    const handle = this.hwv.registerCustomOperator(instanceOp);
    const instanceBtn = document.getElementById("instance-button")!;
    
Now, let's connect it to the UI:

.. code-block:: ts    
    
    instanceBtn.onclick = () => {
    
      // Use the button to push and pop the operator from the operator stack
      if (instanceBtn.innerHTML.includes("Instance Part")) {
        // Gather nodes to be instanced
        let nodeIds: number[] = [];
        const selectionItems = this.hwv.selectionManager.getResults();
        selectionItems.map((selection) => {
          nodeIds.push(selection.getNodeId());
        });
        
        if (selectionItems.length !== 0) {
          instanceBtn.innerHTML = "Disable Instancing";
          instanceOp.setNodesToInstance(nodeIds);
          // Remove the selection operator from the stack while instancing
          this.hwv.view.operatorManager.push(handle);
          this.hwv.view.operatorManager.remove(OperatorId.Select);
          this.hwv.selectionManager.setHighlightNodeSelection(false);
          this.hwv.selectionManager.setHighlightFaceElementSelection(false);
          this.hwv.selectionManager.setPickTolerance(0);
        } 
        else {
          alert(
            "Try Again. Please first select nodes from the model to instance!"
          );
        }
      } 
      else {
        instanceBtn.innerHTML = "Instance Part";
        // Remove the instance operator from the stack and reenable selection and highlighting
        this.hwv.selectionManager.clear();
        this.hwv.view.operatorManager.remove(handle);
        this.hwv.view.operatorManager.push(OperatorId.Select);
        this.hwv.selectionManager.setHighlightNodeSelection(true);
        this.hwv.selectionManager.setHighlightFaceElementSelection(true);
      }
    };
