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
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
#include "convert_worker.h"
|
|
2
|
+
|
|
3
|
+
#include "gltf_writer.h"
|
|
4
|
+
#include "mesh_types.h"
|
|
5
|
+
#include "occt_convert.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <chrono>
|
|
9
|
+
#include <cmath>
|
|
10
|
+
#include <exception>
|
|
11
|
+
#include <iomanip>
|
|
12
|
+
#include <iostream>
|
|
13
|
+
#include <limits>
|
|
14
|
+
#include <sstream>
|
|
15
|
+
#include <utility>
|
|
16
|
+
|
|
17
|
+
static std::string FormatHMSms(const std::uint64_t ms)
|
|
18
|
+
{
|
|
19
|
+
const std::uint64_t milli = ms % 1000;
|
|
20
|
+
const std::uint64_t totalSec = ms / 1000;
|
|
21
|
+
const std::uint64_t sec = totalSec % 60;
|
|
22
|
+
const std::uint64_t totalMin = totalSec / 60;
|
|
23
|
+
const std::uint64_t min = totalMin % 60;
|
|
24
|
+
const std::uint64_t hour = totalMin / 60;
|
|
25
|
+
|
|
26
|
+
std::ostringstream oss;
|
|
27
|
+
oss << std::setfill('0')
|
|
28
|
+
<< std::setw(2) << hour << ":"
|
|
29
|
+
<< std::setw(2) << min << ":"
|
|
30
|
+
<< std::setw(2) << sec << "."
|
|
31
|
+
<< std::setw(3) << milli;
|
|
32
|
+
return oss.str();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static std::string FormatSeconds(const std::uint64_t ms)
|
|
36
|
+
{
|
|
37
|
+
std::ostringstream oss;
|
|
38
|
+
oss << std::fixed << std::setprecision(3) << (static_cast<double>(ms) / 1000.0) << "s";
|
|
39
|
+
return oss.str();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static void ApplyUnitScale(SceneData& scene, const double s)
|
|
43
|
+
{
|
|
44
|
+
if (s == 1.0)
|
|
45
|
+
{
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// Apply uniform scale on root nodes so it propagates to the whole hierarchy.
|
|
49
|
+
for (int rootIdx : scene.roots)
|
|
50
|
+
{
|
|
51
|
+
if (rootIdx < 0 || static_cast<size_t>(rootIdx) >= scene.nodes.size())
|
|
52
|
+
{
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
auto& m = scene.nodes[static_cast<size_t>(rootIdx)].matrix;
|
|
56
|
+
// Column-major 4x4: scale the basis vectors and translation.
|
|
57
|
+
m[0] *= static_cast<float>(s); m[1] *= static_cast<float>(s); m[2] *= static_cast<float>(s);
|
|
58
|
+
m[4] *= static_cast<float>(s); m[5] *= static_cast<float>(s); m[6] *= static_cast<float>(s);
|
|
59
|
+
m[8] *= static_cast<float>(s); m[9] *= static_cast<float>(s); m[10] *= static_cast<float>(s);
|
|
60
|
+
m[12] *= static_cast<float>(s); m[13] *= static_cast<float>(s); m[14] *= static_cast<float>(s);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
using Mat4 = std::array<float, 16>;
|
|
65
|
+
|
|
66
|
+
static Mat4 MulMat4(const Mat4& A, const Mat4& B);
|
|
67
|
+
|
|
68
|
+
static Mat4 YUpToZUpMat4()
|
|
69
|
+
{
|
|
70
|
+
// Rotate +90 degrees about X:
|
|
71
|
+
// (x, y, z) -> (x, -z, y)
|
|
72
|
+
// Column-major 4x4.
|
|
73
|
+
return Mat4{
|
|
74
|
+
1, 0, 0, 0,
|
|
75
|
+
0, 0, 1, 0,
|
|
76
|
+
0, -1, 0, 0,
|
|
77
|
+
0, 0, 0, 1
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static void ApplyYUpToZUp(SceneData& scene)
|
|
82
|
+
{
|
|
83
|
+
const Mat4 R = YUpToZUpMat4();
|
|
84
|
+
for (int rootIdx : scene.roots)
|
|
85
|
+
{
|
|
86
|
+
if (rootIdx < 0 || static_cast<size_t>(rootIdx) >= scene.nodes.size())
|
|
87
|
+
{
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
scene.nodes[(size_t)rootIdx].matrix = MulMat4(R, scene.nodes[(size_t)rootIdx].matrix);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
using CenterMode = ConvertWorker::OutputOptions::CenterMode;
|
|
95
|
+
|
|
96
|
+
static Mat4 IdentityMat4()
|
|
97
|
+
{
|
|
98
|
+
return Mat4{
|
|
99
|
+
1,0,0,0,
|
|
100
|
+
0,1,0,0,
|
|
101
|
+
0,0,1,0,
|
|
102
|
+
0,0,0,1
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static Mat4 MulMat4(const Mat4& A, const Mat4& B)
|
|
107
|
+
{
|
|
108
|
+
Mat4 C{};
|
|
109
|
+
for (int col = 0; col < 4; ++col)
|
|
110
|
+
{
|
|
111
|
+
for (int row = 0; row < 4; ++row)
|
|
112
|
+
{
|
|
113
|
+
float sum = 0.0f;
|
|
114
|
+
for (int k = 0; k < 4; ++k)
|
|
115
|
+
{
|
|
116
|
+
sum += A[k * 4 + row] * B[col * 4 + k];
|
|
117
|
+
}
|
|
118
|
+
C[col * 4 + row] = sum;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return C;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
static inline void TransformPoint(const Mat4& M, float& x, float& y, float& z)
|
|
125
|
+
{
|
|
126
|
+
const float tx = M[0] * x + M[4] * y + M[8] * z + M[12];
|
|
127
|
+
const float ty = M[1] * x + M[5] * y + M[9] * z + M[13];
|
|
128
|
+
const float tz = M[2] * x + M[6] * y + M[10] * z + M[14];
|
|
129
|
+
x = tx; y = ty; z = tz;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
static inline void TransformVector3x3(const Mat4& M, float& x, float& y, float& z)
|
|
133
|
+
{
|
|
134
|
+
const float tx = M[0] * x + M[4] * y + M[8] * z;
|
|
135
|
+
const float ty = M[1] * x + M[5] * y + M[9] * z;
|
|
136
|
+
const float tz = M[2] * x + M[6] * y + M[10] * z;
|
|
137
|
+
x = tx; y = ty; z = tz;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static inline void Normalize3(float& x, float& y, float& z)
|
|
141
|
+
{
|
|
142
|
+
const float len2 = x * x + y * y + z * z;
|
|
143
|
+
if (len2 > 0.0f)
|
|
144
|
+
{
|
|
145
|
+
const float inv = 1.0f / std::sqrt(len2);
|
|
146
|
+
x *= inv; y *= inv; z *= inv;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
struct BBox3D
|
|
151
|
+
{
|
|
152
|
+
float minX = std::numeric_limits<float>::infinity();
|
|
153
|
+
float maxX = -std::numeric_limits<float>::infinity();
|
|
154
|
+
float minY = std::numeric_limits<float>::infinity();
|
|
155
|
+
float maxY = -std::numeric_limits<float>::infinity();
|
|
156
|
+
float minZ = std::numeric_limits<float>::infinity();
|
|
157
|
+
float maxZ = -std::numeric_limits<float>::infinity();
|
|
158
|
+
bool valid = false;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
static void BakeTransformsToMeshes(SceneData& scene, BBox3D* bbox)
|
|
162
|
+
{
|
|
163
|
+
if (scene.nodes.empty())
|
|
164
|
+
{
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
std::vector<Mat4> world(scene.nodes.size(), IdentityMat4());
|
|
169
|
+
const Mat4 I = IdentityMat4();
|
|
170
|
+
|
|
171
|
+
std::function<void(int, const Mat4&)> dfs = [&](int idx, const Mat4& parent) {
|
|
172
|
+
if (idx < 0 || static_cast<size_t>(idx) >= scene.nodes.size())
|
|
173
|
+
{
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const Mat4 w = MulMat4(parent, scene.nodes[(size_t)idx].matrix);
|
|
177
|
+
world[(size_t)idx] = w;
|
|
178
|
+
for (int c : scene.nodes[(size_t)idx].children)
|
|
179
|
+
{
|
|
180
|
+
dfs(c, w);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
for (int root : scene.roots)
|
|
185
|
+
{
|
|
186
|
+
dfs(root, I);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
std::vector<MeshData> baked;
|
|
190
|
+
baked.reserve(scene.nodes.size());
|
|
191
|
+
|
|
192
|
+
for (size_t ni = 0; ni < scene.nodes.size(); ++ni)
|
|
193
|
+
{
|
|
194
|
+
SceneNode& node = scene.nodes[ni];
|
|
195
|
+
if (node.meshIndex < 0)
|
|
196
|
+
{
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const int srcIdx = node.meshIndex;
|
|
200
|
+
if (static_cast<size_t>(srcIdx) >= scene.meshes.size())
|
|
201
|
+
{
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const MeshData& src = scene.meshes[(size_t)srcIdx];
|
|
206
|
+
MeshData dst;
|
|
207
|
+
dst.materialIndex = src.materialIndex;
|
|
208
|
+
dst.positions = src.positions;
|
|
209
|
+
dst.normals = src.normals;
|
|
210
|
+
|
|
211
|
+
const Mat4& M = world[ni];
|
|
212
|
+
|
|
213
|
+
// Transform positions
|
|
214
|
+
for (size_t i = 0; i + 2 < dst.positions.size(); i += 3)
|
|
215
|
+
{
|
|
216
|
+
float x = dst.positions[i + 0];
|
|
217
|
+
float y = dst.positions[i + 1];
|
|
218
|
+
float z = dst.positions[i + 2];
|
|
219
|
+
TransformPoint(M, x, y, z);
|
|
220
|
+
dst.positions[i + 0] = x;
|
|
221
|
+
dst.positions[i + 1] = y;
|
|
222
|
+
dst.positions[i + 2] = z;
|
|
223
|
+
|
|
224
|
+
if (bbox)
|
|
225
|
+
{
|
|
226
|
+
bbox->valid = true;
|
|
227
|
+
bbox->minX = std::min(bbox->minX, x);
|
|
228
|
+
bbox->maxX = std::max(bbox->maxX, x);
|
|
229
|
+
bbox->minY = std::min(bbox->minY, y);
|
|
230
|
+
bbox->maxY = std::max(bbox->maxY, y);
|
|
231
|
+
bbox->minZ = std::min(bbox->minZ, z);
|
|
232
|
+
bbox->maxZ = std::max(bbox->maxZ, z);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Transform normals (3x3) + normalize
|
|
237
|
+
for (size_t i = 0; i + 2 < dst.normals.size(); i += 3)
|
|
238
|
+
{
|
|
239
|
+
float x = dst.normals[i + 0];
|
|
240
|
+
float y = dst.normals[i + 1];
|
|
241
|
+
float z = dst.normals[i + 2];
|
|
242
|
+
TransformVector3x3(M, x, y, z);
|
|
243
|
+
Normalize3(x, y, z);
|
|
244
|
+
dst.normals[i + 0] = x;
|
|
245
|
+
dst.normals[i + 1] = y;
|
|
246
|
+
dst.normals[i + 2] = z;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
baked.push_back(std::move(dst));
|
|
250
|
+
node.meshIndex = static_cast<int>(baked.size() - 1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
scene.meshes = std::move(baked);
|
|
254
|
+
|
|
255
|
+
// Reset all node transforms to identity (apply transforms).
|
|
256
|
+
for (auto& node : scene.nodes)
|
|
257
|
+
{
|
|
258
|
+
node.matrix = IdentityMat4();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
static void ShiftMeshes(SceneData& scene, const float dx, const float dy, const float dz)
|
|
263
|
+
{
|
|
264
|
+
for (auto& mesh : scene.meshes)
|
|
265
|
+
{
|
|
266
|
+
for (size_t i = 0; i + 2 < mesh.positions.size(); i += 3)
|
|
267
|
+
{
|
|
268
|
+
mesh.positions[i + 0] -= dx;
|
|
269
|
+
mesh.positions[i + 1] -= dy;
|
|
270
|
+
mesh.positions[i + 2] -= dz;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
ConvertWorker::ConvertWorker(const Napi::Env& env,
|
|
276
|
+
Napi::Promise::Deferred deferred,
|
|
277
|
+
std::string inputPath,
|
|
278
|
+
const int logLevel,
|
|
279
|
+
std::vector<VariantOptions> variants)
|
|
280
|
+
: Napi::AsyncWorker(env),
|
|
281
|
+
deferred_(std::move(deferred)),
|
|
282
|
+
inputPath_(std::move(inputPath)),
|
|
283
|
+
logLevel_(logLevel),
|
|
284
|
+
variants_(std::move(variants))
|
|
285
|
+
{
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
void ConvertWorker::Execute()
|
|
289
|
+
{
|
|
290
|
+
try
|
|
291
|
+
{
|
|
292
|
+
if (logLevel_ >= 1)
|
|
293
|
+
{
|
|
294
|
+
std::cerr << "[occt-gltf] ===== Convert START =====\n";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (variants_.empty())
|
|
298
|
+
{
|
|
299
|
+
throw std::runtime_error("No output variants specified.");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Load STEP/XDE once (dominant cost), then run meshing+export for each variant.
|
|
303
|
+
ConvertStats shared;
|
|
304
|
+
StepXdeContext* ctx = LoadStepXdeContext(inputPath_, /*readNames*/ true, /*readColors*/ true, &shared, logLevel_);
|
|
305
|
+
try
|
|
306
|
+
{
|
|
307
|
+
// Process coarsest tessellation first to avoid reusing a finer triangulation for a coarse variant.
|
|
308
|
+
std::vector<size_t> order(variants_.size());
|
|
309
|
+
for (size_t i = 0; i < variants_.size(); ++i) order[i] = i;
|
|
310
|
+
std::stable_sort(order.begin(), order.end(), [&](size_t a, size_t b) {
|
|
311
|
+
const auto& A = variants_[a].tess;
|
|
312
|
+
const auto& B = variants_[b].tess;
|
|
313
|
+
if (A.linearDeflection != B.linearDeflection) return A.linearDeflection > B.linearDeflection;
|
|
314
|
+
return A.angularDeflection > B.angularDeflection;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
for (size_t oi = 0; oi < order.size(); ++oi)
|
|
318
|
+
{
|
|
319
|
+
const VariantOptions& v = variants_[order[oi]];
|
|
320
|
+
|
|
321
|
+
if (logLevel_ >= 1)
|
|
322
|
+
{
|
|
323
|
+
std::cerr << "[occt-gltf] Variant " << (oi + 1) << "/" << order.size()
|
|
324
|
+
<< " -> " << v.outputPath
|
|
325
|
+
<< " (lin=" << v.tess.linearDeflection
|
|
326
|
+
<< ", ang=" << v.tess.angularDeflection
|
|
327
|
+
<< ", normals=" << (v.tess.smoothNormals ? "smooth" : "flat");
|
|
328
|
+
if (v.tess.smoothNormals)
|
|
329
|
+
{
|
|
330
|
+
std::cerr << ", crease=" << v.tess.normalCreaseAngle;
|
|
331
|
+
}
|
|
332
|
+
std::cerr
|
|
333
|
+
<< ", minBBoxDiag=" << v.filter.minBBoxDiagonal
|
|
334
|
+
<< ", unitScale=" << v.output.unitScale
|
|
335
|
+
<< ", bake=" << (v.output.bakeTransforms ? "true" : "false")
|
|
336
|
+
<< ", center=";
|
|
337
|
+
switch (v.output.center)
|
|
338
|
+
{
|
|
339
|
+
case CenterMode::XY: std::cerr << "xy"; break;
|
|
340
|
+
case CenterMode::XZ: std::cerr << "xz"; break;
|
|
341
|
+
case CenterMode::YZ: std::cerr << "yz"; break;
|
|
342
|
+
case CenterMode::None: default: std::cerr << "none"; break;
|
|
343
|
+
}
|
|
344
|
+
std::cerr << ")\n";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
ConvertStats st;
|
|
348
|
+
st.msRead = shared.msRead;
|
|
349
|
+
st.msTransfer = shared.msTransfer;
|
|
350
|
+
|
|
351
|
+
SceneData scene = ConvertStepXdeContextToScene(ctx,
|
|
352
|
+
v.tess.linearDeflection,
|
|
353
|
+
v.tess.angularDeflection,
|
|
354
|
+
v.tess.smoothNormals,
|
|
355
|
+
v.tess.normalCreaseAngle,
|
|
356
|
+
v.filter.minBBoxDiagonal,
|
|
357
|
+
&st,
|
|
358
|
+
logLevel_);
|
|
359
|
+
|
|
360
|
+
ApplyUnitScale(scene, v.output.unitScale);
|
|
361
|
+
if (v.output.zUp)
|
|
362
|
+
{
|
|
363
|
+
ApplyYUpToZUp(scene);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (v.output.bakeTransforms)
|
|
367
|
+
{
|
|
368
|
+
const bool needCenter = (v.output.center != CenterMode::None);
|
|
369
|
+
BBox3D bbox;
|
|
370
|
+
BakeTransformsToMeshes(scene, needCenter ? &bbox : nullptr);
|
|
371
|
+
if (needCenter && bbox.valid)
|
|
372
|
+
{
|
|
373
|
+
const float cx = (bbox.minX + bbox.maxX) * 0.5f;
|
|
374
|
+
const float cy = (bbox.minY + bbox.maxY) * 0.5f;
|
|
375
|
+
const float cz = (bbox.minZ + bbox.maxZ) * 0.5f;
|
|
376
|
+
|
|
377
|
+
float dx = 0.0f, dy = 0.0f, dz = 0.0f;
|
|
378
|
+
switch (v.output.center)
|
|
379
|
+
{
|
|
380
|
+
case CenterMode::XY:
|
|
381
|
+
dx = cx;
|
|
382
|
+
dy = cy;
|
|
383
|
+
if (v.output.zUp)
|
|
384
|
+
{
|
|
385
|
+
dz = bbox.minZ; // ground for Z-up: make minZ = 0
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
case CenterMode::XZ:
|
|
389
|
+
dx = cx;
|
|
390
|
+
dz = cz;
|
|
391
|
+
if (!v.output.zUp)
|
|
392
|
+
{
|
|
393
|
+
dy = bbox.minY; // ground for Y-up: make minY = 0
|
|
394
|
+
}
|
|
395
|
+
break;
|
|
396
|
+
case CenterMode::YZ: dy = cy; dz = cz; break;
|
|
397
|
+
case CenterMode::None: default: break;
|
|
398
|
+
}
|
|
399
|
+
ShiftMeshes(scene, dx, dy, dz);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (v.output.center != CenterMode::None)
|
|
403
|
+
{
|
|
404
|
+
if (logLevel_ >= 1)
|
|
405
|
+
{
|
|
406
|
+
std::cerr << "[occt-gltf] Warning: output.center requested but output.bakeTransforms=false; ignored.\n";
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const auto tWrite0 = std::chrono::steady_clock::now();
|
|
411
|
+
GltfWriteOptions wopt;
|
|
412
|
+
wopt.dracoEnabled = v.output.dracoEnabled;
|
|
413
|
+
wopt.dracoCompressionLevel = v.output.dracoCompressionLevel;
|
|
414
|
+
wopt.dracoQuantBitsPosition = v.output.dracoQuantBitsPosition;
|
|
415
|
+
wopt.dracoQuantBitsNormal = v.output.dracoQuantBitsNormal;
|
|
416
|
+
st.glbBytes = WriteGLB(v.outputPath, scene, wopt);
|
|
417
|
+
const auto tWrite1 = std::chrono::steady_clock::now();
|
|
418
|
+
st.msWrite =
|
|
419
|
+
(std::uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(tWrite1 - tWrite0).count();
|
|
420
|
+
|
|
421
|
+
st.msTotal = st.msRead + st.msTransfer + st.msMesh + st.msExtract + st.msWrite;
|
|
422
|
+
|
|
423
|
+
if (logLevel_ >= 1)
|
|
424
|
+
{
|
|
425
|
+
std::cerr << "[occt-gltf] DONE. faces=" << st.facesWithTriangulation << "/" << st.facesTotal
|
|
426
|
+
<< ", triangles=" << st.triangles
|
|
427
|
+
<< ", vertices=" << st.vertices
|
|
428
|
+
<< ", glbBytes=" << st.glbBytes
|
|
429
|
+
<< ", total=" << FormatHMSms(st.msTotal) << "\n";
|
|
430
|
+
std::cerr << "[occt-gltf] Timing. read=" << FormatHMSms(st.msRead)
|
|
431
|
+
<< ", transfer=" << FormatHMSms(st.msTransfer)
|
|
432
|
+
<< ", mesh=" << FormatHMSms(st.msMesh)
|
|
433
|
+
<< ", extract=" << FormatHMSms(st.msExtract)
|
|
434
|
+
<< ", write=" << FormatHMSms(st.msWrite) << "\n";
|
|
435
|
+
std::cerr << "[occt-gltf] Scene. nodes=" << scene.nodes.size()
|
|
436
|
+
<< ", meshes=" << scene.meshes.size()
|
|
437
|
+
<< ", skippedNoTri=" << st.skippedMeshesNoTriangulation
|
|
438
|
+
<< ", skippedSmall=" << st.skippedSmallParts << "\n";
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
FreeStepXdeContext(ctx);
|
|
443
|
+
}
|
|
444
|
+
catch (...)
|
|
445
|
+
{
|
|
446
|
+
FreeStepXdeContext(ctx);
|
|
447
|
+
throw;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (logLevel_ >= 1)
|
|
451
|
+
{
|
|
452
|
+
std::cerr << "[occt-gltf] ===== Convert END =====\n";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (const std::exception& ex)
|
|
456
|
+
{
|
|
457
|
+
SetError(ex.what());
|
|
458
|
+
}
|
|
459
|
+
catch (...)
|
|
460
|
+
{
|
|
461
|
+
SetError("Unknown error");
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
void ConvertWorker::OnOK()
|
|
466
|
+
{
|
|
467
|
+
deferred_.Resolve(Env().Undefined());
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
void ConvertWorker::OnError(const Napi::Error& e)
|
|
471
|
+
{
|
|
472
|
+
deferred_.Reject(e.Value());
|
|
473
|
+
}
|
|
474
|
+
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <napi.h>
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
class ConvertWorker final : public Napi::AsyncWorker
|
|
9
|
+
{
|
|
10
|
+
public:
|
|
11
|
+
struct TessellationOptions
|
|
12
|
+
{
|
|
13
|
+
double linearDeflection = 0.1;
|
|
14
|
+
double angularDeflection = 0.5; // radians
|
|
15
|
+
|
|
16
|
+
// Generate smooth vertex normals by averaging adjacent triangle normals.
|
|
17
|
+
// If false, flat normals are generated (one normal per triangle).
|
|
18
|
+
bool smoothNormals = true;
|
|
19
|
+
|
|
20
|
+
// Crease angle (radians) used when smoothNormals=true. Faces meeting at angles
|
|
21
|
+
// larger than this are treated as sharp edges (their normals won't be averaged).
|
|
22
|
+
// Default 60 degrees.
|
|
23
|
+
double normalCreaseAngle = 1.0471975511965976;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
struct FilterOptions
|
|
27
|
+
{
|
|
28
|
+
// Discard leaf parts with bounding-box diagonal smaller than this value (<= 0 disables).
|
|
29
|
+
// Units are the STEP model units as imported by OCCT (usually mm).
|
|
30
|
+
double minBBoxDiagonal = 0.0;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
struct OutputOptions
|
|
34
|
+
{
|
|
35
|
+
// Unit scale applied to the exported scene (via root node transforms).
|
|
36
|
+
// OCCT XSTEP defaults to millimeters, while glTF convention is meters.
|
|
37
|
+
// Therefore default is 0.001 (mm -> m).
|
|
38
|
+
double unitScale = 0.001;
|
|
39
|
+
|
|
40
|
+
// If true, bake node transforms into mesh vertex data and reset all node transforms to identity
|
|
41
|
+
// (similar to Blender "Apply transforms").
|
|
42
|
+
bool bakeTransforms = false;
|
|
43
|
+
|
|
44
|
+
// Recenter the exported model so that the selected plane center of its bounding box is at (0,0).
|
|
45
|
+
// - Requires bakeTransforms=true (we shift vertex positions).
|
|
46
|
+
enum class CenterMode
|
|
47
|
+
{
|
|
48
|
+
None = 0,
|
|
49
|
+
XY, // center X/Y to (0,0)
|
|
50
|
+
XZ, // center X/Z to (0,0) and shift Y so bbox.minY becomes 0 (ground)
|
|
51
|
+
YZ
|
|
52
|
+
};
|
|
53
|
+
CenterMode center = CenterMode::None;
|
|
54
|
+
|
|
55
|
+
// If true, treat the source coordinates as Z-up (common in CAD/Blender) and rotate
|
|
56
|
+
// the exported scene into Z-up orientation (useful when your runtime (e.g. three.js)
|
|
57
|
+
// uses Z-up world: THREE.Object3D.DEFAULT_UP.set(0,0,1)).
|
|
58
|
+
//
|
|
59
|
+
// Note: glTF's standard coordinate system is Y-up. Enabling zUp makes the exported
|
|
60
|
+
// data Z-up (non-standard glTF), so Y-up viewers may show it rotated.
|
|
61
|
+
bool zUp = false;
|
|
62
|
+
|
|
63
|
+
// Draco mesh compression (KHR_draco_mesh_compression)
|
|
64
|
+
bool dracoEnabled = false;
|
|
65
|
+
int dracoCompressionLevel = 7; // 0..10 (higher = smaller/slower)
|
|
66
|
+
int dracoQuantBitsPosition = 14;
|
|
67
|
+
int dracoQuantBitsNormal = 10;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
struct VariantOptions
|
|
71
|
+
{
|
|
72
|
+
std::string outputPath;
|
|
73
|
+
TessellationOptions tess;
|
|
74
|
+
FilterOptions filter;
|
|
75
|
+
OutputOptions output;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
ConvertWorker(const Napi::Env& env,
|
|
79
|
+
Napi::Promise::Deferred deferred,
|
|
80
|
+
std::string inputPath,
|
|
81
|
+
int logLevel,
|
|
82
|
+
std::vector<VariantOptions> variants);
|
|
83
|
+
|
|
84
|
+
void Execute() override;
|
|
85
|
+
void OnOK() override;
|
|
86
|
+
void OnError(const Napi::Error& e) override;
|
|
87
|
+
|
|
88
|
+
private:
|
|
89
|
+
Napi::Promise::Deferred deferred_;
|
|
90
|
+
std::string inputPath_;
|
|
91
|
+
int logLevel_ = 1; // 0=quiet,1=info,2=debug
|
|
92
|
+
std::vector<VariantOptions> variants_;
|
|
93
|
+
};
|
|
94
|
+
|