Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
ReadOBJ.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 #include "ReadOBJ.hpp"
17 #include <iostream>
18 #include <sstream>
19 #include <fstream>
20 #include <vector>
21 #include <cstdlib>
22 #include <map>
23 #include <cassert>
24 #include <cmath>
25 
26 #include "moab/Core.hpp"
27 #include "moab/Interface.hpp"
28 #include "moab/ReadUtilIface.hpp"
29 #include "Internals.hpp"
30 #include "moab/Range.hpp"
31 #include "moab/CartVect.hpp"
32 #include "moab/FileOptions.hpp"
33 #include "FileTokenizer.hpp"
34 #include "MBTagConventions.hpp"
35 #include "moab/CN.hpp"
36 #include "moab/GeomTopoTool.hpp"
37 
38 namespace moab
39 {
40 
42 {
43  return new ReadOBJ( iface );
44 }
45 
46 // Subset of starting tokens currently supported
47 const char* ReadOBJ::delimiters = " ";
48 const char* object_start_token = "o";
49 const char* group_start_token = "g";
50 const char* vertex_start_token = "v";
51 const char* face_start_token = "f";
52 
53 #define OBJ_AMBIGUOUS "AMBIGUOUS"
54 #define OBJ_UNDEFINED "UNDEFINED"
55 
56 // Name of geometric entities
57 const char* const geom_name[] = { "Vertex\0", "Curve\0", "Surface\0", "Volume\0" };
58 
59 // Geometric Categories
60 const char geom_category[][CATEGORY_TAG_SIZE] = { "Vertex\0", "Curve\0", "Surface\0", "Volume\0", "Group\0" };
61 
62 // Constructor
64  : MBI( impl ), geom_tag( 0 ), id_tag( 0 ), name_tag( 0 ), category_tag( 0 ), faceting_tol_tag( 0 ),
65  geometry_resabs_tag( 0 ), obj_name_tag( 0 )
66 {
67  assert( NULL != impl );
69  myGeomTool = new GeomTopoTool( impl );
70  assert( NULL != readMeshIface );
71 
72  // Get all handles
73  int negone = -1;
74  ErrorCode rval;
76  &negone );
77  MB_CHK_ERR_RET( rval );
78 
79  id_tag = MBI->globalId_tag();
80 
82  MB_CHK_ERR_RET( rval );
83 
86  MB_CHK_ERR_RET( rval );
87 
88  rval = MBI->tag_get_handle( "OBJECT_NAME", 32, MB_TYPE_OPAQUE, obj_name_tag, MB_TAG_SPARSE | MB_TAG_CREAT );
89  MB_CHK_ERR_RET( rval );
90 
92  MB_CHK_ERR_RET( rval );
93 
94  rval =
96  MB_CHK_ERR_RET( rval );
97 }
98 
99 // Destructor
101 {
102  if( readMeshIface )
103  {
105  readMeshIface = 0;
106  }
107 
108  delete myGeomTool;
109 }
110 
111 ErrorCode ReadOBJ::read_tag_values( const char* /*file_name*/,
112  const char* /*tag_name*/,
113  const FileOptions& /*opts*/,
114  std::vector< int >& /*tag_values_out*/,
115  const SubsetList* /*subset_list*/ )
116 {
117  return MB_NOT_IMPLEMENTED;
118 }
119 
120 // Load the file as called by the Interface function
121 ErrorCode ReadOBJ::load_file( const char* filename,
122  const EntityHandle*,
123  const FileOptions&,
124  const ReaderIface::SubsetList* subset_list,
125  const Tag* /*file_id_tag*/ )
126 {
127  int ignored = 0; // Number of lines not beginning with o, v, or f
128  std::string line; // The current line being read
129  EntityHandle vert_meshset;
130  EntityHandle curr_meshset; // Current object meshset
131  std::string object_name;
132  std::vector< EntityHandle > vertex_list;
133  int object_id = 0, group_id = 0; // ID number for each volume/surface
134  int num_groups;
135 
136  // At this time, there is no support for reading a subset of the file
137  if( subset_list )
138  {
139  MB_SET_ERR( MB_UNSUPPORTED_OPERATION, "Reading subset of files not supported for OBJ." );
140  }
141 
142  std::ifstream input_file( filename ); // Filestream for OBJ file
143 
144  // Check that the file can be read
145  if( !input_file.good() )
146  {
147  std::cout << "Problems reading file = " << filename << std::endl;
148  return MB_FILE_DOES_NOT_EXIST;
149  }
150 
151  // If the file can be read
152  if( input_file.is_open() )
153  {
154 
155  // create meshset for global vertices
156  MB_CHK_SET_ERR( MBI->create_meshset( MESHSET_SET, vert_meshset ), "Failed to create global vert meshset." );
157 
158  while( std::getline( input_file, line ) )
159  {
160  // Skip blank lines in file
161  if( line.length() == 0 ) continue;
162 
163  // Tokenize the line
164  std::vector< std::string > tokens;
165  tokenize( line, tokens, delimiters );
166 
167  // Each group and object line must have a name, token size is at least 2
168  // Each vertex and face line should have token size of at least 4
169  if( tokens.size() < 2 ) continue;
170 
171  switch( get_keyword( tokens ) )
172  {
173  // Object line
174  case object_start: {
175  object_id++;
176  object_name = tokens[1]; // Get name of object
177 
178  // Create new meshset for object
179  MB_CHK_ERR( create_new_object( object_name, object_id, curr_meshset ) );
180  break;
181  }
182 
183  // Group line
184  case group_start: {
185  group_id++;
186  num_groups = tokens.size() - 1;
187  std::string group_name = "Group";
188  for( int i = 0; i < num_groups; i++ )
189  {
190  group_name = group_name + '_' + tokens[i + 1];
191  }
192 
193  // Create new meshset for group
194  MB_CHK_ERR( create_new_group( group_name, group_id, curr_meshset ) );
195  break;
196  }
197 
198  // Vertex line
199  case vertex_start: {
200  // Read vertex and return EH
201  EntityHandle new_vertex_eh;
202  MB_CHK_ERR( create_new_vertex( tokens, new_vertex_eh ) );
203 
204  // Add new vertex EH to list
205  vertex_list.push_back( new_vertex_eh );
206 
207  // Add new vertex EH to the meshset
208  MB_CHK_SET_ERR( MBI->add_entities( vert_meshset, &new_vertex_eh, 1 ),
209  "Failed to add vertex to global meshset." );
210  break;
211  }
212 
213  // Face line
214  case face_start: {
215  // Faces in .obj file can have 2, 3, or 4 vertices. If the face has
216  // 3 vertices, the EH will be immediately added to the meshset.
217  // If 4, face is split into triangles. Anything else is ignored.
218  EntityHandle new_face_eh;
219 
220  if( tokens.size() == 4 )
221  {
222  MB_CHK_ERR( create_new_face( tokens, vertex_list, new_face_eh ) );
223 
224  // Add new face EH to the meshset
225  MBI->add_entities( curr_meshset, &new_face_eh, 1 );
226  }
227 
228  else if( tokens.size() == 5 )
229  {
230  // Split_quad fxn will create 2 new triangles from 1 quad
231  Range new_faces_eh;
232  MB_CHK_ERR( split_quad( tokens, vertex_list, new_faces_eh ) );
233 
234  // Add new faces created by split quad to meshset
235  MBI->add_entities( curr_meshset, new_faces_eh );
236  }
237 
238  else
239  {
240  std::cout << "Neither tri nor a quad: " << line << std::endl;
241  }
242 
243  break;
244  }
245 
246  case valid_unsupported: {
247  // First token is not recognized as a supported character
248  ++ignored;
249  break;
250  }
251 
252  default: {
253  MB_SET_ERR( MB_FAILURE, "Invalid/unrecognized line" );
254  }
255  }
256  }
257  }
258 
259  // If no object lines are read (those beginning w/ 'o'), file is not obj type
260  if( object_id == 0 && group_id == 0 )
261  {
262  MB_SET_ERR( MB_FAILURE, "This is not an obj file. " );
263  }
264 
265  std::cout << "There were " << ignored << " ignored lines in this file." << std::endl;
266 
267  input_file.close();
268 
269  return MB_SUCCESS;
270 }
271 
272 /* The tokenize function will split an input line
273  * into a vector of strings based upon the delimiter
274  */
275 void ReadOBJ::tokenize( const std::string& str, std::vector< std::string >& tokens, const char* delimiters2 )
276 {
277  tokens.clear();
278 
279  std::string::size_type next_token_end, next_token_start = str.find_first_not_of( delimiters2, 0 );
280 
281  while( std::string::npos != next_token_start )
282  {
283  next_token_end = str.find_first_of( delimiters2, next_token_start );
284  if( std::string::npos == next_token_end )
285  {
286  tokens.push_back( str.substr( next_token_start ) );
287  next_token_start = std::string::npos;
288  }
289  else
290  {
291  tokens.push_back( str.substr( next_token_start, next_token_end - next_token_start ) );
292  next_token_start = str.find_first_not_of( delimiters2, next_token_end );
293  }
294  }
295 }
296 
297 keyword_type ReadOBJ::get_keyword( std::vector< std::string > tokens )
298 {
299  std::map< std::string, keyword_type > keywords;
300 
301  // currently supported
302  keywords["o"] = object_start;
303  keywords["g"] = group_start;
304  keywords["f"] = face_start;
305  keywords["v"] = vertex_start;
306 
307  // not currently supported, will be ignored
308  keywords["vn"] = valid_unsupported;
309  keywords["vt"] = valid_unsupported;
310  keywords["vp"] = valid_unsupported;
311  keywords["s"] = valid_unsupported;
312  keywords["mtllib"] = valid_unsupported;
313  keywords["usemtl"] = valid_unsupported;
314  keywords["#"] = valid_unsupported;
315  keywords["cstype"] = valid_unsupported;
316  keywords["deg"] = valid_unsupported;
317  keywords["bmat"] = valid_unsupported;
318  keywords["step"] = valid_unsupported;
319  keywords["p"] = valid_unsupported;
320  keywords["l"] = valid_unsupported;
321  keywords["curv"] = valid_unsupported;
322  keywords["curv2"] = valid_unsupported;
323  keywords["surf"] = valid_unsupported;
324  keywords["parm"] = valid_unsupported;
325  keywords["trim"] = valid_unsupported;
326  keywords["hole"] = valid_unsupported;
327  keywords["scrv"] = valid_unsupported;
328  keywords["sp"] = valid_unsupported;
329  keywords["end"] = valid_unsupported;
330  keywords["mg"] = valid_unsupported;
331  keywords["bevel"] = valid_unsupported;
332  keywords["c_interp"] = valid_unsupported;
333  keywords["d_interp"] = valid_unsupported;
334  keywords["lod"] = valid_unsupported;
335  keywords["shadow_obj"] = valid_unsupported;
336  keywords["trace_obj"] = valid_unsupported;
337  keywords["ctech"] = valid_unsupported;
338  keywords["stech"] = valid_unsupported;
339 
340  return keywords[match( tokens[0], keywords )];
341 }
342 
343 template < typename T >
344 std::string ReadOBJ::match( const std::string& token, std::map< std::string, T >& tokenList )
345 {
346  // Initialize with no match and obj_undefined as return string
347  std::string best_match = OBJ_UNDEFINED;
348 
349  // Search the map
350  for( typename std::map< std::string, T >::iterator thisToken = tokenList.begin(); thisToken != tokenList.end();
351  ++thisToken )
352  {
353  // If a perfect match break the loop (assume keyword list is unambiguous)
354  if( token == ( *thisToken ).first )
355  {
356  best_match = token;
357  break;
358  }
359  }
360 
361  // Possible return values: OBJ_UNDEFINED, keyword from list
362  return best_match;
363 }
364 
365 /*
366  * The create_new_object function starts a new meshset for each object
367  * that will contain all faces that make up the object.
368  */
369 ErrorCode ReadOBJ::create_new_object( std::string object_name, int curr_object, EntityHandle& object_meshset )
370 {
371  // Create meshset to store object
372  // This is also referred to as the surface meshset
373  MB_CHK_SET_ERR( MBI->create_meshset( MESHSET_SET, object_meshset ), "Failed to generate object mesh set." );
374 
375  // Set surface meshset tags
376  MB_CHK_SET_ERR( MBI->tag_set_data( name_tag, &object_meshset, 1, object_name.c_str() ),
377  "Failed to set mesh set name tag." );
378 
379  MB_CHK_SET_ERR( MBI->tag_set_data( id_tag, &object_meshset, 1, &( curr_object ) ),
380  "Failed to set mesh set ID tag." );
381 
382  int dim = 2;
383  MB_CHK_SET_ERR( MBI->tag_set_data( geom_tag, &object_meshset, 1, &( dim ) ), "Failed to set mesh set dim tag." );
384 
385  MB_CHK_SET_ERR( MBI->tag_set_data( category_tag, &object_meshset, 1, geom_category[2] ),
386  "Failed to set mesh set category tag." );
387 
388  /* Create volume entity set corresponding to surface
389  The volume meshset will have one child--
390  the meshset of the surface that bounds the object.
391  */
392  EntityHandle vol_meshset;
393  MB_CHK_SET_ERR( MBI->create_meshset( MESHSET_SET, vol_meshset ), "Failed to create volume mesh set." );
394 
395  MB_CHK_SET_ERR( MBI->add_parent_child( vol_meshset, object_meshset ),
396  "Failed to add object mesh set as child of volume mesh set." );
397 
398  /* Set volume meshset tags
399  The volume meshset is tagged with the same name as the surface meshset
400  for each object because of the direct relation between these entities.
401  */
402  MB_CHK_SET_ERR( MBI->tag_set_data( obj_name_tag, &vol_meshset, 1, object_name.c_str() ),
403  "Failed to set mesh set name tag." );
404 
405  MB_CHK_SET_ERR( MBI->tag_set_data( id_tag, &vol_meshset, 1, &( curr_object ) ), "Failed to set mesh set ID tag." );
406 
407  dim = 3;
408  MB_CHK_SET_ERR( MBI->tag_set_data( geom_tag, &vol_meshset, 1, &( dim ) ), "Failed to set mesh set dim tag." );
409 
410  MB_CHK_SET_ERR( MBI->tag_set_data( name_tag, &vol_meshset, 1, geom_name[3] ), "Failed to set mesh set name tag." );
411 
412  MB_CHK_SET_ERR( MBI->tag_set_data( category_tag, &vol_meshset, 1, geom_category[3] ),
413  "Failed to set mesh set category tag." );
414 
415  MB_CHK_SET_ERR( myGeomTool->set_sense( object_meshset, vol_meshset, SENSE_FORWARD ),
416  "Failed to set surface sense." );
417 
418  return moab::MB_SUCCESS;
419 }
420 
421 /*
422  * The create_new_group function starts a new meshset for each group
423  * that will contain all faces that make up the group
424  */
425 ErrorCode ReadOBJ::create_new_group( std::string group_name, int curr_group, EntityHandle& group_meshset )
426 {
427  // Create meshset to store group
428  MB_CHK_SET_ERR( MBI->create_meshset( MESHSET_SET, group_meshset ), "Failed to generate group mesh set." );
429 
430  // Set meshset tags
431  MB_CHK_SET_ERR( MBI->tag_set_data( name_tag, &group_meshset, 1, group_name.c_str() ),
432  "Failed to set mesh set name tag." );
433 
434  MB_CHK_SET_ERR( MBI->tag_set_data( id_tag, &group_meshset, 1, &( curr_group ) ), "Failed to set mesh set ID tag." );
435 
436  return moab::MB_SUCCESS;
437 }
438 
439 /* The create_new_vertex function converts a vector
440  of tokens (v x y z) to
441  the vertex format; a structure that has the three
442  coordinates as members.
443  */
444 ErrorCode ReadOBJ::create_new_vertex( std::vector< std::string > v_tokens, EntityHandle& vertex_eh )
445 {
446  vertex next_vertex;
447 
448  for( int i = 1; i < 4; i++ )
449  next_vertex.coord[i - 1] = atof( v_tokens[i].c_str() );
450 
451  MB_CHK_SET_ERR( MBI->create_vertex( next_vertex.coord, vertex_eh ), "Unbale to create vertex." );
452 
453  return moab::MB_SUCCESS;
454 }
455 
456 /* The create_new_face function converts a vector
457  of tokens ( f v1 v2 v3) ) to the face format;
458  a structure that has the three
459  connectivity points as members.
460  */
461 ErrorCode ReadOBJ::create_new_face( std::vector< std::string > f_tokens,
462  const std::vector< EntityHandle >& vertex_list,
463  EntityHandle& face_eh )
464 {
465  face next_face;
466 
467  for( int i = 1; i < 4; i++ )
468  {
469  int vertex_id = atoi( f_tokens[i].c_str() );
470 
471  // Some faces contain format 'vertex/texture'
472  // Remove the '/texture' and add the vertex to the list
473  std::size_t slash = f_tokens[i].find( '/' );
474  if( slash != std::string::npos )
475  {
476  std::string face = f_tokens[i].substr( 0, slash );
477  vertex_id = atoi( face.c_str() );
478  }
479 
480  next_face.conn[i - 1] = vertex_list[vertex_id - 1];
481  }
482 
483  MB_CHK_SET_ERR( MBI->create_element( MBTRI, next_face.conn, 3, face_eh ), "Unable to create new face." );
484 
485  return moab::MB_SUCCESS;
486 }
487 
488 // The split_quad function divides a quad face into 4 tri faces.
489 ErrorCode ReadOBJ::split_quad( std::vector< std::string > f_tokens,
490  std::vector< EntityHandle >& vertex_list,
491  Range& face_eh )
492 {
493  std::vector< EntityHandle > quad_vert_eh;
494 
495  // Loop over quad connectivity getting vertex EHs
496  for( int i = 1; i < 5; i++ )
497  {
498  int vertex_id = atoi( f_tokens[i].c_str() );
499  std::size_t slash = f_tokens[i].find( '/' );
500  if( slash != std::string::npos )
501  {
502  std::string face = f_tokens[i].substr( 0, slash );
503  vertex_id = atoi( face.c_str() );
504  }
505 
506  quad_vert_eh.push_back( vertex_list[vertex_id - 1] );
507  }
508 
509  // Create new tri faces
510  MB_CHK_SET_ERR( create_tri_faces( quad_vert_eh, face_eh ), "Failed to create triangles when splitting quad." );
511 
512  return moab::MB_SUCCESS;
513 }
514 
515 ErrorCode ReadOBJ::create_tri_faces( std::vector< EntityHandle > quad_vert_eh,
516  // EntityHandle center_vertex_eh,
517  Range& face_eh )
518 {
519  EntityHandle connectivity[3];
520  EntityHandle new_face;
521 
522  connectivity[0] = quad_vert_eh[0];
523  connectivity[1] = quad_vert_eh[1];
524  connectivity[2] = quad_vert_eh[2];
525  MB_CHK_SET_ERR( MBI->create_element( MBTRI, connectivity, 3, new_face ), "failed to create triangle" );
526  face_eh.insert( new_face );
527 
528  connectivity[0] = quad_vert_eh[2];
529  connectivity[1] = quad_vert_eh[3];
530  connectivity[2] = quad_vert_eh[0];
531  MB_CHK_SET_ERR( MBI->create_element( MBTRI, connectivity, 3, new_face ), "failed to create second triangle" );
532  face_eh.insert( new_face );
533 
534  return moab::MB_SUCCESS;
535 }
536 
537 } // namespace moab