AutodeskのForge Viewerを利用してFBXファイルを表示する

Autodesk社の[Forge Viewer](https://developer.autodesk.com/en/docs/viewer/v2/overview/)はWebGLベースのJavaScriptライブラリーであり、他の[Forge API](https://developer.autodesk.com/)の機能と合わせて使うことで様々なツールで作成された2Dや3DデータをWebブラウザ上で表示することが出来ます。

本記事では、手元のFBXをクラウドにアップロードしてViwerを利用したWebページで表示するまでの一連の手順について説明します。

## Autodesk Forgeとは何か

> [Autodesk Forge プラットフォームとは](http://adndevblog.typepad.com/technology_perspective/2016/01/autodesk-forge-platfotrm.html)　(引用元: [Technology Perspective from Japan](http://adndevblog.typepad.com/technology_perspective/))
> Autodesk  Forge(http://forge.autodesk.com/) は、クラウドを利用して ‘接続された’ エコ システムを構築する サービス や ツール、製品のためのプラットフォームです。
>
> オートデスクのクラウド サービスを構成するさまざまなテクノロジを Web サービス API や SDK として提供する開発プラットフォーム、Forge を利用するアイデアや開発を助成するファンド、コンファレンスやミートアップ、フォーラムを通して開発コミュニティをバックアップするデベロッパ プログラムで構成されています。

幅広い概念なのでちょっと難しいですが、Autodesk社独自のPaaS的なサービスと理解しています。(間違っていたらご指摘下さい)

## 事前準備

+ Autodeskアカウントの取得
+ [Forge Developer](https://developer.autodesk.com/)ページへのサインイン
+ 自前のWebサーバー。もしくはホスティングサービス([GitHub Pages](https://pages.github.com/)など)のアカウント
+ [cURL](https://curl.haxx.se/)
+ [Forge Deliverable API](https://developer.autodesk.com/en/docs/model-derivative/v2)によりSVF formatへの変換が可能なファイル。変換可能なファイルのリストは[こちらを参照](https://developer.autodesk.com/en/docs/model-derivative/v2/overview/supported-translations/)
+ テキストエディタ
+ (Windowsで以下に挙げたコマンドをそのまま利用する場合は、Bash on Ubuntu on WindowsやMinGWやCygwinなどが必要になるかもしれません。筆者はMacを使いました)

## アップロードから表示までの手順

それでは本題である手元のFBXファイルをアップロードしてViwerを利用したWebページで表示するまでの一連の流れについて説明します。
![image](https://qiita-image-store.s3.amazonaws.com/0/39561/6ec2479c-8b02-62a4-7650-97140309e3dc.png)
手順はこのようなイメージです。

### 1. APIの登録

Forge Developerにログインした後、[My Apps](https://developer.autodesk.com/myapps)のページから"CREATE APP"を選択してアプリケーションを登録していきます。
本家の[Create an App](https://developer.autodesk.com/en/docs/oauth/v2/tutorials/create-app/)に書かれている内容と同等です。

#### 1.1. My Appsページ
![image](https://qiita-image-store.s3.amazonaws.com/0/39561/2bda9f78-5961-f397-6f4f-fd1cd0623e42.png)

#### 1.2. CREATE APPページ
![image](https://qiita-image-store.s3.amazonaws.com/0/39561/dea7713e-1832-cc7f-3f91-e960c2f467bb.png)

ここではGitHub Pagesを利用したTestという名前のアプリケーションを作ってみます。
今回は3-legged authenticationは使用しませんが、空欄のままだとアプリケーションが作成できないので、仮のものを入れておきます。

#### 1.3. Testアプリケーションのページ
![image](https://qiita-image-store.s3.amazonaws.com/0/39561/e757ed1c-b263-588c-f4ce-7548b6439b15.png)

次の2-Leggedトークンを取得する時に、ここの記載されているClient IDとClient Secretを使用します。

### 2. 2-Leggedトークンの取得

本家の[Get a 2-Legged Token](https://developer.autodesk.com/en/docs/oauth/v2/tutorials/get-2-legged-token/)に書かれている内容とほぼ同等ですが、ここの例では`scope=data:read`と書いてありますが、今回は後述するApp-Managed Bucketの作成とアップローを可能にするために`scope=bucket:create%20bucket:read%20data:write%20data:read`を設定しています。

ここからは、ターミナルを開いてcurlコマンドを使っていきます。

```bash
curl -v 'https://developer.api.autodesk.com/authentication/v1/authenticate' \
-X 'POST' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=[CLIENT_ID]&client_secret=[CLIENT_SECRET]&grant_type=client_credentials&scope=bucket:create%20bucket:read%20data:write%20data:read'
```
`[CLIENT_ID]`と`[CLIENT_SECRET]`は、上述のアプリケーションを登録した時に発行されたClient IDとClient Secretに置き換えてください。

コマンドが成功すると、次のような結果が返ってきます。

```bash
{"access_token":"[ACCESS_TOKEN]","token_type":"Bearer","expires_in":86399}
```

なお、上記の`[ACCESS_TOKEN]`のところには数十〜数百文字の文字列が入ってきます。
この`[ACCESS_TOKEN]`の値は今後の作業で頻繁に使うので、次のようにして環境変数に登録しておきます。

```bash
export ACCESS_TOKEN="[ACCESS_TOKEN]"
```

上記の`[ACCESS_TOKEN]`の値は前述の通り適宜書き換えて下さい。

### 3. ファイルのアップロード

#### 3.1. App-Managed Bucketの作成

本家の[Create an App-Managed Bucket and Upload a File](https://developer.autodesk.com/en/docs/data/v2/tutorials/app-managed-bucket/)に習って作業を進めていきます。
今後`bucketKey`の値を何度も使うことになるので、環境変数に登録しておきます。

```bash
export BUCKET_KEY="[BUCKET_KEY]"
```

上記の`[BUCKET_KEY]`の値は適宜好みの値に書き換えて下さい。
なお、この値は他のユーザーが作成した値と重複しない値にする必要があるようです。
重複すると次のコマンドを実行した時にエラーメッセージが返ってきます。

```bash
curl -v 'https://developer.api.autodesk.com/oss/v2/buckets' \
-X 'POST' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer '${ACCESS_TOKEN} \
-d '{"bucketKey":"'${BUCKET_KEY}'","policyKey":"persistent"}'
```

成功すると次のような結果が返ってきます。

```bash
{"bucketKey":"[BUCKET_KEY]","bucketOwner":"[BUCKET_OWNER]","createdDate":1489572965267,"permissions":[{"authId":"[AUTH_ID]","access":"full"}],"policyKey":"persistent"}%
```

なお上記の、`[BUCKET_KEY]`、`[BUCKET_OWNER]`そして`[AUTH_ID]`は、実際には固有の値が入っています。

### 3.2. App-Managed Bucketへのアップロード

作成したApp-Managed Bucketに、手元のファイルをアップロードしていきます。
今回利用するファイルは、カレントディレクトリにあることを想定しています。
例によって何度か利用することになるので、ファイル名を環境変数に登録しておきます。

```bash
export FILE_NAME="[FILE_NAME]"
```

ここで`[FILE_NAME]`は各自が用意したファイル名で置き換えて下さい。
なお、URL的にエスケープが必要な文字が含まれたファイル名は、試していませんが避けた方が良いと思います。

そして次のコマンドでアップロードします。

```bash
curl -v 'https://developer.api.autodesk.com/oss/v2/buckets/'${BUCKET_KEY}'/objects/'${FILE_NAME} \
-X 'PUT' \
-H 'Authorization: Bearer '${ACCESS_TOKEN} \
-H 'Content-Type: application/octet-stream'\
-H 'Content-Length: '`ls -l ${FILE_NAME} | awk '{print $5}'` \
-T ${FILE_NAME}
```

なお、途中に含まれている`ls -l ${FILE_NAME} | awk '{print $5}'`では、ファイルサイズを取得しています。
アップロードが成功すると、次のような結果が返ってきます。

```bash
{
  "bucketKey" : "[BUCKET_KEY]",
  "objectId" : "urn:adsk.objects:os.object:[BUCKET_KEY]/[FILE_NAME]",
  "objectKey" : "[FILE_NAME]",
  "sha1" : "[SHA1]",
  "size" : [FILE_SIZE],
  "contentType" : "application/octet-stream-H",
  "location" : "https://developer.api.autodesk.com/oss/v2/buckets/[BUCKET_KEY]/objects/[FILE_NAME]"
* Connection #0 to host developer.api.autodesk.com left intact
}* Rebuilt URL to: Content-Length: [FILE_SIZE]/
```

ここで、実際には`[BUCKET_KEY]`、`[FILE_NAME]`、`[SHA1]`および`[FILE_SIZE]`は各自の値が入っています。

### 4. SVF formatへの変換

ここからは、本家の[Prepare a File for the Viewer](https://developer.autodesk.com/en/docs/model-derivative/v2/tutorials/prepare-file-for-viewer/)に習って、App-Managed BucketにアップロードしたファイルをViewerが扱うことの出来るSVF formatに変換していきます。

#### 4.1. URNをURL-safe Base64 (no padding)に変換する

[このオンラインツール](http://www.freeformatter.com/base64-encoder.html)を使ってURNをBase64エンコードします。

URNの値は上記の例では次のようになっていました。
`urn:adsk.objects:os.object:[BUCKET_KEY]/[FILE_NAME]`
この文字列をエンコードすると、末尾に`=`や`==`が含まれた文字列になることがあります。
その場合、末尾の`=`や`==`を取り除いた値が、URL-safe Base64 (no padding)になり、これを利用していきます。

URL-safe Base64 (no padding)の値は何度も利用するので環境変数に登録しておきます。

```bash
export URN="[URN]"
```

ここで`[URN]`は各自のURL-safe Base64 (no padding)の値です。

#### 4.2. SVF formatへの変換

次のコマンドを使って、SVF formatへの変換を実行します。

```bash
curl -X 'POST' \
-H 'Authorization: Bearer '${ACCESS_TOKEN} \
-H 'Content-Type: application/json' \
-v 'https://developer.api.autodesk.com/modelderivative/v2/designdata/job' \
-d '{"input":{"urn":"'${URN}'"},"output":{"formats":[{"type":"svf","views":["2d","3d"]}]}}'
```

コマンドが成功すると、次のような結果が返ってきます。

```bash
{"result":"created","urn":"[URN]","acceptedJobs":{"output":{"formats":[{"type":"svf","views":["2d","3d"]}]}},"registerKeys":["[REGISTER_KEYS]"]}
```

ここで`[URN]`と`[REGISTER_KEYS]`は各自の値です。

## 5. ViewerのHTMLファイルを作成する

本家の[Basic Viewer](https://developer.autodesk.com/en/docs/viewer/v2/tutorials/basic-viewer/)の説明に習って、HTMLファイルを作成し、Webサーバーに配置します。

```html
<head>
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
    <meta charset="utf-8">

    <!-- The Viewer CSS -->
    <link rel="stylesheet" href="https://developer.api.autodesk.com/viewingservice/v1/viewers/style.min.css" type="text/css">

    <!-- Developer CSS -->
    <style>
        body {
            margin: 0;
        }
        #MyViewerDiv {
            width: 100%;
            height: 100%;
            margin: 0;
            background-color: #F0F8FF;
        }
    </style>
</head>
<body>

    <!-- The Viewer will be instantiated here -->
    <div id="MyViewerDiv"></div>

    <!-- The Viewer JS -->
    <script src="https://developer.api.autodesk.com/viewingservice/v1/viewers/three.min.js"></script>
    <script src="https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js"></script>

    <!-- Developer JS -->
    <script>
        var viewer;
        var options = {
            env: 'AutodeskProduction',
            accessToken: '<YOUR_APPLICATION_TOKEN>'
        };
        var documentId = 'urn:<YOUR_URN_ID>';
        Autodesk.Viewing.Initializer(options, function onInitialized(){
            Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
        });

        /**
        * Autodesk.Viewing.Document.load() success callback.
        * Proceeds with model initialization.
        */
        function onDocumentLoadSuccess(doc) {

            // A document contains references to 3D and 2D viewables.
            var viewables = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {'type':'geometry'}, true);
            if (viewables.length === 0) {
                console.error('Document contains no viewables.');
                return;
            }

            // Choose any of the avialble viewables
            var initialViewable = viewables[0];
            var svfUrl = doc.getViewablePath(initialViewable);
            var modelOptions = {
                sharedPropertyDbPath: doc.getPropertyDbPath()
            };

            var viewerDiv = document.getElementById('MyViewerDiv');
            viewer = new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv);
            viewer.start(svfUrl, modelOptions, onLoadModelSuccess, onLoadModelError);
        }

        /**
         * Autodesk.Viewing.Document.load() failuire callback.
         */
        function onDocumentLoadFailure(viewerErrorCode) {
            console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode);
        }

        /**
         * viewer.loadModel() success callback.
         * Invoked after the model's SVF has been initially loaded.
         * It may trigger before any geometry has been downloaded and displayed on-screen.
         */
        function onLoadModelSuccess(model) {
            console.log('onLoadModelSuccess()!');
            console.log('Validate model loaded: ' + (viewer.model === model));
            console.log(model);
        }

        /**
         * viewer.loadModel() failure callback.
         * Invoked when there's an error fetching the SVF file.
         */
        function onLoadModelError(viewerErrorCode) {
            console.error('onLoadModelError() - errorCode:' + viewerErrorCode);
        }

    </script>
</body>
</html>
```

ここで`<YOUR_APPLICATION_TOKEN>`は前述の`[ACCESS_TOKEN]`の値に置き換え、`<YOUR_URN_ID>`は同様に前述の`[URN]`(つまりURL-safe Base64 (no padding))に置き換えて下さい。
HTMLファイルが作成できたらWebサーバーに配置します。

## 6. Webブラウザで確認する

Webサーバーに配置したViewerのページをWebGLをサポートしているブラウザで開きます。
すると、少し間を置いてこのような画面を確認できると思います。
![image](https://qiita-image-store.s3.amazonaws.com/0/39561/b0cf784c-6544-53f9-e7d0-04e274cd538b.png)
画面下部にあるボタンやマウスを使って、視点を制御したり、断面形状を見たり、寸法を測ったりすることが出来ますのでぜひやってみて下さい。
もしもACCESS_TOKENやURNの設定ミスによってエラーが発生した場合は空白の表示になりますので、デベロッパーツールなどで確認してみて下さい。

## Forge APIの情報源

+ [Forge Developers](https://developer.autodesk.com/): 本家。アプリケーションの管理機能やドキュメントや各種サンプルが網羅されている
+ [Forge Community Blog](https://forge.autodesk.com/blog): 本家のブログ。いち早く情報をキャッチするならここ
+ [Technology Perspective from Japan](http://adndevblog.typepad.com/technology_perspective/): 日本のオートデスク株式会社のブログ。日本語で丁寧な解説が書かれている
 + [拙作のTechnology Perspective from Japanの索引](http://qiita.com/comocc/items/93ac6753d4facf8fb176): 上記ブログエントリーのA-Z順の索引