Coverage Report

Created: 2026-10-28 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/3MF/D3MFExporter.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
#ifndef ASSIMP_BUILD_NO_EXPORT
42
#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
43
44
#include "D3MFExporter.h"
45
46
#include <assimp/Exceptional.h>
47
#include <assimp/StringUtils.h>
48
#include <assimp/scene.h>
49
#include <assimp/DefaultLogger.hpp>
50
#include <assimp/Exporter.hpp>
51
#include <assimp/IOSystem.hpp>
52
53
#include "3MFXmlTags.h"
54
#include "D3MFOpcPackage.h"
55
56
#ifdef ASSIMP_USE_HUNTER
57
#include <zip/zip.h>
58
#else
59
#include <contrib/zip/src/zip.h>
60
#endif
61
62
namespace Assimp {
63
64
1.81k
void ExportScene3MF(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) {
65
1.81k
    if (nullptr == pIOSystem) {
66
0
        throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile));
67
0
    }
68
1.81k
    D3MF::D3MFExporter myExporter(pFile, pScene);
69
1.81k
    if (myExporter.validate()) {
70
1.81k
        if (pIOSystem->Exists(pFile)) {
71
0
            if (!pIOSystem->DeleteFile(pFile)) {
72
0
                throw DeadlyExportError("File exists, cannot override : " + std::string(pFile));
73
0
            }
74
0
        }
75
1.81k
        bool ok = myExporter.exportArchive(pFile);
76
1.81k
        if (!ok) {
77
0
            throw DeadlyExportError("Could not export 3MP archive: " + std::string(pFile));
78
0
        }
79
1.81k
    }
