マーターポート・ラボ　2021年

# Matterport Labs 2021 ([English](https://qiita.com/loic-meister-guild/items/59a2750f17fb554daaee))
[Matterport Labs](https://labs.matterport.com)のページは、Matterport自身の開発者が、__マーターポート・プラットフォーム__を使用して何を開発できるかについてのアイデアを示すための場所です。この記事の執筆時点で、3つのプロトタイプを使用できます：

1. [__Pano Painter__](https://labs.matterport.com/#/app/1): 水平の表面にのみテクスチャを適用し、どんな表面にでも色を付けるペイントツール。
1. [__Fly Through Generator__](https://labs.matterport.com/#/app/2): ユーザーがシーン内で選択した2点間のフライスルーを自動的に生成するパスファインダーツール。
1. [__Spatial Planner__](https://labs.matterport.com/#/app/3): 部屋ごとにシーン内の家具のレイアウトを計画するためのルームプランナーツール。

プロトタイプの作成に使用された一部の機能はSDK（およびAPI）の現在のバージョンでは実装してないため、これらのプロトタイプは__Matterport SDKではなく__、__マーターポート・プラットフォーム__を使用して開発できると書いてます。

と言うのは？
## マーターポート・プラットフォーム
__マーターポート・プラットフォーム__って何ですか？

__マーターポート・プラットフォーム__は、[__Showcase SDK__](https://support.matterport.com/hc/en-us/articles/360061477754)、[__Model API__](https://support.matterport.com/hc/en-us/articles/360041976053)、[チュートリアル](https://github.com/matterport/showcase-sdk-tutorial)のコードサンプル、SDKの[サンプル](https://github.com/matterport/showcase-sdk-examples)で構成されています。また、公式には配布されていませんが、ブラウザーの開発ツールから簡単にアクセスでき、SDKの未来のバージョンで公式に導入される可能性のある機能を示めします。

使用されるスタックには、次のテクノロジーが含まれます：

- Yarn
- TypeScript
- React
- Three.js（バージョン0.124.0以下）

アプリケーションに応じて他のライブラリと同様に。

でも、どうやってLabsアプリのコードを手に入れられますか？
## Labsアプリのコードとアセット
Labsアプリのコードは、ブラウザーの開発ツールから入手できます。

Chromeバージョン91.0.4472.114（公式ビルド）（x86_64）を使用していますが、Chromeでこれを行う方法は次のとおりです：

1) コードを表示するアプリケーションを起動します（この例では、[__Pano Painter__](https://labs.matterport.com/#/app/1)アプリケーションを使用します）。
2) 開発ツールを開きます。
3) `ソース`タブを選択します：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/307df144-dea4-9d61-1bd8-dce75fd99740.png)
4) そして`Page`タブを選択し、`top`アイテムをダブルクリックして中身を見ます：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/5fb5f8f6-19cb-6275-00f1-1ad9342b3e0b.png)
そこにはたくさんのものがあるので、もっとも面白い項目を開いてみましょう：

