2021년 10월 4일 월요일

CDE 개발 지원 오픈소스 BIM 라이브러리 IFC.js 분석 및 사용방법

이 글은 CDE(Common Data Environment) 개발 지원 BIM(Building Information Modeling) 라이브러리 IFC.js 분석 및 사용방법을 간략히 설명한다. IFC는 3차원 건설 정보 모델로 건설 부재의 객체별로 형상과 속성을 저장할 수 있다. 


IFC.js는 최근 개발된 IFC관련 라이브러리로 이를 이용한 개발 사례가 조금씩 나타나고 있다.
IFC.js 활용 BIM-GIS 기반 CityLite 개발 사례

IFC.js 소개
IFC.js는 BIM 개방형 표준파일포맷인 IFC(Industry Foundation Classes)파일을 로딩, 디스플레이 및 편집할 수 있는 자바스크립트 라이브러리이다. 이 라이브러리는 안토니오 곤잘레스(Antonio Gonzalez Viegas)에 의해 개발이 주도되고 있다. 참고로, 그는 스페인 2018년 ETSAS 건축과를 졸업하고, BIM 연구 개발 경험이 많은 디지털 아키텍처이며, 재즈를 좋아한다.
IFC는 읽고 쓰기 어려운 복잡한 포맷이다. 실제 IFC 구조는 다음과 같다. 이 파일 포맷을 해석(parse, 파싱)하기 위한 여러 라이브러리가 이미 오래전부터 있었다. IFC.js는 웹기반으로 동작되는 순수 Javascript와 빠른 렌더링 성능을 지원하는 C++ WASM(Web assembly. 예제)기술을 이용해 개발되었다. 이를 통해 웹브라우져에서도 60 fps까지 렌더링 처리 된다. 그러므로, 스마트폰을 포함한 모든 컴퓨터 디스플레이에서 IFC.js 기능을 사용할 수 있다.

소스코드는 아래 github에 있으며, node, npm으로 실행할 수 있다.

설치
node.js, npm 패키지를 설치한다. 윈도우, 우분투, 리눅스 모두 가능하다.
node.js 설치(윈도우 버전 경우)

우분투 리눅스나 윈도우 파워쉘 터미널에서 아래 명령을 실행한다.
node -v
npm -v

버전이 제대로 출력되면, 아래 명령을 실행한다.
cd web-ifc-viewer
npm i yarn
혹시 설치 안되면, 몇몇 라이브러리와 버전 불일치 발생할 수 있으니 다음 명령으로 설치한다. 
npm install --legacy-peer-deps
npm install --force

실행 화면(터미널)
실행한다.
npm run init-repo
npm run start

다음 링크를 클릭해, 클롬에서 열어본다.
그럼, 다음과 같은 IFC BIM viewer 화면을 확인할 수 있다. 

IFC.js 사용 방법
뷰어에서 왼쪽 side bar에서 파일 열기 메뉴를 통해, IFC 파일을 로딩할 수 있다.

주요 API 사용 방법은 다음과 같다.
// 라이브러리 임포트
import { IfcLoader } from 'web-ifc-three';
import { Scene } from 'three';

// THREE.js scene 생성
const scene = new Scene();

// IFC 로딩 및 scene 추가
const ifcLoader = new IfcLoader();
ifcLoader.load(ifcURL, (geometry) => scene.add(geometry));

이외에 BIM 속성 검색, 자료값 얻기, 섹션 표시 등 다양한 기능을 지원한다.

BIM 뷰어 화면 구조 분석
index.html은 다음과 같이 간단히 정의되어 있다. side bar와 viewer 요소가 정의되어 있다.
<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="stylesheet" type="text/css" href="./styles.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>IFC.js</title>
  </head>
  <body>
    <aside class="side-menu" id="side-menu-left"></aside>
      <div id="viewer-container"></div>
    <script type="module" src="./build/main.js"></script>
  </body>
</html>

화면 구조는 다음과 같다.

