Advanced usage of A360 View and Data API

Posted :

On Oct 9th 2015, the conference called Autodesk University Japan was held in Tokyo, and Akatuka-san who is working for Mozilla japan and I gave a talk about WoT using A360 View-and-Data-API, which is a Web service by Autodesk.

What is View and Data API?

First of all, I’m gonna explain what View and Data API is. Literally, it consists of 2 features, the temporary file storage and the viewer.

The storage accepts several 3D model file formats, such as CAD format, STL, DXF, OBJ, 3DS, DAE and many others. Then it hosts them.

The viewer can show 3D file which is hosted in the storage. The viewer was built on top of three.js and WebGL, done with 100% web standards technology, and no need any plugins.

Additionally, the storage and viewer are connected through WebSocket which is kinda secure streaming connection. Thanks to the connection, it is possible to show a high-poly 3D model, even under internet connection, and the original model file data can be hidden.

Get started

TL;DR, I’ve made a short video and you can just watch it! Most blog posts about the API by Autodesk people are working with Windows, but this time, I will do on Mac book. I hope this could be helpful for you.

Start from View and Data API Beta - Quick Start Guide. Example code is available on github https://github.com/yomotsu/get-started-with-a360-view-api


At that demo what I made for the conference, I added an animation feature which was supposed to sync with a physical clock device. Unfortunately it was not perfect at that time tho. Anyway applying basic animations is possible under the View API version 1.2.16.

How to apply animations

Basically, View API is a viewer for high poly 3D models, and not for interactive projects so far I think. However you can handle animations with basic transforms, such as translate, rotate and scale, via three.js way but quite different. To do so, I’m gonna share some tips.

How to detect loaded event

If you would like to apply an animation or even just a transform, your app has to wait for load to complete. To detect the timing, there is an event, called Autodesk.Viewing.GEOMETRY_LOADED_EVENT.

1
2
3
4
5
6
7
8
var viewer = new Autodesk.Viewing.Viewer3D( viewerElement, {} );
// snip...
viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
function () {
// on model loaded callback...
}
);

How to translate

Use FragProxy feature to get kinda ghost of an actual mesh. Then apply transforms, and update it.

1
2
3
4
5
6
7
// after loaded entire model...
var fragId = 0; // {INT} index of fragments of the model, starting at 0
var fragProxy = viewer.impl.getFragmentProxy( viewer.model, fragId );
fragProxy.getAnimTransform();
fragProxy.position.set( 10, 10, 10 );
fragProxy.updateAnimTransform();
viewer.impl.sceneUpdated( true );

How to rotate

Similar to translate, but a little bit complicated. Because the viewer does not support local coords, and you have to use world coords instead. Plus fragProxy only accepts quaternion to rotate. When you would like to rotate a mesh in the model, follow the steps below.

The model is assumed to contain some meshes,

  • Before upload the model to the storage, align all parts (meshes) to center of the bounding box which contain all the model, using your 3D modeler software. Because when the model is loaded into the viewer, the center of the bounding box will be the origin of the world coords. The would X, Y, Z axises will be the axis of rotation.
  • And then, after loaded the entire model, just re-arrange each parts to the original position.
  • If you would like to apply rotate animation, follow the steps
    1. Rotate a mesh using quaternion.
    2. Then move position to whatever you want to place.

e.g. to rotate 60 deg,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// assume to the all parts (meshes) are aligned to center...
// after loaded all...

var AXIS_Z = new THREE.Vector3( 0, 0, 1 );
var position = new THREE.Vector3( 10, 10, 10 );
var deg = 60;
var fragId = 0; // {INT} index of fragments of the model, starting at 0
var fragProxy = viewer.impl.getFragmentProxy( viewer.model, fragId );

fragProxy.getAnimTransform();
// rotate first
flagProxy.quaternion.setFromAxisAngle( AXIS_Z, THREE.Math.degToRad( deg ) );
// re-arrange to the original position
fragProxy.position.copy( position );
fragProxy.updateAnimTransform();
viewer.impl.sceneUpdated( true );

How to animate it

Just render every frame like three.js way after you applied transform with above tips.

How to set the viewport size

After you make a viewer instance, set container size immediately.

1
2
3
var viewer = new Autodesk.Viewing.Viewer3D( viewerElement, {} );
viewer.container.style.width = '600px';
viewer.container.style.height = '400px';

How to resize the viewport size whenever you want

Set container size and then, execute resize() method.

1
2
3
viewer.container.style.width  = '1000px';
viewer.container.style.height = '1000px';
viewer.resize();

The code of the the demo

At the end, I will attach the code for the demo. This is not connected to the clock device and works as a stand alone with animations.

HTML

1
2
3
4
5
6
7
8
9
<link rel="stylesheet" href="https://developer.api.autodesk.com/viewingservice/v1/viewers/style.css">
<script src="https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js"></script>

<!-- snip... -->

<button onclick="modelData.play()">play</button>
<button onclick="modelData.pause()">pause</button>

