Coverage Report

Created: 2026-06-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Step/StepExporter.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following conditions are met:
12
13
* Redistributions of source code must retain the above
14
  copyright notice, this list of conditions and the
15
  following disclaimer.
16
17
* Redistributions in binary form must reproduce the above
18
  copyright notice, this list of conditions and the
19
  following disclaimer in the documentation and/or other
20
  materials provided with the distribution.
21
22
* Neither the name of the assimp team, nor the names of its
23
  contributors may be used to endorse or promote products
24
  derived from this software without specific prior
25
  written permission of the assimp team.
26
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39
----------------------------------------------------------------------
40
*/
41
42
#ifndef ASSIMP_BUILD_NO_EXPORT
43
#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
44
45
#include "AssetLib/Step/StepExporter.h"
46
#include "PostProcessing/ConvertToLHProcess.h"
47
48
#include <assimp/Bitmap.h>
49
#include <assimp/BaseImporter.h>
50
#include <assimp/fast_atof.h>
51
#include <assimp/SceneCombiner.h>
52
#include <assimp/Exceptional.h>
53
#include <assimp/DefaultIOSystem.h>
54
#include <assimp/IOSystem.hpp>
55
#include <assimp/scene.h>
56
#include <assimp/light.h>
57
58
#include <iostream>
59
#include <ctime>
60
#include <set>
61
#include <map>
62
#include <list>
63
#include <memory>
64
65
//
66
#if _MSC_VER > 1500 || (defined __GNUC__)
67
#   define ASSIMP_STEP_USE_UNORDERED_MULTIMAP
68
#   else
69
#   define step_unordered_map map
70
#   define step_unordered_multimap multimap
71
#endif
72
73
#ifdef ASSIMP_STEP_USE_UNORDERED_MULTIMAP
74
#   include <unordered_map>
75
#   if defined(_MSC_VER) && _MSC_VER <= 1600
76
#       define step_unordered_map tr1::unordered_map
77
#       define step_unordered_multimap tr1::unordered_multimap
78
#   else
79
#       define step_unordered_map unordered_map
80
#       define step_unordered_multimap unordered_multimap
81
#   endif
82
#endif
83
84
typedef std::step_unordered_map<aiVector3D*, int> VectorIndexUMap;
85
86
/* Tested with Step viewer v4 from www.ida-step.net */
87
88
using namespace Assimp;
89
90
namespace Assimp
91
{
92
93
// ------------------------------------------------------------------------------------------------
94
// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
95
void ExportSceneStep(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
96
0
{
97
0
    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
98
0
    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
99
100
    // create/copy Properties
101
0
    ExportProperties props(*pProperties);
102
103
    // invoke the exporter
104
0
    StepExporter iDoTheExportThing( pScene, pIOSystem, path, file, &props);
105
106
    // we're still here - export successfully completed. Write result to the given IOSYstem
107
0
    std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
108
0
    if (outfile == nullptr) {
109
0
        throw DeadlyExportError("could not open output .stp file: " + std::string(pFile));
110
0
    }
111
112
    // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy.
113
0
    outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1);
114
0
}
115
116
} // end of namespace Assimp
117
118
119
namespace {
120
    // Collect world transformations for each node
121
0
    void CollectTrafos(const aiNode* node, std::map<const aiNode*, aiMatrix4x4>& trafos) {
122
0
        const aiMatrix4x4& parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4();
123
0
        trafos[node] = parent * node->mTransformation;
124
0
        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
125
0
            CollectTrafos(node->mChildren[i], trafos);
126
0
        }
127
0
    }
128
129
    // Generate a flat list of the meshes (by index) assigned to each node
130
0
    void CollectMeshes(const aiNode* node, std::multimap<const aiNode*, unsigned int>& meshes) {
131
0
        for (unsigned int i = 0; i < node->mNumMeshes; ++i) {
132
0
            meshes.insert(std::make_pair(node, node->mMeshes[i]));
133
0
        }
134
0
        for (unsigned int i = 0; i < node->mNumChildren; ++i) {
135
0
            CollectMeshes(node->mChildren[i], meshes);
136
0
        }
137
0
    }