・ __labs.matterport.com__には、ルートページのアセットとバンドルされたコードが含まれていますが、今は触らずに置いておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/ab84befc-4ebc-6d9e-a929-f7c8cb0ba6a3.png)
・ __app-iframe (index.html)__＞__static.matterport.com__には、ペインターツールのアセットとバンドルされたコードが含まれています。これは探しているソースコードではないため、アセットのみ抽出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/94818a2e-cb7c-2efa-2cba-c1f2ae3f4a8a.png)
・ __app-iframe (index.html)__＞__webpack__には、探しているコードなどが含まれています。`./fonts/`には、アプリケーションで使用されるフォントが含まれています。`./src/`には、ペインターツールのコードが含まれています。 `../`には、このアプリケーション`@mp/common`および`@mp/save`で使用される2つのMatterportライブラリのコードが含まれています（後者は公式SDKでは使用できません）。`Hls/`および`datadisk/jenkins/workspace/sdk_examples-tags_painter-1.4.1/node_modules`には、ペインターアプリケーションによってインポートされたライブラリに関する情報が含まれています
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/09533243-4473-629b-3ba9-bc079c2635f4.png)
・ __app-iframe (index.html)__＞__sdk-iframe (showcase.html)__＞__static.matterport.com__には、[SDK Bundle](https://matterport.github.io/showcase-sdk/sdkbundle_installation.html)のアセットとバンドルコードが含まれています。LabsアプリケーションはSDKバンドルの新しいバージョンを使用しているため、これを抽出します。現在のバージョンのSDKバンドルのファイルをこれらに置き換える必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/2368aa04-1cab-074e-2cde-05dfc42dcbf1.png)
コードとアセットが見つかったので、それを実行するために新しいプロジェクトを構築する必要があります．．．
## 基本プロジェクト
Labsアプリケーションの基本プロジェクトを構築するには、[SDKバンドルチュートリアル](https://matterport.github.io/showcase-sdk/sdkbundle_tutorials_setup.html)のローカル環境のセットアップから始めましょう：

```
git clone https://github.com/matterport/showcase-sdk-tutorial.git
mv showcase-sdk-tutorial matterport-base
cd matterport-base 
curl https://static.matterport.com/showcase-sdk/bundle/3.1.42.14-24-gb057e5145/showcase-bundle.zip -o bundle.zip
unzip bundle.zip -d ./bundle
rm -rf bundle.zip
cd ..
```
次に、[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)からマーターポートライブラリを取得しましょう：

```
git clone https://github.com/matterport/showcase-sdk-examples.git
cd showcase-sdk-examples
yarn install
yarn install-bundle
cp -r packages/common ../matterport-base/common
cp -r packages/core ../matterport-base/core
cd ..
rm -rf showcase-sdk-examples
```
`index.html`ファイルにSDKキーを設定することを忘れないでください：

```html:index.html
// ...
  <iframe id="showcase"
          src="/bundle/showcase.html?m=22Ub5eknCVx&play=1&qs=1&log=0&applicationKey=PUT_YOUR_SDK_KEY_HERE"// ...
```
そして、`src/`フォルダの`src/index.ts`ファイルでもSDKキーを設定する：

```Typescript:src/index.ts
// ...
const key = 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
ライブラリーをインストールします：

```
yarn install
```
それでサーバーを起動します：

```
yarn start
```
ブラウザーで[`http://localhost:8000/`](http://localhost:8000/)を開いてください。

デバッグコンソールを開くと、次のように表示されます：
`Hello Bundle SDK!`

### 最小限のReactアプリ
LabsアプリケーションはReactとThree.jsを使用しているので、基本プロジェクトを改善しましょう。

まず、`src/index.ts`の名前を`src/index.tsx`に変更します：

```
mv src/index.ts src/index.tsx
```
次に、Reactアプリケーションを作成しましょう：

-  `index.tsx`は`Main`のReactコンポーネントをレンダリングします：

```TypeScript:src/index.tsx
import './main.css';

import React from 'react';
import * as ReactDom from 'react-dom';
import { Main } from './components/Main';

ReactDom.render((
  <Main
    onMount={onMount}
  />
), document.getElementById('content'));

async function onMount(modelSid: string) {

}
```
- メインのReactコンポーネントのファイルを作成します：

```TypeScript:src/components/Main.tsx
import React, { Component, Fragment } from 'react';
import { Frame } from './Frame';
import { Dict } from '@mp/core';
const defaultSid = 'j4RZx7ZGM6T';
type Props = {
  onMount?(modelSid: string): void;
};
type State = {
  sdk: any | undefined;
  error: string | undefined;
  inProgress: boolean;
  warning: string;
};
export class Main extends Component<Props, State> {
  private modelSid: string;
  private apiHost: string;
  private applicationKey: string;
  private iframeRef = React.createRef<HTMLIFrameElement>();
  constructor(props: Props) {
    super(props);
    const urlParams = new URLSearchParams(window.location.search);
    this.modelSid = urlParams.get('m') || defaultSid;
    this.apiHost  = urlParams.get('apiHost') || '';
    this.applicationKey = urlParams.get('applicationKey') || 'PUT_YOUR_SDK_KEY_HERE';
    this.state = {
      sdk: undefined,
      error: undefined,
      inProgress: false,
      warning: '',
    };
  }
  componentDidMount() {
    if (this.props.onMount) {
      this.props.onMount(this.modelSid);
    }
    connectSdk(
      this.iframeRef.current,
      this.applicationKey,
      (sdk) => this.onSdkConnect(sdk),
      (error) => this.setState({ error })
    );
  }
  componentWillUnmount() {
  }
  async onSdkConnect(sdk: State['sdk']) {
    type Sweep = {
      alignmentType: string;
    };
    const sweepCollection: Dict<{ aligned: boolean; }> = {};
    sdk.Sweep.data.subscribe({
      onAdded(index: string, item: Sweep) {
        sweepCollection[index] = { aligned: item.alignmentType === 'aligned' };
        setAlignedWarning();
      }
    });
    const currentPose: { sweep: string } = {
      sweep: '',
    }
    sdk.Camera.pose.subscribe({
      onChanged(pose: { sweep: string; }) {
        if (currentPose.sweep !== pose.sweep) {
          currentPose.sweep = pose.sweep;
          if (sweepCollection[currentPose.sweep]) {
            setAlignedWarning();
          }
        }
      }
    });
    const setAlignedWarning = () => {
      const currentSweep = sweepCollection[currentPose.sweep];
      if (currentSweep) {
        this.setState({
          warning: currentSweep.aligned ? '' : 'Navigate to an aligned sweep to use the paint tools',
        });
      }
    }
  }
  render() {
    return (
      <Fragment>
        <Frame
          getRef={this.iframeRef}
          modelSid={this.modelSid}
          apiHost={this.apiHost}
          applicationKey={this.applicationKey}
        />
      </Fragment>
    );
  }
}
function connectSdk(iframe: HTMLIFrameElement, applicationKey: string, onConnect: (sdk: State['sdk']) => void, onError: (error: string) => void): void {
  iframe.addEventListener('load', async function () {
    try {
      const sdk = await (iframe.contentWindow as any).MP_SDK.connect(iframe, applicationKey, '3.5');
      onConnect(sdk);
    } catch (e) {
      onError(e);
    }
  });
}
```
- __iframe__をレンダリングするReactコンポーネントのファイルを作成します：

```TypeScript:src/components/Frame.tsx
import React, { Component } from 'react';
interface Props {
  src: string;
}
export class Frame extends Component<Props, {}> {
  render() {
    return (
      <div className='frame'>
        <iframe id='sdk-iframe' className='frame' src={this.props.src + '&title=0&qs=1&hr=0&brand=0&help=0'}></iframe>
      </div>
    );
  }
}
```
- 基本的なCSSファイルを作成します：

```CSS:src/main.css
#sdk-iframe {
  width: 100vw;
  height: 100vh;
}

body {
  -ms-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  overflow: hidden;
}
```
##### TypeScript設定
以下を`compilerOptions`に追加します：

```JSON:tsconfig.json
    "experimentalDecorators": true,
    "jsx": "react",
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
```
`compilerOptions`の`lib`設定を変更します：

```JSON:tsconfig.json
    "lib": [
      "dom",
      "es2015"
    ],
```
`include`設定を変更します：

```JSON:tsconfig.json
  "include": [
    "src",
    "src/**/*.ts",
    "@types/**/*.d.ts",
  ]
```
##### Webpack設定
エントリ名を`index.ts`から`index.tsx`に変更します：

```JavaScript:webpack.config.js
entry: {
    app: './src/index.tsx',
  },
```
ReactとTypeScriptのファイル拡張子を追加します：

```JavaScript:webpack.config.js

  resolve: {
    extensions: ['.js', '.jsx', '.json', '.ts', '.tsx']
  },
```
モジュールローダーのルールを変更します：

```JavaScript:webpack.config.js
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        loader: 'ts-loader'
      },
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ]
  },
```
#### マーターポートライブラリー

ほとんどのマーターポートのライブラリーは、[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)から入手できます：

- `@mp/bundle-sdk`ライブラリーのコードは`packages/bundle/`フォルダーにあります。
- `@mp/common`ライブラリーのコードは`packages/common/`フォルダーにあります。
- `@mp/core`ライブラリーのコードは`packages/core/`フォルダーにあります。

ただし、バージョンは__Matterport Labs__のアプリケーションで使用されているバージョンとはおそらく異なり、`@mp/save`ライブラリーのコードは[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)にないため、ブラウザーから抽出する必要があります。

ブラウザからアプリケーションのコードを抽出したのと同じ方法で、ライブラリコードを抽出できます。ソースファイルを右クリックして、`名前を付けて保存`を選択するだけです。

- `@mp/common`ライブラリのコード：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/d6967cb4-3d4e-4734-ad4e-fd913acc7384.png)
※ マウスカーソルを制御するコードは、[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)の`@mp/common`ライブラリーコードにはないため、この機能を使用できるようにするには、`common/src/index.ts`ファイルと`common/src/MouseCursor.ts`ファイルを抽出する必要があります。

- `@mp/save`ライブラリのコード：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/631d768d-855d-457e-c8db-0d1486de44f9.png)

#####  @mp/saveライブラリーの再構築

`@mp/save`ライブラリーコードを再構築するために、Chromeからファイルを抽出し、それらの階層を再作成しましょう：

```
+ save/
    + mp_labs_save/
        + save/
            + dexie/
                | dexie.js
            | App.ts
            | AppMessages.ts
            | Page.ts
            | PageMessages.ts
```
次に、ライブラリーのルートフォルダで、`@mp/core`ライブラリーから`package.json`と`tsconfig.json`をコピーします：

```
+ save/
    + mp_labs_save/
        + save/
            + dexie/
                | dexie.js
            | App.ts
            | AppMessages.ts
            | Page.ts
            | PageMessages.ts
    | package.json
    | tsconfig.json
```

`package.json`ファイル内のTypeScriptモジュールルートファイルの名前とパスを変更します。

```JSON:package.json
{
  "name": "@mp/save",
  "version": "1.0.0",
  "main": "mp_labs_save/save/index.ts",
  "scripts": {
  }
}
```
最後に、TypeScriptモジュールのルートファイルを作成する必要があります。`@mp/core`ライブラリから`core/src/index.js`ファイルを複製しましょう：

```TypeScript:index.ts
import * as App from './AppModule';
import * as Page from './PageModule';
import { Dict } from './types';

export { App, Page, Dict }
```
ライブラリのタイプ階層を正しく構造化するには、2つの追加ファイルが必要です。1つは`App`のタイプを定義するためのものです：

```TypeScript:AppModule.ts
export { OutdatedData, ISaveRequestHandler, AppSaver } from './App';
export { AppMsgType, SchemaDescriptor, SchemaResponse, SaveResponse, MigrateResponse, PayloadMap } from './AppMessages';
```
もう1つは、`Page`のタイプ用に：