<div id="viewerContainer"></div>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
;( function () {

// A360 viewer version: 1.2.16

'use strict';

let urn = 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6ZXhhbXBsZTEvY2xvY2szLjNkcw';
let viewerElement = document.getElementById( 'viewerContainer' );
let viewer = new Autodesk.Viewing.Viewer3D( viewerElement, {} );

let ModelData = function ( viewer ) {

let that = this;

this.isAnim = false;
this.viewer = viewer;
this.clock = new THREE.Clock();
this.mesh = [
{
// outer gear
id: 0,
flagProxy: null,
position: new THREE.Vector3( 0, 16.4, 5.5 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// body
id: 1,
flagProxy: null,
position: new THREE.Vector3( 0, 15, 5 ),
animation: ( function ( time ) {

return function ( time ) {

that.translate( this.flagProxy, this.position );

}

} )()
},
{
// inner gear
id: 2,
flagProxy: null,
position: new THREE.Vector3( 0, 16.4, 5.5 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// small gear top
id: 3,
flagProxy: null,
position: new THREE.Vector3( 0, 53.5, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 + 20 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// small gear left
id: 4,
flagProxy: null,
position: new THREE.Vector3( 5.2, 29, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// small gear right
id: 5,
flagProxy: null,
position: new THREE.Vector3( -5.2, 29, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// shaft top
id: 6,
flagProxy: null,
position: new THREE.Vector3( 0, 53.5, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// shaft left
id: 7,
flagProxy: null,
position: new THREE.Vector3( 5.2, 29, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 );
that.translate( this.flagProxy, this.position );

}

} )()
},
{
// shaft left
id: 8,
flagProxy: null,
position: new THREE.Vector3( -5.2, 29, 5.25 ),
animation: ( function ( time ) {

const AXIS = new THREE.Vector3( 0, 0, 1 );

return function ( time ) {

that.rotate( this.flagProxy, AXIS, time * 60 * 5 );
that.translate( this.flagProxy, this.position );

}

} )()
}
];

//mesh
this.mesh.forEach( function ( obj ) {

let flagProxy;

flagProxy = viewer.impl.getFragmentProxy( viewer.model, obj.id );
obj.flagProxy = flagProxy;
obj.animation( 0 );

} );

}

ModelData.prototype = {

translate: function ( flagProxy, vec3 ) {

flagProxy.getAnimTransform();
flagProxy.position.copy( vec3 );
flagProxy.updateAnimTransform();

},

scale: function ( flagProxy, vec3 ) {

flagProxy.getAnimTransform();
flagProxy.scale.copy( vec3 );
flagProxy.updateAnimTransform();

},

rotate: function ( flagProxy, axis, deg ) {

flagProxy.getAnimTransform();
flagProxy.quaternion.setFromAxisAngle( axis, THREE.Math.degToRad( deg ) );
flagProxy.updateAnimTransform();

},

play: function () {

this.clock.start();
this.isAnim = true;
this.animation();

},

pause: function () {

this.clock.stop();
this.isAnim = false;

},

animation: function () {

if ( !this.isAnim ) { return; }

requestAnimationFrame( this.animation.bind( this ) );
let theta = this.clock.getElapsedTime();

this.mesh.forEach( function ( obj ) {

obj.animation( theta );

} );

viewer.impl.invalidate( true );

}

};

//--

let accessToken = {
// expires: 0,
data: null,
get: function () {

return this.data;

},
update: function () {

return new Promise( function( onFulfilled, onRejected ) {

var req = new XMLHttpRequest();
req.open( 'GET', './get-token.php' );

req.onload = function() {

if ( req.status === 200 ) {

let data = JSON.parse( req.response );
// accessToken.expires = Date.now() + data.expires_in;
accessToken.data = data.access_token;
onFulfilled();

}

};

req.send();

} );

}
};


//--

function initViewer () {

let options = {

env:'AutodeskProduction',
accessToken: accessToken.get(),
document : 'urn:' + urn,

};

return new Promise( function( onFulfilled, onRejected ) {

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

viewer.initialize();
viewer.impl.setLightPreset( 8 );
viewer.setOptimizeNavigation( true );
// viewer.navigation.setZoomTowardsPivot( true );
// viewer.navigation.setReverseZoomDirection( true );
onFulfilled();


viewer.addEventListener(
Autodesk.Viewing.SELECTION_CHANGED_EVENT,
function ( event ) {
console.log( event.fragIdsArray );
} );

} );

} );

}


function loadDocument () {

return new Promise( function( onFulfilled, onRejected ) {

viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
function () { onFulfilled(); }
);

Autodesk.Viewing.Document.load( 'urn:' + urn, function ( doc ) {

let geometryItems = [];

geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(
doc.getRootItem(),
{
'type' : 'geometry',
'role' : '3d'
},
true
);

if ( geometryItems.length > 0 ) {

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

}

}, function( errorMsg ) {// onErrorCallback

alert( 'Load Error: ' + errorMsg );

} );

} );

}


function onload () {

console.log( 'loaded!' );
window.modelData = new ModelData( viewer );

}

//--

Promise.resolve()
.then( accessToken.update )
.then( initViewer )
.then( loadDocument )
.then( onload );

} )();

get-token.php (replace XXX… and YYY… to your setting)

Actually, the code is very similar to the curl command what you saw at the first step, then this will return a token in JSON.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

$MY_CONSUMER_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
$MY_CONSUMER_SECRET = 'YYYYYYYYYYYYYYYY';
$url = 'https://developer.api.autodesk.com/authentication/v1/authenticate';
$data = 'client_id='.$MY_CONSUMER_KEY.'&client_secret='.$MY_CONSUMER_SECRET.'&grant_type=client_credentials';
$header = array( 'Content-Type: application/x-www-form-urlencoded' );

$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $data ); // --data
curl_setopt( $ch, CURLOPT_HTTPHEADER, $header ); // --header
curl_exec( $ch );
curl_close( $ch );

?>