138
}
139
140
// ------------------------------------------------------------------------------------------------
141
// Constructor for a specific scene to export
142
StepExporter::StepExporter(const aiScene* pScene, IOSystem* pIOSystem, const std::string& path,
143
    const std::string& file, const ExportProperties* pProperties) :
144
0
    mProperties(pProperties), mIOSystem(pIOSystem), mFile(file), mPath(path),
145
0
    mScene(pScene), endstr(";\n") {
146
0
    CollectTrafos(pScene->mRootNode, trafos);
147
0
    CollectMeshes(pScene->mRootNode, meshes);
148
149
    // make sure that all formatting happens using the standard, C locale and not the user's current locale
150
0
    mOutput.imbue(std::locale("C"));
151
0
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
152
153
    // start writing
154
0
    WriteFile();
155
0
}
156
157
// ------------------------------------------------------------------------------------------------
158
// Starts writing the contents
159
void StepExporter::WriteFile()
160
0
{
161
    // see http://shodhganga.inflibnet.ac.in:8080/jspui/bitstream/10603/14116/11/11_chapter%203.pdf
162
    // note, that all realnumber values must be comma separated in x files
163
0
    mOutput.setf(std::ios::fixed);
164
    // precision for double
165
    // see http://stackoverflow.com/questions/554063/how-do-i-print-a-double-value-with-full-precision-using-cout
166
0
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
167
168
    // standard color
169
0
    aiColor4D fColor;
170
0
    fColor.r = 0.8f;
171
0
    fColor.g = 0.8f;
172
0
    fColor.b = 0.8f;
173
174
0
    int ind = 100; // the start index to be used
175
0
    std::vector<int> faceEntryLen; // numbers of entries for a triangle/face
176
    // prepare unique (count triangles and vertices)
177
178
0
    VectorIndexUMap uniqueVerts; // use a map to reduce find complexity to log(n)
179
0
    VectorIndexUMap::iterator it;
180
181
0
    for (unsigned int i=0; i<mScene->mNumMeshes; ++i)
182
0
    {
183
0
        aiMesh* mesh = mScene->mMeshes[i];
184
0
        for (unsigned int j=0; j<mesh->mNumFaces; ++j)
185
0
        {
186
0
            aiFace* face = &(mesh->mFaces[j]);
187
188
0
            if (face->mNumIndices >= 3) faceEntryLen.push_back(15 + 5 * face->mNumIndices);
189
0
        }
190
0
        for (unsigned int j=0; j<mesh->mNumVertices; ++j)
191
0
        {
192
0
            aiVector3D* v = &(mesh->mVertices[j]);
193
0
            it =uniqueVerts.find(v);
194
0
            if (it == uniqueVerts.end())
195
0
            {
196
0
                uniqueVerts[v] = -1; // first mark the vector as not transformed
197
0
            }
198
0
        }
199
0
    }
200
201
0
    static const unsigned int date_nb_chars = 20;
202
0
    char date_str[date_nb_chars];
203
0
    std::time_t date = std::time(nullptr);
204
0
    std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
205
206
    // write the header
207
0
    mOutput << "ISO-10303-21" << endstr;
208
0
    mOutput << "HEADER" << endstr;
209
0
    mOutput << "FILE_DESCRIPTION(('STEP AP214'),'1')" << endstr;
210
0
    mOutput << "FILE_NAME('" << mFile << ".stp','" << date_str << "',(' '),(' '),'Spatial InterOp 3D',' ',' ')" << endstr;
211
0
    mOutput << "FILE_SCHEMA(('automotive_design'))" << endstr;
212
0
    mOutput << "ENDSEC" << endstr;
213
214
    // write the top of data
215
0
    mOutput << "DATA" << endstr;
216
0
    mOutput << "#1=MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION(' ',(";
217
0
    size_t countFace = faceEntryLen.size();
218
0
    size_t faceLenIndex = ind + 2 * uniqueVerts.size();
219
0
    for (size_t i=0; i<countFace; ++i)
220
0
    {
221
0
        mOutput << "#" << faceLenIndex;
222
0
        if (i!=countFace-1) mOutput << ",";
223
0
        faceLenIndex += faceEntryLen[i];
224
0
    }
225
0
    mOutput << "),#6)" << endstr;
226
227
0
    mOutput << "#2=PRODUCT_DEFINITION_CONTEXT('',#7,'design')" << endstr;
228
0
    mOutput << "#3=APPLICATION_PROTOCOL_DEFINITION('INTERNATIONAL STANDARD','automotive_design',1994,#7)" << endstr;
229
0
    mOutput << "#4=PRODUCT_CATEGORY_RELATIONSHIP('NONE','NONE',#8,#9)" << endstr;
230
0
    mOutput << "#5=SHAPE_DEFINITION_REPRESENTATION(#10,#11)" << endstr;
231
0
    mOutput << "#6= (GEOMETRIC_REPRESENTATION_CONTEXT(3)GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#12))GLOBAL_UNIT_ASSIGNED_CONTEXT((#13,#14,#15))REPRESENTATION_CONTEXT('NONE','WORKSPACE'))" << endstr;
232
0
    mOutput << "#7=APPLICATION_CONTEXT(' ')" << endstr;
233
0
    mOutput << "#8=PRODUCT_CATEGORY('part','NONE')" << endstr;
234
0
    mOutput << "#9=PRODUCT_RELATED_PRODUCT_CATEGORY('detail',' ',(#17))" << endstr;
235
0
    mOutput << "#10=PRODUCT_DEFINITION_SHAPE('NONE','NONE',#18)" << endstr;
236
0
    mOutput << "#11=MANIFOLD_SURFACE_SHAPE_REPRESENTATION('Root',(#16,#19),#6)" << endstr;
237
0
    mOutput << "#12=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.0E-006),#13,'','')" << endstr;
238
0
    mOutput << "#13=(CONVERSION_BASED_UNIT('METRE',#20)LENGTH_UNIT()NAMED_UNIT(#21))" << endstr;
239
0
    mOutput << "#14=(NAMED_UNIT(#22)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.))" << endstr;
240
0
    mOutput << "#15=(NAMED_UNIT(#22)SOLID_ANGLE_UNIT()SI_UNIT($,.STERADIAN.))" << endstr;
241
0
    mOutput << "#16=SHELL_BASED_SURFACE_MODEL('Root',(#29))" << endstr;
242
0
    mOutput << "#17=PRODUCT('Root','Root','Root',(#23))" << endstr;
243
0
    mOutput << "#18=PRODUCT_DEFINITION('NONE','NONE',#24,#2)" << endstr;
244
0
    mOutput << "#19=AXIS2_PLACEMENT_3D('',#25,#26,#27)" << endstr;
245
0
    mOutput << "#20=LENGTH_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.0),#28)" << endstr;
246
0
    mOutput << "#21=DIMENSIONAL_EXPONENTS(1.0,0.0,0.0,0.0,0.0,0.0,0.0)" << endstr;
247
0
    mOutput << "#22=DIMENSIONAL_EXPONENTS(0.0,0.0,0.0,0.0,0.0,0.0,0.0)" << endstr;
248
0
    mOutput << "#23=PRODUCT_CONTEXT('',#7,'mechanical')" << endstr;
249
0
    mOutput << "#24=PRODUCT_DEFINITION_FORMATION_WITH_SPECIFIED_SOURCE(' ','NONE',#17,.NOT_KNOWN.)" << endstr;
250
0
    mOutput << "#25=CARTESIAN_POINT('',(0.0,0.0,0.0))" << endstr;
251
0
    mOutput << "#26=DIRECTION('',(0.0,0.0,1.0))" << endstr;
252
0
    mOutput << "#27=DIRECTION('',(1.0,0.0,0.0))" << endstr;
253
0
    mOutput << "#28= (NAMED_UNIT(#21)LENGTH_UNIT()SI_UNIT(.MILLI.,.METRE.))" << endstr;
254
0
    mOutput << "#29=CLOSED_SHELL('',(";
255
0
    faceLenIndex = ind + 2 * uniqueVerts.size() + 8;
256
0
    for (size_t i=0; i<countFace; ++i)
257
0
    {
258
0
        mOutput << "#" << faceLenIndex;
259
0
        if (i!=countFace-1) mOutput << ",";
260
0
        faceLenIndex += faceEntryLen[i];
261
0
    }
262
0
    mOutput << "))" << endstr;
263
264
    // write all the unique transformed CARTESIAN and VERTEX
265
0
    for (MeshesByNodeMap::const_iterator it2 = meshes.begin(); it2 != meshes.end(); ++it2)
266
0
    {
267
0
        const aiNode& node = *(*it2).first;
268
0
        unsigned int mesh_idx = (*it2).second;
269
270
0
        const aiMesh* mesh = mScene->mMeshes[mesh_idx];
271
0
        aiMatrix4x4& trafo = trafos[&node];
272
0
        for (unsigned int i = 0; i < mesh->mNumVertices; ++i)
273
0
        {
274
0
            aiVector3D* v = &(mesh->mVertices[i]);
275
0
            it = uniqueVerts.find(v);
276
0
            if (it->second >=0 ) continue;
277
0
            it->second = ind; // this one is new, so set the index (ind)
278
0
            aiVector3D vt = trafo * (*v); // transform the coordinate
279
0
            mOutput << "#" << it->second << "=CARTESIAN_POINT('',(" << vt.x << "," << vt.y << "," << vt.z << "))" << endstr;
280
0
            mOutput << "#" << it->second+1 << "=VERTEX_POINT('',#" << it->second << ")" << endstr;
281
0
            ind += 2;
282
0
        }
283
0
    }
284
285
    // write the triangles
286
0
    for (unsigned int i=0; i<mScene->mNumMeshes; ++i)
287
0
    {
288
0
        aiMesh* mesh = mScene->mMeshes[i];
289
0
        for (unsigned int j=0; j<mesh->mNumFaces; ++j)
290
0
        {
291
0
            aiFace* face = &(mesh->mFaces[j]);
292
293
0
            const int numIndices = face->mNumIndices;
294
0
            if (numIndices < 3) continue;
295
296
0
            std::vector<int> pidArray(numIndices, -1); // vertex id
297
0
            std::vector<aiVector3D> dvArray(numIndices); // edge dir
298
0
            for (int k = 0; k < numIndices; ++k)
299
0
            {
300
0
                aiVector3D *v1 = &(mesh->mVertices[face->mIndices[k]]);
301
0
                pidArray[k] = uniqueVerts.find(v1)->second;
302
303
0
                aiVector3D *v2 = nullptr;
304
0
                if (k + 1 == numIndices)
305
0
                    v2 = &(mesh->mVertices[face->mIndices[0]]);
306
0
                else
307
0
                    v2 = &(mesh->mVertices[face->mIndices[k + 1]]);
308
0
                dvArray[k] = *v2 - *v1;
309
0
                dvArray[k].Normalize();
310
0
            }
311
312
0
            aiVector3D dvY = dvArray[1];
313
0
            aiVector3D dvX = dvY ^ dvArray[0];
314
0
            dvX.Normalize();
315
316
            // mean vertex color for the face if available
317
0
            if (mesh->HasVertexColors(0))
318
0
            {
319
0
                fColor.r = 0.0;
320
0
                fColor.g = 0.0;
321
0
                fColor.b = 0.0;
322
0
                fColor += mesh->mColors[0][face->mIndices[0]];
323
0
                fColor += mesh->mColors[0][face->mIndices[1]];
324
0
                fColor += mesh->mColors[0][face->mIndices[2]];
325
0
                fColor /= 3.0f;
326
0
            }
327
328
0
            int sid = ind; // the sub index
329
0
            mOutput << "#" << sid << "=STYLED_ITEM('',(#" << sid+1 << "),#" << sid+8 << ")" << endstr; /* the item that must be referenced in #1 */
330
            /* This is the color information of the Triangle */
331
0
            mOutput << "#" << sid+1 << "=PRESENTATION_STYLE_ASSIGNMENT((#" << sid+2 << "))" << endstr;
332
0
            mOutput << "#" << sid+2 << "=SURFACE_STYLE_USAGE(.BOTH.,#" << sid+3 << ")" << endstr;
333
0
            mOutput << "#" << sid+3 << "=SURFACE_SIDE_STYLE('',(#" << sid+4 << "))" << endstr;
334
0
            mOutput << "#" << sid+4 << "=SURFACE_STYLE_FILL_AREA(#" << sid+5 << ")" << endstr;
335
0
            mOutput << "#" << sid+5 << "=FILL_AREA_STYLE('',(#" << sid+6 << "))" << endstr;
336
0
            mOutput << "#" << sid+6 << "=FILL_AREA_STYLE_COLOUR('',#" << sid+7 << ")" << endstr;
337
0
            mOutput << "#" << sid+7 << "=COLOUR_RGB(''," << fColor.r << "," << fColor.g << "," << fColor.b << ")" << endstr;
338
339
            /* this is the geometry */
340
0
            mOutput << "#" << sid+8 << "=FACE_SURFACE('',(#" << sid+13 << "),#" << sid+9<< ",.T.)" << endstr; /* the face that must be referenced in 29 */
341
342
            /* 2 directions of the plane */
343
0
            mOutput << "#" << sid+9 << "=PLANE('',#" << sid+10 << ")" << endstr;
344
0
            mOutput << "#" << sid+10 << "=AXIS2_PLACEMENT_3D('',#" << pidArray[0] << ",#" << sid+11 << ",#" << sid+12 << ")" << endstr;
345
346
0
            mOutput << "#" << sid + 11 << "=DIRECTION('',(" << dvX.x << "," << dvX.y << "," << dvX.z << "))" << endstr;
347
0
            mOutput << "#" << sid + 12 << "=DIRECTION('',(" << dvY.x << "," << dvY.y << "," << dvY.z << "))" << endstr;
348
349
0
            mOutput << "#" << sid+13 << "=FACE_BOUND('',#" << sid+14 << ",.T.)" << endstr;
350
0
            mOutput << "#" << sid+14 << "=EDGE_LOOP('',(";
351
0
            int edgeLoopStart = sid + 15;
352
0
            for (int k = 0; k < numIndices; ++k)
353
0
            {
354
0
                if (k == 0)
355
0
                    mOutput << "#";
356
0
                else
357
0
                    mOutput << ",#";
358
0
                mOutput << edgeLoopStart + k;
359
0
            }
360
0
            mOutput << "))" << endstr;
361
362
            /* edge loop  */
363
0
            int orientedEdgesStart = edgeLoopStart + numIndices;
364
0
            for (int k=0; k < numIndices; k++)
365
0
            {
366
0
                mOutput << "#" << edgeLoopStart+k << "=ORIENTED_EDGE('',*,*,#" << orientedEdgesStart + k << ",.T.)" << endstr;
367
0
            }
368
369
            /* oriented edges */
370
0
            int lineStart = orientedEdgesStart + numIndices;
371
0
            for (int k=0; k < numIndices; ++k)
372
0
            {
373
0
                if (k == 0)
374
0
                    mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.F.)" << endstr;
375
0
                else if (k+1 == numIndices)
376
0
                    mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[0]+1 << ",#" << lineStart+k << ",.T.)" << endstr;
377
0
                else
378
0
                    mOutput << "#" << orientedEdgesStart+k << "=EDGE_CURVE('',#" << pidArray[k]+1 << ",#" << pidArray[k+1]+1 << ",#" << lineStart+k << ",.T.)" << endstr;
379
0
            }
380
381
            /* n lines and n vectors for the lines for the n edge curves */
382
0
            int vectorStart = lineStart + numIndices;
383
0
            for (int k=0; k < numIndices; ++k)
384
0
            {
385
0
                mOutput << "#" << lineStart+k << "=LINE('',#" << pidArray[k] << ",#" << vectorStart+k << ")" << endstr;
386
0
            }
387
388
0
            int directionStart = vectorStart + numIndices;
389
0
            for (int k=0; k < numIndices; ++k)
390
0
            {
391
0
                mOutput << "#" << vectorStart+k << "=VECTOR('',#" << directionStart+k << ",1.0)" << endstr;
392
0
            }
393
394
0
            for (int k=0; k < numIndices; ++k)
395
0
            {
396
0
                const aiVector3D &dv = dvArray[k];
397
0
                mOutput << "#" << directionStart + k << "=DIRECTION('',(" << dv.x << "," << dv.y << "," << dv.z << "))" << endstr;
398
0
            }
399
0
            ind += 15 + 5*numIndices; // increase counter
400
0
        }
401
0
    }
402
403
0
    mOutput << "ENDSEC" << endstr; // end of data section
404
0
    mOutput << "END-ISO-10303-21" << endstr; // end of file
405
0
}
406
407
#endif
408
#endif