stl_reader
stl_reader.h
Go to the documentation of this file.
1/*
2 Copyright (c) 2018-2023, Sebastian Reiter (s.b.reiter@gmail.com)
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12
13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23*/
24
25
127#ifndef __H__STL_READER
128#define __H__STL_READER
129
130#include <algorithm>
131#include <exception>
132#include <fstream>
133#include <sstream>
134#include <vector>
135
136#ifdef STL_READER_NO_EXCEPTIONS
137 #define STL_READER_THROW(msg) return false;
138 #define STL_READER_COND_THROW(cond, msg) if(cond) return false;
139#else
141 #define STL_READER_THROW(msg) {std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
142
144 #define STL_READER_COND_THROW(cond, msg) if(cond){std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
145#endif
146
147
148namespace stl_reader {
149
151
191template <class TNumberContainer1, class TNumberContainer2,
192 class TIndexContainer1, class TIndexContainer2>
193bool ReadStlFile(const char* filename,
194 TNumberContainer1& coordsOut,
195 TNumberContainer2& normalsOut,
196 TIndexContainer1& trisOut,
197 TIndexContainer2& solidRangesOut);
198
199
201
204template <class TNumberContainer1, class TNumberContainer2,
205 class TIndexContainer1, class TIndexContainer2>
206bool ReadStlFile_ASCII(const char* filename,
207 TNumberContainer1& coordsOut,
208 TNumberContainer2& normalsOut,
209 TIndexContainer1& trisOut,
210 TIndexContainer2& solidRangesOut);
211
213
217template <class TNumberContainer1, class TNumberContainer2,
218 class TIndexContainer1, class TIndexContainer2>
219bool ReadStlFile_BINARY(const char* filename,
220 TNumberContainer1& coordsOut,
221 TNumberContainer2& normalsOut,
222 TIndexContainer1& trisOut,
223 TIndexContainer2& solidRangesOut);
224
226
230inline bool StlFileHasASCIIFormat(const char* filename);
231
232
234template <class TNumber = float, class TIndex = unsigned int>
235class StlMesh {
236public:
239 {
240 solids.resize (2, 0);
241 }
242
244
245 StlMesh (const char* filename)
246 {
247 read_file (filename);
248 }
249
250 StlMesh (const std::string& filename)
251 {
252 read_file (filename);
253 }
257
258 bool read_file (const char* filename)
259 {
260 bool res = false;
261
262 #ifndef STL_READER_NO_EXCEPTIONS
263 try {
264 #endif
265
266 res = ReadStlFile (filename, coords, normals, tris, solids);
267
268 #ifndef STL_READER_NO_EXCEPTIONS
269 } catch (std::exception& e) {
270 #else
271 if (!res) {
272 #endif
273
274 coords.clear ();
275 normals.clear ();
276 tris.clear ();
277 solids.clear ();
278 STL_READER_THROW (e.what());
279 }
280
281 return res;
282 }
283
284 bool read_file (const std::string& filename)
285 {
286 return read_file (filename.c_str());
287 }
291 size_t num_vrts () const
292 {
293 return coords.size() / 3;
294 }
295
297 const TNumber* vrt_coords (const size_t vi) const
298 {
299 return &coords[vi * 3];
300 }
301
303 size_t num_tris () const
304 {
305 return tris.size() / 3;
306 }
307
309 const TIndex* tri_corner_inds (const size_t ti) const
310 {
311 return &tris [ti * 3];
312 }
313
315 const TIndex tri_corner_ind (const size_t ti, const size_t ci) const
316 {
317 return tris [ti * 3 + ci];
318 }
319
327 const TNumber* tri_corner_coords (const size_t ti, const size_t ci) const
328 {
329 return &coords[tri_corner_ind(ti, ci) * 3];
330 }
331
333 const TNumber* tri_normal (const size_t ti) const
334 {
335 return &normals [ti * 3];
336 }
337
339
344 size_t num_solids () const
345 {
346 if(solids.empty ())
347 return 0;
348 return solids.size () - 1;
349 }
350
352 TIndex solid_tris_begin (const size_t si) const
353 {
354 return solids [si];
355 }
356
358 TIndex solid_tris_end (const size_t si) const
359 {
360 return solids [si + 1];
361 }
362
364
366 const TNumber* raw_coords () const
367 {
368 if(coords.empty())
369 return NULL;
370 return &coords[0];
371 }
372
374
376 const TNumber* raw_normals () const
377 {
378 if(normals.empty())
379 return NULL;
380 return &normals[0];
381 }
382
384
386 const TIndex* raw_tris () const
387 {
388 if(tris.empty())
389 return NULL;
390 return &tris[0];
391 }
392
394
396 const TIndex* raw_solids () const
397 {
398 if(solids.empty())
399 return NULL;
400 return &solids[0];
401 }
402
403private:
404 std::vector<TNumber> coords;
405 std::vector<TNumber> normals;
406 std::vector<TIndex> tris;
407 std::vector<TIndex> solids;
408};
409
410
412// IMPLEMENTATION
414
415
416namespace stl_reader_impl {
417
418 // a coordinate triple with an additional index. The index is required
419 // for RemoveDoubles, so that triangles can be reindexed properly.
420 template <typename number_t, typename index_t>
421 struct CoordWithIndex {
422 number_t data[3];
423 index_t index;
424
425 bool operator == (const CoordWithIndex& c) const
426 {
427 return (c[0] == data[0]) && (c[1] == data[1]) && (c[2] == data[2]);
428 }
429
430 bool operator != (const CoordWithIndex& c) const
431 {
432 return (c[0] != data[0]) || (c[1] != data[1]) || (c[2] != data[2]);
433 }
434
435 bool operator < (const CoordWithIndex& c) const
436 {
437 return (data[0] < c[0])
438 || (data[0] == c[0] && data[1] < c[1])
439 || (data[0] == c[0] && data[1] == c[1] && data[2] < c[2]);
440 }
441
442 inline number_t& operator [] (const size_t i) {return data[i];}
443 inline number_t operator [] (const size_t i) const {return data[i];}
444 };
445
446 // sorts the array coordsWithIndexInOut and copies unique indices to coordsOut.
447 // Triangle-corners are re-indexed on the fly and degenerated triangles are removed.
448 template <class TNumberContainer1, class TNumberContainer2,
449 class TIndexContainer1, class TIndexContainer2>
450 void RemoveDoubles (TNumberContainer1& uniqueCoordsOut,
451 TIndexContainer1& trisInOut,
452 TNumberContainer2& normalsInOut,
453 TIndexContainer2& solidsInOut,
454 std::vector <CoordWithIndex<
455 typename TNumberContainer1::value_type,
456 typename TIndexContainer1::value_type> >
457 &coordsWithIndexInOut)
458 {
459 using namespace std;
460
461 typedef typename TNumberContainer1::value_type number_t;
462 typedef typename TIndexContainer1::value_type index_t;
463
464 sort (coordsWithIndexInOut.begin(), coordsWithIndexInOut.end());
465
466 // first count unique indices
467 index_t numUnique = 1;
468 for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
469 if(coordsWithIndexInOut[i] != coordsWithIndexInOut[i - 1])
470 ++numUnique;
471 }
472
473 uniqueCoordsOut.resize (numUnique * 3);
474 vector<index_t> newIndex (coordsWithIndexInOut.size());
475
476 TIndexContainer2 newSolids;
477
478 // copy unique coordinates to 'uniqueCoordsOut' and create an index-map
479 // 'newIndex', which allows to re-index triangles later on.
480 index_t curInd = 0;
481 newIndex[coordsWithIndexInOut[0].index] = 0;
482 for(index_t i = 0; i < 3; ++i)
483 uniqueCoordsOut[i] = coordsWithIndexInOut[0][i];
484
485 for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
486 const CoordWithIndex <number_t, index_t> c = coordsWithIndexInOut[i];
487 if(c != coordsWithIndexInOut[i - 1]){
488 ++curInd;
489 for(index_t j = 0; j < 3; ++j)
490 uniqueCoordsOut[curInd * 3 + j] = coordsWithIndexInOut[i][j];
491 }
492
493 newIndex[c.index] = static_cast<index_t> (curInd);
494 }
495
496 // re-index triangles, so that they refer to 'uniqueCoordsOut'
497 // make sure to only add triangles which refer to three different indices
498 index_t numUniqueTriInds = 0;
499 for(index_t i = 0; i < trisInOut.size(); i+=3){
500
501 const index_t triInd = i / 3;
502 const index_t newTriInd = numUniqueTriInds / 3;
503 if (newSolids.size () < solidsInOut.size () &&
504 solidsInOut [newSolids.size ()] <= triInd)
505 {
506 newSolids.push_back (newTriInd);
507 }
508
509 index_t ni[3];
510 for(index_t j = 0; j < 3; ++j)
511 ni[j] = newIndex[trisInOut[i+j]];
512
513 if((ni[0] != ni[1]) && (ni[0] != ni[2]) && (ni[1] != ni[2])){
514 for(index_t j = 0; j < 3; ++j)
515 {
516 trisInOut[numUniqueTriInds + j] = ni[j];
517 normalsInOut[numUniqueTriInds + j] = normalsInOut [i + j];
518 }
519 numUniqueTriInds += 3;
520 }
521 }
522
523 if(numUniqueTriInds < trisInOut.size())
524 {
525 trisInOut.resize (numUniqueTriInds);
526 normalsInOut.resize (numUniqueTriInds);
527 }
528
529 if (!newSolids.empty ())
530 newSolids.push_back (numUniqueTriInds / 3);
531
532 using std::swap;
533 swap (solidsInOut, newSolids);
534 }
535}// end of namespace stl_reader_impl
536
537
538template <class TNumberContainer1, class TNumberContainer2,
539 class TIndexContainer1, class TIndexContainer2>
540bool ReadStlFile(const char* filename,
541 TNumberContainer1& coordsOut,
542 TNumberContainer2& normalsOut,
543 TIndexContainer1& trisOut,
544 TIndexContainer2& solidRangesOut)
545{
546 if(StlFileHasASCIIFormat(filename))
547 return ReadStlFile_ASCII(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
548 else
549 return ReadStlFile_BINARY(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
550}
551
552
553template <class TNumberContainer1, class TNumberContainer2,
554 class TIndexContainer1, class TIndexContainer2>
555bool ReadStlFile_ASCII(const char* filename,
556 TNumberContainer1& coordsOut,
557 TNumberContainer2& normalsOut,
558 TIndexContainer1& trisOut,
559 TIndexContainer2& solidRangesOut)
560{
561 using namespace std;
562 using namespace stl_reader_impl;
563
564 typedef typename TNumberContainer1::value_type number_t;
565 typedef typename TIndexContainer1::value_type index_t;
566
567 coordsOut.clear();
568 normalsOut.clear();
569 trisOut.clear();
570 solidRangesOut.clear();
571
572 ifstream in(filename);
573 STL_READER_COND_THROW(!in, "Couldn't open file " << filename);
574
575 vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
576
577 string buffer;
578 vector<string> tokens;
579 int lineCount = 1;
580 int maxNumTokens = 0;
581 size_t numFaceVrts = 0;
582
583 while(!(in.eof() || in.fail()))
584 {
585 // read the line and tokenize.
586 // In order to reuse memory in between lines, 'tokens' won't be cleared.
587 // Instead we count the number of tokens using 'tokenCount'.
588 getline(in, buffer);
589
590 istringstream line(buffer);
591 int tokenCount = 0;
592 while(!(line.eof() || line.fail())){
593 if(tokenCount >= maxNumTokens){
594 maxNumTokens = tokenCount + 1;
595 tokens.resize(maxNumTokens);
596 }
597 line >> tokens[tokenCount];
598 ++tokenCount;
599 }
600
601 if(tokenCount > 0)
602 {
603 string& tok = tokens[0];
604 if(tok.compare("vertex") == 0){
605 if(tokenCount < 4){
606 STL_READER_THROW("ERROR while reading from " << filename <<
607 ": vertex not specified correctly in line " << lineCount);
608 }
609
610 // read the position
611 CoordWithIndex <number_t, index_t> c;
612 for(size_t i = 0; i < 3; ++i)
613 c[i] = static_cast<number_t> (atof(tokens[i+1].c_str()));
614 c.index = static_cast<index_t>(coordsWithIndex.size());
615 coordsWithIndex.push_back(c);
616 ++numFaceVrts;
617 }
618 else if(tok.compare("facet") == 0)
619 {
620 STL_READER_COND_THROW(tokenCount < 5,
621 "ERROR while reading from " << filename <<
622 ": triangle not specified correctly in line " << lineCount);
623
624 STL_READER_COND_THROW(tokens[1].compare("normal") != 0,
625 "ERROR while reading from " << filename <<
626 ": Missing normal specifier in line " << lineCount);
627
628 // read the normal
629 for(size_t i = 0; i < 3; ++i)
630 normalsOut.push_back (static_cast<number_t> (atof(tokens[i+2].c_str())));
631
632 numFaceVrts = 0;
633 }
634 else if(tok.compare("outer") == 0){
635 STL_READER_COND_THROW ((tokenCount < 2) || (tokens[1].compare("loop") != 0),
636 "ERROR while reading from " << filename <<
637 ": expecting outer loop in line " << lineCount);
638 }
639 else if(tok.compare("endfacet") == 0){
640 STL_READER_COND_THROW(numFaceVrts != 3,
641 "ERROR while reading from " << filename <<
642 ": bad number of vertices specified for face in line " << lineCount);
643
644 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
645 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
646 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
647 }
648 else if(tok.compare("solid") == 0){
649 solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
650 }
651 }
652 lineCount++;
653 }
654
655 solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
656
657 RemoveDoubles (coordsOut, trisOut, normalsOut, solidRangesOut, coordsWithIndex);
658
659 return true;
660}
661
662
663template <class TNumberContainer1, class TNumberContainer2,
664 class TIndexContainer1, class TIndexContainer2>
665bool ReadStlFile_BINARY(const char* filename,
666 TNumberContainer1& coordsOut,
667 TNumberContainer2& normalsOut,
668 TIndexContainer1& trisOut,
669 TIndexContainer2& solidRangesOut)
670{
671 using namespace std;
672 using namespace stl_reader_impl;
673
674 typedef typename TNumberContainer1::value_type number_t;
675 typedef typename TIndexContainer1::value_type index_t;
676
677 coordsOut.clear();
678 normalsOut.clear();
679 trisOut.clear();
680 solidRangesOut.clear();
681
682 ifstream in(filename, ios::binary);
683 STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
684
685 char stl_header[80];
686 in.read(stl_header, 80);
687 STL_READER_COND_THROW(!in, "Error while parsing binary stl header in file " << filename);
688
689 unsigned int numTris = 0;
690 in.read((char*)&numTris, 4);
691 STL_READER_COND_THROW(!in, "Couldnt determine number of triangles in binary stl file " << filename);
692
693 vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
694
695 for(unsigned int tri = 0; tri < numTris; ++tri){
696 float d[12];
697 in.read((char*)d, 12 * 4);
698 STL_READER_COND_THROW(!in, "Error while parsing trianlge in binary stl file " << filename);
699
700 for(int i = 0; i < 3; ++i)
701 normalsOut.push_back (d[i]);
702
703 for(size_t ivrt = 1; ivrt < 4; ++ivrt){
704 CoordWithIndex <number_t, index_t> c;
705 for(size_t i = 0; i < 3; ++i)
706 c[i] = d[ivrt * 3 + i];
707 c.index = static_cast<index_t>(coordsWithIndex.size());
708 coordsWithIndex.push_back(c);
709 }
710
711 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
712 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
713 trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
714
715 char addData[2];
716 in.read(addData, 2);
717 STL_READER_COND_THROW(!in, "Error while parsing additional triangle data in binary stl file " << filename);
718 }
719
720 solidRangesOut.push_back(0);
721 solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
722
723 RemoveDoubles (coordsOut, trisOut, normalsOut, solidRangesOut, coordsWithIndex);
724
725 return true;
726}
727
728
729inline bool StlFileHasASCIIFormat(const char* filename)
730{
731 using namespace std;
732 ifstream in(filename);
733 STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
734
735 char chars [256];
736 in.read (chars, 256);
737 string buffer (chars, in.gcount());
738 transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
739 return buffer.find ("solid") != string::npos &&
740 buffer.find ("\n") != string::npos &&
741 buffer.find ("facet") != string::npos &&
742 buffer.find ("normal") != string::npos;
743}
744
745} // end of namespace stl_reader
746
747#endif //__H__STL_READER
convenience mesh class which makes accessing the stl data more easy
Definition: stl_reader.h:235
const TNumber * vrt_coords(const size_t vi) const
returns an array of 3 floating point values, one for each coordinate of the vertex
Definition: stl_reader.h:297
size_t num_solids() const
returns the number of solids of the mesh
Definition: stl_reader.h:344
const TNumber * tri_corner_coords(const size_t ti, const size_t ci) const
returns an array of 3 floating point values, one for each coordinate of the specified corner of the s...
Definition: stl_reader.h:327
const TNumber * raw_coords() const
returns a pointer to the coordinate array, containing num_vrts()*3 entries.
Definition: stl_reader.h:366
size_t num_tris() const
returns the number of triangles in the mesh
Definition: stl_reader.h:303
const TNumber * raw_normals() const
returns a pointer to the normal array, containing num_tris()*3 entries.
Definition: stl_reader.h:376
StlMesh(const char *filename)
initializes the mesh from the stl-file specified through filename
Definition: stl_reader.h:245
TIndex solid_tris_begin(const size_t si) const
returns the index of the first triangle in the given solid
Definition: stl_reader.h:352
const TIndex tri_corner_ind(const size_t ti, const size_t ci) const
returns the index of the corner with index 0<=ci<3 of triangle ti
Definition: stl_reader.h:315
const TIndex * raw_tris() const
returns a pointer to the triangle array, containing num_tris()*3 entries.
Definition: stl_reader.h:386
bool read_file(const char *filename)
fills the mesh with the contents of the specified stl-file
Definition: stl_reader.h:258
const TIndex * raw_solids() const
returns a pointer to the solids array, containing num_solids()+1 entries.
Definition: stl_reader.h:396
StlMesh()
initializes an empty mesh
Definition: stl_reader.h:238
TIndex solid_tris_end(const size_t si) const
returns the index of the triangle behind the last triangle in the given solid
Definition: stl_reader.h:358
size_t num_vrts() const
returns the number of vertices in the mesh
Definition: stl_reader.h:291
const TIndex * tri_corner_inds(const size_t ti) const
returns an array of 3 indices, one for each corner vertex of the triangle
Definition: stl_reader.h:309
const TNumber * tri_normal(const size_t ti) const
returns an array of 3 floating point values defining the normal of a tri
Definition: stl_reader.h:333
bool ReadStlFile(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads an ASCII or binary stl file into several arrays.
Definition: stl_reader.h:540
bool ReadStlFile_ASCII(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads an ASCII stl file into several arrays.
Definition: stl_reader.h:555
bool StlFileHasASCIIFormat(const char *filename)
Determines whether a stl file has ASCII format.
Definition: stl_reader.h:729
#define STL_READER_COND_THROW(cond, msg)
Throws an std::runtime_error with the given message, if the given condition evaluates to true.
Definition: stl_reader.h:144
#define STL_READER_THROW(msg)
Throws an std::runtime_error with the given message.
Definition: stl_reader.h:141
bool ReadStlFile_BINARY(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads a binary stl file into several arrays.
Definition: stl_reader.h:665