moab
ReadSTL.cpp
Go to the documentation of this file.
00001 
00022 #include "ReadSTL.hpp"
00023 #include "FileTokenizer.hpp" // for FileTokenizer
00024 #include "Internals.hpp"
00025 #include "moab/Interface.hpp"
00026 #include "moab/ReadUtilIface.hpp"
00027 #include "moab/Range.hpp"
00028 #include "moab/FileOptions.hpp"
00029 #include "SysUtil.hpp"
00030 
00031 #include <errno.h>
00032 #include <string.h>
00033 #include <limits.h>
00034 #include <assert.h>
00035 #include <map>
00036 
00037 namespace moab {
00038 
00039 
00040 ReadSTL::ReadSTL(Interface* impl)
00041     : mdbImpl(impl)
00042 {
00043   mdbImpl->query_interface(readMeshIface);
00044 }
00045 
00046 ReadSTL::~ReadSTL()
00047 {
00048   if (readMeshIface) {
00049     mdbImpl->release_interface(readMeshIface);
00050     readMeshIface = 0;
00051   }
00052 }
00053 
00054 // Used to put points in an STL tree-based container
00055 bool ReadSTL::Point::operator<( const ReadSTL::Point& other ) const
00056 {
00057   return 0 > memcmp( this, &other, sizeof(ReadSTL::Point) );
00058 }
00059 
00060 
00061 
00062 ErrorCode ReadSTL::read_tag_values( const char* /* file_name */,
00063                                     const char* /* tag_name */,
00064                                     const FileOptions& /* opts */,
00065                                     std::vector<int>& /* tag_values_out */,
00066                                     const SubsetList* /* subset_list */ )
00067 {
00068   return MB_NOT_IMPLEMENTED;
00069 }
00070 
00071 // Generic load function for both ASCII and binary.  Calls
00072 // pure-virtual function implemented in subclasses to read
00073 // the data from the file.
00074 ErrorCode ReadSTL::load_file( const char* filename,
00075                               const EntityHandle* , 
00076                               const FileOptions& opts,
00077                               const ReaderIface::SubsetList* subset_list,
00078                               const Tag* file_id_tag )
00079 {
00080   if (subset_list) {
00081     readMeshIface->report_error( "Reading subset of files not supported for STL." );
00082     return MB_UNSUPPORTED_OPERATION;
00083   }
00084 
00085   ErrorCode result;
00086 
00087   std::vector<ReadSTL::Triangle> triangles;
00088  
00089   bool is_ascii = false, is_binary = false;
00090   if (MB_SUCCESS == opts.get_null_option( "ASCII" ))
00091     is_ascii = true;
00092   if (MB_SUCCESS == opts.get_null_option( "BINARY" ))
00093     is_binary = true;
00094   if (is_ascii && is_binary) {
00095     readMeshIface->report_error( "Conflicting options: BINARY ASCII\n" );
00096     return MB_FAILURE;
00097   }
00098   
00099   bool big_endian = false, little_endian = false;
00100   if (MB_SUCCESS == opts.get_null_option( "BIG_ENDIAN" ))
00101     big_endian = true;
00102   if (MB_SUCCESS == opts.get_null_option( "LITTLE_ENDIAN" ))
00103     little_endian = true;
00104   if (big_endian && little_endian) {
00105     readMeshIface->report_error( "Conflicting options: BIG_ENDIAN LITTLE_ENDIAN\n" );
00106     return MB_FAILURE;
00107   }
00108   ByteOrder byte_order =    big_endian ? STL_BIG_ENDIAN 
00109                        : little_endian ? STL_LITTLE_ENDIAN 
00110                        :                 STL_UNKNOWN_BYTE_ORDER;
00111  
00112   if (is_ascii) 
00113     result = ascii_read_triangles( filename, triangles );
00114   else if (is_binary)
00115     result = binary_read_triangles( filename, byte_order, triangles );
00116   else {
00117       // try ASCII first
00118     result = ascii_read_triangles( filename, triangles );
00119     if (MB_SUCCESS != result) 
00120         // ASCII failed, try binary
00121       result = binary_read_triangles( filename, byte_order, triangles );
00122   }
00123   if (MB_SUCCESS != result)
00124     return result;
00125 
00126     // Create a std::map from position->handle, and such
00127     // that all positions are specified, and handles are zero.
00128   std::map<Point,EntityHandle> vertex_map;
00129   for (std::vector<Triangle>::iterator i = triangles.begin(); i != triangles.end(); ++i)
00130   {
00131     vertex_map[i->points[0]] = 0;
00132     vertex_map[i->points[1]] = 0;
00133     vertex_map[i->points[2]] = 0;
00134   }
00135   
00136     // Create vertices 
00137   std::vector<double*> coord_arrays;
00138   EntityHandle vtx_handle = 0;
00139   result = readMeshIface->get_node_coords( 3, vertex_map.size(), MB_START_ID,
00140                                            vtx_handle, coord_arrays );
00141   if (MB_SUCCESS != result)
00142     return result;
00143     
00144     // Copy vertex coordinates into entity sequence coordinate arrays
00145     // and copy handle into vertex_map.
00146   double *x = coord_arrays[0], *y = coord_arrays[1], *z = coord_arrays[2];
00147   for (std::map<Point,EntityHandle>::iterator i = vertex_map.begin();
00148        i != vertex_map.end(); ++i)
00149   {
00150     i->second = vtx_handle; ++vtx_handle;
00151     *x = i->first.coords[0]; ++x;
00152     *y = i->first.coords[1]; ++y;
00153     *z = i->first.coords[2]; ++z;
00154   }
00155   
00156     // Allocate triangles
00157   EntityHandle elm_handle = 0;
00158   EntityHandle* connectivity;
00159   result = readMeshIface->get_element_connect( triangles.size(),
00160                                              3,
00161                                              MBTRI,
00162                                              MB_START_ID,
00163                                              elm_handle,
00164                                              connectivity );
00165   if (MB_SUCCESS != result)
00166     return result;
00167   
00168     // Use vertex_map to reconver triangle connectivity from
00169     // vertex coordinates.
00170   EntityHandle *conn_sav = connectivity;
00171   for (std::vector<Triangle>::iterator i = triangles.begin(); i != triangles.end(); ++i)
00172   {
00173     *connectivity = vertex_map[i->points[0]]; ++connectivity;
00174     *connectivity = vertex_map[i->points[1]]; ++connectivity;
00175     *connectivity = vertex_map[i->points[2]]; ++connectivity;
00176   }
00177   
00178     // notify MOAB of the new elements
00179   result = readMeshIface->update_adjacencies(elm_handle, triangles.size(), 
00180                                              3, conn_sav);
00181   if (MB_SUCCESS != result) return result;
00182 
00183   if (file_id_tag) {
00184     Range vertices(vtx_handle, vtx_handle+vertex_map.size()-1);
00185     Range elements(elm_handle, elm_handle+triangles.size()-1);
00186     readMeshIface->assign_ids( *file_id_tag, vertices );
00187     readMeshIface->assign_ids( *file_id_tag, elements );
00188   }
00189 
00190   return MB_SUCCESS;
00191 }
00192 
00193 
00194 // Read ASCII file
00195 ErrorCode ReadSTL::ascii_read_triangles( const char* name,
00196                                           std::vector<ReadSTL::Triangle>& tris )
00197 {
00198   FILE* file = fopen( name, "r" );
00199   if (!file)
00200   {
00201     readMeshIface->report_error( "%s: %s\n", name, strerror(errno) );
00202     return MB_FILE_DOES_NOT_EXIST;
00203   }
00204   
00205   char header[82];
00206   if (!fgets( header, sizeof(header), file ) ||  // read header line
00207       strlen( header ) < 6                   ||  // must be at least 6 chars
00208       header[strlen(header) - 1] != '\n'     ||  // cannot exceed 80 chars
00209       memcmp( header, "solid", 5 )           ||  // must begin with "solid"
00210       !isspace( header[5] ) )                    // followed by a whitespace char
00211   {
00212     readMeshIface->report_error( "%s: %s\n", name, strerror(errno) );
00213     fclose( file );
00214     return MB_FILE_WRITE_ERROR;
00215   }
00216   
00217     // Use tokenizer for remainder of parsing
00218   FileTokenizer tokens( file, readMeshIface );
00219   
00220   Triangle tri;
00221   float norm[3];
00222   
00223     // Read until end of file.  If we reach "endsolid", read
00224     // was successful.  If EOF before "endsolid", return error.
00225   for (;;)
00226   {
00227       // Check for either another facet or the end of the list.
00228     const char* const expected[] = { "facet", "endsolid", 0 };
00229     switch( tokens.match_token( expected ) )
00230     {
00231       case 1:  break;                        // found another facet
00232       case 2:  return MB_SUCCESS;            // found "endsolid" -- done
00233       default: return MB_FILE_WRITE_ERROR;   // found something else, or EOF
00234     }
00235     
00236     if (!tokens.match_token( "normal" ) ||    // expect "normal" keyword
00237         !tokens.get_floats( 3, norm )   ||    // followed by normal vector
00238         !tokens.match_token( "outer" )  ||    // followed by "outer loop"
00239         !tokens.match_token( "loop" )   )
00240       return MB_FILE_WRITE_ERROR;
00241     
00242       // for each of three triangle vertices
00243     for (int i = 0; i < 3; i++)
00244     {
00245       if (!tokens.match_token("vertex") ||
00246           !tokens.get_floats( 3, tri.points[i].coords ))
00247         return MB_FILE_WRITE_ERROR;
00248     }
00249     
00250     if (!tokens.match_token( "endloop" ) ||   // facet ends with "endloop"
00251         !tokens.match_token( "endfacet" ) )   // and then "endfacet"
00252       return MB_FILE_WRITE_ERROR;
00253     
00254     tris.push_back( tri );
00255   }
00256 }
00257 
00258 
00259 // Header block from binary STL file (84 bytes long)
00260 struct BinaryHeader {
00261   char comment[80]; // 80 byte comment string (null terminated?)
00262   uint32_t count;   // number of triangles - 4 byte integer
00263 }; 
00264 
00265 // Triangle spec from file (50 bytes)
00266 struct BinaryTri {
00267   float normal[3];  // Normal as 3 4-byte little-endian IEEE floats
00268   float coords[9];  // Vertex coords as 9 4-byte little-endian IEEE floats
00269   char pad[2];
00270 };
00271 
00272 // Read a binary STL file
00273 ErrorCode ReadSTL::binary_read_triangles( const char* name,
00274                                            ReadSTL::ByteOrder byte_order,
00275                                            std::vector<ReadSTL::Triangle>& tris )
00276 {
00277   FILE* file = fopen( name, "rb" );
00278   if (!file)
00279   {
00280     readMeshIface->report_error( "%s: %s\n", name, strerror(errno) );
00281     return MB_FILE_DOES_NOT_EXIST;
00282   }
00283   
00284     // Read header block
00285   BinaryHeader header;
00286   if (fread( &header, 84, 1, file ) != 1)
00287   {
00288     fclose ( file );
00289     readMeshIface->report_error( "%s: %s\n", name, strerror(errno) );
00290     return MB_FILE_WRITE_ERROR;
00291   }
00292   
00293     // Allow user setting for byte order, default to little endian
00294   const bool want_big_endian = (byte_order == STL_BIG_ENDIAN);
00295   const bool am_big_endian = !SysUtil::little_endian();
00296   bool swap_bytes = (want_big_endian == am_big_endian);
00297   
00298     // Compare the number of triangles to the length of the file.  
00299     // The file must contain an 80-byte description, a 4-byte 
00300     // triangle count and 50 bytes per triangle.
00301     //
00302     // The triangle count *may* allow us to determine the byte order
00303     // of the file, if it is not an endian-symetric value.  
00304     //
00305     // We need to compare the expected size calculated from the triangle
00306     // count with the file size anyway, as an invalid file or a byte-
00307     // swapping issue could result in a very large (incorrect) value for
00308     // num_tri, resulting in a SEGFAULT. 
00309   
00310     // Get expected number of triangles
00311   if (swap_bytes)
00312     SysUtil::byteswap( &header.count, 1 );
00313   unsigned long num_tri = header.count;
00314   
00315     // Get the file length
00316   long filesize = SysUtil::filesize( file );
00317   if (filesize >= 0) // -1 indicates could not determine file size (e.g. reading from FIFO)
00318   {
00319       // Check file size, but be careful of numeric overflow
00320     if (ULONG_MAX / 50 - 84 < num_tri ||   // next calc would have oveflow
00321         84 + 50 * num_tri != (unsigned long)filesize)
00322     {
00323         // Unless the byte order was specified explicitly in the 
00324         // tag, try the opposite byte order.
00325       uint32_t num_tri_tmp = header.count;
00326       SysUtil::byteswap( &num_tri_tmp, 1 );
00327       unsigned long num_tri_swap = num_tri_tmp;
00328       if (byte_order != STL_UNKNOWN_BYTE_ORDER || // If byte order was specified, fail now
00329           ULONG_MAX / 50 - 84 < num_tri_swap  || // watch for overflow in next line
00330           84 + 50 * num_tri_swap != (unsigned long)filesize)
00331       {
00332         fclose( file );
00333         readMeshIface->report_error( "%s: not a binary STL file\n", name );
00334         return MB_FILE_DOES_NOT_EXIST;
00335       }
00336       swap_bytes = !swap_bytes;
00337       num_tri = num_tri_swap;
00338     }
00339   }
00340     
00341     // Allocate storage for triangles
00342   tris.resize( num_tri );
00343   
00344     // Read each triangle
00345   BinaryTri tri;  // binary block read from file
00346   for (std::vector<Triangle>::iterator i = tris.begin(); i != tris.end(); ++i)
00347   {
00348     if (fread( &tri, 50, 1, file ) != 1)
00349     {
00350       fclose( file );
00351       readMeshIface->report_error( "%s: %s\n", name, strerror(errno) );
00352       return MB_FILE_WRITE_ERROR;
00353     }
00354     
00355     if (swap_bytes)
00356       SysUtil::byteswap( tri.coords, 9 );
00357     
00358     for (unsigned j = 0; j < 9; ++j)
00359       i->points[j/3].coords[j%3] = tri.coords[j];
00360   }
00361   
00362   fclose( file );
00363   return MB_SUCCESS;
00364 }
00365 
00366 
00367 ReaderIface* ReadSTL::factory( Interface* iface )
00368   { return new ReadSTL(iface); }
00369 
00370 } // namespace moab
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines