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>
267 lines
12 KiB
Markdown
267 lines
12 KiB
Markdown
# 선가중치(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)
|