refactor: establish unified viewer workspaces

This commit is contained in:
2026-07-29 09:11:14 +09:00
parent bedb357259
commit 3279ac099e
27 changed files with 591 additions and 4483 deletions
+11 -103
View File
@@ -1,113 +1,21 @@
# dwg-dxf-viewer-sample # hmwebviewer workspace
**hmwebviewer 형제 폴더**에 분리된 DWG/DXF 2D 뷰어 샘플입니다. Three.js 기반 3D viewer와 DWG/DXF 2D viewer sample을 한 저장소에서 관리합니다.
`docs/modules-dwg-dxf.html` 모듈 지도의 카드 이 저장소의 구조 통합과 검증 계획은 Gitea issue #5를 기준으로 진행합니다.
(`acadrust-dwg`, `dxf-parser`, `Viewer2D` …)를 **직접 조합**해 동작하는 독립 프로젝트입니다.
`hmwebviewer` 저장소 안에 의존하지 않습니다.
``` ## Workspace
D:\MYCLAUDE_PROJECT\
hmwebviewer\ ← 본 제품
dwg-dxf-viewer-sample\ ← 이 샘플 (형제)
```
--- - `@hmwebviewer/viewer-3d`: 기존 3D viewer application
- `@hmwebviewer/viewer-2d-sample`: DWG/DXF sample application
- `@hmwebviewer/viewer2d`: 재사용 가능한 DWG/DXF parser와 renderer module
## 실행 ## 실행
```bash ```bash
cd D:\MYCLAUDE_PROJECT\dwg-dxf-viewer-sample npm ci
npm install npm run dev:2d
npm run dev npm run dev:3d
# → http://localhost:5173
``` ```
| UI | 모듈 경로 | 전체 검증은 `npm run verify`로 실행합니다.
|----|-----------|
| **Sample DWG** | `ext2d`**acadrust-dwg WASM**`Viewer2D` |
| **Sample DXF** | `ext2d`**dxf-parser + hatch + adapter**`Viewer2D` |
| 파일 드롭 / Open | 확장자로 위 둘 중 하나 |
모듈 지도: 브라우저에서 [`docs/modules-dwg-dxf.html`](./docs/modules-dwg-dxf.html)
조합 코드: [`src/main.ts`](./src/main.ts)
---
## 레이아웃
```
dwg-dxf-viewer-sample/
docs/modules-dwg-dxf.html # 모듈 조합 레시피 HTML
public/
fonts/NanumGothic-Regular.ttf
samples/BasicSample.dwg
samples/simple.dxf
src/
main.ts # 모듈을 import 해서 조립
viewer2d/ # 재사용 모듈 본체
acadrust-dwg/ # WASM
Viewer2D.js
dwgParser.ts
parseDxf.ts
dxfAdapter.js
dxfHatchHandler.js
ext2d.ts
slugText.ts
package.json
vite.config.ts
```
---
## 조합 파이프라인
```
File/URL
→ ext2d (isDwg / isDxf)
→ parseDwgBuffer | parseDxfBuffer
│ │
└ acadrust-dwg ├ decodeDxf
├ dxf-parser + dxfHatchHandler
└ dxfAdapter
→ CadParseResult
→ Viewer2D.load() (+ three, slugText/font)
```
---
## 의존성
| 패키지 | 역할 |
|--------|------|
| `three` | WebGL 렌더 / OrbitControls |
| `dxf-parser` | DXF 텍스트 파싱 |
| `opentype.js` | TTF 글리프 (slugText) |
WASM(`acadrust_dwg_bg.wasm`)은 `src/viewer2d/acadrust-dwg/` 에 포함되어 있으며
Vite `?url` import 로 로드됩니다.
---
## 다른 앱에 이식
1. `src/viewer2d/` 폴더 복사
2. `npm i three dxf-parser opentype.js`
3. 폰트 `public/fonts/NanumGothic-Regular.ttf`
4. Vite: `assetsInclude: ['**/*.wasm']`
5. `src/main.ts` 의 ①~④ 블록을 호스트에 붙이고 ⑤ UI만 교체
소스 동기화(선택): 본가 `hmwebviewer/src/viewer2d` 가 업데이트되면 해당 파일을
이 프로젝트 `src/viewer2d` 로 다시 복사하면 됩니다.
---
## 라이선스 메모
| 조각 | 라이선스 |
|------|----------|
| Viewer2D 포트 · 샘플 글루 | MIT (hmwebviewer 계열) |
| acadrust (WASM 내부) | MPL-2.0 |
| dxf-parser · three | MIT |
| NanumGothic | OFL |
+113
View File
@@ -0,0 +1,113 @@
# dwg-dxf-viewer-sample
**hmwebviewer 형제 폴더**에 분리된 DWG/DXF 2D 뷰어 샘플입니다.
`docs/modules-dwg-dxf.html` 모듈 지도의 카드
(`acadrust-dwg`, `dxf-parser`, `Viewer2D` …)를 **직접 조합**해 동작하는 독립 프로젝트입니다.
`hmwebviewer` 저장소 안에 의존하지 않습니다.
```
D:\MYCLAUDE_PROJECT\
hmwebviewer\ ← 본 제품
dwg-dxf-viewer-sample\ ← 이 샘플 (형제)
```
---
## 실행
```bash
cd D:\MYCLAUDE_PROJECT\dwg-dxf-viewer-sample
npm install
npm run dev
# → http://localhost:5173
```
| UI | 모듈 경로 |
|----|-----------|
| **Sample DWG** | `ext2d`**acadrust-dwg WASM**`Viewer2D` |
| **Sample DXF** | `ext2d`**dxf-parser + hatch + adapter**`Viewer2D` |
| 파일 드롭 / Open | 확장자로 위 둘 중 하나 |
모듈 지도: 브라우저에서 [`docs/modules-dwg-dxf.html`](./docs/modules-dwg-dxf.html)
조합 코드: [`src/main.ts`](./src/main.ts)
---
## 레이아웃
```
dwg-dxf-viewer-sample/
docs/modules-dwg-dxf.html # 모듈 조합 레시피 HTML
public/
fonts/NanumGothic-Regular.ttf
samples/BasicSample.dwg
samples/simple.dxf
src/
main.ts # 모듈을 import 해서 조립
viewer2d/ # 재사용 모듈 본체
acadrust-dwg/ # WASM
Viewer2D.js
dwgParser.ts
parseDxf.ts
dxfAdapter.js
dxfHatchHandler.js
ext2d.ts
slugText.ts
package.json
vite.config.ts
```
---
## 조합 파이프라인
```
File/URL
→ ext2d (isDwg / isDxf)
→ parseDwgBuffer | parseDxfBuffer
│ │
└ acadrust-dwg ├ decodeDxf
├ dxf-parser + dxfHatchHandler
└ dxfAdapter
→ CadParseResult
→ Viewer2D.load() (+ three, slugText/font)
```
---
## 의존성
| 패키지 | 역할 |
|--------|------|
| `three` | WebGL 렌더 / OrbitControls |
| `dxf-parser` | DXF 텍스트 파싱 |
| `opentype.js` | TTF 글리프 (slugText) |
WASM(`acadrust_dwg_bg.wasm`)은 `src/viewer2d/acadrust-dwg/` 에 포함되어 있으며
Vite `?url` import 로 로드됩니다.
---
## 다른 앱에 이식
1. `src/viewer2d/` 폴더 복사
2. `npm i three dxf-parser opentype.js`
3. 폰트 `public/fonts/NanumGothic-Regular.ttf`
4. Vite: `assetsInclude: ['**/*.wasm']`
5. `src/main.ts` 의 ①~④ 블록을 호스트에 붙이고 ⑤ UI만 교체
소스 동기화(선택): 본가 `hmwebviewer/src/viewer2d` 가 업데이트되면 해당 파일을
이 프로젝트 `src/viewer2d` 로 다시 복사하면 됩니다.
---
## 라이선스 메모
| 조각 | 라이선스 |
|------|----------|
| Viewer2D 포트 · 샘플 글루 | MIT (hmwebviewer 계열) |
| acadrust (WASM 내부) | MPL-2.0 |
| dxf-parser · three | MIT |
| NanumGothic | OFL |
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@hmwebviewer/viewer-2d-sample",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite --port 5173 --strictPort",
"build": "tsc --noEmit && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hmwebviewer/viewer2d": "*",
"three": "0.185.1"
}
}
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*", "vite.config.ts"]
}
-4304
View File
File diff suppressed because it is too large Load Diff
+5 -13
View File
@@ -1,5 +1,5 @@
{ {
"name": "hmwebviewer", "name": "@hmwebviewer/viewer-3d",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.0",
"type": "module", "type": "module",
@@ -8,21 +8,13 @@
"build": "tsc --noEmit && vite build", "build": "tsc --noEmit && vite build",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"postinstall": "node tools/copy-decoders.mjs" "stage:decoders": "node tools/copy-decoders.mjs"
}, },
"dependencies": { "dependencies": {
"three": "^0.169.0", "three": "0.185.1",
"web-ifc": "^0.0.77" "web-ifc": "0.0.77"
}, },
"devDependencies": { "devDependencies": {
"@gltf-transform/cli": "^4.4.0", "puppeteer-core": "^25.1.0"
"@gltf-transform/core": "^4.4.0",
"@gltf-transform/extensions": "^4.4.0",
"@gltf-transform/functions": "^4.4.0",
"@types/three": "^0.169.0",
"draco3d": "^1.5.7",
"puppeteer-core": "^25.1.0",
"typescript": "^5.6.0",
"vite": "^5.4.0"
} }
} }
-1
View File
@@ -24,7 +24,6 @@ Both are dependencies of `KTX2Loader`:
```js ```js
const ktx2Loader = new KTX2Loader(); const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( 'examples/jsm/libs/basis/' );
ktx2Loader.detectSupport( renderer ); ktx2Loader.detectSupport( renderer );
ktx2Loader.load( 'diffuse.ktx2', function ( texture ) { ktx2Loader.load( 'diffuse.ktx2', function ( texture ) {
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
ply
format ascii 1.0
comment Three.js r185 Float64 normalization fixture
element vertex 3
property double x
property double y
property double z
property uchar red
property uchar green
property uchar blue
element face 1
property list uchar int vertex_indices
end_header
0.0 0.0 0.0 255 0 0
100.0 0.0 0.0 0 255 0
0.0 100.0 0.0 0 0 255
3 0 1 2
View File
View File
+28 -1
View File
@@ -62,6 +62,21 @@ async function getPLY(): Promise<PLYLoaderT> {
return (_ply = new PLYLoader()); return (_ply = new PLYLoader());
} }
function normalizePlyAttributes(geometry: THREE.BufferGeometry): void {
for (const name of ['position', 'normal', 'uv', 'color']) {
const attribute = geometry.getAttribute(name);
if (!attribute || !(attribute.array instanceof Float64Array)) continue;
geometry.setAttribute(
name,
new THREE.Float32BufferAttribute(
new Float32Array(attribute.array),
attribute.itemSize,
attribute.normalized,
),
);
}
}
/** /**
* Load any supported model URL and resolve to a scene-addable Object3D. * Load any supported model URL and resolve to a scene-addable Object3D.
* `nameHint` carries the real file name for ext detection when `url` is a * `nameHint` carries the real file name for ext detection when `url` is a
@@ -165,7 +180,18 @@ export async function loadModel(
case 'dae': { case 'dae': {
const loader = await getCollada(); const loader = await getCollada();
return new Promise<THREE.Object3D>((resolve, reject) => { return new Promise<THREE.Object3D>((resolve, reject) => {
loader.load(url, (result) => resolve(result.scene), onXhr, (err) => reject(err)); loader.load(
url,
(result) => {
if (!result) {
reject(new Error('Collada loader returned no scene'));
return;
}
resolve(result.scene);
},
onXhr,
(err) => reject(err),
);
}); });
} }
case 'ply': { case 'ply': {
@@ -174,6 +200,7 @@ export async function loadModel(
loader.load( loader.load(
url, url,
(geometry) => { (geometry) => {
normalizePlyAttributes(geometry);
const hasColor = geometry.hasAttribute('color'); const hasColor = geometry.hasAttribute('color');
if (geometry.index) { if (geometry.index) {
// Triangle mesh // Triangle mesh
+7 -3
View File
@@ -2,14 +2,16 @@
// runtime paths /draco/ and /basis/ resolve (version-matched to installed three). // runtime paths /draco/ and /basis/ resolve (version-matched to installed three).
// Also copies the web-ifc single-thread WASM to public/web-ifc/ for the IFC path. // Also copies the web-ifc single-thread WASM to public/web-ifc/ for the IFC path.
// Runs automatically via `npm install` (postinstall). Safe to re-run. // Runs automatically via `npm install` (postinstall). Safe to re-run.
import { cpSync, copyFileSync, existsSync, mkdirSync } from 'node:fs'; import { cpSync, copyFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
import { dirname, resolve } from 'node:path'; import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, '..'); const root = resolve(__dirname, '..');
const libs = resolve(root, 'node_modules/three/examples/jsm/libs'); const threeEntry = fileURLToPath(import.meta.resolve('three'));
const threeRoot = resolve(dirname(threeEntry), '..');
const libs = resolve(threeRoot, 'examples/jsm/libs');
const targets = [ const targets = [
[resolve(libs, 'draco/gltf'), resolve(root, 'public/draco')], [resolve(libs, 'draco/gltf'), resolve(root, 'public/draco')],
[resolve(libs, 'basis'), resolve(root, 'public/basis')], [resolve(libs, 'basis'), resolve(root, 'public/basis')],
@@ -25,13 +27,15 @@ for (const [src, dest] of targets) {
console.warn(`[copy-decoders] source missing: ${src}`); console.warn(`[copy-decoders] source missing: ${src}`);
continue; continue;
} }
rmSync(dest, { force: true, recursive: true });
mkdirSync(dest, { recursive: true }); mkdirSync(dest, { recursive: true });
cpSync(src, dest, { recursive: true }); cpSync(src, dest, { recursive: true });
console.log(`[copy-decoders] ${src} -> ${dest}`); console.log(`[copy-decoders] ${src} -> ${dest}`);
} }
// web-ifc single-thread WASM (+ MT build if present) for the IFC load path. // web-ifc single-thread WASM (+ MT build if present) for the IFC load path.
const ifcSrcDir = resolve(root, 'node_modules/web-ifc'); const ifcEntry = fileURLToPath(import.meta.resolve('web-ifc'));
const ifcSrcDir = dirname(ifcEntry);
const ifcDest = resolve(root, 'public/web-ifc'); const ifcDest = resolve(root, 'public/web-ifc');
if (existsSync(ifcSrcDir)) { if (existsSync(ifcSrcDir)) {
mkdirSync(ifcDest, { recursive: true }); mkdirSync(ifcDest, { recursive: true });
+3 -1
View File
@@ -7,7 +7,9 @@ export default defineConfig({
sourcemap: true, sourcemap: true,
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { three: ['three'] }, manualChunks(id) {
if (id.includes('/node_modules/three/')) return 'three';
},
}, },
}, },
}, },
+365 -8
View File
@@ -1,25 +1,46 @@
{ {
"name": "dwg-dxf-viewer-sample", "name": "hmwebviewer-workspace",
"version": "0.1.0", "version": "0.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "dwg-dxf-viewer-sample", "name": "hmwebviewer-workspace",
"version": "0.1.0", "version": "0.2.0",
"hasInstallScript": true,
"workspaces": [ "workspaces": [
"apps/*",
"packages/*" "packages/*"
], ],
"dependencies": {
"@hmwebviewer/viewer2d": "workspace:*",
"three": "0.185.1"
},
"devDependencies": { "devDependencies": {
"@playwright/test": "1.62.0", "@playwright/test": "1.62.0",
"@types/three": "0.185.1", "@types/three": "0.185.1",
"typescript": "7.0.2", "typescript": "7.0.2",
"vite": "8.1.5", "vite": "8.1.5",
"vitest": "4.1.10" "vitest": "4.1.10"
},
"engines": {
"node": ">=24.0.0",
"npm": ">=11.0.0"
}
},
"apps/viewer-2d-sample": {
"name": "@hmwebviewer/viewer-2d-sample",
"version": "0.1.0",
"dependencies": {
"@hmwebviewer/viewer2d": "*",
"three": "0.185.1"
}
},
"apps/viewer-3d": {
"name": "@hmwebviewer/viewer-3d",
"version": "0.1.0",
"dependencies": {
"three": "0.185.1",
"web-ifc": "0.0.77"
},
"devDependencies": {
"puppeteer-core": "^25.1.0"
} }
}, },
"node_modules/@dimforge/rapier3d-compat": { "node_modules/@dimforge/rapier3d-compat": {
@@ -66,6 +87,14 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@hmwebviewer/viewer-2d-sample": {
"resolved": "apps/viewer-2d-sample",
"link": true
},
"node_modules/@hmwebviewer/viewer-3d": {
"resolved": "apps/viewer-3d",
"link": true
},
"node_modules/@hmwebviewer/viewer2d": { "node_modules/@hmwebviewer/viewer2d": {
"resolved": "packages/viewer2d", "resolved": "packages/viewer2d",
"link": true "link": true
@@ -125,6 +154,35 @@
"node": ">=20" "node": ">=20"
} }
}, },
"node_modules/@puppeteer/browsers": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.6.tgz",
"integrity": "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"modern-tar": "^0.7.6",
"yargs": "^18.0.0"
},
"bin": {
"browsers": "lib/main-cli.js"
},
"engines": {
"node": ">=22.12.0"
},
"peerDependencies": {
"proxy-agent": ">=8.0.1",
"yauzl": "^2.10.0 || ^3.4.0"
},
"peerDependenciesMeta": {
"proxy-agent": {
"optional": true
},
"yauzl": {
"optional": true
}
}
},
"node_modules/@rolldown/binding-android-arm64": { "node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
@@ -973,6 +1031,19 @@
"url": "https://opencollective.com/vitest" "url": "https://opencollective.com/vitest"
} }
}, },
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/assertion-error": { "node_modules/assertion-error": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -993,6 +1064,94 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/chromium-bidi": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz",
"integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"mitt": "^3.0.1",
"zod": "^3.24.1"
},
"engines": {
"node": ">=20.19.0 <22.0.0 || >=22.12.0"
},
"peerDependencies": {
"devtools-protocol": "*"
}
},
"node_modules/cliui": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
"integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^7.2.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cliui/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/cliui/node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
"dev": true,
"license": "MIT"
},
"node_modules/cliui/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/convert-source-map": { "node_modules/convert-source-map": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1010,6 +1169,13 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/devtools-protocol": {
"version": "0.0.1653615",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1653615.tgz",
"integrity": "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/dxf-parser": { "node_modules/dxf-parser": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/dxf-parser/-/dxf-parser-1.1.2.tgz", "resolved": "https://registry.npmjs.org/dxf-parser/-/dxf-parser-1.1.2.tgz",
@@ -1026,6 +1192,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/estree-walker": { "node_modules/estree-walker": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -1086,6 +1262,29 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.33.0", "version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
@@ -1389,6 +1588,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true,
"license": "MIT"
},
"node_modules/modern-tar": {
"version": "0.7.7",
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz",
"integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.16", "version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
@@ -1519,6 +1735,24 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/puppeteer-core": {
"version": "25.4.0",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.4.0.tgz",
"integrity": "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "3.0.6",
"chromium-bidi": "17.0.2",
"devtools-protocol": "0.0.1653615",
"typed-query-selector": "^2.12.2",
"webdriver-bidi-protocol": "0.4.2",
"ws": "^8.21.1"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/rolldown": { "node_modules/rolldown": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
@@ -1584,6 +1818,22 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/three": { "node_modules/three": {
"version": "0.185.1", "version": "0.185.1",
"resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.185.1.tgz",
@@ -1642,6 +1892,13 @@
"license": "0BSD", "license": "0BSD",
"optional": true "optional": true
}, },
"node_modules/typed-query-selector": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
"integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "7.0.2", "version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
@@ -1860,6 +2117,19 @@
} }
} }
}, },
"node_modules/web-ifc": {
"version": "0.0.77",
"resolved": "https://registry.npmjs.org/web-ifc/-/web-ifc-0.0.77.tgz",
"integrity": "sha512-VzQ0W/Iiqbidxn1ECUvz6qJ6p2sXBVNcOsUOBCETzy77psAH6yFLKQm74aXabkx3JH4OvFVHe8k1qS6+Z2zl1w==",
"license": "MPL-2.0"
},
"node_modules/webdriver-bidi-protocol": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.2.tgz",
"integrity": "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/why-is-node-running": { "node_modules/why-is-node-running": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@@ -1877,6 +2147,93 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/ws": {
"version": "8.21.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "18.1.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cliui": "^9.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"string-width": "^8.2.1",
"y18n": "^5.0.5",
"yargs-parser": "^22.0.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yargs-parser": {
"version": "22.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
"integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
"dev": true,
"license": "ISC",
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=23"
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.5.0",
"strip-ansi": "^7.1.2"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"packages/viewer2d": { "packages/viewer2d": {
"name": "@hmwebviewer/viewer2d", "name": "@hmwebviewer/viewer2d",
"version": "0.1.0", "version": "0.1.0",
+16 -13
View File
@@ -1,23 +1,22 @@
{ {
"name": "dwg-dxf-viewer-sample", "name": "hmwebviewer-workspace",
"private": true, "private": true,
"version": "0.1.0", "version": "0.2.0",
"description": "Standalone sample: compose acadrust-dwg + dxf-parser + Viewer2D (sibling of hmwebviewer)",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
"apps/*",
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"dev": "vite --port 5173 --strictPort", "dev:2d": "npm --workspace @hmwebviewer/viewer-2d-sample run dev",
"build": "tsc --noEmit && vite build", "dev:3d": "npm --workspace @hmwebviewer/viewer-3d run dev",
"preview": "vite preview", "build": "npm run build --workspaces --if-present",
"typecheck": "tsc --noEmit", "typecheck": "npm run typecheck --workspaces --if-present",
"test": "vitest run", "test": "npm run test:unit",
"test:unit": "vitest run tests/viewer2d.contract.test.ts" "test:unit": "vitest run",
}, "test:e2e": "playwright test",
"dependencies": { "verify": "npm run typecheck && npm run test:unit && npm run build && npm run test:e2e",
"@hmwebviewer/viewer2d": "workspace:*", "postinstall": "npm --workspace @hmwebviewer/viewer-3d run stage:decoders"
"three": "0.185.1"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.62.0", "@playwright/test": "1.62.0",
@@ -25,5 +24,9 @@
"typescript": "7.0.2", "typescript": "7.0.2",
"vite": "8.1.5", "vite": "8.1.5",
"vitest": "4.1.10" "vitest": "4.1.10"
},
"engines": {
"node": ">=24.0.0",
"npm": ">=11.0.0"
} }
} }
+2
View File
@@ -1,3 +1,5 @@
/// <reference path="./shims.d.ts" />
import { Viewer2D } from './Viewer2D.js'; import { Viewer2D } from './Viewer2D.js';
import { parseDwgBuffer, type CadParseResult } from './dwgParser'; import { parseDwgBuffer, type CadParseResult } from './dwgParser';
import { is2D, isDwg } from './ext2d'; import { is2D, isDwg } from './ext2d';
+3 -1
View File
@@ -4,7 +4,9 @@ import { isCad2DFile, parseCad } from '@hmwebviewer/viewer2d';
describe('viewer2d package interface', () => { describe('viewer2d package interface', () => {
it('parses the sample DXF through the public package interface', async () => { it('parses the sample DXF through the public package interface', async () => {
const file = await readFile(new URL('../public/samples/simple.dxf', import.meta.url)); const file = await readFile(
new URL('../apps/viewer-2d-sample/public/samples/simple.dxf', import.meta.url),
);
const result = await parseCad({ const result = await parseCad({
name: 'simple.dxf', name: 'simple.dxf',
data: file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength), data: file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength),
+1 -2
View File
@@ -10,6 +10,5 @@
"esModuleInterop": true, "esModuleInterop": true,
"isolatedModules": true, "isolatedModules": true,
"allowJs": true "allowJs": true
}, }
"include": ["src/**/*", "packages/*/src/**/*", "vite.config.ts"]
} }