10 Mar 2021

Add Viewer extensions to Design Automation for Inventor sample

I have a few articles on the sample application written by our team to help people create their own Configurator 360-like solutions.

One question that came up recently was about loading an extension into the Viewer in it - e.g. adding this object transformation tool to it.
So let's go through the steps.

The part where the Forge Viewer is used can be found here, in forgeView.js:

forgeView.js

The TransformationExtension I provided the link to above has 3 files: main.js, main.css and transformicon.png
To avoid confusion later in the poject I renamed the files to transformtool.js and transformtool.css and placed them in the WebApplication >> ClientApp >> src >> components folder - see picture.

I placed transformicon.png in the WebApplication >> ClientApp >> public folder and changed the content of transformtool.css accordingly to reflect the new path of the image:

.transformextensionicon {
    background-image: url(/transformicon.png); 
    background-size: 24px;
    background-repeat: no-repeat;
    background-position: center;
}

In order to use the extension we need to import it in forgeView.js (which is only possible at the top of the file), but in its current state transformtool.js would try to register itself with the Viewer as soon as it's loaded. That is too early, since the Viewer's source file will only be loaded later, on-demand. So we'd get errors like these:

Failed to compile

I placed the original content of the transformtool.js file inside a function and exported that, plus import-ed the css file. Also needed to declare AutodeskAutodeskNamespace and THREE, which should be available under window once the viewer is loaded.

import './transformtool.css';

