@flyfish-dev/cad-viewer 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +11 -0
  3. package/README.md +411 -0
  4. package/README.zh-CN.md +411 -0
  5. package/dist/assets/DwgWorker-CuZJ5EUe.js +349 -0
  6. package/dist/assets/DwgWorker-CuZJ5EUe.js.map +1 -0
  7. package/dist/assets/index-C365l3i9.js +4161 -0
  8. package/dist/assets/index-C365l3i9.js.map +1 -0
  9. package/dist/cad-viewer.es.js +2992 -0
  10. package/dist/cad-viewer.es.js.map +1 -0
  11. package/dist/cad-viewer.umd.cjs +30 -0
  12. package/dist/cad-viewer.umd.cjs.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +1 -0
  15. package/dist/style.css +1 -0
  16. package/dist/types/core/color.d.ts +21 -0
  17. package/dist/types/core/color.d.ts.map +1 -0
  18. package/dist/types/core/entity.d.ts +34 -0
  19. package/dist/types/core/entity.d.ts.map +1 -0
  20. package/dist/types/core/format.d.ts +8 -0
  21. package/dist/types/core/format.d.ts.map +1 -0
  22. package/dist/types/core/geometry.d.ts +17 -0
  23. package/dist/types/core/geometry.d.ts.map +1 -0
  24. package/dist/types/core/transform.d.ts +20 -0
  25. package/dist/types/core/transform.d.ts.map +1 -0
  26. package/dist/types/core/types.d.ts +195 -0
  27. package/dist/types/core/types.d.ts.map +1 -0
  28. package/dist/types/index.d.ts +12 -0
  29. package/dist/types/index.d.ts.map +1 -0
  30. package/dist/types/loaders/CadLoaderRegistry.d.ts +15 -0
  31. package/dist/types/loaders/CadLoaderRegistry.d.ts.map +1 -0
  32. package/dist/types/loaders/dwf/DwfLoader.d.ts +15 -0
  33. package/dist/types/loaders/dwf/DwfLoader.d.ts.map +1 -0
  34. package/dist/types/loaders/dwg/DwgLoader.d.ts +15 -0
  35. package/dist/types/loaders/dwg/DwgLoader.d.ts.map +1 -0
  36. package/dist/types/loaders/dwg/DwgParser.d.ts +24 -0
  37. package/dist/types/loaders/dwg/DwgParser.d.ts.map +1 -0
  38. package/dist/types/loaders/dwg/DwgWorker.d.ts +2 -0
  39. package/dist/types/loaders/dwg/DwgWorker.d.ts.map +1 -0
  40. package/dist/types/loaders/dwg/DwgWorkerClient.d.ts +18 -0
  41. package/dist/types/loaders/dwg/DwgWorkerClient.d.ts.map +1 -0
  42. package/dist/types/loaders/dwg/dwgVersion.d.ts +9 -0
  43. package/dist/types/loaders/dwg/dwgVersion.d.ts.map +1 -0
  44. package/dist/types/loaders/dwg/libredwgWasmModuleProxy.d.ts +14 -0
  45. package/dist/types/loaders/dwg/libredwgWasmModuleProxy.d.ts.map +1 -0
  46. package/dist/types/loaders/dxf/DxfLoader.d.ts +12 -0
  47. package/dist/types/loaders/dxf/DxfLoader.d.ts.map +1 -0
  48. package/dist/types/loaders/index.d.ts +8 -0
  49. package/dist/types/loaders/index.d.ts.map +1 -0
  50. package/dist/types/viewer/CadCanvasRenderer.d.ts +142 -0
  51. package/dist/types/viewer/CadCanvasRenderer.d.ts.map +1 -0
  52. package/dist/types/viewer/CadViewer.d.ts +55 -0
  53. package/dist/types/viewer/CadViewer.d.ts.map +1 -0
  54. package/dist/types/viewer/CadWebGLRenderer.d.ts +69 -0
  55. package/dist/types/viewer/CadWebGLRenderer.d.ts.map +1 -0
  56. package/dist/wasm/libredwg-web.js +15 -0
  57. package/dist/wasm/libredwg-web.wasm +0 -0
  58. package/package.json +103 -0
