For quite sometime I've been playing with cURL and the Forge Platform, and to start this 2017 I will be posting about my research using cURL and JQ processor together with the Forge Platform.
Using Terminal I found a quicker and simpler way for myself to use different workflows when using the Authentication API, such actions as obtaining 2 legged & 3 legged access tokens. With the Data Management API, I've been able to create, upload, request translation, access to Hubs and add new files to specific projects that I'm part of. Everything from the Terminal using cURL. But I know what you can be thinking "cURL from terminal, is so unorganized, so hard to read, so easy to mess up." I could not agree more with some of these thoughts. When your response has more than 1 line of JSON data returned, I agree it can be messy and hard to read.
I know others have decided to use REST Apps such as Paw or Postman, since the fear of messing up one character in your cURL can give you problems and at the same time, you get a better organized JSON result. But bare with me, I found out about JQ while using cURL and since then It has made quite a difference when testing the api's.
What is JQ? jq is a lightweight and flexible command-line JSON processor. A jq program is a “filter”: it takes an input, and produces an output. There are a lot of builtin filters for extracting a particular field of an object, or converting a number to a string, or various other standard tasks. It lets you visualize the JSON response in a organized way and it becomes easier to read when using terminal. How to use it? It requires a basic installation, JQ can be download for different OS platforms from here. After installation has been performed you should restart your Terminal and you would be ready for testing. You can check your version of JQ by simply typing "jq --version" in your terminal, which will assure you JQ was successfully installed.
Let's look at how the previous cURL actions to obtain a 2 legged access token and create a bucket looks after using JQ from Terminal.
As we can see JQ structures and color codes the result JSON from our REST call using cURL. Later on I will be posting the entire workflow on how to start from obtaining a 2 legged access token up to translating a file and get back the URN ready to be displayed in the Viewer. Followed by a 3rd post on how to access my a360 hubs and add a file to my project or another project I'm part of, all using cURL and the JQ processor.
For the sake of completeness, those two scripts have been added to this repository as well.
Setup and Usage
Two aspects need to be prepared: Forge and Python.
Before you can make any use of the Forge web services, you will need to register an app and request the API client id and client secret for it
at developer.autodesk.com
> my apps.
These scripts assume that you have stored these credentials in the environment variables FORGE_CLIENT_ID and FORGE_CLIENT_SECRET.
In View and Data client side API, The assets in the Autodesk Viewer have an object tree, a tree structure that represents the model hierarchy. Each element in model can be representing as a node of model tree. Each node has a dbId, this is a unique id for the element in the model. There is a one-to-one correspondence between a node and a dbId.
In View and Data client API, some methods use dbId as parameter and some methods use node as parameter, in that case you will need to do the conversion. To get the node object from dbId, here is code snippet:
var node = viewer.model.getData()
.instanceTree.dbIdToNode[dbId];
To get dbId of a node, just use the dbId property:
To work with Autodesk View and Data API, you will need understand the coordinate system definition of 3D model space. As you know, View and Data API is build on top of Three.js, while there is an AxisHelper in three.js to show the axes. As asked in this post, if you just use it directly in viewer, it does not work, you will get an error message :"Only THREE.Mesh can be rendered by the Firefly renderer. Use THREE.Mesh to draw lines." The reason is that we have a custom rendered on top of three.js, so this renderer cannot render that AxisHelper. You would need to handle creation of the lines by yourself.
I created an extension so that you can use it directly:
// It is recommended to load the extension when geometry is loaded
viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
function(){
viewer.loadExtension('Autodesk.ADN.Viewing.Extension.AxisHelper');
});
Here is a screen-shot, the red line is X-axis, green line is Y-axis and blue line is Z-axis:
Here is the source code of this extension, but you may need to check it from github to get the latest version as it may be updated from time to time.
///////////////////////////////////////////////////////////////////////////////
AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
Autodesk.ADN.Viewing.Extension.AxisHelper = function (viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
var _self = this;
var _axisLines = [];
_self.load = function () {
console.log('Autodesk.ADN.Viewing.Extension.AxisHelper loaded');
addAixsHelper();
//workaround//have to call this to show up the axis
viewer.restoreState(viewer.getState());
returntrue;
};
_self.unload = function () {
removeAixsHelper();
console.log('Autodesk.ADN.Viewing.Extension.AxisHelper unloaded');
returntrue;
};
var addAixsHelper = function() {
_axisLines = [];
//get bounding box of the modelvar boundingBox = viewer.model.getBoundingBox();
var maxpt = boundingBox.max;
var minpt = boundingBox.min;
var xdiff = maxpt.x - minpt.x;
var ydiff = maxpt.y - minpt.y;
var zdiff = maxpt.z - minpt.z;
//make the size is bigger than the max bounding box //so that it is visible var size = Math.max(xdiff,ydiff,zdiff) * 1.2;
//console.log('axix size :' + size);// x-axis is redvar material_X_Axis = new THREE.LineBasicMaterial({
color: 0xff0000, //red
linewidth: 2
});
viewer.impl.matman().addMaterial('material_X_Axis',material_X_Axis,true);
//draw the x-axix linevar xLine = drawLine(
{x : 0, y : 0, z : 0} ,
{x : size, y : 0, z : 0} ,
material_X_Axis);
_axisLines.push(xLine);
// y-axis is greenvar material_Y_Axis = new THREE.LineBasicMaterial({
color: 0x00ff00, //green
linewidth: 2
});
viewer.impl.matman().addMaterial('material_Y_Axis',material_Y_Axis,true);
//draw the y-axix linevar yLine = drawLine(
{x : 0, y : 0, z : 0} ,
{x : 0, y : size, z : 0} ,
material_Y_Axis);
_axisLines.push(yLine);
// z-axis is bluevar material_Z_Axis = new THREE.LineBasicMaterial({
color: 0x0000ff, //blue
linewidth: 2
});
viewer.impl.matman().addMaterial('material_Z_Axis',material_Z_Axis,true);
//draw the z-axix linevar zLine = drawLine(
{x : 0, y : 0, z : 0} ,
{x : 0, y : 0, z : size} ,
material_Z_Axis);
_axisLines.push(zLine);
}
var drawLine = function(start, end, material) {
var geometry = new THREE.Geometry();
geometry.vertices.push(new THREE.Vector3(
start.x, start.y, start.z));
geometry.vertices.push(new THREE.Vector3(
end.x, end.y, end.z));
var line = new THREE.Line(geometry, material);
viewer.impl.scene.add(line);
//refresh viewer
viewer.impl.invalidate(true);
return line;
}
var removeAixsHelper = function() {
_axisLines = [];
_axisLines.forEach(function(line){
viewer.impl.scene.remove(line);
});
//remove materials
delete viewer.impl.matman().materials.material_X_Axis;
delete viewer.impl.matman().materials.material_Y_Axis;
delete viewer.impl.matman().materials.material_Z_Axis;
}
};
Autodesk.ADN.Viewing.Extension.AxisHelper.prototype =
Object.create(Autodesk.Viewing.Extension.prototype);
Autodesk.ADN.Viewing.Extension.AxisHelper.prototype.constructor =
Autodesk.ADN.Viewing.Extension.AxisHelper;
Autodesk.Viewing.theExtensionManager.registerExtension(
'Autodesk.ADN.Viewing.Extension.AxisHelper',
Autodesk.ADN.Viewing.Extension.AxisHelper);
In previous post, I learned that it is possible to integrate the build and test process automatically on new github push with Travis-CI, but as you can see, actually there is no test case yet. In this post I will keep sharing what I learned about the unit testing. Behavior Driven Development(BDD) or Test Driven Development(TDD) is very popular and recommended, which makes your code more robust and also makes you feel safe when refactoring your code.
I will start from the unit testing of the REST API, I use our basic view and data API sample workflow-node.js-view.and.data, the server side exposes a REST API to get access token, the viewer client will call this REST API to initialize the viewer and show models on web page. So I will do the unit test of the server side REST API first. There are quite a lot different test frameworks in node.js world, in this post I will use Jasmine-node and Frisby, I do not have strong oponion of these frameworks, you may have your own faverite ones, I use them just becase they come to me first :)
Install Jasmine and Frisby
If you do not have [Node.js] installed, you should install it first, I will skip this part.
Jasmine is a Behavior Driven Development testing framework for JavaScript. I am using Jasmine-node here since I am working on a node.js project. You can install it goblally:
npm install jasmine-node -g
Another test framework I use is Frisby, which is a REST API testing framework built on node.js and Jasmine that makes testing API endpoints easy, fast, as it desclimed on it's homepage. You can install it with NPM:
npm install frisby -g
To make them run on other environment, I need to add them into package.json of my node.js project so that they can be installed with 'npm install' command:
Firstly let's write our test case with Jasmine. The convention of Jasmine is that the test case file name should end with 'spec.js', for convenience I put all the specification files into a folder named as 'spec', when I run the test case, I use following command, Jasmine will run all the '*spec.js' in this folder:
jasmine-node spec
Now I create an specification file for the REST API. To use Autodesk View and Data API, we need to create an App at http://developer.autodesk.com to get the consumer key and consummer secret, with this key pair to get the access token.Please refer to our code samples if you are not familiar with view and data API. I need to create such REST API for the access token. For example, with the HTTP GET /api/token, returns the token in JSON similar like blow:
Next, let's do more work, the token API should return a JSON result, the response content type should be JSON, so the spec should be similar like below:
So, that's it about basic REST API testing with Frisby, you can learn more about frisby at here. Here is my complete test case for my token API
var frisby = require('frisby');
frisby.create('Get access token')
.get('http://localhost:3000/api/token')
.expectStatus(200)
.expectHeaderContains('content-type', 'application/json')
.expectBodyContains('access_token')
.expectJSONTypes({
access_token : String,
token_type : String,
expires_in : Number
})
//the access token should contains {token_type : 'Bearer'}
.expectJSON({token_type : 'Bearer'})
.expectJSON({
access_token : function(val) {
//this is a valid sample access token
var sample_token = '2974LErhmJIlyeewjp34lmfZaBpl';
expect(val.length).toEqual(sample_token.length);
}
})
.toss();
Hookup the test script with npm
The continuous integration tool run the test automaticaly by running npm test, so we need to add our jasmine-node test to the package.json file as below:
We had some issue with our viewer live sample - SAP integration demo - last week. After investigation, it turns out that it is just due to a small annoy thing – the encoding of filename. I am sharing my experience so that you don’t walk into the same issue latter.
To view the model, we need to upload the model file to Autodesk cloud, and register it for translation. The file identifier in cloud will be an URL like baseurl/bucket/{bucket_key}/objects/{object_key}. Generally speaking, the object key is the file name of model. As you know, we need to encode the filename to serve as object key, right?
I firstly use following JavaScript code to do encoding:
var objectKey = escape(filename);
In most time, it works just fine. But recently, we run into trouble, because one filename has a special character plus (‘+’). escape() does not encode the ‘+’ character. Following are some testing:
var filename = 'file name + somethig.txt'; console.log(escape(filename)); > file%20name%20+%20somethig.txt console.log(encodeURI(filename)); > file%20name%20+%20somethig.txt console.log(encodeURIComponent(filename)); > file%20name%20%2B%20somethig.txt
Form the result, you notice that only encodeURIComponent encodes the plus sign(‘+’) to “%2B”. So we need to use encodeURIComponent to encode filenames when uploading with View and Data API, otherwise you will run into problem. Not a big deal but it takes time to figure out why :s
If you encode from server side with C#, the commonly used HttpUtility.UrlEncode() is also problematic for this scenario. We should use Uri.EscapeDataString() instead.
Hope this helps some if you run into the same issue.
Philippe also presented a very nice and absolutely minimal
basic viewer sample in node.js demonstrating
how to implement a private web service to obtain the authorisation token without exposing your key and secret in your JavaScript source.
The new push of the Cloud based View & Data API is coming with an exciting feature that will enable developers to easily componentize their code, reuse and isolate features using a modular approach.
The mechanism is pretty simple: you derive a JavaScript class from Autodesk.Viewing.Extension and then load that extension when the viewer starts or at runtime.
See the code below where I highlighted the portion of the code relevant to the extensions. The viewer.loadExtension / unloadExtension methods allow you even load or unload extensions without reloading the current model loaded in the viewer.
-
function initializeViewer(containerId, documentId, role) {
var viewerContainer = document.getElementById(containerId);
var viewer = new Autodesk.Viewing.Private.GuiViewer3D(
In my previous post I was exposing a JavaScript wrapper for the View & Data which makes it easy to upload and translate your files from a client html page.
I’ve been playing with the Viewer API for couple of months now and I found it useful to use a little wrapper around the client API, so common tasks such as loading/closing a document, saving/restoring a view, getting a property value are more straightforward.
Here is the most basic example on how to use the wrapper:
$(document).ready(function () {
// document URN
var documentId = "dXJuOmFkc2sub2JqZ...real urn’s are longer ...";
// creates new AdnViewerManager instance
adnViewerMng = new Autodesk.ADN.Toolkit.Viewer.AdnViewerManager(
Input the name of a bucket and select files to upload. If the bucket doesn't exist it will be created, then the files will be upload and translated, ready for viewing using the output URN.
Token generation cannot be achieved from a browser, so you need API keys to generate the token some other way: using curl, an executable, ADN samples, ...