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 "WriteVtk.hpp"
25 #include "moab/VtkUtil.hpp"
26 #include "SysUtil.hpp"
27 
28 #include <fstream>
29 #include <iostream>
30 #include <stdio.h>
31 #include <assert.h>
32 #include <vector>
33 #include <set>
34 #include <map>
35 #include <iterator>
36 
37 #include "moab/Interface.hpp"
38 #include "moab/Range.hpp"
39 #include "moab/CN.hpp"
40 #include "MBTagConventions.hpp"
41 #include "moab/WriteUtilIface.hpp"
42 #include "Internals.hpp"
43 #include "moab/FileOptions.hpp"
44 
45 #define INS_ID(stringvar, prefix, id) \
46   sprintf(stringvar, prefix, id)
47 
48 namespace moab {
49 
50 const int DEFAULT_PRECISION = 10;
51 const bool DEFAULT_STRICT = true;
52 
factory(Interface * iface)53 WriterIface *WriteVtk::factory(Interface* iface)
54 {
55   return new WriteVtk(iface);
56 }
57 
WriteVtk(Interface * impl)58 WriteVtk::WriteVtk(Interface* impl)
59   : mbImpl(impl), writeTool(0), mStrict(DEFAULT_STRICT), freeNodes(0), createOneNodeCells(false)
60 {
61   assert(impl != NULL);
62   impl->query_interface(writeTool);
63 }
64 
~WriteVtk()65 WriteVtk::~WriteVtk()
66 {
67   mbImpl->release_interface(writeTool);
68 }
69 
write_file(const char * file_name,const bool overwrite,const FileOptions & opts,const EntityHandle * output_list,const int num_sets,const std::vector<std::string> &,const Tag * tag_list,int num_tags,int)70 ErrorCode WriteVtk::write_file(const char *file_name,
71                                const bool overwrite,
72                                const FileOptions& opts,
73                                const EntityHandle *output_list,
74                                const int num_sets,
75                                const std::vector<std::string>& /* qa_list */,
76                                const Tag* tag_list,
77                                int num_tags,
78                                int /* export_dimension */)
79 {
80   ErrorCode rval;
81 
82   // Get precision for node coordinates
83   int precision;
84   if (MB_SUCCESS != opts.get_int_option("PRECISION", precision))
85     precision = DEFAULT_PRECISION;
86 
87   if (MB_SUCCESS == opts.get_null_option("STRICT"))
88     mStrict = true;
89   else if (MB_SUCCESS == opts.get_null_option("RELAXED"))
90     mStrict = false;
91   else
92     mStrict = DEFAULT_STRICT;
93 
94   if (MB_SUCCESS == opts.get_null_option("CREATE_ONE_NODE_CELLS"))
95     createOneNodeCells = true;
96 
97   // Get entities to write
98   Range nodes, elems;
99   rval = gather_mesh(output_list, num_sets, nodes, elems);
100   if (MB_SUCCESS != rval)
101     return rval;
102 
103   // Honor overwrite flag
104   if (!overwrite) {
105     rval = writeTool->check_doesnt_exist(file_name);
106     if (MB_SUCCESS != rval)
107       return rval;
108   }
109 
110   // Create file
111   std::ofstream file(file_name);
112   if (!file) {
113     MB_SET_ERR(MB_FILE_WRITE_ERROR, "Could not open file: " << file_name);
114   }
115   file.precision(precision);
116 
117   // Write file
118   if ((rval = write_header(file              )) != MB_SUCCESS ||
119       (rval = write_nodes( file, nodes       )) != MB_SUCCESS ||
120       (rval = write_elems( file, nodes, elems)) != MB_SUCCESS ||
121       (rval = write_tags ( file, true,  nodes, tag_list, num_tags)) != MB_SUCCESS ||
122       (rval = write_tags ( file, false, elems, tag_list, num_tags)) != MB_SUCCESS) {
123     file.close();
124     remove(file_name);
125     return rval;
126   }
127 
128   return MB_SUCCESS;
129 }
130 
gather_mesh(const EntityHandle * set_list,int num_sets,Range & nodes,Range & elems)131 ErrorCode WriteVtk::gather_mesh(const EntityHandle* set_list,
132                                 int num_sets,
133                                 Range& nodes,
134                                 Range& elems)
135 {
136   ErrorCode rval;
137   int e;
138 
139   if (!set_list || !num_sets) {
140     Range a;
141     rval = mbImpl->get_entities_by_handle(0, a);
142     if (MB_SUCCESS != rval)
143       return rval;
144 
145     Range::const_iterator node_i, elem_i, set_i;
146     node_i = a.lower_bound(a.begin(), a.end(), CREATE_HANDLE(   MBVERTEX, 0, e));
147     elem_i = a.lower_bound(   node_i, a.end(), CREATE_HANDLE(     MBEDGE, 0, e));
148     set_i  = a.lower_bound(   elem_i, a.end(), CREATE_HANDLE(MBENTITYSET, 0, e));
149     nodes.merge(node_i, elem_i);
150     elems.merge(elem_i, set_i);
151 
152     // Filter out unsupported element types
153     EntityType et = MBEDGE;
154     for (et++; et < MBENTITYSET; et++) {
155       if (VtkUtil::get_vtk_type(et, CN::VerticesPerEntity(et)))
156         continue;
157       Range::iterator
158         eit = elems.lower_bound(elems.begin(), elems.end(), CREATE_HANDLE(et, 0, e)),
159         ep1it = elems.lower_bound(elems.begin(), elems.end(), CREATE_HANDLE(et + 1, 0, e));
160       elems.erase(eit, ep1it);
161     }
162   }
163   else {
164     std::set<EntityHandle> visited;
165     std::vector<EntityHandle> sets;
166     sets.reserve(num_sets);
167     std::copy(set_list, set_list + num_sets, std::back_inserter(sets));
168     while (!sets.empty()) {
169       // Get next set
170       EntityHandle set = sets.back();
171       sets.pop_back();
172       // Skip sets we've already done
173       if (!visited.insert(set).second)
174         continue;
175 
176       Range a;
177       rval = mbImpl->get_entities_by_handle(set, a);
178       if (MB_SUCCESS != rval)
179         return rval;
180 
181       Range::const_iterator node_i, elem_i, set_i;
182       node_i = a.lower_bound(a.begin(), a.end(), CREATE_HANDLE(   MBVERTEX, 0, e));
183       elem_i = a.lower_bound(   node_i, a.end(), CREATE_HANDLE(     MBEDGE, 0, e));
184       set_i  = a.lower_bound(   elem_i, a.end(), CREATE_HANDLE(MBENTITYSET, 0, e));
185       nodes.merge(node_i, elem_i);
186       elems.merge(elem_i, set_i);
187       std::copy(set_i, a.end(), std::back_inserter(sets));
188 
189       a.clear();
190       rval = mbImpl->get_child_meshsets(set, a);
191       std::copy(a.begin(), a.end(), std::back_inserter(sets));
192     }
193 
194     for (Range::const_iterator ei = elems.begin(); ei != elems.end(); ++ei) {
195       std::vector<EntityHandle> connect;
196       rval = mbImpl->get_connectivity(&(*ei), 1, connect);
197       if (MB_SUCCESS != rval)
198         return rval;
199 
200       for (unsigned int i = 0; i < connect.size(); ++i)
201         nodes.insert(connect[i]);
202     }
203   }
204 
205   if (nodes.empty()) {
206     MB_SET_ERR(MB_ENTITY_NOT_FOUND, "Nothing to write");
207   }
208 
209   return MB_SUCCESS;
210 }
211 
write_header(std::ostream & stream)212 ErrorCode WriteVtk::write_header(std::ostream& stream)
213 {
214   stream << "# vtk DataFile Version 3.0" << std::endl;
215   stream << MOAB_VERSION_STRING << std::endl;
216   stream << "ASCII" << std::endl;
217   stream << "DATASET UNSTRUCTURED_GRID" << std::endl;
218   return MB_SUCCESS;
219 }
220 
write_nodes(std::ostream & stream,const Range & nodes)221 ErrorCode WriteVtk::write_nodes(std::ostream& stream, const Range& nodes)
222 {
223   ErrorCode rval;
224 
225   stream << "POINTS " << nodes.size() << " double" << std::endl;
226 
227   double coords[3];
228   for (Range::const_iterator i = nodes.begin(); i != nodes.end(); ++i) {
229     coords[1] = coords[2] = 0.0;
230     rval = mbImpl->get_coords(&(*i), 1, coords);
231     if (MB_SUCCESS != rval)
232       return rval;
233     stream << coords[0] << ' ' << coords[1] << ' ' << coords[2] << std::endl;
234   }
235 
236   return MB_SUCCESS;
237 }
238 
write_elems(std::ostream & stream,const Range & nodes,const Range & elems)239 ErrorCode WriteVtk::write_elems(std::ostream& stream,
240                                 const Range& nodes,
241                                 const Range& elems)
242 {
243   ErrorCode rval;
244 
245 
246   Range connectivity; // because we now support polyhedra, it could contain faces
247   rval = mbImpl->get_connectivity(elems, connectivity); MB_CHK_ERR(rval);
248 
249   Range nodes_from_connectivity = connectivity.subset_by_type(MBVERTEX);
250   Range faces_from_connectivity = subtract(connectivity, nodes_from_connectivity); // these could be faces of polyhedra
251 
252   Range connected_nodes;
253   rval = mbImpl->get_connectivity(faces_from_connectivity, connected_nodes); MB_CHK_ERR(rval);
254   connected_nodes.merge(nodes_from_connectivity);
255 
256   Range free_nodes = subtract(nodes, connected_nodes);
257 
258   // Get and write counts
259   unsigned long num_elems, num_uses;
260   num_elems = num_uses = elems.size();
261 
262   std::map<EntityHandle, int> sizeFieldsPolyhedra;
263 
264   for (Range::const_iterator i = elems.begin(); i != elems.end(); ++i) {
265     EntityType type = mbImpl->type_from_handle(*i);
266     if (!VtkUtil::get_vtk_type(type, CN::VerticesPerEntity(type)))
267       continue;
268 
269 
270     EntityHandle elem=*i;
271     const EntityHandle * connect=NULL;
272     int conn_len=0;
273     rval = mbImpl->get_connectivity(elem, connect, conn_len);MB_CHK_ERR(rval);
274 
275     num_uses += conn_len;
276     // if polyhedra, we will count the number of nodes in each face too
277     if ( TYPE_FROM_HANDLE(elem) == MBPOLYHEDRON)
278     {
279       int numFields = 1; // there will be one for number of faces; forgot about this one
280       for (int j=0; j<conn_len; j++)
281       {
282         const EntityHandle * conn = NULL;
283         int num_nd=0;
284         rval = mbImpl->get_connectivity(connect[j], conn, num_nd);MB_CHK_ERR(rval);
285         numFields += num_nd +1;
286       }
287       sizeFieldsPolyhedra[elem] = numFields; // will be used later, at writing
288       num_uses +=  (numFields-conn_len);
289     }
290   }
291   freeNodes = (int)free_nodes.size();
292   if (!createOneNodeCells)
293     freeNodes=0; // do not create one node cells
294   stream << "CELLS " << num_elems + freeNodes<< ' ' << num_uses + 2*freeNodes << std::endl;
295 
296   // Write element connectivity
297   std::vector<int> conn_data;
298   std::vector<unsigned> vtk_types(elems.size() + freeNodes );
299   std::vector<unsigned>::iterator t = vtk_types.begin();
300   for (Range::const_iterator i = elems.begin(); i != elems.end(); ++i) {
301     // Get type information for element
302     EntityHandle elem = *i;
303     EntityType type = TYPE_FROM_HANDLE(elem);
304 
305     // Get element connectivity
306     const EntityHandle *  connect = NULL;
307     int conn_len = 0;
308     rval = mbImpl->get_connectivity(elem, connect, conn_len); MB_CHK_ERR(rval);
309 
310     // Get VTK type
311     const VtkElemType* vtk_type = VtkUtil::get_vtk_type(type, conn_len);
312     if (!vtk_type) {
313       // Try connectivity with 1 fewer node
314       vtk_type = VtkUtil::get_vtk_type(type, conn_len - 1);
315       if (vtk_type)
316         conn_len--;
317       else {
318         MB_SET_ERR(MB_FAILURE, "Vtk file format does not support elements of type " << CN::EntityTypeName(type) << " (" << (int)type << ") with " << conn_len << " nodes");
319       }
320     }
321 
322     // Save VTK type index for later
323     *t = vtk_type->vtk_type;
324     ++t;
325 
326     if (type!=MBPOLYHEDRON)
327     {
328       // Get IDs from vertex handles
329       assert(conn_len > 0);
330       conn_data.resize(conn_len);
331       for (int j = 0; j < conn_len; ++j)
332         conn_data[j] = nodes.index(connect[j]);
333 
334       // Write connectivity list
335       stream << conn_len;
336       if (vtk_type->node_order)
337         for (int k = 0; k < conn_len; ++k)
338           stream << ' ' << conn_data[vtk_type->node_order[k]];
339       else
340         for (int k = 0; k < conn_len; ++k)
341           stream << ' ' << conn_data[k];
342       stream << std::endl;
343     }
344     else
345     {
346       // POLYHEDRON needs a special case, loop over faces to get nodes
347       stream << sizeFieldsPolyhedra[elem] << " " << conn_len;
348       for (int k=0; k<conn_len; k++)
349       {
350         EntityHandle face=connect[k];
351         const EntityHandle * conn = NULL;
352         int num_nodes=0;
353         rval = mbImpl->get_connectivity(face, conn, num_nodes);MB_CHK_ERR(rval);
354         //        num_uses += num_nd + 1; // 1 for number of vertices in face
355         conn_data.resize(num_nodes);
356         for (int j = 0; j < num_nodes; ++j)
357           conn_data[j] = nodes.index(conn[j]);
358 
359         stream << ' ' << num_nodes;
360 
361         for (int j = 0; j < num_nodes; ++j)
362           stream << ' ' << conn_data[j];
363       }
364       stream << std::endl;
365 
366     }
367   }
368 
369   if (createOneNodeCells)
370     for (Range::const_iterator v=free_nodes.begin(); v!= free_nodes.end(); ++v, ++t)
371     {
372       EntityHandle node=*v;
373       stream << "1 " << nodes.index(node) << std::endl;
374       *t = 1;
375     }
376 
377   // Write element types
378   stream << "CELL_TYPES " << vtk_types.size() << std::endl;
379   for (std::vector<unsigned>::const_iterator i = vtk_types.begin(); i != vtk_types.end(); ++i)
380     stream << *i << std::endl;
381 
382   return MB_SUCCESS;
383 }
384 
write_tags(std::ostream & stream,bool nodes,const Range & entities,const Tag * tag_list,int num_tags)385 ErrorCode WriteVtk::write_tags(std::ostream& stream,
386                                bool nodes,
387                                const Range& entities,
388                                const Tag* tag_list,
389                                int num_tags)
390 {
391   ErrorCode rval;
392 
393   // The #$%@#$% MOAB interface does not have a function to retrieve
394   // all entities with a tag, only all entities with a specified type
395   // and tag. Define types to loop over depending on the if vertex
396   // or element tag data is being written. It seems horribly inefficient
397   // to have the implementation subdivide the results by type and have
398   // to call that function once for each type just to recombine the results.
399   // Unfortunately, there doesn't seem to be any other way.
400   EntityType low_type, high_type;
401   if (nodes) {
402     low_type = MBVERTEX;
403     high_type = MBEDGE;
404   }
405   else {
406     low_type = MBEDGE;
407     high_type = MBENTITYSET;
408   }
409 
410   // Get all defined tags
411   std::vector<Tag> tags;
412   std::vector<Tag>::iterator i;
413   rval = writeTool->get_tag_list(tags, tag_list, num_tags, false);
414   if (MB_SUCCESS != rval)
415     return rval;
416 
417   // For each tag...
418   bool entities_have_tags = false;
419   for (i = tags.begin(); i != tags.end(); ++i) {
420     // Skip tags holding entity handles -- no way to save them
421     DataType dtype;
422     rval = mbImpl->tag_get_data_type(*i, dtype);
423     if (MB_SUCCESS != rval)
424       return rval;
425     if (dtype == MB_TYPE_HANDLE)
426       continue;
427 
428     // If in strict mode, don't write tags that do not fit in any
429     // attribute type (SCALAR : 1 to 4 values, VECTOR : 3 values, TENSOR : 9 values)
430     if (mStrict) {
431       int count;
432       rval = mbImpl->tag_get_length(*i, count);
433       if (MB_SUCCESS != rval)
434         return rval;
435       if (count < 1 || (count > 4 && count != 9))
436         continue;
437     }
438 
439     // Get subset of input entities that have the tag set
440     Range tagged;
441     for (EntityType type = low_type; type < high_type; ++type) {
442       Range tmp_tagged;
443       rval = mbImpl->get_entities_by_type_and_tag(0, type, &(*i), 0, 1, tmp_tagged);
444       if (MB_SUCCESS != rval)
445         return rval;
446       tmp_tagged = intersect(tmp_tagged, entities);
447       tagged.merge(tmp_tagged);
448     }
449 
450     // If any entities were tagged
451     if (!tagged.empty()) {
452       // If this is the first tag being written for the
453       // entity type, write the label marking the beginning
454       // of the tag data.
455       if (!entities_have_tags) {
456         entities_have_tags = true;
457         if (nodes)
458           stream << "POINT_DATA "  << entities.size() << std::endl;
459         else
460           stream << "CELL_DATA " << entities.size() + freeNodes << std::endl;
461       }
462 
463       // Write the tag
464       rval = write_tag(stream, *i, entities, tagged);
465       if (MB_SUCCESS != rval)
466         return rval;
467     }
468   }
469 
470   return MB_SUCCESS;
471 }
472 
473 template <typename T>
write_data(std::ostream & stream,const std::vector<T> & data,unsigned vals_per_tag)474 void WriteVtk::write_data(std::ostream& stream,
475                           const std::vector<T>& data,
476                           unsigned vals_per_tag)
477 {
478   typename std::vector<T>::const_iterator d = data.begin();
479   const unsigned long n = data.size() / vals_per_tag;
480 
481   for (unsigned long i = 0; i < n; ++i) {
482     for (unsigned j = 0; j < vals_per_tag; ++j, ++d) {
483       if (sizeof(T) == 1)
484         stream << (unsigned int)*d << ' ';
485       else
486         stream << *d << ' ';
487     }
488     stream << std::endl;
489   }
490 }
491 
492 //template <>
493 //void WriteVtk::write_data<unsigned char>(std::ostream& stream,
494 //                                         const std::vector<unsigned char>& data,
495 //                                         unsigned vals_per_tag)
496 //{
497 //  std::vector<unsigned char>::const_iterator d = data.begin();
498 //  const unsigned long n = data.size() / vals_per_tag;
499 //
500 //  for (unsigned long i = 0; i < n; ++i) {
501 //    for (unsigned j = 0; j < vals_per_tag; ++j, ++d)
502 //      stream << (unsigned int)*d << ' ';
503 //    stream << std::endl;
504 //  }
505 //}
506 
507 template <typename T>
write_tag(std::ostream & stream,Tag tag,const Range & entities,const Range & tagged,const int)508 ErrorCode WriteVtk::write_tag(std::ostream& stream,
509                               Tag tag,
510                               const Range& entities,
511                               const Range& tagged,
512                               const int)
513 {
514   ErrorCode rval;
515   int addFreeNodes = 0;
516   if (TYPE_FROM_HANDLE(entities[0])>MBVERTEX)
517     addFreeNodes = freeNodes;
518   // we created freeNodes 1-node cells, so we have to augment cell data too
519   // we know that the 1 node cells are added at the end, after all other cells;
520   // so the default values will be set to those extra , artificial cells
521   const unsigned long n = entities.size() + addFreeNodes;
522 
523   // Get tag properties
524 
525   std::string name;
526   int vals_per_tag;
527   if (MB_SUCCESS != mbImpl->tag_get_name(tag, name) ||
528       MB_SUCCESS != mbImpl->tag_get_length(tag, vals_per_tag))
529     return MB_FAILURE;
530 
531   // Get a tag value for each entity. Do this by initializing the
532   // "data" vector with zero, and then filling in the values for
533   // the entities that actually have the tag set.
534   std::vector<T> data;
535   data.resize(n * vals_per_tag, 0);
536   // If there is a default value for the tag, set the actual default value
537   std::vector<T> def_value(vals_per_tag);
538   rval = mbImpl->tag_get_default_value(tag, &(def_value[0]));
539   if (MB_SUCCESS == rval)
540      SysUtil::setmem(&(data[0]), &(def_value[0]), vals_per_tag * sizeof(T), n);
541 
542   Range::const_iterator t = tagged.begin();
543   typename std::vector<T>::iterator d = data.begin();
544   for (Range::const_iterator i = entities.begin();
545        i != entities.end() && t != tagged.end(); ++i, d += vals_per_tag) {
546     if (*i == *t) {
547       ++t;
548       rval = mbImpl->tag_get_data(tag, &(*i), 1, &(*d));
549       if (MB_SUCCESS != rval)
550         return rval;
551     }
552   }
553 
554   // Write the tag values, one entity per line.
555   write_data(stream, data, vals_per_tag);
556 
557   return MB_SUCCESS;
558 }
559 
write_bit_tag(std::ostream & stream,Tag tag,const Range & entities,const Range & tagged)560 ErrorCode WriteVtk::write_bit_tag(std::ostream& stream,
561                                   Tag tag,
562                                   const Range& entities,
563                                   const Range& tagged)
564 {
565   ErrorCode rval;
566   const unsigned long n = entities.size();
567 
568   // Get tag properties
569 
570   std::string name;
571   int vals_per_tag;
572   if (MB_SUCCESS != mbImpl->tag_get_name(tag, name) ||
573       MB_SUCCESS != mbImpl->tag_get_length(tag, vals_per_tag))
574     return MB_FAILURE;
575 
576   if (vals_per_tag > 8) {
577     MB_SET_ERR(MB_FAILURE, "Invalid tag size for bit tag \"" << name << "\"");
578   }
579 
580   // Get a tag value for each entity.
581   // Get bits for each entity and unpack into
582   // one integer in the 'data' array for each bit.
583   // Initialize 'data' to zero because we will skip
584   // those entities for which the tag is not set.
585   std::vector<unsigned short> data;
586   data.resize(n * vals_per_tag, 0);
587   Range::const_iterator t = tagged.begin();
588   std::vector<unsigned short>::iterator d = data.begin();
589   for (Range::const_iterator i = entities.begin();
590        i != entities.end() && t != tagged.end(); ++i) {
591     if (*i == *t) {
592       ++t;
593       unsigned char value;
594       rval = mbImpl->tag_get_data(tag, &(*i), 1, &value);
595       for (int j = 0; j < vals_per_tag; ++j, ++d)
596         *d = (unsigned short)(value & (1 << j) ? 1 : 0);
597       if (MB_SUCCESS != rval)
598         return rval;
599     }
600     else {
601       // If tag is not set for entity, skip values in array
602       d += vals_per_tag;
603     }
604   }
605 
606   // Write the tag values, one entity per line.
607   write_data(stream, data, vals_per_tag);
608 
609   return MB_SUCCESS;
610 }
611 
write_tag(std::ostream & s,Tag tag,const Range & entities,const Range & tagged)612 ErrorCode WriteVtk::write_tag(std::ostream& s, Tag tag,
613                               const Range& entities,
614                               const Range& tagged)
615 {
616   // Get tag properties
617   std::string name;
618   DataType type;
619   int vals_per_tag;
620   if (MB_SUCCESS != mbImpl->tag_get_name(tag, name) ||
621       MB_SUCCESS != mbImpl->tag_get_length(tag, vals_per_tag) ||
622       MB_SUCCESS != mbImpl->tag_get_data_type(tag, type))
623     return MB_FAILURE;
624 
625   // Skip tags of type ENTITY_HANDLE
626   if (MB_TYPE_HANDLE == type)
627     return MB_FAILURE;
628 
629   // Now that we're past the point where the name would be used in
630   // an error message, remove any spaces to conform to VTK file.
631   for (std::string::iterator i = name.begin(); i != name.end(); ++i) {
632     if (isspace(*i) || iscntrl(*i))
633       *i = '_';
634   }
635 
636   // Write the tag description
637   if (3 == vals_per_tag && MB_TYPE_DOUBLE == type)
638     s << "VECTORS " << name << ' ' << VtkUtil::vtkTypeNames[type] << std::endl;
639   else if (9 == vals_per_tag)
640     s << "TENSORS " << name << ' ' << VtkUtil::vtkTypeNames[type] << std::endl;
641   else
642     s << "SCALARS " << name << ' ' << VtkUtil::vtkTypeNames[type] << ' '
643       << vals_per_tag << std::endl << "LOOKUP_TABLE default" << std::endl;
644 
645   // Write the tag data
646   switch (type) {
647     case MB_TYPE_OPAQUE:
648       return write_tag<unsigned char>(s, tag, entities, tagged, 0);
649     case MB_TYPE_INTEGER:
650       return write_tag<int>(s, tag, entities, tagged, 0);
651     case MB_TYPE_DOUBLE:
652       return write_tag<double>(s, tag, entities, tagged, 0);
653     case MB_TYPE_BIT:
654       return write_bit_tag(s, tag, entities, tagged);
655     default:
656       return MB_FAILURE;
657   }
658 }
659 
660 } // namespace moab
661