feat(viewer2d): render DWG lineweight in screen pixels

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>
This commit is contained in:
minsung
2026-08-04 17:25:11 +09:00
co-authored by Claude Opus 5
parent eec33820a6
commit c67a803e4d
12 changed files with 6922 additions and 34 deletions
+41 -1
View File
@@ -14,6 +14,45 @@
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink); font-family: var(--sans); }
#app { position: fixed; inset: 0; }
/* ── 상단 로딩 프로그레스 바 (src/topProgress.ts 가 구동) ───────────────
.bar : 실제 진행률. width 트랜지션 = 메인 스레드 의존.
.sweep : WASM 파싱 / Viewer2D.load() 같은 동기 블로킹 구간용. transform
키프레임은 컴포지터 스레드에서 돌기 때문에 메인 스레드가 멈춰
있어도 계속 흐른다 — 그 구간에 줄 수 있는 유일한 피드백. */
#progress {
position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 100;
pointer-events: none; overflow: hidden;
opacity: 0; transition: opacity .3s ease .15s;
}
#progress.on { opacity: 1; transition-delay: 0s; }
#progress .bar {
position: absolute; top: 0; bottom: 0; left: 0; width: 0;
background: linear-gradient(90deg, var(--accent), #58a6ff 55%, var(--wasm));
box-shadow: 0 0 10px rgba(63,185,80,.65), 0 0 4px rgba(88,166,255,.5);
transition: width .28s cubic-bezier(.25,.8,.25,1);
}
#progress.err .bar {
background: var(--danger);
box-shadow: 0 0 10px rgba(248,81,73,.7);
}
#progress .sweep {
position: absolute; top: 0; bottom: 0; left: 0; width: 34%;
background: linear-gradient(90deg, transparent, rgba(230,237,243,.9), transparent);
transform: translateX(-105%); will-change: transform; opacity: 0;
}
#progress.busy .sweep {
opacity: 1;
animation: pr-sweep 1.15s cubic-bezier(.4,0,.2,1) infinite;
}
@keyframes pr-sweep {
from { transform: translateX(-105%); }
to { transform: translateX(400%); }
}
@media (prefers-reduced-motion: reduce) {
#progress .sweep { animation: none; }
#progress .bar { transition: none; }
}
#toolbar {
position: fixed; top: 12px; left: 12px; right: 12px; z-index: 10;
display: flex; flex-wrap: wrap; gap: 8px; align-items: center; pointer-events: none;
@@ -159,7 +198,7 @@
}
.layer-sub-btn:hover { color: var(--ink); border-color: var(--accent); background: rgba(63,185,80,.1); }
</style>
<script type="module" crossorigin src="/assets/index-KG0HO38y.js"></script>
<script type="module" crossorigin src="/assets/index-D3FqDGwP.js"></script>
</head>
<body>
<div id="app"></div>
@@ -175,6 +214,7 @@
<div id="spaces" title="Model Space / Paper Space (Layout)"></div>
<button id="fit" type="button">Fit (F)</button>
<button id="theme" type="button">Theme</button>
<button id="lwt" type="button" title="선가중치 표시 (AutoCAD LWDISPLAY)">선가중치</button>
<button id="layersBtn" type="button">Layers</button>
<button id="propsBtn" type="button">Props (속성)</button>
<div id="status">ready</div>