moab
|
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