# 선가중치(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)
