Building a web-based viewer using the Autodesk View & Data API – Part 2

In the last post we saw the process for getting content uploaded to Autodesk storage and translated into the format required by the Autodesk 360 viewer. In this post we're going to show the steps to take that data and embed it in a "simple" HTML page. (Any complex capability in this page it's due to the UI code that Dan Wellman kindly allowed me to borrow for the sample: otherwise what it does is very simple indeed.)

There are, of course, more complex samples that the ADN team has developed to demonstrate the richness of the new View & Data API. You can, for example, isolate geometry and search for particular metadata.

As I've mentioned before, the model I've used for this sample is big and flat, so isolating sets of components was out of the question: I've opted to use simple view changes to highlight different sections of the model. And the latest functionality that was pushed live in late July provides some nice navigation capabilities, smoothly transitioning the view where possible.

Firstly, though, here's the code for the very basic HTML page:

<!DOCTYPE HTML>

<html>

  <head>

    <meta charset="utf-8">

    <title>Steampunk Morgan Viewer</title>

    <link rel="stylesheet" href="css/css-transforms-viewer.css">

    <meta

      name="viewport"

      content="width=device-width, minimum-scale=1.0, maximum-scale=1.0" />

    <meta charset="utf-8">

 

    <link

      rel="stylesheet"

      href="https://developer.api.autodesk.com/viewingservice/v1/viewers/style.css"

      type="text/css">

    <script

      src="https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js">

    </script>

    <script src="js/jquery.js"></script>

    <script src="js/jquery.easing.1.3.js"></script>

    <script src="js/jquery.csstransform.pack.js"></script>

    <script src="js/steampunk.js"></script>

  </head>

  <body onload="initialize();" oncontextmenu="return false;">

    <div id="viewer">

      <div id="cog"><!-- --></div>

      <div id="window">

        <div id="viewer3d"

             style="width:468px; height:468px; overflow: hidden;">

        </div>

      </div>

      <div id="ui">

        <ul>

          <li id="label1">

            <a href="#entirety" title="Entirety">Entirety</a>

          </li>

          <li id="label2">

            <a href="#engine" title="Engine">Engine</a>

          </li>

          <li id="label3">

            <a href="#body" title="Body">Body</a>

          </li>

          <li id="label4">

            <a href="#interior" title="Interior">Interior</a>

          </li>

          <li id="label5">

            <a href="#wheels" title="Wheels">Wheels</a>

          </li>

        </ul>

        <div><!-- --></div>

      </div>

    </div>

  </body>

</html>

Here's the main custom JavaScript it references (steampunk.js):

var viewer;

 

// Many thanks to Dan Wellman (@danwellman). Not only did he write

// the excellent post that formed the basis for this application's

// Steampunk UI, he provided the artwork to help build a custom

// version...

// http://www.dmxzone.com/go/18220/an-image-viewer-with-the-dmxzone-universal-css-transforms-library/

 

function initialize() {

 

  // Get our access token from the internal web-service API

 

  $.get("http://" + window.location.host + '/api/token',

    function (accessToken) {

 

      var options = {};

      options.env = "AutodeskProduction";

      options.accessToken = accessToken;

      options.document =       "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1NwTTNXNy5mM2Q=";

 

      // Create and initialize our 3D viewer

 

      var elem = document.getElementById('viewer3d');

      viewer = new Autodesk.Viewing.Viewer3D(elem, {});

 

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

 

        viewer.initialize();

 

        // Go with the "Riverbank" lighting and background effect

 

        viewer.impl.setLightPreset(8);

 

        // We have a heavy model, so let's save some work during

        // navigation

 

        viewer.setOptimizeNavigation(true);

 

        // Let's zoom in and out of the pivot - the screen

        // real estate is fairly limited - and reverse the

        // zoom direction

 

        viewer.navigation.setZoomTowardsPivot(true);

        viewer.navigation.setReverseZoomDirection(true);

 

        loadDocument(viewer, options.document);

      });

    }

  );

 

  // Set up some UI elements for the Steampunk UI

 

  $("#ui").find("div").attr("id", "over");

  $("#window").wrapInner("<div id=\"wrapper\">");

 

  // Store positions

 

  var overPositions =

        {

          entirety: 0, engine: 75, body: 152,

          interior: 230, wheels: 310

        },

      cogPositions =

        {

          entirety: 5, engine: 80, body: 154,

          interior: 235, wheels: 310

        },

      previousCogPosition = 0;

 

  // Animation function

 

  function animator(pointer, callback) {

 

    // Move cog

 

    $("#cog").animate({

      "translateY": parseInt(cogPositions[pointer]),

      "rotate":

        (parseInt(cogPositions[pointer]) < previousCogPosition) ?

        "-=365" : "+=365"

    }, function () {

      previousCogPosition = cogPositions[pointer];

    });

 

    // Move over

 

    $("#over").animate({

      "translateY": parseInt(overPositions[pointer])

    });

 

    // Add a delay so the camera changes after the cog has stopped

    // whirring

 

    if (callback) {

      setTimeout(function () { callback(); }, 400);

    }

  }

 

  // Is there a hash?

 

  if (window.location.hash) {

 

    // Store the hash

 

    var hash = window.location.hash.split("#")[1];

 

    // Position over   

 

    animator(hash);

  }

 

  // Add transitions

 

  $("#ui a").click(function (e) {

    e.preventDefault();

 

    // Store new pointer

 

    var pointer = $(this).attr("href").split("#")[1];

 

    // Call animation function

 

    animator(pointer, function () {

      if (pointer === "entirety") {

        zoomEntirety();

      } else if (pointer === "engine") {

        zoomEngine();

      } else if (pointer === "body") {

        zoomBody();

      } else if (pointer === "interior") {

        zoomInterior();

      } else if (pointer === "wheels") {

        zoomWheels();

      }

    });

  });

}

 