대부분의 기능은 main.js 자바스크립트에 구현되어 있다. 
이 main.js는 10만라인이 넘게 구현된 자바스크립트 파일이며, IFC로딩, 렌더링, 이벤트처리, 계산 등 대부분의 기능이 구현되어 있다.
시퀀스 구조 분석
주요 시퀀스 구조는 다음과 같다.
1. HTML 렌더링
2. VIEWER WEBGL 렌더링
3. OPEN FILE 이벤트 발생
4. loadIFC(url)
5. url이 가리키는 파일로 부터 파싱해 IFC element 데이터 구조 생성
6. 렌더링 가속을 위해 IFC element의 geometry 추출하여 메쉬(mesh) 그래픽 구조 생성
7. load 완료 시 scene 렌더링 루프에 추가
8. scene의 geometry의 vertex buffer를 WASM에 전달해 WebGL로 렌더링
 
주요 루틴은 다음과 같다.
class IFCLoader extends Loader {
  load(url, onLoad, onProgress, onError) {
    const scope = this;
    const loader = new FileLoader(scope.manager);
    loader.setPath(scope.path);
    loader.setResponseType('arraybuffer');
    loader.setRequestHeader(scope.requestHeader);
    loader.setWithCredentials(scope.withCredentials);
    loader.load(url, async function (buffer) {

      try {
        onLoad(await scope.parse(buffer));  // 1. IFC 데이터가 있는 buffer를 파싱함
      }
    }, onProgress, onError);
  }

  parse(buffer) {
    return this.ifcManager.parse(buffer);    // 2. ifcManager 객체의 parse 함수 호출
  }
}

async parse(buffer) {   // 파일 구조 해석
  if (this.state.api.wasmModule === undefined)
    await this.state.api.Init();
  await this.newIfcModel(buffer);  // Ifc모델 생성
  this.loadedModels++;
  return this.loadAllGeometry();    // Geometry 정보 해석
}

async loadAllGeometry() {
  await this.saveAllPlacedGeometriesByMaterial();   // Mesh 그래픽 구조 생성
  return this.generateAllGeometriesByMaterial();
}

async saveAllPlacedGeometriesByMaterial() {
  const flatMeshes = await this.state.api.LoadAllGeometry(this.currentWebIfcID);
  for (let i = 0; i < flatMeshes.size(); i++) {
    const flatMesh = flatMeshes.get(i);
    const placedGeom = flatMesh.geometries;
    for (let j = 0; j < placedGeom.size(); j++) {
      await this.savePlacedGeometry(placedGeom.get(j), flatMesh.expressID);
    }
  }
}

이벤트 처리는 다음과 같다.
const loadButton = createSideMenuButton('./resources/folder-icon.svg');
loadButton.addEventListener('click', () => {
  loadButton.blur();
  inputElement.click();  // IFC 파일 열기
});

const sectionButton = createSideMenuButton('./resources/section-plane-down.svg');
sectionButton.addEventListener('click', () => {
  sectionButton.blur();
  viewer.toggleClippingPlanes();  // 클리핑 처리
});

const dropBoxButton = createSideMenuButton('./resources/dropbox-icon.svg');
dropBoxButton.addEventListener('click', () => {
  dropBoxButton.blur();
  viewer.openDropboxWindow();  // 드롭박스 처리
});

const handleKeyDown = (event) => {
  if (event.code === 'Delete') {
    // viewer.removeClippingPlane();
    viewer.dimensions.delete();     // 치수 삭제
    viewer.context.ifcCamera.unlock();
  }
  if (event.code === 'KeyH') {
    viewer.context.ifcCamera.goToHomeView();  // 카메라 홈 위치로 이동
  }
  if (event.code === 'KeyD') {
    viewer.context.ifcCamera.setNavigationMode(NavigationModes.FirstPerson);  // 1인칭 시점 카메라 이동
  }
  if (event.code === 'Escape') {
    window.onmousemove = viewer.IFC.prePickIfcItem;  // Ifc 요소 선택 모드
  }
  if (event.code === "KeyP") {
    viewer.context.ifcCamera.toggleProjection();  // 카메라 프로젝션 모드
  }
};

