docs: [bim] BIM 뷰어 문서 Phase 2 반영

- Phase 1 전용 → Phase 2 완료 상태로 업데이트
- IFC 업로드/다운로드, web-ifc, 듀얼 모드 설명 추가
- IFC→Three.js 변환 파이프라인, IFC Export 구조 문서화
- BimScene 클래스 메서드 목록 최신화
This commit is contained in:
김보곤
2026-03-12 13:35:14 +09:00
parent efd0427cd6
commit fa85bd388a

View File

@@ -1,7 +1,7 @@
# BIM 뷰어 — Three.js 기반 웹 3D 건물 모델 뷰어
# BIM 뷰어 — Three.js + web-ifc 기반 웹 3D BIM 뷰어
> **작성일**: 2026-03-12
> **상태**: Phase 1 완료 (프로토타입)
> **상태**: Phase 2 완료 (IFC 업로드 + 다운로드)
> **소스**: `mng/resources/views/juil/bim-viewer.blade.php`
---
@@ -12,97 +12,216 @@
건설PMIS(Construction Project Management Information System)의 BIM(Building Information Modeling) 뷰어 기능이다. 웹 브라우저에서 별도 플러그인 없이 3D 건물 모델을 렌더링하고, 요소를 선택하여 속성 정보를 조회할 수 있다.
### 1.2 핵심 기술 스택
### 1.2 주요 기능
| 기 | 역할 | CDN |
|------|------|-----|
| **Three.js r128** | WebGL 기반 3D 렌더링 엔진 | `cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js` |
| **OrbitControls** | 카메라 회전/줌/팬 | `cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js` |
| 기 | 설명 | Phase |
|------|------|-------|
| 데모 건물 렌더링 | 물류센터 3D 모델 (BoxGeometry 조합) | Phase 1 |
| 요소 클릭 선택 | Raycaster로 3D 요소 선택 → 속성 | Phase 1 |
| 시점 전환 | 투시도/정면/우측/상부/배면 프리셋 | Phase 1 |
| 요소 토글 & 와이어프레임 | 요소 유형별 표시/숨김, 와이어프레임 모드 | Phase 1 |
| **IFC 파일 업로드** | `.ifc` 파일 업로드 → 실제 BIM 모델 렌더링 | **Phase 2** |
| **IFC 파일 다운로드** | 현재 모델을 IFC 파일로 내보내기 | **Phase 2** |
| **드래그 앤 드롭** | IFC 파일을 뷰포트에 드래그하여 로드 | **Phase 2** |
### 1.3 핵심 기술 스택
| 기술 | 역할 | 버전/CDN |
|------|------|----------|
| **Three.js** | WebGL 기반 3D 렌더링 엔진 | r128 (CDN) |
| **OrbitControls** | 카메라 회전/줌/팬 조작 | r128 (CDN) |
| **web-ifc** | IFC 파일 파싱 (WASM 기반) | 0.0.66 (CDN, 지연 로드) |
| **React 18** | UI 컴포넌트 (사이드바, 툴바, 정보 패널) | CDN + Babel 브라우저 트랜스파일 |
| **Tailwind CSS** | UI 스타일링 | CDN |
### 1.3 아키텍처
### 1.4 아키텍처
```
┌─────────────────────────────────────────────────┐
│ Blade 템플릿 (@extends layouts.app) │
│ ┌─────────────────────────────────────────────┐│
│ │ React (CDN + Babel) ││
│ │ ┌──────┐ ┌──────────────┐ ┌───────────┐ ││
│ │ │사이드 │ │ Three.js │ │ 정보 패널 │ ││
│ │ │ 바 │ │ 3D Viewport │ │ (React) │ ││
│ │ │(React)│ │ (WebGL) │ ││
│ │ └──────┘ └──────────────┘ └───────────┘ ││
│ │ ┌────────────── ││
│ │ │ 툴바(React) ││
│ │ └──────────────┘ ││
│ └─────────────────────────────────────────────┘
─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────
│ Blade 템플릿 (@extends layouts.app)
│ ┌─────────────────────────────────────────────────────┐│
│ │ React (CDN + Babel) ││
│ │ ┌──────┐ ┌──────────────────┐ ┌───────────────┐ ││
│ │ │사이드 │ │ Three.js │ │ 정보 패널 │ ││
│ │ │ 바 │ │ 3D Viewport │ │ (모델/선택/ │ ││
│ │ │(React)│ │ (WebGL) │ 통계) │ ││
│ │ └──────┘ └──────────────────┘ └───────────────┘ ││
│ │ ┌──────────────────┐ ││
│ │ │ 툴바 (React) ││
│ │ │ IFC업로드/다운로드│ ││
└──────────────────┘ │
│ └─────────────────────────────────────────────────────┘
│ │
│ ┌─────────────────┐ ┌──────────────────────────────┐ │
│ │ IFCHelper │ │ generateDemoIFC() │ │
│ │ (web-ifc 래퍼) │ │ (Three.js → IFC2X3 변환) │ │
│ └─────────────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
- **React**: UI 컴포넌트 관리 (사이드바, 툴바, 정보 패널)
- **Three.js**: 3D 렌더링 (건물, 조명, 그림자, 카메라)
- **web-ifc**: IFC 파일 파싱 → 지오메트리 추출 (WASM, 지연 로드)
- **연결 방식**: React `useRef`로 DOM 요소를 Three.js에 전달, `useEffect`로 생명주기 관리
---
## 2. Three.js 렌더링 파이프라인
## 2. 듀얼 모드 — 데모 / IFC
### 2.1 기본 구조
BIM 뷰어는 두 가지 모드로 동작한다:
| 항목 | 데모 모드 (`demo`) | IFC 모드 (`ifc`) |
|------|-------------------|------------------|
| 진입 방식 | 기본 (페이지 로드 시) | IFC 파일 업로드/드래그 시 |
| 모델 원본 | `createDemoBuilding()` (코드 내 하드코딩) | 사용자 업로드 `.ifc` 파일 |
| 지오메트리 | `BoxGeometry` 조합 (~200개) | web-ifc `StreamAllMeshes``BufferGeometry` |
| 요소 선택 시 | `userData` (type, name, material, floor 등) | `IFCHelper.getElementInfo()` (IFC 속성) |
| 요소 토글 | 유형별 Group 표시/숨김 | 미지원 (전체 모델 표시) |
| IFC 다운로드 | `generateDemoIFC()` → IFC2X3 STEP 생성 | 원본 버퍼 그대로 재다운로드 |
모드 전환 흐름:
```
[페이지 로드] → 데모 모드
├── IFC 업로드 ──→ IFC 모드 (데모 숨김)
│ │
│ ├── "데모 모델" 버튼 ──→ 데모 모드 복귀
│ └── 다른 IFC 업로드 ──→ 새 IFC 로드
└── IFC 다운로드 → generateDemoIFC() 실행 → .ifc 파일 다운로드
```
---
## 3. IFC 파일 처리 (Phase 2)
### 3.1 web-ifc 지연 로드
web-ifc WASM 번들은 약 7MB이다. 페이지 로드 시 다운로드하지 않고, IFC 파일을 처음 업로드할 때만 로드한다:
```javascript
// 동적 module script 주입 방식
function loadWebIFCLib() {
const s = document.createElement('script');
s.type = 'module';
s.textContent = `
import * as WebIFC from '${WEBIFC_CDN}web-ifc-api.js';
window._WebIFC = WebIFC;
window.dispatchEvent(new CustomEvent('_wifcOk'));
`;
document.head.appendChild(s);
}
```
> Babel `text/babel` 컨텍스트에서 동적 `import()`가 불안정하므로, 별도 `<script type="module">`을 주입하여 ES module import를 수행하고 `window._WebIFC`로 노출한다.
### 3.2 IFCHelper 클래스
web-ifc API를 래핑하여 파싱과 요소 정보 조회를 담당한다:
```javascript
class IFCHelper {
async init(onMsg) // web-ifc 초기화, WASM 로드, typeMap 구축
parse(buffer, onMsg) // IFC 파일 → StreamAllMeshes → 지오메트리 배열
getElementInfo(expressID) // expressID → IFC 속성 (타입, 이름, GlobalId 등)
close() // 모델 닫기, 캐시 정리
}
```
### 3.3 IFC → Three.js 변환 파이프라인
```
.ifc 파일 (ArrayBuffer)
↓ IFCHelper.parse()
web-ifc StreamAllMeshes
↓ 각 메시에서 vertices + indices + transform 추출
Float32Array (위치 3 + 노멀 3 = stride 6)
↓ Three.js BufferGeometry로 변환
BufferAttribute(position, 3) + BufferAttribute(normal, 3)
↓ Matrix4.fromArray(transform) 적용
MeshPhongMaterial (IFC 원본 색상 + 투명도)
THREE.Mesh → ifcGroup(THREE.Group)에 추가
fitToModel() → 바운딩 박스 기준 카메라 자동 조정
```
### 3.4 IFC 파일 다운로드 (IFC Export)
| 모드 | 동작 | 출력 파일명 |
|------|------|-----------|
| 데모 | `generateDemoIFC(meshes)` → IFC2X3 STEP 텍스트 생성 | `SAM_Demo_Building.ifc` |
| IFC | `this.ifcBuffer` (업로드 시 저장한 원본) 그대로 다운로드 | `model.ifc` |
데모 모델의 IFC 변환 구조:
```
Three.js 메시 (BoxGeometry)
↓ geometry.parameters → width, height, depth
↓ mesh.position → x, y, z
↓ mesh.userData → type, floor, name, material
IFC2X3 STEP 엔티티 생성:
IfcProject → IfcSite → IfcBuilding → IfcBuildingStorey (1F~RF)
각 메시 → IfcRectangleProfileDef + IfcExtrudedAreaSolid + IfcLocalPlacement
유형 매핑: column→IFCCOLUMN, beam→IFCBEAM, floor/roof→IFCSLAB, ...
좌표계 변환:
Three.js (x, y, z) → IFC (x, z, y) // Y-up → Z-up
요소 기준점: 바닥 중심 (Three.js center → IFC bottom)
```
---
## 4. Three.js 렌더링 파이프라인
### 4.1 기본 구조
```
브라우저 (Chrome/Edge)
└── WebGL API (GPU 가속 그래픽)
└── Three.js (WebGL 추상화 라이브러리)
└── Three.js r128 (WebGL 추상화 라이브러리)
├── Scene — 3D 공간 (모든 객체의 컨테이너)
├── Camera — 시점 (PerspectiveCamera, FOV 45도)
├── Renderer — 화면 출력 (WebGLRenderer, 안티앨리어싱)
├── Lights — 조명 (Hemisphere + Directional + Ambient)
── Mesh — 물체 = Geometry(형태) + Material(재질)
── Mesh — 물체 = Geometry(형태) + Material(재질)
└── FogExp2 — 거리 안개 (밀도 0.004)
```
### 2.2 렌더링 루프
### 4.2 렌더링 루프
Three.js는 매 프레임(~60fps)마다 장면을 다시 그린다:
```javascript
animate() {
requestAnimationFrame(() => this.animate()); // 다음 프레임 예약
// 카메라 전환 애니메이션 (lerp: 선형 보간)
requestAnimationFrame(() => this.animate());
if (this.targetPos) {
this.camera.position.lerp(this.targetPos, 0.07); // 현재→목표, 7%씩 이동
this.camera.position.lerp(this.targetPos, 0.07);
this.controls.target.lerp(this.targetLook, 0.07);
if (this.camera.position.distanceTo(this.targetPos) < 0.2)
this.targetPos = null; // 도착 시 애니메이션 종료
this.targetPos = null;
}
this.controls.update(); // 사용자 조작 반영
this.renderer.render(this.scene, this.camera); // GPU 렌더링
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
```
> `lerp` (Linear Interpolation): 현재 값에서 목표 값으로 매 프레임 일정 비율만큼 이동하는 방식이다. 처음엔 빠르게, 목표에 가까울수록 느리게 이동하여 자연스러운 이징 효과를 만든다.
### 2.3 조명 구성
### 4.3 조명 구성
```javascript
// 반구광 — 하늘(파랑) + 지면(갈색) 사이 자연 채광
HemisphereLight(0xb1e1ff, 0xb97a20, 0.55)
// 태양광 — 그림자 생성, 건물에 방향성 빛
DirectionalLight(0xffffff, 0.85) // 위치: (45, 50, 35)
HemisphereLight(0xb1e1ff, 0xb97a20, 0.55) // 반구광 (하늘+지면)
DirectionalLight(0xffffff, 0.85) // 태양광 (그림자)
└── Shadow Map: 2048×2048 (PCFSoftShadowMap)
// 환경광 — 그림자 영역의 최소 밝기
AmbientLight(0xffffff, 0.25)
AmbientLight(0xffffff, 0.25) // 환경광
```
---
## 3. 건물 모델 생성
## 5. 데모 건물 모델
### 3.1 건물 사양
### 5.1 건물 사양
| 항목 | 값 |
|------|------|
@@ -112,44 +231,21 @@ AmbientLight(0xffffff, 0.25)
| 기둥 격자 | 7 × 4 (간격 10m × 10m) |
| 좌표계 | X = 가로(길이), Y = 높이(상향), Z = 세로(깊이) |
### 3.2 핵심 원리 — 모든 요소는 박스의 조합
### 5.2 요소 색상 체계
```javascript
// 기둥 하나 = 가로 0.45m × 높이 3.7m × 세로 0.45m 박스
new THREE.BoxGeometry(0.45, 3.7, 0.45)
| 요소 | 색상 코드 | 개수 |
|------|----------|------|
| 바닥 슬래브 | `0x81C784` 연두 | 3 |
| 지붕 | `0x4CAF50` 초록 | 1 |
| 기둥 | `0xFF9800` 주황 | 84 |
| 보 | `0xFFC107` 황금 | 33 |
| 벽체 | `0x42A5F5` 파랑 | ~50 |
| 창호 | `0x00BCD4` 시안 (반투명) | 18 |
| 출입문 | `0x8B4513` 갈색 | 3 |
| 계단실 | `0xFFAB91` 살몬 | 15 |
| **합계** | | **~200+** |
// 바닥 슬래브 = 60m × 0.3m × 30m 얇은 판
new THREE.BoxGeometry(60.6, 0.3, 30.6)
// 창문 = 반투명 재질 + 얇은 박스
new THREE.MeshPhongMaterial({ color: 0x00BCD4, transparent: true, opacity: 0.35 })
```
이것을 **반복문**으로 배치하면 건물이 완성된다:
```javascript
// 7 × 4 격자 × 3층 = 기둥 84개
for (let floor = 0; floor < 3; floor++)
for (let x = 0; x < 7; x++)
for (let z = 0; z < 4; z++)
this.box(0.45, 3.7, 0.45, [x * 10, floor * 4 + 2, z * 10], {...}, 'column');
```
### 3.3 요소 색상 체계
| 요소 | 색상 코드 | 시각적 | 개수 |
|------|----------|--------|------|
| 바닥 슬래브 | `0x81C784` | 연두 | 3 |
| 지붕 | `0x4CAF50` | 초록 | 1 |
| 기둥 | `0xFF9800` | 주황 | 84 |
| 보 | `0xFFC107` | 황금 | 33 |
| 벽체 | `0x42A5F5` | 파랑 | ~50 |
| 창호 | `0x00BCD4` | 시안 (반투명) | 18 |
| 출입문 | `0x8B4513` | 갈색 | 3 |
| 계단실 | `0xFFAB91` | 살몬 | 15 |
| **합계** | | | **~200+** |
### 3.4 건물 구성 상세
### 5.3 건물 단면
```
지붕 슬래브 ────── y = 12.0m
@@ -171,134 +267,118 @@ for (let floor = 0; floor < 3; floor++)
---
## 4. 사용자 인터랙션
## 6. 사용자 인터랙션
### 4.1 카메라 조작 (OrbitControls)
### 6.1 카메라 조작 (OrbitControls)
| 조작 | 동작 | 원리 |
|------|------|------|
| 마우스 드래그 | 카메라가 건물 중심을 공전 | Orbit (구면 좌표계 회전) |
| 스크롤 (휠) | 줌 인/아웃 | 카메라 거리 조절 |
| 우클릭 드래그 | 시점 평행 이동 | Pan (카메라 + 타겟 동시 이동) |
| 조작 | 동작 |
|------|------|
| 마우스 드래그 | 카메라가 건물 중심을 공전 (Orbit) |
| 스크롤 (휠) | 줌 인/아웃 |
| 우클릭 드래그 | 시점 평행 이동 (Pan) |
OrbitControls는 `damping` (감쇠)이 적용되어 조작 후 부드럽게 멈춘다:
```javascript
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.08;
```
### 4.2 요소 클릭 선택 (Raycaster)
### 6.2 요소 클릭 선택 (Raycaster)
```
마우스 클릭 좌표 (2D 화면)
마우스 클릭 좌표 (2D)
↓ NDC 좌표 변환 (-1 ~ +1)
카메라에서 클릭 방향으로 "광선(Ray)" 발사
카메라에서 광선(Ray) 발사
↓ raycaster.intersectObjects()
광선이 3D 메시와 충돌 검사
3D 메시와 충돌 검사
충돌한 메시의 userData (이름, 재질, 층, 치수) 읽기
[데모 모드] userData (type, name, material...) 읽기
[IFC 모드] expressID → IFCHelper.getElementInfo() → IFC 속성 조회
React 상태 업데이트 → 우측 패널에 정보 표시
React 상태 업데이트 → 우측 정보 패널 표시
```
각 요소에 메타데이터를 미리 저장해두어 클릭 시 즉시 조회된다:
### 6.3 시점 전환
```javascript
mesh.userData = {
type: 'column', // 요소 유형
name: 'C-001', // 식별자
material: 'H형강 (SS400)', // 재질
floor: '1F', // 층
dimensions: '400×400×3,700mm', // 치수
grid: '(1,1)', // 격자 위치
};
```
| 프리셋 | 용도 |
|--------|------|
| 투시도 | 전체 조감 |
| 정면 | 남측 입면 |
| 우측 | 동측 입면 |
| 상부 | 평면도 |
| 배면 | 북측 (하역장) |
### 4.3 시점 전환
IFC 모드에서는 모델 바운딩 박스 기준으로 시점이 자동 계산된다.
| 프리셋 | 카메라 위치 | 용도 |
|--------|-----------|------|
| 투시도 | (55, 35, 55) | 전체 조감 |
| 정면 | (30, 8, -35) | 남측 입면 |
| 우측 | (85, 8, 15) | 동측 입면 |
| 상부 | (30, 55, 15) | 평면도 |
| 배면 | (30, 8, 55) | 북측 (하역장) |
### 6.4 IFC 업로드 / 다운로드
`lerp` 보간으로 현재 위치에서 목표까지 부드럽게 전환된다.
### 4.4 요소 토글 & 와이어프레임
- **요소 토글**: `THREE.Group` 단위로 `visible` 속성을 전환하여 요소 유형별 표시/숨김
- **와이어프레임**: 모든 메시의 `material.wireframe` 속성을 일괄 전환
| 기능 | 방법 |
|------|------|
| IFC 업로드 | 툴바 "IFC 업로드" 버튼 또는 뷰포트에 드래그 앤 드롭 |
| IFC 다운로드 | 툴바 "IFC 다운로드" 버튼 (초록색) |
| 데모 복귀 | IFC 모드에서 "데모 모델" 버튼 |
---
## 5. React-Three.js 연동 패턴
## 7. React-Three.js 연동 패턴
### 5.1 BimScene 클래스
### 7.1 BimScene 클래스
Three.js 로직을 `BimScene` 클래스로 캡슐화하여 React와 분리한다:
```javascript
class BimScene {
constructor(el) { /* DOM 요소 참조 */ }
init() { /* Scene, Camera, Renderer, Controls, 건물 생성 */ }
onClick(e) { /* Raycaster로 요소 선택 */ }
setView(preset) { /* 카메라 전환 */ }
toggleGroup() { /* 요소 유형 표시/숨김 */ }
animate() { /* 렌더 루프 */ }
dispose() { /* 리소스 정리 */ }
constructor(el) // DOM 요소 참조, 상태 초기화
init() // Scene, Camera, Renderer, Controls, 데모 건물 생성
createDemoBuilding() // 데모 물류센터 모델 생성
async loadIFC(buffer) // IFC 파일 파싱 → BufferGeometry → 씬 추가
clearIFCModel() // IFC 모델 정리 (geometry/material dispose)
switchToDemo() // 데모 모드 복귀
exportIFC() // IFC 파일 다운로드 (데모: 생성, IFC: 원본)
onClick(e) // Raycaster로 요소 선택
setView(preset) // 카메라 전환 (lerp 보간)
fitToModel(obj) // 바운딩 박스 기준 카메라 자동 조정
toggleGroup(name) // 요소 유형 표시/숨김 (데모 모드)
toggleWireframe(on) // 와이어프레임 토글
getCounts() // 요소 통계 반환
animate() // 렌더 루프
dispose() // 리소스 정리
}
```
### 5.2 React에서 사용
### 7.2 React 컴포넌트 구성
```javascript
function BimViewerApp() {
const vpRef = useRef(null); // 3D 뷰포트 DOM 참조
const sceneRef = useRef(null); // BimScene 인스턴스 참조
| 컴포넌트 | 역할 |
|----------|------|
| `BimViewerApp` | 루트 컴포넌트 — 상태 관리, 이벤트 핸들러 |
| `BimSidebar` | 좌측 PMIS 메뉴 사이드바 |
| `BimToolbar` | 하단 툴바 (시점, 토글, 업로드/다운로드) |
| `BimInfoPanel` | 우측 패널 (모델 정보, 선택 요소, 통계) |
| `LoadingOverlay` | IFC 로딩 중 오버레이 |
useEffect(() => {
const bim = new BimScene(vpRef.current);
bim.init();
bim.onSelect = setSelected; // 선택 이벤트 → React 상태
sceneRef.current = bim;
return () => bim.dispose(); // 언마운트 시 정리
}, []);
### 7.3 통신 패턴
return (
<div className="flex">
<BimSidebar />
<div ref={vpRef} className="flex-1" /> {/* Three.js 렌더링 대상 */}
<BimInfoPanel selected={selected} />
</div>
);
}
```
React → Three.js: sceneRef.current.setView('front')
sceneRef.current.exportIFC()
sceneRef.current.loadIFC(buffer)
핵심 원칙:
- Three.js는 React 렌더 사이클 **밖에서** 자체 애니메이션 루프를 실행한다
- React → Three.js 통신: `sceneRef.current.setView()` 등 메서드 직접 호출
- Three.js → React 통신: `onSelect` 콜백으로 React `setState` 호출
Three.js → React: bim.onSelect = setSelected (요소 선택 콜백)
bim.onProgress = setLoading (로딩 상태 콜백)
```
---
## 6. 파일 구조
## 8. 파일 구조
| 파일 | 위치 | 설명 |
|------|------|------|
| `bim-viewer.blade.php` | `mng/resources/views/juil/` | BIM 뷰어 전체 (Three.js + React) |
| `bim-viewer.blade.php` | `mng/resources/views/juil/` | BIM 뷰어 전체 (~900줄, Three.js + web-ifc + React) |
| `PlanningController.php` | `mng/app/Http/Controllers/Juil/` | `bimViewer()` 메서드 |
| `web.php` | `mng/routes/` | `GET /juil/construction-pmis/bim-viewer` |
---
## 7. 향후 계획 (Phase 2+)
## 9. 향후 계획 (Phase 3+)
| Phase | 기능 | 기술 |
|-------|------|------|
| **Phase 2** | IFC 파일 업로드 + 실제 BIM 모델 렌더링 | [That Open Engine](https://github.com/ThatOpen/engine_components) (구 IFC.js) |
| ~~Phase 1~~ | ~~데모 건물 + 3D 뷰어~~ | ~~Three.js r128~~ (완료) |
| ~~Phase 2~~ | ~~IFC 업로드/다운로드 + 실제 BIM 렌더링~~ | ~~web-ifc 0.0.66~~ (완료) |
| **Phase 3** | 요소 속성 패널 고도화, 2D 평면도 모드 | Three.js Orthographic Camera |
| **Phase 4** | 모델 파일 관리, 버전 이력, 협력업체 조회 | Laravel API + DB |