Files
dwg-dxf-viewer-sample/docs/model-paper-space.md

371 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Model / Layout (Paper) 뷰어 이슈 — 원인 분석 및 처리 이력
| 항목 | 내용 |
|------|------|
| 작성 목적 | 샘플에서 재현·수정한 이슈를 **원본 `hmwebviewer`** 에 이식하기 위한 기술 메모 |
| 샘플 | `dwg-dxf-viewer-sample` |
| 원본 | `hmwebviewer` (`src/viewer2d/` …) |
| 주 재현 도면 | `C0060202-001-평면및종단면도(1)(대산방향STA.18+492.08-19+411.50).dwg` |
| 관련 도면 | `C0060203-*`, `BasicSample.dwg` (회귀) |
| 최종 정리 | 2026-07-29 |
| 상태 | **샘플 패치 완료** / 원본 뷰어 UI·Viewer2D 이식은 확인 후 |
### 제약
| 대상 | 정책 |
|------|------|
| **오픈소스 파서 본체 (acadrust 등 crates.io)** | **수정 금지** (unmodified 사용) |
| SORTENTSTABLE / 헤더 확장 export | 파서 수정 없이 **뷰어 규칙으로 대체** |
| 한글 인코딩 (CP949 mojibake) | 별도 문서 [`korean-dwg-text-encoding.md`](./korean-dwg-text-encoding.md) — **본 문서와 독립, 둘 다 필요** |
---
## 0. 이슈 한눈에 보기
| # | 증상 | 원인 | 처리 (샘플) | 수정 위치 |
|---|------|------|-------------|-----------|
| 1 | 글자·선이 한곳에 뒤섞여 겹침 | Model + Paper 동시 렌더 | 공간 필터 + Model/Layout 탭 | `cadSpaces.ts`, `Viewer2D.js`, `main.ts`, `index.html` |
| 2 | Layout에 도곽만 있고 평면/종단 비어 있음 | VIEWPORT 미투영 | Layout 시 Model을 VP 창에 투영·클립 | `Viewer2D.js` |
| 3 | Fit / 좌표가 CAD와 다름, (0,0)에 없음 | WCS 표시 + 이상치 bbox | **Model UCS 좌표** + 백분위 Fit | `Viewer2D.js` |
| 4 | 선형·종단에서 글자만 거대 | `minTextH = diag×0.0002` ≈ 250u | `min(diag×0.0002, medianHeight)` | `Viewer2D.js` |
| 5 | Layout에서 VP 내용이 도곽·주석 위 | VP를 paper 뒤에 append | VP 먼저 + `z=-1` + underlay 텍스트 | `Viewer2D.js` |
| 6 | 커서 좌표 표시 | — | 우하단 X/Y (Model=UCS) | `#coords`, `screenToWorld` |
---
## 1. DWG 공간 개념 (배경)
| 공간 | 블록 이름 예 | 역할 |
|------|----------------|------|
| Model Space | `*Model_Space` | 실좌표(측량 등) 본도면 |
| Paper Space / Layout | `*Paper_Space`, `*Paper_Space0`… | 출력 시트, 도곽, 주석, VIEWPORT |
- 엔티티 `ownerHandle` → 소속 블록 핸들.
- Layout은 보통 **VIEWPORT** 로 Model 일부를 시트 좌표에 투영한다.
- Model에는 **원거리 이중 좌표**가 흔함 (예: 원점 근처 종단 + X≈-436k 근처 선형).
### 재현 도면 규모 (C0060202-001, 참고)
| Space | handle (dec / hex) | 엔티티 수 (대략) | 좌표 |
|-------|--------------------|------------------|------|
| `*Model_Space` | 31 / `1f` | ~17,377 | 측량·다중 군집 |
| `*Paper_Space` | 210 / `d2` | ~200+ | 시트 (~1.7e32.6e3) |
| VIEWPORT | paper 소유 | 3 | overall 1 + 종단·평면 창 2 |
---
## 2. 이슈 #1 — Model + Paper 동시 렌더
### 2.1 현상
다색 TEXT가 중앙에 뭉치고, 기하·주석이 읽기 불가.
CAD에서는 Layout 활성 시 정상 시트.
### 2.2 원인
기존 `Viewer2D.load()`:
1. `*Model_Space` / `*Paper_Space` 를 space 로 인식
2. **사용자 블록 정의**만 메인 루프에서 스킵 (INSERT 전개용)
3. Model·Paper **둘 다** 메인 루프에서 그림
→ 좌표계·스케일이 다른 두 공간을 한 씬에 합치고 `fit()` → 파국.
### 2.3 처리
| 파일 | 내용 |
|------|------|
| `src/viewer2d/cadSpaces.ts` | `listCadSpaces`, `pickDefaultSpace`, `buildAllowedOwnerHexes` |
| `Viewer2D.js` | `allowedOwners` 필터, `setSpace` / `getSpaces` / `getActiveSpace` |
| `main.ts` + `index.html` | **Model / Layout** 탭, 로드 시 기본 공간 |
**기본 탭:** Paper에 엔티티 있으면 Layout, 없으면 Model.
```
C0060202-001 → Layout
BasicSample (paper 0) → Model
```
---
## 3. 이슈 #2 — Layout이 비어 보임 (VIEWPORT)
### 3.1 현상
공간 분리 후 Layout은 도곽·주석만 있고, CAD처럼 평면·종단이 없음.
### 3.2 원인
- Paper 엔티티만으로는 시트 장식/주석 수준.
- 본도면은 Model에 있고, CAD는 **VIEWPORT** 로 비춤.
- 뷰어가 Layout에서 Model을 VP에 투영하지 않으면 창이 비어 보임.
### 3.3 VIEWPORT 데이터
acadrust 타입에는 `Viewport` 가 있음.
뷰어는 parseResult 의 `type: "VIEWPORT"` 필드를 사용한다.
재현 도면 VP 예:
| 역할 | size (대략) | twist | 비고 |
|------|-------------|-------|------|
| overall paper | 시트 전체 | 0 | 스킵 (모델 창 아님) |
| 종단 창 | ~840×302 | 0 | Model 투영 |
| 평면 창 | ~840×215 | ~1.28 rad | Model 투영 |
**overall 판별:** `id===1` 또는 (scale≈1 ∧ viewTarget≈0 ∧ viewCenter≈paper center).
### 3.4 처리 (`Viewer2D.js`)
Layout 활성 시:
1. (순서: 아래 이슈 #5) Model을 각 활성 VIEWPORT에 투영
2. 변환 (2D / top, ODA·acadrust writer 규약):
```
DCS = R(+twist) * (model viewTarget)
paper = center + (paperHeight / viewHeight) * (DCS viewCenter)
```
3. paper 사각형 CohenSutherland 클리핑
4. `_isModelViewport` / `_viewportModelToPaper` / `_emitModelThroughViewport`
### 3.5 파서 정책
- **acadrust(오픈소스 본체)는 수정하지 않는다.**
- VIEWPORT JSON 방출이 래퍼/기존 빌드에 이미 있으면 뷰어만 사용.
- 추가 파서 기능(SORTENTS 등)은 **넣지 않음** → 뷰어 규칙으로 충분.
---
## 4. 이슈 #3 — Zoom Fit
### 4.1 확정: 가시 엔티티 AABB
| 항목 | 내용 |
|------|------|
| 범위 | 활성 Model/Layout + 숨긴 레이어 제외, 렌더 시 `expand` |
| 이상치 | 샘플 **0.5%99.5%** 로 극단 점 제거 → `_contentBox` |
| `fit()` | **`_contentBox` 우선** (씬 재계산보다 로드 시점과 동일) |
### 4.2 가로로 매우 긴 Model (C0060202 / bb.dwg)
실측 기하(백분위 후) 대략:
| | WCS | CAD UCS |
|--|-----|---------|
| 폭 | ~438000 | ~-437000 … ~1000 |
| 높이 | **~2900** | ~-2950 … 0 |
| 종횡비 | **~150:1** | |
화면 비율(~2:1)로 **classic contain** 하면:
- 세로 시야를 폭에 맞춰 키움 → 내용 높이가 화면의 **~1%**
- **수 픽셀 두께의 가로 선**처럼 보임 (하늘색 박스도 상단에 얇은 선)
`bb.dwg`는 같은 종횡비의 단순 박스 2개라 식별 가능했고, 원본 1.7만 엔티티는 한 줄로 뭉개짐.
### 4.3 처리 (극단 스트립 Fit)
```
contentAspect = bw / bh
if contentAspect > screenAspect × 8: // 매우 가로로 김
vh = bh × pad // Y를 화면에 채움
vw = vh × screenAspect // 좌우는 잘라 보고 패닝
else if contentAspect < screenAspect / 8:
// 매우 세로로 김 → 폭 채움
else:
// 일반 contain
```
→ 선형/종단 구간이 **세로로 화면에 채워짐**. 전체 연장은 좌우 팬.
CAD status 참고 extents(Y ±10만)와 파서 실측(Y 약 3천)이 다를 수 있음 — Fit은 **파서가 그린 가시 점** 기준.
---
## 5. 이슈 #4 — 글자만 거대 (minTextH)
### 5.1 현상
선형(~-436802, -1949) 및 종단 근처에서 `MOT`/`FLOW`/수치가 등고선보다 훨씬 큼.
### 5.2 원인
| 항목 | C0060202-001 Model |
|------|---------------------|
| CAD TEXT height | ~0.3 ~ 3.5 (중앙값 ~3, FLOW≈0.3) |
| 전체 bbox 대각선 | ~1.25×10⁶ |
| 구 `minTextH = diag × 0.0002` | **~250** |
`_drawTexts``Math.max(t.height, minTextH)`**모든 라벨이 높이 ~250** 강제.
작은 시트 도면용 “fit 시 가독성” floor가 측량 Model에서 폭주한 것.
### 5.3 처리
```js
minTextH = Math.min(diagSize * 0.0002, medianNativeHeight);
```
- 측량 Model: floor ≤ median → **사실상 CAD 높이 유지**
- 작은 용지 좌표: diag floor가 작으면 기존과 유사
---
## 6. 이슈 #5 — Layout draw order (VIEWPORT가 위)
### 6.1 현상
VIEWPORT로 올린 Model 기하/라벨이 **도곽·NOTE 위**에 그려짐.
### 6.2 원인 (뷰어)
기존: paper 패스 후 VP 투영을 **버퍼 뒤**에 append → VP가 위.
### 6.3 DWG draw-order 메타 (참고만)
| 소스 | 의미 | 정책 |
|------|------|------|
| `SORTENTSTABLE` / `ACAD_SORTENTS` | sort handle 낮을수록 아래 | acadrust에 타입 있음 |
| Header `SORTENTS` | 정렬 플래그 | — |
| parseResult 엔티티 | zOrder 등 **없음** | — |
**오픈소스 파서를 고치지 않으므로 SORTENTS export는 하지 않는다.**
Layout 관례로 고정:
> **VIEWPORT 안 Model 내용 < Paper 주석·도곽**
### 6.4 처리
1. VP Model 투영을 **paper 엔티티 루프보다 먼저** 방출
2. VP 선분 `z = -1`, paper `z = 0` (카메라 +Z 기준 뒤/앞)
3. 텍스트: `underlay` (VP) / 일반 (paper) 분리, `renderOrder` 0 → 1
4. slug/스프라이트 배치도 underlay 먼저 추가
---
## 7. 커서 좌표 / Fit 좌표계 (UCS)
### 7.1 현상
- Model에서 우하단 좌표로 (0,0) 근처를 찾아도 기하가 없음
- Fit 후 표시 좌표가 CAD status bar 와 다름
### 7.2 원인
| 프레임 | 내용 |
|--------|------|
| 기하 저장 (파서 WCS) | 예: 선형 클러스터 ≈ (162065, 476725) |
| CAD Model UCS 원점 (`vars.ucsOrigin`) | ≈ (598948, 478643) |
| CAD에 보이는 좌표 | `UCS = WCS ucsOrigin` → 선형 ≈ **(-436883, -1918)** |
뷰어가 **WCS를 그대로** 읽어 주면 CAD 사용자 좌표와 어긋난다.
또한 Fit bbox에 극단 이상치(수 점)가 포함되면 중심이 빈 공간으로 갈 수 있다.
CAD 참고 extents (Model, 사용자 측정):
```
X: -439601.13 … 4412.05
Y: -100418.91 … 99422.03
```
### 7.3 처리
1. **좌표 표시 (Model):** WCS → **model UCS**
`cad = project(WCS ucsOrigin)` (축 단위 직교 시 subtract origin)
Layout/Paper: identity (시트 좌표)
2. **Pick/measure:** 내부 **WCS** (`_screenToWcs`)
3. **Fit:** 가시 기하 **0.5%99.5% 백분위** AABB (이상치 제거)
4. 스크린→WCS: ortho frustum × camera right/up (view-twist 포함)
### 7.4 검증용 심플 도면 `bb.dwg`
Model 엔티티 5개 (사각형 2 + 연결 poly + hatch 2). `ucsOrigin ≈ (598948, 478643)`.
| 덩어리 | WCS (대략) | CAD UCS (대략) |
|--------|------------|----------------|
| 선형 쪽 박스 | (161969…162448, 475696…476795) | **(-436979…-436500, -2947…-1848)** |
| 원점 쪽 박스 | (598948…600030, 477765…478643) | **(0…1081, -878…0)** — **UCS (0,0)에 모서리** |
| Fit CAD 범위 | — | X ≈ -436979…1081, Y ≈ -2947…0 |
**확인 절차**
1. `bb.dwg` 열기 → **Model** 탭 (기본이 Layout이면 반드시 Model로)
2. Fit → 우하단 좌표가 대략 위 UCS 범위 안
3. 커서 오른쪽 박스 모서리 ≈ **X=0, Y=0**
4. 왼쪽 박스 ≈ **X=-436900, Y=-2400** 부근
CAD WCS (0,0) 에는 기하가 없음. CAD status의 (0,0)은 **UCS 원점**이다.
---
## 8. 검증 체크리스트
| 확인 | 기대 |
|------|------|
| C0060202-001 기본 탭 | Layout |
| Layout | 도곽 + 평면/종단 VP, 주석이 VP 위, 한글(인코딩 패치 병행) |
| Model | ~17k 엔티티, 공간 필터로 paper 제외 |
| Fit | **현재 가시** 전체 AABB (이중 좌표면 둘 다 포함) |
| Model 줌 인 선형/종단 | 글자 크기 ≈ CAD (거대 빌보드 없음) |
| BasicSample | Model 기본, 회귀 없음 |
| 우하단 Model | 커서 좌표 ≈ CAD UCS (선형 ~-436k 등) |
| Fit 후 중심 | 빈 (0,0) WCS 가 아닌 실제 기하 범위 |
---
## 9. 원본 `hmwebviewer` 이식 가이드
| 순서 | 작업 |
|------|------|
| 1 | `cadSpaces.ts` 신규 복사 |
| 2 | `Viewer2D.js` — 공간 필터, VP 투영, draw order, minTextH, fit/AABB, 좌표 API **diff 이식** (통째 덮어쓰기 비권장) |
| 3 | `Viewer2D.d.ts` 동기화 |
| 4 | 호스트 UI: Model/Layout 탭, `pickDefaultSpace` + `spaceHandle` |
| 5 | (선택) 우하단 좌표 표시 |
| 6 | `fixDwgKoreanText` 는 [한글 문서](./korean-dwg-text-encoding.md) 대로 별도 |
| 7 | C0060202-001 + BasicSample 회귀 |
**하지 말 것**
| 금지 | 이유 |
|------|------|
| acadrust 등 오픈소스 파서 수정 | 정책 |
| SORTENTS를 위해 파서 개조 | 뷰어 규칙으로 대체 |
| Model+Paper 동시 렌더 복귀 | 이슈 #1 재발 |
| 밀도 기반 Fit 재도입 | 이슈 #3 에서 철회 |
| `minTextH = diag×0.0002` 단독 | 이슈 #4 재발 |
---
## 10. 잔여 / 후속 (뷰어 측만)
1. VIEWPORT 안 솔리드 HATCH/SOLID — 윤곽 위주 근사
2. 다중 named Layout 탭 캡션 (블록명 `*Paper_Space*` 이상)
3. TILEMODE/CTAB 을 파서 없이 알 수 없으면 현재 휴리스틱 유지
4. 비직교 VIEWPORT (3D viewDirection)
5. (선택) Model 이중 군집 점프 UI
6. (선택) Fit “전체/선택” 분리는 요구 시만
---
## 11. 한 줄 요약
> **Model/Paper를 나눠 그리고, Layout은 VIEWPORT로 Model을 투영하되 paper 아래에 깐다.
> Fit은 가시 엔티티 bbox. 글자 높이는 CAD median을 넘기지 않는다.
> 오픈소스 파서는 수정하지 않으며, draw-order는 Layout 관례로 처리한다.**
---
## 12. 참고 파일 (샘플)
| 경로 | 역할 |
|------|------|
| [`../src/viewer2d/cadSpaces.ts`](../src/viewer2d/cadSpaces.ts) | 공간 목록·기본 탭·owner 클로저 |
| [`../src/viewer2d/Viewer2D.js`](../src/viewer2d/Viewer2D.js) | 필터, VP 투영, draw order, minTextH, fit, 좌표 |
| [`../src/viewer2d/Viewer2D.d.ts`](../src/viewer2d/Viewer2D.d.ts) | 타입 |
| [`../src/main.ts`](../src/main.ts) | 탭·좌표 UI 글루 |
| [`../index.html`](../index.html) | `#spaces`, `#coords` |
| [`korean-dwg-text-encoding.md`](./korean-dwg-text-encoding.md) | CP949 한글 깨짐 (병행 필수) |