Mesh Oriented datABase  (version 5.5.0)
An array-based unstructured mesh library
ReadSTL.cpp
Go to the documentation of this file.
1 /**
2  * MOAB, a Mesh-Oriented datABase, is a software component for creating,
3  * storing and accessing finite element mesh data.
4  *
5  * Copyright 2004 Sandia Corporation. Under the terms of Contract
6  * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
7  * retains certain rights in this software.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  */
15 
16 /**
17  * \class ReadSTL
18  * \brief ASCII and Binary Stereo Lithography File readers.
19  * \author Jason Kraftcheck
20  */
21 
22 #include "ReadSTL.hpp"
23 #include "FileTokenizer.hpp" // For FileTokenizer
24 #include "Internals.hpp"
25 #include "moab/Interface.hpp"
26 #include "moab/ReadUtilIface.hpp"
27 #include "moab/Range.hpp"
28 #include "moab/FileOptions.hpp"
29 #include "SysUtil.hpp"
30 
31 #include <cerrno>
32 #include <cstring>
33 #include <climits>
34 #include <cassert>
35 #include <map>
36 
37 namespace moab
38 {
39 
40 ReadSTL::ReadSTL( Interface* impl ) : mdbImpl( impl )
41 {
43 }
44 
46 {
47  if( readMeshIface )
48  {
50  readMeshIface = NULL;
51  }
52 }
53 
54 // Used to put points in an STL tree-based container
55 bool ReadSTL::Point::operator<( const ReadSTL::Point& other ) const
56 {
57  return 0 > memcmp( this, &other, sizeof( ReadSTL::Point ) );
58 }
59 
60 ErrorCode ReadSTL::read_tag_values( const char* /* file_name */,
61  const char* /* tag_name */,
62  const FileOptions& /* opts */,
63  std::vector< int >& /* tag_values_out */,
64  const SubsetList* /* subset_list */ )
65 {
66  return MB_NOT_IMPLEMENTED;
67 }
68 
69 // Generic load function for both ASCII and binary. Calls
70 // pure-virtual function implemented in subclasses to read
71 // the data from the file.
72 ErrorCode ReadSTL::load_file( const char* filename,
73  const EntityHandle* /* file_set */,
74  const FileOptions& opts,
75  const ReaderIface::SubsetList* subset_list,
76  const Tag* file_id_tag )
77 {
78  if( subset_list )
79  {
80  MB_SET_ERR( MB_UNSUPPORTED_OPERATION, "Reading subset of files not supported for STL" );
81  }
82 
83  ErrorCode result;
84 
85  std::vector< ReadSTL::Triangle > triangles;
86 
87  bool is_ascii = false, is_binary = false;
88  if( MB_SUCCESS == opts.get_null_option( "ASCII" ) ) is_ascii = true;
89  if( MB_SUCCESS == opts.get_null_option( "BINARY" ) ) is_binary = true;
90  if( is_ascii && is_binary )
91  {
92  MB_SET_ERR( MB_FAILURE, "Conflicting options: BINARY ASCII" );
93  }
94 
95  bool big_endian = false, little_endian = false;
96  if( MB_SUCCESS == opts.get_null_option( "BIG_ENDIAN" ) ) big_endian = true;
97  if( MB_SUCCESS == opts.get_null_option( "LITTLE_ENDIAN" ) ) little_endian = true;
98  if( big_endian && little_endian )
99  {
100  MB_SET_ERR( MB_FAILURE, "Conflicting options: BIG_ENDIAN LITTLE_ENDIAN" );
101  }
103 
104  if( is_ascii )
105  result = ascii_read_triangles( filename, triangles );
106  else if( is_binary )
107  result = binary_read_triangles( filename, byte_order, triangles );
108  else
109  {
110  // Try ASCII first
111  result = ascii_read_triangles( filename, triangles );
112  if( MB_SUCCESS != result )
113  // ASCII failed, try binary
114  result = binary_read_triangles( filename, byte_order, triangles );
115  }
116  if( MB_SUCCESS != result ) return result;
117 
118  // Create a std::map from position->handle, and such
119  // that all positions are specified, and handles are zero.
120  std::map< Point, EntityHandle > vertex_map;
121  for( std::vector< Triangle >::iterator i = triangles.begin(); i != triangles.end(); ++i )
122  {
123  vertex_map[i->points[0]] = 0;
124  vertex_map[i->points[1]] = 0;
125  vertex_map[i->points[2]] = 0;
126  }
127 
128  // Create vertices
129  std::vector< double* > coord_arrays;
130  EntityHandle vtx_handle = 0;
131  result = readMeshIface->get_node_coords( 3, vertex_map.size(), MB_START_ID, vtx_handle, coord_arrays );
132  if( MB_SUCCESS != result ) return result;
133 
134  // Copy vertex coordinates into entity sequence coordinate arrays
135  // and copy handle into vertex_map.
136  double *x = coord_arrays[0], *y = coord_arrays[1], *z = coord_arrays[2];
137  for( std::map< Point, EntityHandle >::iterator i = vertex_map.begin(); i != vertex_map.end(); ++i )
138  {
139  i->second = vtx_handle;
140  ++vtx_handle;
141  *x = i->first.coords[0];
142  ++x;
143  *y = i->first.coords[1];
144  ++y;
145  *z = i->first.coords[2];
146  ++z;
147  }
148 
149  // Allocate triangles
150  EntityHandle elm_handle = 0;
151  EntityHandle* connectivity;
152  result = readMeshIface->get_element_connect( triangles.size(), 3, MBTRI, MB_START_ID, elm_handle, connectivity );
153  if( MB_SUCCESS != result ) return result;
154 
155  // Use vertex_map to recover triangle connectivity from
156  // vertex coordinates.
157  EntityHandle* conn_sav = connectivity;
158  for( std::vector< Triangle >::iterator i = triangles.begin(); i != triangles.end(); ++i )
159  {
160  *connectivity = vertex_map[i->points[0]];
161  ++connectivity;
162  *connectivity = vertex_map[i->points[1]];
163  ++connectivity;
164  *connectivity = vertex_map[i->points[2]];
165  ++connectivity;
166  }
167 
168  // Notify MOAB of the new elements
169  result = readMeshIface->update_adjacencies( elm_handle, triangles.size(), 3, conn_sav );
170  if( MB_SUCCESS != result ) return result;
171 
172  if( file_id_tag )
173  {
174  Range vertices( vtx_handle, vtx_handle + vertex_map.size() - 1 );
175  Range elements( elm_handle, elm_handle + triangles.size() - 1 );
176  readMeshIface->assign_ids( *file_id_tag, vertices );
177  readMeshIface->assign_ids( *file_id_tag, elements );
178  }
179 
180  return MB_SUCCESS;
181 }
182 
183 // Read ASCII file
184 ErrorCode ReadSTL::ascii_read_triangles( const char* name, std::vector< ReadSTL::Triangle >& tris )
185 {
186  FILE* file = fopen( name, "r" );
187  if( !file )
188  {
189  return MB_FILE_DOES_NOT_EXIST;
190  }
191 
192  char header[81];
193  if( !fgets( header, sizeof( header ), file ) || // Read header line
194  strlen( header ) < 6 || // Must be at least 6 chars
195  header[strlen( header ) - 1] != '\n' || // Cannot exceed 80 chars
196  memcmp( header, "solid", 5 ) || // Must begin with "solid"
197  !isspace( header[5] ) )
198  { // Followed by a whitespace char
199  fclose( file );
200  return MB_FILE_WRITE_ERROR;
201  }
202 
203  // Use tokenizer for remainder of parsing
204  FileTokenizer tokens( file, readMeshIface );
205 
206  Triangle tri;
207  float norm[3];
208 
209  // Read until end of file. If we reach "endsolid", read
210  // was successful. If EOF before "endsolid", return error.
211  for( ;; )
212  {
213  // Check for either another facet or the end of the list.
214  const char* const expected[] = { "facet", "endsolid", 0 };
215  switch( tokens.match_token( expected ) )
216  {
217  case 1:
218  break; // Found another facet
219  case 2:
220  return MB_SUCCESS; // Found "endsolid" -- done
221  default:
222  return MB_FILE_WRITE_ERROR; // Found something else, or EOF
223  }
224 
225  if( !tokens.match_token( "normal" ) || // Expect "normal" keyword
226  !tokens.get_floats( 3, norm ) || // Followed by normal vector
227  !tokens.match_token( "outer" ) || // Followed by "outer loop"
228  !tokens.match_token( "loop" ) )
229  return MB_FILE_WRITE_ERROR;
230 
231  // For each of three triangle vertices
232  for( int i = 0; i < 3; i++ )
233  {
234  if( !tokens.match_token( "vertex" ) || !tokens.get_floats( 3, tri.points[i].coords ) )
235  return MB_FILE_WRITE_ERROR;
236  }
237 
238  if( !tokens.match_token( "endloop" ) || // Facet ends with "endloop"
239  !tokens.match_token( "endfacet" ) ) // and then "endfacet"
240  return MB_FILE_WRITE_ERROR;
241 
242  tris.push_back( tri );
243  }
244 
245  fclose( file );
246  return MB_SUCCESS;
247 }
248 
249 // Header block from binary STL file (84 bytes long)
251 {
252  char comment[80]; // 80 byte comment string (null terminated?)
253  uint32_t count; // Number of triangles - 4 byte integer
254 };
255 
256 // Triangle spec from file (50 bytes)
257 struct BinaryTri
258 {
259  float normal[3]; // Normal as 3 4-byte little-endian IEEE floats
260  float coords[9]; // Vertex coords as 9 4-byte little-endian IEEE floats
261  char pad[2];
262 };
263 
264 // Read a binary STL file
266  ReadSTL::ByteOrder byte_order,
267  std::vector< ReadSTL::Triangle >& tris )
268 {
269  FILE* file = fopen( name, "rb" );
270  if( !file )
271  {
272  return MB_FILE_DOES_NOT_EXIST;
273  }
274 
275  // Read header block
276  BinaryHeader header;
277  if( fread( &header, 84, 1, file ) != 1 )
278  {
279  fclose( file );
280  return MB_FILE_WRITE_ERROR;
281  }
282 
283  // Allow user setting for byte order, default to little endian
284  const bool want_big_endian = ( byte_order == STL_BIG_ENDIAN );
285  const bool am_big_endian = !SysUtil::little_endian();
286  bool swap_bytes = ( want_big_endian == am_big_endian );
287 
288  // Compare the number of triangles to the length of the file.
289  // The file must contain an 80-byte description, a 4-byte
290  // triangle count and 50 bytes per triangle.
291  //
292  // The triangle count *may* allow us to determine the byte order
293  // of the file, if it is not an endian-symmetric value.
294  //
295  // We need to compare the expected size calculated from the triangle
296  // count with the file size anyway, as an invalid file or a byte-
297  // swapping issue could result in a very large (incorrect) value for
298  // num_tri, resulting in a SEGFAULT.
299 
300  // Get expected number of triangles
301  if( swap_bytes ) SysUtil::byteswap( &header.count, 1 );
302  unsigned long num_tri = header.count;
303 
304  // Get the file length
305  long filesize = SysUtil::filesize( file );
306  if( filesize >= 0 )
307  { // -1 indicates could not determine file size (e.g. reading from FIFO)
308  // Check file size, but be careful of numeric overflow
309  if( ULONG_MAX / 50 - 84 < num_tri || // Next calc would have overflow
310  84 + 50 * num_tri != (unsigned long)filesize )
311  {
312  // Unless the byte order was specified explicitly in the
313  // tag, try the opposite byte order.
314  uint32_t num_tri_tmp = header.count;
315  SysUtil::byteswap( &num_tri_tmp, 1 );
316  unsigned long num_tri_swap = num_tri_tmp;
317  if( byte_order != STL_UNKNOWN_BYTE_ORDER || // If byte order was specified, fail now
318  ULONG_MAX / 50 - 84 < num_tri_swap || // Watch for overflow in next line
319  84 + 50 * num_tri_swap != (unsigned long)filesize )
320  {
321  fclose( file );
322  return MB_FILE_WRITE_ERROR;
323  }
325  num_tri = num_tri_swap;
326  }
327  }
328 
329  // Allocate storage for triangles
330  tris.resize( num_tri );
331 
332  // Read each triangle
333  BinaryTri tri; // Binary block read from file
334  for( std::vector< Triangle >::iterator i = tris.begin(); i != tris.end(); ++i )
335  {
336  if( fread( &tri, 50, 1, file ) != 1 )
337  {
338  fclose( file );
339  return MB_FILE_WRITE_ERROR;
340  }
341 
342  if( swap_bytes ) SysUtil::byteswap( tri.coords, 9 );
343 
344  for( unsigned j = 0; j < 9; ++j )
345  i->points[j / 3].coords[j % 3] = tri.coords[j];
346  }
347 
348  fclose( file );
349  return MB_SUCCESS;
350 }
351 
353 {
354  return new ReadSTL( iface );
355 }
356 
357 } // namespace moab