80
1.81k
}
81
82
namespace D3MF {
83
84
D3MFExporter::D3MFExporter(const char *pFile, const aiScene *pScene) :
85
1.81k
        mArchiveName(pFile), mScene(pScene) {
86
    // empty
87
1.81k
}
88
89
1.81k
D3MFExporter::~D3MFExporter() {
90
3.63k
    for (size_t i = 0; i < mRelations.size(); ++i) {
91
1.81k
        delete mRelations[i];
92
1.81k
    }
93
1.81k
    mRelations.clear();
94
1.81k
}
95
96
1.81k
bool D3MFExporter::validate() const {
97
1.81k
    if (mArchiveName.empty()) {
98
0
        return false;
99
0
    }
100
101
1.81k
    if (nullptr == mScene) {
102
0
        return false;
103
0
    }
104
105
1.81k
    return true;
106
1.81k
}
107
108
1.81k
bool D3MFExporter::exportArchive(const char *file) {
109
1.81k
    bool ok(true);
110
111
1.81k
    mZipArchive = zip_open(file, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
112
1.81k
    if (nullptr == mZipArchive) {
113
0
        return false;
114
0
    }
115
116
1.81k
    ok |= exportContentTypes();
117
1.81k
    ok |= export3DModel();
118
1.81k
    ok |= exportRelations();
119
120
1.81k
    zip_close(mZipArchive);
121
1.81k
    mZipArchive = nullptr;
122
123
1.81k
    return ok;
124
1.81k
}
125
126
1.81k
bool D3MFExporter::exportContentTypes() {
127
1.81k
    mContentOutput.clear();
128
129
1.81k
    mContentOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
130
1.81k
    mContentOutput << std::endl;
131
1.81k
    mContentOutput << "<Types xmlns = \"http://schemas.openxmlformats.org/package/2006/content-types\">";
132
1.81k
    mContentOutput << std::endl;
133
1.81k
    mContentOutput << "<Default Extension = \"rels\" ContentType = \"application/vnd.openxmlformats-package.relationships+xml\" />";
134
1.81k
    mContentOutput << std::endl;
135
1.81k
    mContentOutput << "<Default Extension = \"model\" ContentType = \"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />";
136
1.81k
    mContentOutput << std::endl;
137
1.81k
    mContentOutput << "</Types>";
138
1.81k
    mContentOutput << std::endl;
139
1.81k
    zipContentType(XmlTag::CONTENT_TYPES_ARCHIVE);
140
141
1.81k
    return true;
142
1.81k
}
143
144
1.81k
bool D3MFExporter::exportRelations() {
145
1.81k
    mRelOutput.clear();
146
147
1.81k
    mRelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
148
1.81k
    mRelOutput << std::endl;
149
1.81k
    mRelOutput << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">";
150
151
3.63k
    for (size_t i = 0; i < mRelations.size(); ++i) {
152
1.81k
        if (mRelations[i]->target[0] == '/') {
153
1.81k
            mRelOutput << "<Relationship Target=\"" << mRelations[i]->target << "\" ";
154
1.81k
        } else {
155
0
            mRelOutput << "<Relationship Target=\"/" << mRelations[i]->target << "\" ";
156
0
        }
157
1.81k
        mRelOutput << "Id=\"" << mRelations[i]->id << "\" ";
158
1.81k
        mRelOutput << "Type=\"" << mRelations[i]->type << "\" />";
159
1.81k
        mRelOutput << std::endl;
160
1.81k
    }
161
1.81k
    mRelOutput << "</Relationships>";
162
1.81k
    mRelOutput << std::endl;
163
164
1.81k
    zipRelInfo("_rels", ".rels");
165
1.81k
    mRelOutput.flush();
166
167
1.81k
    return true;
168
1.81k
}
169
170
1.81k
bool D3MFExporter::export3DModel() {
171
1.81k
    mModelOutput.clear();
172
173
1.81k
    writeHeader();
174
1.81k
    mModelOutput << "<" << XmlTag::model << " " << XmlTag::model_unit << "=\"millimeter\""
175
1.81k
                 << " xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">"
176
1.81k
                 << std::endl;
177
1.81k
    mModelOutput << "<" << XmlTag::resources << ">";
178
1.81k
    mModelOutput << std::endl;
179
180
1.81k
    writeMetaData();
181
182
1.81k
    writeBaseMaterials();
183
184
1.81k
    writeObjects();
185
186
1.81k
    mModelOutput << "</" << XmlTag::resources << ">";
187
1.81k
    mModelOutput << std::endl;
188
1.81k
    writeBuild();
189
190
1.81k
    mModelOutput << "</" << XmlTag::model << ">\n";
191
192
1.81k
    OpcPackageRelationship *info = new OpcPackageRelationship;
193
1.81k
    info->id = "rel0";
194
1.81k
    info->target = "/3D/3DModel.model";
195
1.81k
    info->type = XmlTag::PACKAGE_START_PART_RELATIONSHIP_TYPE;
196
1.81k
    mRelations.push_back(info);
197
198
1.81k
    zipModel("3D", "3DModel.model");
199
1.81k
    mModelOutput.flush();
200
201
1.81k
    return true;
202
1.81k
}
203
204
1.81k
void D3MFExporter::writeHeader() {
205
1.81k
    mModelOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
206
1.81k
    mModelOutput << std::endl;
207
1.81k
}
208
209
1.81k
void D3MFExporter::writeMetaData() {
210
1.81k
    if (nullptr == mScene->mMetaData) {
211
0
        return;
212
0
    }
213
214
1.81k
    const unsigned int numMetaEntries(mScene->mMetaData->mNumProperties);
215
1.81k
    if (0 == numMetaEntries) {
216
0
        return;
217
0
    }
218
219
1.81k
    const aiString *key = nullptr;
220
1.81k
    const aiMetadataEntry *entry(nullptr);
221
11.8k
    for (size_t i = 0; i < numMetaEntries; ++i) {
222
10.0k
        mScene->mMetaData->Get(i, key, entry);
223
10.0k
        std::string k(key->C_Str());
224
10.0k
        aiString value;
225
10.0k
        mScene->mMetaData->Get(k, value);
226
10.0k
        mModelOutput << "<" << XmlTag::meta << " " << XmlTag::meta_name << "=\"" << key->C_Str() << "\">";
227
10.0k
        mModelOutput << value.C_Str();
228
10.0k
        mModelOutput << "</" << XmlTag::meta << ">" << std::endl;
229
10.0k
    }
230
1.81k
}
231
232
1.81k
void D3MFExporter::writeBaseMaterials() {
233
1.81k
    mModelOutput << "<basematerials id=\"1\">\n";
234
1.81k
    std::string strName, hexDiffuseColor, tmp;
235
3.82k
    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
236
2.00k
        aiMaterial *mat = mScene->mMaterials[i];
237
2.00k
        aiString name;
238
2.00k
        if (mat->Get(AI_MATKEY_NAME, name) != aiReturn_SUCCESS) {
239
359
            strName = "basemat_" + ai_to_string(i);
240
1.65k
        } else {
241
1.65k
            strName = name.C_Str();
242
1.65k
        }
243
2.00k
        aiColor4D color;
244
2.00k
        if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS) {
245
1.80k
            hexDiffuseColor.clear();
246
1.80k
            tmp.clear();
247
            // rgbs %
248
1.80k
            if (color.r <= 1 && color.g <= 1 && color.b <= 1 && color.a <= 1) {
249
250
1.77k
                hexDiffuseColor = ai_rgba2hex(
251
1.77k
                        (int)(((ai_real)color.r) * 255),
252
1.77k
                        (int)(((ai_real)color.g) * 255),
253
1.77k
                        (int)(((ai_real)color.b) * 255),
254
1.77k
                        (int)(((ai_real)color.a) * 255),
255
1.77k
                        true);
256
257
1.77k
            } else {
258
25
                hexDiffuseColor = "#";
259
25
                tmp = ai_decimal_to_hexa((ai_real)color.r);
260
25
                hexDiffuseColor += tmp;
261
25
                tmp = ai_decimal_to_hexa((ai_real)color.g);
262
25
                hexDiffuseColor += tmp;
263
25
                tmp = ai_decimal_to_hexa((ai_real)color.b);
264
25
                hexDiffuseColor += tmp;
265
25
                tmp = ai_decimal_to_hexa((ai_real)color.a);
266
25
                hexDiffuseColor += tmp;
267
25
            }
268
1.80k
        } else {
269
205
            hexDiffuseColor = "#FFFFFFFF";
270
205
        }
271
272
2.00k
        mModelOutput << "<base name=\"" + strName + "\" " + " displaycolor=\"" + hexDiffuseColor + "\" />\n";
273
2.00k
    }
274
1.81k
    mModelOutput << "</basematerials>\n";
275
1.81k
}
276
277
1.81k
void D3MFExporter::writeObjects() {
278
1.81k
    if (nullptr == mScene->mRootNode) {
279
0
        return;
280
0
    }
281
282
1.81k
    aiNode *root = mScene->mRootNode;
283
91.4k
    for (unsigned int i = 0; i < root->mNumChildren; ++i) {
284
89.6k
        aiNode *currentNode(root->mChildren[i]);
285
89.6k
        if (nullptr == currentNode) {
286
0
            continue;
287
0
        }
288
89.6k
        mModelOutput << "<" << XmlTag::object << " id=\"" << i + 2 << "\" type=\"model\">";
289
89.6k
        mModelOutput << std::endl;
290
108k
        for (unsigned int j = 0; j < currentNode->mNumMeshes; ++j) {
291
18.9k
            aiMesh *currentMesh = mScene->mMeshes[currentNode->mMeshes[j]];
292
18.9k
            if (nullptr == currentMesh) {
293
0
                continue;
294
0
            }
295
18.9k
            writeMesh(currentMesh);
296
18.9k
        }
297
89.6k
        mBuildItems.push_back(i);
298
299
89.6k
        mModelOutput << "</" << XmlTag::object << ">";
300
89.6k
        mModelOutput << std::endl;
301
89.6k
    }
302
1.81k
}
303
304
18.9k
void D3MFExporter::writeMesh(aiMesh *mesh) {
305
18.9k
    if (nullptr == mesh) {
306
0
        return;
307
0
    }
308
309
18.9k
    mModelOutput << "<"
310
18.9k
                 << XmlTag::mesh
311
18.9k
                 << ">" << "\n";
312
18.9k
    mModelOutput << "<"
313
18.9k
                 << XmlTag::vertices
314
18.9k
                 << ">" << "\n";
315
293k
    for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
316
274k
        writeVertex(mesh->mVertices[i]);
317
274k
    }
