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