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>
6.8 KiB
SubDMesh(MESH) 렌더링
| 항목 | 내용 |
|---|---|
| 증상 | 대산당진 2공구_노면_*.dwg 의 노면/부체도로면이 뷰어에 전혀 나오지 않음 |
| 원인 | ① dwg-wasm 이 MESH 엔티티를 parseResult 에 아예 내보내지 않음 ② acadrust 의 배열 개수 상한(100,000)이 큰 메쉬를 잘라내 뒤따르는 face/edge 목록까지 깨뜨림 |
| 대상 클래스 | AcDbSubDMesh (DXF 엔티티명 MESH, AutoCAD 2010+) |
| 수정일 | 2026-08-04 |
| 반영 상태 | 파서(wasm)·속성 패널·문서는 이 커밋에 포함. Viewer2D.js 렌더러 hunk 는 아직 미커밋 — 같은 파일에서 진행 중인 lineweight/fat-line 리팩터와 hunk 가 겹쳐 분리가 안 된다. 렌더러 원본은 본가 hmwebviewer 35f5b1c 에 들어가 있고, 이 저장소에는 작업 트리에만 있다. 그 리팩터가 커밋될 때 같이 들어간다. |
1. 무엇이 안 나왔나
문제 도면(AC1032 / AutoCAD 2018)에는 MESH 엔티티가 9개 있다.
| handle | layer | 정점 | 면 | 모서리 |
|---|---|---|---|---|
| 4188 | 도로면 | 105,497 | 185,444 | 290,931 |
| 4189 | 부체도로면 | 11,712 | 12,568 | 24,226 |
| 296564 … 297665 | 연결부 (7개) | 28 ~ 53 | 25 ~ 47 | 51 ~ 96 |
파싱 결과 엔티티 히스토그램에는 MESH 가 0개 였다. 즉 뷰어의 렌더 문제가 아니라
파서 출력에 데이터 자체가 없었다.
2. 원인 두 겹
2-1. dwg-wasm 이 MESH 를 버리고 있었다
rust/dwg-wasm/src/lib.rs 의 entity_to_json() 마지막 arm:
// Not rendered by Viewer2D (leaders, rays, meshes, …) — skip.
_ => {}
acadrust 자체는 MESH 를 완전히 읽고 있었다 (OBJ_MESH → entities::read_mesh() →
EntityType::Mesh). JSON 직렬화 단계에서만 빠져 있었다.
2-2. acadrust 의 배열 상한이 큰 메쉬를 깨뜨렸다
object_reader/mod.rs 에는 손상 데이터 방어용 상한이 있다.
const MAX_ARRAY_COUNT: i32 = 100_000;
fn safe_count(raw: i32) -> i32 { raw.max(0).min(MAX_ARRAY_COUNT) }
read_mesh() 가 이 상한을 정점 개수에 그대로 쓰고 있었다. 도로면 메쉬의 실제 정점은
105,497개 → 100,000 으로 잘림 → 5,497개 분량의 비트를 안 읽고 다음 필드로 넘어감 →
그 뒤의 face/edge/crease 목록이 통째로 어긋난다. 진단 출력이 이렇게 나왔다.
MESH #1 verts=100000 faces=14 edges=3
first face sizes: [64, 0, 64, 23, 0, 0, 0, 15]
정점 수가 그럴듯해 보여서 에러로 드러나지 않는 종류의 고장이다. 정상 TIN 이면 face 는 전부 3정점이어야 한다.
수정: read_mesh() 전용으로 “남은 스트림 비트 수” 기준 상한을 쓴다
(stream_bounded_count). 항목 1개의 최소 인코딩 길이(3BD = 6비트, BitLong = 2비트)로
남은 비트를 나눈 값이 상한이므로, 무제한 할당 방어는 그대로 유지되면서 정상 도면은
잘리지 않는다.
MESH #1 verts=105497 faces=185444 edges=290931
first face sizes: [3, 3, 3, 3, 3, 3, 3, 3]
acadrust 는 MPL-2.0. 이 저장소 기준으로 처음 생긴 로컬 패치다. MPL 은 파일 단위 copyleft 이므로 해당 파일이 MPL 로 남고 소스가 저장소에 포함되면 요건을 만족한다.
rust/dwg-wasm/Cargo.toml·src/lib.rs의 "consumed unmodified" 문구를 갱신했다.
3. parseResult 스키마
MESH 는 평면 숫자 배열로 나간다. {x,y,z} 객체와 중첩 face 배열을 쓰면 같은 숫자에
대해 JSON 이 약 3배로 부푼다 (이 도면 기준 전체 JSON 116MB → 130MB 로 끝난 이유).
{
"type": "MESH",
"handle": { "value": 4188 }, "layer": "도로면", "entityHeader": { … },
"vertices": [x, y, z, x, y, z, …], // 평면
"vertexCount": 105497,
"faceList": [n, i0, …, i(n-1), n, …],// DXF group 93 배열과 같은 형태 (n각형 허용)
"faceCount": 185444,
"edges": [a, b, a, b, …], // acadrust 가 중복 제거해 준 모서리 목록
"subdivisionLevel": 0,
"blendCrease": true,
"meshVersion": 2
}
범위를 벗어난 인덱스는 u32::MAX 로 나가므로, 소비 측 경계 검사에서 그 모서리 하나만
버려진다 (엉뚱한 정점으로 감기지 않는다).
4. 렌더링
Viewer2D._meshSegs() 가 면 wireframe 을 선분으로 뱉는다. AutoCAD 의 2D 와이어프레임
표시와 같은 그림이다.
edges우선. 이미 중복이 제거돼 있어 삼각형이 공유하는 모서리를 한 번만 그린다. 이 도면에서 315,633 선분 (faceList 로 그리면 741,072 → 약 1.8배).edges가 비어 있으면faceList로 폴백. 두 경로의 그림 범위(extent)는 동일함을 확인.- z 는 선분 양 끝 정점 표고의 중간값. 이 도면은 LWPOLYLINE·TEXT 도 이미 z 0~68 대역에 있으므로 메쉬만 위로 뜨지 않는다.
- 세 군데 디스패치 모두에 연결: 모델 공간 switch, 페이퍼 뷰포트 통과 switch,
_insertEntities(블록 안의 MESH,xf로 좌표 변환).
한계
subdivisionLevel > 0 인 메쉬는 기본(control) 메쉬만 그린다. AutoCAD 는 그 정점들을
Catmull-Clark 로 세분한 결과를 보여주므로, 그런 메쉬는 AutoCAD 보다 각져 보인다.
문제 도면은 9개 전부 subdivisionLevel = 0 이라 차이가 없다.
POLYFACE MESH / POLYGON MESH(구형 POLYLINE flag 16/64)는 별개 엔티티이며 이 작업
범위 밖이다.
5. 검증 도구
# 네이티브에서 메쉬가 제대로 읽히는지 (hmwebviewer 쪽)
cd D:\MYCLAUDE_PROJECT\hmwebviewer\rust\dwg-wasm
cargo run --release --bin meshprobe -- <file.dwg>
face 크기가 전부 3(또는 4)이 아니라 [64, 0, 64, 23, …] 처럼 들쭉날쭉하면 위 2-2 의
스트림 어긋남을 다시 의심한다.
6. wasm 재빌드
MESH 지원은 wasm 안에 들어 있으므로 재빌드가 필요하다. 빌드 스크립트는 hmwebviewer 에만 있고, 산출물을 이 저장소로 복사한다.
cd D:\MYCLAUDE_PROJECT\hmwebviewer
npm run build:dwg-wasm # → hmwebviewer/src/viewer2d/acadrust-dwg/
cp src/viewer2d/acadrust-dwg/acadrust_dwg* \
../dwg-dxf-viewer-sample/src/viewer2d/acadrust-dwg/
이후 정적 배포는 deploy-dist2-static-build.md 절차를 따른다.
7. 참고
- 렌더러:
../src/viewer2d/Viewer2D.js—_meshSegs,case 'MESH' - 속성 패널:
../src/propertyInspector.ts—case 'MESH' - 직렬화:
hmwebviewer/rust/dwg-wasm/src/lib.rs—EntityType::Mesh - 비트 파서:
hmwebviewer/rust/acadrust/src/io/dwg/dwg_stream_readers/object_reader/entities.rs—read_mesh,stream_bounded_count