New arrival

I’m having a bit of time out of work at the moment after the arrival of our second child, Chloe Eve Smith. Born 11th August at 4:36PM weighing 8lb 10.5oz.

Advertisements

Autodesk Forge

Back in June, I was extremely fortunate to attend Forge DevCon 2016 at the Fort Mason Centre for Art & Culture in San Francisco.

The conference was a packed 2 days of keynotes and tech talks on the capabilities of the Forge Platform and Autodesk’s strategy for it. For those new to the platform, it is essentially a set of cloud services, APIs, and SDKs, to allow developers to quickly create the data, apps, experiences, and services that power the future of making things.

forge1

Amar Hanspal, Senior VP, Products at Autodesk introduces the Forge platform

In this blog post, I’ll show usage of the Model Derivative API, which is used to translate design files from one format to another, to prepare them for the online viewer. It can also be used to extract data from the model.

We’ll also look at the Forge Viewer, a WebGL-based, JavaScript library for 3D and 2D model rendering. 3D and 2D model data may come from a wide array of applications, such as AutoCAD, Fusion 360, Revit, IFC etc.

forge2

Fort Mason Center – San Francisco

Preparing your file for viewing

Firstly, we’ll use the Model Derivative API to upload and translate a Revit file. Files are uploaded to a “bucket”, which we’ll need to create as a one time task.

Step 1 – Create your app

Before you can get going, you need to sign in to Autodesk developer account (https://developer.autodesk.com/) and create a new application. Select the APIs you want to use and give your app a name.

forge4

Create a new app

You will then be given a Client ID and Client Secret to allow your app to obtain authentication tokens to use against the Forge APIs.

Step 2 – Obtain an authentication token

Pretty much all requests to the Forge APIs require a bearer token to authenticate them. The application I’m building up for the blog post will use ASP.NET Core and will be written in C#. I will obtain tokens with the following code:

public async Task<string> GetToken(bool allowUpload = false)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri("https://developer.api.autodesk.com");
    var content = new FormUrlEncodedContent(new[] 
    {
        new KeyValuePair<string, string>("client_id", "<id>"),
        new KeyValuePair<string, string>("client_secret", "<secret>"),
        new KeyValuePair<string, string>("grant_type", "client_credentials"),
        new KeyValuePair<string, string>("scope", (allowUpload) ? "data:write data:read" : "data:read")
 });

    var result = await client.PostAsync("/authentication/v1/authenticate", content);

    JObject resultContent = JObject.Parse(await result.Content.ReadAsStringAsync());
    var token = resultContent["access_token"].ToString();

    return token;
}

This is a pretty straightforward web request, it’s worth pointing out the “scope”header. The value passed in here determines the permissions the token has e.g. read only, write, bucket creation etc.

Step 3 – Create a bucket

Models that you want to use with the Forge API’s must be uploaded to a storage area called an Open Storage Service (OSS) bucket. For the example in this blog post, this is a one time action – i.e. we will only create a bucket once and then use it for all of our models. For more information about buckets see this article.

As this is a one time action for us, we’ll use cURL to send the request to create the bucket rather than writing any C# code. We will need a bearer token though, and the token will need permission to create a bucket.

We get a token with the following request:

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id=<id>&client_secret=<secret>&grant_type=client_credentials&scope=bucket:create bucket:read data:write data:read' "https://developer.api.autodesk.com/authentication/v1/authenticate"

Then create a bucket using the bearer token:

curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer <token>" -d '{
 "bucketKey":"<bucket name>",
 "policyKey":"transient"
 }' "https://developer.api.autodesk.com/oss/v2/buckets"

 Step 4 – Upload a model

Now that we have a bucket to work with, we can upload files to it for processing. The process is really simple, we send the file to the API as a byte array – passing a valid bucket name. If successful, we get the object id of the file in the bucket. Subsequent calls to the API’s will now use the objectId (or source URN), which must be passed as a BASE64 encoded string – Autodesk recommend the use of a URL safe BASE64 string (RFC 6920).

public async Task<string> PutFile(string bucketName, string fileName, byte[] array)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri($"https://developer.api.autodesk.com");
    client.DefaultRequestHeaders.Add("authorization", $"Bearer {await GetToken(true)}");
    client.DefaultRequestHeaders.Add("cache-control", "no-cache");

    var content = new ByteArrayContent(array);
    content.Headers.Add("Content-Type", "application/octet-stream");

    var result = await client.PutAsync($"/oss/v2/buckets/{bucketName}/objects/{fileName}", content);

    var resultContent = await result.Content.ReadAsStringAsync();
    Console.WriteLine(resultContent.ToString());

    JObject json = JObject.Parse(resultContent);

    // Have we got an objectId?
    JToken objectId;
    if (json.TryGetValue("objectId", out objectId))
    {
        // Base64 encode the object id
        var encodedObjectId = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(objectId.ToString()));
        return encodedObjectId;
    }

    return "";
}

Step 5 – Translate the model

Next we need to request that the model we uploaded is translated to a more optimised SVF format for rendering. We do this with the following request:

