Files
dwg-dxf-viewer-sample/docs/lineweight-rendering.md
minsungandClaude Opus 5 1f80442f6c feat(ui): options panel with an IndexedDB-backed lineweight display scale
Set the default lineweight scale to 11 px/mm: the value the user matched
against AutoCAD on the actual screen. The 20 px/mm derived from their
screenshot was too heavy in practice - the capture and the monitor do
not share a DPI - so the constant is now only a starting point.

Move the lineweight controls off the toolbar into an Options panel:
display toggle, scale slider plus number box, a readout of what the
drawing's own weights come out to, grid toggle, zoom speed, and a reset.
Settings persist in IndexedDB (hmw-viewer/options) rather than
localStorage, so values keep their type and the store has room to grow
per-drawing later; every call degrades quietly if storage is blocked.

Scale changes still apply to the live materials - each fat batch now
remembers its weight in mm - so dragging the slider retunes a 37MB
drawing instantly.

Verified headless: 0.35mm renders 3-4px at scale 11 and 8-9px at 25,
matching the readout, and the value survives a reload via IndexedDB.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 09:00:37 +09:00

16 KiB
Raw Permalink Blame History

선가중치(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 × 11       (LW_PX_PER_MM 기본값, 최대 48px)
0.35mm = 3.9px · 0.50mm = 5.5px · 2.11mm = 23px

이 배율은 상수로 확정할 수 있는 값이 아니다. AutoCAD 도 모델 공간에서는 선가중치를 줌과 무관한 픽셀로만 그리고, 그 환산은 "선가중치 설정 → 표시 배율" 슬라이더가 정한다. 즉 화면 DPI·모니터에 따라 맞는 값이 달라진다.

  • 코드 기본값: LW_PX_PER_MM = 11 (사용자가 실제 화면에서 AutoCAD 와 나란히 놓고 맞춘 값)
  • 런타임 조정: 옵션 패널 → 선가중치 → 표시 배율, 또는 setLineweightScale(pxPerMm)
  • 조정은 재파싱 없이 즉시 반영된다 (3-6 절)

시행착오 기록: 처음 8px/mm(96dpi 어림) → 손테스트 "얇다" 반려 → 정답지 캡처에서 역산해 20px/mm → 실제 화면 대조 후 최종 11px/mm. 상수 추정이 두 번 빗나갔고, 그래서 값을 코드에서 빼 옵션으로 노출했다.

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-6. 배율 조정은 리빌드 없이

헤어라인 컷오프가 mm 값(0.25mm)이라 배율이 바뀌어도 어떤 세그먼트가 어느 버킷에 들어가는지는 변하지 않는다. 모든 굵기가 같은 배수로 움직일 뿐이다.

→ 각 fat 배치가 자기 굵기를 mm 로 기억(_fatBufs[].lwMm)하고, setLineweightScale()material.linewidth 만 다시 쓴다. 37MB 도면에서도 슬라이더를 끄는 동안 지연이 없다.

반면 표시 on/off 는 굵기가 0이 되어 버킷 구성이 바뀌므로 재빌드가 필요하다.

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_jsonentityHeader lineWeight
tables_valuelayers[] 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

툴바 옵션 버튼 → 옵션 (Options) 패널 (index.html · src/main.ts).

항목 동작
표시 (LWDISPLAY) on/off. 체크 해제 시 전부 헤어라인 (재빌드)
표시 배율 슬라이더 + 숫자 입력 (px/mm). 즉시 반영
환산 표시 그 도면이 실제로 쓰는 굵기만 0.35mm → 3.9px 식으로 표기
↺ 기본값 배율·표시·그리드·줌속도 초기화

설정은 IndexedDB (hmw-viewer DB → options 스토어)에 저장한다 (../src/optionsStore.ts). 키: lineweightScale, lineweightOn, grid, zoomSpeed. 스토리지가 막혀 있어도 조용히 기본값으로 동작한다.

속성 패널에는 선가중치 (Lineweight) 행이 있다 (src/propertyInspector.ts). -1BYLAYER, -2BYBLOCK, -3DEFAULT (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 노면측구 0.35mm (헤드리스 실측)
8 px/mm (최초, 반려) 4 px 23 px
20 px/mm (캡처 역산) 10 px 7 px
11 px/mm (현재 기본값) 5.5 px 34 px
25 px/mm (참고) 12.5 px 89 px

캡처 이미지에서 역산한 20 은 실제 화면에서 과했다 — 캡처 DPI 와 사용자 모니터가 달라서다. 최종값은 사용자가 뷰어와 AutoCAD 를 나란히 놓고 맞춘 11. 이래서 상수로 못 박지 않고 옵션으로 뺐다.

5-6. 회귀 없음

  • npx tsc --noEmit 통과
  • npm run build 통과
  • 대조 도면 4종: 굵기 전부 Default → 화면 변화 없음(설계 3-2 의도대로)

6. 남은 과제

  1. 배율 기본값은 환경 의존 — 11 은 특정 모니터에서 맞춘 값이다. 다른 화면에서는 옵션 패널로 다시 맞춰야 한다. devicePixelRatio 로 자동 보정하는 것은 검토만 하고 넣지 않았다(캡처·원격 데스크톱 등에서 오히려 어긋난다).
  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. 참고