// Helper functions to zoom into a specific part of the model

 

function zoomEntirety() {

  zoom(-48722.5, -54872, 44704.8, 10467.3, 1751.8, 1462.8);

}

function zoomEngine() {

  zoom(-17484, -364, 4568, 12927, 173, 1952);

}

function zoomBody() {

  zoom(53143, -7200, 5824, 12870, -327.5, 1674);

}

function zoomInterior() {

  zoom(20459, -19227, 19172.5, 13845, 1228.6, 2906);

}

function zoomWheels() {

  zoom(260.3, 26327, 954, 371.5, 134, 2242.7);

}

 

// Set the camera based on a position and target location

 

function zoom(px, py, pz, tx, ty, tz) {

 

  // Make sure our up vector is correct for this model

 

  var camera = viewer.autocamCamera;

  camera.up = new THREE.Vector3(0, 0, 1);

  viewer.navigation.setWorldUpVector(camera.up);

 

  // This performs a smooth view transition (we might also use

  // setView() to get there more directly)

 

  viewer.impl.controls.transitionView(

    new THREE.Vector3(px, py, pz), new THREE.Vector3(tx, ty, tz),

    camera.fov, camera.up, true

  );

 

  //viewer.navigation.setRequestTransition(

  //  true,

  //  new THREE.Vector3(px, py, pz), new THREE.Vector3(tx, ty, tz),

  //  viewer.getFOV()

  //);

}

 

// Progress listener to set the view once the data has started

// loading properly (we get a 5% notification early on that we

// need to ignore - it comes too soon)

 

function progressListener(param) {

 

  if (param.percent > 0.1 && param.percent < 5) {

 

    // Remove the listener once called - one-time operation

 

    viewer.removeEventListener("progress", progressListener);

 

    // Iterate the materials to change any red ones to grey

 

    for (var p in viewer.impl.matman().materials) {

      var m = viewer.impl.matman().materials[p];

      if (m.color.r >= 0.5 && m.color.g == 0 && m.color.b == 0) {

        m.color.r = m.color.g = m.color.b = 0.5;

        m.needsUpdate = true;

      }

    }

 

    // Zoom to the overal view initially

 

    zoomEntirety();

  }

}

 

function loadDocument(viewer, docId) {

 

  if (docId.substring(0, 4) !== 'urn:')

    docId = 'urn:' + docId;

 

  Autodesk.Viewing.Document.load(docId,

    function (document) {

      var geometryItems = [];

 

      if (geometryItems.length == 0) {

        geometryItems =

          Autodesk.Viewing.Document.getSubItemsWithProperties(

            document.getRootItem(),

            { 'type': 'geometry', 'role': '3d' },

            true

          );

      }

      if (geometryItems.length > 0) {

        viewer.load(document.getViewablePath(geometryItems[0]));

      }

 

      viewer.addEventListener("progress", progressListener);

    },

    function (errorMsg, httpErrorCode) {

      var container = document.getElementById('viewer3d');

      if (container) {

        alert("Load error " + errorMsg);

      }

    }

  );

}

A couple of things to note: the document ID is the Base64-encoded URN we saw last time. Which is hosted on my storage, so you'll need to change this to the equivalent URN on your own, in due course.

You'll also note that the code calls a REST API on the server hosting the page (/api/token) in order to get an access token. This is the internal API that very simply calls into the Autodesk web-service that deals with authentication.

Here's the JavaScript code – using the Node.js framework – that implements this API:

var CONSUMER_KEY = 'K8whhq86fnoYqw4GXAW0ID1hH';

var CONSUMER_SECRET = 'DC2cBoXIy8';

var BASE_URL = 'https://developer.api.autodesk.com';

 

var request = require('request');

 

exports.getToken = function (req, res) {

 

  var params = {

    client_id: CONSUMER_KEY,

    client_secret: CONSUMER_SECRET,

    grant_type: 'client_credentials'

  }

 

  request.post(BASE_URL + '/authentication/v1/authenticate',

    { form: params },

    function (error, response, body) {

      if (!error && response.statusCode == 200) {               

        var authResponse = JSON.parse(body);

        res.send(authResponse.access_token);

      }

    }

  );

};

The key and secret are the same (edited) ones we saw in the last post. The caller will receive an authorization token they can then use to call into the View & Data API directly, without requiring further API calls from this particular web-service. (Unless the viewer needs another token, in which case this should get called again.)

So how do you get this sample working? The simplest way would be to clone the sample's public GitHub repository to your local system (this can be done either using a GitHub client app or the command-line) and then create a new, private repository on GitHub containing this sample with a couple of modified files: you should (of course) change the key and secret to be your own in api.js, but you will also need to update the document ID (the value of options.document in steampunk.js) to point to your own content that was uploaded and translated using the process we saw last time.

Once these changes are committed to GitHub, you can create a free app on Heroku, which connects directly to GitHub and pulls down the code from there in order to build it. Very, very easy to deploy an app in this way (I won't go through the specific steps in this post, but if there's demand I can certainly do so in a follow-up – just post a comment if you'd like to see that).

Et voilà, the working application (if you see white mudguards and interior leather, give it some time: some materials are still loading). Hopefully you can see that it's possible to create a fairly impressive web-based viewer for your content without a great deal of code.

The working application

Of course there's much more you can do with the View & Data API than I've shown in this sample, so I do recommend perusing the other samples on GitHub, as well as playing with the running samples themselves. And you'll find more information on this interesting new API on the ADN team's Cloud & Mobile DevBlog.

Update:

I've had to make s few changes to the client-side JavaScript file for it to work with a recent release of the viewer. I've gone ahead and updated the code in the post (you can always see what changes have been made in GitHub, of course).

15 responses to “Building a web-based viewer using the Autodesk View & Data API – Part 2”

  1. Hi Kean,

    The given Consumer Key & Secret were forbidden to get access to the Developer API site. Therefore Node.js server returns "undefined" authentication response. Could you please check it? The demo web shows up at http://localhost:5000, but without 3D model. I guess developers have to use their own secret keys, not "public" ones from the blog.

    1. Hi Khoa,

      Yes, I mentioned in both posts that you won't be able to use the provided secret and key.

      Regards,

      Kean

      1. Those secret and key are very important to get access to the Autodesk cloud. I am waiting to get them to explore this very excited feature. Thanks.

        1. I'll check in with ADN to see what's happening.

          Kean

        2. Hi Khoa,

          Alright - it turns out that access keys aren't yet being assigned. (If I'd realised I would have of course mentioned that in my posts.) I'm told it should be a matter of days before that starts, hopefully not longer.

          Thanks for your patience,

          Kean

          1. Thank you to let me know. I think this issue would be very common for developers starting to work on View & Data API.

            Khoa

            1. I think that's an understatement, Khoa (I have trouble seeing how people will use the API without an access key 🙁 :-).

              Kean

              1. I'd like the client JavaScript side of the new API. I am learning with it. Hopefully this API will be on public soon.

  2. The given Customer Key & Key were not allowed to get accessibility the Designer API site. Therefore Node.js server profits "undefined" verification reaction. Could you please check it? The trial web reveals up at http://localhost:5000, but without 3D design. I think designers have to use their own secret important factors, not "public" ones from the weblog.

    Incinerador de Grasa

  3. Hi, could you please let me know how can I get a consumer key and cusumer secret?

    1. Kean Walmsley Avatar

      You can get one at developer.autodesk.com.

      Kean

  4. Hi Kean,

    I'm getting the {"reason":"Unable to parse JSON"} error when I try to create a Bucket. Would you please let me know what am I missing here ?

    -Bharath

    1. Hi Bharath,

      Sorry, no idea. Please post information to reproduce the problem on the View & Data API Forum:

      forums.autodesk.com/

      Regards,

      Kean

      1. Thanks for getting back Kean. I have posted this in the forum

      2. Hi Kean, I figured it out.
        I was using oss/v2 and we I should have used "policyKey" instead of "policy" in the JSON string.

        Thanks,
        Bharath

Leave a Reply to Khoa Ho Cancel reply

Your email address will not be published. Required fields are marked *