318
18.9k
    mModelOutput << "</"
319
18.9k
                 << XmlTag::vertices << ">"
320
18.9k
                 << "\n";
321
322
18.9k
    const unsigned int matIdx(mesh->mMaterialIndex);
323
324
18.9k
    writeFaces(mesh, matIdx);
325
326
18.9k
    mModelOutput << "</"
327
18.9k
                 << XmlTag::mesh << ">"
328
18.9k
                 << "\n";
329
18.9k
}
330
331
274k
void D3MFExporter::writeVertex(const aiVector3D &pos) {
332
274k
    mModelOutput << "<" << XmlTag::vertex << " x=\"" << pos.x << "\" y=\"" << pos.y << "\" z=\"" << pos.z << "\" />";
333
274k
    mModelOutput << std::endl;
334
274k
}
335
336
18.9k
void D3MFExporter::writeFaces(aiMesh *mesh, unsigned int matIdx) {
337
18.9k
    if (nullptr == mesh) {
338
0
        return;
339
0
    }
340
341
18.9k
    if (!mesh->HasFaces()) {
342
0
        return;
343
0
    }
344
18.9k
    mModelOutput << "<"
345
18.9k
                 << XmlTag::triangles << ">"
346
18.9k
                 << "\n";
347
303k
    for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {
348
284k
        aiFace &currentFace = mesh->mFaces[i];
349
        // A 3MF triangle has exactly three vertices; points and lines have no
350
        // representation in it, and reading three indices from them overruns.
351
284k
        if (currentFace.mNumIndices != 3) {
352
7.88k
            continue;
353
7.88k
        }
354
276k
        mModelOutput << "<" << XmlTag::triangle << " v1=\"" << currentFace.mIndices[0] << "\" v2=\""
355
276k
                     << currentFace.mIndices[1] << "\" v3=\"" << currentFace.mIndices[2]
356
276k
                     << "\" pid=\"1\" p1=\"" + ai_to_string(matIdx) + "\" />";
357
276k
        mModelOutput << "\n";
358
276k
    }
359
18.9k
    mModelOutput << "</"
360
18.9k
                 << XmlTag::triangles
361
18.9k
                 << ">";
362
18.9k
    mModelOutput << "\n";
363
18.9k
}
364
365
1.81k
void D3MFExporter::writeBuild() {
366
1.81k
    mModelOutput << "<"
367
1.81k
                 << XmlTag::build
368
1.81k
                 << ">"
369
1.81k
                 << "\n";
370
371
91.4k
    for (size_t i = 0; i < mBuildItems.size(); ++i) {
372
89.6k
        mModelOutput << "<" << XmlTag::item << " objectid=\"" << i + 2 << "\"/>";
373
89.6k
        mModelOutput << "\n";
374
89.6k
    }
375
1.81k
    mModelOutput << "</" << XmlTag::build << ">";
376
1.81k
    mModelOutput << "\n";
377
1.81k
}
378
379
1.81k
void D3MFExporter::zipContentType(const std::string &filename) {
380
1.81k
    addFileInZip(filename, mContentOutput.str());
381
1.81k
}
382
383
1.81k
void D3MFExporter::zipModel(const std::string &folder, const std::string &modelName) {
384
1.81k
    const std::string entry = folder + "/" + modelName;
385
1.81k
    addFileInZip(entry, mModelOutput.str());
386
1.81k
}
387
388
1.81k
void D3MFExporter::zipRelInfo(const std::string &folder, const std::string &relName) {
389
1.81k
    const std::string entry = folder + "/" + relName;
390
1.81k
    addFileInZip(entry, mRelOutput.str());
391
1.81k
}
392
393
5.45k
void D3MFExporter::addFileInZip(const std::string& entry, const std::string& content) {
394
5.45k
    if (nullptr == mZipArchive) {
395
0
        throw DeadlyExportError("3MF-Export: Zip archive not valid, nullptr.");
396
0
    }
397
398
5.45k
    zip_entry_open(mZipArchive, entry.c_str());
399
5.45k
    zip_entry_write(mZipArchive, content.c_str(), content.size());
400
5.45k
    zip_entry_close(mZipArchive);
401
5.45k
}
402
403
} // Namespace D3MF
404
} // Namespace Assimp
405
406
#endif // ASSIMP_BUILD_NO_3MF_EXPORTER
407
#endif // ASSIMP_BUILD_NO_EXPORT