Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f80442f6c | ||
|
|
a924b1ec34 | ||
|
|
cf1298749b | ||
|
|
ce3859c487 |
@@ -112,6 +112,7 @@ Vite `?url` import 로 로드됩니다.
|
||||
| `dist2` 정적 빌드 배포 절차 (공식 배포 스크립트 없음) | [`docs/deploy-dist2-static-build.md`](./docs/deploy-dist2-static-build.md) |
|
||||
| SubDMesh(MESH) 미표시 — parseResult 누락 + 큰 메쉬 잘림 | [`docs/subdmesh-rendering.md`](./docs/subdmesh-rendering.md) |
|
||||
| 선가중치(Lineweight) 미적용 — parseResult 누락 + WebGL 1px 한계 | [`docs/lineweight-rendering.md`](./docs/lineweight-rendering.md) |
|
||||
| acadrust MPL-2.0 고지 누락 (수정본 wasm 공개 배포) | [`docs/license-mpl2-acadrust.md`](./docs/license-mpl2-acadrust.md) |
|
||||
| ↳ 위 둘의 측정 결과 + 상호작용(메쉬 선분이 굵기 버킷 상한을 넘길 위험) | [`docs/improvement-results-2026-08-04.md`](./docs/improvement-results-2026-08-04.md) |
|
||||
|
||||
샘플 패치: `fixDwgKoreanText`, `cadSpaces` + Viewer2D 공간 필터, Model/Layout 탭.
|
||||
@@ -124,6 +125,11 @@ Vite `?url` import 로 로드됩니다.
|
||||
| 조각 | 라이선스 |
|
||||
|------|----------|
|
||||
| Viewer2D 포트 · 샘플 글루 | MIT (hmwebviewer 계열) |
|
||||
| acadrust (WASM 내부, `read_mesh` 로컬 패치 포함) | MPL-2.0 |
|
||||
| dxf-parser · three | MIT |
|
||||
| acadrust (WASM 내부, `read_mesh` 로컬 패치 포함) | **MPL-2.0 — 수정본** |
|
||||
| dxf-parser · three · opentype.js | MIT |
|
||||
| NanumGothic | OFL |
|
||||
|
||||
acadrust 는 파일 단위 카피레프트라 **패치한 파일의 소스를 배포 대상자에게 제공할 의무**가
|
||||
있다. 고지·MPL 원문·패치는 [`THIRD-PARTY-NOTICES.md`](./THIRD-PARTY-NOTICES.md) 와
|
||||
`public/licenses/`(배포 시 `/licenses/`)에 동봉한다.
|
||||
의무 범위와 조치 근거: [`docs/license-mpl2-acadrust.md`](./docs/license-mpl2-acadrust.md)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Third-party notices
|
||||
|
||||
This viewer bundles third-party code. The notices below apply to the source
|
||||
tree **and** to the built site (`dist2/`, deployed at
|
||||
`https://dwg-dxf-viewer-sample.pages.dev/`), because the build embeds the
|
||||
components listed here.
|
||||
|
||||
Served copies of the license texts live under
|
||||
[`public/licenses/`](./public/licenses/) → `/licenses/…` on the deployed site.
|
||||
|
||||
---
|
||||
|
||||
## acadrust 0.4.1 — Mozilla Public License 2.0 — **MODIFIED**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Component | `acadrust` (DWG/DXF parser, Rust) |
|
||||
| Version | 0.4.1 |
|
||||
| License | MPL-2.0 — full text: [`public/licenses/acadrust-MPL-2.0.txt`](./public/licenses/acadrust-MPL-2.0.txt) (`/licenses/acadrust-MPL-2.0.txt`) |
|
||||
| Upstream | https://github.com/hakanaktt/acadrust · https://crates.io/crates/acadrust/0.4.1 |
|
||||
| Upstream commit | `f249c2f816acf36ee51cd5533716bdd443c2517e` (from the crate's `.cargo_vcs_info.json`) |
|
||||
| Author | Hakan AK |
|
||||
| Shipped as | compiled into `assets/acadrust_dwg_bg-*.wasm` (Executable Form) |
|
||||
|
||||
**This copy is modified.** One file differs from the published crate:
|
||||
|
||||
```
|
||||
src/io/dwg/dwg_stream_readers/object_reader/entities.rs (read_mesh array-count bounds)
|
||||
```
|
||||
|
||||
The modification bounds `read_mesh`'s array counts by the bits remaining in the
|
||||
object stream instead of the blanket 100 000-item guard, which truncated and
|
||||
thereby corrupted large SubD meshes.
|
||||
|
||||
### How to obtain the Source Code Form (MPL-2.0 §3.2)
|
||||
|
||||
1. Get the unmodified crate source — `cargo vendor`, or
|
||||
`https://crates.io/api/v1/crates/acadrust/0.4.1/download`, or the upstream
|
||||
commit above.
|
||||
2. Apply [`public/licenses/acadrust-0.4.1-read_mesh.patch`](./public/licenses/acadrust-0.4.1-read_mesh.patch)
|
||||
(`/licenses/acadrust-0.4.1-read_mesh.patch` on the deployed site):
|
||||
|
||||
```bash
|
||||
tar xf acadrust-0.4.1.crate && cd acadrust-0.4.1
|
||||
patch -p1 < acadrust-0.4.1-read_mesh.patch
|
||||
```
|
||||
|
||||
The result is the exact Source Code Form of the acadrust code compiled into the
|
||||
shipped `.wasm`. Both the original and the modified file remain under MPL-2.0.
|
||||
|
||||
---
|
||||
|
||||
## dwg-wasm — MIT
|
||||
|
||||
The wasm-bindgen wrapper that turns acadrust output into this viewer's
|
||||
`parseResult` (`hmwebviewer/rust/dwg-wasm/`). Our own code, MIT. MPL-2.0 §3.3
|
||||
permits distributing this Larger Work under different terms as long as the
|
||||
MPL-covered files above keep their license — which they do.
|
||||
|
||||
---
|
||||
|
||||
## Other components
|
||||
|
||||
| Component | License |
|
||||
|---|---|
|
||||
| three.js | MIT |
|
||||
| dxf-parser | MIT |
|
||||
| opentype.js | MIT |
|
||||
| NanumGothic (`public/fonts/`) | SIL Open Font License 1.1 |
|
||||
| Viewer2D / sample glue | MIT |
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+73
-2
@@ -66,6 +66,25 @@
|
||||
button.primary { background: #238636; border-color: #2ea043; }
|
||||
button.wasm { border-color: var(--wasm); color: var(--wasm); }
|
||||
label.btn input { display: none; }
|
||||
/* 옵션 패널 — 값은 IndexedDB(hmw-viewer/options)에 저장된다 */
|
||||
#optBody { padding: 12px 14px; overflow: auto; font-size: 12px; color: var(--ink); }
|
||||
.opt-sec { margin-bottom: 16px; }
|
||||
.opt-sec > h4 {
|
||||
margin: 0 0 8px; font-size: 11px; font-weight: 700; letter-spacing: .05em;
|
||||
text-transform: uppercase; color: var(--muted);
|
||||
}
|
||||
.opt-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.opt-row > label { flex: 1; color: var(--ink); }
|
||||
.opt-row input[type=range] { flex: 1; min-width: 90px; accent-color: var(--accent); }
|
||||
.opt-row input[type=number] {
|
||||
width: 58px; background: #0d1117; color: var(--ink);
|
||||
border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px;
|
||||
font: 12px var(--mono); text-align: right;
|
||||
}
|
||||
.opt-row input[type=checkbox] { width: 15px; height: 15px; accent-color: var(--accent); }
|
||||
.opt-unit { font: 11px var(--mono); color: var(--muted); }
|
||||
.opt-note { font: 11px var(--mono); color: var(--accent); line-height: 1.6; word-break: keep-all; }
|
||||
.opt-help { font-size: 11px; color: var(--muted); line-height: 1.5; margin: 4px 0 0; }
|
||||
#status {
|
||||
margin-left: auto; font: 12px var(--mono); color: var(--muted);
|
||||
background: rgba(22,27,34,.85); border: 1px solid var(--line);
|
||||
@@ -169,6 +188,7 @@
|
||||
|
||||
#properties { right: 12px; }
|
||||
#layers { right: 344px; display: none; }
|
||||
#options { right: 676px; width: 340px; height: auto; max-height: 70vh; display: none; }
|
||||
|
||||
#propBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
#layerBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
@@ -198,7 +218,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-D3FqDGwP.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-VgpJrp_J.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
@@ -214,7 +234,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="optionsBtn" type="button" title="표시 옵션 (선가중치 · 그리드 · 줌)">옵션</button>
|
||||
<button id="layersBtn" type="button">Layers</button>
|
||||
<button id="propsBtn" type="button">Props (속성)</button>
|
||||
<div id="status">ready</div>
|
||||
@@ -250,6 +270,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 옵션 (Options) 패널 — 설정은 IndexedDB 에 저장 -->
|
||||
<div id="options" class="cad-panel">
|
||||
<div class="hdr">
|
||||
<h3><span>⚙️</span> 옵션 (Options)</h3>
|
||||
<div class="hdr-actions">
|
||||
<button id="optReset" type="button" title="기본값으로">↺ 기본값</button>
|
||||
<button id="collapseOptBtn" class="hdr-btn collapse-btn" type="button" title="접기">▾</button>
|
||||
<button id="closeOptBtn" class="hdr-btn" type="button" title="닫기">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optBody">
|
||||
<div class="opt-sec">
|
||||
<h4>선가중치 (Lineweight)</h4>
|
||||
<div class="opt-row">
|
||||
<label for="optLwOn">표시 (LWDISPLAY)</label>
|
||||
<input id="optLwOn" type="checkbox" />
|
||||
</div>
|
||||
<div class="opt-row">
|
||||
<label for="optLwScale">표시 배율</label>
|
||||
<input id="optLwScale" type="range" min="2" max="60" step="0.5" value="11" />
|
||||
<input id="optLwScaleNum" type="number" min="1" max="80" step="0.5" value="11" />
|
||||
<span class="opt-unit">px/mm</span>
|
||||
</div>
|
||||
<div id="optLwOut" class="opt-note">—</div>
|
||||
<p class="opt-help">
|
||||
AutoCAD "선가중치 설정 → 표시 배율"과 같다. 모델 공간에서 선가중치는 줌과
|
||||
무관한 화면 픽셀로 그려지므로, 화면 DPI에 맞춰 이 값을 조정한다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="opt-sec">
|
||||
<h4>화면 (Display)</h4>
|
||||
<div class="opt-row">
|
||||
<label for="optGrid">그리드 표시</label>
|
||||
<input id="optGrid" type="checkbox" />
|
||||
</div>
|
||||
<div class="opt-row">
|
||||
<label for="optZoomSpeed">줌 속도</label>
|
||||
<input id="optZoomSpeed" type="range" min="0.2" max="3" step="0.1" value="1" />
|
||||
<input id="optZoomSpeedNum" type="number" min="0.1" max="5" step="0.1" value="1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 속성 (Properties) 패널 -->
|
||||
<div id="properties" class="cad-panel">
|
||||
<div class="hdr">
|
||||
@@ -276,6 +341,12 @@
|
||||
코드: <code>src/main.ts</code> ·
|
||||
지도: <a href="/docs/modules-dwg-dxf.html" target="_blank">docs/modules-dwg-dxf.html</a>
|
||||
</p>
|
||||
<!-- MPL-2.0 §3.2(b): recipients of the wasm must be told where the
|
||||
acadrust source (with our local patch) can be obtained. -->
|
||||
<p style="margin:6px 0 0">
|
||||
오픈소스 고지: <a href="/licenses/THIRD-PARTY-NOTICES.md" target="_blank">THIRD-PARTY-NOTICES</a>
|
||||
(acadrust · MPL-2.0 · <a href="/licenses/acadrust-0.4.1-read_mesh.patch" target="_blank">local patch</a>)
|
||||
</p>
|
||||
</aside>
|
||||
␍
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Third-party notices
|
||||
|
||||
This viewer bundles third-party code. The notices below apply to the source
|
||||
tree **and** to the built site (`dist2/`, deployed at
|
||||
`https://dwg-dxf-viewer-sample.pages.dev/`), because the build embeds the
|
||||
components listed here.
|
||||
|
||||
Served copies of the license texts live under
|
||||
[`public/licenses/`](./public/licenses/) → `/licenses/…` on the deployed site.
|
||||
|
||||
---
|
||||
|
||||
## acadrust 0.4.1 — Mozilla Public License 2.0 — **MODIFIED**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Component | `acadrust` (DWG/DXF parser, Rust) |
|
||||
| Version | 0.4.1 |
|
||||
| License | MPL-2.0 — full text: [`public/licenses/acadrust-MPL-2.0.txt`](./public/licenses/acadrust-MPL-2.0.txt) (`/licenses/acadrust-MPL-2.0.txt`) |
|
||||
| Upstream | https://github.com/hakanaktt/acadrust · https://crates.io/crates/acadrust/0.4.1 |
|
||||
| Upstream commit | `f249c2f816acf36ee51cd5533716bdd443c2517e` (from the crate's `.cargo_vcs_info.json`) |
|
||||
| Author | Hakan AK |
|
||||
| Shipped as | compiled into `assets/acadrust_dwg_bg-*.wasm` (Executable Form) |
|
||||
|
||||
**This copy is modified.** One file differs from the published crate:
|
||||
|
||||
```
|
||||
src/io/dwg/dwg_stream_readers/object_reader/entities.rs (read_mesh array-count bounds)
|
||||
```
|
||||
|
||||
The modification bounds `read_mesh`'s array counts by the bits remaining in the
|
||||
object stream instead of the blanket 100 000-item guard, which truncated and
|
||||
thereby corrupted large SubD meshes.
|
||||
|
||||
### How to obtain the Source Code Form (MPL-2.0 §3.2)
|
||||
|
||||
1. Get the unmodified crate source — `cargo vendor`, or
|
||||
`https://crates.io/api/v1/crates/acadrust/0.4.1/download`, or the upstream
|
||||
commit above.
|
||||
2. Apply [`public/licenses/acadrust-0.4.1-read_mesh.patch`](./public/licenses/acadrust-0.4.1-read_mesh.patch)
|
||||
(`/licenses/acadrust-0.4.1-read_mesh.patch` on the deployed site):
|
||||
|
||||
```bash
|
||||
tar xf acadrust-0.4.1.crate && cd acadrust-0.4.1
|
||||
patch -p1 < acadrust-0.4.1-read_mesh.patch
|
||||
```
|
||||
|
||||
The result is the exact Source Code Form of the acadrust code compiled into the
|
||||
shipped `.wasm`. Both the original and the modified file remain under MPL-2.0.
|
||||
|
||||
---
|
||||
|
||||
## dwg-wasm — MIT
|
||||
|
||||
The wasm-bindgen wrapper that turns acadrust output into this viewer's
|
||||
`parseResult` (`hmwebviewer/rust/dwg-wasm/`). Our own code, MIT. MPL-2.0 §3.3
|
||||
permits distributing this Larger Work under different terms as long as the
|
||||
MPL-covered files above keep their license — which they do.
|
||||
|
||||
---
|
||||
|
||||
## Other components
|
||||
|
||||
| Component | License |
|
||||
|---|---|
|
||||
| three.js | MIT |
|
||||
| dxf-parser | MIT |
|
||||
| opentype.js | MIT |
|
||||
| NanumGothic (`public/fonts/`) | SIL Open Font License 1.1 |
|
||||
| Viewer2D / sample glue | MIT |
|
||||
@@ -0,0 +1,111 @@
|
||||
--- a/src/io/dwg/dwg_stream_readers/object_reader/entities.rs
|
||||
+++ b/src/io/dwg/dwg_stream_readers/object_reader/entities.rs
|
||||
@@ -1682,20 +1682,49 @@
|
||||
MLineData { scale_factor, justification, start_point, normal, openclosed, lines_in_style, vertex_count, vertices, style_handle }
|
||||
}
|
||||
|
||||
+/// Cap an array count against the bits actually left in the object's main
|
||||
+/// stream instead of the blanket [`MAX_ARRAY_COUNT`].
|
||||
+///
|
||||
+/// MESH is the one entity whose arrays legitimately run past 100 000 items: a
|
||||
+/// road-surface mesh in a civil drawing carries hundreds of thousands of
|
||||
+/// vertices. Clamping those to the blanket cap does not merely truncate the
|
||||
+/// mesh — the reader then continues from the wrong bit offset, so the face,
|
||||
+/// edge and crease lists that follow decode as garbage (observed: a 100 000
|
||||
+/// vertex clamp left a 14-face mesh with face sizes `[64, 0, 64, 23, 0, …]`).
|
||||
+/// Bounding by the remaining bits keeps the corrupt-data protection — no
|
||||
+/// unbounded allocation is possible — without mis-parsing a valid drawing.
|
||||
+///
|
||||
+/// `min_bits_per_item` is the shortest legal encoding of one element: a
|
||||
+/// BitDouble is 2 bits at minimum (the 0.0 / 1.0 forms), so a 3BD vertex is 6
|
||||
+/// and a BitLong is 2.
|
||||
+fn stream_bounded_count(reader: &DwgMergedReader, raw: i32, min_bits_per_item: i64) -> i32 {
|
||||
+ let raw = raw.max(0) as i64;
|
||||
+ let total_bits = (reader.main().data_len() as i64).saturating_mul(8);
|
||||
+ // The handle stream follows the main stream; anything past its start is not
|
||||
+ // array data. handle_start() is 0 when the framing did not supply it.
|
||||
+ let handle_start = reader.handle_start();
|
||||
+ let end_bits = if handle_start > 0 { handle_start.min(total_bits) } else { total_bits };
|
||||
+ let remaining = (end_bits - reader.position_in_bits()).max(0);
|
||||
+ raw.min(remaining / min_bits_per_item.max(1)).min(i32::MAX as i64) as i32
|
||||
+}
|
||||
+
|
||||
pub fn read_mesh(reader: &mut DwgMergedReader) -> MeshData {
|
||||
let version = reader.read_bit_short();
|
||||
let blend_crease = reader.read_bit();
|
||||
let subdivision_level = reader.read_bit_long();
|
||||
|
||||
- let num_verts = safe_count(reader.read_bit_long());
|
||||
+ let raw_verts = reader.read_bit_long();
|
||||
+ let num_verts = stream_bounded_count(reader, raw_verts, 6);
|
||||
let mut vertices = Vec::with_capacity(num_verts as usize);
|
||||
for _ in 0..num_verts { vertices.push(reader.read_3bit_double()); }
|
||||
|
||||
- let total_face_data = safe_count(reader.read_bit_long());
|
||||
+ let raw_face_data = reader.read_bit_long();
|
||||
+ let total_face_data = stream_bounded_count(reader, raw_face_data, 2);
|
||||
let mut faces = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < total_face_data {
|
||||
- let n = safe_count(reader.read_bit_long());
|
||||
+ let raw_n = reader.read_bit_long();
|
||||
+ let n = stream_bounded_count(reader, raw_n, 2);
|
||||
i += 1;
|
||||
let mut face = Vec::new();
|
||||
for _ in 0..n {
|
||||
@@ -1705,7 +1734,8 @@
|
||||
faces.push(face);
|
||||
}
|
||||
|
||||
- let num_edges = safe_count(reader.read_bit_long());
|
||||
+ let raw_edges = reader.read_bit_long();
|
||||
+ let num_edges = stream_bounded_count(reader, raw_edges, 4);
|
||||
let mut edges = Vec::with_capacity(num_edges as usize);
|
||||
for _ in 0..num_edges {
|
||||
let s = reader.read_bit_long();
|
||||
@@ -1713,7 +1743,8 @@
|
||||
edges.push((s, e));
|
||||
}
|
||||
|
||||
- let num_creases = safe_count(reader.read_bit_long());
|
||||
+ let raw_creases = reader.read_bit_long();
|
||||
+ let num_creases = stream_bounded_count(reader, raw_creases, 2);
|
||||
let mut crease_values = Vec::with_capacity(num_creases as usize);
|
||||
for _ in 0..num_creases { crease_values.push(reader.read_bit_double()); }
|
||||
|
||||
@@ -2350,14 +2381,31 @@
|
||||
pub fn read_ole2frame(reader: &mut DwgMergedReader, version: DwgVersion) -> Ole2FrameData {
|
||||
let ver = reader.read_bit_short();
|
||||
let mode = if version.r2000_plus() { reader.read_bit_short() } else { 0 };
|
||||
- // OLE binary data can be very large (embedded images/documents),
|
||||
- // so don't use safe_count (100 KB cap). Use a generous 10 MB cap instead.
|
||||
let data_len = (reader.read_bit_long().max(0) as usize).min(10_000_000);
|
||||
let data = reader.read_bytes(data_len);
|
||||
let trailing_byte = if version.r2000_plus() { reader.read_byte() } else { 3 };
|
||||
- let upper_left = reader.read_3bit_double();
|
||||
- let lower_right = reader.read_3bit_double();
|
||||
+
|
||||
+ let mut upper_left = Vector3::new(0.0, 0.0, 0.0);
|
||||
+ let mut lower_right = Vector3::new(0.0, 0.0, 0.0);
|
||||
+
|
||||
+ // Extract 4-corner coordinates stored in the OLE binary data header starting at offset 2
|
||||
+ if data.len() >= 66 {
|
||||
+ let ul_x = f64::from_le_bytes(data[2..10].try_into().unwrap());
|
||||
+ let ul_y = f64::from_le_bytes(data[10..18].try_into().unwrap());
|
||||
+ let ul_z = f64::from_le_bytes(data[18..26].try_into().unwrap());
|
||||
+ let lr_x = f64::from_le_bytes(data[50..58].try_into().unwrap());
|
||||
+ let lr_y = f64::from_le_bytes(data[58..66].try_into().unwrap());
|
||||
+ let lr_z = if data.len() >= 74 { f64::from_le_bytes(data[66..74].try_into().unwrap()) } else { 0.0 };
|
||||
+
|
||||
+ if ul_x.is_finite() && ul_y.is_finite() && lr_x.is_finite() && lr_y.is_finite()
|
||||
+ && (ul_x != 0.0 || ul_y != 0.0 || lr_x != 0.0 || lr_y != 0.0) {
|
||||
+ upper_left = Vector3::new(ul_x, ul_y, if ul_z.is_finite() { ul_z } else { 0.0 });
|
||||
+ lower_right = Vector3::new(lr_x, lr_y, if lr_z.is_finite() { lr_z } else { 0.0 });
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
let ole_type = if version.r2000_plus() { reader.read_byte() } else { 2 };
|
||||
+
|
||||
Ole2FrameData { version: ver, mode, data, trailing_byte, upper_left, lower_right, ole_type }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under the name "LEGAL", and included in all Covered Software
|
||||
distributed in Source Code Form. Except to the extent prohibited by
|
||||
statute or regulation, such description must be sufficiently detailed
|
||||
for a recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
# acadrust MPL-2.0 — 의무 사항과 조치 내역
|
||||
|
||||
| 항목 | 내용 |
|
||||
|------|------|
|
||||
| 작성 목적 | 파서(acadrust, MPL-2.0)를 담은 wasm 을 공개 배포하면서 지켜야 할 의무를 확정하고 조치 내역 기록 |
|
||||
| 대상 배포물 | `https://dwg-dxf-viewer-sample.pages.dev/` (Cloudflare Pages, 공개) |
|
||||
| 검토일 | 2026-08-04 |
|
||||
| 상태 | **조치 완료** — 고지 파일·MPL 원문·패치 동봉, 사이트에서 링크 |
|
||||
|
||||
---
|
||||
|
||||
## 0. 결론 (세 줄)
|
||||
|
||||
1. **이번 lineweight 작업은 MPL 파일을 건드리지 않았다.** 수정한 `dwg-wasm/src/lib.rs` 는
|
||||
우리 MIT 코드다. → MPL 의무가 *새로* 생기지 않았다.
|
||||
2. 그러나 **이전부터** acadrust 를 한 파일 패치해서 wasm 에 컴파일해 공개 배포 중이었고,
|
||||
그 상태가 **MPL-2.0 §3.2 위반**이었다. 라이선스 원문도 고지도 소스 입수 경로도 없었다.
|
||||
3. **upstream(hakanaktt/acadrust)에 PR 을 올릴 의무는 없다.** MPL 은 기여 강제가 아니다.
|
||||
**배포물에 고지 + 소스 입수 경로만 넣으면 된다.** ← 이번에 그렇게 조치했다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 무엇이 MPL 이고 무엇이 우리 것인가
|
||||
|
||||
| 코드 | 위치 | 라이선스 | 이번 세션에서 수정? |
|
||||
|------|------|----------|---------------------|
|
||||
| **acadrust 0.4.1** (DWG 파서 본체) | `hmwebviewer/rust/acadrust/` | **MPL-2.0** | **아니오** |
|
||||
| dwg-wasm (wasm-bindgen 래퍼 + JSON 직렬화) | `hmwebviewer/rust/dwg-wasm/src/lib.rs` | MIT (우리 코드) | 예 (lineWeight 필드 추가) |
|
||||
| Viewer2D 외 프론트엔드 | `src/` | MIT | 예 |
|
||||
|
||||
MPL-2.0 은 **파일 단위(file-level) 카피레프트**다. GPL 처럼 프로젝트 전체로 번지지 않는다.
|
||||
MPL 파일을 고쳤으면 **그 파일만** MPL 로 유지하고 소스를 공개하면 되고,
|
||||
MPL 파일을 호출·링크하는 우리 코드(§3.3 의 "Larger Work")는 우리가 원하는 라이선스로 배포할 수 있다.
|
||||
|
||||
→ **`lib.rs` 에 필드 세 개 추가한 것은 MPL 전염 사유가 아니다.**
|
||||
|
||||
## 2. 그럼에도 의무가 있는 이유 — 이미 한 파일을 고쳤다
|
||||
|
||||
vendored acadrust 를 crates.io 원본과 diff 하면 **정확히 한 파일**이 다르다.
|
||||
|
||||
```bash
|
||||
diff -rq ~/.cargo/registry/src/index.crates.io-*/acadrust-0.4.1/src \
|
||||
hmwebviewer/rust/acadrust/src
|
||||
# → src/io/dwg/dwg_stream_readers/object_reader/entities.rs 만 differ
|
||||
```
|
||||
|
||||
`read_mesh` 의 배열 길이를 남은 비트 수로 제한하는 패치다(대형 SubD 메쉬 손상 수정,
|
||||
[`subdmesh-rendering.md`](./subdmesh-rendering.md)). **이 파일은 Modified Covered
|
||||
Software** 이고, 그 소스는 MPL 하에 제공되어야 한다.
|
||||
|
||||
그리고 `.wasm` 은 acadrust 를 컴파일해 담은 **Executable Form** 이다. 공개 사이트에서
|
||||
브라우저로 내려보내는 순간 MPL 상 "Distribution" 에 해당한다.
|
||||
|
||||
### 적용되는 조항
|
||||
|
||||
| 조항 | 요구 |
|
||||
|------|------|
|
||||
| §3.1 | Source Code Form 을 배포하면 MPL 하에 배포하고 라이선스 사본을 동봉 |
|
||||
| **§3.2** | **Executable Form 배포 시**: (a) 해당 Covered Software 의 Source Code Form 을 MPL 하에 제공하고, (b) **수령자에게 그 입수 방법을 알릴 것** (배포 비용 이상 청구 금지) |
|
||||
| §3.3 | Larger Work(우리 앱)는 다른 라이선스로 배포 가능 — 위 (a)(b)를 지키는 한 |
|
||||
| §3.4 | 라이선스·저작권 고지를 제거하지 말 것 |
|
||||
|
||||
**MPL 에는 upstream 기여 의무 조항이 없다.** §3.2 는 "수령자에게 제공"이지
|
||||
"원저자에게 반영"이 아니다.
|
||||
|
||||
## 3. 위반 상태였던 지점 (조치 전)
|
||||
|
||||
- 배포 사이트에 MPL-2.0 **원문 없음**
|
||||
- acadrust 를 쓴다는 **고지 없음** (README 표에만 한 줄, 배포물에는 미포함)
|
||||
- 패치된 `entities.rs` 소스를 **얻을 방법 없음**
|
||||
- `hmwebviewer` GitHub 저장소는 비공개(API 404)
|
||||
- 샘플 저장소는 사내 gitea
|
||||
- 즉 공개 배포 대상자가 소스에 접근할 경로가 전무
|
||||
|
||||
## 4. 조치 사항 (완료)
|
||||
|
||||
### 4-1. 배포물에 동봉
|
||||
|
||||
| 파일 | 배포 URL | 내용 |
|
||||
|------|----------|------|
|
||||
| `public/licenses/acadrust-MPL-2.0.txt` | `/licenses/acadrust-MPL-2.0.txt` | MPL-2.0 원문 (§3.1·§3.4) |
|
||||
| `public/licenses/acadrust-0.4.1-read_mesh.patch` | `/licenses/acadrust-0.4.1-read_mesh.patch` | 우리 수정분 diff (§3.2(a)) |
|
||||
| `public/licenses/THIRD-PARTY-NOTICES.md` | `/licenses/THIRD-PARTY-NOTICES.md` | 컴포넌트·버전·업스트림·수정 여부·복원 절차 (§3.2(b)) |
|
||||
|
||||
저장소 루트에도 같은 내용의 [`../THIRD-PARTY-NOTICES.md`](../THIRD-PARTY-NOTICES.md) 를 둔다.
|
||||
|
||||
### 4-2. 사이트에서 링크
|
||||
|
||||
`index.html` 우측 "Module recipe" 패널 하단에 고지 링크 추가.
|
||||
§3.2(b) 의 "reasonable manner" 요건은 **수령자가 실제로 찾을 수 있는 위치**를 요구한다.
|
||||
|
||||
### 4-3. 소스 복원 절차 명시
|
||||
|
||||
원본 크레이트 + 패치 조합이면 컴파일된 소스를 바이트 단위로 재현할 수 있다.
|
||||
|
||||
```bash
|
||||
curl -O https://crates.io/api/v1/crates/acadrust/0.4.1/download
|
||||
tar xf download && cd acadrust-0.4.1
|
||||
patch -p1 < acadrust-0.4.1-read_mesh.patch
|
||||
```
|
||||
|
||||
업스트림 커밋 `f249c2f816acf36ee51cd5533716bdd443c2517e` 도 고지에 적어 뒀다
|
||||
(크레이트의 `.cargo_vcs_info.json` 값).
|
||||
|
||||
## 5. 질문에 대한 답
|
||||
|
||||
> **"해당 레포(upstream)에 올려야 하나, 배포시 문서만 추가하면 되나?"**
|
||||
|
||||
**문서(고지) + 소스 입수 경로만 배포물에 넣으면 된다. upstream PR 은 의무가 아니다.**
|
||||
|
||||
| 선택지 | MPL 상 필요? | 판단 |
|
||||
|--------|--------------|------|
|
||||
| 배포물에 MPL 원문 + 고지 + 패치 동봉 | **필수** | ✅ 이번에 조치 |
|
||||
| upstream `hakanaktt/acadrust` 에 PR | 아니오 | 선택 — read_mesh 수정은 일반 버그픽스라 올리면 유지보수 부담이 준다 (권장, 의무 아님) |
|
||||
| `hmwebviewer` 저장소 공개 전환 | 아니오 | 불필요 — 패치 동봉으로 §3.2 충족 |
|
||||
| 패치된 acadrust 전체 소스를 이 저장소에 vendoring | 아니오 | 더 엄격한 대안. 189 파일이 샘플 저장소에 들어와 무거워짐. 원본+패치 방식은 배포판(distro) 관행과 동일하고 §3.2 를 충족 |
|
||||
|
||||
> **"lineweight 작업으로 라이선스 의무가 생겼나?"**
|
||||
|
||||
**아니오.** 이번에 고친 `dwg-wasm/src/lib.rs` 는 MIT 인 우리 파일이고, MPL 파일은
|
||||
건드리지 않았다(위 §2 diff 로 확인). 의무는 **이전 read_mesh 패치 + wasm 공개 배포**에서
|
||||
이미 발생해 있었고, 이번에 뒤늦게 정리한 것이다.
|
||||
|
||||
## 6. 앞으로 지킬 것 (체크리스트)
|
||||
|
||||
- [ ] `rust/acadrust/` 아래 파일을 고치면 → 패치 파일을 다시 뽑아 `public/licenses/` 갱신
|
||||
- [ ] acadrust 버전을 올리면 → 고지의 버전·커밋·패치를 함께 갱신
|
||||
- [ ] 새 서드파티를 번들하면 → `THIRD-PARTY-NOTICES.md` 에 행 추가
|
||||
- [ ] MPL 파일에서 라이선스 헤더 주석을 지우지 말 것 (§3.4)
|
||||
- [ ] 배포 전 `/licenses/` 3개 파일이 실제로 서빙되는지 확인
|
||||
|
||||
## 7. 참고
|
||||
|
||||
- MPL-2.0 원문: [`../public/licenses/acadrust-MPL-2.0.txt`](../public/licenses/acadrust-MPL-2.0.txt)
|
||||
- FAQ: https://www.mozilla.org/MPL/2.0/FAQ/ (Q: file-level copyleft, Q: 배포 의무 범위)
|
||||
- 수정 경위: [`subdmesh-rendering.md`](./subdmesh-rendering.md)
|
||||
- 이번 파서 변경(MIT 영역): [`lineweight-rendering.md`](./lineweight-rendering.md) §4-1
|
||||
@@ -96,10 +96,22 @@ lineweight 는 **화면 픽셀 폭**이다. 줌해도 굵기가 변하지 않는
|
||||
따라서 월드 단위가 아니라 스크린 스페이스로 그려야 한다.
|
||||
|
||||
```
|
||||
px = mm × 8 (LW_PX_PER_MM, 최대 24px)
|
||||
0.25mm ≈ 2px · 0.50mm ≈ 4px · 2.11mm ≈ 17px ← AutoCAD 기본 표시 배율과 동일 눈금
|
||||
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)이 도면 대부분을 차지한다.
|
||||
@@ -129,6 +141,17 @@ px = mm × 8 (LW_PX_PER_MM, 최대 24px)
|
||||
- **레이어 토글** — 인스턴스 지오메트리는 인덱스 재작성이 불가
|
||||
- **선택 하이라이트** — 색상 배열이 여러 버퍼로 쪼개짐
|
||||
|
||||
### 3-6. 배율 조정은 리빌드 없이
|
||||
|
||||
헤어라인 컷오프가 **mm 값**(0.25mm)이라 배율이 바뀌어도 어떤 세그먼트가 어느 버킷에
|
||||
들어가는지는 변하지 않는다. 모든 굵기가 같은 배수로 움직일 뿐이다.
|
||||
|
||||
→ 각 fat 배치가 자기 굵기를 **mm 로 기억**(`_fatBufs[].lwMm`)하고,
|
||||
`setLineweightScale()` 은 `material.linewidth` 만 다시 쓴다. 37MB 도면에서도 슬라이더를
|
||||
끄는 동안 지연이 없다.
|
||||
|
||||
반면 **표시 on/off** 는 굵기가 0이 되어 버킷 구성이 바뀌므로 재빌드가 필요하다.
|
||||
|
||||
### 3-5. 표시 on/off — AutoCAD LWDISPLAY 와 동일하게
|
||||
|
||||
굵기 표시는 CAD 에서 원래 켜고 끌 수 있는 옵션이다. 도면의 헤더 값을 기본으로 따르되
|
||||
@@ -180,9 +203,21 @@ entity lineWeight = { -3:15176, -1:2613, 13:8794, 35:37944, 40:119, 50:849 }
|
||||
|
||||
### 4-3. UI
|
||||
|
||||
- `index.html` · `src/main.ts` — 툴바 **선가중치** 버튼 (상태에 따라 강조, 툴팁에 ON/OFF)
|
||||
- `src/propertyInspector.ts` — `선가중치 (Lineweight)` 행 추가.
|
||||
`-1`→`BYLAYER`, `-2`→`BYBLOCK`, `-3`→`DEFAULT (0.25mm)`, 그 외 `0.35mm` 형식
|
||||
툴바 **옵션** 버튼 → `옵션 (Options)` 패널 (`index.html` · `src/main.ts`).
|
||||
|
||||
| 항목 | 동작 |
|
||||
|------|------|
|
||||
| 표시 (LWDISPLAY) | on/off. 체크 해제 시 전부 헤어라인 (재빌드) |
|
||||
| 표시 배율 | 슬라이더 + 숫자 입력 (px/mm). 즉시 반영 |
|
||||
| 환산 표시 | 그 도면이 실제로 쓰는 굵기만 `0.35mm → 3.9px` 식으로 표기 |
|
||||
| ↺ 기본값 | 배율·표시·그리드·줌속도 초기화 |
|
||||
|
||||
설정은 **IndexedDB** (`hmw-viewer` DB → `options` 스토어)에 저장한다
|
||||
([`../src/optionsStore.ts`](../src/optionsStore.ts)). 키: `lineweightScale`,
|
||||
`lineweightOn`, `grid`, `zoomSpeed`. 스토리지가 막혀 있어도 조용히 기본값으로 동작한다.
|
||||
|
||||
속성 패널에는 `선가중치 (Lineweight)` 행이 있다 (`src/propertyInspector.ts`).
|
||||
`-1`→`BYLAYER`, `-2`→`BYBLOCK`, `-3`→`DEFAULT (0.25mm)`, 그 외 `0.35mm` 형식.
|
||||
|
||||
### 4-4. 배포
|
||||
|
||||
@@ -231,7 +266,42 @@ LWPOLYLINE · #151257
|
||||
|
||||
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 | `노면측구` 0.35mm (헤드리스 실측) |
|
||||
|--|--|--|
|
||||
| 8 px/mm (최초, 반려) | 4 px | 2–3 px |
|
||||
| 20 px/mm (캡처 역산) | 10 px | 7 px |
|
||||
| **11 px/mm (현재 기본값)** | **5.5 px** | **3–4 px** |
|
||||
| 25 px/mm (참고) | 12.5 px | 8–9 px |
|
||||
|
||||
캡처 이미지에서 역산한 20 은 실제 화면에서 과했다 — 캡처 DPI 와 사용자 모니터가 달라서다.
|
||||
최종값은 사용자가 뷰어와 AutoCAD 를 나란히 놓고 맞춘 **11**. 이래서 상수로 못 박지 않고
|
||||
옵션으로 뺐다.
|
||||
|
||||
### 5-6. 회귀 없음
|
||||
|
||||
- `npx tsc --noEmit` 통과
|
||||
- `npm run build` 통과
|
||||
@@ -241,8 +311,9 @@ cyan 픽셀 수가 감소(11502→11486) → fat 배치의 정점이 강조색
|
||||
|
||||
## 6. 남은 과제
|
||||
|
||||
1. **표시 배율 고정** — `LW_PX_PER_MM = 8` 이 상수다. AutoCAD 는 "선가중치 설정 → 표시 배율"
|
||||
슬라이더로 조절 가능하므로, 필요하면 `setLineweightScale()` 로 노출할 것.
|
||||
1. **배율 기본값은 환경 의존** — 11 은 특정 모니터에서 맞춘 값이다. 다른 화면에서는
|
||||
옵션 패널로 다시 맞춰야 한다. `devicePixelRatio` 로 자동 보정하는 것은 검토만 하고
|
||||
넣지 않았다(캡처·원격 데스크톱 등에서 오히려 어긋난다).
|
||||
2. **ByBlock 근사** — 블록 자식의 `-2` 는 INSERT 굵기를 상속하지만, 중첩 블록에서
|
||||
중간 INSERT 가 `-2` 인 경우까지는 추적하지 않는다(점선 처리와 동일한 기존 정책).
|
||||
3. **DXF 레이어 굵기 없음** — `dxf-parser` 의 LAYER 핸들러가 코드 370 을 무시한다.
|
||||
|
||||
+72
-1
@@ -66,6 +66,25 @@
|
||||
button.primary { background: #238636; border-color: #2ea043; }
|
||||
button.wasm { border-color: var(--wasm); color: var(--wasm); }
|
||||
label.btn input { display: none; }
|
||||
/* 옵션 패널 — 값은 IndexedDB(hmw-viewer/options)에 저장된다 */
|
||||
#optBody { padding: 12px 14px; overflow: auto; font-size: 12px; color: var(--ink); }
|
||||
.opt-sec { margin-bottom: 16px; }
|
||||
.opt-sec > h4 {
|
||||
margin: 0 0 8px; font-size: 11px; font-weight: 700; letter-spacing: .05em;
|
||||
text-transform: uppercase; color: var(--muted);
|
||||
}
|
||||
.opt-row { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.opt-row > label { flex: 1; color: var(--ink); }
|
||||
.opt-row input[type=range] { flex: 1; min-width: 90px; accent-color: var(--accent); }
|
||||
.opt-row input[type=number] {
|
||||
width: 58px; background: #0d1117; color: var(--ink);
|
||||
border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px;
|
||||
font: 12px var(--mono); text-align: right;
|
||||
}
|
||||
.opt-row input[type=checkbox] { width: 15px; height: 15px; accent-color: var(--accent); }
|
||||
.opt-unit { font: 11px var(--mono); color: var(--muted); }
|
||||
.opt-note { font: 11px var(--mono); color: var(--accent); line-height: 1.6; word-break: keep-all; }
|
||||
.opt-help { font-size: 11px; color: var(--muted); line-height: 1.5; margin: 4px 0 0; }
|
||||
#status {
|
||||
margin-left: auto; font: 12px var(--mono); color: var(--muted);
|
||||
background: rgba(22,27,34,.85); border: 1px solid var(--line);
|
||||
@@ -169,6 +188,7 @@
|
||||
|
||||
#properties { right: 12px; }
|
||||
#layers { right: 344px; display: none; }
|
||||
#options { right: 676px; width: 340px; height: auto; max-height: 70vh; display: none; }
|
||||
|
||||
#propBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
#layerBody { flex: 1; overflow-y: auto; display: flex; flex-direction: column; }
|
||||
@@ -213,7 +233,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="optionsBtn" type="button" title="표시 옵션 (선가중치 · 그리드 · 줌)">옵션</button>
|
||||
<button id="layersBtn" type="button">Layers</button>
|
||||
<button id="propsBtn" type="button">Props (속성)</button>
|
||||
<div id="status">ready</div>
|
||||
@@ -249,6 +269,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 옵션 (Options) 패널 — 설정은 IndexedDB 에 저장 -->
|
||||
<div id="options" class="cad-panel">
|
||||
<div class="hdr">
|
||||
<h3><span>⚙️</span> 옵션 (Options)</h3>
|
||||
<div class="hdr-actions">
|
||||
<button id="optReset" type="button" title="기본값으로">↺ 기본값</button>
|
||||
<button id="collapseOptBtn" class="hdr-btn collapse-btn" type="button" title="접기">▾</button>
|
||||
<button id="closeOptBtn" class="hdr-btn" type="button" title="닫기">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optBody">
|
||||
<div class="opt-sec">
|
||||
<h4>선가중치 (Lineweight)</h4>
|
||||
<div class="opt-row">
|
||||
<label for="optLwOn">표시 (LWDISPLAY)</label>
|
||||
<input id="optLwOn" type="checkbox" />
|
||||
</div>
|
||||
<div class="opt-row">
|
||||
<label for="optLwScale">표시 배율</label>
|
||||
<input id="optLwScale" type="range" min="2" max="60" step="0.5" value="11" />
|
||||
<input id="optLwScaleNum" type="number" min="1" max="80" step="0.5" value="11" />
|
||||
<span class="opt-unit">px/mm</span>
|
||||
</div>
|
||||
<div id="optLwOut" class="opt-note">—</div>
|
||||
<p class="opt-help">
|
||||
AutoCAD "선가중치 설정 → 표시 배율"과 같다. 모델 공간에서 선가중치는 줌과
|
||||
무관한 화면 픽셀로 그려지므로, 화면 DPI에 맞춰 이 값을 조정한다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="opt-sec">
|
||||
<h4>화면 (Display)</h4>
|
||||
<div class="opt-row">
|
||||
<label for="optGrid">그리드 표시</label>
|
||||
<input id="optGrid" type="checkbox" />
|
||||
</div>
|
||||
<div class="opt-row">
|
||||
<label for="optZoomSpeed">줌 속도</label>
|
||||
<input id="optZoomSpeed" type="range" min="0.2" max="3" step="0.1" value="1" />
|
||||
<input id="optZoomSpeedNum" type="number" min="0.1" max="5" step="0.1" value="1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 속성 (Properties) 패널 -->
|
||||
<div id="properties" class="cad-panel">
|
||||
<div class="hdr">
|
||||
@@ -275,6 +340,12 @@
|
||||
코드: <code>src/main.ts</code> ·
|
||||
지도: <a href="/docs/modules-dwg-dxf.html" target="_blank">docs/modules-dwg-dxf.html</a>
|
||||
</p>
|
||||
<!-- MPL-2.0 §3.2(b): recipients of the wasm must be told where the
|
||||
acadrust source (with our local patch) can be obtained. -->
|
||||
<p style="margin:6px 0 0">
|
||||
오픈소스 고지: <a href="/licenses/THIRD-PARTY-NOTICES.md" target="_blank">THIRD-PARTY-NOTICES</a>
|
||||
(acadrust · MPL-2.0 · <a href="/licenses/acadrust-0.4.1-read_mesh.patch" target="_blank">local patch</a>)
|
||||
</p>
|
||||
</aside>
|
||||
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Third-party notices
|
||||
|
||||
This viewer bundles third-party code. The notices below apply to the source
|
||||
tree **and** to the built site (`dist2/`, deployed at
|
||||
`https://dwg-dxf-viewer-sample.pages.dev/`), because the build embeds the
|
||||
components listed here.
|
||||
|
||||
Served copies of the license texts live under
|
||||
[`public/licenses/`](./public/licenses/) → `/licenses/…` on the deployed site.
|
||||
|
||||
---
|
||||
|
||||
## acadrust 0.4.1 — Mozilla Public License 2.0 — **MODIFIED**
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Component | `acadrust` (DWG/DXF parser, Rust) |
|
||||
| Version | 0.4.1 |
|
||||
| License | MPL-2.0 — full text: [`public/licenses/acadrust-MPL-2.0.txt`](./public/licenses/acadrust-MPL-2.0.txt) (`/licenses/acadrust-MPL-2.0.txt`) |
|
||||
| Upstream | https://github.com/hakanaktt/acadrust · https://crates.io/crates/acadrust/0.4.1 |
|
||||
| Upstream commit | `f249c2f816acf36ee51cd5533716bdd443c2517e` (from the crate's `.cargo_vcs_info.json`) |
|
||||
| Author | Hakan AK |
|
||||
| Shipped as | compiled into `assets/acadrust_dwg_bg-*.wasm` (Executable Form) |
|
||||
|
||||
**This copy is modified.** One file differs from the published crate:
|
||||
|
||||
```
|
||||
src/io/dwg/dwg_stream_readers/object_reader/entities.rs (read_mesh array-count bounds)
|
||||
```
|
||||
|
||||
The modification bounds `read_mesh`'s array counts by the bits remaining in the
|
||||
object stream instead of the blanket 100 000-item guard, which truncated and
|
||||
thereby corrupted large SubD meshes.
|
||||
|
||||
### How to obtain the Source Code Form (MPL-2.0 §3.2)
|
||||
|
||||
1. Get the unmodified crate source — `cargo vendor`, or
|
||||
`https://crates.io/api/v1/crates/acadrust/0.4.1/download`, or the upstream
|
||||
commit above.
|
||||
2. Apply [`public/licenses/acadrust-0.4.1-read_mesh.patch`](./public/licenses/acadrust-0.4.1-read_mesh.patch)
|
||||
(`/licenses/acadrust-0.4.1-read_mesh.patch` on the deployed site):
|
||||
|
||||
```bash
|
||||
tar xf acadrust-0.4.1.crate && cd acadrust-0.4.1
|
||||
patch -p1 < acadrust-0.4.1-read_mesh.patch
|
||||
```
|
||||
|
||||
The result is the exact Source Code Form of the acadrust code compiled into the
|
||||
shipped `.wasm`. Both the original and the modified file remain under MPL-2.0.
|
||||
|
||||
---
|
||||
|
||||
## dwg-wasm — MIT
|
||||
|
||||
The wasm-bindgen wrapper that turns acadrust output into this viewer's
|
||||
`parseResult` (`hmwebviewer/rust/dwg-wasm/`). Our own code, MIT. MPL-2.0 §3.3
|
||||
permits distributing this Larger Work under different terms as long as the
|
||||
MPL-covered files above keep their license — which they do.
|
||||
|
||||
---
|
||||
|
||||
## Other components
|
||||
|
||||
| Component | License |
|
||||
|---|---|
|
||||
| three.js | MIT |
|
||||
| dxf-parser | MIT |
|
||||
| opentype.js | MIT |
|
||||
| NanumGothic (`public/fonts/`) | SIL Open Font License 1.1 |
|
||||
| Viewer2D / sample glue | MIT |
|
||||
@@ -0,0 +1,111 @@
|
||||
--- a/src/io/dwg/dwg_stream_readers/object_reader/entities.rs
|
||||
+++ b/src/io/dwg/dwg_stream_readers/object_reader/entities.rs
|
||||
@@ -1682,20 +1682,49 @@
|
||||
MLineData { scale_factor, justification, start_point, normal, openclosed, lines_in_style, vertex_count, vertices, style_handle }
|
||||
}
|
||||
|
||||
+/// Cap an array count against the bits actually left in the object's main
|
||||
+/// stream instead of the blanket [`MAX_ARRAY_COUNT`].
|
||||
+///
|
||||
+/// MESH is the one entity whose arrays legitimately run past 100 000 items: a
|
||||
+/// road-surface mesh in a civil drawing carries hundreds of thousands of
|
||||
+/// vertices. Clamping those to the blanket cap does not merely truncate the
|
||||
+/// mesh — the reader then continues from the wrong bit offset, so the face,
|
||||
+/// edge and crease lists that follow decode as garbage (observed: a 100 000
|
||||
+/// vertex clamp left a 14-face mesh with face sizes `[64, 0, 64, 23, 0, …]`).
|
||||
+/// Bounding by the remaining bits keeps the corrupt-data protection — no
|
||||
+/// unbounded allocation is possible — without mis-parsing a valid drawing.
|
||||
+///
|
||||
+/// `min_bits_per_item` is the shortest legal encoding of one element: a
|
||||
+/// BitDouble is 2 bits at minimum (the 0.0 / 1.0 forms), so a 3BD vertex is 6
|
||||
+/// and a BitLong is 2.
|
||||
+fn stream_bounded_count(reader: &DwgMergedReader, raw: i32, min_bits_per_item: i64) -> i32 {
|
||||
+ let raw = raw.max(0) as i64;
|
||||
+ let total_bits = (reader.main().data_len() as i64).saturating_mul(8);
|
||||
+ // The handle stream follows the main stream; anything past its start is not
|
||||
+ // array data. handle_start() is 0 when the framing did not supply it.
|
||||
+ let handle_start = reader.handle_start();
|
||||
+ let end_bits = if handle_start > 0 { handle_start.min(total_bits) } else { total_bits };
|
||||
+ let remaining = (end_bits - reader.position_in_bits()).max(0);
|
||||
+ raw.min(remaining / min_bits_per_item.max(1)).min(i32::MAX as i64) as i32
|
||||
+}
|
||||
+
|
||||
pub fn read_mesh(reader: &mut DwgMergedReader) -> MeshData {
|
||||
let version = reader.read_bit_short();
|
||||
let blend_crease = reader.read_bit();
|
||||
let subdivision_level = reader.read_bit_long();
|
||||
|
||||
- let num_verts = safe_count(reader.read_bit_long());
|
||||
+ let raw_verts = reader.read_bit_long();
|
||||
+ let num_verts = stream_bounded_count(reader, raw_verts, 6);
|
||||
let mut vertices = Vec::with_capacity(num_verts as usize);
|
||||
for _ in 0..num_verts { vertices.push(reader.read_3bit_double()); }
|
||||
|
||||
- let total_face_data = safe_count(reader.read_bit_long());
|
||||
+ let raw_face_data = reader.read_bit_long();
|
||||
+ let total_face_data = stream_bounded_count(reader, raw_face_data, 2);
|
||||
let mut faces = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < total_face_data {
|
||||
- let n = safe_count(reader.read_bit_long());
|
||||
+ let raw_n = reader.read_bit_long();
|
||||
+ let n = stream_bounded_count(reader, raw_n, 2);
|
||||
i += 1;
|
||||
let mut face = Vec::new();
|
||||
for _ in 0..n {
|
||||
@@ -1705,7 +1734,8 @@
|
||||
faces.push(face);
|
||||
}
|
||||
|
||||
- let num_edges = safe_count(reader.read_bit_long());
|
||||
+ let raw_edges = reader.read_bit_long();
|
||||
+ let num_edges = stream_bounded_count(reader, raw_edges, 4);
|
||||
let mut edges = Vec::with_capacity(num_edges as usize);
|
||||
for _ in 0..num_edges {
|
||||
let s = reader.read_bit_long();
|
||||
@@ -1713,7 +1743,8 @@
|
||||
edges.push((s, e));
|
||||
}
|
||||
|
||||
- let num_creases = safe_count(reader.read_bit_long());
|
||||
+ let raw_creases = reader.read_bit_long();
|
||||
+ let num_creases = stream_bounded_count(reader, raw_creases, 2);
|
||||
let mut crease_values = Vec::with_capacity(num_creases as usize);
|
||||
for _ in 0..num_creases { crease_values.push(reader.read_bit_double()); }
|
||||
|
||||
@@ -2350,14 +2381,31 @@
|
||||
pub fn read_ole2frame(reader: &mut DwgMergedReader, version: DwgVersion) -> Ole2FrameData {
|
||||
let ver = reader.read_bit_short();
|
||||
let mode = if version.r2000_plus() { reader.read_bit_short() } else { 0 };
|
||||
- // OLE binary data can be very large (embedded images/documents),
|
||||
- // so don't use safe_count (100 KB cap). Use a generous 10 MB cap instead.
|
||||
let data_len = (reader.read_bit_long().max(0) as usize).min(10_000_000);
|
||||
let data = reader.read_bytes(data_len);
|
||||
let trailing_byte = if version.r2000_plus() { reader.read_byte() } else { 3 };
|
||||
- let upper_left = reader.read_3bit_double();
|
||||
- let lower_right = reader.read_3bit_double();
|
||||
+
|
||||
+ let mut upper_left = Vector3::new(0.0, 0.0, 0.0);
|
||||
+ let mut lower_right = Vector3::new(0.0, 0.0, 0.0);
|
||||
+
|
||||
+ // Extract 4-corner coordinates stored in the OLE binary data header starting at offset 2
|
||||
+ if data.len() >= 66 {
|
||||
+ let ul_x = f64::from_le_bytes(data[2..10].try_into().unwrap());
|
||||
+ let ul_y = f64::from_le_bytes(data[10..18].try_into().unwrap());
|
||||
+ let ul_z = f64::from_le_bytes(data[18..26].try_into().unwrap());
|
||||
+ let lr_x = f64::from_le_bytes(data[50..58].try_into().unwrap());
|
||||
+ let lr_y = f64::from_le_bytes(data[58..66].try_into().unwrap());
|
||||
+ let lr_z = if data.len() >= 74 { f64::from_le_bytes(data[66..74].try_into().unwrap()) } else { 0.0 };
|
||||
+
|
||||
+ if ul_x.is_finite() && ul_y.is_finite() && lr_x.is_finite() && lr_y.is_finite()
|
||||
+ && (ul_x != 0.0 || ul_y != 0.0 || lr_x != 0.0 || lr_y != 0.0) {
|
||||
+ upper_left = Vector3::new(ul_x, ul_y, if ul_z.is_finite() { ul_z } else { 0.0 });
|
||||
+ lower_right = Vector3::new(lr_x, lr_y, if lr_z.is_finite() { lr_z } else { 0.0 });
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
let ole_type = if version.r2000_plus() { reader.read_byte() } else { 2 };
|
||||
+
|
||||
Ole2FrameData { version: ver, mode, data, trailing_byte, upper_left, lower_right, ole_type }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under the name "LEGAL", and included in all Covered Software
|
||||
distributed in Source Code Form. Except to the extent prohibited by
|
||||
statute or regulation, such description must be sufficiently detailed
|
||||
for a recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
+86
-11
@@ -29,6 +29,7 @@ import { setDefaultFontUrl } from './viewer2d/slugText';
|
||||
import { renderPropertyInspector } from './propertyInspector';
|
||||
import { LayerPanelManager } from './layerPanel';
|
||||
import { makePanelDraggableAndCollapsible } from './panelUtils';
|
||||
import { getAllOptions, setOption } from './optionsStore';
|
||||
|
||||
// parseDxfBuffer 내부:
|
||||
// dxf-parser → HatchHandler → dxfAdapter
|
||||
@@ -162,7 +163,7 @@ async function loadAndRender(buf: ArrayBuffer, name: string): Promise<CadParseRe
|
||||
const def = pickDefaultSpace(spaces);
|
||||
viewer.load(result, { spaceHandle: def?.handleHex ?? null });
|
||||
viewer.resize();
|
||||
syncLwtBtn();
|
||||
syncOptions();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -230,17 +231,91 @@ document.getElementById('theme')!.addEventListener('click', () => {
|
||||
viewer.setTheme(dark);
|
||||
fillLayers();
|
||||
});
|
||||
// 선가중치(LWT): 처음에는 도면의 LWDISPLAY 값을 따르고, 누르면 강제 on/off.
|
||||
const lwtBtn = document.getElementById('lwt') as HTMLButtonElement;
|
||||
const syncLwtBtn = () => {
|
||||
const on = viewer.getLineweightEnabled();
|
||||
lwtBtn.classList.toggle('primary', on);
|
||||
lwtBtn.title = `선가중치 표시 ${on ? 'ON' : 'OFF'} (AutoCAD LWDISPLAY)`;
|
||||
};
|
||||
lwtBtn.addEventListener('click', () => {
|
||||
viewer.setLineweightEnabled(!viewer.getLineweightEnabled());
|
||||
syncLwtBtn();
|
||||
// ── 옵션 패널 ────────────────────────────────────────────────────
|
||||
// 값은 IndexedDB(hmw-viewer/options)에 남는다. 선가중치 배율은 재파싱 없이
|
||||
// 즉시 반영되므로 드래그하면서 화면을 보고 맞출 수 있다.
|
||||
const optionsPanel = document.getElementById('options')!;
|
||||
const optLwOn = document.getElementById('optLwOn') as HTMLInputElement;
|
||||
const optLwScale = document.getElementById('optLwScale') as HTMLInputElement;
|
||||
const optLwScaleNum = document.getElementById('optLwScaleNum') as HTMLInputElement;
|
||||
const optLwOut = document.getElementById('optLwOut')!;
|
||||
const optGrid = document.getElementById('optGrid') as HTMLInputElement;
|
||||
const optZoomSpeed = document.getElementById('optZoomSpeed') as HTMLInputElement;
|
||||
const optZoomSpeedNum = document.getElementById('optZoomSpeedNum') as HTMLInputElement;
|
||||
|
||||
makePanelDraggableAndCollapsible(optionsPanel, {
|
||||
collapseBtnEl: document.getElementById('collapseOptBtn')!,
|
||||
});
|
||||
document.getElementById('optionsBtn')!.addEventListener('click', () => {
|
||||
optionsPanel.style.display = optionsPanel.style.display === 'flex' ? 'none' : 'flex';
|
||||
});
|
||||
document.getElementById('closeOptBtn')!.addEventListener('click', () => {
|
||||
optionsPanel.style.display = 'none';
|
||||
});
|
||||
|
||||
/** 컨트롤 값을 뷰어의 현재 상태로 되돌린다(도면을 새로 열었을 때도 호출). */
|
||||
function syncOptions() {
|
||||
const s = viewer.getLineweightScale();
|
||||
optLwOn.checked = viewer.getLineweightEnabled();
|
||||
optLwScale.value = String(s);
|
||||
optLwScaleNum.value = String(s);
|
||||
const classes = viewer.getLineweightClasses();
|
||||
optLwOut.textContent = classes.length
|
||||
? classes.map((mm) => `${mm.toFixed(2)}mm → ${Math.round(mm * s * 10) / 10}px`).join(' ')
|
||||
: `이 도면에는 굵은 선이 없다 (0.50mm 였다면 ${Math.round(0.5 * s * 10) / 10}px)`;
|
||||
}
|
||||
|
||||
function applyLwScale(v: number, persist = true) {
|
||||
if (!Number.isFinite(v) || v <= 0) return;
|
||||
viewer.setLineweightScale(v);
|
||||
if (persist) void setOption('lineweightScale', v);
|
||||
syncOptions();
|
||||
}
|
||||
optLwScale.addEventListener('input', () => applyLwScale(parseFloat(optLwScale.value)));
|
||||
optLwScaleNum.addEventListener('input', () => applyLwScale(parseFloat(optLwScaleNum.value)));
|
||||
|
||||
optLwOn.addEventListener('change', () => {
|
||||
viewer.setLineweightEnabled(optLwOn.checked);
|
||||
void setOption('lineweightOn', optLwOn.checked);
|
||||
syncOptions();
|
||||
});
|
||||
|
||||
optGrid.addEventListener('change', () => {
|
||||
viewer.setGrid(optGrid.checked);
|
||||
void setOption('grid', optGrid.checked);
|
||||
});
|
||||
|
||||
function applyZoomSpeed(v: number, persist = true) {
|
||||
if (!Number.isFinite(v) || v <= 0) return;
|
||||
viewer.setZoomSpeed(v);
|
||||
optZoomSpeed.value = String(v);
|
||||
optZoomSpeedNum.value = String(v);
|
||||
if (persist) void setOption('zoomSpeed', v);
|
||||
}
|
||||
optZoomSpeed.addEventListener('input', () => applyZoomSpeed(parseFloat(optZoomSpeed.value)));
|
||||
optZoomSpeedNum.addEventListener('input', () => applyZoomSpeed(parseFloat(optZoomSpeedNum.value)));
|
||||
|
||||
document.getElementById('optReset')!.addEventListener('click', () => {
|
||||
viewer.setLineweightScale(null); // 뷰어 기본 배율
|
||||
viewer.setLineweightEnabled(null); // 도면의 LWDISPLAY 를 다시 따름
|
||||
viewer.setGrid(false);
|
||||
applyZoomSpeed(1);
|
||||
optGrid.checked = false;
|
||||
void setOption('lineweightScale', null);
|
||||
void setOption('lineweightOn', null);
|
||||
void setOption('grid', false);
|
||||
syncOptions();
|
||||
});
|
||||
|
||||
// 저장된 옵션 복원 (IndexedDB 는 비동기라 첫 도면 로드와 경쟁하지 않도록 즉시 적용)
|
||||
void getAllOptions().then((o) => {
|
||||
if (typeof o.lineweightScale === 'number') applyLwScale(o.lineweightScale, false);
|
||||
if (typeof o.lineweightOn === 'boolean') viewer.setLineweightEnabled(o.lineweightOn);
|
||||
if (o.grid === true) { viewer.setGrid(true); optGrid.checked = true; }
|
||||
if (typeof o.zoomSpeed === 'number') applyZoomSpeed(o.zoomSpeed, false);
|
||||
syncOptions();
|
||||
});
|
||||
syncOptions();
|
||||
document.getElementById('layersBtn')!.addEventListener('click', () => {
|
||||
layersPanel.style.display = layersPanel.style.display === 'flex' ? 'none' : 'flex';
|
||||
});
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 뷰어 옵션 영구 저장소 (IndexedDB).
|
||||
*
|
||||
* localStorage 대신 IndexedDB 를 쓰는 이유: 값이 문자열로 강제되지 않고(숫자/불린/
|
||||
* 객체 그대로), 용량 제한이 사실상 없으며, 나중에 도면별 설정처럼 구조가 커져도
|
||||
* 같은 저장소를 그대로 쓸 수 있다.
|
||||
*
|
||||
* DB 하나(`hmw-viewer`) 안에 key-value 스토어(`options`) 하나만 둔다.
|
||||
* 실패(사생활 보호 모드, 스토리지 차단 등)해도 앱이 죽지 않도록 모든 API 는
|
||||
* 조용히 기본값으로 떨어진다.
|
||||
*/
|
||||
|
||||
const DB_NAME = 'hmw-viewer';
|
||||
const DB_VERSION = 1;
|
||||
const STORE = 'options';
|
||||
|
||||
let dbPromise: Promise<IDBDatabase | null> | null = null;
|
||||
|
||||
function openDb(): Promise<IDBDatabase | null> {
|
||||
if (dbPromise) return dbPromise;
|
||||
dbPromise = new Promise((resolve) => {
|
||||
if (typeof indexedDB === 'undefined') return resolve(null);
|
||||
let req: IDBOpenDBRequest;
|
||||
try {
|
||||
req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
} catch {
|
||||
return resolve(null);
|
||||
}
|
||||
req.onupgradeneeded = () => {
|
||||
const db = req.result;
|
||||
if (!db.objectStoreNames.contains(STORE)) db.createObjectStore(STORE);
|
||||
};
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => resolve(null);
|
||||
req.onblocked = () => resolve(null);
|
||||
});
|
||||
return dbPromise;
|
||||
}
|
||||
|
||||
function tx<T>(mode: IDBTransactionMode, run: (store: IDBObjectStore) => IDBRequest): Promise<T | null> {
|
||||
return openDb().then(
|
||||
(db) =>
|
||||
new Promise<T | null>((resolve) => {
|
||||
if (!db) return resolve(null);
|
||||
try {
|
||||
const t = db.transaction(STORE, mode);
|
||||
const req = run(t.objectStore(STORE));
|
||||
req.onsuccess = () => resolve(req.result as T);
|
||||
req.onerror = () => resolve(null);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** 저장된 옵션 하나. 없으면 `fallback`. */
|
||||
export async function getOption<T>(key: string, fallback: T): Promise<T> {
|
||||
const v = await tx<T>('readonly', (s) => s.get(key));
|
||||
return v === null || v === undefined ? fallback : v;
|
||||
}
|
||||
|
||||
/** 옵션 하나 저장. 저장이 막혀 있어도 예외를 던지지 않는다. */
|
||||
export async function setOption(key: string, value: unknown): Promise<void> {
|
||||
await tx('readwrite', (s) => s.put(value, key));
|
||||
}
|
||||
|
||||
/** 전체 옵션을 한 번에 읽는다 (기동 시 1회 호출용). */
|
||||
export async function getAllOptions(): Promise<Record<string, unknown>> {
|
||||
const keys = (await tx<IDBValidKey[]>('readonly', (s) => s.getAllKeys())) ?? [];
|
||||
const vals = (await tx<unknown[]>('readonly', (s) => s.getAll())) ?? [];
|
||||
const out: Record<string, unknown> = {};
|
||||
keys.forEach((k, i) => { out[String(k)] = vals[i]; });
|
||||
return out;
|
||||
}
|
||||
Vendored
+7
@@ -16,9 +16,16 @@ export class Viewer2D {
|
||||
setAccent(hex: string): void;
|
||||
setTheme(dark: boolean): void;
|
||||
setGrid(visible: boolean): void;
|
||||
setZoomSpeed(speed: number): void;
|
||||
getZoomSpeed(): number;
|
||||
/** Lineweight display (AutoCAD LWDISPLAY); null follows the drawing's own flag. */
|
||||
setLineweightEnabled(on: boolean | null): void;
|
||||
getLineweightEnabled(): boolean;
|
||||
/** Lineweight display scale in px per mm (AutoCAD's display-scale slider). Default 20. */
|
||||
setLineweightScale(pxPerMm: number | null): void;
|
||||
getLineweightScale(): number;
|
||||
/** Distinct lineweights (mm) present in the loaded drawing, ascending. */
|
||||
getLineweightClasses(): number[];
|
||||
getLayerInfo(): { name: string; colorHex: string; count: number; visible: boolean }[];
|
||||
setHiddenLayers(nameSet: Set<string>): void;
|
||||
/** Model / Paper (Layout) spaces for the loaded drawing. */
|
||||
|
||||
+52
-10
@@ -25,15 +25,17 @@ const CLICK_THRESHOLD_PX = 14;
|
||||
// ── Lineweight (AutoCAD LWDISPLAY) ─────────────────────────────────────────
|
||||
// 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
|
||||
// when you zoom in — at roughly 8 px per mm on the default display-scale
|
||||
// slider (0.25 mm ≈ 2 px, 2.11 mm ≈ 17 px), so that is the mapping used here.
|
||||
const LW_PX_PER_MM = 8;
|
||||
// when you zoom in. The px-per-mm factor is AutoCAD's "Adjust Display Scale"
|
||||
// slider and depends on the monitor, so it is only a default: 11 px/mm is the
|
||||
// value that matched an AutoCAD reference side by side (0.35 mm ≈ 4 px,
|
||||
// 0.50 mm ≈ 5.5 px). Retune per host/monitor with setLineweightScale().
|
||||
const LW_PX_PER_MM = 11;
|
||||
// LWDEFAULT: what -3 (and a layer that never set one) resolves to.
|
||||
const LW_DEFAULT_MM = 0.25;
|
||||
// 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.
|
||||
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
|
||||
// many segments in one weight bucket, fall back to hairline instead of risking
|
||||
// a GPU/heap stall on huge survey drawings.
|
||||
@@ -105,6 +107,7 @@ export class Viewer2D {
|
||||
this._lwForced = null;
|
||||
this._lwDisplay = false;
|
||||
this._curLwPx = 0;
|
||||
this._lwScale = null; // null = LW_PX_PER_MM (setLineweightScale)
|
||||
SlugTextEngine.shared().catch(() => {}); // warm the font load; sprite fallback covers failure
|
||||
|
||||
this._scene = new THREE.Scene();
|
||||
@@ -466,7 +469,11 @@ export class Viewer2D {
|
||||
const key = `${curDash ? curDash.key : ''}#${lwPx}`;
|
||||
let bk = segBuckets.get(key);
|
||||
if (!bk) {
|
||||
bk = { dash: curDash?.dash ?? 0, gap: curDash?.gap ?? 0, lwPx, verts: [], colors: [], segLayer: [], entIdx: -2 };
|
||||
// lwMm is kept so setLineweightScale() can retune the material width
|
||||
// without rebuilding the scene (the hairline cut-off is in mm, so
|
||||
// bucket membership does not depend on the scale).
|
||||
bk = { dash: curDash?.dash ?? 0, gap: curDash?.gap ?? 0, lwPx, lwMm: this._curLwMm || 0,
|
||||
verts: [], colors: [], segLayer: [], entIdx: -2 };
|
||||
segBuckets.set(key, bk);
|
||||
}
|
||||
// Selection highlight recolors an entity's own vertices. Its colors no
|
||||
@@ -1205,7 +1212,7 @@ export class Viewer2D {
|
||||
this._sizeLineMaterial(fmat);
|
||||
const fline = new LineSegments2(fgeom, fmat);
|
||||
if (bk.dash > 0) fline.computeLineDistances();
|
||||
this._registerFat(fline, bk.segLayer);
|
||||
this._registerFat(fline, bk.segLayer, bk.lwMm);
|
||||
bk.rt = this._fatBufs[this._fatBufs.length - 1];
|
||||
this._group.add(fline);
|
||||
} else {
|
||||
@@ -1340,7 +1347,7 @@ export class Viewer2D {
|
||||
* into the front of the instance buffers and drops `instanceCount`.
|
||||
* The untouched originals are kept so any later mask starts from clean data.
|
||||
*/
|
||||
_registerFat(mesh, segLayerArr) {
|
||||
_registerFat(mesh, segLayerArr, lwMm = 0) {
|
||||
const geo = mesh.geometry;
|
||||
const posBuf = geo.attributes.instanceStart?.data;
|
||||
const colBuf = geo.attributes.instanceColorStart?.data;
|
||||
@@ -1348,7 +1355,7 @@ export class Viewer2D {
|
||||
posBuf.setUsage(THREE.DynamicDrawUsage);
|
||||
colBuf?.setUsage(THREE.DynamicDrawUsage);
|
||||
this._fatBufs.push({
|
||||
mesh,
|
||||
mesh, lwMm,
|
||||
segLayer: Int32Array.from(segLayerArr),
|
||||
posBuf, colBuf,
|
||||
posSrc: Float32Array.from(posBuf.array),
|
||||
@@ -1377,6 +1384,34 @@ export class Viewer2D {
|
||||
/** Whether lineweights are currently being drawn. */
|
||||
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).
|
||||
*
|
||||
* Applied to the live materials, not by rebuilding: the hairline cut-off is
|
||||
* a millimetre value, so changing the scale moves every weight by the same
|
||||
* factor without changing which bucket a segment belongs to. Retuning is
|
||||
* therefore instant even on a 37 MB drawing.
|
||||
*
|
||||
* @param {number|null} pxPerMm null restores the default.
|
||||
*/
|
||||
setLineweightScale(pxPerMm) {
|
||||
this._lwScale = (pxPerMm > 0) ? pxPerMm : null;
|
||||
const scale = this.getLineweightScale();
|
||||
for (const buf of this._fatBufs) {
|
||||
if (!(buf.lwMm > 0)) continue;
|
||||
buf.mesh.material.linewidth = Math.min(LW_MAX_PX, Math.round(buf.lwMm * scale * 10) / 10);
|
||||
}
|
||||
}
|
||||
|
||||
/** Current lineweight display scale (px per mm). */
|
||||
getLineweightScale() { return this._lwScale || LW_PX_PER_MM; }
|
||||
|
||||
/** Lineweights present in the current drawing, in mm (ascending). */
|
||||
getLineweightClasses() {
|
||||
return [...new Set(this._fatBufs.map((b) => b.lwMm).filter((m) => m > 0))].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
/** Track an entity-owned object so layer toggles can flip its visibility. */
|
||||
_addObj(obj, layer) {
|
||||
const key = layer ?? this._curLayer;
|
||||
@@ -2321,10 +2356,15 @@ export class Viewer2D {
|
||||
* assigned a weight render exactly as before.
|
||||
*/
|
||||
_lwPx(entity, inheritedPx = 0) {
|
||||
this._curLwMm = 0;
|
||||
if (!this._lwDisplay) return 0;
|
||||
let raw = entity?.entityHeader?.lineWeight ?? entity?.lineWeight ?? entity?.lineweight;
|
||||
if (typeof raw !== 'number') return 0;
|
||||
if (raw === -2) return inheritedPx; // ByBlock
|
||||
if (raw === -2) { // ByBlock
|
||||
const scale = this.getLineweightScale();
|
||||
this._curLwMm = inheritedPx > 0 ? inheritedPx / scale : 0;
|
||||
return inheritedPx;
|
||||
}
|
||||
if (raw === -1) { // ByLayer
|
||||
const lh = entity.layerHandle?.value ?? entity.layerHandle;
|
||||
const byHandle = lh != null ? this._layerLwByHandle.get(String(lh)) : undefined;
|
||||
@@ -2334,7 +2374,9 @@ export class Viewer2D {
|
||||
}
|
||||
const mm = raw === -3 ? LW_DEFAULT_MM : raw / 100;
|
||||
if (!(mm > LW_HAIRLINE_MM)) return 0;
|
||||
return Math.min(LW_MAX_PX, Math.round(mm * LW_PX_PER_MM * 10) / 10);
|
||||
this._curLwMm = mm;
|
||||
const scale = this.getLineweightScale();
|
||||
return Math.min(LW_MAX_PX, Math.round(mm * scale * 10) / 10);
|
||||
}
|
||||
|
||||
// Active-viewport VIEWTWIST (radians). The sheet is un-twisted by rotating the
|
||||
|
||||
Reference in New Issue
Block a user