BIM/CIMデータのメタバース展示場を作ってみた　#2

# BIM/CIMデータのメタバース展示場を作ってみた　#2


みなさん、こんちにちは！  
この記事は[国交DPFアドベントカレンダー2023](https://qiita.com/advent-calendar/2023/mlit-dpf)に参加しています。本日は23日目です。  
今日は昨日に引き続き国土交通データプラットフォームから取得できるBIM/CIMデータ（IFC形式）をメタバースに表示させて、仮想空間にBIM/CIMの展示場を作ってみようと思います。  
昨日の記事をまだ見てないよという方はまずは[こちらの記事](https://qiita.com/dpf_hirometai/items/735772416775bbd4aa9d)を見てみてください。

<br>

# 昨日のおさらい
* 指定した期間の電子納品保管管理システムのBIM/CIMデータ(IFC形式)を取得

<br>

昨日のIFCデータ取得はうまくできましたでしょうか？  
今日はそのデータを使って、メタバースを作っていきます！  

<br>

# 今日やること  
* 取得したBIM/CIMデータ（IFC形式）をOBJ形式に変換する
* 変換したBIM/CIMデータをメタバースに投入して、展示場を作成する

<br>

それでは早速、昨日取得したIFCデータをメタバースに載せるための形式に変換したいと思います！  
その前に・・・今回変換をするにあたって、そもそも形式を変換する必要あるの？それはなんで？  
となる方もいるかと思いますので、簡単に説明しようと思います。  


<br>

### そもそもIFC形式って何なの？ 
IFC形式のデータって建築・土木に携わっている方以外だと中々普段見かけることは少ないかもしれませんね。  
IFC（Industry Foundation Classes）形式は、主に建築、エンジニアリング、建設業界で使用される、建築情報モデリング（BIM）データの共有および交換のためのオープンかつ標準化されたファイル形式のことです。 

<br>

よくわからないけど、建築・土木では最近よく使われている形式なんですね！なるほどです！  

<br>

### メタバースって最近よく聞くけど実際なんなの？  
メタバースについても簡単に紹介します。  
メタバースは、現実世界と仮想世界を融合させたデジタル空間のことです。  
メタバースは、3Dの仮想環境で構成されていて、VR（仮想現実）やAR（拡張現実）の技術を活用して、ユーザーにリアルな体験を提供することができます。  
近年注目されているWeb3技術の一貫として、ブロックチェーン技術との融合が期待されていますよね！  

<br>

今回はIFCデータをこのメタバースで表現してみよう！と、そういうことですね！  

<br>

### IFC形式のデータとメタバースって親和性はどうなの？    
IFC形式のデータもメタバースも同じ3Dという点では、一見して親和性が高いような気もしますよね。  
ただ、そんな単純な話ではないようです・・・。  
IFC形式は確かに標準的なフォーマットではあるんですが、一般的にメタバースではゲームエンジンや専用の3Dモデリングツールを使用しているので、互換性の問題やIFCがもともと建築情報をモデリングするために複雑化したデータ構造になっているという点から相性が良くないようです。  

<br>

じゃあそのままだと、メタバースには取り込めないってことなんですね・・・。

<br>

### じゃあどうすればいいのか？  
IFC形式のファイルをメタバースで使用可能なフォーマット（例：OBJ, FBX, GLTF）に変換するツールが公開されていますので、それらを活用して、メタバースと相性のいい3Dファイル形式に変換してあげます。  

<br>

:::note info
* IFC形式のファイルとメタバースは親和性が低い
* 変換ツールを使えばメタバースでも表示ができる
:::

<br>

## IFC形式のファイルを他の3Dファイル形式に変換  
IFC形式そのままではメタバースには投入できないことがわかったので、ここからはダウンロードしたIFCデータを変換していく作業をしていきましょう！  

<br>

### 変換ツールのダウンロード
今回は、「IfcOpenShell」というツールを使って変換をしてみたいと思います。  
ツールのダウンロードは[こちら](https://blenderbim.org/docs-python/ifcconvert/installation.html)から行えます。  
私はWindows 64bit環境なので、「build-win64」をダウンロードしました！  
ダウンロードしたZipファイルを解凍すると「IfcConvert.exe」という実行ファイルがあるので、このファイルを昨日Cドライブ直下に作成した「project」フォルダにコピーします。  

<br>

実行は以下のコマンドで行えます。
```cmd:cmd
C:\project>IfcConvert [options] <input.ifc> [<output>]
```
ただこれだと、1ファイルずつやらなきゃいけない・・・。  
これはめんどくさいですね。。  
 
<br>

なので、変換用の実行プログラムも書いてみました。  

<br>

### 変換実行プログラム  
今回はIFC形式からGLB形式への変換を行ってみます。  
プログラムには、大まかに以下のようなことを書いています。  
* 指定フォルダに格納されているIFC形式を指定したフォルダにGLBに変換して保存する。
* 保存先のフォルダがなければ新しく作成する。
* 変換の処理状況と完了時の処理件数、実行結果をコンソール上にログ出力する。  

<br>

```javascript
import axios from "axios";
import fs from "fs";
import { mkdir } from "fs/promises";
import { exec } from 'child_process'; 
import path from "path";
```  
まずは必要なライブラリをインポートします。今回はコマンド実行を行いますので、「child_process」をインポートしています。  

<br>

```javascript
const folderPath = '\IFC_2022-03'; // 変換するファイルがあるフォルダのパス
const convertedFolderPath = `${folderPath}_converted`; // 変換後のファイルを保存する新しいフォルダ

async function CreateFolders(foldername) {
  try {
    if (!fs.existsSync(foldername)) {
      await mkdir(foldername, {
        recursive: true,
      });
    }
  } catch (error) {
    console.error(`Error while creating folder ${foldername}: ${error.message}`);
  }
}
```  
変換元フォルダ（IFCファイルがあるフォルダ）と変換先のフォルダを指定してあげます。  
変換先のフォルダは存在しなければ、指定された名前で新しく作成します。  
CreateFolders関数を使って、変換したファイルを格納するためのフォルダを作成します。  

<br>

```javascript
function convertIfcToGlb(filePath, outputFilePath) {
  return new Promise((resolve, reject) => {
    console.log(`Converting: ${filePath} to ${outputFilePath}`);
    exec(`IfcConvert "${filePath}" "${outputFilePath}"`, (error, stdout, stderr) => {
      if (error) {
        console.error(`Error occurred in converting ${filePath}: ${error.message}`);
        reject(error);
        return;
      }
      if (stderr) {
        console.error(`Stderr for ${filePath}: ${stderr}`);
        reject(stderr);
        return;
      }
      console.log(`Conversion completed for ${filePath}`);
      resolve(stdout);
    });
  });
}
```  
convertIfcToGlb関数を使って、変換コマンドを実行する際の処理を記載してあげます。  
exec関数を使用してIfcConvertコマンドを実行します。このコマンドは、filePathで指定されたIFCファイルを outputFilePathで指定されたGLBファイルに変換しています。 
変換開始時に、変換元と変換先のファイルパスをコンソールに出力し、変換完了時には、成功したことを示すメッセージをコンソールに出力します。  

<br>

```javascript
async function convertAllIfcFiles(folderPath, convertedFolderPath) {
  let convertedCount = 0; 

  try {
    await CreateFolders(convertedFolderPath);
    const files = fs.readdirSync(folderPath);
    for (const file of files) {
      const filePath = path.join(folderPath, file);
      if (path.extname(file).toLowerCase() === '.ifc') {
        const outputFilePath = path.join(convertedFolderPath, path.basename(file, '.ifc') + '.glb');
        await convertIfcToGlb(filePath, outputFilePath);
        convertedCount++; 
      }
    }
    console.log(`All conversions completed. Total converted files: ${convertedCount}.`);
  } catch (error) {
    console.error(`Error while converting files: ${error.message}`);
  }
}

convertAllIfcFiles(folderPath, convertedFolderPath);
```  
最後に、指定されたフォルダ内の全てのIFCファイルをGLB形式に変換するための非同期関数を定義してあげます。  
先ほど記載したCreateFolders関数を非同期で呼び出して、convertedFolderPathで指定されたパスにフォルダを作成してあげます。  
fs.readdirSyncを使って、folderPathからファイルの一覧を同期的に取得します。  
全ての変換処理が完了したら、変換されたファイルの合計数をコンソールに出力します。  

<br>

全体はこんな感じです。  
```javascript
import axios from "axios";
import fs from "fs";
import { mkdir } from "fs/promises";
import { exec } from 'child_process'; 
import path from "path";

const folderPath = '\IFC_2022-03'; 
const convertedFolderPath = `${folderPath}_converted`; 

async function CreateFolders(foldername) {
  try {
    if (!fs.existsSync(foldername)) {
      await mkdir(foldername, {
        recursive: true,
      });
    }
  } catch (error) {
    console.error(`Error while creating folder ${foldername}: ${error.message}`);
  }
}

function convertIfcToGlb(filePath, outputFilePath) {
  return new Promise((resolve, reject) => {
    console.log(`Converting: ${filePath} to ${outputFilePath}`);
    exec(`IfcConvert "${filePath}" "${outputFilePath}"`, (error, stdout, stderr) => {
      if (error) {
        console.error(`Error occurred in converting ${filePath}: ${error.message}`);
        reject(error);
        return;
      }
      if (stderr) {
        console.error(`Stderr for ${filePath}: ${stderr}`);
        reject(stderr);
        return;
      }
      console.log(`Conversion completed for ${filePath}`);
      resolve(stdout);
    });
  });
}

async function convertAllIfcFiles(folderPath, convertedFolderPath) {
  let convertedCount = 0; 

  try {
    await CreateFolders(convertedFolderPath);
    const files = fs.readdirSync(folderPath);
    for (const file of files) {
      const filePath = path.join(folderPath, file);
      if (path.extname(file).toLowerCase() === '.ifc') {
        const outputFilePath = path.join(convertedFolderPath, path.basename(file, '.ifc') + '.glb');
        await convertIfcToGlb(filePath, outputFilePath);
        convertedCount++; 
      }
    }
    console.log(`All conversions completed. Total converted files: ${convertedCount}.`);
  } catch (error) {
    console.error(`Error while converting files: ${error.message}`);
  }
}

convertAllIfcFiles(folderPath, convertedFolderPath);
```  

<br>

## それでは実行してみましょう！  
Node.jsを使って実行してみます。  

<br>

コマンドプロンプトが起動できたら、今回のプログラムを実行するディレクトリに移動していきましょう。  
「project」フォルダに先ほど作成したスクリプトファイル「cals_ifc_convert.js」を配置しました。  

<br>
移動できたら以下のコマンドを入力して・・・  
<br>

```cmd:cmd
C:\project>node cals_ifc_convert.js
```  
<br>

<span style="font-size: 200%;">昨日と同様にEnterキーをどうぞ！</span>  

<br>

```cmd:cmd
C:\project>node cals_ifc_convert.js
Converting: IFC_2022-03\01本谷川-8(工事用道路).ifc to IFC_2022-03_converted\01本谷川-8(工事用道路).glb
Conversion completed for IFC_2022-03\01本谷川-8(工事用道路).ifc
Converting: IFC_2022-03\030_南線BOX.ifc to IFC_2022-03_converted\030_南線BOX.glb
Conversion completed for IFC_2022-03\030_南線BOX.ifc
Converting: IFC_2022-03\3号橋_伸縮装置.ifc to IFC_2022-03_converted\3号橋_伸縮装置.glb
Conversion completed for IFC_2022-03\3号橋_伸縮装置.ifc
///...
All conversions completed. Total converted files: 12.
```  
こんな感じで表示されたら<span style="font-size: 200%;">成功です！</span>  
対象の12ファイルすべて正常に変換できたみたいです。  

<br>

保存先フォルダを確認してみます。  

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/281ecaba-80ef-b25f-7dc6-cf214d0271f9.png" width="500px">  
<br>

GLB形式のデータがちゃんと入っていました！  
さて、やっとここまできました。  
変換したデータをメタバースに載せていきましょう！  
実はここまで結構めんどくさかったんですが、メタバースに載せる作業は結構簡単なんですｗ  

<br>

## メタバースで表示してみる！  
メタバース空間を作成するツールは、実は結構たくさんあります。  
今回紹介は割愛しますが、色々と調べてみてくださいね！  
そんな中でも今回は、「Spatial」というメタバースプラットフォームを使ってみようと思います。  
「Spatial」は、無料でメタバースを作成できて、かつ直感的な操作で空間を作っていくことができます。    
※有料プランもあります。  

<br>

Spatialの公式HPは[こちら](https://www.spatial.io/)  
まずは、アカウント登録をしてみてください。  
Spatialの概要やアカウント登録、空のメタバース空間を作成する手順は、[こちら](https://udemy.benesse.co.jp/design/3d/spatial.html)を参考にしてみてください。  

<br>

上記の参照記事を見て気づいた方もいるかもしれませんが、
SpatialでGLB形式のデータを表示させる方法は、「ドラッグ＆ドロップ」で終わっちゃうんです！  
すごく簡単ですよね？  
もちろん、アップロードした3Dデータの大きさを変えたり、向きを変えたりする操作は必要ですが、それも難しくないので、是非自分の好きなBIM/CIM空間を作成してみてください。  

ちなみに私はこんな感じで、それっぽい看板とオブジェクトを添えて、展示場っぽくしてみました。    
名付けて・・・  

### 「BIM/CIM Village」　　です！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/07b7d114-c089-d869-924c-7de488abdefb.png" width="500px">  

<br>

もともと展示場を作ると言っていたので、各BIM/CIMデータの近くには構造物や工事の名前を示したパネルを用意しました！  

<br>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/d545ba89-e1b2-4e8c-ed77-62bf66644e0d.png" width="500px"> 

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/75661a1a-fd2a-ab58-09aa-224fa371fb08.png" width="500px"> 


<br>

皆さんも素敵な展示場を作成することができましたか？  

<br>

# 最後に 
2日間にわたって、国土交通データプラットフォームから取得したIFCデータをメタバースで表示するということをやってきました。  
長くなってしまいすみませんでした。。  
IFCデータをメタバースで表現できることはこれから色々と活用の場もありそうです！今後も新しい技術がさらに出現してくると思いますが、今あるデータとどのような融合ができるのか楽しみですね！     
それでは、終盤にさしかかっていますが、引き続き国交DPFアドカレをお楽しみください！  
ありがとうございました！