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>
12 KiB
선가중치(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 필드명 문자열을 직접 조회:
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 는 성능을 위해 도면의 모든 선분을 하나의 병합 버퍼로 그린다.
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 |
재빌드 + 산출물 복사:
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 |
| 조립부 | 굵은 버킷 → 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. 배포
./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 참조).
커밋/푸시는 하지 않았다 — 공유 원격이라 사용자 확인 필요.
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. 남은 과제
- 표시 배율 고정 —
LW_PX_PER_MM = 8이 상수다. AutoCAD 는 "선가중치 설정 → 표시 배율" 슬라이더로 조절 가능하므로, 필요하면setLineweightScale()로 노출할 것. - ByBlock 근사 — 블록 자식의
-2는 INSERT 굵기를 상속하지만, 중첩 블록에서 중간 INSERT 가-2인 경우까지는 추적하지 않는다(점선 처리와 동일한 기존 정책). - DXF 레이어 굵기 없음 —
dxf-parser의 LAYER 핸들러가 코드 370 을 무시한다. DXF 는 엔티티 자체 굵기(entity.lineweight)만 반영되고 ByLayer 는 Default 로 떨어진다. celweight미사용 — 헤더 CELWEIGHT 를 값이 아니라 DWG 5비트 테이블 인덱스로 내보내고 있다(노면 도면에서29= ByLayer 인덱스). 현재 렌더링에 쓰지 않으므로 방치했으나, 쓰게 되면LineWeight::from_dwg_index변환을 파서 쪽에 넣어야 한다.- 40만 세그먼트 캡 — 초과 시 조용히 얇아지는 대신
console.warn만 남는다. 상태 표시줄 등 사용자에게 보이는 알림이 필요할 수 있다.
7. 참고
- 렌더러:
../src/viewer2d/Viewer2D.js—_lwPx,_registerFat,_applyLayerVisibility,_paintMeta,setLineweightEnabled - 속성 패널:
../src/propertyInspector.ts—formatLineweight - 파서:
hmwebviewer/rust/dwg-wasm/src/lib.rs—common_json,tables_value,vars_value - 폭(width)과의 차이:
_segWidths/_widePolyMesh(도면단위 실폭) - 같은 유형의 선례(파서 필드 누락 → 렌더 불가):
subdmesh-rendering.md - 배포 절차:
deploy-dist2-static-build.md