Coverage Report

Created: 2024-08-02 07:04

/src/assimp/code/AssetLib/AMF/AMFImporter.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2024, assimp team
7
8
All rights reserved.
9
10
Redistribution and use of this software in source and binary forms,
11
with or without modification, are permitted provided that the following
12
conditions are met:
13
14
* Redistributions of source code must retain the above
15
copyright notice, this list of conditions and the
16
following disclaimer.
17
18
* Redistributions in binary form must reproduce the above
19
copyright notice, this list of conditions and the
20
following disclaimer in the documentation and/or other
21
materials provided with the distribution.
22
23
* Neither the name of the assimp team, nor the names of its
24
contributors may be used to endorse or promote products
25
derived from this software without specific prior
26
written permission of the assimp team.
27
28
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
---------------------------------------------------------------------------
40
*/
41
42
#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
43
44
// Header files, Assimp.
45
#include "AMFImporter.hpp"
46
47
#include <assimp/DefaultIOSystem.h>
48
#include <assimp/fast_atof.h>
49
#include <assimp/StringUtils.h>
50
51
// Header files, stdlib.
52
#include <memory>
53
54
namespace Assimp {
55
56
static constexpr aiImporterDesc Description = {
57
    "Additive manufacturing file format(AMF) Importer",
58
    "smalcom",
59
    "",
60
    "See documentation in source code. Chapter: Limitations.",
61
    aiImporterFlags_SupportTextFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental,
62
    0,
63
    0,
64
    0,
65
    0,
66
    "amf"
67
};
68
69
33
void AMFImporter::Clear() {
70
33
    mNodeElement_Cur = nullptr;
71
33
    mUnit.clear();
72
33
    mMaterial_Converted.clear();
73
33
    mTexture_Converted.clear();
74
    // Delete all elements
75
33
    if (!mNodeElement_List.empty()) {
76
0
        for (AMFNodeElementBase *ne : mNodeElement_List) {
77
0
            delete ne;
78
0
        }
79
80
0
        mNodeElement_List.clear();
81
0
    }
82
33
}
83
84
AMFImporter::AMFImporter() AI_NO_EXCEPT :
85
        mNodeElement_Cur(nullptr),
86
33
        mXmlParser(nullptr) {
87
    // empty
88
33
}
89
90
33
AMFImporter::~AMFImporter() {
91
33
    delete mXmlParser;
92
    // Clear() is accounting if data already is deleted. So, just check again if all data is deleted.
93
33
    Clear();
94
33
}
95
96
/*********************************************************************************************************************************************/
97
/************************************************************ Functions: find set ************************************************************/
98
/*********************************************************************************************************************************************/
99
100
0
bool AMFImporter::Find_NodeElement(const std::string &pID, const AMFNodeElementBase::EType pType, AMFNodeElementBase **pNodeElement) const {
101
0
    for (AMFNodeElementBase *ne : mNodeElement_List) {
102
0
        if ((ne->ID == pID) && (ne->Type == pType)) {
103
0
            if (pNodeElement != nullptr) {
104
0
                *pNodeElement = ne;
105
0
            }
106
107
0
            return true;
108
0
        }
109
0
    } // for(CAMFImporter_NodeElement* ne: mNodeElement_List)
110
111
0
    return false;
112
0
}
113
114
0
bool AMFImporter::Find_ConvertedNode(const std::string &pID, NodeArray &nodeArray, aiNode **pNode) const {
115
0
    aiString node_name(pID.c_str());
116
0
    for (aiNode *node : nodeArray) {
117
0
        if (node->mName == node_name) {
118
0
            if (pNode != nullptr) {
119
0
                *pNode = node;
120
0
            }
121
122
0
            return true;
123
0
        }
124
0
    } // for(aiNode* node: pNodeList)
125
126
0
    return false;
127
0
}
128
129
0
bool AMFImporter::Find_ConvertedMaterial(const std::string &pID, const SPP_Material **pConvertedMaterial) const {
130
0
    for (const SPP_Material &mat : mMaterial_Converted) {
131
0
        if (mat.ID == pID) {
132
0
            if (pConvertedMaterial != nullptr) {
133
0
                *pConvertedMaterial = &mat;
134
0
            }
135
136
0
            return true;
137
0
        }
138
0
    } // for(const SPP_Material& mat: mMaterial_Converted)
139
140
0
    return false;
141
0
}
142
143
/*********************************************************************************************************************************************/
144
/************************************************************ Functions: throw set ***********************************************************/
145
/*********************************************************************************************************************************************/
146
147
0
void AMFImporter::Throw_CloseNotFound(const std::string &nodeName) {
148
0
    throw DeadlyImportError("Close tag for node <" + nodeName + "> not found. Seems file is corrupt.");
149
0
}
150
151
0
void AMFImporter::Throw_IncorrectAttr(const std::string &nodeName, const std::string &attrName) {
152
0
    throw DeadlyImportError("Node <" + nodeName + "> has incorrect attribute \"" + attrName + "\".");
153
0
}
154
155
0
void AMFImporter::Throw_IncorrectAttrValue(const std::string &nodeName, const std::string &attrName) {
156
0
    throw DeadlyImportError("Attribute \"" + attrName + "\" in node <" + nodeName + "> has incorrect value.");
157
0
}
158
159
0
void AMFImporter::Throw_MoreThanOnceDefined(const std::string &nodeName, const std::string &pNodeType, const std::string &pDescription) {
160
0
    throw DeadlyImportError("\"" + pNodeType + "\" node can be used only once in " + nodeName + ". Description: " + pDescription);
161
0
}
162
163
0
void AMFImporter::Throw_ID_NotFound(const std::string &pID) const {
164
0
    throw DeadlyImportError("Not found node with name \"", pID, "\".");
165
0
}
166
167
/*********************************************************************************************************************************************/
168
/************************************************************* Functions: XML set ************************************************************/
169
/*********************************************************************************************************************************************/
170
171
0
void AMFImporter::XML_CheckNode_MustHaveChildren(pugi::xml_node &node) {
172
0
    if (node.children().begin() == node.children().end()) {
173
0
        throw DeadlyImportError(std::string("Node <") + node.name() + "> must have children.");
174
0
    }
175
0
}
176
177
0
bool AMFImporter::XML_SearchNode(const std::string &nodeName) {
178
0
    return nullptr != mXmlParser->findNode(nodeName);
179
0
}
180
181
0
static bool ParseHelper_Decode_Base64_IsBase64(const char pChar) {
182
0
    return (isalnum((unsigned char)pChar) || (pChar == '+') || (pChar == '/'));
183
0
}
184
185
0
void AMFImporter::ParseHelper_Decode_Base64(const std::string &pInputBase64, std::vector<uint8_t> &pOutputData) const {
186
    // With help from
187
    // René Nyffenegger http://www.adp-gmbh.ch/cpp/common/base64.html
188
0
    const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
189
190
0
    uint8_t tidx = 0;
191
0
    uint8_t arr4[4], arr3[3];
192
193
    // check input data
194
0
    if (pInputBase64.size() % 4) {
195
0
        throw DeadlyImportError("Base64-encoded data must have size multiply of four.");
196
0
    }
197
198
    // prepare output place
199
0
    pOutputData.clear();
200
0
    pOutputData.reserve(pInputBase64.size() / 4 * 3);
201
202
0
    for (size_t in_len = pInputBase64.size(), in_idx = 0; (in_len > 0) && (pInputBase64[in_idx] != '='); in_len--) {
203
0
        if (ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) {
204
0
            arr4[tidx++] = pInputBase64[in_idx++];
205
0
            if (tidx == 4) {
206
0
                for (tidx = 0; tidx < 4; tidx++)
207
0
                    arr4[tidx] = (uint8_t)base64_chars.find(arr4[tidx]);
208
209
0
                arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
210
0
                arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
211
0
                arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
212
0
                for (tidx = 0; tidx < 3; tidx++)
213
0
                    pOutputData.push_back(arr3[tidx]);
214
215
0
                tidx = 0;
216
0
            } // if(tidx == 4)
217
0
        } // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx]))
218
0
        else {
219
0
            in_idx++;
220
0
        } // if(ParseHelper_Decode_Base64_IsBase64(pInputBase64[in_idx])) else
221
0
    }
222
223
0
    if (tidx) {
224
0
        for (uint8_t i = tidx; i < 4; i++)
225
0
            arr4[i] = 0;
226
0
        for (uint8_t i = 0; i < 4; i++)
227
0
            arr4[i] = (uint8_t)(base64_chars.find(arr4[i]));
228
229
0
        arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
230
0
        arr3[1] = ((arr4[1] & 0x0F) << 4) + ((arr4[2] & 0x3C) >> 2);
231
0
        arr3[2] = ((arr4[2] & 0x03) << 6) + arr4[3];
232
0
        for (uint8_t i = 0; i < (tidx - 1); i++)
233
0
            pOutputData.push_back(arr3[i]);
234
0
    }
235
0
}
236
237
0
void AMFImporter::ParseFile(const std::string &pFile, IOSystem *pIOHandler) {
238
0
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
239
240
    // Check whether we can read from the file
241
0
    if (file == nullptr) {
242
0
        throw DeadlyImportError("Failed to open AMF file ", pFile, ".");
243
0
    }
244
245
0
    mXmlParser = new XmlParser();
246
0
    if (!mXmlParser->parse(file.get())) {
247
0
        delete mXmlParser;
248
0
        mXmlParser = nullptr;
249
0
        throw DeadlyImportError("Failed to create XML reader for file ", pFile, ".");
250
0
    }
251
252
    // Start reading, search for root tag <amf>
253
0
    if (!mXmlParser->hasNode("amf")) {
254
0
        throw DeadlyImportError("Root node \"amf\" not found.");
255
0
    }
256
0
    ParseNode_Root();
257
0
} // namespace Assimp
258
259
0
void AMFImporter::ParseHelper_Node_Enter(AMFNodeElementBase *node) {
260
0
    mNodeElement_Cur->Child.push_back(node); // add new element to current element child list.
261
0
    mNodeElement_Cur = node;
262
0
}
263
264
0
void AMFImporter::ParseHelper_Node_Exit() {
265
0
    if (mNodeElement_Cur != nullptr) mNodeElement_Cur = mNodeElement_Cur->Parent;
266
0
}
267
268
// <amf
269
// unit="" - The units to be used. May be "inch", "millimeter", "meter", "feet", or "micron".
270
// version="" - Version of file format.
271
// >
272
// </amf>
273
// Root XML element.
274
// Multi elements - No.
275
0
void AMFImporter::ParseNode_Root() {
276
0
    AMFNodeElementBase *ne = nullptr;
277
0
    XmlNode *root = mXmlParser->findNode("amf");
278
0
    if (nullptr == root) {
279
0
        throw DeadlyImportError("Root node \"amf\" not found.");
280
0
    }
281
0
    XmlNode node = *root;
282
0
    mUnit = ai_tolower(std::string(node.attribute("unit").as_string()));
283
284
0
    mVersion = node.attribute("version").as_string();
285
286
    // Read attributes for node <amf>.
287
    // Check attributes
288
0
    if (!mUnit.empty()) {
289
0
        if ((mUnit != "inch") && (mUnit != "millimeters") && (mUnit != "millimeter") && (mUnit != "meter") && (mUnit != "feet") && (mUnit != "micron")) {
290
0
            Throw_IncorrectAttrValue("unit", mUnit);
291
0
        }
292
0
    }
293
294
    // create root node element.
295
0
    ne = new AMFRoot(nullptr);
296
297
0
    mNodeElement_Cur = ne; // set first "current" element
298
    // and assign attribute's values
299
0
    ((AMFRoot *)ne)->Unit = mUnit;
300
0
    ((AMFRoot *)ne)->Version = mVersion;
301
302
    // Check for child nodes
303
0
    for (XmlNode &currentNode : node.children() ) {
304
0
        const std::string currentName = currentNode.name();
305
0
        if (currentName == "object") {
306
0
            ParseNode_Object(currentNode);
307
0
        } else if (currentName == "material") {
308
0
            ParseNode_Material(currentNode);
309
0
        } else if (currentName == "texture") {
310
0
            ParseNode_Texture(currentNode);
311
0
        } else if (currentName == "constellation") {
312
0
            ParseNode_Constellation(currentNode);
313
0
        } else if (currentName == "metadata") {
314
0
            ParseNode_Metadata(currentNode);
315
0
        }
316
0
        mNodeElement_Cur = ne;
317
0
    }
318
0
    mNodeElement_Cur = ne; // force restore "current" element
319
0
    mNodeElement_List.push_back(ne); // add to node element list because its a new object in graph.
320
0
}
321
322
// <constellation
323
// id="" - The Object ID of the new constellation being defined.
324
// >
325
// </constellation>
326
// A collection of objects or constellations with specific relative locations.
327
// Multi elements - Yes.
328
// Parent element - <amf>.
329
0
void AMFImporter::ParseNode_Constellation(XmlNode &node) {
330
0
    std::string id;
331
0
    id = node.attribute("id").as_string();
332
333
    // create and if needed - define new grouping object.
334
0
    AMFNodeElementBase *ne = new AMFConstellation(mNodeElement_Cur);
335
336
0
    AMFConstellation &als = *((AMFConstellation *)ne); // alias for convenience
337
338
0
    if (!id.empty()) {
339
0
        als.ID = id;
340
0
    }
341
342
    // Check for child nodes
343
0
    if (!node.empty()) {
344
0
        ParseHelper_Node_Enter(ne);
345
0
        for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
346
0
            std::string name = currentNode.name();
347
0
            if (name == "instance") {
348
0
                ParseNode_Instance(currentNode);
349
0
            } else if (name == "metadata") {
350
0
                ParseNode_Metadata(currentNode);
351
0
            }
352
0
        }
353
0
        ParseHelper_Node_Exit();
354
0
    } else {
355
0
        mNodeElement_Cur->Child.push_back(ne);
356
0
    }
357
0
    mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
358
0
}
359
360
// <instance
361
// objectid="" - The Object ID of the new constellation being defined.
362
// >
363
// </instance>
364
// A collection of objects or constellations with specific relative locations.
365
// Multi elements - Yes.
366
// Parent element - <amf>.
367
0
void AMFImporter::ParseNode_Instance(XmlNode &node) {
368
0
    AMFNodeElementBase *ne(nullptr);
369
370
    // Read attributes for node <constellation>.
371
0
    std::string objectid = node.attribute("objectid").as_string();
372
373
    // used object id must be defined, check that.
374
0
    if (objectid.empty()) {
375
0
        throw DeadlyImportError("\"objectid\" in <instance> must be defined.");
376
0
    }
377
    // create and define new grouping object.
378
0
    ne = new AMFInstance(mNodeElement_Cur);
379
0
    AMFInstance &als = *((AMFInstance *)ne);
380
0
    als.ObjectID = objectid;
381
382
0
    if (!node.empty()) {
383
0
        ParseHelper_Node_Enter(ne);
384
0
        for (auto &currentNode : node.children()) {
385
0
            const std::string &currentName = currentNode.name();
386
0
            if (currentName == "deltax") {
387
0
                XmlParser::getValueAsReal(currentNode, als.Delta.x);
388
0
            } else if (currentName == "deltay") {
389
0
                XmlParser::getValueAsReal(currentNode, als.Delta.y);
390
0
            } else if (currentName == "deltaz") {
391
0
                XmlParser::getValueAsReal(currentNode, als.Delta.z);
392
0
            } else if (currentName == "rx") {
393
0
                XmlParser::getValueAsReal(currentNode, als.Delta.x);
394
0
            } else if (currentName == "ry") {
395
0
                XmlParser::getValueAsReal(currentNode, als.Delta.y);
396
0
            } else if (currentName == "rz") {
397
0
                XmlParser::getValueAsReal(currentNode, als.Delta.z);
398
0
            }
399
0
        }
400
0
        ParseHelper_Node_Exit();
401
0
    } else {
402
0
        mNodeElement_Cur->Child.push_back(ne);
403
0
    }
404
405
0
    mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
406
0
}
407
408
// <object
409
// id="" - A unique ObjectID for the new object being defined.
410
// >
411
// </object>
412
// An object definition.
413
// Multi elements - Yes.
414
// Parent element - <amf>.
415
0
void AMFImporter::ParseNode_Object(XmlNode &node) {
416
0
    AMFNodeElementBase *ne = nullptr;
417
418
    // Read attributes for node <object>.
419
0
    std::string id = node.attribute("id").as_string();
420
421
    // create and if needed - define new geometry object.
422
0
    ne = new AMFObject(mNodeElement_Cur);
423
424
0
    AMFObject &als = *((AMFObject *)ne); // alias for convenience
425
426
0
    if (!id.empty()) {
427
0
        als.ID = id;
428
0
    }
429
430
    // Check for child nodes
431
0
    if (!node.empty()) {
432
0
        ParseHelper_Node_Enter(ne);
433
0
        for (auto &currentNode : node.children()) {
434
0
            const std::string &currentName = currentNode.name();
435
0
            if (currentName == "color") {
436
0
                ParseNode_Color(currentNode);
437
0
            } else if (currentName == "mesh") {
438
0
                ParseNode_Mesh(currentNode);
439
0
            } else if (currentName == "metadata") {
440
0
                ParseNode_Metadata(currentNode);
441
0
            }
442
0
        }
443
0
        ParseHelper_Node_Exit();
444
0
    } else {
445
0
        mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
446
0
    }
447
448
0
    mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
449
0
}
450
451
// <metadata
452
// type="" - The type of the attribute.
453
// >
454
// </metadata>
455
// Specify additional information about an entity.
456
// Multi elements - Yes.
457
// Parent element - <amf>, <object>, <volume>, <material>, <vertex>.
458
//
459
// Reserved types are:
460
// "Name" - The alphanumeric label of the entity, to be used by the interpreter if interacting with the user.
461
// "Description" - A description of the content of the entity
462
// "URL" - A link to an external resource relating to the entity
463
// "Author" - Specifies the name(s) of the author(s) of the entity
464
// "Company" - Specifying the company generating the entity
465
// "CAD" - specifies the name of the originating CAD software and version
466
// "Revision" - specifies the revision of the entity
467
// "Tolerance" - specifies the desired manufacturing tolerance of the entity in entity's unit system
468
// "Volume" - specifies the total volume of the entity, in the entity's unit system, to be used for verification (object and volume only)
469
0
void AMFImporter::ParseNode_Metadata(XmlNode &node) {
470
0
    AMFNodeElementBase *ne = nullptr;
471
472
0
    std::string type = node.attribute("type").as_string(), value;
473
0
    XmlParser::getValueAsString(node, value);
474
475
    // read attribute
476
0
    ne = new AMFMetadata(mNodeElement_Cur);
477
0
    ((AMFMetadata *)ne)->Type = type;
478
0
    ((AMFMetadata *)ne)->Value = value;
479
0
    mNodeElement_Cur->Child.push_back(ne); // Add element to child list of current element
480
0
    mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
481
0
}
482
483
30
bool AMFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*pCheckSig*/) const {
484
30
    static const char *tokens[] = { "<amf" };
485
30
    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
486
30
}
487
488
33
const aiImporterDesc *AMFImporter::GetInfo() const {
489
33
    return &Description;
490
33
}
491
492
0
void AMFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
493
0
    Clear(); // delete old graph.
494
0
    ParseFile(pFile, pIOHandler);
495
0
    Postprocess_BuildScene(pScene);
496
    // scene graph is ready, exit.
497
0
}
498
499
} // namespace Assimp
500
501
#endif // !ASSIMP_BUILD_NO_AMF_IMPORTER