简体   繁体   中英

How to create a Multi-Model Loader and Viewer in Forge

I am trying to build a Forge Multi-Model viewer that takes/loads multiple models from a bucket and displays them in the Viewer, using Nodejs.

I worked with the tutorial for loading views and am looking for a workaround that loads all models in a bucket to the Forge Viewer.

If I understand it correctly, you want to add the multiple model support on the top of view models tutorial, right?

To do so, we need to adjust the ForgeViewer.js, ForgeTree.js and index.html

//  ForgeViewer.js 
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////

var viewer = null;

function launchViewer(models) {
  if (viewer != null) {
    viewer.tearDown()
    viewer.finish()
    viewer = null
    $("#forgeViewer").empty();
  }

  if (!models || models.length <= 0)
    return alert('Empty `models` input');

  var options = {
    env: 'MD20ProdUS',
    api: 'D3S',
    getAccessToken: getForgeToken
  };

  if (LMV_VIEWER_VERSION >= '7.48') {
    options.env = 'AutodeskProduction2';
    options.api = 'streamingV2';
  }

  Autodesk.Viewing.Initializer(options, () => {
    viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));

    //load model one by one in sequence
    const util = new MultipleModelUtil(viewer);
    viewer.multipleModelUtil = util;

    // Use ShareCoordinates alignment instead
    // See https://github.com/yiskang/MultipleModelUtil for details
    // util.options = {
    //   alignment: MultipleModelAlignmentType.ShareCoordinates
    // };

    util.processModels(models);
  });
}

function getForgeToken(callback) {
  jQuery.ajax({
    url: '/api/forge/oauth/token',
    success: function (res) {
      callback(res.access_token, res.expires_in)
    }
  });
}
// ForgeTree.js
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////

$(document).ready(function () {
  prepareAppBucketTree();
  $('#refreshBuckets').click(function () {
    $('#appBuckets').jstree(true).refresh();
  });

  $('#createNewBucket').click(function () {
    createNewBucket();
  });

  $('#submitTranslation').click(function () {
    var treeNode = $('#appBuckets').jstree(true).get_selected(true)[0];
    submitTranslation(treeNode);
  });

  $('#createBucketModal').on('shown.bs.modal', function () {
    $("#newBucketKey").focus();
  });

  $('#CompositeTranslationModal').on('shown.bs.modal', function () {
    $("#rootDesignFilename").focus();
  });

  $('#hiddenUploadField').change(function () {
    var node = $('#appBuckets').jstree(true).get_selected(true)[0];
    var _this = this;
    if (_this.files.length == 0) return;
    var file = _this.files[0];
    switch (node.type) {
      case 'bucket':
        var formData = new FormData();
        formData.append('fileToUpload', file);
        formData.append('bucketKey', node.id);

        $.ajax({
          url: '/api/forge/oss/objects',
          data: formData,
          processData: false,
          contentType: false,
          type: 'POST',
          success: function (data) {
            $('#appBuckets').jstree(true).refresh_node(node);
            _this.value = '';
          }
        });
        break;
    }
  });

  $('#viewModels').click(function () {
    let treeInst = $('#appBuckets').jstree(true);
    let selectedNodeIds = $('#appBuckets').jstree('get_selected');
    let models = [];
    for (let i = 0; i < selectedNodeIds.length; i++) {
      let urn = selectedNodeIds[i];
      let node = treeInst.get_node(`${urn}_anchor`);
      if (!node || (node.type !== 'object'))
        continue;

      models.push({
        name: node.original.text,
        urn: `urn:${urn}`
      });
    }

    if (models.length <= 0 || (models.length !== selectedNodeIds.length))
      alert('Nothing selected or not all selected nodes are object-typed');

    launchViewer(models);
  });
});

function createNewBucket() {
  var bucketKey = $('#newBucketKey').val();
  var policyKey = $('#newBucketPolicyKey').val();
  jQuery.post({
    url: '/api/forge/oss/buckets',
    contentType: 'application/json',
    data: JSON.stringify({ 'bucketKey': bucketKey, 'policyKey': policyKey }),
    success: function (res) {
      $('#appBuckets').jstree(true).refresh();
      $('#createBucketModal').modal('toggle');
    },
    error: function (err) {
      if (err.status == 409)
        alert('Bucket already exists - 409: Duplicated')
      console.log(err);
    }
  });
}

function prepareAppBucketTree() {
  $('#appBuckets').jstree({
    'core': {
      'themes': { "icons": true },
      'data': {
        "url": '/api/forge/oss/buckets',
        "dataType": "json",
        'multiple': true,
        "data": function (node) {
          return { "id": node.id };
        }
      }
    },
    'types': {
      'default': {
        'icon': 'glyphicon glyphicon-question-sign'
      },
      '#': {
        'icon': 'glyphicon glyphicon-cloud'
      },
      'bucket': {
        'icon': 'glyphicon glyphicon-folder-open'
      },
      'object': {
        'icon': 'glyphicon glyphicon-file'
      }
    },
    "checkbox": {
      keep_selected_style: false,
      three_state: false,
      deselect_all: true,
      cascade: 'none'
    },
    "plugins": ["types", "checkbox", "state", "sort", "contextmenu"],
    contextmenu: { items: autodeskCustomMenu }
  }).on('loaded.jstree', function () {
    $('#appBuckets').jstree('open_all');
    $('#viewModels').show();
  // }).bind("activate_node.jstree", function (evt, data) {
  //   if (data != null && data.node != null && data.node.type == 'object') {
  //     $("#forgeViewer").empty();
  //     var urn = data.node.id;
  //     jQuery.ajax({
  //       url: '/api/forge/modelderivative/manifest/' + urn,
  //       success: function (res) {
  //         if (res.progress === 'success' || res.progress === 'complete') launchViewer(urn);
  //         else $("#forgeViewer").html('The translation job still running: ' + res.progress + '. Please try again in a moment.');
  //       },
  //       error: function (err) {
  //         var msgButton = 'This file is not translated yet! ' +
  //           '<button class="btn btn-xs btn-info" onclick="translateObject()"><span class="glyphicon glyphicon-eye-open"></span> ' +
  //           'Start translation</button>'
  //         $("#forgeViewer").html(msgButton);
  //       }
  //     });
  //   }
  });
}

