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