정적 구조 분석
정적 구조를 분석하기 위해 UML(Unified Modeling Language)로 역설계해 보았다.
패키지 구조

앞서 설명된 라이브러리 이외에 애니메이션 엔진인 GSAP(GreenSock animation platform) 코드가 일부 사용되었다.
GASP

주요 클래스는 다음과 같다. IFCManager가 IfcAPI, model, parser 등 전체를 관리하고, Loader가 파일을 파싱하는 구조이다.

카메라 등 애니메이션 처리는 GreenSock을 사용한다. 치수 등 측정을 지원하기 위한 클래스가 구현되어 있다. 

main.js 모듈은 기타 벡터, 행렬 등 유틸리티 클래스와 함수가 정의되어 있다.

BIM 객체 속성 표시 기능 구현해 보기
앞서 분석된 내용을 참고하여, BIM 객체의 속성을 표시하는 기능을 간단히 구현해 본다. 우선, 각 소스코드를 아래와 같이 편집한다.

// web-ifc-viewer\example\index.html 파일을 열고, 아래와 같이 section, textarea 요소 추가
  ...
  <body>
    <aside class="side-half-menu" id="side-menu-left"></aside>

    <div id="viewer-container"></div>
    <section class="panel-botton" id="side-panel-bottom">
      <textarea class="textarea-bottom" id="textarea-bottom" spellcheck="false" readonly>test</textarea>
    </section>    
    <script type="module" src="./build/main.js"></script>
  </body>

// web-ifc-viewer\example\styles.css 파일을 열고, 아래 style을 추가함
...
.side-menu {
    z-index: 1;
    position: fixed;
    background-color: #36393F;
    height: 100vh;
}

.side-half-menu {
    z-index: 1;
    position: fixed;
    background-color: #36393F;
    height: 80vh;
}

.panel-botton {
    z-index: 1;
    position: fixed;    
    background-color: #FFB555;
    width: 100%;
    height: 20vh;    
}

.textarea-bottom {
    width: 100%;
    height: 80%; 
}

.side-sub-menu {
    width: 100%;
    height: inherit;
    margin: 2px;
}

