Autodesk ForgeにおけるViewerことはじめ

Autodesk Forgeは基本的にはA360などのサーバ上にアップされたBIMデータをウェブAPIを通じて操作できるようにしたもので、疎結合なアーキテクチャとなっています。つまりはビューワは独立に制作することができます。描画のライブラリはAutodeskから提供されており、ライブラリはthreeJSを拡張したものになっているそうです。BIMデータをアップしたときに取得する、Access TokenやデータのURNが既知であれば、HTMLファイルだけで3次元形状をブラウザ上に表示することができます。サーバ側の仕組みとしては、先に述べたAccess Tokenなどを保持したり、データベースと連携したりといったアイデアがあるはずで、それによってアプリケーションの拡張が可能と考えます。

それでは、今回はNodeJS+Expressでクライアントを試作してみます。

# 準備
## BIMデータの準備
まずは、ForgeアプリとBIMのデータの登録を行います。[以前のエントリ](https://qiita.com/Takashi_Kasuya/items/409997752b47b038abe4)にやり方があるので、参考にしてください。
Access Tokenのみならず、BIMデータの場所を示すURNも必要になります。[ここ](https://qiita.com/Takashi_Kasuya/items/584430d0e68ace79e958)に変換の仕方があるので参考にしてください。これらがきちんと取得していないとまったく動きません。

## NodeJS+Express環境の準備
別にクライアントだけでも良いのですが、今後の拡張も鑑みてNodeJS＋Expessで作ってみます。ExpressというのはNodeJSで有名なMVCフレームワークです。セットアップについては、[ここら辺](http://expressjs.com/ja/starter/installing.html)を参考にしてください。私はVisual Studioのテンプレートを使って作りました。package.jsonとエントリポイントであるapp.jsは以下です。これをnpm startなどとすると、[localhost:3000](http://localhost:3000)あたりで、アプリが立ち上がります。ちなみに、./routes/index.js内に書かれたルーティングルールでルーティングされます。今回は./views以下のindex.pugにルーティングするようにしています。テンプレートエンジンはPUGです。この辺りはEXPRESSのほぼテンプレなので、説明は割愛します。

``` package.json
{
  "name": "forge-express",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node app"
  },
  "description": "Forge sample using NodeJS + Express",
  "dependencies": {
    "body-parser": "^1.15.0",
    "cookie-parser": "^1.4.0",
    "debug": "^2.2.0",
    "express": "^4.14.0",
    "morgan": "^1.7.0",
    "pug": "^2.0.0-beta6",
    "serve-favicon": "^2.3.0"
  }
}
```

```app.js
'use strict';
var debug = require('debug');
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function (req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

if (app.get('env') === 'development') {
    app.use(function (err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: {}
    });
});

app.set('port', process.env.PORT || 3000);

var server = app.listen(app.get('port'), function () {
    debug('Express server listening on port ' + server.address().port);
});
```

# 基本的なForge Viewerの制作
HTMLのもととなるPUGファイルは、views以下に配置しています。ルート（/)が参照されたら、まずはlayout.pugの内容が出力されるようになってます。ここでは、まずForgeに関係するCSS、JSライブラリを読み込んでいます。最後に読み込んでいるviewer.jsが今回の肝となる部分です。

```layout.pug
doctype html
html
  head
    title= title
    meta(name='viewport', content='width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no')
    link(rel='stylesheet', href='/stylesheets/main.css')
    link(rel='stylesheet', href='https://developer.api.autodesk.com/modelderivative/v2/viewers/style.min.css', type='text/css')
    
  body
    block content
    script(src='https://developer.api.autodesk.com/modelderivative/v2/viewers/viewer3D.min.js')
    script(src='javascripts/viewer.js')
```

続いて、上記で参照しているblock contentの部分です。ここは簡単にMyViewerDivというタグがあるだけ。このDIV要素にForgeのビューワが読み込まれます。ついでにCSSも設定しておきます。画面いっぱいに表示できるように変えておきます。

```index.pug
extends layout

block content
  div#MyViewerDiv
```

```main.css
body {
    margin: 0;
}

#MyViewerDiv {
    width: 100%;
    height: 100%;
    margin: 0;
    background-color: #F0F8FF;
}
```

## データ表示用のJavascript
先のviewer.jsにBIMデータの読み込み、表示の処理を書き込んでいきます。ここで、準備の項で述べたアクセストークンとURNが必要になります。

```viewer.js
var viewerApp;
var options = {
    env: 'AutodeskProduction',
    getAccessToken: function (onGetAccessToken) {
        var accessToken = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
        var expireTimeSeconds = 60 * 30;
        onGetAccessToken(accessToken, expireTimeSeconds);
    }
};
var documentId = 'urn:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';

Autodesk.Viewing.Initializer(options, function onInitialized() {
    // 読み込みが終わった後に呼ばれるハンドラ。MyViewerDiv内にビューワをロードする。
    viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
    viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D);
    // コンテンツのロードを始める。
    viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});

function onDocumentLoadSuccess(doc) {
    // A document contains references to 3D and 2D viewables.
    var viewables = viewerApp.bubble.search({ 'type': 'geometry' });
    if (viewables.length === 0) {
        console.error('Document contains no viewables.');
        return;
    }
    viewerApp.selectItem(viewables[0].data, onItemLoadSuccess, onItemLoadFail)
}

function onDocumentLoadFailure(viewerErrorCode) {
    console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
}

function onItemLoadSuccess(viewer, item) {
    console.log('onItemLoadSuccess()!');
    console.log(viewer);
    console.log(item);
    console.log('Viewers are equal: ' + (viewer === viewerApp.getCurrentViewer()));
}

function onItemLoadFail(errorCode) {
    console.error('onItemLoadFail() - errorCode: ' + errorCode);
}
```

まず、optionsというオブジェクトをInitializerの引数としています。optionsにはenvとgetAccessTokenという関数がパラメータとしてあるが、後者は自動的にExpireされる前にアクセストークンを更新するような処理となるようです（隠蔽されていてわからない）。実際は、getAccessTokenという関数の代わりに、AccessTokenというパラメータとしても問題はありません。

初期化の処理が終わると、ViewingApplicationがnewされます。その際にビューワをロードするDIV要素を指定しています。
そのあとはviewerApp.k3DやAutodesk.Viewing.Private.GuiViewer3DなどのExtentionを読み込んでから、コンテンツ（BIMデータなど）のロードを始めています。それぞれに失敗した時と、成功した時のハンドラとなる関数を用意しています。
viewer.selectItemを呼び出して成功したら(onItemLoadSuccessが呼ばれたら）、画面に表示されます。

<img width="958" alt="viewer.png" src="https://qiita-image-store.s3.amazonaws.com/0/51703/caa93a37-de4d-8a88-6378-c491e1e9b7d7.png">

とりあえず、この記事ではここまで。
次回はビューワの柔軟な操作が可能なExtensionsやEvent処理についても説明します。