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>
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>
The road-surface drawing (대산당진 2공구_노면) rendered nothing for its
노면/부체도로면 geometry. The cause was not the renderer: the parseResult
contained zero MESH entities.
Two faults, both in the wasm parser, fixed upstream in hmwebviewer 35f5b1c:
1. dwg-wasm never serialized MESH. acadrust reads the entity in full; only the
JSON arm was missing.
2. acadrust's read_mesh clamped its array counts with the blanket 100_000-item
corrupt-data guard. This drawing's road mesh has 105_497 vertices, so the
count was truncated, the remaining vertex bits were never consumed, and the
face/edge/crease lists after them decoded from the wrong bit offset —
producing a plausible-looking vertex count next to a nonsense 14-face list.
Counts are now bounded by the bits actually left in the object stream.
before verts=100000 faces=14 face sizes [64, 0, 64, 23, 0, 0, 0, 15]
after verts=105497 faces=185444 face sizes [3, 3, 3, ...]
This commit carries the vendored side of that work:
- the rebuilt wasm, which now emits MESH as flat arrays (vertices [x,y,z,...],
faceList in DXF group-93 layout, deduplicated edges). The sample drawing goes
from 116 MB to 130 MB of parseResult with parse time unchanged at ~3.2 s.
- a MESH case in the property inspector (vertex/face/edge counts, subdivision
level, bbox and elevation range)
- docs/subdmesh-rendering.md, plus README pointers
- the acadrust licence notice, which can no longer say "consumed unmodified" —
read_mesh now carries a local patch. MPL-2.0 is file-level copyleft, so the
patched file stays under MPL and its source ships in hmwebviewer.
Verified: 9 MESH entities parsed from the drawing; entity histograms over six
other sample drawings are byte-identical to the previous wasm, so nothing else
moved.
Two deliberate omissions, both because a lineweight/fat-line refactor is in
flight in this working tree:
- Viewer2D.js is not included. Its _meshSegs helper and three MESH dispatch
sites share hunks with that refactor and cannot be separated; the same
renderer code is committed upstream in hmwebviewer 35f5b1c and will land here
with the refactor. docs/subdmesh-rendering.md records this.
- dist2/ is not rebuilt here. The current build embeds the half-wired
lineweight button and progress-bar markup; deploy per
docs/deploy-dist2-static-build.md once that work lands.
The wasm binary also carries the new lineweight parser fields (lwdisplay,
celweight, layer and entity lineWeight), since it was built from a tree that
already had them. They are additive JSON keys with no consumer in this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
setHiddenLayers() called load(result, {keepView:true}), so every layer
on/off re-parsed and re-tessellated the whole drawing — arc sampling,
hatch triangulation, complex linetypes and the Slug text batch all rebuilt
from scratch. On an 18 MB DWG a single eye-icon click took 5 s+.
Build every layer once, then switch geometry on and off:
- Merged LineSegments buffers get an index buffer plus a per-segment layer
id (_registerIndexed). A toggle rewrites the index and setDrawRange;
positions, colors and lineDistances are untouched, so draw order, dash
phase and the selection-highlight offsets in meta.colStart stay valid.
- Per-entity meshes (hatch fills, SOLID quads, arrowheads, OLE images,
sprites) are tagged by layer via _addObj and toggled with .visible.
- Slug text builds one merged mesh per CAD layer instead of one per
draw order, so text hides with a visible flag. The shader source is
identical across batches, so three still shares one WebGLProgram.
- Hidden geometry stays in the pick buffers, so _onClick now skips it via
the _metaHidden mask.
- Fit keeps framing only what is drawn: per-layer Box3 + fit samples are
recorded at build and unioned over the visible layers.
- getLayerInfo() caches the O(entities) name/color/count pass per load;
only the visible flag is recomputed.
Layer panel: one delegated click listener instead of re-binding a handler
per row, and a toggle patches the affected row's opacity/icon in place
rather than regenerating the whole list's innerHTML.
Trade-off: hidden layers are now built and kept in memory, so loading a
file with layers already off costs what a full load costs.
Rebuilds dist2.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SlugTextBatch.add() scaled em-unit glyph outlines by the raw DWG
`height` value, but glyph ink only reaches capHeightEm (~0.74 em) of
the em box. Rendered cap height ended up ~26% short of spec (e.g. a
3.6-height TABLE cell text rendered at ~2.68), while the canvas-sprite
fallback already corrected for this via measured ascent. Divide by
capHeightEm so both paths agree.
Rebuilds dist2 (was stale since before the Entity selection/Property
Inspector/Layer panel work landed).
Mirrors hmwebviewer's rust/dwg-wasm change into the sample: the wasm
artifact plus the parser wrapper, which now goes through parse_dwg_json
and JSON.parse instead of the JsValue-returning parse_dwg.
Building the whole result as a serde_json::Value tree pushed the live
wasm heap near 2.9GB for a 19MB drawing, and the allocator's cost grows
with that heap, so parsing went quadratic — 354s, with the tab frozen
throughout. Streaming one entity at a time keeps it linear: 2.5s.
The same build also fixes three bugs that drew stray 4km grey shapes
where two CF-PATT SOLID hatches should be. Max hatch span 4655 -> 110.
Adds two docs recording both investigations, in the format of the
existing ones: measurements, the hypotheses that were ruled out, the
fixes, regression checks, and the MPL-2.0 position (acadrust is
unmodified, so no new source-disclosure obligation arises).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>