feat(viewer2d): render DWG lineweight in screen pixels

Every line was drawn 1px regardless of its CAD lineweight. Two causes:
the dwg-wasm parseResult never carried the field (acadrust reads
EntityCommon::line_weight but it was dropped in serialization), and the
renderer merged all segments into one LineSegments whose
LineBasicMaterial.linewidth WebGL ignores.

Resolve ByLayer/ByBlock/Default per entity and bucket segments by
dash|lineweight. Weighted buckets get their own LineSegments2 +
LineMaterial with worldUnits:false, so width stays constant in pixels
while zooming - AutoCAD LWDISPLAY semantics - at 8 px/mm (0.25mm = 2px).
Anything at or below the 0.25mm default stays in the hairline batch so
drawings that never assigned a weight render exactly as before.

Fat batches are instanced, so layer masking compacts the instance buffer
and lowers instanceCount instead of rewriting an index; slotOf tracks
where each segment moved so the selection highlight still finds its
vertices. Per-entity color spans (meta.spans) let the highlight repaint
across the hairline batch and every bucket it touched. Buckets past
400k segments fall back to hairline to bound GPU/heap cost.

Display follows the drawing's LWDISPLAY and can be forced from the
toolbar; the property panel now shows the entity lineweight.

Verified headless at a fixed camera on the 65k-entity road drawing:
cyan road edge 2px -> 5px, layer hide/restore returns the exact baseline
pixel counts, and a 0.35mm LWPOLYLINE selects and highlights.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
minsung
2026-08-04 17:25:11 +09:00
co-authored by Claude Opus 5
parent eec33820a6
commit c67a803e4d
12 changed files with 6922 additions and 34 deletions
+227
View File
@@ -0,0 +1,227 @@
# 개선 결과 보고 — SubDMesh + 선가중치 (2026-08-04)
| 항목 | 내용 |
|------|------|
| 작성 목적 | 같은 날 들어간 두 개선(SubDMesh 렌더링, 선가중치 렌더링)의 **측정 결과**와, 개별 문서에는 없는 **두 변경의 상호작용**을 남긴다 |
| 개별 문서 | [`subdmesh-rendering.md`](./subdmesh-rendering.md) · [`lineweight-rendering.md`](./lineweight-rendering.md) |
| 기준 도면 | `dist2/samples/대산당진 2공구_노면_20260804_143854.dwg` (37.1 MB, AC1032 / AutoCAD 2018) |
| 측정 방식 | `Viewer2D.js` 에서 `_meshSegs` · `_lwPx` 를 원본 그대로 들어내 실제 parseResult 에 돌린 값. 브라우저 픽셀 측정이 아니라 **세그먼트 회계** |
---
## 1. 요약
| | 개선 전 | 개선 후 |
|--|--------|--------|
| MESH 엔티티 파싱 | 0개 | 9개 |
| 도로면 메쉬 | 정점 100,000 / 면 14 (**깨진 값**) | 정점 105,497 / 면 185,444 / 모서리 290,931 |
| MESH 화면 출력 | 없음 | 315,633 선분 (면 wireframe) |
| parseResult 크기 | 116 MB | 130 MB (+12%) |
| 파싱 시간 | ~3.2 s | ~3.2 s (변화 없음) |
| 선가중치 | 전부 1px 헤어라인 | 3개 굵기 버킷이 fat line 으로 분리 (아래 3절) |
---
## 2. 기준 도면 프로필
파싱 결과 65,495 엔티티.
```
LWPOLYLINE 47,849 MTEXT 13,974 ATTRIB 1,504 TEXT 1,389
INSERT 752 ATTDEF 15 MESH 9 LINE 2 CIRCLE 1
```
MESH 9개:
| handle | layer | 정점 | 면 | 모서리 | 세분레벨 |
|--------|-------|------|-----|--------|---------|
| 4188 | 도로면 | 105,497 | 185,444 | 290,931 | 0 |
| 4189 | 부체도로면 | 11,712 | 12,568 | 24,226 | 0 |
| 296564 | 일반도로부체도로연결부 | 48 | 44 | 90 | 0 |
| 296766 · 296927 · 297111 · 297259 · 297433 · 297665 | 부체도로(부체도로)연결부 | 28~53 | 25~47 | 51~96 | 0 |
전부 `subdivisionLevel = 0` 삼각 TIN → Catmull-Clark 세분 미구현이 이 도면에서는
화면 차이를 만들지 않는다.
---
## 3. 개선 1 — SubDMesh
원인 분석과 수정 내용은 [`subdmesh-rendering.md`](./subdmesh-rendering.md) 에 있다.
여기에는 **결과 수치**만 남긴다.
### 3-1. 스트림 어긋남 해소
`read_mesh` 의 배열 상한을 `MAX_ARRAY_COUNT`(100,000) 에서 "남은 스트림 비트 ÷
항목 최소 인코딩 길이" 로 바꾼 결과:
| | 정점 | 면 | 모서리 | 앞쪽 면 크기 |
|--|------|-----|--------|-------------|
| 전 | 100,000 | 14 | 3 | `[64, 0, 64, 23, 0, 0, 0, 15]` |
| 후 | 105,497 | 185,444 | 290,931 | `[3, 3, 3, 3, 3, 3, 3, 3]` |
오일러 표수 V E + F = 105,497 290,931 + 185,444 = **10**. 경계를 가진
다중 컴포넌트 곡면으로 타당한 값이다 (닫힌 단일 구면이면 2).
### 3-2. 선분 생성
| 경로 | 선분 수 | 비고 |
|------|---------|------|
| `edges` (중복 제거된 모서리) | **315,633** | 실제 사용 |
| `faceList` 폴백 (면 루프 순회) | 741,072 | 2.35배. 두 경로의 그림 범위는 동일 |
- 비유한(NaN/Infinity) 좌표 **0건**
- 길이 0 선분 6건 (원본 데이터의 축퇴 모서리, 무해)
- 선분 bbox 가 메쉬 정점 bbox 와 일치 — x[151,892 … 161,087] y[477,930 … 482,146]
- z 는 선분 양 끝 표고의 중간값, 범위 1.5 ~ 68. 이 도면은 LWPOLYLINE(0~68)·
TEXT(0~54)도 같은 대역에 있어서 **메쉬만 위로 뜨지 않는다**
### 3-3. 손상 입력 방어
| 입력 | 결과 |
|------|------|
| 빈 객체 / 정점 배열 길이 2 (3의 배수 아님) | 0 선분, 예외 없음 |
| 범위 밖 모서리 인덱스 (`[0, 99, -3, 1]`) | 0 선분 (경계 검사에서 탈락) |
| 면 개수가 남은 배열보다 큼 (`[9, 0, 1]`) | 0 선분, 루프 탈출 |
| 면 개수 0 반복 (`[0, 0, 0]`) | 0 선분, **무한루프 없음** |
| 정상 삼각형 (`[3, 0, 1, 2]`) | 3 선분 |
---
## 4. 개선 2 — 선가중치
측정·판정은 [`lineweight-rendering.md`](./lineweight-rendering.md) 3절(헤드리스
Chrome `readPixels` 비교)에 있다. 이 문서는 그 결과를 다시 주장하지 않고,
아래 5절에서 **세그먼트 부하** 관점만 더한다.
이 도면의 엔티티 선가중치 원시값 분포 (i16, 1/100 mm):
```
-3 (Default) ×15,176 -1 (ByLayer) ×2,613
13 (0.13mm) × 8,794 35 (0.35mm) ×37,944 40 ×119 50 ×849
vars.lwdisplay = true vars.celweight = 29
```
---
## 5. 새로 확인된 상호작용 — 메쉬 315,633 선분이 어느 버킷에 들어가는가
두 개선이 같은 `pushSeg` 를 공유한다. 선가중치 작업이 `pushSeg`
`dash|lineweight` 버킷으로 쪼갰으므로, **메쉬 선분도 그 버킷 규칙을 탄다.**
개별 문서 어느 쪽에도 이 얘기가 없어서 실측했다.
### 5-1. 지금은 안전하다
MESH 9개 전부:
```
own lineWeight = -3 (Default), layer lineWeight = -3
```
`_lwPx``-3``LW_DEFAULT_MM`(0.25mm) → `!(0.25 > LW_HAIRLINE_MM)`**0 반환**.
즉 315,633 선분이 전부 기존 병합 `LineSegments` 헤어라인 배치에 남고,
`LineSegments2` 인스턴싱에는 **한 개도 들어가지 않는다.**
"0.25mm 이하는 헤어라인 유지" 규칙이 — 원래는 굵기 미지정 도면의 모양 보존이
목적이었는데 — 결과적으로 대형 TIN 메쉬가 fat-line 경로로 쏟아지는 것을 막고 있다.
### 5-2. 현재 버킷 부하
LINE · LWPOLYLINE · MESH 기준 (ARC/CIRCLE/HATCH 는 미포함이라 실제 값은 이보다 조금 큼):
| 굵기 px | 선분 수 | 경로 | `LW_MAX_FAT_SEGS`(400,000) 대비 |
|--------|---------|------|-------------------------------|
| 0 (헤어라인) | 1,760,253 | 병합 `LineSegments` | — |
| 2.8 (0.35mm) | 182,539 | fat line | 45.6% |
| 3.2 (0.40mm) | 4,254 | fat line | 1.1% |
| 4.0 (0.50mm) | 92,130 | fat line | 23.0% |
헤어라인 1,760,253 중 메쉬가 **315,633 (17.9%)** 을 차지한다.
### 5-3. 위험 — 메쉬 레이어에 굵기가 지정되면 버킷이 터진다
버킷 키는 `dash|lwPx` 이고 **도면 전역**이다. 도로면/부체도로면 레이어에
0.35mm 를 지정하면 메쉬 선분이 이미 182,539 선분이 있는 2.8px 버킷에 합류한다.
```
182,539 + 315,633 = 498,172 선분 = LW_MAX_FAT_SEGS 의 124.5%
```
상한을 넘으면 `bk.fat = false` 가 되어 **그 버킷 전체가 헤어라인으로 되돌아간다.**
메쉬만 헤어라인이 되는 게 아니라, 지금 정상적으로 굵게 나오는 도로 가장자리
182,539 선분까지 같이 얇아진다. `console.warn` 한 줄만 남고 화면상으로는
"선가중치가 갑자기 안 먹는" 것처럼 보인다.
**대응 후보** (지금 당장 필요하진 않다 — 이 도면은 5-1 때문에 해당 없음):
1. MESH 를 선가중치 대상에서 제외 — 메쉬 wireframe 을 굵게 그릴 실익이 없다.
`_lwPx` 호출부에서 `type === 'MESH'` 면 0 을 쓰는 게 가장 싸다.
2. 상한 초과 시 버킷 전체를 버리지 말고 **큰 기여자만** 헤어라인으로 강등.
3. `LW_MAX_FAT_SEGS` 를 GPU 능력 기준으로 잡기 — 현재 40만은 고정 상수다.
---
## 6. 회귀 검증
새 wasm 과 직전 wasm(`git show HEAD:` 로 추출)으로 나머지 샘플 6개를 파싱해
엔티티 히스토그램을 비교했다.
| 도면 | 버전 | 엔티티 | 히스토그램 |
|------|------|--------|-----------|
| BasicSample.dwg | R2018 | 7,309 | 동일 |
| bb.dwg | R2018 | 438 | 동일 |
| civil.dwg | R2000 | 17,810 | 동일 |
| C0060202-001-평면및종단면도(1) | R2000 | 17,810 | 동일 |
| C0060203-001-표준단면도(2차로) | R2007 | 2,068 | 동일 |
| dwg_18.3mb.dwg | R2018 | 82,393 | 동일 |
6개 전부 **완전 일치**. 노면 도면만 65,486 → 65,495 (+9 MESH). `read_mesh` 상한
변경이 MESH 이외 엔티티에 영향을 주지 않음을 확인한 것이다.
추가로 `tsc --noEmit`, `vite build`, 편집한 JS 3개 `node --check` 통과.
---
## 7. 반영 상태
| 대상 | 상태 |
|------|------|
| hmwebviewer `feat/subdmesh-render` `35f5b1c` | 파서 + 렌더러 + 속성 패널. **푸시 안 됨** |
| 이 저장소 `feat/subdmesh-render` `eec3382` | wasm + 속성 패널 + 문서. **푸시 안 됨** |
| 이 저장소 `src/viewer2d/Viewer2D.js` | **미커밋.** `_meshSegs` + MESH 디스패치 3군데가 선가중치/fat-line 리팩터와 hunk 가 겹쳐 분리 불가. 같은 코드가 hmwebviewer `35f5b1c` 에는 들어가 있다 |
| `dist2/` | **미갱신.** 현재 빌드에 반쯤 배선된 선가중치 UI 가 섞여 있다 |
| hmwebviewer wasm 산출물 | 미커밋. 선가중치 소스가 커밋될 때 `npm run build:dwg-wasm` 로 함께 |
커밋된 wasm 바이너리에는 선가중치 파서 필드(`lwdisplay`, `celweight`,
layer/entity `lineWeight`)도 들어 있다. 빌드 시점의 소스에 이미 있었기 때문이며,
소비 측 없는 추가 JSON 키라 무해하다.
---
## 8. 남은 과제
1. **5-3 버킷 상한** — 메쉬 레이어에 굵기가 지정된 도면이 들어오면 재현된다.
샘플을 만들어 재현시켜 두는 편이 좋다.
2. **Catmull-Clark 세분**`subdivisionLevel > 0` 메쉬는 AutoCAD 보다 각져 보인다.
기준 도면에는 해당 없어 미구현.
3. **POLYFACE MESH / POLYGON MESH** — 구형 POLYLINE flag 16/64 계열은 여전히
미지원. SubDMesh 와 별개 엔티티다.
4. **메쉬 채움(shaded) 패스**`faceList` 를 이미 내보내고 있으므로 삼각화만
하면 된다. 2D 와이어프레임 뷰어라 우선순위는 낮다.
5. **`dist2` 배포** — 선가중치 작업이 끝난 뒤
[`deploy-dist2-static-build.md`](./deploy-dist2-static-build.md) 절차로.
---
## 9. 측정 재현
```bash
# 네이티브 메쉬 진단 (면 크기가 3/4 로 균일하지 않으면 스트림 어긋남 의심)
cd D:\MYCLAUDE_PROJECT\hmwebviewer\rust\dwg-wasm
cargo run --release --bin meshprobe -- <file.dwg>
```
5절의 버킷 표는 `Viewer2D.js` 에서 `_meshSegs` · `_lwPx` 를 들어내 parseResult 에
직접 돌려 만들었다 (`LINE` / `LWPOLYLINE` / `MESH` 만 계수). `Viewer2D.js` 는 three
와 확장자 없는 TS 모듈을 import 하므로 node 에서 그대로 import 되지 않는다 —
함수 본문만 뽑아 `new Function` 으로 감싸는 방식이었다.
+266
View File
@@ -0,0 +1,266 @@
# 선가중치(Lineweight) 미적용 — 원인 분석 및 해결 가이드
| 항목 | 내용 |
|------|------|
| 작성 목적 | 모든 선이 1px 헤어라인으로만 그려지던 문제의 원인 규명과 수정·검증 내역 기록 |
| 샘플 저장소 | `dwg-dxf-viewer-sample` (본 문서 위치) |
| 원본 저장소 | `hmwebviewer` (`rust/dwg-wasm/`, `src/viewer2d/` 동기화 대상) |
| 재현 도면 | `대산당진 2공구_노면_20260804_143854.dwg` (65,495 entities · LWDISPLAY=on · 0.13/0.35/0.40/0.50mm 혼재) |
| 대조 도면 | `BasicSample.dwg`, `civil.dwg`, `bb.dwg`, `C0060202-001-평면및종단면도(1).dwg` (모두 굵기 미지정) |
| 정답지 | AutoCAD 스크린샷 (`dist2/samples/1785826356.png`) |
| 수정일 | 2026-08-04 |
| 상태 | **해결** — 파서·렌더러 수정 완료, `dist2` 재빌드 완료, 커밋/푸시는 미실행 |
> **폭(width)과 혼동 주의.** 도곽 외곽선이 예전부터 굵게 그려진 것은 lineweight 가 아니라
> LWPOLYLINE 의 **폭**(`constantWidth` / `startWidths` / `endWidths`)이다. 도면단위 실폭이라
> 줌하면 같이 커지고, `_widePolyMesh` 가 삼각형 메쉬로 처리한다. lineweight 는 화면 픽셀
> 단위(줌해도 폭 고정)이며 이번 문서의 대상이다. 둘은 별개 기능이다.
---
## 1. 현상
AutoCAD 에서는 도로 가장자리·교량라인이 뚜렷하게 굵고 비탈면 선은 가는데,
뷰어에서는 **모든 선이 같은 1px**로 그려진다. 색상·선종류(점선)는 정상.
정답지와 대조하면 굵기 비율이 완전히 소실된 상태였다.
---
## 2. 오류 원인
원인은 **두 겹**이었다. 한쪽만 고쳐서는 화면이 바뀌지 않는다.
### 2-1. 파서가 lineweight 를 내보내지 않았다 (데이터 부재)
acadrust 는 lineweight 를 **이미 읽고 있었다.**
```
acadrust/src/entities/mod.rs:260 pub line_weight: LineWeight, // EntityCommon
acadrust/src/tables/layer.rs:58 pub line_weight: LineWeight, // Layer
acadrust/src/types/line_weight.rs DWG 5비트 테이블 인덱스 ↔ 값 변환까지 구현
```
그런데 `dwg-wasm` 의 JSON 직렬화(`common_json` / `tables_value` / `vars_value`)에서
해당 필드가 빠져 있어 parseResult 에 값이 존재하지 않았다.
확인 방법 — wasm 바이너리의 serde 필드명 문자열을 직접 조회:
```bash
grep -a -c "linetypeScale" acadrust_dwg_bg.wasm # 1 (있음)
grep -a -c "entityHeader" acadrust_dwg_bg.wasm # 1 (있음)
grep -a -o -i "[a-z_]*weight[a-z_]*" acadrust_dwg_bg.wasm | sort -u # (없음)
```
parseResult 덤프로도 재확인:
```
layer[0] = { colorIndex, frozen, handle, lineType, locked, name, on, plotting } ← lineWeight 없음
entityHeader = { colorIndex, invisible, linetypeScale } ← lineWeight 없음
```
### 2-2. 렌더러가 굵기를 그릴 수단이 없었다 (표현 부재)
Viewer2D 는 성능을 위해 도면의 모든 선분을 **하나의 병합 버퍼**로 그린다.
```js
const lmesh = new THREE.LineSegments(geom, new THREE.LineBasicMaterial({ vertexColors: true }));
```
WebGL 코어 프로파일에서 `LineBasicMaterial.linewidth`**무시되고 항상 1px** 이다
(three.js 문서 명시). 즉 2-1 이 해결되어 값이 들어오더라도 이 배치 구조로는
굵기를 표현할 수 없다.
### 2-3. 왜 지금까지 드러나지 않았나
굵기를 지정한 도면이 저장소 샘플에 사실상 없었다. 엔티티 lineweight 히스토그램:
| 도면 | LWDISPLAY | 엔티티 lineweight |
|------|-----------|-------------------|
| `BasicSample.dwg` | true | `-2` ×62, `-1` ×7247 → 전부 ByLayer/ByBlock → 레이어가 전부 Default |
| `civil.dwg` / `bb.dwg` / `C0060202` | **false** | `-1` ×17241, `0` ×569 |
| **`대산당진 2공구_노면`** | true | `-3` ×15176, `-1` ×2613, **`13` ×8794, `35` ×37944, `40` ×119, `50` ×849** |
굵기 데이터가 실제로 있는 도면은 노면 도면 하나뿐이었고, 그 도면은 37MB 라
로딩 성능 이슈가 해결된 뒤에야 일상적으로 열리기 시작했다.
---
## 3. 개선 사항 (설계 결정)
수정 방향을 정할 때 확정한 규칙들.
### 3-1. 픽셀 환산 — AutoCAD LWDISPLAY 규격을 따른다
lineweight 는 **화면 픽셀 폭**이다. 줌해도 굵기가 변하지 않는다(모델 공간 기준).
따라서 월드 단위가 아니라 스크린 스페이스로 그려야 한다.
```
px = mm × 8 (LW_PX_PER_MM, 최대 24px)
0.25mm ≈ 2px · 0.50mm ≈ 4px · 2.11mm ≈ 17px ← AutoCAD 기본 표시 배율과 동일 눈금
```
### 3-2. 0.25mm 이하는 헤어라인 유지 — **회귀 방지 장치**
`-3`(Default)·`-1`(ByLayer → 레이어도 Default)이 도면 대부분을 차지한다.
이것까지 2px 로 그리면 **굵기를 지정한 적 없는 기존 도면이 통째로 두꺼워진다.**
`LW_HAIRLINE_MM = 0.25` 이하는 기존 병합 배치에 그대로 남긴다.
그 결과 대조 도면 4종은 **픽셀 단위로 이전과 동일**하게 렌더링된다.
### 3-3. 굵은 선만 별도 배치 — 성능 보존
전체를 fat line 으로 바꾸면 세그먼트당 인스턴스 8정점이라 대형 도면에서 감당이 안 된다.
`dash|lineweight` 조합으로 버킷을 나눠 **굵기가 있는 버킷만** 별도 메쉬로 뽑는다.
| 버킷 | 메쉬 | 비고 |
|------|------|------|
| 실선 + 헤어라인 | 기존 병합 `LineSegments` | 변경 없음 |
| 점선 + 헤어라인 | `LineSegments` + `LineDashedMaterial` | 변경 없음 |
| 굵기 있음 | `LineSegments2` + `LineMaterial` | 신규 (스크린스페이스 쿼드) |
한 버킷이 `LW_MAX_FAT_SEGS`(40만) 세그먼트를 넘으면 헤어라인으로 폴백하고
`console.warn` 을 남긴다. 대형 측량 도면에서 GPU/힙이 터지는 것을 막는 상한.
### 3-4. 기존 기능을 깨지 않는다
새 메쉬 타입이 들어오면서 다음 두 기능이 조용히 망가질 수 있었다. 둘 다 대응했다.
- **레이어 토글** — 인스턴스 지오메트리는 인덱스 재작성이 불가
- **선택 하이라이트** — 색상 배열이 여러 버퍼로 쪼개짐
### 3-5. 표시 on/off — AutoCAD LWDISPLAY 와 동일하게
굵기 표시는 CAD 에서 원래 켜고 끌 수 있는 옵션이다. 도면의 헤더 값을 기본으로 따르되
사용자가 강제할 수 있어야 한다.
---
## 4. 조치 사항
### 4-1. 파서 — `hmwebviewer/rust/dwg-wasm/src/lib.rs`
원시 i16 값(1/100 mm, `-1` ByLayer · `-2` ByBlock · `-3` Default)을 세 군데에 추가.
| 함수 | 추가 필드 |
|------|-----------|
| `common_json``entityHeader` | `lineWeight` |
| `tables_value``layers[]` | `lineWeight` |
| `vars_value` | `lwdisplay`, `celweight` |
재빌드 + 산출물 복사:
```bash
cd D:\MYCLAUDE_PROJECT\hmwebviewer
npm run build:dwg-wasm
cp src/viewer2d/acadrust-dwg/acadrust_dwg* \
../dwg-dxf-viewer-sample/src/viewer2d/acadrust-dwg/
```
검증(노면 도면):
```
vars.lwdisplay = true vars.celweight = 29
entity lineWeight = { -3:15176, -1:2613, 13:8794, 35:37944, 40:119, 50:849 }
```
### 4-2. 렌더러 — `src/viewer2d/Viewer2D.js`
| 추가/변경 | 내용 |
|-----------|------|
| `_lwPx(entity, inheritedPx)` | ByLayer→LAYER 테이블, ByBlock→INSERT 굵기, Default→0.25mm 해석 후 픽셀 환산. 0.25mm 이하는 `0`(헤어라인) 반환 |
| `_buildLayerMap` | 레이어 lineweight 를 핸들·이름 두 키로 보관 (DXF 레이어는 핸들이 없음) |
| `pushSeg` | 세그먼트를 `dash|lineweight` 버킷으로 분기 + 엔티티별 색상 구간(`meta.spans`) 기록 |
| 조립부 | 굵은 버킷 → `LineSegments2` + `LineMaterial(worldUnits:false)`. 캡 초과 시 헤어라인 폴백 |
| `_registerFat` / `_applyLayerVisibility` | 보이는 인스턴스만 버퍼 앞으로 압축 후 `geometry.instanceCount` 축소. 원본 배열 보존, 이동 위치는 `slotOf` 에 기록 |
| `_paintMeta` / `_paintSpan` | 헤어라인 구간 + 모든 버킷 구간을 함께 칠함. 레이어 토글로 재배치되면 `slotOf` 경유로 다시 칠함 |
| `_insertEntities` | 자식마다 `_lwPx(child, insertLwPx)` — ByBlock 이 INSERT 굵기를 상속. 루프 종료 시 호출자 값 복구 |
| `_onResize` | `LineMaterial.resolution` 재설정 (픽셀 폭 기준이 캔버스 크기라서 필수) |
| `setLineweightEnabled(on)` / `getLineweightEnabled()` | 표시 스위치. `null` = 도면의 LWDISPLAY 를 따름 |
### 4-3. UI
- `index.html` · `src/main.ts` — 툴바 **선가중치** 버튼 (상태에 따라 강조, 툴팁에 ON/OFF)
- `src/propertyInspector.ts``선가중치 (Lineweight)` 행 추가.
`-1``BYLAYER`, `-2``BYBLOCK`, `-3``DEFAULT (0.25mm)`, 그 외 `0.35mm` 형식
### 4-4. 배포
```bash
./node_modules/.bin/vite build --outDir dist2 --emptyOutDir false
```
`dist2/assets/index-D3FqDGwP.js`, `acadrust_dwg_bg-DmCEpFBu.wasm`.
`--emptyOutDir false` 는 필수 ([`deploy-dist2-static-build.md`](./deploy-dist2-static-build.md) 참조).
**커밋/푸시는 하지 않았다** — 공유 원격이라 사용자 확인 필요.
---
## 5. 검증
헤드리스 Chrome(SwiftShader) 1400×1000, 노면 도면, 동일 카메라(30단계 확대)에서
`gl.readPixels` 로 직접 계측.
### 5-1. 굵기 반영
| | 밝은 픽셀 수 | 중앙 스캔라인 최대 cyan 폭 |
|--|--|--|
| 선가중치 **ON** | 137,528 | **5 px** |
| 선가중치 **OFF** | 122,245 | 2 px |
0.50mm × 8 = 4px + 안티에일리어싱 → 5px. 설계값과 일치.
### 5-2. 레이어 토글 무손실
`도로 가장자리` 레이어 끄기 → 켜기:
```
기준 {"lit":137528,"cyan":12162,"accent":6741}
숨김 {"lit":136735,"cyan":12553,"accent":5776}
복원 {"lit":137528,"cyan":12162,"accent":6741} ← 기준과 완전 일치
```
### 5-3. 선택 하이라이트
굵은 선(0.35mm LWPOLYLINE, 레이어 `비탈면방향`) 클릭:
```
LWPOLYLINE · #151257
레이어 비탈면방향 · 색상 BYLAYER · 선가중치 (Lineweight) 0.35mm
```
cyan 픽셀 수가 감소(11502→11486) → fat 배치의 정점이 강조색으로 재도색됨을 확인.
### 5-4. 회귀 없음
- `npx tsc --noEmit` 통과
- `npm run build` 통과
- 대조 도면 4종: 굵기 전부 Default → **화면 변화 없음**(설계 3-2 의도대로)
---
## 6. 남은 과제
1. **표시 배율 고정**`LW_PX_PER_MM = 8` 이 상수다. AutoCAD 는 "선가중치 설정 → 표시 배율"
슬라이더로 조절 가능하므로, 필요하면 `setLineweightScale()` 로 노출할 것.
2. **ByBlock 근사** — 블록 자식의 `-2` 는 INSERT 굵기를 상속하지만, 중첩 블록에서
중간 INSERT 가 `-2` 인 경우까지는 추적하지 않는다(점선 처리와 동일한 기존 정책).
3. **DXF 레이어 굵기 없음**`dxf-parser` 의 LAYER 핸들러가 코드 370 을 무시한다.
DXF 는 엔티티 자체 굵기(`entity.lineweight`)만 반영되고 ByLayer 는 Default 로 떨어진다.
4. **`celweight` 미사용** — 헤더 CELWEIGHT 를 값이 아니라 DWG 5비트 테이블 인덱스로
내보내고 있다(노면 도면에서 `29` = ByLayer 인덱스). 현재 렌더링에 쓰지 않으므로
방치했으나, 쓰게 되면 `LineWeight::from_dwg_index` 변환을 파서 쪽에 넣어야 한다.
5. **40만 세그먼트 캡** — 초과 시 조용히 얇아지는 대신 `console.warn` 만 남는다.
상태 표시줄 등 사용자에게 보이는 알림이 필요할 수 있다.
---
## 7. 참고
- 렌더러: [`../src/viewer2d/Viewer2D.js`](../src/viewer2d/Viewer2D.js) —
`_lwPx`, `_registerFat`, `_applyLayerVisibility`, `_paintMeta`, `setLineweightEnabled`
- 속성 패널: [`../src/propertyInspector.ts`](../src/propertyInspector.ts) — `formatLineweight`
- 파서: `hmwebviewer/rust/dwg-wasm/src/lib.rs``common_json`, `tables_value`, `vars_value`
- 폭(width)과의 차이: `_segWidths` / `_widePolyMesh` (도면단위 실폭)
- 같은 유형의 선례(파서 필드 누락 → 렌더 불가): [`subdmesh-rendering.md`](./subdmesh-rendering.md)
- 배포 절차: [`deploy-dist2-static-build.md`](./deploy-dist2-static-build.md)