public static async Task<string> Translate(string urn)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri($"https://developer.api.autodesk.com");
    client.DefaultRequestHeaders.Add("authorization", $"Bearer {await GetToken(true)}");

    string json = @"{ ""input"": { ""urn"": """ + urn + @""" }, ""output"": { ""formats"": [{ ""type"": ""svf"", ""views"": [""2d"", ""3d""] }] } }";
    var content = new StringContent(json);
    content.Headers.Remove("Content-Type");
    content.Headers.Add("Content-Type", "application/json");
    
    // Force file to be re-translated if it already exists in the bucket
    //content.Headers.Add("x-ads-force", "true");

    var response = await client.PostAsync("modelderivative/v2/designdata/job", content);

    var resultContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine(resultContent.ToString());

    JObject jsonPayload = JObject.Parse(resultContent);

    // Have we got a result?
    JToken result;
    if (jsonPayload.TryGetValue("result", out result))
    {
        return result.ToString();
    }

    return "";
}

Translation can take a while, in our application we want to show some feedback of progress. This can be achieved by polling another API endpoint for progress information:

public async Task<string> GetTranslationProgress(string urn)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri("https://developer.api.autodesk.com");
    client.DefaultRequestHeaders.Add("authorization", $"Bearer {await GetToken()}");

    var result = await client.GetAsync($"/modelderivative/v2/designdata/{urn}/manifest");

    JObject resultContent = JObject.Parse(await result.Content.ReadAsStringAsync());
    var progress = resultContent["progress"].ToString();

    return progress;
}

Step 6 – Get model metadata

Our model has now been uploaded and is being translated, for our application we want to extract metadata from the model. We want to look for objects in the model that have a “CPI” type or instance property.

In order to obtain the metadata, we need to send a request to the Model Derivative API to obtain the metadata.

public async Task<string> GetMetadataModelGuid(string urn)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri($"https://developer.api.autodesk.com");
    client.DefaultRequestHeaders.Add("authorization", $"Bearer {await GetToken(true)}");

    var response = await client.GetAsync($"/modelderivative/v2/designdata/{urn}/metadata");

    var resultContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine(resultContent.ToString());

    JObject jsonPayload = JObject.Parse(resultContent);

    // Have we got a result?
    if (jsonPayload["data"]["metadata"][0]["guid"] != null)
    {
        return jsonPayload["data"]["metadata"][0]["guid"].Value<string>();
    }

    return "";
}

The response will contain a list of model views within the model – Revit files can have a number of model views. For this example application, we’ll naively return the GUID of the first model view and assume it’s the view our user is after.

We can then get all of the properties in that model view with the following request:

public async Task<JObject> GetMetadataModelProperties(string urn, string modelGuid)
{
    HttpClient client = new HttpClient();

    client.BaseAddress = new Uri($"https://developer.api.autodesk.com");
    client.DefaultRequestHeaders.Add("authorization", $"Bearer {await GetToken(true)}");

    var response = await client.GetAsync($"/modelderivative/v2/designdata/{urn}/metadata/{modelGuid}/properties");

    var resultContent = await response.Content.ReadAsStringAsync();
    Console.WriteLine(resultContent.ToString());

    JObject jsonPayload = JObject.Parse(resultContent);
    return jsonPayload;
}

Viewing the model

Everything is now setup to initialise the Forge Viewer – our model is uploaded and translated – the only thing that remains is setting up our MVC view to display the model.

Step 1 – Stylesheets

Add the following styles to your view:

<link rel="stylesheet" href="https://developer.api.autodesk.com/viewingservice/v1/viewers/style.min.css?v=2.8" type="text/css">
<link rel="stylesheet" href="https://developer.api.autodesk.com/viewingservice/v1/viewers/A360.css?v=2.8" type="text/css">

At the time of writing, v2.8 was the latest version. You can omit the version number to use the latest version, but this isn’t recommended in a Production application.

Step 2 – JavaScript reference

Next add the following JavaScript references:

https://developer.api.autodesk.com/viewingservice/v1/viewers/three.min.js?v=2.8
 https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js?v=2.8
 https://developer.api.autodesk.com/viewingservice/v1/viewers/Autodesk360App.js?v=2.8

NOTE that the Forge viewer is built on the excellent three.js.

Step 3 – Create and initialise the viewer

The viewer is initialise with the (BASE64 encoded) source URN obtained during translation.

    var viewerApp;
    var options = {
        env: 'AutodeskProduction',
        accessToken: '@(await ForgeServices.GetToken())'
    };

    var documentId = 'urn:@ViewData["urn"]';

    Autodesk.Viewing.Initializer(options, onInitialized);

    function onInitialized() {
        viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
        viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D);
        viewerApp.loadDocument(documentId, onDocumentLoaded);
    }

    function onDocumentLoaded(lmvDoc) {
        var modelNodes = viewerApp.bubble.search(av.BubbleNode.MODEL_NODE); // 3D designs
        var sheetNodes = viewerApp.bubble.search(av.BubbleNode.SHEET_NODE); // 2D designs
        var allNodes = modelNodes.concat(sheetNodes);

        if (allNodes.length) {
            viewerApp.selectItem(allNodes[0].data);
 
            if (allNodes.length === 1) {
                alert('This tutorial works best with documents with more than one viewable!');
            }
        } else {
            alert('There are no viewables for the provided URN!');
        }
    }

In the onDocumentLoaded function, we are simply selecting the first 3D view we find – agin, this is a little naive and assumes that the first 3D view is the one the user wants to see.

And that’s all there is to it – our viewer is now initialised, with all panning, zooming and orbiting goodness you’d expect. There’s even a Duke Nukem 3D style First Person mode allowing you to walk through the model with the keyboard 🙂

This slideshow requires JavaScript.

And finally…

At the start of the blog, I mentioned that I was using ASP.NET Core. All of the above screenshots were taken on a ASP.NET Core application running on Mac OS X. Microsoft are doing some amazing things with .NET, the tooling isn’t quite there yet but it is awesome seeing .NET applications running on other platforms.

forge7

ASP.NET Core MVC application running on Mac OS X El Capitan