export default function RegisterTransformTool () {

    var Autodesk = window.Autodesk;
    var AutodeskNamespace = window.AutodeskNamespace;
    var THREE = window.THREE;

    AutodeskNamespace("Autodesk.ADN.Viewing.Extension");

    Autodesk.ADN.Viewing.Extension.TransformTool = function (viewer, options) {

        ///////////////////////////////////////////////////////////////////////////
        //
        //
        ///////////////////////////////////////////////////////////////////////////
        function TransformTool() {

            var _hitPoint = null;

            var _isDragging = false;

            var _transformMesh = null;

            var _modifiedFragIdMap = {};

            var _selectedFragProxyMap = {};

            var _transformControlTx = null;

            ///////////////////////////////////////////////////////////////////////////
            // Creates a dummy mesh to attach control to
            //
            ///////////////////////////////////////////////////////////////////////////
            function createTransformMesh() {

                var material = new THREE.MeshPhongMaterial(
                    { color: 0xff0000 });

                viewer.impl.matman().addMaterial(
                    guid(),
                    material,
                    true);

                var sphere = new THREE.Mesh(
                    new THREE.SphereGeometry(0.0001, 5),
                    material);

                sphere.position.set(0, 0, 0);

                return sphere;
            }

            ///////////////////////////////////////////////////////////////////////////
            // on translation change
            //
            ///////////////////////////////////////////////////////////////////////////
            function onTxChange() {

                for (var fragId in _selectedFragProxyMap) {

                    var fragProxy = _selectedFragProxyMap[fragId];

                    var position = new THREE.Vector3(
                        _transformMesh.position.x - fragProxy.offset.x,
                        _transformMesh.position.y - fragProxy.offset.y,
                        _transformMesh.position.z - fragProxy.offset.z);

                    fragProxy.position = position;

                    fragProxy.updateAnimTransform();
                }

                viewer.impl.sceneUpdated(true);
            }

            ///////////////////////////////////////////////////////////////////////////
            // on camera changed
            //
            ///////////////////////////////////////////////////////////////////////////
            function onCameraChanged() {

                _transformControlTx.update();
            }

            ///////////////////////////////////////////////////////////////////////////
            // item selected callback
            //
            ///////////////////////////////////////////////////////////////////////////
            function onItemSelected(event) {

                _selectedFragProxyMap = {};

                //component unselected

                if (!event.fragIdsArray.length) {

                    _hitPoint = null;

                    _transformControlTx.visible = false;

                    _transformControlTx.removeEventListener(
                        'change', onTxChange);

                    viewer.removeEventListener(
                        Autodesk.Viewing.CAMERA_CHANGE_EVENT,
                        onCameraChanged);

                    return;
                }


                if (_hitPoint) {

                    _transformControlTx.visible = true;

                    _transformControlTx.setPosition(_hitPoint);

                    _transformControlTx.addEventListener(
                        'change', onTxChange);

                    viewer.addEventListener(
                        Autodesk.Viewing.CAMERA_CHANGE_EVENT,
                        onCameraChanged);

                    event.fragIdsArray.forEach(function (fragId) {

                        var fragProxy = viewer.impl.getFragmentProxy(
                            viewer.model,
                            fragId);

                        fragProxy.getAnimTransform();

                        var offset = {

                            x: _hitPoint.x - fragProxy.position.x,
                            y: _hitPoint.y - fragProxy.position.y,
                            z: _hitPoint.z - fragProxy.position.z
                        };

                        fragProxy.offset = offset;

                        _selectedFragProxyMap[fragId] = fragProxy;

                        _modifiedFragIdMap[fragId] = {};
                    });

                    _hitPoint = null;
                }
                else {

                    _transformControlTx.visible = false;
                }
            }

            ///////////////////////////////////////////////////////////////////////////
            // normalize screen coordinates
            //
            ///////////////////////////////////////////////////////////////////////////
            function normalize(screenPoint) {

                var viewport = viewer.navigation.getScreenViewport();

                var n = {
                    x: (screenPoint.x - viewport.left) / viewport.width,
                    y: (screenPoint.y - viewport.top) / viewport.height
                };

                return n;
            }

            ///////////////////////////////////////////////////////////////////////////
            // get 3d hit point on mesh
            //
            ///////////////////////////////////////////////////////////////////////////
            function getHitPoint(event) {

                var screenPoint = {
                    x: event.clientX,
                    y: event.clientY
                };

                var n = normalize(screenPoint);

                var hitPoint = viewer.utilities.getHitPoint(n.x, n.y);

                return hitPoint;
            }

            ///////////////////////////////////////////////////////////////////////////
            // returns all transformed meshes
            //
            ///////////////////////////////////////////////////////////////////////////
            this.getTransformMap = function () {

                var transformMap = {};

                for (var fragId in _modifiedFragIdMap) {

                    var fragProxy = viewer.impl.getFragmentProxy(
                        viewer.model,
                        fragId);

                    fragProxy.getAnimTransform();

                    transformMap[fragId] = {
                        position: fragProxy.position
                    };

                    fragProxy = null;
                }

                return transformMap;
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.getNames = function () {

                return ['Dotty.Viewing.Tool.TransformTool'];
            };

            this.getName = function () {

                return 'Dotty.Viewing.Tool.TransformTool';
            };

            ///////////////////////////////////////////////////////////////////////////
            // activates tool
            //
            ///////////////////////////////////////////////////////////////////////////
            this.activate = function () {

                viewer.select([]);

                var bbox = viewer.model.getBoundingBox();

                viewer.impl.createOverlayScene(
                    'Dotty.Viewing.Tool.TransformTool');

                _transformControlTx = new THREE.TransformControls(
                    viewer.impl.camera,
                    viewer.impl.canvas,
                    "translate");

                _transformControlTx.setSize(
                    bbox.getBoundingSphere().radius * 5);

                _transformControlTx.visible = false;

                viewer.impl.addOverlay(
                    'Dotty.Viewing.Tool.TransformTool',
                    _transformControlTx);

                _transformMesh = createTransformMesh();

                _transformControlTx.attach(_transformMesh);

                viewer.addEventListener(
                    Autodesk.Viewing.SELECTION_CHANGED_EVENT,
                    onItemSelected);
            };

            ///////////////////////////////////////////////////////////////////////////
            // deactivate tool
            //
            ///////////////////////////////////////////////////////////////////////////
            this.deactivate = function () {

                viewer.impl.removeOverlay(
                    'Dotty.Viewing.Tool.TransformTool',
                    _transformControlTx);

                _transformControlTx.removeEventListener(
                    'change',
                    onTxChange);

                _transformControlTx = null;

                viewer.impl.removeOverlayScene(
                    'Dotty.Viewing.Tool.TransformTool');

                viewer.removeEventListener(
                    Autodesk.Viewing.CAMERA_CHANGE_EVENT,
                    onCameraChanged);

                viewer.removeEventListener(
                    Autodesk.Viewing.SELECTION_CHANGED_EVENT,
                    onItemSelected);
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.update = function (t) {

                return false;
            };

            this.handleSingleClick = function (event, button) {


                return false;
            };

            this.handleDoubleClick = function (event, button) {

                return false;
            };


            this.handleSingleTap = function (event) {

                return false;
            };


            this.handleDoubleTap = function (event) {

                return false;
            };

            this.handleKeyDown = function (event, keyCode) {

                return false;
            };

            this.handleKeyUp = function (event, keyCode) {

                return false;
            };

            this.handleWheelInput = function (delta) {

                return false;
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.handleButtonDown = function (event, button) {

                _hitPoint = getHitPoint(event);

                _isDragging = true;

                if (_transformControlTx.onPointerDown(event))
                    return true;

                //return _transRotControl.onPointerDown(event);
                return false;
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.handleButtonUp = function (event, button) {

                _isDragging = false;

                if (_transformControlTx.onPointerUp(event))
                    return true;

                //return _transRotControl.onPointerUp(event);
                return false;
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.handleMouseMove = function (event) {

                if (_isDragging) {

                    if (_transformControlTx.onPointerMove(event)) {

                        return true;
                    }

                    return false;
                }

                if (_transformControlTx.onPointerHover(event))
                    return true;

                //return _transRotControl.onPointerHover(event);
                return false;
            };

            ///////////////////////////////////////////////////////////////////////////
            //
            //
            ///////////////////////////////////////////////////////////////////////////
            this.handleGesture = function (event) {

                return false;
            };

            this.handleBlur = function (event) {

                return false;
            };

            this.handleResize = function () {

            };
        }

        Autodesk.Viewing.Extension.call(this, viewer, options);

        var _self = this;

        _self.tool = null;

        _self.toolactivated = false;

        ///////////////////////////////////////////////////////
        // extension load callback
        //
        ///////////////////////////////////////////////////////
        _self.load = function () {

            console.log('Autodesk.ADN.Viewing.Extension.TransformTool loaded');

            return true;
        };

        _self.onToolbarCreated = function () {
            // Create a new toolbar group if it doesn't exist
            this._group = this.viewer.toolbar.getControl('transformExtensionsToolbar');
            if (!this._group) {
                this._group = new Autodesk.Viewing.UI.ControlGroup('transformExtensionsToolbar');
                this.viewer.toolbar.addControl(this._group);
            }

            // Add a new button to the toolbar group
            this._button = new Autodesk.Viewing.UI.Button('transformExtensionButton');
            this._button.onClick = (ev) => {
                // Execute an action here
                if (!_self.toolactivated) {
                    _self.initialize();
                    _self.toolactivated = true;
                } else {
                    viewer.toolController.deactivateTool(_self.tool.getName());
                    _self.toolactivated = false;
                }
            };
            this._button.setToolTip('Transform Extension');
            this._button.addClass('transformextensionicon');
            this._group.addControl(this._button);
        };

        _self.initialize = function () {
            _self.tool = new TransformTool();

            viewer.toolController.registerTool(_self.tool);

            if (this.viewer.model.getInstanceTree()) {
                _self.customize();
            } else {
                this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, _self.customize());
            }
        };

        _self.customize = function () {
            viewer.toolController.activateTool(_self.tool.getName());
        };

        ///////////////////////////////////////////////////////
        // extension unload callback
        //
        ///////////////////////////////////////////////////////
        _self.unload = function () {

            if (_self.tool) viewer.toolController.deactivateTool(_self.tool.getName());
            // Clean our UI elements if we added any
            if (this._group) {
                this._group.removeControl(this._button);
                if (this._group.getNumberOfControls() === 0) {
                    this.viewer.toolbar.removeControl(this._group);
                }
            }
            console.log('Autodesk.ADN.Viewing.Extension.TransformTool unloaded');

            return true;
        };

        ///////////////////////////////////////////////////////
        // new random guid
        //
        ///////////////////////////////////////////////////////
        function guid() {

            var d = new Date().getTime();

            var guid = 'xxxx-xxxx-xxxx-xxxx-xxxx'.replace(
                /[xy]/g,
                function (c) {
                    var r = (d + Math.random() * 16) % 16 | 0;
                    d = Math.floor(d / 16);
                    return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
                });

            return guid;
        };
    };

    Autodesk.ADN.Viewing.Extension.TransformTool.prototype =
        Object.create(Autodesk.Viewing.Extension.prototype);

    Autodesk.ADN.Viewing.Extension.TransformTool.prototype.constructor =
        Autodesk.ADN.Viewing.Extension.TransformTool;

    Autodesk.Viewing.theExtensionManager.registerExtension(
        'TransformationExtension',
        Autodesk.ADN.Viewing.Extension.TransformTool);
}

Now I can import it inside forgeView.js and only call RegisterTransformTool() when it's needed. After that I can also make the Viewer load the registered "TransformationExtension" by adding it inside the "extensions" option of its constructor:

import './forgeView.css';
import Message from './message';
import repo from '../Repository';
import { viewerCss, viewerJs } from './shared';
import registerTransformTool from './transformtool';

let Autodesk = null;

export class ForgeView extends Component {

    constructor(props){
      super(props);

      this.viewerDiv = React.createRef();
      this.viewer = null;
    }

    handleScriptLoad() {

        const options = repo.hasAccessToken() ?
                            { accessToken: repo.getAccessToken() } :
                            { env: 'Local' };

        Autodesk = window.Autodesk;

        registerTransformTool();

        const container = this.viewerDiv.current;
        this.viewer = new Autodesk.Viewing.GuiViewer3D(container, { extensions: ["TransformationExtension"]});

        // uncomment this for Viewer debugging
        //this.viewer.debugEvents(true);

        Autodesk.Viewing.Initializer(options, this.handleViewerInit.bind(this));
    }

   // etc

And we're ready.
Now when running our updated app, the TransformationExtension's button will show up on the toolbar and can be used - see image on top.

Related Article