// E:\Projects\web-ifc-viewer\example\build.js 파일을 열고, ondblclick 이벤트 위치에 아래 코드를 추가함
...
    window.onkeydown = handleKeyDown;
    window.ondblclick = async () => {
      viewer.clipper.createPlane();

      const item = await viewer.IFC.pickIfcItem(true);  // 현재 마우스 클릭 위치의 Ifc element pick해 얻기
      if(item.modelID === undefined || item.id === undefined ) return;

      const pset = await viewer.IFC.getProperties(item.modelID, item.id, true);
      console.log(pset);

      var data = "";
      const text = document.getElementById('textarea-bottom'); // 2021.10. Pset 출력 text area 획득

      for(var i = 0; i < pset.type.length; i++) {
        data = data + pset.type[i].constructor.name + '\n';
      }

      for(var i = 0; i < pset.psets.length; i++) {
        data = data + pset.psets[i].Name.value + '\n';
        for(var j = 0; j < pset.psets[i].HasProperties.length; j++) {
            data = data + '   ' + pset.psets[i].HasProperties[j].value + '\n';    
        }
      }

      text.value = data;
...

구현 결과는 다음과 같다. IFC를 로딩한 후, 벽체 등 객체를 더블클릭하면 아래 패널창에 속성값이 출력될 것이다. 
벽체 객체 선택 후 속성 출력된 모습

IFC.js 응용과 한계
IFC.js를 응용하는 방법은 다양하다. 개발자가 언급한 것처럼 CDE를 구현할 수도 있고, BIM 모델 체커를 만들 수도 있다. IFC의 Property Set에 외부 정보를 연결하면, IoT, 디지털 트윈 등을 구현할 수 있다. GIS와 연계하면 BIM-GIS 개발이 가능할 것이다. 

다만, 유사한 오픈소스 프로젝트인 xBIM과 비교를 해보면, 정보 쿼리 기능, BIM 서버 연계, 대용량 처리, 확장성 등의 개선이 필요해 보인다. 그럼에도 가장 간편하게 BIM 응용 서비스를 웹 기반으로 개발할 수 있다는 점, WASM을 이용한 그래픽 가속 기능은 큰 장점이다.

마무리
이 글에서는 간단히 CDE 개발을 지원하는 오픈소스 BIM 라이브러리인 IFC.js를 분석하고 사용방법을 확인해 보았다. IFC.js는 구조가 복잡하고 사용이 쉽지는 않다. 그럼에도 앞서 언급한 장점이 많은 라이브러리이다. 
IFC.js 기여자
레퍼런스
xBIM web viewer example

부록: IFC viewer 오픈소스 license
현 시점에서 ifc viewer 중 MIT, BSD 라이센스는 web-ifc-viewer, ifc.js, ifcplusplus밖에 없다. 성능이 좋은 xbim, xeokit은 copyleft에 해당하는 오픈소스로 이를 사용한 코드를 오픈소스로 공개하도록 강제한다. 이를 어길 시 소송을 당할 수 있다.

부록: ISO 19650 CDE 관련 참고
ISO 19650와 호환되는 CDE 개발 시 참고 자료는 다음과 같다. 
산출물 정보 컨테이너 ID 관리
CDE 작업흐름 관리
부록: IFC.js 형상 파싱 및 메쉬 생성 그래픽 엔진 분석
IFC.js의 그래픽 엔진은 Three.js 의 메쉬 생성 부분을 사용한다. 코드를 다운로드한 후 분석을 해 보면, IFC 파싱을 담당하고 있는 IFCWorker.js 의 Geometry to Mesh 코드가 Three.js 코드를 사용하고 있는 것을 확인할 수 있다. IFC는 각 Product의 형상을 파라메터만 가지고 있으므로, 이를 디스플레이하거나 메쉬 간 연산을 위해서는 다음과 같은 변환 작업이 필요하다.

   mesh = convert(product.geometry.{type, parameters})
   // display(mesh) 

IFCWorker.js는 각 IFC버전별로 파싱하는 함수를 다음과 같은 맵 테이블로 정의하고 있다 

각 IFC product의 지오메트리는 다음과 같은 BufferGeometry에서 파생받은 클래스에 의해 파라메터가 해석되어 mesh 정보로 생성된다. 이 메쉬 생성 전략은 Solid model의 B-rep 형식에 따라 각각 다르므로, 이에 적합한 메쉬 생성 함수를 사용하도록 코딩된다. 생성된 메쉬 정보는 BufferGeometry에 저장된다. 
ExtrudeGeometry (build.js)

파싱되어 생성된 메쉬는 다음과 같이 접근되어 사용된다. 

퍼버 지오메트리는 IFC file serialize 성능 속도 개선을 위해 file로 저장 및 로딩되는 캐쉬 전략을 사용한다. 

부록: ifcviewer 초기화 루틴
ifcviewer는 아직 완벽한 CRUD(create, read, update, delete) 모델을 지원하지 않는다. 그러므로, 일부 소스 수정이 필요하다. 다음은 ifc viewer 초기화 관련 주요 코드를 보여준다. 

      const container = document.getElementById('viewer-container');
      const ifcviewer = new IfcViewerAPI({ container, backgroundColor: new Color(255, 255, 255) });
      ifcviewer.axes.setAxes();
      ifcviewer.grid.setGrid();
      ifcviewer.shadowDropper.darkness = 0.0;
      ifcviewer.context.ifcCamera.cameraControls;
      ifcviewer.IFC.loader.ifcManager;
      ifcviewer.IFC.setWasmPath('files/');
      ifcviewer.IFC.loader.ifcManager.applyWebIfcConfig({
         USE_FAST_BOOLS: true,
         COORDINATE_TO_ORIGIN: true
      });
      ifcviewer.context.renderer.postProduction.active = true;
      let first = true;
      let model;
      const loadIfc = async (event) => {
         const selectedFile = event.target.files[0];
         if(!selectedFile) return;
         ifcviewer.IFC.loader.ifcManager.parser.setupOptionalCategories({
         [IFCSPACE]: false,
         [IFCOPENINGELEMENT]: false
         });
         model = await ifcviewer.IFC.loadIfc(selectedFile, false);
         if(first) first = false;
         else {
            ClippingEdges.forceStyleUpdate = true;
         }
      };

      const inputElement = document.createElement('input');
      inputElement.setAttribute('type', 'file');
      inputElement.classList.add('hidden');
      inputElement.addEventListener('change', loadIfc, false);

      const handleKeyDown = async (event) => {
         if (event.code === 'Delete') {
         ifcviewer.clipper.deletePlane();
         ifcviewer.dimensions.delete();
         }
         if (event.code === 'Escape') {
         ifcviewer.IFC.selector.unHighlightIfcItems();
         }
         if (event.code === 'KeyC') {
         ifcviewer.context.ifcCamera.toggleProjection();
         }
         if (event.code === 'KeyD') {
         ifcviewer.IFC.removeIfcModel(0);
         }
         if (event.code === 'KeyI') {
         const ifcUrl = "/static/test.ifc";
         await ifcviewer.IFC.loadIfcUrl(ifcUrl);
         }   
      };

      window.onmousemove = () => ifcviewer.IFC.selector.prePickIfcItem();
      window.onkeydown = handleKeyDown;
      window.ondblclick = async () => {
         if (ifcviewer.clipper.active) {
            ifcviewer.clipper.createPlane();
         } else {
            const result = await ifcviewer.IFC.selector.highlightIfcItem(true);
            if (!result) 
               return;
            const { modelID, id } = result;
            const props = await ifcviewer.IFC.getProperties(modelID, id, true, false);
            console.log(props);
         }
      }
   
      window.IfcViewerAPI = IfcViewerAPI;
      window.ifcviewer = ifcviewer;

부록: web assembly 예제 (참고)
웹어셈블리는 C++과 같은 다양한 언어로부터, 클라이언트에 실행되는 웹 바이너리 실행코드를 생성한다. Emscripten은 웹 어셈블리를 컴파일한다. 참고로, Emscripten은 오픈소스 컴파일러로, C/C++ 등 소스코드를 컴파일해 웹에서 실행할 수 있는 코드를 생성한다. 이 도구는 언리얼, 유티니와 같은 상용 코드베이스를 포함하고 있다. 이 도구를 통해, 웹 켄버스 그래픽, 이벤트 등 다양한 웹 브라이져 객체를 다룰수 있다(링크). 또한, 게임 등 다양한 소프트웨어를 웹 버전으로 포팅할 때도 사용된다(데모).
포팅된 3차원 유니티 게임 스크린샷(링크)

이 예제는 모질라에서 제공하는 튜토리얼 중 일부를 참고한 것이다(링크).

1. 코딩
#include <stdio.h>

int main() {
    printf("Hello World\n");
    return 0;
}

2. 웹 어셈플리 컴파일
emcc hello.c -o hello.html

이 결과로, hello.wasm, hello.js, hello.html 파일이 생성된다.

3. 생성된 html 을 웹브라우저에 로딩
이 결과로 앞의 프린트 문 내용이 웹페이지에 출력되는 것을 볼 수 있다.

웹어셈블리를 이용해 웹에서 제공되지 않는 기능을 추가할 수 있다. 예를 들어, 다음 예는 커스텀 그래픽 객체를 추가한다.



댓글 없음:

댓글 쓰기