1/*
2---------------------------------------------------------------------------
3Open Asset Import Library (assimp)
4---------------------------------------------------------------------------
5
6Copyright (c) 2006-2017, assimp team
7
8
9All rights reserved.
10
11Redistribution and use of this software in source and binary forms,
12with or without modification, are permitted provided that the following
13conditions are met:
14
15* Redistributions of source code must retain the above
16 copyright notice, this list of conditions and the
17 following disclaimer.
18
19* Redistributions in binary form must reproduce the above
20 copyright notice, this list of conditions and the
21 following disclaimer in the documentation and/or other
22 materials provided with the distribution.
23
24* Neither the name of the assimp team, nor the names of its
25 contributors may be used to endorse or promote products
26 derived from this software without specific prior
27 written permission of the assimp team.
28
29THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40---------------------------------------------------------------------------
41*/
42
43/** @file Implementation of the STL importer class */
44
45
46#ifndef ASSIMP_BUILD_NO_STL_IMPORTER
47
48// internal headers
49#include "STLLoader.h"
50#include "ParsingUtils.h"
51#include "fast_atof.h"
52#include <memory>
53#include <assimp/IOSystem.hpp>
54#include <assimp/scene.h>
55#include <assimp/DefaultLogger.hpp>
56#include <assimp/importerdesc.h>
57
58using namespace Assimp;
59
60namespace {
61
62static const aiImporterDesc desc = {
63 "Stereolithography (STL) Importer",
64 "",
65 "",
66 "",
67 aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
68 0,
69 0,
70 0,
71 0,
72 "stl"
73};
74
75// A valid binary STL buffer should consist of the following elements, in order:
76// 1) 80 byte header
77// 2) 4 byte face count
78// 3) 50 bytes per face
79static bool IsBinarySTL(const char* buffer, unsigned int fileSize) {
80 if( fileSize < 84 ) {
81 return false;
82 }
83
84 const char *facecount_pos = buffer + 80;
85 uint32_t faceCount( 0 );
86 ::memcpy( &faceCount, facecount_pos, sizeof( uint32_t ) );
87 const uint32_t expectedBinaryFileSize = faceCount * 50 + 84;
88
89 return expectedBinaryFileSize == fileSize;
90}
91
92// An ascii STL buffer will begin with "solid NAME", where NAME is optional.
93// Note: The "solid NAME" check is necessary, but not sufficient, to determine
94// if the buffer is ASCII; a binary header could also begin with "solid NAME".
95static bool IsAsciiSTL(const char* buffer, unsigned int fileSize) {
96 if (IsBinarySTL(buffer, fileSize))
97 return false;
98
99 const char* bufferEnd = buffer + fileSize;
100
101 if (!SkipSpaces(&buffer))
102 return false;
103
104 if (buffer + 5 >= bufferEnd)
105 return false;
106
107 bool isASCII( strncmp( buffer, "solid", 5 ) == 0 );
108 if( isASCII ) {
109 // A lot of importers are write solid even if the file is binary. So we have to check for ASCII-characters.
110 if( fileSize >= 500 ) {
111 isASCII = true;
112 for( unsigned int i = 0; i < 500; i++ ) {
113 if( buffer[ i ] > 127 ) {
114 isASCII = false;
115 break;
116 }
117 }
118 }
119 }
120 return isASCII;
121}
122} // namespace
123
124// ------------------------------------------------------------------------------------------------
125// Constructor to be privately used by Importer
126STLImporter::STLImporter()
127 : mBuffer(),
128 fileSize(),
129 pScene()
130{}
131
132// ------------------------------------------------------------------------------------------------
133// Destructor, private as well
134STLImporter::~STLImporter()
135{}
136
137// ------------------------------------------------------------------------------------------------
138// Returns whether the class can handle the format of the given file.
139bool STLImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
140{
141 const std::string extension = GetExtension(pFile);
142
143 if( extension == "stl" ) {
144 return true;
145 } else if (!extension.length() || checkSig) {
146 if( !pIOHandler ) {
147 return true;
148 }
149 const char* tokens[] = {"STL","solid"};
150 return SearchFileHeaderForToken(pIOHandler,pFile,tokens,2);
151 }
152
153 return false;
154}
155
156// ------------------------------------------------------------------------------------------------
157const aiImporterDesc* STLImporter::GetInfo () const {
158 return &desc;
159}
160
161void addFacesToMesh(aiMesh* pMesh)
162{
163 pMesh->mFaces = new aiFace[pMesh->mNumFaces];
164 for (unsigned int i = 0, p = 0; i < pMesh->mNumFaces;++i) {
165
166 aiFace& face = pMesh->mFaces[i];
167 face.mIndices = new unsigned int[face.mNumIndices = 3];
168 for (unsigned int o = 0; o < 3;++o,++p) {
169 face.mIndices[o] = p;
170 }
171 }
172}
173
174// ------------------------------------------------------------------------------------------------
175// Imports the given file into the given scene structure.
176void STLImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler )
177{
178 std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
179
180 // Check whether we can read from the file
181 if( file.get() == NULL) {
182 throw DeadlyImportError( "Failed to open STL file " + pFile + ".");
183 }
184
185 fileSize = (unsigned int)file->FileSize();
186
187 // allocate storage and copy the contents of the file to a memory buffer
188 // (terminate it with zero)
189 std::vector<char> mBuffer2;
190 TextFileToBuffer(file.get(),mBuffer2);
191
192 this->pScene = pScene;
193 this->mBuffer = &mBuffer2[0];
194
195 // the default vertex color is light gray.
196 clrColorDefault.r = clrColorDefault.g = clrColorDefault.b = clrColorDefault.a = (ai_real) 0.6;
197
198 // allocate a single node
199 pScene->mRootNode = new aiNode();
200
201 bool bMatClr = false;
202
203 if (IsBinarySTL(mBuffer, fileSize)) {
204 bMatClr = LoadBinaryFile();
205 } else if (IsAsciiSTL(mBuffer, fileSize)) {
206 LoadASCIIFile( pScene->mRootNode );
207 } else {
208 throw DeadlyImportError( "Failed to determine STL storage representation for " + pFile + ".");
209 }
210
211 // create a single default material, using a white diffuse color for consistency with
212 // other geometric types (e.g., PLY).
213 aiMaterial* pcMat = new aiMaterial();
214 aiString s;
215 s.Set(AI_DEFAULT_MATERIAL_NAME);
216 pcMat->AddProperty(&s, AI_MATKEY_NAME);
217
218 aiColor4D clrDiffuse(ai_real(1.0),ai_real(1.0),ai_real(1.0),ai_real(1.0));
219 if (bMatClr) {
220 clrDiffuse = clrColorDefault;
221 }
222 pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_DIFFUSE);
223 pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_SPECULAR);
224 clrDiffuse = aiColor4D( ai_real(1.0), ai_real(1.0), ai_real(1.0), ai_real(1.0));
225 pcMat->AddProperty(&clrDiffuse,1,AI_MATKEY_COLOR_AMBIENT);
226
227 pScene->mNumMaterials = 1;
228 pScene->mMaterials = new aiMaterial*[1];
229 pScene->mMaterials[0] = pcMat;
230}
231
232// ------------------------------------------------------------------------------------------------
233// Read an ASCII STL file
234void STLImporter::LoadASCIIFile( aiNode *root ) {
235 std::vector<aiMesh*> meshes;
236 std::vector<aiNode*> nodes;
237 const char* sz = mBuffer;
238 const char* bufferEnd = mBuffer + fileSize;
239 std::vector<aiVector3D> positionBuffer;
240 std::vector<aiVector3D> normalBuffer;
241
242 // try to guess how many vertices we could have
243 // assume we'll need 160 bytes for each face
244 size_t sizeEstimate = std::max(1u, fileSize / 160u ) * 3;
245 positionBuffer.reserve(sizeEstimate);
246 normalBuffer.reserve(sizeEstimate);
247
248 while (IsAsciiSTL(sz, static_cast<unsigned int>(bufferEnd - sz))) {
249 std::vector<unsigned int> meshIndices;
250 aiMesh* pMesh = new aiMesh();
251 pMesh->mMaterialIndex = 0;
252 meshIndices.push_back((unsigned int) meshes.size() );
253 meshes.push_back(pMesh);
254 aiNode *node = new aiNode;
255 node->mParent = root;
256 nodes.push_back( node );
257 SkipSpaces(&sz);
258 ai_assert(!IsLineEnd(sz));
259
260 sz += 5; // skip the "solid"
261 SkipSpaces(&sz);
262 const char* szMe = sz;
263 while (!::IsSpaceOrNewLine(*sz)) {
264 sz++;
265 }
266
267 size_t temp;
268 // setup the name of the node
269 if ((temp = (size_t)(sz-szMe))) {
270 if (temp >= MAXLEN) {
271 throw DeadlyImportError( "STL: Node name too long" );
272 }
273 std::string name( szMe, temp );
274 node->mName.Set( name.c_str() );
275 //pScene->mRootNode->mName.length = temp;
276 //memcpy(pScene->mRootNode->mName.data,szMe,temp);
277 //pScene->mRootNode->mName.data[temp] = '\0';
278 } else {
279 pScene->mRootNode->mName.Set("<STL_ASCII>");
280 }
281
282 unsigned int faceVertexCounter = 3;
283 for ( ;; ) {
284 // go to the next token
285 if(!SkipSpacesAndLineEnd(&sz))
286 {
287 // seems we're finished although there was no end marker
288 DefaultLogger::get()->warn("STL: unexpected EOF. \'endsolid\' keyword was expected");
289 break;
290 }
291 // facet normal -0.13 -0.13 -0.98
292 if (!strncmp(sz,"facet",5) && IsSpaceOrNewLine(*(sz+5)) && *(sz + 5) != '\0') {
293
294 if (faceVertexCounter != 3) {
295 DefaultLogger::get()->warn("STL: A new facet begins but the old is not yet complete");
296 }
297 faceVertexCounter = 0;
298 normalBuffer.push_back(aiVector3D());
299 aiVector3D* vn = &normalBuffer.back();
300
301 sz += 6;
302 SkipSpaces(&sz);
303 if (strncmp(sz,"normal",6)) {
304 DefaultLogger::get()->warn("STL: a facet normal vector was expected but not found");
305 } else {
306 if (sz[6] == '\0') {
307 throw DeadlyImportError("STL: unexpected EOF while parsing facet");
308 }
309 sz += 7;
310 SkipSpaces(&sz);
311 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->x );
312 SkipSpaces(&sz);
313 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->y );
314 SkipSpaces(&sz);
315 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->z );
316 normalBuffer.push_back(*vn);
317 normalBuffer.push_back(*vn);
318 }
319 } else if (!strncmp(sz,"vertex",6) && ::IsSpaceOrNewLine(*(sz+6))) { // vertex 1.50000 1.50000 0.00000
320 if (faceVertexCounter >= 3) {
321 DefaultLogger::get()->error("STL: a facet with more than 3 vertices has been found");
322 ++sz;
323 } else {
324 if (sz[6] == '\0') {
325 throw DeadlyImportError("STL: unexpected EOF while parsing facet");
326 }
327 sz += 7;
328 SkipSpaces(&sz);
329 positionBuffer.push_back(aiVector3D());
330 aiVector3D* vn = &positionBuffer.back();
331 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->x );
332 SkipSpaces(&sz);
333 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->y );
334 SkipSpaces(&sz);
335 sz = fast_atoreal_move<ai_real>(sz, (ai_real&)vn->z );
336 faceVertexCounter++;
337 }
338 } else if (!::strncmp(sz,"endsolid",8)) {
339 do {
340 ++sz;
341 } while (!::IsLineEnd(*sz));
342 SkipSpacesAndLineEnd(&sz);
343 // finished!
344 break;
345 } else { // else skip the whole identifier
346 do {
347 ++sz;
348 } while (!::IsSpaceOrNewLine(*sz));
349 }
350 }
351
352 if (positionBuffer.empty()) {
353 pMesh->mNumFaces = 0;
354 throw DeadlyImportError("STL: ASCII file is empty or invalid; no data loaded");
355 }
356 if (positionBuffer.size() % 3 != 0) {
357 pMesh->mNumFaces = 0;
358 throw DeadlyImportError("STL: Invalid number of vertices");
359 }
360 if (normalBuffer.size() != positionBuffer.size()) {
361 pMesh->mNumFaces = 0;
362 throw DeadlyImportError("Normal buffer size does not match position buffer size");
363 }
364 pMesh->mNumFaces = static_cast<unsigned int>(positionBuffer.size() / 3);
365 pMesh->mNumVertices = static_cast<unsigned int>(positionBuffer.size());
366 pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
367 memcpy(pMesh->mVertices, &positionBuffer[0].x, pMesh->mNumVertices * sizeof(aiVector3D));
368 positionBuffer.clear();
369 pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
370 memcpy(pMesh->mNormals, &normalBuffer[0].x, pMesh->mNumVertices * sizeof(aiVector3D));
371 normalBuffer.clear();
372
373 // now copy faces
374 addFacesToMesh(pMesh);
375
376 // assign the meshes to the current node
377 pushMeshesToNode( meshIndices, node );
378 }
379
380 // now add the loaded meshes
381 pScene->mNumMeshes = (unsigned int)meshes.size();
382 pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
383 for (size_t i = 0; i < meshes.size(); i++) {
384 pScene->mMeshes[ i ] = meshes[i];
385 }
386
387 root->mNumChildren = (unsigned int) nodes.size();
388 root->mChildren = new aiNode*[ root->mNumChildren ];
389 for ( size_t i=0; i<nodes.size(); ++i ) {
390 root->mChildren[ i ] = nodes[ i ];
391 }
392}
393
394// ------------------------------------------------------------------------------------------------
395// Read a binary STL file
396bool STLImporter::LoadBinaryFile()
397{
398 // allocate one mesh
399 pScene->mNumMeshes = 1;
400 pScene->mMeshes = new aiMesh*[1];
401 aiMesh* pMesh = pScene->mMeshes[0] = new aiMesh();
402 pMesh->mMaterialIndex = 0;
403
404 // skip the first 80 bytes
405 if (fileSize < 84) {
406 throw DeadlyImportError("STL: file is too small for the header");
407 }
408 bool bIsMaterialise = false;
409
410 // search for an occurrence of "COLOR=" in the header
411 const unsigned char* sz2 = (const unsigned char*)mBuffer;
412 const unsigned char* const szEnd = sz2+80;
413 while (sz2 < szEnd) {
414
415 if ('C' == *sz2++ && 'O' == *sz2++ && 'L' == *sz2++ &&
416 'O' == *sz2++ && 'R' == *sz2++ && '=' == *sz2++) {
417
418 // read the default vertex color for facets
419 bIsMaterialise = true;
420 DefaultLogger::get()->info("STL: Taking code path for Materialise files");
421 const ai_real invByte = (ai_real)1.0 / ( ai_real )255.0;
422 clrColorDefault.r = (*sz2++) * invByte;
423 clrColorDefault.g = (*sz2++) * invByte;
424 clrColorDefault.b = (*sz2++) * invByte;
425 clrColorDefault.a = (*sz2++) * invByte;
426 break;
427 }
428 }
429 const unsigned char* sz = (const unsigned char*)mBuffer + 80;
430
431 // now read the number of facets
432 pScene->mRootNode->mName.Set("<STL_BINARY>");
433
434 pMesh->mNumFaces = *((uint32_t*)sz);
435 sz += 4;
436
437 if (fileSize < 84 + pMesh->mNumFaces*50) {
438 throw DeadlyImportError("STL: file is too small to hold all facets");
439 }
440
441 if (!pMesh->mNumFaces) {
442 throw DeadlyImportError("STL: file is empty. There are no facets defined");
443 }
444
445 pMesh->mNumVertices = pMesh->mNumFaces*3;
446
447 aiVector3D* vp,*vn;
448 vp = pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
449 vn = pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
450
451 for (unsigned int i = 0; i < pMesh->mNumFaces;++i) {
452
453 // NOTE: Blender sometimes writes empty normals ... this is not
454 // our fault ... the RemoveInvalidData helper step should fix that
455 *vn = *((aiVector3D*)sz);
456 sz += sizeof(aiVector3D);
457 *(vn+1) = *vn;
458 *(vn+2) = *vn;
459 vn += 3;
460
461 *vp++ = *((aiVector3D*)sz);
462 sz += sizeof(aiVector3D);
463
464 *vp++ = *((aiVector3D*)sz);
465 sz += sizeof(aiVector3D);
466
467 *vp++ = *((aiVector3D*)sz);
468 sz += sizeof(aiVector3D);
469
470 uint16_t color = *((uint16_t*)sz);
471 sz += 2;
472
473 if (color & (1 << 15))
474 {
475 // seems we need to take the color
476 if (!pMesh->mColors[0])
477 {
478 pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices];
479 for (unsigned int i = 0; i <pMesh->mNumVertices;++i)
480 *pMesh->mColors[0]++ = this->clrColorDefault;
481 pMesh->mColors[0] -= pMesh->mNumVertices;
482
483 DefaultLogger::get()->info("STL: Mesh has vertex colors");
484 }
485 aiColor4D* clr = &pMesh->mColors[0][i*3];
486 clr->a = 1.0;
487 const ai_real invVal( (ai_real)1.0 / ( ai_real )31.0 );
488 if (bIsMaterialise) // this is reversed
489 {
490 clr->r = (color & 0x31u) *invVal;
491 clr->g = ((color & (0x31u<<5))>>5u) *invVal;
492 clr->b = ((color & (0x31u<<10))>>10u) *invVal;
493 }
494 else
495 {
496 clr->b = (color & 0x31u) *invVal;
497 clr->g = ((color & (0x31u<<5))>>5u) *invVal;
498 clr->r = ((color & (0x31u<<10))>>10u) *invVal;
499 }
500 // assign the color to all vertices of the face
501 *(clr+1) = *clr;
502 *(clr+2) = *clr;
503 }
504 }
505
506 // now copy faces
507 addFacesToMesh(pMesh);
508
509 // add all created meshes to the single node
510 pScene->mRootNode->mNumMeshes = pScene->mNumMeshes;
511 pScene->mRootNode->mMeshes = new unsigned int[pScene->mNumMeshes];
512 for (unsigned int i = 0; i < pScene->mNumMeshes; i++)
513 pScene->mRootNode->mMeshes[i] = i;
514
515 if (bIsMaterialise && !pMesh->mColors[0])
516 {
517 // use the color as diffuse material color
518 return true;
519 }
520 return false;
521}
522
523void STLImporter::pushMeshesToNode( std::vector<unsigned int> &meshIndices, aiNode *node ) {
524 ai_assert( nullptr != node );
525 if ( meshIndices.empty() ) {
526 return;
527 }
528
529 node->mNumMeshes = static_cast<unsigned int>( meshIndices.size() );
530 node->mMeshes = new unsigned int[ meshIndices.size() ];
531 for ( size_t i=0; i<meshIndices.size(); ++i ) {
532 node->mMeshes[ i ] = meshIndices[ i ];
533 }
534 meshIndices.clear();
535}
536
537#endif // !! ASSIMP_BUILD_NO_STL_IMPORTER
538