Search code examples
javascriptautodesk-forgeautodesk-viewer

How to load extensions for the Forge Viewer (without a viewerApp)


I'm trying to developp a Forge Autodesk Viewer for a webapp, this tutorial. I have an issue while trying to load extensions, indeed they never load on the viewer.

I've already developped the viewer of the tutorial, and the extensions worked correctly. The main difference between my viewer and the tutorial's viewer is the use of a viewerApp in the tutorial while I had to use directly a GUIViewer3D (For the aggregation of several models).

I've already tried to load the viewer and the extensions in a different order, but it didn't change worked either. I assumed the code of the extension is correct, since it works in the tutorial, but I'm not sure about how I linked it to my viewer.

The code to load the viewer :

Autodesk.Viewing.Initializer(options, function onInitialized() {

    // Initialisation du Viewer
    var viewerDiv = document.getElementById('MyViewerDiv');
    var config = {
        extensions: ['DockingPanelExtension']
    };
    viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv, config);
    viewer.initialize();
});

The code of the index

<head>
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
    <meta charset="utf-8">

    <!-- The Viewer CSS -->
    <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/style.min.css"
        type="text/css">

    <!-- Developer CSS -->
    <link rel="stylesheet" href="/static/style.css" type="text/css">

    <!-- Common packages: jQuery, Bootstrap, jsTree -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js"></script>
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default/style.min.css" />

</head>

<body>

    <!-- Fixed navbar by Bootstrap: https://getbootstrap.com/examples/navbar-fixed-top/ -->
    <nav class="navbar navbar-default navbar-fixed-top">
        <div class="container-fluid">
            <ul class="nav navbar-nav left">
                <li>
                    <a href="http://developer.autodesk.com" target="_blank">
                        <img alt="IM-Pact" src="static/img/IMPact.png"
                            height="20">
                    </a>
                </li>
                <li>
                    <button type="button" class="btn btn-default navbar-btn" onClick="callNew()">Add next model</button>
                </li>
            </ul>
        </div>
    </nav>
    <!-- End of navbar -->

    <div class="container-fluid fill">
        <div class="row fill">
            <div class="col-sm-4 fill">
                <div class="panel panel-default fill">
                    <div class="panel-heading" data-toggle="tooltip">
                        Buckets &amp; Objects
                        <span id="refreshBuckets" class="glyphicon glyphicon-refresh" style="cursor: pointer"></span>
                        <button class="btn btn-xs btn-info" style="float: right" id="showFormCreateBucket"
                            data-toggle="modal" data-target="#createBucketModal">
                            <span class="glyphicon glyphicon-folder-close"></span> New bucket
                        </button>
                    </div>
                    <div id="appBuckets">
                        tree here
                    </div>
                </div>
            </div>
            <div class="col-sm-8 fill">
                <div id="MyViewerDiv"></div>
            </div>
        </div>
    </div>
    <form id="uploadFile" method='post' enctype="multipart/form-data">
        <input id="hiddenUploadField" type="file" name="theFile" style="visibility:hidden" />
    </form>
    <!-- Modal Create Bucket -->
    <div class="modal fade" id="createBucketModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Cancel">
                        <span aria-hidden="true">&times;</span>
                    </button>
                    <h4 class="modal-title" id="myModalLabel">Create new bucket</h4>
                </div>
                <div class="modal-body">
                    <input type="text" id="newBucketKey" class="form-control"> For demonstration purposes, objects
                    (files) are
                    NOT automatically translated. After you upload, right click on
                    the object and select "Translate". Bucket keys must be of the form [-_.a-z0-9]{3,128}
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                    <button type="button" class="btn btn-primary" id="createNewBucket">Go ahead, create the
                        bucket</button>
                </div>
            </div>
        </div>
    </div>

    <!-- <button id="MyNextButton" onClick="callNext()">Next!</button> -->

    <!-- The Viewer JS -->
    <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.min.js?v=v6.6"></script>

    <!-- Developer JS -->
    <script src="static/js/docLoad.js"></script>
    <script src="static/js/modelLoad.js"></script>
    <script src="static/js/extensions/dockingpannelextension.js"></script>
    <script src="static/js/viewer.js"></script>
    <script src="static/js/tree.js"></script>
</body>

The code of the extension

// *******************************************
// Model Summary Extension
// *******************************************

var propsToList = [];

function addToList(item) {
    if (propsToList.includes(item)) {
        var index = propsToList.indexOf(item);
        propsToList.splice(index, 1);
    } else {
        propsToList.push(item)
    }
    console.log(propsToList)
}

function ModelSummaryExtension(viewer, options) {
    Autodesk.Viewing.Extension.call(this, viewer, options);
    this.panel = null; // create the panel variable
}

ModelSummaryExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
ModelSummaryExtension.prototype.constructor = ModelSummaryExtension;

ModelSummaryExtension.prototype.load = function () {
    if (this.viewer.toolbar) {
        // Toolbar is already available, create the UI
        this.createUI();
    } else {
        // Toolbar hasn't been created yet, wait until we get notification of its creation
        this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
        this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
    }
    return true;
};

ModelSummaryExtension.prototype.onToolbarCreated = function () {
    this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
    this.onToolbarCreatedBinded = null;
    this.createUI();
};

