occt-gltf-addon 0.1.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/CMakeLists.txt +183 -0
- package/LICENSE +22 -0
- package/README.md +74 -0
- package/example.js +81 -0
- package/index.d.ts +71 -0
- package/index.js +52 -0
- package/package.json +71 -0
- package/scripts/install.js +79 -0
- package/src/addon.cpp +504 -0
- package/src/convert_worker.cpp +474 -0
- package/src/convert_worker.h +94 -0
- package/src/gltf_writer.cpp +367 -0
- package/src/gltf_writer.h +24 -0
- package/src/gltf_writer_scene.cpp +545 -0
- package/src/mesh_types.h +78 -0
- package/src/occt_convert.cpp +313 -0
- package/src/occt_convert.h +40 -0
- package/src/occt_convert_xde.cpp +760 -0
package/CMakeLists.txt
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.16)
|
|
2
|
+
|
|
3
|
+
project(occt_gltf_addon LANGUAGES CXX)
|
|
4
|
+
|
|
5
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
6
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
7
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
8
|
+
|
|
9
|
+
# --- Node / N-API via cmake-js ---
|
|
10
|
+
# cmake-js provides:
|
|
11
|
+
# - CMAKE_JS_INC: include dirs for Node headers
|
|
12
|
+
# - CMAKE_JS_LIB: link library for Node
|
|
13
|
+
if(NOT DEFINED CMAKE_JS_INC OR NOT DEFINED CMAKE_JS_LIB)
|
|
14
|
+
message(FATAL_ERROR "This project is intended to be built with cmake-js. Run: npm run build")
|
|
15
|
+
endif()
|
|
16
|
+
|
|
17
|
+
# node-addon-api include dir (header-only)
|
|
18
|
+
execute_process(
|
|
19
|
+
COMMAND node -p "require('node-addon-api').include"
|
|
20
|
+
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
|
|
21
|
+
OUTPUT_VARIABLE NODE_ADDON_API_DIR
|
|
22
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
23
|
+
)
|
|
24
|
+
# node-addon-api returns a quoted string; strip quotes if present
|
|
25
|
+
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
|
|
26
|
+
|
|
27
|
+
# --- OCCT ---
|
|
28
|
+
# You can override by exporting OCCT_ROOT=/path/to/your/occt/install-prefix
|
|
29
|
+
if(DEFINED ENV{OCCT_ROOT} AND NOT "$ENV{OCCT_ROOT}" STREQUAL "")
|
|
30
|
+
set(OCCT_ROOT "$ENV{OCCT_ROOT}" CACHE PATH "OCCT install prefix" FORCE)
|
|
31
|
+
else()
|
|
32
|
+
if(APPLE)
|
|
33
|
+
set(OCCT_ROOT "/opt/homebrew" CACHE PATH "OCCT install prefix (e.g. /opt/homebrew or /usr/local)")
|
|
34
|
+
else()
|
|
35
|
+
set(OCCT_ROOT "/usr" CACHE PATH "OCCT install prefix (e.g. /usr, /usr/local, or your custom install)")
|
|
36
|
+
endif()
|
|
37
|
+
endif()
|
|
38
|
+
|
|
39
|
+
find_path(OCCT_INCLUDE_DIR
|
|
40
|
+
NAMES Standard.hxx
|
|
41
|
+
PATHS
|
|
42
|
+
"${OCCT_ROOT}/include/opencascade"
|
|
43
|
+
"${OCCT_ROOT}/include"
|
|
44
|
+
NO_DEFAULT_PATH
|
|
45
|
+
)
|
|
46
|
+
if(NOT OCCT_INCLUDE_DIR)
|
|
47
|
+
message(FATAL_ERROR "OCCT headers not found. Expected Standard.hxx under ${OCCT_ROOT}/include/opencascade. Set -DOCCT_ROOT=...")
|
|
48
|
+
endif()
|
|
49
|
+
|
|
50
|
+
set(_OCCT_LIBDIRS
|
|
51
|
+
"${OCCT_ROOT}/lib"
|
|
52
|
+
"${OCCT_ROOT}/lib64"
|
|
53
|
+
"${OCCT_ROOT}/lib/x86_64-linux-gnu"
|
|
54
|
+
"${OCCT_ROOT}/lib/aarch64-linux-gnu"
|
|
55
|
+
"${OCCT_ROOT}/lib/arm64-linux-gnu"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
function(_occt_find_lib outvar name)
|
|
59
|
+
find_library(${outvar}
|
|
60
|
+
NAMES ${name}
|
|
61
|
+
PATHS ${_OCCT_LIBDIRS}
|
|
62
|
+
NO_DEFAULT_PATH
|
|
63
|
+
)
|
|
64
|
+
if(NOT ${outvar})
|
|
65
|
+
message(FATAL_ERROR "OCCT library ${name} not found under ${OCCT_ROOT}. Set OCCT_ROOT=/path/to/occt.")
|
|
66
|
+
endif()
|
|
67
|
+
endfunction()
|
|
68
|
+
|
|
69
|
+
# Find one of multiple possible toolkit names (OCCT version differences).
|
|
70
|
+
function(_occt_find_lib_any outvar)
|
|
71
|
+
set(_names ${ARGN})
|
|
72
|
+
find_library(${outvar}
|
|
73
|
+
NAMES ${_names}
|
|
74
|
+
PATHS ${_OCCT_LIBDIRS}
|
|
75
|
+
NO_DEFAULT_PATH
|
|
76
|
+
)
|
|
77
|
+
if(NOT ${outvar})
|
|
78
|
+
string(JOIN ", " _namesStr ${_names})
|
|
79
|
+
message(FATAL_ERROR "OCCT library not found. Tried: ${_namesStr} under ${OCCT_ROOT}. Set OCCT_ROOT=/path/to/occt.")
|
|
80
|
+
endif()
|
|
81
|
+
endfunction()
|
|
82
|
+
|
|
83
|
+
# Minimal set for STEP reading + meshing + topology
|
|
84
|
+
_occt_find_lib(OCCT_TKernel TKernel)
|
|
85
|
+
_occt_find_lib(OCCT_TKMath TKMath)
|
|
86
|
+
_occt_find_lib(OCCT_TKG2d TKG2d)
|
|
87
|
+
_occt_find_lib(OCCT_TKG3d TKG3d)
|
|
88
|
+
_occt_find_lib(OCCT_TKGeomBase TKGeomBase)
|
|
89
|
+
_occt_find_lib(OCCT_TKBRep TKBRep)
|
|
90
|
+
_occt_find_lib(OCCT_TKTopAlgo TKTopAlgo)
|
|
91
|
+
_occt_find_lib(OCCT_TKMesh TKMesh)
|
|
92
|
+
_occt_find_lib(OCCT_TKXSBase TKXSBase)
|
|
93
|
+
|
|
94
|
+
# OCCT 8.x puts STEPControl_Reader into TKDESTEP; older OCCT uses TKSTEP (+ STEPBase/STEPAttr).
|
|
95
|
+
_occt_find_lib_any(OCCT_TKSTEP_LIKE TKDESTEP TKSTEP)
|
|
96
|
+
|
|
97
|
+
# XDE / OCAF toolkits for STEPCAFControl_Reader + document/labels/names
|
|
98
|
+
_occt_find_lib(OCCT_TKCDF TKCDF)
|
|
99
|
+
_occt_find_lib(OCCT_TKLCAF TKLCAF)
|
|
100
|
+
_occt_find_lib(OCCT_TKCAF TKCAF)
|
|
101
|
+
_occt_find_lib(OCCT_TKXCAF TKXCAF)
|
|
102
|
+
|
|
103
|
+
add_library(${PROJECT_NAME} SHARED
|
|
104
|
+
src/addon.cpp
|
|
105
|
+
src/convert_worker.cpp
|
|
106
|
+
src/occt_convert_xde.cpp
|
|
107
|
+
src/gltf_writer_scene.cpp
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# --- Optional: Draco (KHR_draco_mesh_compression) ---
|
|
111
|
+
option(OCCT_GLTF_ADDON_ENABLE_DRACO "Enable Draco mesh compression support" ON)
|
|
112
|
+
if(OCCT_GLTF_ADDON_ENABLE_DRACO)
|
|
113
|
+
if(DEFINED ENV{DRACO_ROOT} AND NOT "$ENV{DRACO_ROOT}" STREQUAL "")
|
|
114
|
+
set(DRACO_ROOT "$ENV{DRACO_ROOT}" CACHE PATH "Draco install prefix" FORCE)
|
|
115
|
+
else()
|
|
116
|
+
set(DRACO_ROOT "" CACHE PATH "Draco install prefix (optional)")
|
|
117
|
+
endif()
|
|
118
|
+
|
|
119
|
+
find_path(DRACO_INCLUDE_DIR
|
|
120
|
+
NAMES draco/compression/encode.h
|
|
121
|
+
PATHS
|
|
122
|
+
"${DRACO_ROOT}/include"
|
|
123
|
+
"/usr/include"
|
|
124
|
+
"/opt/homebrew/include"
|
|
125
|
+
"/usr/local/include"
|
|
126
|
+
)
|
|
127
|
+
find_library(DRACO_LIBRARY
|
|
128
|
+
NAMES draco
|
|
129
|
+
PATHS
|
|
130
|
+
"${DRACO_ROOT}/lib"
|
|
131
|
+
"/usr/lib"
|
|
132
|
+
"/usr/lib/x86_64-linux-gnu"
|
|
133
|
+
"/opt/homebrew/lib"
|
|
134
|
+
"/usr/local/lib"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if(DRACO_INCLUDE_DIR AND DRACO_LIBRARY)
|
|
138
|
+
message(STATUS "Draco enabled: ${DRACO_LIBRARY}")
|
|
139
|
+
target_include_directories(${PROJECT_NAME} PRIVATE ${DRACO_INCLUDE_DIR})
|
|
140
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE ${DRACO_LIBRARY})
|
|
141
|
+
target_compile_definitions(${PROJECT_NAME} PRIVATE OCCT_GLTF_WITH_DRACO=1)
|
|
142
|
+
else()
|
|
143
|
+
message(STATUS "Draco not found; building without Draco support. Install via 'brew install draco' or set DRACO_ROOT.")
|
|
144
|
+
endif()
|
|
145
|
+
endif()
|
|
146
|
+
|
|
147
|
+
target_include_directories(${PROJECT_NAME} PRIVATE
|
|
148
|
+
${CMAKE_JS_INC}
|
|
149
|
+
${NODE_ADDON_API_DIR}
|
|
150
|
+
${OCCT_INCLUDE_DIR}
|
|
151
|
+
${CMAKE_CURRENT_LIST_DIR}/src
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
|
155
|
+
NAPI_CPP_EXCEPTIONS
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
target_link_libraries(${PROJECT_NAME} PRIVATE
|
|
159
|
+
${CMAKE_JS_LIB}
|
|
160
|
+
${OCCT_TKXSBase}
|
|
161
|
+
${OCCT_TKSTEP_LIKE}
|
|
162
|
+
${OCCT_TKXCAF}
|
|
163
|
+
${OCCT_TKCAF}
|
|
164
|
+
${OCCT_TKLCAF}
|
|
165
|
+
${OCCT_TKCDF}
|
|
166
|
+
${OCCT_TKMesh}
|
|
167
|
+
${OCCT_TKTopAlgo}
|
|
168
|
+
${OCCT_TKBRep}
|
|
169
|
+
${OCCT_TKGeomBase}
|
|
170
|
+
${OCCT_TKG3d}
|
|
171
|
+
${OCCT_TKG2d}
|
|
172
|
+
${OCCT_TKMath}
|
|
173
|
+
${OCCT_TKernel}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Ensure the loaded addon can locate OCCT dylibs at runtime.
|
|
177
|
+
set_target_properties(${PROJECT_NAME} PROPERTIES
|
|
178
|
+
PREFIX ""
|
|
179
|
+
SUFFIX ".node"
|
|
180
|
+
BUILD_RPATH "${_OCCT_LIBDIRS}"
|
|
181
|
+
INSTALL_RPATH "${_OCCT_LIBDIRS}"
|
|
182
|
+
)
|
|
183
|
+
|
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
### occt-gltf-addon
|
|
2
|
+
|
|
3
|
+
Node.js N-API 原生扩展:使用 OpenCascade (OCCT) 将 **STEP (.step/.stp)** 转换为 **glTF 2.0 (GLB)**。
|
|
4
|
+
|
|
5
|
+
#### 安装(从 npm 安装会本地编译)
|
|
6
|
+
|
|
7
|
+
这个包默认会在 `npm install` 时执行本地编译(`cmake-js`)。
|
|
8
|
+
|
|
9
|
+
你需要:
|
|
10
|
+
- **CMake + C++ 编译器 + Python3**
|
|
11
|
+
- **OCCT 安装**(共享库 + headers)
|
|
12
|
+
|
|
13
|
+
通过环境变量指定 OCCT 安装前缀:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export OCCT_ROOT=/path/to/occt/prefix
|
|
17
|
+
npm i occt-gltf-addon
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
可选(启用 Draco 压缩需要开发包):
|
|
21
|
+
- `brew install draco` / `apt-get install libdraco-dev`
|
|
22
|
+
|
|
23
|
+
如需跳过编译(不推荐,运行时会找不到 `.node`):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
OCCT_GLTF_ADDON_SKIP_BUILD=1 npm i occt-gltf-addon
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
#### API
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
const { convertSTEPToGLTF } = require('occt-gltf-addon');
|
|
35
|
+
|
|
36
|
+
await convertSTEPToGLTF({
|
|
37
|
+
inputPath: '/abs/in.step',
|
|
38
|
+
outputPath: '/abs/out.glb',
|
|
39
|
+
tessellation: { linearDeflection: 0.1, angularDeflection: 0.5 },
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
支持一次调用输出多个变体(只做一次 STEP read + XDE transfer):
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
await convertSTEPToGLTF({
|
|
47
|
+
inputPath,
|
|
48
|
+
logLevel: 'info',
|
|
49
|
+
variants: [
|
|
50
|
+
{ outputPath: 'out.min.glb', tessellation: { linearDeflection: 5, angularDeflection: Math.PI/3 } },
|
|
51
|
+
{ outputPath: 'out.glb', tessellation: { linearDeflection: 0.1, angularDeflection: 0.5 } },
|
|
52
|
+
],
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
#### 关键选项说明(简版)
|
|
59
|
+
|
|
60
|
+
- **tessellation.linearDeflection / angularDeflection**:B-Rep 三角化精度
|
|
61
|
+
- **tessellation.smoothNormals / normalCreaseAngle**:平滑法线(默认开,60° crease)
|
|
62
|
+
- **filter.minBBoxDiagonal**:按包围盒对角线过滤小零件(单位为 STEP/OCCT 导入后的单位,通常 mm)
|
|
63
|
+
- **output.unit**:`'m'|'cm'|'mm'`(默认导出为米)
|
|
64
|
+
- **output.bakeTransforms**:把节点变换烘焙进顶点并把 node matrix 归零(类似 Blender Apply)
|
|
65
|
+
- **output.center**:`'none'|'xy'|'xz'|'yz'`(配合 bake 使用;Y-up 时 `xz` 会 ground 到 `minY=0`;Z-up 时 `xy` 会 ground 到 `minZ=0`)
|
|
66
|
+
- **output.zUp**:导出为 **Z-up 坐标**(适配 three.js `THREE.Object3D.DEFAULT_UP.set(0,0,1)`);注意这会变成 **非标准 glTF(标准是 Y-up)**
|
|
67
|
+
- **output.draco**:启用 `KHR_draco_mesh_compression`(部分导入器不支持)
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
#### License
|
|
72
|
+
|
|
73
|
+
本 addon 代码以 MIT 发布;运行时依赖 OCCT(LGPL 2.1)与可选 Draco(Apache 2.0)。
|
|
74
|
+
|
package/example.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { convertSTEPToGLTF } = require('./index');
|
|
3
|
+
|
|
4
|
+
function ensureGlbExt(p) {
|
|
5
|
+
const parsed = path.parse(p);
|
|
6
|
+
if (!parsed.ext) return `${p}.glb`;
|
|
7
|
+
return p;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const inputPath = process.argv[2];
|
|
12
|
+
if (!inputPath) {
|
|
13
|
+
console.error('Usage: node example.js <input.step> [output.glb]');
|
|
14
|
+
console.error(' It will write 2 files: <output>.min.glb and <output>.glb');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const outputPathArg = process.argv[3] || path.join(process.cwd(), 'out.glb');
|
|
18
|
+
const outputPath = ensureGlbExt(outputPathArg);
|
|
19
|
+
const parsed = path.parse(outputPath);
|
|
20
|
+
const outputPathMin = path.join(parsed.dir, `${parsed.name}.min${parsed.ext}`);
|
|
21
|
+
|
|
22
|
+
// “正常”参数:你可以在这里调整成你想要的默认精度
|
|
23
|
+
const tessNormal = {
|
|
24
|
+
linearDeflection: 0.1,
|
|
25
|
+
angularDeflection: 0.5,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// “极简”参数:基于正常参数做粗化(更少三角面,更快)
|
|
29
|
+
const tessMin = {
|
|
30
|
+
linearDeflection: 5,
|
|
31
|
+
angularDeflection: Math.PI / 3,
|
|
32
|
+
};
|
|
33
|
+
const filterMin = {
|
|
34
|
+
minBBoxDiagonal: 60
|
|
35
|
+
};
|
|
36
|
+
const outputOpts = {
|
|
37
|
+
unit: 'm',
|
|
38
|
+
bakeTransforms: true,
|
|
39
|
+
center: 'xy', // 'none' | 'xy' | 'xz' | 'yz'
|
|
40
|
+
// If your runtime uses Z-up world (e.g. three.js: THREE.Object3D.DEFAULT_UP.set(0,0,1)),
|
|
41
|
+
// enable this to export Z-up coordinates (note: non-standard glTF; Y-up viewers may show it rotated).
|
|
42
|
+
zUp: true,
|
|
43
|
+
// Draco (KHR_draco_mesh_compression). NOTE: some importers (e.g. Blender) may not support Draco.
|
|
44
|
+
// draco: { enabled: true, compressionLevel: 7, quantizationBitsPosition: 14, quantizationBitsNormal: 10 },
|
|
45
|
+
draco: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
compressionLevel: 7, // 0..10 (越大越小/越慢)
|
|
48
|
+
quantizationBitsPosition: 14, // 1..30
|
|
49
|
+
quantizationBitsNormal: 10, // 1..30
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
console.log('Writing (min):', outputPathMin);
|
|
54
|
+
console.log('Writing (normal):', outputPath);
|
|
55
|
+
await convertSTEPToGLTF({
|
|
56
|
+
inputPath,
|
|
57
|
+
logLevel: 'info', // 'quiet' | 'info' | 'debug'
|
|
58
|
+
variants: [
|
|
59
|
+
{
|
|
60
|
+
outputPath: outputPathMin,
|
|
61
|
+
tessellation: tessMin,
|
|
62
|
+
filter: filterMin,
|
|
63
|
+
output: outputOpts,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
outputPath,
|
|
67
|
+
tessellation: tessNormal,
|
|
68
|
+
output: outputOpts,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log('Wrote:', outputPathMin);
|
|
74
|
+
console.log('Wrote:', outputPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((e) => {
|
|
78
|
+
console.error(e);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
|
81
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export as namespace OcctGltfAddon;
|
|
2
|
+
export = OcctGltfAddon;
|
|
3
|
+
|
|
4
|
+
declare namespace OcctGltfAddon {
|
|
5
|
+
type LogLevel = 0 | 1 | 2 | 'quiet' | 'info' | 'debug';
|
|
6
|
+
|
|
7
|
+
interface TessellationOptions {
|
|
8
|
+
linearDeflection?: number;
|
|
9
|
+
angularDeflection?: number; // radians
|
|
10
|
+
smoothNormals?: boolean;
|
|
11
|
+
normalCreaseAngle?: number; // radians
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface FilterOptions {
|
|
15
|
+
minBBoxDiagonal?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type OutputUnit = 'm' | 'cm' | 'mm';
|
|
19
|
+
type CenterMode = 'none' | 'xy' | 'xz' | 'yz';
|
|
20
|
+
|
|
21
|
+
interface DracoOptions {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
compressionLevel?: number; // 0..10
|
|
24
|
+
quantizationBitsPosition?: number; // 1..30
|
|
25
|
+
quantizationBitsNormal?: number; // 1..30
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface OutputOptions {
|
|
29
|
+
unit?: OutputUnit;
|
|
30
|
+
unitScale?: number;
|
|
31
|
+
|
|
32
|
+
bakeTransforms?: boolean;
|
|
33
|
+
center?: CenterMode;
|
|
34
|
+
// legacy booleans
|
|
35
|
+
centerXY?: boolean;
|
|
36
|
+
centerXZ?: boolean;
|
|
37
|
+
centerYZ?: boolean;
|
|
38
|
+
|
|
39
|
+
// Export Z-up coordinates (useful for three.js Z-up worlds). Non-standard glTF.
|
|
40
|
+
zUp?: boolean;
|
|
41
|
+
zup?: boolean;
|
|
42
|
+
|
|
43
|
+
// KHR_draco_mesh_compression
|
|
44
|
+
draco?: boolean | DracoOptions;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface VariantOptions {
|
|
48
|
+
outputPath: string;
|
|
49
|
+
tessellation?: TessellationOptions;
|
|
50
|
+
filter?: FilterOptions;
|
|
51
|
+
output?: OutputOptions;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ConvertOptions {
|
|
55
|
+
inputPath: string;
|
|
56
|
+
|
|
57
|
+
// Single-output mode
|
|
58
|
+
outputPath?: string;
|
|
59
|
+
tessellation?: TessellationOptions;
|
|
60
|
+
filter?: FilterOptions;
|
|
61
|
+
output?: OutputOptions;
|
|
62
|
+
|
|
63
|
+
// Multi-output mode
|
|
64
|
+
variants?: VariantOptions[];
|
|
65
|
+
|
|
66
|
+
logLevel?: LogLevel;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function convertSTEPToGLTF(options: ConvertOptions): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
|
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// CommonJS entrypoint. cmake-js outputs: build/<Config>/<target>.node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
// Prefer prebuilt, platform-specific package when available (fast install, no local compile).
|
|
6
|
+
if (!process.env.OCCT_GLTF_ADDON_BINDING_PATH) {
|
|
7
|
+
try {
|
|
8
|
+
if (process.platform === 'linux' && process.arch === 'x64') {
|
|
9
|
+
// eslint-disable-next-line import/no-dynamic-require, global-require
|
|
10
|
+
module.exports = require('occt-gltf-addon-linux-x64');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
} catch (e) {
|
|
14
|
+
// ignore and fall back to local build output
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const overridePath = process.env.OCCT_GLTF_ADDON_BINDING_PATH;
|
|
19
|
+
const candidates = overridePath
|
|
20
|
+
? [overridePath]
|
|
21
|
+
: [
|
|
22
|
+
path.join(__dirname, 'build', 'Release', 'occt_gltf_addon.node'),
|
|
23
|
+
path.join(__dirname, 'build', 'RelWithDebInfo', 'occt_gltf_addon.node'),
|
|
24
|
+
path.join(__dirname, 'build', 'Debug', 'occt_gltf_addon.node'),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
let lastErr = null;
|
|
28
|
+
for (const p of candidates) {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(p)) continue;
|
|
31
|
+
// eslint-disable-next-line import/no-dynamic-require, global-require
|
|
32
|
+
module.exports = require(p);
|
|
33
|
+
return;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
lastErr = e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const msg = [
|
|
40
|
+
'[occt-gltf-addon] Failed to load native addon (.node).',
|
|
41
|
+
`Tried: ${candidates.map((p) => JSON.stringify(p)).join(', ')}`,
|
|
42
|
+
'',
|
|
43
|
+
'If you installed from npm, ensure native build succeeded:',
|
|
44
|
+
'- CMake + C++ toolchain are installed',
|
|
45
|
+
"- OCCT is installed and OCCT_ROOT points to its install prefix (e.g. '/opt/homebrew', '/usr', '/usr/local', or your custom install)",
|
|
46
|
+
'',
|
|
47
|
+
"You can try: 'npm rebuild occt-gltf-addon' (with OCCT_ROOT exported).",
|
|
48
|
+
].join('\n');
|
|
49
|
+
|
|
50
|
+
// Preserve original error for debugging.
|
|
51
|
+
throw new Error(`${msg}\n\n${String(lastErr && (lastErr.stack || lastErr.message || lastErr))}`);
|
|
52
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "occt-gltf-addon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "STEP (.step/.stp) to glTF (GLB) converter using OpenCascade (OCCT) via a Node.js N-API addon",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Open-Cascade-SAS/OCCT.git",
|
|
12
|
+
"directory": "occt-gltf-addon"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Open-Cascade-SAS/OCCT/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/Open-Cascade-SAS/OCCT/tree/master/occt-gltf-addon",
|
|
18
|
+
"keywords": [
|
|
19
|
+
"occt",
|
|
20
|
+
"opencascade",
|
|
21
|
+
"step",
|
|
22
|
+
"stp",
|
|
23
|
+
"gltf",
|
|
24
|
+
"glb",
|
|
25
|
+
"cad",
|
|
26
|
+
"napi",
|
|
27
|
+
"node-addon-api",
|
|
28
|
+
"addon"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"os": [
|
|
34
|
+
"darwin",
|
|
35
|
+
"linux"
|
|
36
|
+
],
|
|
37
|
+
"cpu": [
|
|
38
|
+
"arm64",
|
|
39
|
+
"x64"
|
|
40
|
+
],
|
|
41
|
+
"files": [
|
|
42
|
+
"index.js",
|
|
43
|
+
"index.d.ts",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"CMakeLists.txt",
|
|
47
|
+
"src/**",
|
|
48
|
+
"scripts/**",
|
|
49
|
+
"example.js"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"install": "node scripts/install.js",
|
|
53
|
+
"build": "cmake-js compile -O build",
|
|
54
|
+
"clean": "rm -rf build",
|
|
55
|
+
"rebuild": "npm run clean && npm run build",
|
|
56
|
+
"example": "node example.js",
|
|
57
|
+
"test": "node -e \"console.log('no tests')\"",
|
|
58
|
+
|
|
59
|
+
"docker:build:linux-amd64": "bash docker/build-linux-amd64.sh",
|
|
60
|
+
"docker:image:linux-amd64": "docker buildx build --platform linux/amd64 -f docker/Dockerfile.linux-amd64 -t occt-addon-build:linux-amd64 ../..",
|
|
61
|
+
"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
|
+
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"occt-gltf-addon-linux-x64": "0.1.0"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"cmake-js": "^7.3.1",
|
|
68
|
+
"node-addon-api": "^7.1.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const { spawnSync } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function run(cmd, args, opts) {
|
|
7
|
+
const r = spawnSync(cmd, args, { stdio: 'inherit', ...opts });
|
|
8
|
+
if (r.error) throw r.error;
|
|
9
|
+
if (r.status !== 0) {
|
|
10
|
+
const e = new Error(`${cmd} exited with code ${r.status}`);
|
|
11
|
+
e.code = r.status;
|
|
12
|
+
throw e;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function note(msg) {
|
|
17
|
+
console.error(`[occt-gltf-addon] ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
if (process.env.OCCT_GLTF_ADDON_SKIP_BUILD === '1') {
|
|
22
|
+
note('Skipping native build (OCCT_GLTF_ADDON_SKIP_BUILD=1).');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (process.platform === 'win32') {
|
|
27
|
+
note('Windows is not supported by this package at the moment.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pkgRoot = path.join(__dirname, '..');
|
|
32
|
+
|
|
33
|
+
// If a platform-specific prebuilt package is installed and loadable, skip building from source.
|
|
34
|
+
// This avoids expensive compilation during "npm install".
|
|
35
|
+
const prebuiltName =
|
|
36
|
+
process.platform === 'linux' && process.arch === 'x64'
|
|
37
|
+
? 'occt-gltf-addon-linux-x64'
|
|
38
|
+
: null;
|
|
39
|
+
if (prebuiltName) {
|
|
40
|
+
try {
|
|
41
|
+
// eslint-disable-next-line import/no-dynamic-require, global-require
|
|
42
|
+
require(prebuiltName);
|
|
43
|
+
note(`Using prebuilt binary from ${prebuiltName}; skipping local build.`);
|
|
44
|
+
return;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// Prebuilt missing or not loadable (e.g. GLIBC mismatch). Fall back to local build.
|
|
47
|
+
note(`Prebuilt package ${prebuiltName} not usable; falling back to local build.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helpful hint for first-time users.
|
|
52
|
+
if (!process.env.OCCT_ROOT) {
|
|
53
|
+
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
|
+
}
|
|
55
|
+
|
|
56
|
+
// Use the locally installed cmake-js (dependency).
|
|
57
|
+
const cmakeJsBin = path.join(pkgRoot, 'node_modules', '.bin', process.platform === 'win32' ? 'cmake-js.cmd' : 'cmake-js');
|
|
58
|
+
if (!fs.existsSync(cmakeJsBin)) {
|
|
59
|
+
note(`cmake-js not found at ${cmakeJsBin}. Try: 'npm install' again.`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
note('Building native addon with cmake-js...');
|
|
64
|
+
run(cmakeJsBin, ['compile', '-O', 'build'], { cwd: pkgRoot, env: process.env });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
main();
|
|
69
|
+
} catch (e) {
|
|
70
|
+
note('Native build failed.');
|
|
71
|
+
note(String(e && (e.stack || e.message || e)));
|
|
72
|
+
note('Troubleshooting:');
|
|
73
|
+
note('- Ensure you have a C++ toolchain + CMake installed');
|
|
74
|
+
note('- Ensure OCCT is installed and OCCT_ROOT points to its prefix');
|
|
75
|
+
note('- On macOS (Apple Silicon): Homebrew OCCT is typically under /opt/homebrew');
|
|
76
|
+
note("- To skip building (not recommended): set OCCT_GLTF_ADDON_SKIP_BUILD=1");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|