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,313 @@
|
|
|
1
|
+
#include "occt_convert.h"
|
|
2
|
+
|
|
3
|
+
#include <chrono>
|
|
4
|
+
#include <iostream>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
#include <unordered_map>
|
|
7
|
+
|
|
8
|
+
// OCCT tessellation
|
|
9
|
+
#include <BRepMesh_IncrementalMesh.hxx>
|
|
10
|
+
#include <BRep_Tool.hxx>
|
|
11
|
+
#include <Poly_Triangulation.hxx>
|
|
12
|
+
#include <TopExp_Explorer.hxx>
|
|
13
|
+
#include <TopoDS.hxx>
|
|
14
|
+
#include <TopoDS_Face.hxx>
|
|
15
|
+
#include <TopoDS_Shape.hxx>
|
|
16
|
+
#include <TopLoc_Location.hxx>
|
|
17
|
+
#include <gp_Pnt.hxx>
|
|
18
|
+
#include <gp_Vec.hxx>
|
|
19
|
+
#include <gp_Trsf.hxx>
|
|
20
|
+
|
|
21
|
+
// OCCT XDE (assembly + names)
|
|
22
|
+
#include <STEPCAFControl_Reader.hxx>
|
|
23
|
+
#include <TDocStd_Document.hxx>
|
|
24
|
+
#include <TDataStd_Name.hxx>
|
|
25
|
+
#include <TDF_Label.hxx>
|
|
26
|
+
#include <TDF_LabelSequence.hxx>
|
|
27
|
+
#include <XCAFDoc_DocumentTool.hxx>
|
|
28
|
+
#include <XCAFDoc_ShapeTool.hxx>
|
|
29
|
+
#include <TopTools_ShapeMapHasher.hxx>
|
|
30
|
+
#include <NCollection_DataMap.hxx>
|
|
31
|
+
|
|
32
|
+
static inline void AppendVec3(std::vector<float>& dst, const gp_Vec& v)
|
|
33
|
+
{
|
|
34
|
+
dst.push_back(static_cast<float>(v.X()));
|
|
35
|
+
dst.push_back(static_cast<float>(v.Y()));
|
|
36
|
+
dst.push_back(static_cast<float>(v.Z()));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static inline void AppendPnt3(std::vector<float>& dst, const gp_Pnt& p)
|
|
40
|
+
{
|
|
41
|
+
dst.push_back(static_cast<float>(p.X()));
|
|
42
|
+
dst.push_back(static_cast<float>(p.Y()));
|
|
43
|
+
dst.push_back(static_cast<float>(p.Z()));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static std::string ToUtf8(const TCollection_ExtendedString& s)
|
|
47
|
+
{
|
|
48
|
+
const int len = s.LengthOfCString();
|
|
49
|
+
std::string out;
|
|
50
|
+
out.resize((size_t)len);
|
|
51
|
+
std::vector<char> buf((size_t)len + 1);
|
|
52
|
+
Standard_PCharacter p = buf.data();
|
|
53
|
+
s.ToUTF8CString(p);
|
|
54
|
+
out.assign(buf.data(), (size_t)len);
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static std::string GetLabelNameUtf8(const TDF_Label& L)
|
|
59
|
+
{
|
|
60
|
+
occ::handle<TDataStd_Name> nameAttr;
|
|
61
|
+
if (L.FindAttribute(TDataStd_Name::GetID(), nameAttr))
|
|
62
|
+
{
|
|
63
|
+
return ToUtf8(nameAttr->Get());
|
|
64
|
+
}
|
|
65
|
+
return std::string();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static std::array<float, 16> LocationToGltfMatrix(const TopLoc_Location& loc)
|
|
69
|
+
{
|
|
70
|
+
const gp_Trsf t = loc.Transformation();
|
|
71
|
+
// gp_Trsf::Value(i,j): i=1..3, j=1..4 (last column is translation).
|
|
72
|
+
const double m11 = t.Value(1, 1), m12 = t.Value(1, 2), m13 = t.Value(1, 3), m14 = t.Value(1, 4);
|
|
73
|
+
const double m21 = t.Value(2, 1), m22 = t.Value(2, 2), m23 = t.Value(2, 3), m24 = t.Value(2, 4);
|
|
74
|
+
const double m31 = t.Value(3, 1), m32 = t.Value(3, 2), m33 = t.Value(3, 3), m34 = t.Value(3, 4);
|
|
75
|
+
|
|
76
|
+
// glTF expects column-major matrices.
|
|
77
|
+
return {
|
|
78
|
+
(float)m11, (float)m21, (float)m31, 0.0f,
|
|
79
|
+
(float)m12, (float)m22, (float)m32, 0.0f,
|
|
80
|
+
(float)m13, (float)m23, (float)m33, 0.0f,
|
|
81
|
+
(float)m14, (float)m24, (float)m34, 1.0f
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static MeshData ConvertShapeToMesh(const TopoDS_Shape& shape,
|
|
86
|
+
double linearDeflection,
|
|
87
|
+
double angularDeflection,
|
|
88
|
+
ConvertStats* stats)
|
|
89
|
+
{
|
|
90
|
+
using Clock = std::chrono::steady_clock;
|
|
91
|
+
auto ms = [](Clock::time_point a, Clock::time_point b) -> std::uint64_t {
|
|
92
|
+
return (std::uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(b - a).count();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const Clock::time_point tMesh0 = Clock::now();
|
|
96
|
+
BRepMesh_IncrementalMesh mesher(shape, linearDeflection, false /*isRelative*/,
|
|
97
|
+
angularDeflection, true /*inParallel*/);
|
|
98
|
+
mesher.Perform();
|
|
99
|
+
const Clock::time_point tMesh1 = Clock::now();
|
|
100
|
+
if (stats) stats->msMesh += ms(tMesh0, tMesh1);
|
|
101
|
+
|
|
102
|
+
MeshData out;
|
|
103
|
+
out.positions.reserve(1024);
|
|
104
|
+
out.normals.reserve(1024);
|
|
105
|
+
|
|
106
|
+
std::uint64_t faceCount = 0;
|
|
107
|
+
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) { ++faceCount; }
|
|
108
|
+
if (stats) stats->facesTotal += faceCount;
|
|
109
|
+
|
|
110
|
+
const Clock::time_point tExtract0 = Clock::now();
|
|
111
|
+
|
|
112
|
+
std::uint64_t facesWithTri = 0;
|
|
113
|
+
std::uint64_t triCountTotal = 0;
|
|
114
|
+
|
|
115
|
+
for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next())
|
|
116
|
+
{
|
|
117
|
+
const TopoDS_Face face = TopoDS::Face(exp.Current());
|
|
118
|
+
|
|
119
|
+
TopLoc_Location triLoc;
|
|
120
|
+
Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(face, triLoc);
|
|
121
|
+
if (tri.IsNull())
|
|
122
|
+
{
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
++facesWithTri;
|
|
126
|
+
|
|
127
|
+
const gp_Trsf trsf = triLoc.Transformation();
|
|
128
|
+
|
|
129
|
+
for (int i = 1; i <= tri->NbTriangles(); ++i)
|
|
130
|
+
{
|
|
131
|
+
int n1 = 0, n2 = 0, n3 = 0;
|
|
132
|
+
tri->Triangle(i).Get(n1, n2, n3);
|
|
133
|
+
|
|
134
|
+
gp_Pnt p1 = tri->Node(n1).Transformed(trsf);
|
|
135
|
+
gp_Pnt p2 = tri->Node(n2).Transformed(trsf);
|
|
136
|
+
gp_Pnt p3 = tri->Node(n3).Transformed(trsf);
|
|
137
|
+
|
|
138
|
+
const gp_Vec v12(p1, p2);
|
|
139
|
+
const gp_Vec v13(p1, p3);
|
|
140
|
+
gp_Vec n = v12.Crossed(v13);
|
|
141
|
+
if (n.SquareMagnitude() > 0.0)
|
|
142
|
+
{
|
|
143
|
+
n.Normalize();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
AppendPnt3(out.positions, p1);
|
|
147
|
+
AppendPnt3(out.positions, p2);
|
|
148
|
+
AppendPnt3(out.positions, p3);
|
|
149
|
+
|
|
150
|
+
AppendVec3(out.normals, n);
|
|
151
|
+
AppendVec3(out.normals, n);
|
|
152
|
+
AppendVec3(out.normals, n);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
triCountTotal += static_cast<std::uint64_t>(tri->NbTriangles());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const Clock::time_point tExtract1 = Clock::now();
|
|
159
|
+
if (stats)
|
|
160
|
+
{
|
|
161
|
+
stats->facesWithTriangulation += facesWithTri;
|
|
162
|
+
stats->triangles += triCountTotal;
|
|
163
|
+
stats->vertices += static_cast<std::uint64_t>(out.positions.size() / 3);
|
|
164
|
+
stats->msExtract += ms(tExtract0, tExtract1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (out.positions.empty())
|
|
168
|
+
{
|
|
169
|
+
throw std::runtime_error("No triangulation produced (empty mesh).");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static int AddNode(SceneData& scene, SceneNode node)
|
|
176
|
+
{
|
|
177
|
+
scene.nodes.push_back(std::move(node));
|
|
178
|
+
return static_cast<int>(scene.nodes.size() - 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
SceneData ConvertSTEPToScene(const std::string& inputPath,
|
|
182
|
+
double linearDeflection,
|
|
183
|
+
double angularDeflection,
|
|
184
|
+
ConvertStats* stats)
|
|
185
|
+
{
|
|
186
|
+
using Clock = std::chrono::steady_clock;
|
|
187
|
+
auto ms = [](Clock::time_point a, Clock::time_point b) -> std::uint64_t {
|
|
188
|
+
return (std::uint64_t)std::chrono::duration_cast<std::chrono::milliseconds>(b - a).count();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const Clock::time_point tAll0 = Clock::now();
|
|
192
|
+
|
|
193
|
+
if (stats)
|
|
194
|
+
{
|
|
195
|
+
stats->linearDeflection = linearDeflection;
|
|
196
|
+
stats->angularDeflection = angularDeflection;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::cerr << "[occt-gltf] STEP(XDE) read: " << inputPath << "\n";
|
|
200
|
+
|
|
201
|
+
const Clock::time_point tRead0 = Clock::now();
|
|
202
|
+
STEPCAFControl_Reader reader;
|
|
203
|
+
reader.SetNameMode(true); // read names (Unicode)
|
|
204
|
+
reader.SetColorMode(false);
|
|
205
|
+
reader.SetLayerMode(false);
|
|
206
|
+
reader.SetPropsMode(false);
|
|
207
|
+
|
|
208
|
+
const IFSelect_ReturnStatus status = reader.ReadFile(inputPath.c_str());
|
|
209
|
+
const Clock::time_point tRead1 = Clock::now();
|
|
210
|
+
if (status != IFSelect_RetDone)
|
|
211
|
+
{
|
|
212
|
+
throw std::runtime_error("Failed to read STEP file: " + inputPath);
|
|
213
|
+
}
|
|
214
|
+
if (stats) stats->msRead = ms(tRead0, tRead1);
|
|
215
|
+
|
|
216
|
+
const Clock::time_point tTransfer0 = Clock::now();
|
|
217
|
+
std::cerr << "[occt-gltf] XDE Transfer...\n";
|
|
218
|
+
occ::handle<TDocStd_Document> doc = new TDocStd_Document("occt-gltf");
|
|
219
|
+
if (!reader.Transfer(doc))
|
|
220
|
+
{
|
|
221
|
+
throw std::runtime_error("STEPCAFControl_Reader.Transfer() failed.");
|
|
222
|
+
}
|
|
223
|
+
const Clock::time_point tTransfer1 = Clock::now();
|
|
224
|
+
if (stats) stats->msTransfer = ms(tTransfer0, tTransfer1);
|
|
225
|
+
|
|
226
|
+
const occ::handle<XCAFDoc_ShapeTool> shapeTool =
|
|
227
|
+
XCAFDoc_DocumentTool::ShapeTool(doc->Main());
|
|
228
|
+
if (shapeTool.IsNull())
|
|
229
|
+
{
|
|
230
|
+
throw std::runtime_error("XCAFDoc_ShapeTool not available.");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Mesh cache by base shape (location stripped) to support instancing.
|
|
234
|
+
NCollection_DataMap<TopoDS_Shape, int, TopTools_ShapeMapHasher> meshMap;
|
|
235
|
+
|
|
236
|
+
SceneData scene;
|
|
237
|
+
|
|
238
|
+
std::function<int(const TDF_Label&, const TopLoc_Location&, const std::string&)> build;
|
|
239
|
+
build = [&](const TDF_Label& label, const TopLoc_Location& loc, const std::string& overrideName) -> int {
|
|
240
|
+
SceneNode node;
|
|
241
|
+
node.matrix = LocationToGltfMatrix(loc);
|
|
242
|
+
node.name = !overrideName.empty() ? overrideName : GetLabelNameUtf8(label);
|
|
243
|
+
if (node.name.empty())
|
|
244
|
+
{
|
|
245
|
+
node.name = "Node";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (XCAFDoc_ShapeTool::IsAssembly(label))
|
|
249
|
+
{
|
|
250
|
+
const int idx = AddNode(scene, std::move(node));
|
|
251
|
+
NCollection_Sequence<TDF_Label> comps;
|
|
252
|
+
XCAFDoc_ShapeTool::GetComponents(label, comps, false);
|
|
253
|
+
for (NCollection_Sequence<TDF_Label>::Iterator it(comps); it.More(); it.Next())
|
|
254
|
+
{
|
|
255
|
+
const TDF_Label compL = it.Value();
|
|
256
|
+
const TopLoc_Location childLoc = XCAFDoc_ShapeTool::GetLocation(compL);
|
|
257
|
+
TDF_Label refL;
|
|
258
|
+
std::string childName = GetLabelNameUtf8(compL);
|
|
259
|
+
if (!XCAFDoc_ShapeTool::GetReferredShape(compL, refL))
|
|
260
|
+
{
|
|
261
|
+
refL = compL;
|
|
262
|
+
}
|
|
263
|
+
const int childIdx = build(refL, childLoc, childName);
|
|
264
|
+
scene.nodes[idx].children.push_back(childIdx);
|
|
265
|
+
}
|
|
266
|
+
return idx;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Leaf / part: generate or reuse mesh.
|
|
270
|
+
TopoDS_Shape s = XCAFDoc_ShapeTool::GetShape(label);
|
|
271
|
+
if (s.IsNull())
|
|
272
|
+
{
|
|
273
|
+
return AddNode(scene, std::move(node));
|
|
274
|
+
}
|
|
275
|
+
TopoDS_Shape base = s.Located(TopLoc_Location()); // strip location for cache key
|
|
276
|
+
|
|
277
|
+
int meshIdx = -1;
|
|
278
|
+
if (meshMap.IsBound(base))
|
|
279
|
+
{
|
|
280
|
+
meshIdx = meshMap.Find(base);
|
|
281
|
+
}
|
|
282
|
+
else
|
|
283
|
+
{
|
|
284
|
+
std::cerr << "[occt-gltf] Meshing leaf: " << node.name << "\n";
|
|
285
|
+
MeshData mesh = ConvertShapeToMesh(base, linearDeflection, angularDeflection, stats);
|
|
286
|
+
scene.meshes.push_back(std::move(mesh));
|
|
287
|
+
meshIdx = static_cast<int>(scene.meshes.size() - 1);
|
|
288
|
+
meshMap.Bind(base, meshIdx);
|
|
289
|
+
}
|
|
290
|
+
node.meshIndex = meshIdx;
|
|
291
|
+
return AddNode(scene, std::move(node));
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
NCollection_Sequence<TDF_Label> roots;
|
|
295
|
+
shapeTool->GetFreeShapes(roots);
|
|
296
|
+
std::cerr << "[occt-gltf] FreeShapes: " << roots.Length() << "\n";
|
|
297
|
+
|
|
298
|
+
for (NCollection_Sequence<TDF_Label>::Iterator it(roots); it.More(); it.Next())
|
|
299
|
+
{
|
|
300
|
+
const TDF_Label rootL = it.Value();
|
|
301
|
+
const int rootIdx = build(rootL, TopLoc_Location(), std::string());
|
|
302
|
+
scene.roots.push_back(rootIdx);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (stats)
|
|
306
|
+
{
|
|
307
|
+
stats->nodes = static_cast<std::uint64_t>(scene.nodes.size());
|
|
308
|
+
stats->meshes = static_cast<std::uint64_t>(scene.meshes.size());
|
|
309
|
+
stats->msTotal = ms(tAll0, Clock::now());
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return scene;
|
|
313
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "mesh_types.h"
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
|
|
7
|
+
// Opaque handle to an XDE document loaded from STEP.
|
|
8
|
+
// Used to reuse Read+Transfer across multiple output variants in one worker call.
|
|
9
|
+
struct StepXdeContext;
|
|
10
|
+
|
|
11
|
+
// Load STEP into an XDE document (preserves assembly hierarchy, names, colors).
|
|
12
|
+
// - logLevel: 0=quiet, 1=info, 2=debug
|
|
13
|
+
// - stats (optional) will be filled with msRead/msTransfer
|
|
14
|
+
StepXdeContext* LoadStepXdeContext(const std::string& inputPath,
|
|
15
|
+
bool readNames,
|
|
16
|
+
bool readColors,
|
|
17
|
+
ConvertStats* stats,
|
|
18
|
+
int logLevel);
|
|
19
|
+
|
|
20
|
+
void FreeStepXdeContext(StepXdeContext* ctx);
|
|
21
|
+
|
|
22
|
+
// Build a scene from a previously loaded XDE context.
|
|
23
|
+
// This performs meshing + triangle extraction and applies filtering.
|
|
24
|
+
SceneData ConvertStepXdeContextToScene(StepXdeContext* ctx,
|
|
25
|
+
double linearDeflection,
|
|
26
|
+
double angularDeflection,
|
|
27
|
+
bool smoothNormals,
|
|
28
|
+
double normalCreaseAngle,
|
|
29
|
+
double minBBoxDiagonal,
|
|
30
|
+
ConvertStats* stats,
|
|
31
|
+
int logLevel);
|
|
32
|
+
|
|
33
|
+
// Reads STEP and produces a scene graph preserving assembly hierarchy + names (UTF-8).
|
|
34
|
+
// Leaf nodes reference meshes generated from B-Rep tessellation.
|
|
35
|
+
SceneData ConvertSTEPToScene(const std::string& inputPath,
|
|
36
|
+
double linearDeflection,
|
|
37
|
+
double angularDeflection,
|
|
38
|
+
double minBBoxDiagonal,
|
|
39
|
+
ConvertStats* stats = nullptr);
|
|
40
|
+
|