# 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);
   });
   ```
