# 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.7e3–2.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 사각형 Cohen–Sutherland 클리핑 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 | |--|-----|---------| | 폭 | ~438 000 | ~-437 000 … ~1 000 | | 높이 | **~2 900** | ~-2 950 … 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 한글 깨짐 (병행 필수) |