@@ -0,0 +1,411 @@
1
+ # @flyfish-dev/cad-viewer
2
+
3
+ 一个专业、轻量、可扩展的**纯前端 CAD Viewer**。
4
+
5
+ [![License: AGPL-3.0-only](https://img.shields.io/badge/license-AGPL--3.0--only-b31b1b.svg)](LICENSE)
6
+ [![npm](https://img.shields.io/npm/v/@flyfish-dev/cad-viewer.svg)](https://www.npmjs.com/package/@flyfish-dev/cad-viewer)
7
+
8
+ **在线 Demo:** [cad-viewer-iys.pages.dev](https://cad-viewer-iys.pages.dev)
9
+ **源码仓库:** [github.com/flyfish-dev/cad-viewer](https://github.com/flyfish-dev/cad-viewer)
10
+
11
+ 本仓库提供面向浏览器的 loader 架构,支持 **DWG**、**DXF**、以及 **DWFx/XPS 兼容 DWF 预览**,将不同 CAD 格式归一化为统一 `CadDocument`,并通过 WebGL retained-mode 渲染,文字/图片使用轻量 Canvas overlay。文件在浏览器本地读取,组件不会把图纸上传到服务端。
12
+
13
+ > DWG 使用 `@mlightcad/libredwg-web` / LibreDWG WebAssembly。DXF 使用 JavaScript 解析器并带内置 fallback。DWFx 使用 XPS `FixedPage` 2D 页面解析。经典 `.dwf` 里的 WHIP/W2D/W3D 流会被检测并给出明确提示;若要完整解码经典 DWF,仍需要专门 WHIP 解码器或 DWF Toolkit/WASM 实现。
14
+
15
+
16
+ ## 0.5.3 变更
17
+
18
+ - 修复 DWG 图层索引色显示。LibreDWG 输出的索引色图层会优先按 ACI 解析,不再被 converter 的 `0xffffff` 占位值渲染成单色白图。
19
+ - 保留 DWG true-color 数值,即使 RGB 整数落在 ACI 范围内,例如 `0x0000ff`。
20
+ - 展开 INSERT/block 时增加 BYBLOCK 颜色继承。
21
+
22
+ ## 0.5.2 变更
23
+
24
+ - 修复 `@mlightcad/libredwg-web` 入口导致的 Vite 超长 `data:application/wasm;base64,...` warning。
25
+ - DWG worker 构建改为使用 LibreDWG 的轻量 ESM wrapper,并在运行时从 `wasmPath` 加载 `/wasm/libredwg-web.js` 和 `/wasm/libredwg-web.wasm`,避免重复打包内联 wasm。
26
+ - `build:lib` 会生成 `dist/index.js` 兼容入口,兼容仍然请求 `/dist/index.js` 的旧集成方式。
27
+ - Vite dev 模式下新增 `/dist/index.js` 兼容路由,旧 demo 页面会被转到 `/demo/main.ts`,不再直接 404。
28
+ - `npm run preview` 会先构建 demo,干净仓库也可以直接预览。
29
+
30
+ ## 0.5 变更
31
+
32
+ - 默认渲染后端升级为 **WebGL retained renderer**,首帧把 CAD 图元扁平化并上传为 GPU buffer,缩放/平移只更新 view uniform。
33
+ - 新增空间索引和 viewport culling:线段、三角面、点会按图纸范围分桶,放大查看局部区域时只提交可见 batch。
34
+ - 新增大图纸内存策略:坐标以图纸中心为 origin 存入 `Float32Array`,颜色存入 `Uint8Array`,上传后释放 CPU 临时数组,避免 Canvas2D 每帧重建路径。
35
+ - 文字、图片走独立 overlay,并带屏幕尺寸阈值和最大可见 label 限制,避免高密度图纸在缩放时被文字拖慢。
36
+ - `CadViewer` 新增 `renderer: 'auto' | 'webgl' | 'canvas2d'`;`auto` 优先 WebGL,不可用时回退 Canvas2D。
37
+ - Demo 新增 Renderer、visible primitives、GPU memory 指标,便于排查大图纸性能。
38
+
39
+ ## 0.4 变更
40
+
41
+ - DWG 解析默认放到**独立 Web Worker** 中执行,LibreDWG WASM 初始化和二进制解析不会阻塞 UI、缩放和平移。
42
+ - Worker 会常驻并复用 LibreDWG WASM 实例,连续打开多个 DWG 时不重复初始化。
43
+ - 增加 `AbortSignal` 取消加载、worker timeout、加载进度事件,以及显式 worker 资源配置。
44
+ - 默认不再把 DWG raw parser 对象传回主线程,降低内存占用并避免 structured clone 失败;确实需要时可显式设置 `keepRaw: true`。
45
+ - Demo 增加加载遮罩、进度条、取消按钮和 loader mode 指示。
46
+ - `viewer.destroy()` 会清理 Canvas 监听器并终止持有的 DWG worker,适合 SPA 路由切换。
47
+ - 导出 `supportsDwgWorker` 和 `DwgWorkerClient`,方便高级集成。
48
+
49
+ ## 功能特性
50
+
51
+ - **纯前端组件**:`new CadViewer({ container })` 或 `new CadViewer({ canvas })`。
52
+ - **正确的 loader 架构**:DWG / DXF / DWF 独立 loader,可替换、可扩展。
53
+ - **DWG 预览**:通过 LibreDWG WebAssembly 在浏览器本地解析,默认在 Web Worker 中执行。
54
+ - **DXF 预览**:JavaScript 解析,支持常见 ASCII DXF `ENTITIES`,并带 fallback parser。
55
+ - **DWF/DWFx 预览**:支持 DWFx/XPS 2D `FixedPage` 的 Path、Glyphs、图片预览。
56
+ - **CAD 颜色处理**:支持 ACI、BYLAYER、BYBLOCK 继承、DWG 图层颜色、true color、填充色、透明度和自适应对比度。
57
+ - **WebGL 高性能交互**:GPU retained buffers、空间分桶、视口裁剪、缩放、平移、适配窗口、世界坐标、缩放百分比。
58
+ - **专业 Demo**:拖拽打开、紧凑工具栏、状态条、解析/渲染耗时、实体类型统计、warnings 展示。
59
+ - **发布友好**:同时提供 npm library build 和 Cloudflare Pages demo build。
60
+
61
+ ## 安装
62
+
63
+ ```bash
64
+ npm install @flyfish-dev/cad-viewer
65
+ ```
66
+
67
+ 本仓库本地开发:
68
+
69
+ ```bash
70
+ npm install
71
+ npm run dev
72
+ ```
73
+
74
+ DWG loader 需要将 LibreDWG WASM 文件放到公开目录。Demo 使用以下命令复制并校验到 `public/wasm`:
75
+
76
+ ```bash
77
+ npm run copy:wasm
78
+ npm run check:wasm
79
+ ```
80
+
81
+ Demo 会先把 `wasmPath` 解析为绝对 URL,再发送给 worker。你自己的应用也建议使用绝对路径或绝对 URL,例如 `/wasm` 或 `new URL('wasm/', document.baseURI).href`。不要直接把未解析的 `./wasm` 传入 worker,否则它可能会相对于 worker chunk 请求资源。
82
+
83
+ 发布 npm 包时,`build:lib` 也会把这些文件复制到 `dist/wasm` 并作为 package subpath 暴露出来。应用侧仍需要把 `.wasm` 放到可公开访问的 URL,并把该目录传给 `wasmPath`。
84
+
85
+
86
+ ## Demo 启动说明
87
+
88
+ 源码 demo 必须通过 Vite 启动:
89
+
90
+ ```bash
91
+ npm install
92
+ npm run dev
93
+ ```
94
+
95
+ 生产预览使用:
96
+
97
+ ```bash
98
+ npm run preview
99
+ ```
100
+
101
+ 不要直接用普通静态服务器托管源码目录并期望 TypeScript 入口能运行。如果旧页面仍请求 `/dist/index.js`,请先执行 `npm run build:lib` 生成兼容入口,或者直接使用上面的 Vite dev server。
102
+
103
+ ## 基础用法
104
+
105
+ ```ts
106
+ import { CadViewer } from '@flyfish-dev/cad-viewer';
107
+ import '@flyfish-dev/cad-viewer/style.css';
108
+
109
+ const viewer = new CadViewer({
110
+ container: document.querySelector('#viewer')!,
111
+ renderer: 'auto', // WebGL first, Canvas2D fallback
112
+ wasmPath: new URL('wasm/', document.baseURI).href,
113
+ canvasOptions: {
114
+ background: '#05070d',
115
+ foreground: '#f8fafc',
116
+ contrastMode: 'adaptive',
117
+ minColorContrast: 2.45
118
+ }
119
+ });
120
+
121
+ const input = document.querySelector<HTMLInputElement>('input[type=file]')!;
122
+ input.addEventListener('change', async () => {
123
+ const file = input.files?.[0];
124
+ if (!file) return;
125
+ await viewer.loadFile(file);
126
+ });
127
+ ```
128
+
129
+
130
+ ## WebGL 渲染与性能策略
131
+
132
+ 默认 `renderer: 'auto'` 会优先创建 `CadWebGLRenderer`。它不是每次缩放都用 Canvas2D 重新遍历实体,而是在 `setDocument()` 时构建一次 retained scene:
133
+
134
+ ```text
135
+ CadDocument
136
+
137
+ flatten blocks / curves / fills
138
+
139
+ Float32Array positions + Uint8Array colors
140
+
141
+ spatial GPU batches
142
+
143
+ WebGL drawArrays with viewport culling
144
+ ```
145
+
146
+ 可调参数:
147
+
148
+ ```ts
149
+ new CadViewer({
150
+ container,
151
+ renderer: 'auto', // 'webgl' 强制 WebGL;'canvas2d' 强制兼容模式
152
+ canvasOptions: {
153
+ enableSpatialIndex: true,
154
+ spatialIndexCellCount: 96,
155
+ maxVerticesPerBatch: 32768,
156
+ maxCurveSegments: 72,
157
+ textMinPixelHeight: 4,
158
+ maxVisibleTextLabels: 2400,
159
+ powerPreference: 'high-performance',
160
+ preserveDrawingBuffer: false
161
+ },
162
+ onRenderStats(stats) {
163
+ console.log(stats.backend, stats.visiblePrimitiveCount, stats.gpuMemoryBytes);
164
+ }
165
+ });
166
+ ```
167
+
168
+ 对超大图纸,优先调小 `maxCurveSegments`、增大 `spatialIndexCellCount`,并限制 `maxVisibleTextLabels`。
169
+
170
+ ## Worker 化 DWG 解析
171
+
172
+ `DwgLoader` 在浏览器里默认使用 module Web Worker。Worker 内部导入 `@mlightcad/libredwg-web`,初始化 LibreDWG WASM,缓存该 WASM 实例,解码 DWG 字节,并把结果归一化为可 structured-clone 的 `CadDocument` 后发送回 UI 线程。Canvas 渲染仍然留在主线程。
173
+
174
+ ```ts
175
+ const controller = new AbortController();
176
+
177
+ const viewer = new CadViewer({
178
+ container,
179
+ wasmPath: new URL('wasm/', document.baseURI).href,
180
+ useWorker: true,
181
+ workerTimeoutMs: 120_000,
182
+ onLoadProgress(progress) {
183
+ console.log(progress.phase, progress.message, progress.percent);
184
+ }
185
+ });
186
+
187
+ await viewer.preloadDwg(); // 可选:首个文件打开前预热 worker
188
+ await viewer.loadFile(file, { signal: controller.signal });
189
+
190
+ // 取消大型 DWG 加载
191
+ controller.abort();
192
+ ```
193
+
194
+ 如果你的构建系统或 CDN 对 worker 资源路径有特殊要求,可以显式传入 worker 地址:
195
+
196
+ ```ts
197
+ new CadViewer({
198
+ container,
199
+ wasmPath: new URL('wasm/', document.baseURI).href,
200
+ workerUrl: new URL('/assets/dwg-worker.js', window.location.origin)
201
+ });
202
+ ```
203
+
204
+ 默认包是 worker-first 设计。非浏览器运行时建议注册自定义 DWG loader,而不是关闭 worker。
205
+
206
+ ## 组件 API
207
+
208
+ ```ts
209
+ const viewer = new CadViewer({
210
+ container, // 容器元素,组件会自动创建 canvas
211
+ canvas, // 也可以传入已有 canvas
212
+ renderer: 'auto', // 'auto' | 'webgl' | 'canvas2d'
213
+ wasmPath: '/wasm', // LibreDWG WebAssembly 资源路径。Worker 场景建议使用绝对路径/URL
214
+ autoFit: true,
215
+ canvasOptions: {
216
+ background: '#05070d',
217
+ foreground: '#ffffff',
218
+ contrastMode: 'adaptive', // 'adaptive' | 'preserve'
219
+ minColorContrast: 2.45,
220
+ showPageBounds: true,
221
+ showUnsupportedMarkers: false,
222
+ trueColorByteOrder: 'rgb',
223
+ enableSpatialIndex: true,
224
+ spatialIndexCellCount: 96,
225
+ maxVerticesPerBatch: 32768,
226
+ maxCurveSegments: 72,
227
+ textMinPixelHeight: 4,
228
+ maxVisibleTextLabels: 2400
229
+ },
230
+ useWorker: true, // DWG 默认开启
231
+ workerTimeoutMs: 0, // 0 表示不限制
232
+ onLoadProgress(progress) {},
233
+ onLoad(result) {},
234
+ onError(error) {},
235
+ onRenderStats(stats) {},
236
+ onViewChange(event) {}
237
+ });
238
+
239
+ await viewer.loadFile(file);
240
+ await viewer.loadBuffer(arrayBuffer, 'drawing.dxf');
241
+ viewer.fit();
242
+ viewer.zoomIn();
243
+ viewer.zoomOut();
244
+ await viewer.preloadDwg(); // 可选 DWG worker/WASM 预热
245
+ viewer.setCanvasOptions({ background: '#f7f8fb', foreground: '#111827' });
246
+ viewer.clear();
247
+ viewer.destroy();
248
+ ```
249
+
250
+ ## Loader 架构
251
+
252
+ ```text
253
+ File / ArrayBuffer
254
+
255
+ CadLoaderRegistry
256
+
257
+ DwgLoader | DxfLoader | DwfLoader | 自定义 loader
258
+
259
+ CadDocument
260
+
261
+ CadWebGLRenderer | CadCanvasRenderer fallback
262
+
263
+ WebGL preview + Canvas overlay
264
+ ```
265
+
266
+ 所有 loader 都输出统一的 `CadDocument`:
267
+
268
+ ```ts
269
+ interface CadDocument {
270
+ format: 'dwg' | 'dxf' | 'dwf' | 'dwfx' | 'xps' | 'unknown';
271
+ layers: Record<string, CadLayer>;
272
+ blocks: Record<string, CadBlock>;
273
+ entities: CadEntity[];
274
+ pages?: CadPage[];
275
+ warnings: string[];
276
+ raw?: unknown;
277
+ }
278
+ ```
279
+
280
+ 注册自定义 loader:
281
+
282
+ ```ts
283
+ viewer.registerLoader({
284
+ id: 'my-cad-format',
285
+ label: 'My CAD Format',
286
+ formats: ['unknown'],
287
+ accepts(input) {
288
+ return input.fileName?.endsWith('.cad') ?? false;
289
+ },
290
+ async load(input) {
291
+ return {
292
+ document: {
293
+ format: 'unknown',
294
+ layers: {},
295
+ blocks: {},
296
+ entities: [],
297
+ metadata: {},
298
+ warnings: []
299
+ },
300
+ bytes: input.buffer instanceof Uint8Array ? input.buffer.byteLength : 0,
301
+ elapsedMs: 0,
302
+ format: 'unknown',
303
+ warnings: []
304
+ };
305
+ }
306
+ });
307
+ ```
308
+
309
+ ## 格式支持说明
310
+
311
+ | 格式 | Loader | 支持范围 |
312
+ |---|---|---|
313
+ | DWG | `DwgLoader` | 使用 LibreDWG WebAssembly。渲染完整度取决于 LibreDWG converter 暴露出的实体。 |
314
+ | DXF | `DxfLoader` | 使用 `dxf-parser` + 内置 fallback。支持基础实体、block/insert、颜色/图层、多段线、文字、hatch boundary、spline 预览。 |
315
+ | DWFx / XPS | `DwfLoader` | 解析 ZIP/OPC 包,并渲染 2D `FixedPage` path/glyph/image 内容。 |
316
+ | 经典 DWF | `DwfLoader` 检测 | 能检测 WHIP/W2D/W3D 包内容并给出明确错误。完整经典 DWF 需要专门 WHIP 解码器或 DWF Toolkit/WASM。 |
317
+
318
+ ## 颜色处理
319
+
320
+ 颜色解析遵循 CAD 语义,而不是把所有数字都当成 RGB:
321
+
322
+ 1. 显式 CSS color 或 true color 对象;
323
+ 2. 显式 DWG true-color 整数,包括 `0x0000ff` 这类落在 ACI 数值范围内的低 RGB 值;
324
+ 3. 实体 ACI:`colorIndex`、`colorNumber`、`color` 的 `1..255`;
325
+ 4. 展开 INSERT/block 时处理 BYBLOCK:`0` 继承插入引用颜色;
326
+ 5. BYLAYER:`256` 或未设置时查图层颜色;
327
+ 6. viewer foreground 兜底。
328
+
329
+ 当 converter 同时暴露有效 ACI 和占位 RGB 值时,图层颜色优先使用 ACI。ACI `7` 会根据画布前景色显示:深色画布上为浅色,浅色画布上为深色。开启 `contrastMode: 'adaptive'` 后,和当前画布背景过近的颜色会被轻量调整,保证可读性。需要严格保留绘图颜色时,可以使用 `contrastMode: 'preserve'`。
330
+
331
+ 如果某个 converter 输出的是 BGR true-color 整数:
332
+
333
+ ```ts
334
+ new CadViewer({ canvasOptions: { trueColorByteOrder: 'bgr' } });
335
+ ```
336
+
337
+ ## 开发命令
338
+
339
+ ```bash
340
+ npm install
341
+ npm run dev # 运行 demo
342
+ npm run typecheck # TypeScript 检查
343
+ npm run build # 构建 library + demo
344
+ npm run preview # 预览构建后的 demo
345
+ ```
346
+
347
+ ## npm 发布命令
348
+
349
+ 1. 构建并检查包内容:
350
+
351
+ ```bash
352
+ npm run build:lib
353
+ npm run pack:dry
354
+ ```
355
+
356
+ 2. 发布:
357
+
358
+ ```bash
359
+ npm login
360
+ npm publish --access public
361
+ ```
362
+
363
+ 仓库内也提供:
364
+
365
+ ```bash
366
+ npm run release:npm
367
+ ```
368
+
369
+ ## Cloudflare Pages 发布命令
370
+
371
+ 使用 Wrangler Direct Upload:
372
+
373
+ ```bash
374
+ npm install
375
+ npm run build:demo
376
+ npx wrangler pages deploy dist-demo --project-name cad-viewer
377
+ ```
378
+
379
+ 或直接使用:
380
+
381
+ ```bash
382
+ npm run deploy:pages
383
+ ```
384
+
385
+ GitHub Actions 自动发布需要配置仓库 secrets:
386
+
387
+ ```text
388
+ CLOUDFLARE_API_TOKEN
389
+ CLOUDFLARE_ACCOUNT_ID
390
+ ```
391
+
392
+ 工作流文件在 `.github/workflows/pages.yml`。
393
+
394
+ ## 目录结构
395
+
396
+ ```text
397
+ src/
398
+ core/ 格式检测、颜色、几何、变换、统一类型
399
+ loaders/ DwgLoader、DxfLoader、DwfLoader、CadLoaderRegistry
400
+ viewer/ CadViewer 组件和 Canvas renderer
401
+ demo/ 专业 Vite demo UI
402
+ docs/ 中英文架构/格式文档
403
+ scripts/ clean 和 LibreDWG WASM 复制脚本
404
+ public/wasm/ demo 的 WASM 输出目录
405
+ ```
406
+
407
+ ## 许可证
408
+
409
+ AGPL-3.0-only。这是严格 copyleft 许可证:如果你分发修改版本,或通过网络提供修改版本服务,请认真审查源码公开义务。
410
+
411
+ 默认 DWG loader 依赖 `@mlightcad/libredwg-web`,该包为 GPL-3.0-only。若用于闭源商业产品,请替换为合规授权的 DWG parser/converter,并重新审查依赖许可证。