Mesh Oriented datABase  (version 5.5.0)
An array-based unstructured mesh library
WriteTemplate.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 #ifdef WIN32
17 #ifdef _DEBUG
18 // turn off warnings that say they debugging identifier has been truncated
19 // this warning comes up when using some STL containers
20 #pragma warning( disable : 4786 )
21 #endif
22 #endif
23 
24 #include "WriteTemplate.hpp"
25 
26 #include <utility>
27 #include <algorithm>
28 #include <ctime>
29 #include <string>
30 #include <vector>
31 #include <cstdio>
32 #include <cstring>
33 #include <iostream>
34 
35 #include "moab/Interface.hpp"
36 #include "moab/Range.hpp"
37 #include "moab/CN.hpp"
38 #include <cassert>
39 #include "Internals.hpp"
40 #include "ExoIIUtil.hpp"
41 #include "MBTagConventions.hpp"
42 #include "moab/WriteUtilIface.hpp"
43 
44 namespace moab
45 {
46 
47 #define INS_ID( stringvar, prefix, id ) sprintf( stringvar, prefix, id )
48 
50 {
51  return new WriteTemplate( iface );
52 }
53 
54 WriteTemplate::WriteTemplate( Interface* impl ) : mbImpl( impl )
55 {
56  assert( impl != NULL );
57 
59 
60  // Initialize in case tag_get_handle fails below
61  //! Get and cache predefined tag handles
62  int negone = -1;
64  &negone );
65 
67  &negone );
68 
70  &negone );
71 
72  mGlobalIdTag = impl->globalId_tag();
73 
74  impl->tag_get_handle( "WriteTemplate element mark", 1, MB_TYPE_BIT, mEntityMark, MB_TAG_CREAT );
75 }
76 
78 {
81 }
82 
83 void WriteTemplate::reset_matset( std::vector< WriteTemplate::MaterialSetData >& matset_info )
84 {
85  std::vector< WriteTemplate::MaterialSetData >::iterator iter;
86 
87  for( iter = matset_info.begin(); iter != matset_info.end(); ++iter )
88  delete( *iter ).elements;
89 }
90 
91 ErrorCode WriteTemplate::write_file( const char* file_name,
92  const bool /* overwrite (commented out to remove warning) */,
93  const FileOptions& /*opts*/,
94  const EntityHandle* ent_handles,
95  const int num_sets,
96  const std::vector< std::string >& /* qa_list */,
97  const Tag* /* tag_list */,
98  int /* num_tags */,
99  int /* export_dimension */ )
100 {
101  assert( 0 != mMaterialSetTag && 0 != mNeumannSetTag && 0 != mDirichletSetTag );
102 
103  // Check the file name
104  if( NULL == strstr( file_name, ".template" ) ) return MB_FAILURE;
105 
106  std::vector< EntityHandle > matsets, dirsets, neusets;
107 
108  fileName = file_name;
109 
110  // Separate into material sets, dirichlet sets, neumann sets
111 
112  if( num_sets == 0 )
113  {
114  // Default to all defined sets
115  Range this_range;
116  mbImpl->get_entities_by_type_and_tag( 0, MBENTITYSET, &mMaterialSetTag, NULL, 1, this_range );
117  std::copy( this_range.begin(), this_range.end(), std::back_inserter( matsets ) );
118  this_range.clear();
119  mbImpl->get_entities_by_type_and_tag( 0, MBENTITYSET, &mDirichletSetTag, NULL, 1, this_range );
120  std::copy( this_range.begin(), this_range.end(), std::back_inserter( dirsets ) );
121  this_range.clear();
122  mbImpl->get_entities_by_type_and_tag( 0, MBENTITYSET, &mNeumannSetTag, NULL, 1, this_range );
123  std::copy( this_range.begin(), this_range.end(), std::back_inserter( neusets ) );
124  }
125  else
126  {
127  int dummy;
128  for( const EntityHandle* iter = ent_handles; iter < ent_handles + num_sets; ++iter )
129  {
130  if( MB_SUCCESS == mbImpl->tag_get_data( mMaterialSetTag, &( *iter ), 1, &dummy ) )
131  matsets.push_back( *iter );
132  else if( MB_SUCCESS == mbImpl->tag_get_data( mDirichletSetTag, &( *iter ), 1, &dummy ) )
133  dirsets.push_back( *iter );
134  else if( MB_SUCCESS == mbImpl->tag_get_data( mNeumannSetTag, &( *iter ), 1, &dummy ) )
135  neusets.push_back( *iter );
136  }
137  }
138 
139  // If there is nothing to write just return.
140  if( matsets.empty() && dirsets.empty() && neusets.empty() ) return MB_FILE_WRITE_ERROR;
141 
142  std::vector< WriteTemplate::MaterialSetData > matset_info;
143  std::vector< WriteTemplate::DirichletSetData > dirset_info;
144  std::vector< WriteTemplate::NeumannSetData > neuset_info;
145 
146  MeshInfo mesh_info;
147 
148  matset_info.clear();
149  if( gather_mesh_information( mesh_info, matset_info, neuset_info, dirset_info, matsets, neusets, dirsets ) !=
150  MB_SUCCESS )
151  {
152  reset_matset( matset_info );
153  return MB_FAILURE;
154  }
155 
156  // Try to open the file after gather mesh info succeeds
157  if( /* Test for file open failure */ false )
158  {
159  reset_matset( matset_info );
160  return MB_FAILURE;
161  }
162 
163  if( initialize_file( mesh_info ) != MB_SUCCESS )
164  {
165  reset_matset( matset_info );
166  return MB_FAILURE;
167  }
168 
169  if( write_nodes( mesh_info.num_nodes, mesh_info.nodes, mesh_info.num_dim ) != MB_SUCCESS )
170  {
171  reset_matset( matset_info );
172  return MB_FAILURE;
173  }
174 
175  if( write_matsets( mesh_info, matset_info, neuset_info ) )
176  {
177  reset_matset( matset_info );
178  return MB_FAILURE;
179  }
180 
181  return MB_SUCCESS;
182 }
183 
185  std::vector< WriteTemplate::MaterialSetData >& matset_info,
186  std::vector< WriteTemplate::NeumannSetData >& neuset_info,
187  std::vector< WriteTemplate::DirichletSetData >& dirset_info,
188  std::vector< EntityHandle >& matsets,
189  std::vector< EntityHandle >& neusets,
190  std::vector< EntityHandle >& dirsets )
191 {
192  std::vector< EntityHandle >::iterator vector_iter, end_vector_iter;
193 
194  mesh_info.num_nodes = 0;
195  mesh_info.num_elements = 0;
196  mesh_info.num_matsets = 0;
197 
198  int id = 0;
199 
200  vector_iter = matsets.begin();
201  end_vector_iter = matsets.end();
202 
203  mesh_info.num_matsets = matsets.size();
204 
205  std::vector< EntityHandle > parent_meshsets;
206 
207  // Clean out the bits for the element mark
209  mbImpl->tag_get_handle( "WriteTemplate element mark", 1, MB_TYPE_BIT, mEntityMark, MB_TAG_CREAT );
210 
211  int highest_dimension_of_element_matsets = 0;
212 
213  for( vector_iter = matsets.begin(); vector_iter != matsets.end(); ++vector_iter )
214  {
215  WriteTemplate::MaterialSetData matset_data;
216  matset_data.elements = new Range;
217 
218  // For the purpose of qa records, get the parents of these matsets
219  if( mbImpl->get_parent_meshsets( *vector_iter, parent_meshsets ) != MB_SUCCESS ) return MB_FAILURE;
220 
221  // Get all Entity Handles in the mesh set
222  Range dummy_range;
223  mbImpl->get_entities_by_handle( *vector_iter, dummy_range, true );
224 
225  // Find the dimension of the last entity in this range
226  Range::iterator entity_iter = dummy_range.end();
227  --entity_iter;
228  int this_dim = CN::Dimension( TYPE_FROM_HANDLE( *entity_iter ) );
229  entity_iter = dummy_range.begin();
230  while( entity_iter != dummy_range.end() && CN::Dimension( TYPE_FROM_HANDLE( *entity_iter ) ) != this_dim )
231  ++entity_iter;
232 
233  if( entity_iter != dummy_range.end() )
234  std::copy( entity_iter, dummy_range.end(), range_inserter( *( matset_data.elements ) ) );
235 
236  assert( matset_data.elements->begin() == matset_data.elements->end() ||
237  CN::Dimension( TYPE_FROM_HANDLE( *( matset_data.elements->begin() ) ) ) == this_dim );
238 
239  // Get the matset's id
240  if( mbImpl->tag_get_data( mMaterialSetTag, &( *vector_iter ), 1, &id ) != MB_SUCCESS )
241  {
242  MB_SET_ERR( MB_FAILURE, "Couldn't get matset id from a tag for an element matset" );
243  }
244 
245  matset_data.id = id;
246  matset_data.number_attributes = 0;
247 
248  // Iterate through all the elements in the meshset
249  Range::iterator elem_range_iter, end_elem_range_iter;
250  elem_range_iter = matset_data.elements->begin();
251  end_elem_range_iter = matset_data.elements->end();
252 
253  // Get the entity type for this matset, verifying that it's the same for all elements
254  // THIS ASSUMES HANDLES SORT BY TYPE!!!
255  EntityType entity_type = TYPE_FROM_HANDLE( *elem_range_iter );
256  --end_elem_range_iter;
257  if( entity_type != TYPE_FROM_HANDLE( *( end_elem_range_iter++ ) ) )
258  {
259  MB_SET_ERR( MB_FAILURE, "Entities in matset " << id << " not of common type" );
260  }
261 
262  int dimension = CN::Dimension( entity_type );
263 
264  if( dimension > highest_dimension_of_element_matsets ) highest_dimension_of_element_matsets = dimension;
265 
266  matset_data.moab_type = mbImpl->type_from_handle( *( matset_data.elements->begin() ) );
267  if( MBMAXTYPE == matset_data.moab_type ) return MB_FAILURE;
268 
269  std::vector< EntityHandle > tmp_conn;
270  mbImpl->get_connectivity( &( *( matset_data.elements->begin() ) ), 1, tmp_conn );
271  matset_data.element_type =
272  ExoIIUtil::get_element_type_from_num_verts( tmp_conn.size(), entity_type, dimension );
273 
274  if( matset_data.element_type == EXOII_MAX_ELEM_TYPE )
275  {
276  MB_SET_ERR( MB_FAILURE, "Element type in matset " << id << " didn't get set correctly" );
277  }
278 
280 
281  // Number of nodes for this matset
282  matset_data.number_elements = matset_data.elements->size();
283 
284  // Total number of elements
285  mesh_info.num_elements += matset_data.number_elements;
286 
287  // Get the nodes for the elements
288  mWriteIface->gather_nodes_from_elements( *matset_data.elements, mEntityMark, mesh_info.nodes );
289 
290  if( !neusets.empty() )
291  {
292  // If there are neusets, keep track of which elements are being written out
293  for( Range::iterator iter = matset_data.elements->begin(); iter != matset_data.elements->end(); ++iter )
294  {
295  unsigned char bit = 0x1;
296  mbImpl->tag_set_data( mEntityMark, &( *iter ), 1, &bit );
297  }
298  }
299 
300  matset_info.push_back( matset_data );
301  }
302 
303  // If user hasn't entered dimension, we figure it out
304  if( mesh_info.num_dim == 0 )
305  {
306  // Never want 1 or zero dimensions
307  if( highest_dimension_of_element_matsets < 2 )
308  mesh_info.num_dim = 3;
309  else
310  mesh_info.num_dim = highest_dimension_of_element_matsets;
311  }
312 
313  Range::iterator range_iter, end_range_iter;
314  range_iter = mesh_info.nodes.begin();
315  end_range_iter = mesh_info.nodes.end();
316 
317  mesh_info.num_nodes = mesh_info.nodes.size();
318 
319  //------dirsets--------
320 
321  vector_iter = dirsets.begin();
322  end_vector_iter = dirsets.end();
323 
324  for( ; vector_iter != end_vector_iter; ++vector_iter )
325  {
327  dirset_data.id = 0;
328  dirset_data.number_nodes = 0;
329 
330  // Get the dirset's id
331  if( mbImpl->tag_get_data( mDirichletSetTag, &( *vector_iter ), 1, &id ) != MB_SUCCESS )
332  {
333  MB_SET_ERR( MB_FAILURE, "Couldn't get id tag for dirset " << id );
334  }
335 
336  dirset_data.id = id;
337 
338  std::vector< EntityHandle > node_vector;
339  // Get the nodes of the dirset that are in mesh_info.nodes
340  if( mbImpl->get_entities_by_handle( *vector_iter, node_vector, true ) != MB_SUCCESS )
341  {
342  MB_SET_ERR( MB_FAILURE, "Couldn't get nodes in dirset " << id );
343  }
344 
345  std::vector< EntityHandle >::iterator iter, end_iter;
346  iter = node_vector.begin();
347  end_iter = node_vector.end();
348 
349  int j = 0;
350  unsigned char node_marked = 0;
351  ErrorCode result;
352  for( ; iter != end_iter; ++iter )
353  {
354  if( TYPE_FROM_HANDLE( *iter ) != MBVERTEX ) continue;
355  result = mbImpl->tag_get_data( mEntityMark, &( *iter ), 1, &node_marked );MB_CHK_SET_ERR( result, "Couldn't get mark data" );
356 
357  if( 0x1 == node_marked ) dirset_data.nodes.push_back( *iter );
358  j++;
359  }
360 
361  dirset_data.number_nodes = dirset_data.nodes.size();
362  dirset_info.push_back( dirset_data );
363  }
364 
365  //------neusets--------
366  vector_iter = neusets.begin();
367  end_vector_iter = neusets.end();
368 
369  for( ; vector_iter != end_vector_iter; ++vector_iter )
370  {
371  WriteTemplate::NeumannSetData neuset_data;
372 
373  // Get the neuset's id
374  if( mbImpl->tag_get_data( mNeumannSetTag, &( *vector_iter ), 1, &id ) != MB_SUCCESS ) return MB_FAILURE;
375 
376  neuset_data.id = id;
377  neuset_data.mesh_set_handle = *vector_iter;
378 
379  // Get the sides in two lists, one forward the other reverse; starts with forward sense
380  // by convention
381  Range forward_elems, reverse_elems;
382  if( get_neuset_elems( *vector_iter, 0, forward_elems, reverse_elems ) == MB_FAILURE ) return MB_FAILURE;
383 
384  ErrorCode result = get_valid_sides( forward_elems, 1, neuset_data );MB_CHK_SET_ERR( result, "Couldn't get valid sides data" );
385  result = get_valid_sides( reverse_elems, -1, neuset_data );MB_CHK_SET_ERR( result, "Couldn't get valid sides data" );
386 
387  neuset_data.number_elements = neuset_data.elements.size();
388  neuset_info.push_back( neuset_data );
389  }
390 
391  return MB_SUCCESS;
392 }
393 
395 {
396  // This is where we see if underlying element of side set element is included in output
397 
398  unsigned char element_marked = 0;
399  ErrorCode result;
400  for( Range::iterator iter = elems.begin(); iter != elems.end(); ++iter )
401  {
402  // Should insert here if "side" is a quad/tri on a quad/tri mesh
403  result = mbImpl->tag_get_data( mEntityMark, &( *iter ), 1, &element_marked );MB_CHK_SET_ERR( result, "Couldn't get mark data" );
404 
405  if( 0x1 == element_marked )
406  {
407  neuset_data.elements.push_back( *iter );
408 
409  // TJT TODO: the sense should really be # edges + 1or2
410  neuset_data.side_numbers.push_back( ( sense == 1 ? 1 : 2 ) );
411  }
412  else
413  { // Then "side" is probably a quad/tri on a hex/tet mesh
414  std::vector< EntityHandle > parents;
415  int dimension = CN::Dimension( TYPE_FROM_HANDLE( *iter ) );
416 
417  // Get the adjacent parent element of "side"
418  if( mbImpl->get_adjacencies( &( *iter ), 1, dimension + 1, false, parents ) != MB_SUCCESS )
419  {
420  MB_SET_ERR( MB_FAILURE, "Couldn't get adjacencies for neuset" );
421  }
422 
423  if( !parents.empty() )
424  {
425  // Make sure the adjacent parent element will be output
426  for( unsigned int k = 0; k < parents.size(); k++ )
427  {
428  result = mbImpl->tag_get_data( mEntityMark, &( parents[k] ), 1, &element_marked );MB_CHK_SET_ERR( result, "Couldn't get mark data" );
429 
430  int side_no, this_sense, this_offset;
431  if( 0x1 == element_marked &&
432  mbImpl->side_number( parents[k], *iter, side_no, this_sense, this_offset ) == MB_SUCCESS &&
433  this_sense == sense )
434  {
435  neuset_data.elements.push_back( parents[k] );
436  neuset_data.side_numbers.push_back( side_no + 1 );
437  break;
438  }
439  }
440  }
441  else
442  {
443  MB_SET_ERR( MB_FAILURE, "No parent element exists for element in neuset " << neuset_data.id );
444  }
445  }
446  }
447 
448  return MB_SUCCESS;
449 }
450 
451 ErrorCode WriteTemplate::write_nodes( const int num_nodes, const Range& nodes, const int dimension )
452 {
453  // See if should transform coordinates
454  ErrorCode result;
455  Tag trans_tag;
456  result = mbImpl->tag_get_handle( MESH_TRANSFORM_TAG_NAME, 16, MB_TYPE_DOUBLE, trans_tag );
457  bool transform_needed = true;
458  if( result == MB_TAG_NOT_FOUND ) transform_needed = false;
459 
460  int num_coords_to_fill = transform_needed ? 3 : dimension;
461 
462  std::vector< double* > coord_arrays( 3 );
463  coord_arrays[0] = new double[num_nodes];
464  coord_arrays[1] = new double[num_nodes];
465  coord_arrays[2] = NULL;
466 
467  if( num_coords_to_fill == 3 ) coord_arrays[2] = new double[num_nodes];
468 
469  result = mWriteIface->get_node_coords( dimension, num_nodes, nodes, mGlobalIdTag, 0, coord_arrays );
470  if( result != MB_SUCCESS )
471  {
472  delete[] coord_arrays[0];
473  delete[] coord_arrays[1];
474  if( coord_arrays[2] ) delete[] coord_arrays[2];
475  return result;
476  }
477 
478  if( transform_needed )
479  {
480  double trans_matrix[16];
481  const EntityHandle mesh = 0;
482  result = mbImpl->tag_get_data( trans_tag, &mesh, 1, trans_matrix );MB_CHK_SET_ERR( result, "Couldn't get transform data" );
483 
484  for( int i = 0; i < num_nodes; i++ )
485  {
486  double vec1[3];
487  double vec2[3];
488 
489  vec2[0] = coord_arrays[0][i];
490  vec2[1] = coord_arrays[1][i];
491  vec2[2] = coord_arrays[2][i];
492 
493  for( int row = 0; row < 3; row++ )
494  {
495  vec1[row] = 0.0;
496  for( int col = 0; col < 3; col++ )
497  vec1[row] += ( trans_matrix[( row * 4 ) + col] * vec2[col] );
498  }
499 
500  coord_arrays[0][i] = vec1[0];
501  coord_arrays[1][i] = vec1[1];
502  coord_arrays[2][i] = vec1[2];
503  }
504  }
505 
506  // Write the nodes
507 
508  /* Template - write nodes to file here in some way */
509 
510  // Clean up
511  delete[] coord_arrays[0];
512  delete[] coord_arrays[1];
513  if( coord_arrays[2] ) delete[] coord_arrays[2];
514 
515  return MB_SUCCESS;
516 }
517 
519  MeshInfo& /* mesh_info (commented out to remove warning) */,
520  std::vector< WriteTemplate::MaterialSetData >& matset_data,
521  std::vector< WriteTemplate::NeumannSetData >& /* neuset_data (commented out to remove warning) */ )
522 {
523  unsigned int i;
524  std::vector< int > connect;
525  const EntityHandle* connecth;
526  int num_connecth;
527  ErrorCode result;
528 
529  // Don't usually have anywhere near 31 nodes per element
530  connect.reserve( 31 );
531  Range::iterator rit;
532 
534  for( i = 0; i < matset_data.size(); i++ )
535  {
536  matset = matset_data[i];
537 
538  for( rit = matset.elements->begin(); rit != matset.elements->end(); ++rit )
539  {
540  // Get the connectivity of this element
541  result = mbImpl->get_connectivity( *rit, connecth, num_connecth );
542  if( MB_SUCCESS != result ) return result;
543 
544  // Get the vertex ids
545  result = mbImpl->tag_get_data( mGlobalIdTag, connecth, num_connecth, &connect[0] );
546  if( MB_SUCCESS != result ) return result;
547 
548  // Write the data
549  /* Template - write element connectivity here */
550 
551  if( /* Template - check for error condition! */ false ) return MB_FAILURE;
552  }
553  }
554 
555  return MB_SUCCESS;
556 }
557 
559 {
560  // Perform the initializations
561 
562  int coord_size, ncoords;
563 
564  coord_size = mesh_info.num_dim;
565  std::cout << "Coord_size = " << coord_size << std::endl;
566  /* Template - write coord size */
567 
568  ncoords = mesh_info.num_nodes;
569  std::cout << "ncoords = " << ncoords << std::endl;
570  /* Template - write num nodes*/
571 
572  /* Template - write information on the element types & numbers (depends
573  on material and other sets) */
574 
575  /* Node coordinate arrays: */
576  /* Template - initialize variable to hold coordinate arrays */
577 
578  return MB_SUCCESS;
579 }
580 
581 ErrorCode WriteTemplate::open_file( const char* filename )
582 {
583  // Not a valid filename
584  if( strlen( (const char*)filename ) == 0 )
585  {
586  MB_SET_ERR( MB_FAILURE, "Output filename not specified" );
587  }
588 
589  /* Template - open file & store somewhere */
590 
591  // File couldn't be opened
592  if( /* Template - check for file open error here! */ false )
593  {
594  MB_SET_ERR( MB_FAILURE, "Cannot open " << filename );
595  }
596 
597  return MB_SUCCESS;
598 }
599 
601  int current_sense,
602  Range& forward_elems,
603  Range& reverse_elems )
604 {
605  Range neuset_elems, neuset_meshsets;
606 
607  // Get the sense tag; don't need to check return, might be an error if the tag
608  // hasn't been created yet
609  Tag sense_tag = 0;
610  mbImpl->tag_get_handle( "SENSE", 1, MB_TYPE_INTEGER, sense_tag );
611 
612  // Get the entities in this set
613  ErrorCode result = mbImpl->get_entities_by_handle( neuset, neuset_elems, true );
614  if( MB_FAILURE == result ) return result;
615 
616  // Now remove the meshsets into the neuset_meshsets; first find the first meshset,
617  Range::iterator range_iter = neuset_elems.begin();
618  while( TYPE_FROM_HANDLE( *range_iter ) != MBENTITYSET && range_iter != neuset_elems.end() )
619  ++range_iter;
620 
621  // Then, if there are some, copy them into neuset_meshsets and erase from neuset_elems
622  if( range_iter != neuset_elems.end() )
623  {
624  std::copy( range_iter, neuset_elems.end(), range_inserter( neuset_meshsets ) );
625  neuset_elems.erase( range_iter, neuset_elems.end() );
626  }
627 
628  // OK, for the elements, check the sense of this set and copy into the right range
629  // (if the sense is 0, copy into both ranges)
630 
631  // Need to step forward on list until we reach the right dimension
632  Range::iterator dum_it = neuset_elems.end();
633  --dum_it;
634  int target_dim = CN::Dimension( TYPE_FROM_HANDLE( *dum_it ) );
635  dum_it = neuset_elems.begin();
636  while( target_dim != CN::Dimension( TYPE_FROM_HANDLE( *dum_it ) ) && dum_it != neuset_elems.end() )
637  ++dum_it;
638 
639  if( current_sense == 1 || current_sense == 0 )
640  std::copy( dum_it, neuset_elems.end(), range_inserter( forward_elems ) );
641  if( current_sense == -1 || current_sense == 0 )
642  std::copy( dum_it, neuset_elems.end(), range_inserter( reverse_elems ) );
643 
644  // Now loop over the contained meshsets, getting the sense of those and calling this
645  // function recursively
646  for( range_iter = neuset_meshsets.begin(); range_iter != neuset_meshsets.end(); ++range_iter )
647  {
648  // First get the sense; if it's not there, by convention it's forward
649  int this_sense;
650  if( 0 == sense_tag || MB_FAILURE == mbImpl->tag_get_data( sense_tag, &( *range_iter ), 1, &this_sense ) )
651  this_sense = 1;
652 
653  // Now get all the entities on this meshset, with the proper (possibly reversed) sense
654  get_neuset_elems( *range_iter, this_sense * current_sense, forward_elems, reverse_elems );
655  }
656 
657  return result;
658 }
659 
660 } // namespace moab