```TypeScript:PageModule.ts
export { DatabaseConfT, openDb, ISaveObserver, Metadata, AppSaveRequester, setupMockMessageButtons } from './Page';
export { PageMsgType, SchemaRequest, SaveRequest, LoadRequest, MigrateRequest, PayloadMap } from './PageMessages';
```
`index.ts`ファイルも、通常は同じフォルダーで定義され、`type.js`と呼ばれる型を使用するため、`@mp/core`ライブラリから`core/src/type.js`ファイルをコピーするだけです。

そして、以下の様になります：

```
+ save/
    + mp_labs_save/
        + save/
            + dexie/
                | dexie.js
            | App.ts
            | AppMessages.ts
            | AppModule.ts
            | index.ts
            | Page.ts
            | PageMessages.ts
            | PageModule.ts
            | types.ts
    | package.json
    | tsconfig.json
```
##### 「No license field」ワーニング
`warning package.json: No license field`メッセージを回避するには、下記の行を：

```JSON:package.json
...
  "license": "UNLICENSED",
...
```
各ライブラリーフォルダーの`package.json`ファイルの中にコピーします：

- `bundle/package.json`
- `common/package.json`
- `core/package.json`
- `save/package.json`

#### Nodeライブラリー
`yarn`を使用して必要なライブラリーを追加します：

```
yarn add react@16.12.0
yarn add @types/react@16.9.19
yarn add react-dom@16.12.0
yarn add @types/react-dom@16.9.5
yarn add three@0.124.0
yarn add @types/three
yarn add source-map-loader@0.2.4
yarn add css-loader
yarn add style-loader@1.1.3
yarn add webpack-dev-server
yarn add hls.js
yarn add classnames
yarn add dexie
```
### 設定
また、さまざまな設定ファイルを変更する必要があります：

##### package.json
まず、`package.json`ファイルのアプリエントリポイントへのパスを更新しましょう：

```JSON:package.json
...
  "main": "./src/index.tsx",
...
```
##### webpack.config.js
Webpack設定ファイルのアプリエントリポイントパスと出力パスも更新しましょう：

```JavaScript:webpack.config.js
// ...
  entry: './src/index.tsx',
  output: {
    filename: 'js/[name].bundle.js'
  },
// ...
```
##### tsconfig.json
次に、TypeScript設定ファイルを更新しましょう：

```JSON:tsconfig.json
...
    "experimentalDecorators": true,
    "jsx": "react",
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "lib": [
      "dom",
      "es2015"
    ],
  },
  "include": [
    "src",
    "src/**/*.ts",
    "@types/**/*.d.ts",
  ]
```
##### Yarn
また、マーターポートライブラリーのコードをyarnレジストリにリンクして、それらを使用できるようにする必要があります：

```
cd core
yarn link
cd ..
yarn link @mp/core
cd common
yarn link
yarn link @mp/core
cd ..
yarn link @mp/common
cd bundle
yarn link
cd ..
yarn link @mp/bundle-sdk
cd save
yarn link
cd ..
yarn link @mp/save
```
`package.json`にローカルライブラリーの情報を挿入します：

```JSON:package.json
    "@mp/core": "^1.0.0",
    "@mp/common": "^1.0.0",
    "@mp/bundle-sdk": "^1.0.0",
    "@mp/save": "^1.0.0",
```
`common`ライブラリーフォルダーの`common/src/index.ts`ファイルにSDKキーを設定することを忘れないでください：

```Typescript:common/src/index.ts
// ...
export const sdkKey = 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
そして、`src`ライブラリーフォルダーの`src/components/Main.tsx`ファイルにもSDKキーを設定します：

```Typescript:src/components/Main.tsx
// ...
this.applicationKey = urlParams.get('applicationKey') || 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
### サンプルテスト
サンプルをテストするために開発サーバーを起動しましょう：