ModelSummaryExtension.prototype.createUI = function () {
    var _this = this;

    // prepare to execute the button action
    var modelSummaryToolbarButton = new Autodesk.Viewing.UI.Button('runModelSummaryCode');
    modelSummaryToolbarButton.onClick = function (e) {

        // check if the panel is created or not
        if (_this.panel == null) {
            _this.panel = new ModelSummaryPanel(_this.viewer, _this.viewer.container, 'modelSummaryPanel', 'Model Summary');
        }
        // show/hide docking panel
        _this.panel.setVisible(!_this.panel.isVisible());

        // if panel is NOT visible, exit the function
        if (!_this.panel.isVisible()) return;
        // ok, it's visible, let's get the summary!

        // first, the Viewer contains all elements on the model, including
        // categories (e.g. families or part definition), so we need to enumerate
        // the leaf nodes, meaning actual instances of the model. The following
        // getAllLeafComponents function is defined at the bottom
        _this.getAllLeafComponents(function (dbIds) {

            // now for leaf components, let's get some properties
            // and count occurrences of each value.

            // get only the properties we need for the leaf dbIds
            _this.viewer.model.getBulkProperties(dbIds, propsToList, function (dbIdsProps) {

                // iterate through the elements we found
                dbIdsProps.forEach(function (item) {

                    // and iterate through each property
                    item.properties.forEach(function (itemProp) {

                        // now use the propsToList to store the count as a subarray
                        if (propsToList[itemProp.displayName] === undefined)
                            propsToList[itemProp.displayName] = {};

                        // now start counting: if first time finding it, set as 1, else +1
                        if (propsToList[itemProp.displayName][itemProp.displayValue] === undefined)
                            propsToList[itemProp.displayName][itemProp.displayValue] = 1;
                        else
                            propsToList[itemProp.displayName][itemProp.displayValue] += 1;
                    });
                });

                // now ready to show!
                // the Viewer PropertyPanel has the .addProperty that receives the name, value
                // and category, that simple! So just iterate through the list and add them
                propsToList.forEach(function (propName) {
                    if (propsToList[propName] === undefined) return;
                    Object.keys(propsToList[propName]).forEach(function (propValue) {
                        _this.panel.addProperty(
                            /*name*/
                            propValue,
                            /*value*/
                            propsToList[propName][propValue],
                            /*category*/
                            propName);
                    });
                });
            })
        })

    };
    // modelSummaryToolbarButton CSS class should be defined on your .css file
    // you may include icons, below is a sample class:
    modelSummaryToolbarButton.addClass('modelSummaryToolbarButton');
    modelSummaryToolbarButton.setToolTip('Model Summary');

    // SubToolbar
    this.subToolbar = (this.viewer.toolbar.getControl("MyAppToolbar") ?
        this.viewer.toolbar.getControl("MyAppToolbar") :
        new Autodesk.Viewing.UI.ControlGroup('MyAppToolbar'));
    this.subToolbar.addControl(modelSummaryToolbarButton);

    this.viewer.toolbar.addControl(this.subToolbar);
};

ModelSummaryExtension.prototype.unload = function () {
    this.viewer.toolbar.removeControl(this.subToolbar);
    return true;
};

ModelSummaryExtension.prototype.getAllLeafComponents = function (callback) {
    var cbCount = 0; // count pending callbacks
    var components = []; // store the results
    var tree; // the instance tree

    function getLeafComponentsRec(parent) {
        cbCount++;
        if (tree.getChildCount(parent) != 0) {
            tree.enumNodeChildren(parent, function (children) {
                getLeafComponentsRec(children);
            }, false);
        } else {
            components.push(parent);
        }
        if (--cbCount == 0) callback(components);
    }
    this.viewer.getObjectTree(function (objectTree) {
        tree = objectTree;
        var allLeafComponents = getLeafComponentsRec(tree.getRootId());
    });
};

// *******************************************
// Model Summary Panel
// *******************************************
function ModelSummaryPanel(viewer, container, id, title, options) {
    this.viewer = viewer;
    Autodesk.Viewing.UI.PropertyPanel.call(this, container, id, title, options);
}
ModelSummaryPanel.prototype = Object.create(Autodesk.Viewing.UI.PropertyPanel.prototype);
ModelSummaryPanel.prototype.constructor = ModelSummaryPanel;

Autodesk.Viewing.theExtensionManager.registerExtension('ModelSummaryExtension', ModelSummaryExtension);

Thanks in advance !


Solution

  • In the extension JavaScript file, you're registering the extension under the name ModelSummaryExtension, but in the viewer initialization code you're passing the config object with extensions: ['DockingPanelExtension']. That's likely why the extension isn't loaded. Try initializing the GuiViewer3D class with the following config instead:

    let config = {
        extensions: ['ModelSummaryExtension']
    };
    

    EDIT (after the extension naming has been fixed):

    When initializing the GuiViewer3D, call its start() method instead of initialize(). It will internally call initialize() (for initializing internal structures, event handlers, etc.), setUp(); (for configuring the viewer based on your config object), and finally it will call loadModel() if there's a URN or a filepath argument passed to the function.