occt-gltf-addon 0.1.0 → 0.1.2
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.md +24 -8
- package/index.js +4 -3
- package/package.json +5 -3
- package/scripts/install.js +28 -8
- package/smoke_test.js +152 -0
package/README.md
CHANGED
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
Node.js N-API 原生扩展:使用 OpenCascade (OCCT) 将 **STEP (.step/.stp)** 转换为 **glTF 2.0 (GLB)**。
|
|
4
4
|
|
|
5
|
-
####
|
|
5
|
+
#### 安装
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **Linux x64(Ubuntu 等)**:默认会安装预编译包 `occt-gltf-addon-linux-x64`(包含 OCCT runtime),一般 **不需要系统安装 OCCT**。
|
|
8
|
+
- **macOS / 其他平台**:默认会在 `npm install` 时本地编译(`cmake-js`),需要 OCCT 开发文件(headers + libs)。
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
- **CMake + C++ 编译器 + Python3**
|
|
11
|
-
- **OCCT 安装**(共享库 + headers)
|
|
12
|
-
|
|
13
|
-
通过环境变量指定 OCCT 安装前缀:
|
|
10
|
+
本地编译时通过环境变量指定 OCCT 安装前缀:
|
|
14
11
|
|
|
15
12
|
```bash
|
|
16
13
|
export OCCT_ROOT=/path/to/occt/prefix
|
|
17
14
|
npm i occt-gltf-addon
|
|
18
15
|
```
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
如需在 linux-x64 **强制源码编译**(不推荐,且需要你自己装 OCCT):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
OCCT_GLTF_ADDON_FORCE_BUILD=1 OCCT_ROOT=/path/to/occt/prefix npm i occt-gltf-addon
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
可选(仅在 **源码编译** 时用于启用 Draco 支持,需要开发包):
|
|
21
24
|
- `brew install draco` / `apt-get install libdraco-dev`
|
|
22
25
|
|
|
23
26
|
如需跳过编译(不推荐,运行时会找不到 `.node`):
|
|
@@ -55,6 +58,19 @@ await convertSTEPToGLTF({
|
|
|
55
58
|
|
|
56
59
|
---
|
|
57
60
|
|
|
61
|
+
#### Smoke Test(安装后快速验证)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm run smoke -- /abs/path/to/model.step /tmp/occt-gltf-out --draco --zup
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
参数:
|
|
68
|
+
- `--draco`:启用 Draco 输出(可选)
|
|
69
|
+
- `--zup`:导出 Z-up(用于 three.js Z-up 世界;可选)
|
|
70
|
+
- `--quiet`:静默日志(可选)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
58
74
|
#### 关键选项说明(简版)
|
|
59
75
|
|
|
60
76
|
- **tessellation.linearDeflection / angularDeflection**:B-Rep 三角化精度
|
package/index.js
CHANGED
|
@@ -40,9 +40,10 @@ const msg = [
|
|
|
40
40
|
'[occt-gltf-addon] Failed to load native addon (.node).',
|
|
41
41
|
`Tried: ${candidates.map((p) => JSON.stringify(p)).join(', ')}`,
|
|
42
42
|
'',
|
|
43
|
-
'If you installed from npm
|
|
44
|
-
'-
|
|
45
|
-
|
|
43
|
+
'If you installed from npm:',
|
|
44
|
+
'- On linux-x64: you should get the prebuilt optional dependency `occt-gltf-addon-linux-x64` (no system OCCT required).',
|
|
45
|
+
' Ensure optional deps are enabled (do NOT use `--no-optional`).',
|
|
46
|
+
'- Otherwise / if building from source: ensure CMake + C++ toolchain are installed, and OCCT_ROOT points to your OCCT install prefix',
|
|
46
47
|
'',
|
|
47
48
|
"You can try: 'npm rebuild occt-gltf-addon' (with OCCT_ROOT exported).",
|
|
48
49
|
].join('\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "occt-gltf-addon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "STEP (.step/.stp) to glTF (GLB) converter using OpenCascade (OCCT) via a Node.js N-API addon",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"CMakeLists.txt",
|
|
47
47
|
"src/**",
|
|
48
48
|
"scripts/**",
|
|
49
|
-
"example.js"
|
|
49
|
+
"example.js",
|
|
50
|
+
"smoke_test.js"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
53
|
"install": "node scripts/install.js",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"clean": "rm -rf build",
|
|
55
56
|
"rebuild": "npm run clean && npm run build",
|
|
56
57
|
"example": "node example.js",
|
|
58
|
+
"smoke": "node smoke_test.js",
|
|
57
59
|
"test": "node -e \"console.log('no tests')\"",
|
|
58
60
|
|
|
59
61
|
"docker:build:linux-amd64": "bash docker/build-linux-amd64.sh",
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
"docker:image:linux-amd64:nocache": "docker buildx build --no-cache --platform linux/amd64 -f docker/Dockerfile.linux-amd64 -t occt-addon-build:linux-amd64 ../.."
|
|
62
64
|
},
|
|
63
65
|
"optionalDependencies": {
|
|
64
|
-
"occt-gltf-addon-linux-x64": "0.1.
|
|
66
|
+
"occt-gltf-addon-linux-x64": "0.1.2"
|
|
65
67
|
},
|
|
66
68
|
"dependencies": {
|
|
67
69
|
"cmake-js": "^7.3.1",
|
package/scripts/install.js
CHANGED
|
@@ -43,8 +43,19 @@ function main() {
|
|
|
43
43
|
note(`Using prebuilt binary from ${prebuiltName}; skipping local build.`);
|
|
44
44
|
return;
|
|
45
45
|
} catch (e) {
|
|
46
|
-
// Prebuilt missing or not loadable (e.g. GLIBC mismatch).
|
|
47
|
-
|
|
46
|
+
// Prebuilt missing or not loadable (e.g. GLIBC mismatch).
|
|
47
|
+
// On Linux, the default expectation is to use the prebuilt package (no system OCCT required).
|
|
48
|
+
// Only fall back to local build when the user explicitly opts in.
|
|
49
|
+
const canBuildFromSource =
|
|
50
|
+
process.env.OCCT_GLTF_ADDON_FORCE_BUILD === '1' || !!process.env.OCCT_ROOT;
|
|
51
|
+
if (!canBuildFromSource) {
|
|
52
|
+
note(`Prebuilt package ${prebuiltName} not usable, and OCCT_ROOT is not set.`);
|
|
53
|
+
note('This package is intended to work on Linux via the prebuilt optional dependency.');
|
|
54
|
+
note('Fix: ensure optional dependencies are installed (do NOT use --no-optional).');
|
|
55
|
+
note('Or: set OCCT_ROOT and re-install to build from source (or set OCCT_GLTF_ADDON_FORCE_BUILD=1).');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
note(`Prebuilt package ${prebuiltName} not usable; attempting local build from source...`);
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
|
|
@@ -53,15 +64,24 @@ function main() {
|
|
|
53
64
|
note("OCCT_ROOT is not set. If build fails, set OCCT_ROOT to your OCCT install prefix (e.g. '/opt/homebrew', '/usr', '/usr/local', or your custom install).");
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
note('Building native addon with cmake-js...');
|
|
68
|
+
|
|
69
|
+
// Locate cmake-js CLI in a way that works with npm hoisting.
|
|
70
|
+
let cmakeJsCli = null;
|
|
71
|
+
try {
|
|
72
|
+
// eslint-disable-next-line global-require
|
|
73
|
+
cmakeJsCli = require.resolve('cmake-js/bin/cmake-js');
|
|
74
|
+
} catch (e) {
|
|
75
|
+
cmakeJsCli = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!cmakeJsCli || !fs.existsSync(cmakeJsCli)) {
|
|
79
|
+
note('cmake-js is not available (dependency missing).');
|
|
80
|
+
note("Try: 'npm install' again.");
|
|
60
81
|
process.exit(1);
|
|
61
82
|
}
|
|
62
83
|
|
|
63
|
-
|
|
64
|
-
run(cmakeJsBin, ['compile', '-O', 'build'], { cwd: pkgRoot, env: process.env });
|
|
84
|
+
run(process.execPath, [cmakeJsCli, 'compile', '-O', 'build'], { cwd: pkgRoot, env: process.env });
|
|
65
85
|
}
|
|
66
86
|
|
|
67
87
|
try {
|
package/smoke_test.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function usage() {
|
|
7
|
+
console.error('Usage: node smoke_test.js <input.step> [outputDir] [--draco] [--zup] [--quiet]');
|
|
8
|
+
console.error('');
|
|
9
|
+
console.error('Notes:');
|
|
10
|
+
console.error('- Requires OCCT runtime libraries to be available at runtime (LD_LIBRARY_PATH / rpath / system install).');
|
|
11
|
+
console.error('- If you use three.js Z-up world (THREE.Object3D.DEFAULT_UP.set(0,0,1)), pass --zup.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readU32LE(buf, off) {
|
|
15
|
+
return buf.readUInt32LE(off);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseGlbHeaderAndJson(glbPath) {
|
|
19
|
+
const fd = fs.openSync(glbPath, 'r');
|
|
20
|
+
try {
|
|
21
|
+
const header = Buffer.alloc(12);
|
|
22
|
+
fs.readSync(fd, header, 0, 12, 0);
|
|
23
|
+
const magic = header.toString('ascii', 0, 4);
|
|
24
|
+
const version = readU32LE(header, 4);
|
|
25
|
+
|
|
26
|
+
if (magic !== 'glTF' || version !== 2) {
|
|
27
|
+
throw new Error(`Not a glTF 2.0 GLB: magic=${magic}, version=${version}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const chunk0 = Buffer.alloc(8);
|
|
31
|
+
fs.readSync(fd, chunk0, 0, 8, 12);
|
|
32
|
+
const jsonLen = readU32LE(chunk0, 0);
|
|
33
|
+
const jsonType = readU32LE(chunk0, 4);
|
|
34
|
+
if (jsonType !== 0x4e4f534a) {
|
|
35
|
+
throw new Error(`Invalid JSON chunk type: 0x${jsonType.toString(16)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const jsonBuf = Buffer.alloc(jsonLen);
|
|
39
|
+
fs.readSync(fd, jsonBuf, 0, jsonLen, 20);
|
|
40
|
+
const jsonStr = jsonBuf.toString('utf8').replace(/\s+$/g, '');
|
|
41
|
+
const gltf = JSON.parse(jsonStr);
|
|
42
|
+
return { gltf };
|
|
43
|
+
} finally {
|
|
44
|
+
fs.closeSync(fd);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function main() {
|
|
49
|
+
const argv = process.argv.slice(2);
|
|
50
|
+
const flags = new Set(argv.filter((a) => a.startsWith('--')));
|
|
51
|
+
const positional = argv.filter((a) => !a.startsWith('--'));
|
|
52
|
+
|
|
53
|
+
const inputPath = positional[0];
|
|
54
|
+
if (!inputPath) {
|
|
55
|
+
usage();
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const outputDir = positional[1] || fs.mkdtempSync(path.join(os.tmpdir(), 'occt-gltf-addon-'));
|
|
60
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
const enableDraco = flags.has('--draco');
|
|
63
|
+
const zUp = flags.has('--zup');
|
|
64
|
+
const quiet = flags.has('--quiet');
|
|
65
|
+
|
|
66
|
+
const baseName = path.parse(inputPath).name || 'out';
|
|
67
|
+
const outMin = path.join(outputDir, `${baseName}.min.glb`);
|
|
68
|
+
const outNormal = path.join(outputDir, `${baseName}.glb`);
|
|
69
|
+
|
|
70
|
+
// Require the package under test.
|
|
71
|
+
// In a consumer project, this should be: require('occt-gltf-addon')
|
|
72
|
+
// Here we use '.' so it works when running inside the package directory too.
|
|
73
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
74
|
+
const { convertSTEPToGLTF } = require('.');
|
|
75
|
+
|
|
76
|
+
const outputOpts = {
|
|
77
|
+
unit: 'm',
|
|
78
|
+
bakeTransforms: true,
|
|
79
|
+
center: zUp ? 'xy' : 'xz',
|
|
80
|
+
zUp,
|
|
81
|
+
draco: enableDraco
|
|
82
|
+
? { enabled: true, compressionLevel: 7, quantizationBitsPosition: 14, quantizationBitsNormal: 10 }
|
|
83
|
+
: { enabled: false },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const variants = [
|
|
87
|
+
{
|
|
88
|
+
outputPath: outMin,
|
|
89
|
+
tessellation: { linearDeflection: 5, angularDeflection: Math.PI / 3 },
|
|
90
|
+
filter: { minBBoxDiagonal: 60 },
|
|
91
|
+
output: outputOpts,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
outputPath: outNormal,
|
|
95
|
+
tessellation: { linearDeflection: 0.2, angularDeflection: 0.6, smoothNormals: true, normalCreaseAngle: Math.PI / 3 },
|
|
96
|
+
output: outputOpts,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
console.log('[smoke] input:', inputPath);
|
|
101
|
+
console.log('[smoke] outputDir:', outputDir);
|
|
102
|
+
console.log('[smoke] draco:', enableDraco);
|
|
103
|
+
console.log('[smoke] zUp:', zUp);
|
|
104
|
+
|
|
105
|
+
await convertSTEPToGLTF({
|
|
106
|
+
inputPath,
|
|
107
|
+
logLevel: quiet ? 'quiet' : 'info',
|
|
108
|
+
variants,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
for (const p of [outMin, outNormal]) {
|
|
112
|
+
if (!fs.existsSync(p)) {
|
|
113
|
+
throw new Error(`Missing output file: ${p}`);
|
|
114
|
+
}
|
|
115
|
+
const st = fs.statSync(p);
|
|
116
|
+
if (st.size < 32) {
|
|
117
|
+
throw new Error(`Output file too small: ${p} (${st.size} bytes)`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { gltf } = parseGlbHeaderAndJson(p);
|
|
121
|
+
const nodes = Array.isArray(gltf.nodes) ? gltf.nodes.length : 0;
|
|
122
|
+
const meshes = Array.isArray(gltf.meshes) ? gltf.meshes.length : 0;
|
|
123
|
+
const nodesWithMatrix = Array.isArray(gltf.nodes)
|
|
124
|
+
? gltf.nodes.reduce((n, x) => n + (x && Object.prototype.hasOwnProperty.call(x, 'matrix') ? 1 : 0), 0)
|
|
125
|
+
: 0;
|
|
126
|
+
|
|
127
|
+
if (nodes === 0 || meshes === 0) {
|
|
128
|
+
throw new Error(`Invalid glTF content in ${p}: nodes=${nodes}, meshes=${meshes}`);
|
|
129
|
+
}
|
|
130
|
+
if (nodesWithMatrix !== 0) {
|
|
131
|
+
throw new Error(`Expected baked transforms (no node.matrix). Got nodesWithMatrix=${nodesWithMatrix} in ${p}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (enableDraco) {
|
|
135
|
+
const used = gltf.extensionsUsed || [];
|
|
136
|
+
if (!used.includes('KHR_draco_mesh_compression')) {
|
|
137
|
+
throw new Error(`Expected Draco extensionUsed in ${p}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('[smoke] OK:', p, `(${st.size} bytes)`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log('[smoke] PASS');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch((e) => {
|
|
148
|
+
console.error('[smoke] FAIL');
|
|
149
|
+
console.error(e && (e.stack || e.message || e));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
});
|
|
152
|
+
|