```
yarn start
```
ブラウザーで[`http://localhost:8000/`](http://localhost:8000/)を開いてください。
### ラボアプリの抽出
最後に、基本プロジェクトから3つのLabsアプリケーションのプロジェクトを作成しましょう：

```
cd .. 
cp -r matterport-base matterport-pano-painter
cp -r matterport-base matterport-fly-through-generator
cp -r matterport-base matterport-spatial-planner
```
これで、ブラウザ開発ツールから抽出できるコードで、ローカルで起動できるアプリケーションを作成しましょう．．．
## Pano Painter
Matterport Labsアプリの[__Pano Painter__](https://labs.matterport.com/#/app/1)は水平の表面にのみテクスチャを適用し、どんな表面にでも色を付けるペイントツールです。
### コード
まず、ブラウザからコードを抽出しましょう。ソースファイルを右クリックし、メニューで`名前を付けて保存`を選択して、対応する`matterport-pano-painter/src/[…]`パスにファイルを保存します。__TypeScript__ファイルは、処理せずにそのままコピーできます：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/03e49b48-9ca4-3038-87f3-363111ffcaee.png)
以下の様になります：

```
+ matterport-pano-painter/
    + src/
        + components/
            + Controls/
                + styles/
                    | button.css
                    | color-picker.css
                    | history.css
                    | paint-controls.css
                    | range.css
                    | texture-controls.css
                | Button.tsx
                | ColorPicker.tsx
                | HistoryControls.tsx
                | PaintControls.tsx
                | Range.tsx
                | TextureControls.tsx
            + KeyWatcher/
                | KeyWatcher.ts
            + Save/
                | PaintSaver.ts
                | TextureSaver.ts
                | index.ts
            + SceneNodes/
                + SceneComponents/
                    | BrushCursor.ts
                    | BrushInput.ts
                    | Dropper.ts
                    | PaintTarget.ts
                    | TextureSelector.ts
                    | TextureTarget.ts
                    | shader.ts
                | CameraOverrideNode.ts
                | DropperNode.ts
                | PaintNode.ts
                | TextureNode.ts
            | Frame.tsx
            | HistoriedPaintScene.ts
            | History.ts
            | Main.tsx
            | preventDefault.ts
        | fonts.css
        | index.tsx
        | main.css

```
ただし、コンパイルしようとすると、`src/components`にファイルがないことを示す2種類のエラーが発生します：

- `TS2307: Cannot find module '[…]/PointerButtonEvent' or its corresponding type declarations.`
- `TS2339: Property '[button|device|down|position]' does not exist on type 'never'.`

したがって、存在しないモジュールファイルを再構築する必要があります：

```TypeScript:src/components/PointerButtonEvents.ts
import {
  IVector2,
  PointerButton,
  PointerButtonEvent,
  PointerButtonMask,
  PointerDevice,
  PointerMoveEvent,
} from '@mp/common';

export class PointerButtonEventData implements PointerButtonEvent {
  readonly id: number;
  readonly position: IVector2;
  readonly button: PointerButton;
  readonly down: boolean;
  readonly device: PointerDevice;
}
export class PointerMoveEventData implements PointerMoveEvent {
  readonly id: number;
  readonly position: IVector2;
  readonly buttons: PointerButtonMask;
  readonly device: PointerDevice;
}
```

#### CSS
ただし、CSSファイルには、以下のファイルのように、単純なCSSではなくJavaScriptコードを処理するWebpackのモジュールが含まれています：

```JavaScript:src/main.css
// Imports
var ___CSS_LOADER_API_IMPORT___ = require("../../../node_modules/css-loader/dist/runtime/api.js");
var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require("-!../../../node_modules/css-loader/dist/cjs.js!./font.css");
exports = ___CSS_LOADER_API_IMPORT___(false);
exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
// Module
exports.push([module.id, "#sdk-iframe {\n  width: 100vw;\n  height: 100vh;\n}\n\nbody {\n  -ms-user-select: none;\n  -webkit-user-select: none;\n  user-select: none;\n  overflow: hidden;\n}\n\n.banner {\n  position: fixed;\n  bottom: calc(100% - 2em);\n  width: 100%;\n  height: 2em;\n  line-height: 2em;\n  background-color: #f5f4f3;\n  padding-left: 8px;\n  transition: bottom .5s .5s;\n}\n\n.banner.collapsed {\n  bottom: 100%;\n}\n\n.banner.error {\n  background-color: #ff3158;\n}\n\n/* debug */\nbody.has-saves {\n  overflow: scroll;\n  overflow-x: hidden;\n}\n\n.save {\n  margin: 0 32px;\n  width: 64px;\n  height: 25px;\n}\n\n.saves img {\n  width: 128px;\n  height: 128px;\n}\n", ""]);
// Exports
module.exports = exports;
```
次のようなステートメントで`exports`にプッシュされた配列の2番目の引数文字列に含まれているCSSルールを抽出する必要があります：

```CSS:*.css
exports.push([module.id, "[…]", ""]);
```
抽出する文字列の内容は次のようになります：

```String:src/main.css
#sdk-iframe {\n  width: 100vw;\n  height: 100vh;\n}\n\nbody {\n  -ms-user-select: none;\n  -webkit-user-select: none;\n  user-select: none;\n  overflow: hidden;\n}\n\n.banner {\n  position: fixed;\n  bottom: calc(100% - 2em);\n  width: 100%;\n  height: 2em;\n  line-height: 2em;\n  background-color: #f5f4f3;\n  padding-left: 8px;\n  transition: bottom .5s .5s;\n}\n\n.banner.collapsed {\n  bottom: 100%;\n}\n\n.banner.error {\n  background-color: #ff3158;\n}\n\n/* debug */\nbody.has-saves {\n  overflow: scroll;\n  overflow-x: hidden;\n}\n\n.save {\n  margin: 0 32px;\n  width: 64px;\n  height: 25px;\n}\n\n.saves img {\n  width: 128px;\n  height: 128px;\n}\n
```
したがって、ここで、`\n`を実際の`newline`文字に置き換える必要があります：

```CSS:src/main.css
#sdk-iframe {
  width: 100vw;
  height: 100vh;
}

body {
  -ms-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  overflow: hidden;
}

.banner {
  position: fixed;
  bottom: calc(100% - 2em);
  width: 100%;
  height: 2em;
  line-height: 2em;
  background-color: #f5f4f3;
  padding-left: 8px;
  transition: bottom .5s .5s;
}

.banner.collapsed {
  bottom: 100%;
}

.banner.error {
  background-color: #ff3158;
}

/* debug */
body.has-saves {
  overflow: scroll;
  overflow-x: hidden;
}

.save {
  margin: 0 32px;
  width: 64px;
  height: 25px;
}

.saves img {
  width: 128px;
  height: 128px;
}
```
次に、残りのCSSファイルを処理する必要があります．．．ペインターアプリには、2つのディレクトリにCSSファイルがあります。

- `src/`
- `src/component/Controls/styles/`

すべてのCSSファイルをリストしましょう：

```
+ matterport-pano-painter/
    + src/
        + components/
            + Controls/
                + styles/
                    | button.css
                    | color-picker.css
                    | history.css
                    | paint-controls.css
                    | range.css
                    | texture-controls.css
        | fonts.css
        | main.css
```
`src/fonts.css`ファイルにはさらに処理が必要です。

-  ファイルの先頭にある `@font-face`ディレクティブで使用するフォントファイルへのURLパスを参照するため、以下のURLパスプレースホルダーを置き換える必要があります：

```CSS:src/fonts.css
...
@font-face {
  font-family: 'labs-paint';
  src:
    url(" + ___CSS_LOADER_URL_REPLACEMENT_0___ + ") format('truetype'),
    url(" + ___CSS_LOADER_URL_REPLACEMENT_1___ + ") format('woff'),
    url(" + ___CSS_LOADER_URL_REPLACEMENT_2___ + ") format('svg');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}
...
```
ファイルの先頭にそれぞれのパスが定義されている場合：

```CSS:src/fonts.css
...
var ___CSS_LOADER_URL_IMPORT_0___ = require("../fonts/labs-paint.ttf?caj1pz");
var ___CSS_LOADER_URL_IMPORT_1___ = require("../fonts/labs-paint.woff?caj1pz");
var ___CSS_LOADER_URL_IMPORT_2___ = require("../fonts/labs-paint.svg?caj1pz");
...
```
結果のCSSルールは次のようになります：

```CSS:src/fonts.css
@font-face {
  font-family: 'labs-paint';
  src:
    url("../fonts/labs-paint.ttf") format('truetype'),
    url("../fonts/labs-paint.woff") format('woff'),
    url("../fonts/labs-paint.svg") format('svg');
  font-weight: normal;
  font-style: normal;
  font-display: block;
}
...
```
- また、文字列のコンテンツをエンコードするために使用される無関係な`\`文字を、`\" `を`"`に、`\\`を`\`に置き換えることにより、削除する必要があります：

```CSS:src/fonts.css
...
.icon-dropper-fill:before {
  content: "\e901";
}
.icon-dropper-outline:before {
  content: "\e908";
}
.icon-dropper:before {
  content: "\e902";
}
.icon-bucket-fill:before {
  content: "\e906";
}
.icon-bucket:before {
  content: "\e907";
}
.icon-brush:before {
  content: "\e900";
}
.icon-history:before {
  content: "\e904";
}
.icon-checkmark:before {
  content: "\e905";
}
.icon-chevron-down:before {
  content: "\e909";
}
...
```
しかし、残念ながら、アイコンはまだ正しく表示されません...
### アセット
Labsアプリケーションには、フォントまたは画像用のアセットファイルが必要です。ブラウザからも抽出します．．．
##### フォント
まず、フォントをダウンロードしましょう。ソースファイルを右クリックし、メニューで`名前を付けて保存`を選択して、対応する`matterport-pano-painter/fonts/[…]`パスにファイルを保存します：

```
+ matterport-pano-painter/
    + fonts/
        | labs-paint.svg
        | labs-paint.ttf
        | labs-paint.woff
```
##### イメージ
次に、画像をダウンロードしましょう。ソースファイルをクリックし、表示された画像を右クリックして、メニューで`名前を付けて保存`を選択し、対応する`matterport-pano-painter/assets/[…]`パスにファイルを保存します：

```
+ matterport-pano-painter/
    + assets/
        | brick1.jpg
        | carpet1.jpg
        | hardwood1.jpg
        | plywood1.jpg
```
##### バンドルのイメージ
最後に、ペインターアプリケーションバンドルの追加画像をiframeからダウンロードする必要もあります：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/f66cf686-3e69-9457-7d54-1e76b08403fe.png)
そして、それらを対応する`matterport-pano-painter/bundle/images/[…]`パスに保存します：

```
+ matterport-pano-painter/
    + bundle/
        + images/
            | Roboto-Bold.png
            | dot_default_2.png
            | dot_selected_2_2.png
            | line_default_2.png
            | line_selected_2_2.png
            | line_default_2.png
            | line_selected_2_2.png
```
##### モデルSID
また、Labsアプリケーションで使用されるモデルの`AAWs9eZ9ip6`__SID__を使用することはできませんが、同じモデルにアクセスできる別の`j4RZx7ZGM6T`を使用することはできます：

```TypeScript:src/components/Main.tsx
// ...
const defaultSid = 'j4RZx7ZGM6T';
// ...
```
### サンプル
作成したローカルLabsアプリケーションを試してみましょう！
#### インストール
Node.jsモジュールをインストールします：

```
yarn install
```
マーターポートライブラリーをリンクします：

```
cd core
yarn link
cd ..
yarn link @mp/core
cd common
yarn link
yarn link @mp/core
cd ..
yarn link @mp/common
cd bundle
yarn link
cd ..
yarn link @mp/bundle-sdk
cd save
yarn link
cd ..
yarn link @mp/save
```
`package.json`にローカルライブラリーの情報を挿入します：

```JSON:package.json
    "@mp/core": "^1.0.0",
    "@mp/common": "^1.0.0",
    "@mp/bundle-sdk": "^1.0.0",
    "@mp/save": "^1.0.0",
```
`common`ライブラリフォルダの`common/src/index.ts`ファイルにSDKキーを設定することを忘れないでください：

```Typescript:common/src/index.ts
// ...
export const sdkKey = 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
そして、`src`ライブラリフォルダの`src/components/Main.tsx`ファイル：

```Typescript:src/components/Main.tsx
// ...
this.applicationKey = urlParams.get('applicationKey') || 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
※ このサンプルを作成するときに、SDKキーを使用してCORSエラーとアクセスの問題が発生したため、Labsアプリの`08s53auxt9txz1w6hx2iww1qb`を使用しました。

#### 起動
サンプルを起動します：

```
yarn start
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/945dbe9a-c53c-f57d-f9d0-3745a69dfce2.png)
ギットハブで__マーターポート・ラボ__の[Pano Painterのサンプル](https://github.com/loic-meister-guild/pj-matterport-labs-2021/tree/main/matterport-pano-painter)．．．
## Fly Through Generator
Matterport Labsアプリの[__Fly Through Generator__](https://labs.matterport.com/#/app/2)はユーザーがシーン内で選択した2点間のフライスルーを自動的に生成するパスファインダーツールです。
### コード
まず、ブラウザからコードを抽出しましょう。ソースファイルを右クリックし、メニューで`名前を付けて保存`を選択して、対応する`matterport-fly-through-generator/src/[…]`パスにファイルを保存します。__TypeScript__ファイルは、処理せずにそのままコピーできます：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/03e49b48-9ca4-3038-87f3-363111ffcaee.png)
以下の様になります：

```
+ matterport-fly-through-generator/
    + src/
        + react-components/
            | Frame.tsx
            | Main.tsx
            | PlayControl.tsx
            | SpeedControl.tsx
        + scene-components/
            | AxisContril.ts
            | DirtyTracker.ts
            | Grid.ts
            | Grip.ts
            | HighlightPoint.ts
            | NearestSweep.ts
            | PathBuilder.ts
            | PathBuilder2.ts
            | PathVisualizer.ts
            | SceneNodeObserver.ts
            | Sphere.ts
            | SplineCamera.ts
            | SweepVisualizer.ts
            | TunnelVisualizer.ts
        | AppContext.ts
        | AppFSM.ts
        | Application.ts
        | AppSerializer.ts
        | EnvContext.ts
        | index.tsx
        | SdkService.ts
```
#### Nodeライブラリー
`yarn`で次のライブラリーを追加します：

```
yarn add @material-ui/core
yarn add @material-ui/icons
yarn add @tweenjs/tween.js
yarn add rxjs
yarn add xstate@4.10.0
```
#### マーターポートライブラリー
Pano Painterアプリと同様に、ほとんどのMatterportライブラリーは[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)から入手できます：

- `@mp/bundle-sdk`ライブラリのコードは`packages/bundle/`フォルダーのなかにあります
- `@mp/common`ライブラリのコードは`packages/common/`フォルダーのなかにあります
- `@mp/core`ライブラリのコードは`packages/core/`フォルダーのなかにあります

ただし、バージョンは__Matterport Labs__のアプリケーションで使用されているバージョンとはおそらく異なり、`@mp/save`ライブラリーのコードは[Showcase SDK examples](https://github.com/matterport/showcase-sdk-examples)にないため、ブラウザーから抽出するまたはPano Painterアプリからコピーする必要があります。
#### src/interface.tsの再構築
ただし、コンパイルしようとすると、`src/`にファイルがないことを示す3種類のエラーが発生します：

- `TS2307: Cannot find module '[…]/interfaces' or its corresponding type declarations.`
- `TS2339: Property '[composer|debug|nodeFactory]' does not exist on type 'ComponentContext'.`
- `TS2305: Module '"../interfaces"' has no exported member '[IDisposable|PathPoint]'.`

したがって、存在しないファイルを再構築する必要があります：

```TypeScript:src/interfaces.ts
import { BehaviorSubject } from 'rxjs';
import { ISceneNode, SceneComponent } from '@mp/common';
import { ObservableValue } from '@mp/core';
import { Box3, Material, Mesh, MeshStandardMaterial, Vector3 } from 'three';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';
import { Quaternion } from "three"
import { FSMEvent, FSMSchema } from './AppFSM';
import { Interpreter } from 'xstate';


export type PathPoint = {
    position: Vector3;
    floorPosition: Vector3;
    id: string;
}
export type PathState = {
    path: ObservableValue<any[]>,
    pathLength: ObservableValue<Number>,
    validPath: ObservableValue<any>,
}
export type EditState = {
    transformMode: ObservableValue<String>,
    focus: ObservableValue<Vector3>,
    focusTarget: ObservableValue<any>,
    hoverTarget: ObservableValue<any>,
    boxTransform: {
        position: ObservableValue<Vector3>,
        rotation: ObservableValue<Quaternion>,
        scale: ObservableValue<Vector3>,
    },
    startPos: ObservableValue<Vector3>,
    endPos: ObservableValue<Vector3>,
    pathState: {
        path: ObservableValue<any[]>,
        pathLength: ObservableValue<Number>,
        validPath: ObservableValue<boolean>,
    },
    visualizerState: {
        duration: ObservableValue<number>,
    }
}

export type EditMode = 'translate'| 'rotate'|'scale';

export type BoxTransformState = {
    position: ObservableValue<Vector3>;
    rotation: ObservableValue<Quaternion>;
    scale: ObservableValue<Vector3>; 
}

// From src/scene-components/PathBuilder.ts
export type Sweep = {
    floor: number;
    neighbors: string[];
    position: Vector3;
    rotation: Vector3;
    uuid: string;
    id: string;
    alignmentType: String;
}

// From the Spatial Planner sample's src/types.ts
export type IEnvContext = {
    apiHost: string;
    applicationKey: string;
    sid: string;
}

// From the Showcase SDK sample'S packages/inspector/src/Scene.ts
export interface IContext {
    editState: EditState;
    fsm: Interpreter<any, FSMSchema, FSMEvent, any>;
}

export interface IScene {
    readonly objects: BehaviorSubject<ISceneNode[]>;
    readonly widget: SceneComponent | null;
    readonly cameraInput: SceneComponent | null;
    readonly sensor: any;

    /**
     * This function deserializes the provided string into scene nodes.
     * Additionally, it starts the scene nodes right away.
     * @param serialized serialized scene objects
     */
    deserialize(serialized: string): Promise<void>;

    /**
     * Serialize the entire scene to a string.
     */
    serialize(): Promise<string>;
    addObject(node: ISceneNode): void;
    removeObject(node: ISceneNode): void;

    /**
     * Restores the inspector to the it's default state by removing all non-inspector scene nodes.
     */
    reset(): void;
}

export interface IDisposable {
    dispose(): void;
}

export interface IRoom {
    name: string;
    center: Vector3;
    radius: number;
    allMeshes: Mesh[];
    bbox: Box3;
    damMeshes: Mesh[];

    getMaterial(): MeshStandardMaterial;
    getEdgesMaterial(): LineMaterial;
    setVisible(visible: boolean): void;
    setObjectsVisible(visible: boolean): void;
    setObjectsInteractable(interactable: boolean): void;
    setDamVisible(visible: boolean): void;
    setGreyBoxVisible(visible: boolean): void;
    setMaterial(chunkType: ChunkType, material: Material, edges: boolean): IDisposable;
    setMaterial2(chunkType: ChunkType, material: Material, edges: boolean): void;
    setInteractable(interactable: boolean): void;
    meshForChunk(chunkType: ChunkType): Mesh[];
}

export enum ChunkType {
    Wall = 'wall',
    Ceiling = 'ceiling',
    Floor = 'floor',
    Door = 'door',
    Other = 'other',
    FloorCollider = 'FloorCollider',
}

export enum Materials {
    Edges = 'Edges',
    Invisible = 'Invisible',
    SolidMaterial = 'SolidMaterial',
}

export enum MeshDrawStyle {
    Basic = 'Basic',
    GreyBox = 'GreyBox',
    Transitioning = 'Transitioning',
}

export interface IRoomBuilder extends IRoom {
    addMesh(type: ChunkType, mesh: Mesh): void;
    addDamMesh(mesh: Mesh): void;
    addObject(node: ISceneNode, bbox: Box3, disposables: IDisposable[]): void;
    removeObject(node: ISceneNode): void;
    removeAllObjects(): void;
}

export type SelectingStateVisualProps = {
    roomHoverColor: number;
    roomUnhoverColor: number;
    roomHoverOpacity: number;
    roomUnhoverOpacity: number;
    edgesHoverColor: number;
    edgesUnhoverColor: number;
    edgesHoverOpacity: number;
    edgesUnhoverOpacity: number;
    edgesHoverWidth: number;
    edgesUnhoverWidth: number;
};

export type AppState = {
    phase: any;
    rooms: Map<string, IRoomBuilder>;
    roomSelection: ObservableValue<IRoom | null>;
    objectSelection: ObservableValue<ISceneNode | null>;
    meshDrawStyle: ObservableValue<MeshDrawStyle>;
    roomHover: ObservableValue<IRoom[]>;
    clippingHeight: ObservableValue<number>;
    sketchPainter: ObservableValue<SceneComponent | null>;
    selectingStateVisualProps: SelectingStateVisualProps;
}
```

### 設定
Webpack設定ファイルも編集しないといけません：

##### webpack.config.js
コピープラグインを使用して、バンドルの`fonts`フォルダーをコピーしましょう：

```JavaScript:webpack.config.js
// ...
    new CopyPlugin([
      {
        from: 'node_modules/@mp/bundle-sdk',
        to: 'bundle'
      },
      { from: 'assets', to: 'assets'},
      { from: 'node_modules/@mp/bundle-sdk/fonts', to: 'fonts'},
    ]),
// ...
```
### サンプル
作成したローカルLabsアプリケーションを試してみましょう！
#### インストール
Node.jsモジュールをインストールします：

```
yarn install
```
マーターポートライブラリーをリンクします：

```
cd core
yarn link
cd ..
yarn link @mp/core
cd common
yarn link
yarn link @mp/core
cd ..
yarn link @mp/common
cd bundle
yarn link
cd ..
yarn link @mp/bundle-sdk
cd save
yarn link
cd ..
yarn link @mp/save
```
`package.json`にローカルライブラリーの情報を挿入します：

```JSON:package.json
    "@mp/core": "^1.0.0",
    "@mp/common": "^1.0.0",
    "@mp/bundle-sdk": "^1.0.0",
    "@mp/save": "^1.0.0",
```
`common`ライブラリフォルダの`common/src/index.ts`ファイルにSDKキーを設定することを忘れないでください：

```Typescript:common/src/index.ts
// ...
export const sdkKey = 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
そして、`src`ライブラリフォルダの`src/components/Main.tsx`ファイル：

```Typescript:src/components/Main.tsx
// ...
this.applicationKey = urlParams.get('applicationKey') || 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
※ このサンプルを作成するときに、SDKキーを使用してCORSエラーとアクセスの問題が発生したため、Labsアプリの`08s53auxt9txz1w6hx2iww1qb`を使用しました。

#### 起動
サンプルを起動します：

```
yarn start
```

残念ながら、サンプルは正しく機能せず、ヒントはブラウザのJavaScriptコンソールにあります：

```
AppSerializer.ts:44 Uncaught (in promise) components must be an array
```
ギットハブで__マーターポート・ラボ__の[Fly-Through Generator sample](https://github.com/loic-meister-guild/pj-matterport-labs-2021/tree/main/matterport-fly-through-generator)．．．
## Spatial Planner
Matterport Labsアプリの[__Spatial Planner__](https://labs.matterport.com/#/app/3)は部屋ごとにシーン内の家具のレイアウトを計画するためのルームプランナーツールです。
### コード
まず、ブラウザからコードを抽出しましょう。ソースファイルを右クリックし、メニューで`名前を付けて保存`を選択して、対応する`matterport-spatial-planner/src/[…]`パスにファイルを保存します。__TypeScript__ファイルは、処理せずにそのままコピーできます：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/03e49b48-9ca4-3038-87f3-363111ffcaee.png)
以下の様になります：

```
+ matterport-spatial-planner/
    + src/
        + commands/
            | AddObjectCommand.ts
            | AddObjectToRoomCenterCommand.ts
            | AddSketchCommand.ts
            | ClearSelectionCommand.ts
            | DownloadImageCommand.ts
            | MoveCameraCommand.ts
            | RemoveObjectSelectionCommand.ts
            | SetGizmoModeCommand.ts
            | SetMeshDrawStyleCommand.ts
            | UpdateobjectCommand.ts
        + react-components/
            | AddObjectButton.tsx
            | AddObjectModal.tsx
            | AppRoot.tsx
            | BundleIframe.tsx
            | CommandProvider.tsx
            | DimensionEdit.tsx
            | DrawStyleprovider.tsx
            | EditMode.tsx
            | EditSizeModal.tsx
            | EnvContext.tsx
            | FSMContextProvider.tsx
            | FurnitureItem.tsx
            | GizmoModeprovider.tsx
            | ImageButton.styles.tsx
            | ImageButon.tsx
            | Main.tsx
            | NumberEdit.tsx
            | ObjectSelectionProvider.tsx
            | Objectselector.tsx
            | RoomSelectionProvider.tsx
            | SelectRoom.tsx
            | TopLeftControls.tsx
        + scene-components/
            | AnimatedValue.ts
            | BloomEffect.ts
            | ClippingPlane.ts
            | Draggablesurface.ts
            | EdgesMesh.ts
            | EventDispatcher.ts
            | FabricRenderer.ts
            | ItemView.ts
            | Plane.ts
            | PlaneDragHelper.ts
            | RoomComponent.ts
            | Selectable.ts
            | SelectableTint.ts
            | Skydome.ts
            | Slots.ts
            | SmoothTranslation.ts
            | SnapAxis.ts
            | SnapToFloor.ts
            | TransformGizmo.ts
            | TransformGizmo2.ts
        | AnimatedNumberPool.ts
        | Application2.ts
        | ApplicationFSM.ts
        | AppSerializer.ts
        | AssetMap.ts
        | CommandContext.ts
        | DrawStyleContext.ts
        | EditingClickSpy.ts
        | FSMContext.ts
        | GizmoModeContext.ts
        | graphql.ts
        | HideElement.ts
        | HoverSpy.ts
        | index.tsx
        | interfaces.ts
        | MaterialAnimator.ts
        | MaterialStacks.ts
        | MeshDrawStyleUpdater.ts
        | ObjectSelectionContext.ts
        | react-app-env.d.ts
        | Room.ts
        | RoomLoader.ts
        | RoomSelectionContext.ts
        | Roomupdater.ts
        | SelectionClickSpy.ts
        | SelectionMediator.ts
        | SharedMaterials.ts
        | types.tsx
        | util.ts
```
#### Nodeライブラリー
`yarn`で次のライブラリーを追加します：

```
yarn add @material-ui/core
yarn add @material-ui/styles
yarn add @tweenjs/tween.js
yarn add rxjs
yarn add xstate@4.10.0
yarn add fabric
yarn add @types/fabric
yarn add lottie-react
yarn add tweakpane@2.4.3
yarn add jspdf
```
### アセット
Spatial Plannerでは、​​他のソースファイルのようにブラウザから直接抽出できないJSONデータファイルをインポートする必要があります。
### assets/labs_loader.jsonを再構築
`assets/labs_loader.json`ファイルは`src/react-components/AppRoot.tsx`ファイルにインポートされます：

```TypeScript:src/react-components/AppRoot.tsx
// ...
import animation from '../../assets/labs_loader.json';
// ...
```
したがって、存在しないファイルを再構築する必要があります。最終的な`main.bundle.js`ファイル内にそのデータが含まれてるはずです：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/65c52ce1-c2fd-f56f-36e9-0c86b69c0020.png)
`assets/labs_loader.json`ファイルの内容を文字列から解析する`JSON.parse('`ステートメントを探し、次の1つの行を取得します：

```JavaScript:js/main.bundle.js
// ...
    t.exports = JSON.parse('{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.21","a":"Nick Woods","k":"","d":"loader for Matterport Labs","tc":""},"fr":29.9700012207031,"ip":0,"op":91.000003706506,"w":400,"h":400,"nm":"Loader","ddd":1,"assets":[{"id":"comp_0","layers":[{"ddd":1,"ind":1,"ty":4,"nm":"Cube 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":75,"s":[0]},{"t":106.000004317469,"s":[60]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":0,"k":[779,500,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":60,"s":[0,0]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":90,"s":[200,200]},{"t":120.0000048877,"s":[0,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784313725,0.956862745098,0.952941176471,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60.0000024438501,"op":960.000039101602,"st":60.0000024438501,"bm":0},{"ddd":1,"ind":2,"ty":4,"nm":"Cube 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":0,"k":0,"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":0,"k":[500,500,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":30,"s":[0,0]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":60,"s":[200,200]},{"t":90.0000036657751,"s":[0,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784313725,0.956862745098,0.952941176471,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30.0000012219251,"op":930.000037879677,"st":30.0000012219251,"bm":0},{"ddd":1,"ind":3,"ty":4,"nm":"Cube","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"rx":{"a":0,"k":0,"ix":8},"ry":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":15,"s":[0]},{"t":45.0000018328876,"s":[-60]}],"ix":9},"rz":{"a":0,"k":0,"ix":10},"or":{"a":0,"k":[0,0,0],"ix":7},"p":{"a":0,"k":[220,500,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[0,0]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":30,"s":[200,200]},{"t":60.0000024438501,"s":[0,0]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.960784313725,0.956862745098,0.952941176471,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}]}],"fonts":{"list":[{"fName":"IBMPlexSans-Medium","fFamily":"IBM Plex Sans","fStyle":"Medium","ascent":73.9990234375}]},"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Outline","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[350,350],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.61568627451,0.61568627451,0.61568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":60.0000024438501,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[100]},{"t":90.0000036657751,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":5,"nm":"loading","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199.73,112.564,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":36,"f":"IBMPlexSans-Medium","t":"LOADING","j":2,"tr":200,"lh":43.2,"ls":0,"fc":[0.616,0.616,0.616]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Source animation","refId":"comp_0","sr":0.5,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200,220,0],"ix":2},"a":{"a":0,"k":[500,500,0],"ix":1},"s":{"a":0,"k":[30,30,100],"ix":6}},"ao":0,"w":1000,"h":1000,"ip":30.0000012219251,"op":91.000003706506,"st":30.0000012219251,"bm":0}],"markers":[],"chars":[{"ch":"L","size":36,"style":"Medium","w":51.3,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[48.3,0],[48.3,-10],[19.9,-10],[19.9,-69.8],[8.6,-69.8],[8.6,0]],"c":true},"ix":2},"nm":"L","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"L","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"O","size":36,"style":"Medium","w":71.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.7,1.566],[-2.634,3.034],[-1.434,4.5],[0,5.867],[1.433,4.5],[2.633,3.034],[3.7,1.567],[4.533,0],[3.7,-1.566],[2.633,-3.033],[1.433,-4.5],[0,-5.866],[-1.434,-4.5],[-2.634,-3.033],[-3.7,-1.566],[-4.534,0]],"o":[[3.7,-1.566],[2.633,-3.033],[1.433,-4.5],[0,-5.866],[-1.434,-4.5],[-2.634,-3.033],[-3.7,-1.566],[-4.534,0],[-3.7,1.567],[-2.634,3.034],[-1.434,4.5],[0,5.867],[1.433,4.5],[2.633,3.034],[3.7,1.566],[4.533,0]],"v":[[47.95,-1.15],[57.45,-8.05],[63.55,-19.35],[65.7,-34.9],[63.55,-50.45],[57.45,-61.75],[47.95,-68.65],[35.6,-71],[23.25,-68.65],[13.75,-61.75],[7.65,-50.45],[5.5,-34.9],[7.65,-19.35],[13.75,-8.05],[23.25,-1.15],[35.6,1.2]],"c":true},"ix":2},"nm":"O","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[2.2,0.938],[1.566,1.773],[0.866,2.544],[0,3.213],[0,0],[-0.867,2.544],[-1.567,1.773],[-2.2,0.938],[-2.734,0],[-2.234,-0.938],[-1.567,-1.773],[-0.867,-2.544],[0,-3.211],[0,0],[0.866,-2.544],[1.566,-1.773],[2.233,-0.938],[2.666,0]],"o":[[-2.2,-0.938],[-1.567,-1.773],[-0.867,-2.544],[0,0],[0,-3.211],[0.866,-2.544],[1.566,-1.773],[2.2,-0.938],[2.666,0],[2.233,0.938],[1.566,1.773],[0.866,2.544],[0,0],[0,3.213],[-0.867,2.544],[-1.567,1.773],[-2.234,0.938],[-2.734,0]],"v":[[28.2,-10.205],[22.55,-14.27],[18.9,-20.745],[17.6,-29.378],[17.6,-40.422],[18.9,-49.053],[22.55,-55.528],[28.2,-59.594],[35.6,-61],[42.95,-59.594],[48.65,-55.528],[52.3,-49.053],[53.6,-40.422],[53.6,-29.378],[52.3,-20.745],[48.65,-14.27],[42.95,-10.205],[35.6,-8.8]],"c":true},"ix":2},"nm":"O","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"O","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"A","size":36,"style":"Medium","w":67.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[64,0],[40.2,-69.8],[26,-69.8],[2.2,0],[13.7,0],[19.8,-18.9],[45.9,-18.9],[52.2,0]],"c":true},"ix":2},"nm":"A","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[43.1,-28.6],[22.5,-28.6],[32.6,-59.4],[33.1,-59.4]],"c":true},"ix":2},"nm":"A","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"A","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"D","size":36,"style":"Medium","w":68.3,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-3.634,1.467],[-2.567,2.9],[-1.4,4.367],[0,5.8],[1.4,4.367],[2.566,2.9],[3.633,1.467],[4.466,0],[0,0]],"o":[[0,0],[4.466,0],[3.633,-1.466],[2.566,-2.9],[1.4,-4.366],[0,-5.8],[-1.4,-4.366],[-2.567,-2.9],[-3.634,-1.466],[0,0],[0,0]],"v":[[8.6,0],[33.3,0],[45.45,-2.2],[54.75,-8.75],[60.7,-19.65],[62.8,-34.9],[60.7,-50.15],[54.75,-61.05],[45.45,-67.6],[33.3,-69.8],[8.6,-69.8]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-3.2,-3.266],[0,-6.333],[0,0],[3.2,-3.266],[5.2,0]],"o":[[0,0],[0,0],[5.2,0],[3.2,3.267],[0,0],[0,6.334],[-3.2,3.267],[0,0]],"v":[[19.9,-10],[19.9,-59.8],[33.3,-59.8],[45.9,-54.9],[50.7,-40.5],[50.7,-29.3],[45.9,-14.9],[33.3,-10]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"D","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"I","size":36,"style":"Medium","w":41.6,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[36,0],[36,-9.2],[26.4,-9.2],[26.4,-60.6],[36,-60.6],[36,-69.8],[5.6,-69.8],[5.6,-60.6],[15.1,-60.6],[15.1,-9.2],[5.6,-9.2],[5.6,0]],"c":true},"ix":2},"nm":"I","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"I","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"N","size":36,"style":"Medium","w":71.4,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.2,0],[62.8,0],[62.8,-69.8],[52,-69.8],[52,-16.4],[51.7,-16.4],[44,-31.2],[21.2,-69.8],[8.6,-69.8],[8.6,0],[19.4,0],[19.4,-53.4],[19.7,-53.4],[27.4,-38.6]],"c":true},"ix":2},"nm":"N","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"N","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"},{"ch":"G","size":36,"style":"Medium","w":70.5,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0.9,-1.642],[1.5,-1.106],[2,-0.569],[2.2,0],[3.366,3.547],[0,6.492],[0,0],[-3.367,3.547],[-5.534,0],[-2.8,-2.042],[-1.334,-3.28],[0,0],[4.333,2.759],[6.266,0],[3.733,-1.566],[2.666,-3.066],[1.466,-4.5],[0,-5.8],[-1.434,-4.5],[-2.567,-3.033],[-3.534,-1.566],[-4.134,0],[-3.234,2.216],[-0.734,3.373],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,2.212],[-0.9,1.642],[-1.5,1.106],[-2,0.57],[-5.534,0],[-3.367,-3.547],[0,0],[0,-6.491],[3.366,-3.547],[4.266,0],[2.8,2.042],[0,0],[-2.4,-4.92],[-4.334,-2.759],[-4.6,0],[-3.734,1.567],[-2.667,3.067],[-1.467,4.5],[0,5.867],[1.433,4.5],[2.566,3.034],[3.533,1.566],[5.333,0],[3.233,-2.216],[0,0],[0,0]],"v":[[53.5,0],[63.4,0],[63.4,-37.2],[38.1,-37.2],[38.1,-27.5],[52.5,-27.5],[52.5,-22.07],[51.15,-16.289],[47.55,-12.167],[42.3,-9.655],[36,-8.8],[22.65,-14.12],[17.6,-29.178],[17.6,-40.622],[22.65,-55.68],[36,-61],[46.6,-57.936],[52.8,-49.953],[62,-55.339],[51.9,-66.859],[36,-71],[23.5,-68.65],[13.9,-61.7],[7.7,-50.35],[5.5,-34.9],[7.65,-19.35],[13.65,-8.05],[22.8,-1.15],[34.3,1.2],[47.15,-2.123],[53.1,-10.506],[53.5,-10.506]],"c":true},"ix":2},"nm":"G","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"G","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"IBM Plex Sans"}]}')
// ...
```
そして、文字列の内容を新しい`assets/labs_loader.json`としてプロジェクトに保存するだけです！
### 設定
Webpack設定ファイルも編集しないといけません：

##### tsconfig.json

```JSON:tsconfig.json
...
    new CopyPlugin([
      {
        from: 'node_modules/@mp/bundle-sdk',
        to: 'bundle'
      },
      { from: 'assets/*', to: './'},
      { from: 'bundle/images/*', to: './images/'}
    ]),
...
```
### サンプル
作成したローカルLabsアプリケーションを試してみましょう！
#### インストール
Node.jsモジュールをインストールします：

```
yarn install
```
マーターポートライブラリーをリンクします：

```
cd core
yarn link
cd ..
yarn link @mp/core
cd common
yarn link
yarn link @mp/core
cd ..
yarn link @mp/common
cd bundle
yarn link
cd ..
yarn link @mp/bundle-sdk
cd save
yarn link
cd ..
yarn link @mp/save
```
`package.json`にローカルライブラリーの情報を挿入します：

```JSON:package.json
    "@mp/core": "^1.0.0",
    "@mp/common": "^1.0.0",
    "@mp/bundle-sdk": "^1.0.0",
    "@mp/save": "^1.0.0",
```
`common`ライブラリフォルダの`common/src/index.ts`ファイルにSDKキーを設定することを忘れないでください：

```Typescript:common/src/index.ts
// ...
export const sdkKey = 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
そして、`src`ライブラリフォルダの`src/components/Main.tsx`ファイル：

```Typescript:src/components/Main.tsx
// ...
this.applicationKey = urlParams.get('applicationKey') || 'PUT_YOUR_SDK_KEY_HERE';
// ...
```
※ このサンプルを作成するときに、SDKキーを使用してCORSエラーとアクセスの問題が発生したため、Labsアプリの`08s53auxt9txz1w6hx2iww1qb`を使用しました。
#### 起動
サンプルを起動します：

```
yarn start
```
残念ながら、サンプルは正しく機能せず、ヒントはブラウザのJavaScriptコンソールにあります：

```
AppSerializer.ts:44 Uncaught (in promise) components must be an array
```
ギットハブで__マーターポート・ラボ__の[Spatial Plannerのサンプル](https://github.com/loic-meister-guild/pj-matterport-labs-2021/tree/main/matterport-spatial-planner)．．．