fix(viewer2d): show lineweight at AutoCAD's display scale (20 px/mm)

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>
This commit is contained in:
minsung
2026-08-04 20:34:27 +09:00
co-authored by Claude Opus 5
parent ce3859c487
commit cf1298749b
5 changed files with 6051 additions and 11 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -198,7 +198,7 @@
} }
.layer-sub-btn:hover { color: var(--ink); border-color: var(--accent); background: rgba(63,185,80,.1); } .layer-sub-btn:hover { color: var(--ink); border-color: var(--accent); background: rgba(63,185,80,.1); }
</style> </style>
<script type="module" crossorigin src="/assets/index-D3FqDGwP.js"></script> <script type="module" crossorigin src="/assets/index-BsOJ4upQ.js"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+48 -5
View File
@@ -96,10 +96,18 @@ lineweight 는 **화면 픽셀 폭**이다. 줌해도 굵기가 변하지 않는
따라서 월드 단위가 아니라 스크린 스페이스로 그려야 한다. 따라서 월드 단위가 아니라 스크린 스페이스로 그려야 한다.
``` ```
px = mm × 8 (LW_PX_PER_MM, 최대 24px) px = mm × 20 (LW_PX_PER_MM, 최대 48px)
0.25mm ≈ 2px · 0.50mm ≈ 4px · 2.11mm ≈ 17px ← AutoCAD 기본 표시 배율과 동일 눈금 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-2. 0.25mm 이하는 헤어라인 유지 — **회귀 방지 장치**
`-3`(Default)·`-1`(ByLayer → 레이어도 Default)이 도면 대부분을 차지한다. `-3`(Default)·`-1`(ByLayer → 레이어도 Default)이 도면 대부분을 차지한다.
@@ -231,7 +239,40 @@ LWPOLYLINE · #151257
cyan 픽셀 수가 감소(11502→11486) → fat 배치의 정점이 강조색으로 재도색됨을 확인. cyan 픽셀 수가 감소(11502→11486) → fat 배치의 정점이 강조색으로 재도색됨을 확인.
### 5-4. 회귀 없음 ### 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` 통과 - `npx tsc --noEmit` 통과
- `npm run build` 통과 - `npm run build` 통과
@@ -241,8 +282,10 @@ cyan 픽셀 수가 감소(11502→11486) → fat 배치의 정점이 강조색
## 6. 남은 과제 ## 6. 남은 과제
1. **표시 배율 고정**`LW_PX_PER_MM = 8` 이 상수다. AutoCAD 는 "선가중치 설정 → 표시 배율" 1. **표시 배율 UI 부재**값은 `setLineweightScale(pxPerMm)` 로 조절되지만 툴바에는
슬라이더로 조절 가능하므로, 필요하면 `setLineweightScale()` 로 노출할 것. on/off 버튼만 있다. 슬라이더가 필요하면 이 API 에 붙이면 된다.
0.35mm 가 7px 라 `비탈면방향`(34,758개)이 있는 구간은 화면이 상당히 무거워 보인다 —
도면이 지정한 수치대로이긴 하나, 배율을 낮추고 싶으면 이 API 를 쓴다.
2. **ByBlock 근사** — 블록 자식의 `-2` 는 INSERT 굵기를 상속하지만, 중첩 블록에서 2. **ByBlock 근사** — 블록 자식의 `-2` 는 INSERT 굵기를 상속하지만, 중첩 블록에서
중간 INSERT 가 `-2` 인 경우까지는 추적하지 않는다(점선 처리와 동일한 기존 정책). 중간 INSERT 가 `-2` 인 경우까지는 추적하지 않는다(점선 처리와 동일한 기존 정책).
3. **DXF 레이어 굵기 없음**`dxf-parser` 의 LAYER 핸들러가 코드 370 을 무시한다. 3. **DXF 레이어 굵기 없음**`dxf-parser` 의 LAYER 핸들러가 코드 370 을 무시한다.
+3
View File
@@ -19,6 +19,9 @@ export class Viewer2D {
/** Lineweight display (AutoCAD LWDISPLAY); null follows the drawing's own flag. */ /** Lineweight display (AutoCAD LWDISPLAY); null follows the drawing's own flag. */
setLineweightEnabled(on: boolean | null): void; setLineweightEnabled(on: boolean | null): void;
getLineweightEnabled(): boolean; getLineweightEnabled(): boolean;
/** Lineweight display scale in px per mm (AutoCAD's display-scale slider). Default 20. */
setLineweightScale(pxPerMm: number | null): void;
getLineweightScale(): number;
getLayerInfo(): { name: string; colorHex: string; count: number; visible: boolean }[]; getLayerInfo(): { name: string; colorHex: string; count: number; visible: boolean }[];
setHiddenLayers(nameSet: Set<string>): void; setHiddenLayers(nameSet: Set<string>): void;
/** Model / Paper (Layout) spaces for the loaded drawing. */ /** Model / Paper (Layout) spaces for the loaded drawing. */
+24 -5
View File
@@ -25,15 +25,17 @@ const CLICK_THRESHOLD_PX = 14;
// ── Lineweight (AutoCAD LWDISPLAY) ───────────────────────────────────────── // ── Lineweight (AutoCAD LWDISPLAY) ─────────────────────────────────────────
// DWG stores lineweight as an i16 in 1/100 mm (-1 ByLayer, -2 ByBlock, // DWG stores lineweight as an i16 in 1/100 mm (-1 ByLayer, -2 ByBlock,
// -3 Default). AutoCAD shows it in *screen* pixels — the width does not grow // -3 Default). AutoCAD shows it in *screen* pixels — the width does not grow
// when you zoom in — at roughly 8 px per mm on the default display-scale // when you zoom in — at one pixel per 0.05 mm on the default display-scale
// slider (0.25 mm ≈ 2 px, 2.11 mm ≈ 17 px), so that is the mapping used here. // slider (0.50 mm = 10 px, 2.11 mm = 42 px). Measured against an AutoCAD
const LW_PX_PER_MM = 8; // capture of the 노면 drawing: its 0.50 mm road edge is ~10 px, so 20 px/mm.
// Host apps can retune this with setLineweightScale().
const LW_PX_PER_MM = 20;
// LWDEFAULT: what -3 (and a layer that never set one) resolves to. // LWDEFAULT: what -3 (and a layer that never set one) resolves to.
const LW_DEFAULT_MM = 0.25; const LW_DEFAULT_MM = 0.25;
// At or below the CAD default the line stays in the 1-px hairline batch: a // At or below the CAD default the line stays in the 1-px hairline batch: a
// drawing that never assigned weights must look exactly as it did before. // drawing that never assigned weights must look exactly as it did before.
const LW_HAIRLINE_MM = 0.25; const LW_HAIRLINE_MM = 0.25;
const LW_MAX_PX = 24; const LW_MAX_PX = 48;
// Fat lines cost 12 floats + 8 verts per segment (instanced quads). Past this // Fat lines cost 12 floats + 8 verts per segment (instanced quads). Past this
// many segments in one weight bucket, fall back to hairline instead of risking // many segments in one weight bucket, fall back to hairline instead of risking
// a GPU/heap stall on huge survey drawings. // a GPU/heap stall on huge survey drawings.
@@ -105,6 +107,7 @@ export class Viewer2D {
this._lwForced = null; this._lwForced = null;
this._lwDisplay = false; this._lwDisplay = false;
this._curLwPx = 0; this._curLwPx = 0;
this._lwScale = null; // null = LW_PX_PER_MM (setLineweightScale)
SlugTextEngine.shared().catch(() => {}); // warm the font load; sprite fallback covers failure SlugTextEngine.shared().catch(() => {}); // warm the font load; sprite fallback covers failure
this._scene = new THREE.Scene(); this._scene = new THREE.Scene();
@@ -1377,6 +1380,21 @@ export class Viewer2D {
/** Whether lineweights are currently being drawn. */ /** Whether lineweights are currently being drawn. */
getLineweightEnabled() { return this._lwDisplay; } getLineweightEnabled() { return this._lwDisplay; }
/**
* Display scale in screen pixels per mm of lineweight — AutoCAD's
* "Adjust Display Scale" slider. Default 20 (1 px per 0.05 mm).
* @param {number|null} pxPerMm null restores the default.
*/
setLineweightScale(pxPerMm) {
this._lwScale = (pxPerMm > 0) ? pxPerMm : null;
if (this._lastResult && !this._isLoading) {
this.load(this._lastResult, { keepView: true, keepTheme: true, spaceHandle: this._activeSpaceHex });
}
}
/** Current lineweight display scale (px per mm). */
getLineweightScale() { return this._lwScale || LW_PX_PER_MM; }
/** Track an entity-owned object so layer toggles can flip its visibility. */ /** Track an entity-owned object so layer toggles can flip its visibility. */
_addObj(obj, layer) { _addObj(obj, layer) {
const key = layer ?? this._curLayer; const key = layer ?? this._curLayer;
@@ -2334,7 +2352,8 @@ export class Viewer2D {
} }
const mm = raw === -3 ? LW_DEFAULT_MM : raw / 100; const mm = raw === -3 ? LW_DEFAULT_MM : raw / 100;
if (!(mm > LW_HAIRLINE_MM)) return 0; if (!(mm > LW_HAIRLINE_MM)) return 0;
return Math.min(LW_MAX_PX, Math.round(mm * LW_PX_PER_MM * 10) / 10); const scale = this._lwScale || LW_PX_PER_MM;
return Math.min(LW_MAX_PX, Math.round(mm * scale * 10) / 10);
} }
// Active-viewport VIEWTWIST (radians). The sheet is un-twisted by rotating the // Active-viewport VIEWTWIST (radians). The sheet is un-twisted by rotating the