12 KiB
12 KiB
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
-
픽 데이터 수집 (Render Time)
- 렌더링 시 각 엔티티의 실선 선분은
_pickSegs([ax, ay, bx, by, ...]) 배열에 추가하고, owner 엔티티 메타 인덱스는_pickSegMeta에 기록합니다. - 해치(HATCH), 솔리드(SOLID), 텍스트(TEXT) 박스 등 면적을 가진 도형은
_pickFills({ metaIdx, loops }) 배열에 추가합니다.
- 렌더링 시 각 엔티티의 실선 선분은
-
히트 테스트 및 선택 (
_onClick)- 캔버스 클릭 시 브라우저 좌표를 월드 WCS 좌표로 변환 (
screenToWorld). - 클릭 지점으로부터 화면 픽셀 임계값(
CLICK_THRESHOLD_PX) 이내에 있는 가장 가까운 선분을 우선 탐색합니다 (_segDist). - 선분이 없을 경우 면적 도형 내부인지 다각형 포함 테스트(
_fillHit)를 수행하여 엔티티 메타데이터를 결정합니다. _highlight(meta)를 통해 이전 선택 객체 색상을 복원하고 새 선택 객체의 Vertex Color를 선택 강조색(_selColor)으로 변경합니다.onSelect(cb)콜백을 발생시켜 선택된entity객체를 외부 UI로 전달합니다.
- 캔버스 클릭 시 브라우저 좌표를 월드 WCS 좌표로 변환 (
-
선택 객체로 Zoom (
zoomToSelection())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의 레이어 테이블 데이터와 동기화되어 다음 기능을 수행합니다:
- 실시간 키워드 필터링:
const filtered = layers.filter(l => l.name.toLowerCase().includes(this.searchQuery)); - 레이어 숨김/표시 제어:
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(); } - 일괄 동작:
showAllLayers()및hideAllLayers()지원.
3.4 UI 패널 드래그 · 접기 · 크기 조절 모듈 (panelUtils.ts)
모든 플로팅 UI 패널에 자유로운 드래그 이동, 접기/펼치기, **크기 조절(Resize)**을 부여합니다.
export function makePanelDraggableAndCollapsible(
panelEl: HTMLElement,
options?: PanelControlOptions
)
동작 원리:
- 드래그 이동 & 화면 클램핑:
- 패널 헤더(
handleEl)pointerdown시 포인터 시작 좌표와 패널의 상대 위치를 기록합니다. - 이동 시
Math.max(10, Math.min(newLeft, maxLeft))로 클램핑하여 패널이 화면 밖으로 이탈하지 않도록 조절합니다. - 드래그 시 패널 클릭과 함께
globalMaxZIndex를 증가시켜 선택된 패널이 다른 창 위에 표시되도록 처리합니다.
- 패널 헤더(
- 접기/펼치기 (Collapsible):
- 접기(
isCollapsed = true) 시 원래 높이(lastHeight)를 저장하고height = 42px로 변경 및 내용 영역(bodyEl)을display: none처리합니다. - 펼침 시 저장해둔
lastHeight와display: flex를 복원합니다.
- 접기(
- 크기 조절 (Resizable):
- CSS
.cad-panel에resize: both; overflow: hidden;을 적용하여 우하단 리사이즈 핸들을 활성화합니다. .cad-panel.collapsed시에는resize: none !important;를 적용하여 접힘 상태 시 리사이즈를 차단합니다.
- CSS
🎨 4. CSS 패널 스타일링 가이드 (index.html)
다른 프로젝트 이식 시 필요한 공통 패널 스타일 정의:
/* 공통 패널 윈도우 스타일 (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)
- 파일 복사:
src/panelUtils.tssrc/propertyInspector.tssrc/layerPanel.ts
- HTML 창 구조 추가:
index.html에.cad-panel클래스를 가진#properties및#layers요소 추가.
- 타겟 뷰어 이벤트 바인딩 (
main.ts):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); });