function autodeskCustomMenu(autodeskNode) {
  var items;

  switch (autodeskNode.type) {
    case "bucket":
      items = {
        uploadFile: {
          label: "Upload file",
          action: function () {
            uploadFile();
          },
          icon: 'glyphicon glyphicon-cloud-upload'
        }
      };
      break;
    case "object":
      items = {
        translateFile: {
          label: "Translate",
          action: function () {
            var treeNode = $('#appBuckets').jstree(true).get_selected(true)[0];
            translateObject(treeNode);
          },
          icon: 'glyphicon glyphicon-eye-open'
        }
      };
      break;
  }

  return items;
}

function uploadFile() {
  $('#hiddenUploadField').click();
}

function submitTranslation(node) {
  $("#forgeViewer").empty();
  if (node == null) node = $('#appBuckets').jstree(true).get_selected(true)[0];
  var bucketKey = node.parents[0];
  var objectKey = node.id;
  var rootDesignFilename = $('#rootDesignFilename').val();
  var isSvf2 = $('#outputFormat :selected').text() === 'SVF2';
  var xAdsForce = ($('#xAdsForce').is(':checked') === true);
  var data = { 'bucketKey': bucketKey, 'objectName': objectKey, 'isSvf2': (isSvf2 === true), 'xAdsForce': xAdsForce };

  if((rootDesignFilename && rootDesignFilename.trim() && rootDesignFilename.trim().length > 0)) {
    data.rootFilename = rootDesignFilename;
    data.compressedUrn = true;
  }

  jQuery.post({
    url: '/api/forge/modelderivative/jobs',
    contentType: 'application/json',
    data: JSON.stringify(data),
    success: function (res) {
      $('#CompositeTranslationModal').modal('hide');
      $("#forgeViewer").html('Translation started! Please try again in a moment.');
    },
  });
}

function translateObject() {
  $('#CompositeTranslationModal').modal('show');
}
<!DOCTYPE html>
<html>

<head>
  <title>Autodesk Forge Tutorial</title>
  <meta charset="utf-8" />
  <link rel="shortcut icon" href="https://github.com/Autodesk-Forge/learn.forge.viewmodels/raw/master/img/favicon.ico">
  <!-- Common packages: jQuery, Bootstrap, jsTree -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/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" />
  <!-- Autodesk Forge Viewer files -->
  <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css" type="text/css">
  <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>

  <script src="https://unpkg.com/@tweenjs/tween.js@18.6.4/dist/tween.umd.js"></script>
  <!-- MultipleModelUtil -->
  <script src="http://cdn.jsdelivr.net/gh/yiskang/MultipleModelUtil/MultipleModelUtil.js"></script>
  <!-- this project files -->
  <link href="css/main.css" rel="stylesheet" />
  <script src="js/ForgeTree.js"></script>
  <script src="js/ForgeViewer.js"></script>
</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="Autodesk Forge" src="//developer.static.autodesk.com/images/logo_forge-2-line.png" height="20">
          </a>
        </li>
      </ul>
    </div>
  </nav>
  <!-- End of navbar -->
  <div class="container-fluid fill">
    <div class="row fill">
      <div class="col-sm-2 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>
            <span id="viewModels" class="glyphicon glyphicon-eye-open" style="cursor: pointer; display: none" title="View selected models in the Forge Viewer"></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-10 fill">
        <div id="forgeViewer"></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". Note: Technically your bucket name is required to be globally unique across
          the entire platform - to keep things simple with this tutorial your client ID will be prepended by default to
          your bucket name and in turn masked by the UI so you only have to make sure your bucket name is unique within
          your current Forge app.
        </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>

  <!-- Composite model translation -->
  <div class="modal fade" id="CompositeTranslationModal" 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">Composite Translation</h4>
        </div>
        <div class="modal-body">
          <div class="form-horizontal">
            <div class="form-group">
              <label for="outputFormat" class="col-sm-3 control-label">Output Format</label>
              <div class="col-sm-9">
                <select id="outputFormat" class="form-control">
                  <option>SVF</option>
                  <option selected="selected">SVF2</option>
                </select>
              </div>
            </div>
            <div class="form-group" style="margin-bottom: 0;">
              <label for="rootDesignFilename" class="col-sm-3 control-label">Root Filename</label>
              <div class="col-sm-9">
                <input type="text" id="rootDesignFilename" class="form-control" placeholder="Enter the filename of the root design">
              </div>
              <div class="col-sm-12">
                <span class="help-block" style="margin-bottom: 0; padding-left: 20px;">Enter the filename of the root design for the composite model. If the file is not a composite one, please press "Go ahead, submit translation" button directly.</span>
              </div>
            </div>
          </div>
        </div>
        <div class="modal-footer">
          <div class="checkbox pull-left">
            <label>
              <input type="checkbox" id="xAdsForce"> Replace previous result
            </label>
          </div>
          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
          <button type="button" class="btn btn-primary" id="submitTranslation">Go ahead, submit translation</button>
        </div>
      </div>
    </div>
  </div>
</body>

</html>

Here are the changes I made: https://github.com/yiskang/forge-viewmodels-nodejs-svf2/commit/5025b846970c2d95909b379f6704a51ca24caffd

And here is the demo snapshot: 在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM