============
Auto-arrange
============

Perhaps you would like to give the capability to arrange each leaf node of your model tree in an orderly manner on the printing plane. You could have the user interact with your handle operator to drag things around and position them, but a easier way would be to use the model information to automate the placement of nodes. Review the ``Application`` class and look for member functions ``arrangeOnPlane`` and ``_gatherLeavesAndClearMats``. This is an example of the result of the auto-arrange operation we'll be discussing:

.. image:: images/additive-manufacturing-auto-arrange.png

The first thing we'll do to make this happen is we'll hook up to the button with the application logic:

.. code-block:: ts

    document.getElementById("arrange-button")!.onclick = () =>
        this.arrangeOnPlane(this.printingPlane.getDimensions().planeSize);

Next, we'll start our calculations. It is important to remember that the model tree nodes are affected by their parents, so to avoid any odd behavior from the parents, you should reset their transforms back to the identity matrix. Starting with the absolute root node of the model, we will traverse the tree recursively, storing the nodes without children (leaves) in an array, and resetting the parent objects back to their identity matrix. 

The recursive tree traversal will be abstracted into the ``_gatherLeavesAndClearMats`` function.

.. code-block:: ts

    async _gatherLeavesAndClearMats(node: number): Promise<number[]> {
        const result: number[] = [];

        const children = this.hwv.model.getNodeChildren(node);
        if (children.length == 0) {
            result.push(node);
        }

        const ident = new Matrix();
        const promiseArr: Promise<void>[] = [];
        
        for (let i = 0; i < children.length; i++) {
            promiseArr.push(this.hwv.model.setNodeMatrix(children[i], ident, false));
            const leafs = await this._gatherLeavesAndClearMats(children[i]);
            result.push(...leafs);
        }

        await Promise.all(promiseArr);

        return result;
    }

.. admonition:: Note

    Many of the |HCNOW| functions return promises, so it is good to wait for all these promises to resolve before making the next |HCNOW| call. To do this, you can store the returned promises in an array and use ``Promise.all()`` to return a single promise that resolves when all provided promises have resolved.

Note that we are not overwriting the nodes default matrix, since we are providing ``false`` for the set default argument in our ``Communicator.Model.setNodeMatrix`` function. With all the parent nodes at identity, the leaf nodes can be arranged without any unexpected net matrix calculations.        
    
.. code-block:: ts

    async arrangeOnPlane(boundarySize: number) {
        let mainViewer = this.hwv;
        let rootNode = mainViewer.model.getAbsoluteRootNode();
        let ident = new Matrix();
        ident.loadIdentity();

        // Set the root node of the model tree to its identity matrix
        // and recursively do the same for all children
        await mainViewer.model.setNodeMatrix(rootNode, ident, false);
        const leafArray = await this._gatherLeavesAndClearMats(rootNode);

        // Once all nodes have been reset, we can get the node boundings
        const boundingBoxes = await Promise.all(
          leafArray.map(
            (current): Promise<Box> => mainViewer.model.getNodesBounding([current])
          )
        );
        
        // ...
        
With all the nodes matrices reset we can begin to gather the node bounding box information, which will be used to arrange the nodes. We can query the bounding box information by using the ``Communicator.Model.getNodesBounding`` function. The ``getNodesBounding`` function takes an array of node IDs as input and returns a promise with a resulting value of type ``Communicator.Box``. Again, it is good to wait for all these promises to resolve before continuing. Once the bounding boxes have been obtained, we can use that information to gather an idea of the size and spacing needed between nodes.

We will start by looping through each nodes X and Y bounding box extent to determine the largest space in the X and Y direction. This will provide us numbers to define the amount of offset we need between elements in the arrangement. Next, we can define the area of the plane that we want the objects to be arranged to. You may set this as you wish, but I will be using 70% of the plane surface. The idea is that we will query each bounding box space, increment our starting X, Y position (we will start in the [-X,-Y] quadrant) by the spacing amount (plus some padding), and update the X and Y positions for the next part. Once we have the X and Y positions, we can set the node matrix. Again, since this is a returned promise, we will store all the returned promises in an array and return one promise using ``Promise.all()`` to finally resolve the function.

.. code-block:: ts

        // Once all node bounding have been gathered, start arranging
        let partSpacingX = 0;
        let partSpacingY = 0;
        
        for (let bb of boundingBoxes) {
            if (bb.extents().x > partSpacingX) {
                partSpacingX = bb.extents().x;
            }
            if (bb.extents().y > partSpacingY) {
                partSpacingY = bb.extents().y;
            }
        }

        let setNewMatPromises = [];
        let extent = boundarySize * 0.7;
        let x = -extent;
        let y = -extent;

        for (let i = 0; i < boundingBoxes.length; i++) {
            let m = new Matrix();
            m.loadIdentity();
            let bb = boundingBoxes[i];
            let c = bb.center();
            m.m[12] = x + bb.extents().x;
            m.m[13] = y - c.y;
            m.m[14] = -bb.min.z;
            
            setNewMatPromises.push(
                mainViewer.model.setNodeMatrix(leafArray[i], m, false)
            );
            
            x += bb.extents().x + partSpacingX;
            if (x > extent) {
                x = -extent;
                y += partSpacingY * 1.5;
            }
        }

        return Promise.all(setNewMatPromises);
    }
