Files
dwg-dxf-viewer-sample/docs/entity-selection-property-layer-ui.md

234 lines
12 KiB
Markdown

# Entity 선택, 속성 보기(Property Inspector), 레이어 관리(Layer Panel) & UI 패널 통합 가이드
이 문서는 DWG/DXF 뷰어 및 다른 2D/3D CAD 웹 뷰어 프로젝트에서 **엔티티 선택(Picking)**, **상세 속성 패널(Property Inspector)**, **레이어 관리 패널(Layer Panel)**, 그리고 **드래그·접기·리사이즈 가능한 UI 윈도우(Panel Utilities)**를 구축하고 이식하기 위한 통합 개발 및 재사용 가이드입니다.
---
## 📌 1. 전체 아키텍처 & 모듈 구성
본 시스템은 뷰어 코어 엔진과 UI 구성요소가 완벽히 분리된 모듈형 구조로 설계되어 있어, 다른 Three.js/WebGL 기반 뷰어에도 손쉽게 이식할 수 있습니다.
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Canvas Viewport (Viewer2D) │
│ - Entity Raycasting & Pick Index (_pickSegs, _pickFills) │
│ - Highlight & Accent recoloring (_highlight) │
│ - Selected Entity Fit Camera (zoomToSelection) │
│ - Layer Visibility & Info (getLayerInfo, setHiddenLayers) │
└──────────────────────┬──────────────────────────────┬───────────────────────┘
│ Event: onSelect(entity) │ Layer Info API
▼ ▼
┌────────────────────────────────────────┐ ┌──────────────────────────────────┐
│ propertyInspector.ts │ │ layerPanel.ts │
│ - formatEntityProperties() │ │ - LayerPanelManager class │
│ - Entity Type Specific Geometry Math │ │ - Real-time Keyword Search │
│ - Dynamic Property Card Rendering │ │ - Eye Toggle & Show/Hide All │
└───────────────────┬────────────────────┘ └─────────────────┬────────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ panelUtils.ts │
│ - makePanelDraggableAndCollapsible(panelEl, options) │
│ - Header Mouse/Pointer Dragging & Screen Clamping │
│ - Z-Index Top Focus Management │
│ - Height Preservation Collapse / Expand Toggle │
│ - CSS3 resize: both Integration │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 📑 2. 주요 모듈 명세 및 역할
| 모듈 파일 | 역할 및 제공 기능 | 비고 |
| :--- | :--- | :--- |
| **`src/viewer2d/Viewer2D.js`** | 렌더링 시 피킹 인덱스 구축, 레이캐스팅, 강조 표시, `zoomToSelection()` 카메라 핏팅 | 뷰어 코어 엔진 |
| **`src/propertyInspector.ts`** | 엔티티 유형(LINE, CIRCLE, ARC, POLYLINE, TEXT, INSERT 등)별 속성 추출 및 포맷팅, DOM 카드 렌더링 | 속성 패널 모듈 |
| **`src/layerPanel.ts`** | `LayerPanelManager` 클래스 - 검색 필터, 가시성 눈동자 토글, 전체 켜기/끄기, 색상/개수 표시 | 레이어 패널 모듈 |
| **`src/panelUtils.ts`** | `makePanelDraggableAndCollapsible` - 헤더 드래그, 화면 이탈 방지, z-index 최상단 전환, 접기/펼치기 | UI 패널 공통 유틸 |
| **`index.html` & `src/main.ts`** | 패널 HTML 구조, 글래스모피즘 CSS 스타일, 메인 이벤트 바인딩 | 애플리케이션 글루 |
---
## 🔍 3. 핵심 기능별 상세 구현 원리
### 3.1 Entity 선택 (Picking) & Zoom to Selection
1. **픽 데이터 수집 (Render Time)**
- 렌더링 시 각 엔티티의 실선 선분은 `_pickSegs` (`[ax, ay, bx, by, ...]`) 배열에 추가하고, owner 엔티티 메타 인덱스는 `_pickSegMeta`에 기록합니다.
- 해치(HATCH), 솔리드(SOLID), 텍스트(TEXT) 박스 등 면적을 가진 도형은 `_pickFills` (`{ metaIdx, loops }`) 배열에 추가합니다.
2. **히트 테스트 및 선택 (`_onClick`)**
- 캔버스 클릭 시 브라우저 좌표를 월드 WCS 좌표로 변환 (`screenToWorld`).
- 클릭 지점으로부터 화면 픽셀 임계값(`CLICK_THRESHOLD_PX`) 이내에 있는 가장 가까운 선분을 우선 탐색합니다 (`_segDist`).
- 선분이 없을 경우 면적 도형 내부인지 다각형 포함 테스트(`_fillHit`)를 수행하여 엔티티 메타데이터를 결정합니다.
- `_highlight(meta)`를 통해 이전 선택 객체 색상을 복원하고 새 선택 객체의 Vertex Color를 선택 강조색(`_selColor`)으로 변경합니다.
- `onSelect(cb)` 콜백을 발생시켜 선택된 `entity` 객체를 외부 UI로 전달합니다.
3. **선택 객체로 Zoom (`zoomToSelection()`)**
```typescript
zoomToSelection() {
if (!this._selMeta) return;
const idx = this._entityMeta.indexOf(this._selMeta);
if (idx < 0) return;
const box = new THREE.Box3();
// 선택된 메타 인덱스의 선분 및 꽉 찬 루프 좌표로 Box3 확장
for (let i = 0, j = 0; i < S.length; i += 4, j++) {
if (SM[j] === idx) {
box.expandByPoint(new THREE.Vector3(S[i], S[i + 1], 0));
box.expandByPoint(new THREE.Vector3(S[i + 2], S[i + 3], 0));
}
}
if (!box.isEmpty()) this._fit(box);
}
```
---
### 3.2 속성 보기 모듈 (`propertyInspector.ts`)
엔티티의 DXF/DWG 데이터 속성을 사용자가 읽기 편한 일반 정보(General)와 기하학적 정보(Geometry)로 분류하여 직관적으로 렌더링합니다.
#### 주요 엔티티별 계산 속성:
- **`LINE`**: 시작점, 끝점, $\Delta X, \Delta Y$, 길이 ($L = \sqrt{\Delta x^2 + \Delta y^2}$), 각도 ($\theta = \text{atan2}(\Delta y, \Delta x)$)
- **`CIRCLE`**: 중심점, 반지름 ($r$), 지름 ($2r$), 둘레 ($2\pi r$), 넓이 ($\pi r^2$)
- **`ARC`**: 중심점, 반지름, 시작/끝 각도, 중심각 ($\Delta \theta$), 호 길이 ($r \cdot \Delta \theta$)
- **`LWPOLYLINE` / `POLYLINE`**: 정점 개수, 닫힘 여부, 총 세그먼트 길이, 면적 (Shoelace 공식을 이용한 $A = \frac{1}{2} |\sum (x_i y_{i+1} - x_{i+1} y_i)|$)
- **`TEXT` / `MTEXT`**: 텍스트 내용, 높이, 회전각, 정렬, 삽입점
- **`INSERT` (블록)**: 블록 이름, 삽입점, 축척 ($S_x, S_y, S_z$), 회전각
---
### 3.3 레이어 관리 모듈 (`layerPanel.ts`)
`LayerPanelManager` 클래스는 `Viewer2D`의 레이어 테이블 데이터와 동기화되어 다음 기능을 수행합니다:
1. **실시간 키워드 필터링**:
```typescript
const filtered = layers.filter(l => l.name.toLowerCase().includes(this.searchQuery));
```
2. **레이어 숨김/표시 제어**:
```typescript
public toggleLayer(name: string): void {
if (this.hiddenLayers.has(name)) this.hiddenLayers.delete(name);
else this.hiddenLayers.add(name);
this.viewer.setHiddenLayers(this.hiddenLayers);
this.render();
}
```
3. **일괄 동작**: `showAllLayers()` 및 `hideAllLayers()` 지원.
---
### 3.4 UI 패널 드래그 · 접기 · 크기 조절 모듈 (`panelUtils.ts`)
모든 플로팅 UI 패널에 자유로운 **드래그 이동**, **접기/펼치기**, **크기 조절(Resize)**을 부여합니다.
```typescript
export function makePanelDraggableAndCollapsible(
panelEl: HTMLElement,
options?: PanelControlOptions
)
```
#### 동작 원리:
1. **드래그 이동 & 화면 클램핑**:
- 패널 헤더(`handleEl`) `pointerdown` 시 포인터 시작 좌표와 패널의 상대 위치를 기록합니다.
- 이동 시 `Math.max(10, Math.min(newLeft, maxLeft))`로 클램핑하여 패널이 화면 밖으로 이탈하지 않도록 조절합니다.
- 드래그 시 패널 클릭과 함께 `globalMaxZIndex`를 증가시켜 선택된 패널이 다른 창 위에 표시되도록 처리합니다.
2. **접기/펼치기 (Collapsible)**:
- 접기(`isCollapsed = true`) 시 원래 높이(`lastHeight`)를 저장하고 `height = 42px`로 변경 및 내용 영역(`bodyEl`)을 `display: none` 처리합니다.
- 펼침 시 저장해둔 `lastHeight`와 `display: flex`를 복원합니다.
3. **크기 조절 (Resizable)**:
- CSS `.cad-panel`에 `resize: both; overflow: hidden;`을 적용하여 우하단 리사이즈 핸들을 활성화합니다.
- `.cad-panel.collapsed` 시에는 `resize: none !important;`를 적용하여 접힘 상태 시 리사이즈를 차단합니다.
---
## 🎨 4. CSS 패널 스타일링 가이드 (`index.html`)
다른 프로젝트 이식 시 필요한 공통 패널 스타일 정의:
```css
/* 공통 패널 윈도우 스타일 (Properties & Layers - Draggable & Resizable) */
.cad-panel {
position: fixed;
top: 56px;
width: 320px;
height: 480px;
min-width: 250px;
min-height: 140px;
max-width: 90vw;
max-height: 90vh;
z-index: 15;
background: rgba(22, 27, 34, 0.96);
backdrop-filter: blur(12px);
border: 1px solid var(--line);
border-radius: 10px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
overflow: hidden;
resize: both;
transition: border-color 0.15s;
}
/* 패널 접힘 상태 */
.cad-panel.collapsed {
height: 42px !important;
min-height: 42px !important;
max-height: 42px !important;
overflow: hidden !important;
resize: none !important;
}
/* 드래그 핸들 헤더 */
.cad-panel .hdr {
padding: 10px 14px;
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(13, 17, 23, 0.7);
position: sticky;
top: 0;
z-index: 5;
cursor: grab;
user-select: none;
flex-shrink: 0;
}
.cad-panel .hdr:active {
cursor: grabbing;
}
```
---
## 🚀 5. 다른 뷰어 프로젝트로의 이식 단계 (Migration Steps)
1. **파일 복사**:
- `src/panelUtils.ts`
- `src/propertyInspector.ts`
- `src/layerPanel.ts`
2. **HTML 창 구조 추가**:
- `index.html`에 `.cad-panel` 클래스를 가진 `#properties` 및 `#layers` 요소 추가.
3. **타겟 뷰어 이벤트 바인딩 (`main.ts`)**:
```typescript
import { makePanelDraggableAndCollapsible } from './panelUtils';
import { renderPropertyInspector } from './propertyInspector';
import { LayerPanelManager } from './layerPanel';
// 1. 패널 드래그/접기 적용
makePanelDraggableAndCollapsible(document.getElementById('properties')!);
makePanelDraggableAndCollapsible(document.getElementById('layers')!);
// 2. 레이어 매니저 바인딩
const layerManager = new LayerPanelManager(viewer, document.getElementById('layerList')!);
// 3. 엔티티 선택 바인딩
viewer.onSelect((entity) => {
renderPropertyInspector(document.getElementById('propBody')!, entity);
});
```