@file-viewer/geometry-engine 2.1.29 → 2.2.0
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.
- package/README.en.md +34 -14
- package/README.md +34 -14
- package/assets/occt-worker.js +83 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +158 -1
- package/package.json +5 -1
package/README.en.md
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
# @file-viewer/geometry-engine
|
|
2
2
|
|
|
3
|
-
Framework-neutral
|
|
3
|
+
Framework-neutral, UI-free geometry kernel for Flyfish File Viewer. It now decodes STEP / STP, IGES / IGS, and BREP directly in the browser: OCCT tessellation runs in a local worker, with no upload or server-side conversion. IFC and Rhino 3DM currently provide signature detection and accurate capability notices only; their full geometry renderers are not wired yet.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
7
|
-
inspectGeometryKernelFile,
|
|
8
|
-
formatGeometryKernelNotice,
|
|
9
|
-
} from '@file-viewer/geometry-engine'
|
|
6
|
+
import { importOcctGeometryFile } from '@file-viewer/geometry-engine'
|
|
10
7
|
|
|
11
|
-
const
|
|
12
|
-
|
|
8
|
+
const result = await importOcctGeometryFile(buffer, 'step', {
|
|
9
|
+
workerUrl: '/viewer-assets/wasm/model/occt-worker.js',
|
|
10
|
+
runtimeUrl: '/viewer-assets/wasm/model/occt-import-js.js',
|
|
11
|
+
wasmUrl: '/viewer-assets/wasm/model/occt-import-js.wasm',
|
|
12
|
+
params: {
|
|
13
|
+
linearUnit: 'millimeter',
|
|
14
|
+
linearDeflectionType: 'bounding_box_ratio',
|
|
15
|
+
linearDeflection: 0.001,
|
|
16
|
+
angularDeflection: 0.5,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
13
19
|
```
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
`result` contains the OCCT assembly hierarchy, meshes, normals, indices, and face colors for conversion into Three.js or another rendering layer. Parsing uses a one-shot worker by default and transfers both source bytes and mesh buffers. The worker is released after success, cancellation, failure, or timeout. `timeoutMs` defaults to 120 seconds. Set `useWorker: false` only when workers are genuinely unavailable, because that fallback parses on the main thread.
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
- `geometryKernelRoutes` documents the recommended WASM path: OpenCascade / OCCT for STEP, IGES, and BREP; `web-ifc` / That Open for IFC; McNeel `rhino3dm` for Rhino 3DM.
|
|
19
|
-
- `@file-viewer/renderer-3d` consumes this package for accurate conversion guidance without pulling CAD/BIM kernels into core or the default 3D install path.
|
|
20
|
-
- Full visual rendering can evolve here later, or split into focused packages such as `@file-viewer/geometry-occt`, `@file-viewer/geometry-ifc`, and `@file-viewer/geometry-3dm`.
|
|
23
|
+
## Offline Assets
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
The runtime never falls back to a CDN. Deploy these files with the viewer:
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
- `wasm/model/occt-worker.js`
|
|
28
|
+
- `wasm/model/occt-import-js.js`
|
|
29
|
+
- `wasm/model/occt-import-js.wasm`
|
|
30
|
+
- `wasm/model/LICENSE.occt.txt`
|
|
31
|
+
- `wasm/model/LICENSE.occt-import-js.txt`
|
|
32
|
+
|
|
33
|
+
`@file-viewer/vite-plugin`, full packages, and the repository build scripts copy these assets. A bare package integration must self-host them and provide `workerUrl`, `runtimeUrl`, and `wasmUrl`. For subpath deployments, asset domains, or controlled gateways, pass final absolute URLs or correctly prefixed URLs instead of relying on the site root.
|
|
34
|
+
|
|
35
|
+
A strict CSP must at least allow the asset origin in `worker-src`, `script-src`, and `connect-src` for the WASM fetch; some browsers also require `script-src 'wasm-unsafe-eval'` to compile WebAssembly. The classic worker loads the local runtime with `importScripts()`, so allowing only the WASM file is not sufficient.
|
|
36
|
+
|
|
37
|
+
## Boundaries
|
|
38
|
+
|
|
39
|
+
- `inspectGeometryKernelFile()` still offers prefix-only detection for common STEP / IGES / IFC / 3DM / BREP signatures.
|
|
40
|
+
- STEP / STP, IGES / IGS, and BREP use `occt-import-js` / OpenCascade and have a complete mesh-preview path.
|
|
41
|
+
- IFC still needs a dedicated `web-ifc` / That Open integration; 3DM still needs McNeel `rhino3dm`. Neither format is presented as fully previewable yet.
|
|
42
|
+
- The OCCT and `occt-import-js` license notices must ship with the offline assets.
|
|
43
|
+
|
|
44
|
+
Keeping this heavy path in a separate package lets `@file-viewer/core` stay small while worker, WASM, licensing, and real engineering-file regressions remain independently maintainable.
|
package/README.md
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
# @file-viewer/geometry-engine
|
|
2
2
|
|
|
3
|
-
Flyfish File Viewer
|
|
3
|
+
Flyfish File Viewer 的无 UI、框架无关几何内核包。它已经提供浏览器原生的 STEP / STP、IGES / IGS 和 BREP 解析:文件在本地 OCCT Worker 中完成三角化,不需要上传或服务端转换。IFC 和 Rhino 3DM 目前只提供格式识别与准确的能力提示,尚未接入完整几何渲染。
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
7
|
-
inspectGeometryKernelFile,
|
|
8
|
-
formatGeometryKernelNotice,
|
|
9
|
-
} from '@file-viewer/geometry-engine'
|
|
6
|
+
import { importOcctGeometryFile } from '@file-viewer/geometry-engine'
|
|
10
7
|
|
|
11
|
-
const
|
|
12
|
-
|
|
8
|
+
const result = await importOcctGeometryFile(buffer, 'step', {
|
|
9
|
+
workerUrl: '/viewer-assets/wasm/model/occt-worker.js',
|
|
10
|
+
runtimeUrl: '/viewer-assets/wasm/model/occt-import-js.js',
|
|
11
|
+
wasmUrl: '/viewer-assets/wasm/model/occt-import-js.wasm',
|
|
12
|
+
params: {
|
|
13
|
+
linearUnit: 'millimeter',
|
|
14
|
+
linearDeflectionType: 'bounding_box_ratio',
|
|
15
|
+
linearDeflection: 0.001,
|
|
16
|
+
angularDeflection: 0.5,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
13
19
|
```
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
`result` 包含 OCCT 输出的装配层级、网格、法线、索引和面颜色信息,可继续转换为 Three.js 或其他渲染对象。解析默认在一次性 Worker 中执行,文件字节和网格缓冲区使用 transferable 传递;完成、取消、报错或超时后 Worker 都会释放。`timeoutMs` 默认是 120 秒。只有 Worker 确实不可用时才建议设置 `useWorker: false`,因为回退会占用主线程。
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
- `geometryKernelRoutes` 明确每个格式的推荐 WASM 路线:STEP / IGES / BREP 走 OpenCascade / OCCT,IFC 走 `web-ifc` / That Open,3DM 走 McNeel `rhino3dm`。
|
|
19
|
-
- `@file-viewer/renderer-3d` 使用本包展示专业、准确的转换说明;core 和普通 3D 预览不会因此安装 CAD/BIM 几何内核。
|
|
20
|
-
- 后续完整可视渲染可以继续在这个包中分层演进,或拆出更细的 `@file-viewer/geometry-occt`、`@file-viewer/geometry-ifc`、`@file-viewer/geometry-3dm`。
|
|
23
|
+
## 离线资产
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
运行时不访问 CDN。部署时需要把下列文件放到同一套静态资源中:
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
- `wasm/model/occt-worker.js`
|
|
28
|
+
- `wasm/model/occt-import-js.js`
|
|
29
|
+
- `wasm/model/occt-import-js.wasm`
|
|
30
|
+
- `wasm/model/LICENSE.occt.txt`
|
|
31
|
+
- `wasm/model/LICENSE.occt-import-js.txt`
|
|
32
|
+
|
|
33
|
+
`@file-viewer/vite-plugin`、full 包和仓库构建脚本会复制这些资产。直接集成本包时,需要自行托管并传入 `workerUrl`、`runtimeUrl` 和 `wasmUrl`。应用部署在子路径、资源域名或受控网关下时,请传入最终可访问的绝对 URL 或带前缀 URL,不要依赖站点根路径。
|
|
34
|
+
|
|
35
|
+
严格 CSP 至少应允许资产来源出现在 `worker-src`、`script-src` 和用于获取 WASM 的 `connect-src` 中;部分浏览器还要求 `script-src 'wasm-unsafe-eval'` 才能编译 WebAssembly。Worker 使用本地 `importScripts()` 加载 runtime,因此不要只放行 WASM 而漏掉 runtime 脚本。
|
|
36
|
+
|
|
37
|
+
## 能力边界
|
|
38
|
+
|
|
39
|
+
- `inspectGeometryKernelFile()` 仍可只读取文件前缀,识别 STEP / IGES / IFC / 3DM / BREP 常见签名。
|
|
40
|
+
- STEP / STP、IGES / IGS 和 BREP 走 `occt-import-js` / OpenCascade,已经具备完整网格预览路径。
|
|
41
|
+
- IFC 仍需要独立的 `web-ifc` / That Open 集成;3DM 仍需要 McNeel `rhino3dm` 集成。当前不会把它们伪装成已支持预览。
|
|
42
|
+
- OCCT 与 `occt-import-js` 的许可证文件必须随离线资产一起分发。
|
|
43
|
+
|
|
44
|
+
把重型几何能力留在独立包中,可以让 `@file-viewer/core` 保持轻量,同时隔离 Worker、WASM、许可证和真实工程样本回归边界。
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let runtimePromise = null;
|
|
4
|
+
let runtimeKey = '';
|
|
5
|
+
|
|
6
|
+
const normalizeMeshArrays = result => {
|
|
7
|
+
const transfer = [];
|
|
8
|
+
for (const mesh of result?.meshes || []) {
|
|
9
|
+
const position = Float32Array.from(mesh?.attributes?.position?.array || []);
|
|
10
|
+
mesh.attributes = mesh.attributes || {};
|
|
11
|
+
mesh.attributes.position = { array: position };
|
|
12
|
+
transfer.push(position.buffer);
|
|
13
|
+
|
|
14
|
+
if (mesh.attributes.normal?.array) {
|
|
15
|
+
const normal = Float32Array.from(mesh.attributes.normal.array);
|
|
16
|
+
mesh.attributes.normal = { array: normal };
|
|
17
|
+
transfer.push(normal.buffer);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const index = Uint32Array.from(mesh?.index?.array || []);
|
|
21
|
+
mesh.index = { array: index };
|
|
22
|
+
transfer.push(index.buffer);
|
|
23
|
+
}
|
|
24
|
+
return transfer;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getRuntime = (runtimeUrl, wasmUrl) => {
|
|
28
|
+
const key = `${runtimeUrl}\n${wasmUrl}`;
|
|
29
|
+
if (runtimePromise && runtimeKey === key) {
|
|
30
|
+
return runtimePromise;
|
|
31
|
+
}
|
|
32
|
+
runtimeKey = key;
|
|
33
|
+
runtimePromise = Promise.resolve().then(() => {
|
|
34
|
+
if (typeof self.occtimportjs !== 'function') {
|
|
35
|
+
importScripts(runtimeUrl);
|
|
36
|
+
}
|
|
37
|
+
if (typeof self.occtimportjs !== 'function') {
|
|
38
|
+
throw new Error('The OCCT runtime did not expose occtimportjs().');
|
|
39
|
+
}
|
|
40
|
+
return self.occtimportjs({
|
|
41
|
+
locateFile(path) {
|
|
42
|
+
return path.endsWith('.wasm') ? wasmUrl : new URL(path, runtimeUrl).href;
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
runtimePromise.catch(() => {
|
|
47
|
+
runtimePromise = null;
|
|
48
|
+
runtimeKey = '';
|
|
49
|
+
});
|
|
50
|
+
return runtimePromise;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const readGeometry = (occt, format, bytes, params) => {
|
|
54
|
+
switch (format) {
|
|
55
|
+
case 'step':
|
|
56
|
+
return occt.ReadStepFile(bytes, params);
|
|
57
|
+
case 'iges':
|
|
58
|
+
return occt.ReadIgesFile(bytes, params);
|
|
59
|
+
case 'brep':
|
|
60
|
+
return occt.ReadBrepFile(bytes, params);
|
|
61
|
+
default:
|
|
62
|
+
throw new Error(`Unsupported OCCT geometry format: ${format}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
self.addEventListener('message', async event => {
|
|
67
|
+
const { id, format, buffer, params, runtimeUrl, wasmUrl } = event.data || {};
|
|
68
|
+
try {
|
|
69
|
+
if (!id || !(buffer instanceof ArrayBuffer) || !runtimeUrl || !wasmUrl) {
|
|
70
|
+
throw new Error('Invalid geometry worker request.');
|
|
71
|
+
}
|
|
72
|
+
const occt = await getRuntime(runtimeUrl, wasmUrl);
|
|
73
|
+
const result = readGeometry(occt, format, new Uint8Array(buffer), params || null);
|
|
74
|
+
const transfer = normalizeMeshArrays(result);
|
|
75
|
+
self.postMessage({ id, ok: true, result }, transfer);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
self.postMessage({
|
|
78
|
+
id,
|
|
79
|
+
ok: false,
|
|
80
|
+
error: error instanceof Error ? error.message : String(error),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,52 @@ export interface GeometryKernelInspection {
|
|
|
28
28
|
readonly signature?: string;
|
|
29
29
|
readonly warnings: readonly string[];
|
|
30
30
|
}
|
|
31
|
+
export type GeometryOcctFormat = 'step' | 'iges' | 'brep';
|
|
32
|
+
export type GeometryOcctLinearUnit = 'millimeter' | 'centimeter' | 'meter' | 'inch' | 'foot';
|
|
33
|
+
export interface GeometryOcctImportParams {
|
|
34
|
+
linearUnit?: GeometryOcctLinearUnit;
|
|
35
|
+
linearDeflectionType?: 'bounding_box_ratio' | 'absolute_value';
|
|
36
|
+
linearDeflection?: number;
|
|
37
|
+
angularDeflection?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface GeometryKernelArrayAttribute {
|
|
40
|
+
array: ArrayLike<number>;
|
|
41
|
+
}
|
|
42
|
+
export interface GeometryKernelFace {
|
|
43
|
+
first: number;
|
|
44
|
+
last: number;
|
|
45
|
+
color?: readonly number[] | null;
|
|
46
|
+
}
|
|
47
|
+
export interface GeometryKernelMesh {
|
|
48
|
+
name?: string;
|
|
49
|
+
color?: readonly number[];
|
|
50
|
+
brep_faces?: GeometryKernelFace[];
|
|
51
|
+
attributes: {
|
|
52
|
+
position: GeometryKernelArrayAttribute;
|
|
53
|
+
normal?: GeometryKernelArrayAttribute;
|
|
54
|
+
};
|
|
55
|
+
index: GeometryKernelArrayAttribute;
|
|
56
|
+
}
|
|
57
|
+
export interface GeometryKernelNode {
|
|
58
|
+
name?: string;
|
|
59
|
+
meshes: number[];
|
|
60
|
+
children: GeometryKernelNode[];
|
|
61
|
+
}
|
|
62
|
+
export interface GeometryKernelImportResult {
|
|
63
|
+
success: boolean;
|
|
64
|
+
root?: GeometryKernelNode;
|
|
65
|
+
meshes: GeometryKernelMesh[];
|
|
66
|
+
}
|
|
67
|
+
export interface ImportOcctGeometryOptions {
|
|
68
|
+
workerUrl?: string;
|
|
69
|
+
runtimeUrl?: string;
|
|
70
|
+
wasmUrl: string;
|
|
71
|
+
useWorker?: boolean;
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
params?: GeometryOcctImportParams;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}
|
|
76
|
+
export declare const importOcctGeometryFile: (input: ArrayBuffer | Uint8Array, type: string, options: ImportOcctGeometryOptions) => Promise<GeometryKernelImportResult>;
|
|
31
77
|
export declare const geometryKernelRoutes: readonly GeometryKernelRoute[];
|
|
32
78
|
export declare const normalizeGeometryKernelFormat: (type?: string) => GeometryKernelFormat | undefined;
|
|
33
79
|
export declare const getGeometryKernelRoute: (type?: string) => GeometryKernelRoute | undefined;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
+
const DEFAULT_OCCT_IMPORT_TIMEOUT_MS = 120000;
|
|
2
|
+
const occtRuntimePromises = new Map();
|
|
3
|
+
let geometryWorkerRequestId = 0;
|
|
4
|
+
const createGeometryAbortError = (signal) => {
|
|
5
|
+
if ((signal === null || signal === void 0 ? void 0 : signal.reason) instanceof Error) {
|
|
6
|
+
return signal.reason;
|
|
7
|
+
}
|
|
8
|
+
return new DOMException('Geometry import was aborted.', 'AbortError');
|
|
9
|
+
};
|
|
10
|
+
const toOwnedBytes = (input) => {
|
|
11
|
+
return input instanceof Uint8Array
|
|
12
|
+
? input.slice()
|
|
13
|
+
: new Uint8Array(input.slice(0));
|
|
14
|
+
};
|
|
15
|
+
const normalizeOcctFormat = (type) => {
|
|
16
|
+
const format = normalizeGeometryKernelFormat(type);
|
|
17
|
+
if (format === 'step' || format === 'iges' || format === 'brep') {
|
|
18
|
+
return format;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`No OpenCascade import route is available for ${String(type || 'unknown').toUpperCase()}.`);
|
|
21
|
+
};
|
|
22
|
+
const validateOcctResult = (value, format) => {
|
|
23
|
+
const result = value;
|
|
24
|
+
if (!(result === null || result === void 0 ? void 0 : result.success)) {
|
|
25
|
+
throw new Error(`${format.toUpperCase()} geometry parsing failed. The file may be empty, damaged, or unsupported by this OpenCascade build.`);
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(result.meshes) || result.meshes.length === 0) {
|
|
28
|
+
throw new Error(`${format.toUpperCase()} geometry parsing completed without any renderable meshes.`);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
success: true,
|
|
32
|
+
root: result.root,
|
|
33
|
+
meshes: result.meshes,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const getOcctRuntime = (wasmUrl) => {
|
|
37
|
+
let promise = occtRuntimePromises.get(wasmUrl);
|
|
38
|
+
if (!promise) {
|
|
39
|
+
promise = import('occt-import-js')
|
|
40
|
+
.then(module => module.default({
|
|
41
|
+
locateFile(path) {
|
|
42
|
+
return path.endsWith('.wasm') ? wasmUrl : path;
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
occtRuntimePromises.set(wasmUrl, promise);
|
|
46
|
+
promise.catch(() => occtRuntimePromises.delete(wasmUrl));
|
|
47
|
+
}
|
|
48
|
+
return promise;
|
|
49
|
+
};
|
|
50
|
+
const readOcctGeometry = (occt, format, bytes, params) => {
|
|
51
|
+
switch (format) {
|
|
52
|
+
case 'step':
|
|
53
|
+
return occt.ReadStepFile(bytes, params || null);
|
|
54
|
+
case 'iges':
|
|
55
|
+
return occt.ReadIgesFile(bytes, params || null);
|
|
56
|
+
case 'brep':
|
|
57
|
+
return occt.ReadBrepFile(bytes, params || null);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const importOcctGeometryOnMainThread = async (bytes, format, options) => {
|
|
61
|
+
var _a, _b, _c;
|
|
62
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
|
|
63
|
+
throw createGeometryAbortError(options.signal);
|
|
64
|
+
}
|
|
65
|
+
const runtime = await getOcctRuntime(options.wasmUrl);
|
|
66
|
+
if ((_b = options.signal) === null || _b === void 0 ? void 0 : _b.aborted) {
|
|
67
|
+
throw createGeometryAbortError(options.signal);
|
|
68
|
+
}
|
|
69
|
+
const result = readOcctGeometry(runtime, format, bytes, options.params);
|
|
70
|
+
if ((_c = options.signal) === null || _c === void 0 ? void 0 : _c.aborted) {
|
|
71
|
+
throw createGeometryAbortError(options.signal);
|
|
72
|
+
}
|
|
73
|
+
return validateOcctResult(result, format);
|
|
74
|
+
};
|
|
75
|
+
const importOcctGeometryInWorker = (bytes, format, options) => new Promise((resolve, reject) => {
|
|
76
|
+
var _a, _b;
|
|
77
|
+
if (!options.workerUrl || !options.runtimeUrl) {
|
|
78
|
+
reject(new Error('The OCCT worker and runtime URLs are required for worker-based geometry import.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if ((_a = options.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
|
|
82
|
+
reject(createGeometryAbortError(options.signal));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const worker = new Worker(options.workerUrl);
|
|
86
|
+
const requestId = `geometry-${Date.now()}-${++geometryWorkerRequestId}`;
|
|
87
|
+
const timeoutMs = Math.max(1000, options.timeoutMs || DEFAULT_OCCT_IMPORT_TIMEOUT_MS);
|
|
88
|
+
let settled = false;
|
|
89
|
+
const cleanup = () => {
|
|
90
|
+
var _a;
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
(_a = options.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener('abort', onAbort);
|
|
93
|
+
worker.removeEventListener('message', onMessage);
|
|
94
|
+
worker.removeEventListener('error', onError);
|
|
95
|
+
worker.terminate();
|
|
96
|
+
};
|
|
97
|
+
const complete = (action) => {
|
|
98
|
+
if (settled) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
settled = true;
|
|
102
|
+
cleanup();
|
|
103
|
+
action();
|
|
104
|
+
};
|
|
105
|
+
const onAbort = () => complete(() => reject(createGeometryAbortError(options.signal)));
|
|
106
|
+
const onError = (event) => complete(() => reject(new Error(event.message || 'The OCCT geometry worker failed to load.')));
|
|
107
|
+
const onMessage = (event) => {
|
|
108
|
+
const message = event.data || {};
|
|
109
|
+
if (message.id !== requestId) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!message.ok) {
|
|
113
|
+
complete(() => reject(new Error(message.error || `${format.toUpperCase()} geometry parsing failed.`)));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
complete(() => {
|
|
117
|
+
try {
|
|
118
|
+
resolve(validateOcctResult(message.result, format));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
reject(error);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const timeout = setTimeout(() => complete(() => reject(new Error(`${format.toUpperCase()} geometry parsing timed out after ${Math.round(timeoutMs / 1000)} seconds.`))), timeoutMs);
|
|
126
|
+
(_b = options.signal) === null || _b === void 0 ? void 0 : _b.addEventListener('abort', onAbort, { once: true });
|
|
127
|
+
worker.addEventListener('message', onMessage);
|
|
128
|
+
worker.addEventListener('error', onError);
|
|
129
|
+
const transferableBuffer = bytes.buffer;
|
|
130
|
+
worker.postMessage({
|
|
131
|
+
id: requestId,
|
|
132
|
+
format,
|
|
133
|
+
buffer: transferableBuffer,
|
|
134
|
+
params: options.params || null,
|
|
135
|
+
runtimeUrl: options.runtimeUrl,
|
|
136
|
+
wasmUrl: options.wasmUrl,
|
|
137
|
+
}, [transferableBuffer]);
|
|
138
|
+
});
|
|
139
|
+
export const importOcctGeometryFile = async (input, type, options) => {
|
|
140
|
+
const format = normalizeOcctFormat(type);
|
|
141
|
+
const bytes = toOwnedBytes(input);
|
|
142
|
+
const canUseWorker = options.useWorker !== false &&
|
|
143
|
+
typeof Worker === 'function' &&
|
|
144
|
+
Boolean(options.workerUrl && options.runtimeUrl);
|
|
145
|
+
return canUseWorker
|
|
146
|
+
? importOcctGeometryInWorker(bytes, format, options)
|
|
147
|
+
: importOcctGeometryOnMainThread(bytes, format, options);
|
|
148
|
+
};
|
|
1
149
|
const textDecoder = typeof TextDecoder === 'function'
|
|
2
150
|
? new TextDecoder('utf-8', { fatal: false })
|
|
3
151
|
: undefined;
|
|
@@ -35,7 +183,7 @@ export const geometryKernelRoutes = [
|
|
|
35
183
|
},
|
|
36
184
|
],
|
|
37
185
|
serverConversionTargets: ['glb', 'gltf', 'stl', 'obj'],
|
|
38
|
-
licenseNotes: ['OCCT
|
|
186
|
+
licenseNotes: ['Ship the OCCT and occt-import-js license files beside the self-hosted runtime and WASM assets.'],
|
|
39
187
|
},
|
|
40
188
|
{
|
|
41
189
|
format: 'iges',
|
|
@@ -187,6 +335,15 @@ export const formatGeometryKernelNotice = (type, locale = 'zh-CN') => {
|
|
|
187
335
|
const formatLabel = (route === null || route === void 0 ? void 0 : route.label) || String(type || 'Engineering model').toUpperCase();
|
|
188
336
|
const engines = (route === null || route === void 0 ? void 0 : route.recommended.map(item => item.packageName).join(' / ')) || 'OpenCascade / web-ifc / rhino3dm';
|
|
189
337
|
const targets = (route === null || route === void 0 ? void 0 : route.serverConversionTargets.join(' / ')) || 'GLB / GLTF';
|
|
338
|
+
const hasNativeOcctPreview = (route === null || route === void 0 ? void 0 : route.format) === 'step' ||
|
|
339
|
+
(route === null || route === void 0 ? void 0 : route.format) === 'iges' ||
|
|
340
|
+
(route === null || route === void 0 ? void 0 : route.format) === 'brep';
|
|
341
|
+
if (hasNativeOcctPreview) {
|
|
342
|
+
if (locale === 'en-US') {
|
|
343
|
+
return `${formatLabel} is previewed locally with the dedicated OCCT Worker/WASM path. The file stays in the browser; configure options.model only when the self-hosted assets use a custom deployment path.`;
|
|
344
|
+
}
|
|
345
|
+
return `${formatLabel} 已通过独立 OCCT Worker/WASM 在浏览器本地完成预览,文件无需上传;仅当离线资源部署在自定义目录时,才需要覆盖 options.model 路径。`;
|
|
346
|
+
}
|
|
190
347
|
if (locale === 'en-US') {
|
|
191
348
|
return `${formatLabel} requires a dedicated WebAssembly geometry kernel (${engines}) for full browser preview. Flyfish Viewer keeps these heavy runtimes outside core and the default 3D renderer install path; use the dedicated geometry engine route or convert the model to ${targets} in a private pipeline.`;
|
|
192
349
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/geometry-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Framework-neutral geometry kernel boundary package for STEP, IGES, IFC, 3DM, and BREP preview routes in Flyfish File Viewer.",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"dist",
|
|
55
|
+
"assets",
|
|
55
56
|
"README.md",
|
|
56
57
|
"README.en.md",
|
|
57
58
|
"LICENSE"
|
|
@@ -60,6 +61,9 @@
|
|
|
60
61
|
"typescript": "^6.0.3"
|
|
61
62
|
},
|
|
62
63
|
"license": "Apache-2.0",
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"occt-import-js": "0.0.23"
|
|
66
|
+
},
|
|
63
67
|
"scripts": {
|
|
64
68
|
"build": "tsc -b tsconfig.json",
|
|
65
69
|
"type-check": "tsc -b tsconfig.json"
|