Hand test against an AutoCAD capture: the 0.50mm road edge is ~10px there but only ~4px here. Lineweight is zoom-independent screen pixels, so the two captures compare directly - the 8 px/mm mapping was 2.5x too small. AutoCAD's default display scale is one pixel per 0.05mm, so use 20 px/mm (0.35mm = 7px, 0.50mm = 10px, 2.11mm = 42px) and raise the clamp to 48px. Add setLineweightScale()/getLineweightScale() so a host can retune it - AutoCAD exposes the same thing as the Adjust Display Scale slider. Re-measured headless: the 0.35mm road-gutter line now renders ~7px, so both weight classes land on the same scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 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 × 20 (LW_PX_PER_MM, 최대 48px)
0.35mm = 7px · 0.50mm = 10px · 2.11mm = 42px ← AutoCAD 기본 표시 배율(0.05mm당 1px)
배율은 setLineweightScale(pxPerMm) 로 조정 가능하다(AutoCAD "표시 배율" 슬라이더 대응,
null 이면 기본값 복귀).
처음에는 8px/mm 로 넣었다가 손테스트에서 "얇다"고 반려됐다. 정답지(AutoCAD 캡처)의
도로 가장자리(0.50mm) 선이 약 10px 인데 8px/mm 면 4px 밖에 안 된다. lineweight 는 줌 무관 화면 픽셀이므로 두 캡처의 픽셀 두께를 직접 비교하면 되고, 그 결과가 20px/mm 다. 자세한 대조는 5-5 절.
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. 도면이 실제로 들고 있는 수치
굵기는 레이어가 아니라 엔티티에 들어 있다. 23개 레이어 lineweight 는 전부 -3
(Default) 이라 ByLayer 로는 아무 정보도 나오지 않는다.
| 레이어 | ACI | lineweight | 개수 |
|---|---|---|---|
| 도로 가장자리 | 1 (빨강) | 50 = 0.50mm | 18 |
| 도로마킹 | 255 / 7 / 2 / 4 | 50 = 0.50mm | 799 |
| 부체도로 가장자리 | 20 | 50 = 0.50mm | 32 |
| 비탈면방향 | 140 / 150 | 35 = 0.35mm | 34,758 |
| 노면측구 | 4 (시안) | 35 = 0.35mm | 303 |
| 시작·끝라인 / Drawing확폭 | 4 / 6 / 2 / 3 | 40 = 0.40mm | 105 |
| SubContour · MainContour | 3 / 6 | 13 = 0.13mm | 8,794 |
| 도로면 (MESH 삼각망) | 7 | -3 Default | 1 (185,444 faces) |
| Station (중심선·라벨) | 1 (빨강) | -1 → 레이어 -3 → Default | 1,082 |
주의: 화면의 빨간 선이 두 종류다. 굵은 쪽이 도로 가장자리(0.50mm), 도로 한가운데를
지나는 얇은 쪽이 Station 중심선(Default → 헤어라인). 정답지에서 굵게 나오는 것은 전자다.
5-5. 정답지 대조 (배율 확정 근거)
lineweight 는 줌과 무관한 화면 픽셀이므로, 두 캡처의 절대 픽셀 두께를 그대로 비교한다.
도로 가장자리 0.50mm |
헤어라인(MESH, Default) | |
|---|---|---|
| AutoCAD 정답지 | ≈10 px | 1 px |
| 뷰어 (8px/mm, 반려) | ≈4 px | 1 px |
| 뷰어 (20px/mm, 현재) | 10 px | 1 px |
헤드리스 재측정으로 교차 확인: 시안색 노면측구(0.35mm)가 화면에서 ≈7 px
(= 0.35 × 20). 두 클래스가 같은 배율로 맞는다.
5-6. 회귀 없음
npx tsc --noEmit통과npm run build통과- 대조 도면 4종: 굵기 전부 Default → 화면 변화 없음(설계 3-2 의도대로)
6. 남은 과제
- 표시 배율 UI 부재 — 값은
setLineweightScale(pxPerMm)로 조절되지만 툴바에는 on/off 버튼만 있다. 슬라이더가 필요하면 이 API 에 붙이면 된다. 0.35mm 가 7px 라비탈면방향(34,758개)이 있는 구간은 화면이 상당히 무거워 보인다 — 도면이 지정한 수치대로이긴 하나, 배율을 낮추고 싶으면 이 API 를 쓴다. - 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