1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkCONVERGECFDReader.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkCONVERGECFDReader.h"
16 
17 #include "vtkBuffer.h"
18 #include "vtkCellArray.h"
19 #include "vtkCellData.h"
20 #include "vtkCommand.h"
21 #include "vtkDataArraySelection.h"
22 #include "vtkDataAssembly.h"
23 #include "vtkDirectory.h"
24 #include "vtkFloatArray.h"
25 #include "vtkInformation.h"
26 #include "vtkInformationVector.h"
27 #include "vtkNew.h"
28 #include "vtkObjectFactory.h"
29 #include "vtkPartitionedDataSetCollection.h"
30 #include "vtkPointData.h"
31 #include "vtkPolyData.h"
32 #include "vtkStreamingDemandDrivenPipeline.h"
33 #include "vtkUnstructuredGrid.h"
34 
35 #include <vtksys/RegularExpression.hxx>
36 
37 #include <algorithm>
38 #include <array>
39 #include <map>
40 #include <set>
41 #include <sstream>
42 
43 #define H5_USE_16_API
44 #include "vtk_hdf5.h"
45 
46 vtkStandardNewMacro(vtkCONVERGECFDReader);
47 
48 namespace
49 {
50 
51 //----------------------------------------------------------------------------
52 /**
53  * RAII class for automatically closing H5 handles.
54  */
55 #define DefineScopedHandle(name)                                                                   \
56   class ScopedH5##name##Handle                                                                     \
57   {                                                                                                \
58   public:                                                                                          \
59     ScopedH5##name##Handle(const ScopedH5##name##Handle& other) { this->Handle = other.Handle; }   \
60     ScopedH5##name##Handle(hid_t handle)                                                           \
61       : Handle(handle)                                                                             \
62     {                                                                                              \
63     }                                                                                              \
64     virtual ~ScopedH5##name##Handle()                                                              \
65     {                                                                                              \
66       if (this->Handle >= 0)                                                                       \
67       {                                                                                            \
68         H5##name##close(this->Handle);                                                             \
69       }                                                                                            \
70     }                                                                                              \
71                                                                                                    \
72     operator hid_t() const { return this->Handle; }                                                \
73                                                                                                    \
74   private:                                                                                         \
75     hid_t Handle;                                                                                  \
76   };
77 
78 // Defines ScopedH5AHandle closed with H5Aclose
79 DefineScopedHandle(A);
80 
81 // Defines ScopedH5DHandle closed with H5Dclose
82 DefineScopedHandle(D);
83 
84 // Defines ScopedH5FHandle closed with H5Fclose
85 DefineScopedHandle(F);
86 
87 // Defines ScopedH5GHandle closed with H5Gclose
88 DefineScopedHandle(G);
89 
90 // Defines ScopedH5SHandle closed with H5Sclose
91 DefineScopedHandle(S);
92 
93 // Defines ScopedH5THandle closed with H5Tclose
94 DefineScopedHandle(T);
95 
96 //----------------------------------------------------------------------------
97 /**
98  *  Check existence of array defined by pathName relative to fileId.
99  */
ArrayExists(hid_t fileId,const char * pathName)100 bool ArrayExists(hid_t fileId, const char* pathName)
101 {
102   return (H5Lexists(fileId, pathName, H5P_DEFAULT) > 0);
103 }
104 
105 //----------------------------------------------------------------------------
106 /**
107  *  Check existence of array defined by groupName relative to fileId.
108  */
GroupExists(hid_t fileId,const char * groupName)109 bool GroupExists(hid_t fileId, const char* groupName)
110 {
111   // Same implementation as ArrayExists, but that's okay.
112   return (H5Lexists(fileId, groupName, H5P_DEFAULT) > 0);
113 }
114 
115 //----------------------------------------------------------------------------
116 /**
117  *  Get length of array defined by pathName relative to fileId.
118  */
GetDataLength(hid_t fileId,const char * pathName)119 hsize_t GetDataLength(hid_t fileId, const char* pathName)
120 {
121   ScopedH5DHandle arrayId = H5Dopen(fileId, pathName);
122   if (arrayId < 0)
123   {
124     vtkGenericWarningMacro("No array named " << pathName << " available");
125     return 0;
126   }
127 
128   ScopedH5DHandle dataspace = H5Dget_space(arrayId);
129   if (H5Sget_simple_extent_ndims(dataspace) != 1)
130   {
131     vtkGenericWarningMacro("Array " << pathName << " dimensionality is not 1");
132     return 0;
133   }
134 
135   hsize_t length = 0;
136   int numDimensions = H5Sget_simple_extent_dims(dataspace, &length, nullptr);
137   if (numDimensions < 0)
138   {
139     vtkGenericWarningMacro("Failed to get length of array");
140     return 0;
141   }
142 
143   return length;
144 }
145 
146 //----------------------------------------------------------------------------
147 /**
148  *  Read a typed array and into an array passed in by the caller. Checks that the
149  *  number of elements in the array specified by fileId and pathName matches the length
150  *  argument n.
151  *
152  *  Returns true if reading succeeded, false otherwise.
153  */
154 template <typename T>
ReadArray(hid_t fileId,const char * pathName,T * data,hsize_t n)155 bool ReadArray(hid_t fileId, const char* pathName, T* data, hsize_t n)
156 {
157   ScopedH5DHandle arrayId = H5Dopen(fileId, pathName);
158   if (arrayId < 0)
159   {
160     return false;
161   }
162 
163   ScopedH5DHandle rawType = H5Dget_type(arrayId);
164   ScopedH5THandle dataType = H5Tget_native_type(rawType, H5T_DIR_ASCEND);
165   ScopedH5DHandle dataspace = H5Dget_space(arrayId);
166   if (H5Sget_simple_extent_ndims(dataspace) != 1)
167   {
168     vtkGenericWarningMacro("Array " << pathName << " dimensionality is not 1");
169     return false;
170   }
171 
172   hsize_t length = 0;
173   int numDims = H5Sget_simple_extent_dims(dataspace, &length, nullptr);
174   if (numDims < 0)
175   {
176     vtkGenericWarningMacro("Failed to get length of array");
177     return false;
178   }
179 
180   if (n != length)
181   {
182     vtkGenericWarningMacro(
183       "Size of array passed in does not match length of array. Skipping array.");
184     return false;
185   }
186 
187   if (H5Dread(arrayId, dataType, H5S_ALL, H5S_ALL, H5P_DEFAULT, data) < 0)
188   {
189     vtkGenericWarningMacro("Could not read " << pathName);
190     return false;
191   }
192 
193   return true;
194 }
195 
196 //----------------------------------------------------------------------------
197 /**
198  * Get an array of strings from a table defined by pathName relative to fileId.
199  * Strings are returned in the vector of strings parameter that was passed in.
200  */
ReadStrings(hid_t fileId,const char * path,std::vector<std::string> & strings)201 bool ReadStrings(hid_t fileId, const char* path, std::vector<std::string>& strings)
202 {
203   ScopedH5DHandle stringsId = H5Dopen(fileId, path);
204   if (stringsId < 0)
205   {
206     vtkGenericWarningMacro("Could not read " << path);
207     return false;
208   }
209 
210   ScopedH5THandle filetype = H5Dget_type(stringsId);
211   size_t sdim = H5Tget_size(filetype);
212   sdim++; /* Make room for null terminator */
213 
214   ScopedH5SHandle space = H5Dget_space(stringsId);
215   hsize_t dim;
216   int ndims = H5Sget_simple_extent_dims(space, &dim, nullptr);
217   if (ndims != 1)
218   {
219     vtkGenericWarningMacro("String array dimension not 1");
220     return false;
221   }
222 
223   char** rdata = new char*[dim];
224   rdata[0] = new char[dim * sdim];
225   for (hsize_t i = 1; i < dim; ++i)
226   {
227     rdata[i] = rdata[0] + i * sdim;
228   }
229 
230   ScopedH5THandle memtype = H5Tcopy(H5T_C_S1);
231   H5Tset_size(memtype, sdim);
232   if (H5Dread(stringsId, memtype, H5S_ALL, H5S_ALL, H5P_DEFAULT, rdata[0]) < 0)
233   {
234     vtkGenericWarningMacro("Could not read " << path);
235     return false;
236   }
237 
238   strings.clear();
239   for (hsize_t i = 0; i < dim; ++i)
240   {
241     strings.emplace_back(std::string(rdata[i]));
242   }
243 
244   delete[] rdata[0];
245   delete[] rdata;
246 
247   return true;
248 }
249 
SplitScalarAndVectorVariables(std::vector<std::string> & allVariables,std::vector<std::string> & vectorVariables)250 void SplitScalarAndVectorVariables(
251   std::vector<std::string>& allVariables, std::vector<std::string>& vectorVariables)
252 {
253   vectorVariables.clear();
254   for (const auto& varName : allVariables)
255   {
256     // See if variable is an array.
257     if (varName.find_last_of('_') == varName.size() - 2)
258     {
259       const char componentName = varName[varName.size() - 1];
260       if (componentName == 'X')
261       {
262         // Check that components Y and Z exist as well
263         std::string baseName = varName.substr(0, varName.size() - 2);
264         if (std::find(allVariables.begin(), allVariables.end(), baseName + "_Y") !=
265             allVariables.end() &&
266           std::find(allVariables.begin(), allVariables.end(), baseName + "_Z") !=
267             allVariables.end())
268         {
269           vectorVariables.emplace_back(baseName);
270         }
271       }
272     }
273   }
274 
275   // Now remove the vector variables from all variables. At the end, allVariables will contain
276   // only scalar array names.
277   for (const auto& varName : vectorVariables)
278   {
279     allVariables.erase(std::find(allVariables.begin(), allVariables.end(), varName + "_X"));
280     allVariables.erase(std::find(allVariables.begin(), allVariables.end(), varName + "_Y"));
281     allVariables.erase(std::find(allVariables.begin(), allVariables.end(), varName + "_Z"));
282   }
283 }
284 
285 } // anonymous namespace
286 
287 //----------------------------------------------------------------------------
288 class vtkCONVERGECFDReader::vtkInternal
289 {
290 public:
291   vtkCONVERGECFDReader* Self;
292   std::vector<std::string> CellDataScalarVariables;
293   std::vector<std::string> CellDataVectorVariables;
294   std::vector<std::string> ParcelDataTypes;
295   std::vector<std::string> ParcelDataScalarVariables;
296   std::vector<std::string> ParcelDataVectorVariables;
297 
298   // Clears out variable info
Reset()299   void Reset()
300   {
301     this->CellDataScalarVariables.clear();
302     this->CellDataVectorVariables.clear();
303     this->ParcelDataTypes.clear();
304   }
305 
306   // Get a parcel dataset at a given path
ReadParcelDataSet(hid_t streamId,const std::string & path)307   vtkSmartPointer<vtkPolyData> ReadParcelDataSet(hid_t streamId, const std::string& path)
308   {
309     vtkSmartPointer<vtkPolyData> parcels = vtkSmartPointer<vtkPolyData>::New();
310 
311     // Build PARCEL_X address string from path name
312     std::string parcelXPath = path + "/PARCEL_X";
313 
314     // Read parcel point locations
315     hsize_t parcelLength = GetDataLength(streamId, parcelXPath.c_str());
316 
317     vtkNew<vtkFloatArray> parcelPointArray;
318     parcelPointArray->SetNumberOfComponents(3);
319     parcelPointArray->SetNumberOfTuples(parcelLength);
320 
321     vtkNew<vtkBuffer<float>> floatBuffer;
322     floatBuffer->Allocate(parcelLength);
323 
324     std::array<char, 3> dimensionNames = { 'X', 'Y', 'Z' };
325     for (size_t c = 0; c < dimensionNames.size(); ++c)
326     {
327       std::stringstream name;
328       name << path << "/PARCEL_" << dimensionNames[c];
329       if (!ReadArray(streamId, name.str().c_str(), floatBuffer->GetBuffer(), parcelLength))
330       {
331         vtkGenericWarningMacro(
332           "No parcel coordinate array " << name.str() << " dataset available in " << name.str());
333         return nullptr;
334       }
335 
336       for (hsize_t j = 0; j < parcelLength; ++j)
337       {
338         parcelPointArray->SetTypedComponent(j, static_cast<int>(c), floatBuffer->GetBuffer()[j]);
339       }
340     }
341 
342     vtkNew<vtkPoints> parcelPoints;
343     parcelPoints->SetData(parcelPointArray);
344 
345     parcels->SetPoints(parcelPoints);
346 
347     // Create a vertex for each parcel point
348     vtkNew<vtkCellArray> parcelCells;
349     parcelCells->AllocateExact(parcelLength, 1);
350     for (vtkIdType id = 0; id < static_cast<vtkIdType>(parcelLength); ++id)
351     {
352       parcelCells->InsertNextCell(1, &id);
353     }
354     parcels->SetVerts(parcelCells);
355 
356     // Read parcel data arrays
357     for (int i = 0; i < this->Self->ParcelDataArraySelection->GetNumberOfArrays(); ++i)
358     {
359       std::string varName(this->Self->ParcelDataArraySelection->GetArrayName(i));
360       if (varName == "PARCEL_X" || varName == "PARCEL_Y" || varName == "PARCEL_Z" ||
361         this->Self->ParcelDataArraySelection->ArrayIsEnabled(varName.c_str()) == 0)
362       {
363         continue;
364       }
365 
366       auto begin = this->ParcelDataVectorVariables.begin();
367       auto end = this->ParcelDataVectorVariables.end();
368       bool isVector = std::find(begin, end, varName) != end;
369 
370       // This would be a lot simpler using a vtkSOADataArrayTemplate<float>, but
371       // until GetVoidPointer() is removed from more of the VTK code base, we
372       // will use a vtkFloatArray.
373       vtkNew<vtkFloatArray> dataArray;
374       bool success = true;
375       if (isVector)
376       {
377         std::string pathX = path + "/" + varName + "_X";
378         std::string pathY = path + "/" + varName + "_Y";
379         std::string pathZ = path + "/" + varName + "_Z";
380 
381         if (!ArrayExists(streamId, pathX.c_str()))
382         {
383           // This array just doesn't exist in this stream, skip it.
384           continue;
385         }
386 
387         // hsize_t length = GetDataLength(streamId, pathX.c_str());
388         dataArray->SetNumberOfComponents(3);
389         dataArray->SetNumberOfTuples(parcelLength);
390         dataArray->SetName(varName.c_str());
391 
392         if (static_cast<hsize_t>(floatBuffer->GetSize()) != parcelLength)
393         {
394           floatBuffer->Allocate(parcelLength);
395         }
396         success =
397           success && ReadArray(streamId, pathX.c_str(), floatBuffer->GetBuffer(), parcelLength);
398         for (hsize_t j = 0; j < parcelLength; ++j)
399         {
400           dataArray->SetTypedComponent(j, 0, floatBuffer->GetBuffer()[j]);
401         }
402         success =
403           success && ReadArray(streamId, pathY.c_str(), floatBuffer->GetBuffer(), parcelLength);
404         for (hsize_t j = 0; j < parcelLength; ++j)
405         {
406           dataArray->SetTypedComponent(j, 1, floatBuffer->GetBuffer()[j]);
407         }
408         success =
409           success && ReadArray(streamId, pathZ.c_str(), floatBuffer->GetBuffer(), parcelLength);
410         for (hsize_t j = 0; j < parcelLength; ++j)
411         {
412           dataArray->SetTypedComponent(j, 2, floatBuffer->GetBuffer()[j]);
413         }
414       }
415       else // !is_vector
416       {
417         std::string varPath(path);
418         varPath += "/" + varName;
419 
420         if (!ArrayExists(streamId, varPath.c_str()))
421         {
422           // This array just doesn't exist in this stream, skip it.
423           continue;
424         }
425 
426         dataArray->SetNumberOfComponents(1);
427         dataArray->SetNumberOfTuples(parcelLength);
428         dataArray->SetName(varName.c_str());
429         success =
430           success && ReadArray(streamId, varPath.c_str(), dataArray->GetPointer(0), parcelLength);
431       }
432 
433       if (success)
434       {
435         parcels->GetPointData()->AddArray(dataArray);
436       }
437     }
438 
439     return parcels;
440   }
441 };
442 
443 //----------------------------------------------------------------------------
vtkCONVERGECFDReader()444 vtkCONVERGECFDReader::vtkCONVERGECFDReader()
445   : FileName(nullptr)
446   , Internal(new vtkCONVERGECFDReader::vtkInternal())
447 {
448   this->SetNumberOfInputPorts(0);
449   this->SetNumberOfOutputPorts(1);
450   this->Internal->Self = this;
451 
452   this->CellDataArraySelection->AddObserver(
453     vtkCommand::ModifiedEvent, this, &vtkCONVERGECFDReader::Modified);
454   this->ParcelDataArraySelection->AddObserver(
455     vtkCommand::ModifiedEvent, this, &vtkCONVERGECFDReader::Modified);
456 }
457 
458 //----------------------------------------------------------------------------
~vtkCONVERGECFDReader()459 vtkCONVERGECFDReader::~vtkCONVERGECFDReader()
460 {
461   delete[] this->FileName;
462   this->FileName = nullptr;
463   delete this->Internal;
464 }
465 
466 //----------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)467 void vtkCONVERGECFDReader::PrintSelf(ostream& os, vtkIndent indent)
468 {
469   this->Superclass::PrintSelf(os, indent);
470 }
471 
472 //----------------------------------------------------------------------------
RequestInformation(vtkInformation *,vtkInformationVector **,vtkInformationVector * outInfos)473 int vtkCONVERGECFDReader::RequestInformation(
474   vtkInformation*, vtkInformationVector**, vtkInformationVector* outInfos)
475 {
476   if (!this->FileName || this->FileName[0] == '\0')
477   {
478     return 1;
479   }
480 
481   // Reset internal information
482   this->Internal->Reset();
483 
484   ScopedH5FHandle fileId = H5Fopen(this->FileName, H5F_ACC_RDONLY, H5P_DEFAULT);
485   if (fileId < 0)
486   {
487     vtkErrorMacro("Could not open HDF5 file '" << this->FileName << "'");
488     return 0;
489   }
490 
491   // Iterate over all streams to find available cell data arrays and parcel data arrays
492   std::set<std::string> cellVariables;
493   std::set<std::string> parcelVariables;
494   int streamCount = 0;
495   do
496   {
497     herr_t status = 0;
498     std::ostringstream streamName;
499     streamName << "/STREAM_" << std::setw(2) << std::setfill('0') << streamCount;
500     H5Eset_auto(nullptr, nullptr);
501     status = H5Gget_objinfo(fileId, streamName.str().c_str(), false, nullptr);
502     if (status < 0)
503     {
504       break;
505     }
506 
507     // Open the group
508     ScopedH5GHandle streamId = H5Gopen(fileId, streamName.str().c_str());
509     if (streamId < 0)
510     {
511       // Group exists, but could not be opened
512       vtkErrorMacro("Could not open stream " << streamName.str());
513       break;
514     }
515 
516     std::vector<std::string> cellDataVariables;
517     if (!ReadStrings(streamId, "VARIABLE_NAMES/CELL_VARIABLES", cellDataVariables))
518     {
519       vtkErrorMacro("Could not read cell variable names");
520       return 0;
521     }
522 
523     // Insert variables into set to ensure uniqueness
524     for (auto& cellVariableName : cellDataVariables)
525     {
526       cellVariables.insert(cellVariableName);
527     }
528 
529     // Pre- 3.1 format
530     std::vector<std::string> parcelDataScalarVariables;
531     if (ArrayExists(streamId, "VARIABLE_NAMES/PARCEL_VARIABLES"))
532     {
533       if (!ReadStrings(streamId, "VARIABLE_NAMES/PARCEL_VARIABLES", parcelDataScalarVariables))
534       {
535         vtkErrorMacro("Could not read parcel variable names");
536         return 0;
537       }
538 
539       // Copy to set of names to ensure uniqueness
540       for (auto& parcelDataArrayName : parcelDataScalarVariables)
541       {
542         parcelVariables.insert(parcelDataArrayName);
543       }
544     }
545     else
546     {
547       // 3.1 and later format
548       ScopedH5GHandle varNamesHandle = H5Gopen(streamId, "VARIABLE_NAMES");
549       if (varNamesHandle < 0)
550       {
551         vtkErrorMacro("Cannot open /" << streamId << "/VARIABLE_NAMES");
552         return 0;
553       }
554 
555       // Iterate over parcel variable names
556       hsize_t numVariableTypes = 0;
557       herr_t err = H5Gget_num_objs(varNamesHandle, &numVariableTypes);
558       if (err < 0)
559       {
560         vtkErrorMacro("Cannot get number of groups from file");
561         return 0;
562       }
563 
564       for (hsize_t i = 0; i < numVariableTypes; ++i)
565       {
566         status = 0;
567         char groupName[256];
568         status = H5Lget_name_by_idx(streamId, "VARIABLE_NAMES/", H5_INDEX_NAME, H5_ITER_NATIVE, i,
569           groupName, 256, H5P_DEFAULT);
570         if (status < 0)
571         {
572           vtkErrorMacro(<< "error reading parcel variable names");
573           break;
574         }
575 
576         std::string groupNameString(groupName);
577         if (groupNameString == "CELL_VARIABLES")
578         {
579           continue;
580         }
581 
582         auto const underscorePos = groupNameString.find_last_of('_');
583         std::string parcelTypePrefix = groupNameString.substr(0, underscorePos);
584 
585         std::string parcelVariablesGroupName = parcelTypePrefix + "_VARIABLES";
586         std::string parcelVariableTypeName = parcelTypePrefix + "_DATA";
587 
588         // Read parcel array names
589         std::string parcelDataGroup("VARIABLE_NAMES/");
590         parcelDataGroup += parcelVariablesGroupName;
591         if (ArrayExists(streamId, parcelDataGroup.c_str()))
592         {
593           std::vector<std::string> parcelScalarVariables;
594           if (!ReadStrings(streamId, parcelDataGroup.c_str(), parcelScalarVariables))
595           {
596             vtkErrorMacro("Could not read parcel variable names");
597             return 0;
598           }
599 
600           // Insert variable name into set to ensure uniqueness
601           for (auto& var : parcelScalarVariables)
602           {
603             parcelVariables.insert(var);
604           }
605         }
606       }
607     }
608 
609     streamCount++;
610   } while (true); // end iterating over streams
611 
612   constexpr bool defaultEnabledState = true;
613 
614   // Set up cell data array selection
615   this->Internal->CellDataScalarVariables.clear();
616   this->Internal->CellDataVectorVariables.clear();
617 
618   for (auto& cellArrayName : cellVariables)
619   {
620     this->Internal->CellDataScalarVariables.emplace_back(cellArrayName);
621   }
622 
623   // Split cell variables into scalar and vector arrays
624   SplitScalarAndVectorVariables(
625     this->Internal->CellDataScalarVariables, this->Internal->CellDataVectorVariables);
626 
627   for (const auto& varName : this->Internal->CellDataScalarVariables)
628   {
629     if (!this->CellDataArraySelection->ArrayExists(varName.c_str()))
630     {
631       this->CellDataArraySelection->AddArray(varName.c_str(), defaultEnabledState);
632     }
633   }
634 
635   for (const auto& varName : this->Internal->CellDataVectorVariables)
636   {
637     if (!this->CellDataArraySelection->ArrayExists(varName.c_str()))
638     {
639       this->CellDataArraySelection->AddArray(varName.c_str(), defaultEnabledState);
640     }
641   }
642 
643   // Set up parcel data array selection
644   this->Internal->ParcelDataScalarVariables.clear();
645   this->Internal->ParcelDataVectorVariables.clear();
646 
647   for (auto& parcelArrayName : parcelVariables)
648   {
649     this->Internal->ParcelDataScalarVariables.emplace_back(parcelArrayName);
650   }
651 
652   // Split parcel arrays into scalar and vector variables
653   SplitScalarAndVectorVariables(
654     this->Internal->ParcelDataScalarVariables, this->Internal->ParcelDataVectorVariables);
655 
656   // Set up data array status
657   for (const auto& varName : this->Internal->ParcelDataScalarVariables)
658   {
659     if (!this->ParcelDataArraySelection->ArrayExists(varName.c_str()))
660     {
661       this->ParcelDataArraySelection->AddArray(varName.c_str(), defaultEnabledState);
662     }
663   }
664 
665   for (const auto& varName : this->Internal->ParcelDataVectorVariables)
666   {
667     // Skip X, Y, Z points
668     if (varName == "PARCEL")
669     {
670       continue;
671     }
672 
673     if (!this->ParcelDataArraySelection->ArrayExists(varName.c_str()))
674     {
675       this->ParcelDataArraySelection->AddArray(varName.c_str(), defaultEnabledState);
676     }
677   }
678 
679   // Get time information
680   vtkInformation* outInfo = outInfos->GetInformationObject(0);
681   this->ReadTimeSteps(outInfo);
682 
683   return 1;
684 }
685 
686 //----------------------------------------------------------------------------
RequestData(vtkInformation *,vtkInformationVector **,vtkInformationVector * outputVector)687 int vtkCONVERGECFDReader::RequestData(
688   vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
689 {
690   vtkInformation* outInfo = outputVector->GetInformationObject(0);
691 
692   size_t fileIndex = this->SelectTimeStepIndex(outInfo);
693   std::string fileName = this->FileNames[fileIndex];
694 
695   if (fileName.empty())
696   {
697     vtkErrorMacro("No file sequence found");
698     return 0;
699   }
700 
701   vtkPartitionedDataSetCollection* outputPDC = vtkPartitionedDataSetCollection::GetData(outInfo);
702   if (!outputPDC)
703   {
704     vtkErrorMacro("No output available!");
705     return 0;
706   }
707 
708   vtkNew<vtkDataAssembly> hierarchy;
709   hierarchy->Initialize();
710   outputPDC->SetDataAssembly(hierarchy);
711 
712   ScopedH5FHandle fileId = H5Fopen(fileName.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
713   if (fileId < 0)
714   {
715     vtkErrorMacro("Could not open HDF5 file '" << fileName << "'");
716     return 0;
717   }
718 
719   ScopedH5GHandle boundaryHandle = H5Gopen(fileId, "/BOUNDARIES");
720   if (boundaryHandle < 0)
721   {
722     vtkErrorMacro("Cannot open group/BOUNDARIES");
723     return 0;
724   }
725 
726   // Iterate over stream groups
727   hsize_t numObjs = 0;
728   herr_t err = H5Gget_num_objs(fileId, &numObjs);
729   if (err < 0)
730   {
731     vtkErrorMacro("Cannot get number of groups from file");
732     return 0;
733   }
734 
735   int streamCount = 0;
736   do
737   {
738     herr_t status = 0;
739     std::ostringstream streamName;
740     streamName << "/STREAM_" << std::setw(2) << std::setfill('0') << streamCount;
741     H5Eset_auto(nullptr, nullptr);
742     status = H5Gget_objinfo(fileId, streamName.str().c_str(), false, nullptr);
743     if (status < 0)
744     {
745       break;
746     }
747 
748     // Open the group
749     ScopedH5GHandle streamId = H5Gopen(fileId, streamName.str().c_str());
750     if (streamId < 0)
751     {
752       vtkErrorMacro("Could not open stream " << streamName.str());
753       break;
754     }
755 
756     if (!ArrayExists(streamId, "VERTEX_COORDINATES/X"))
757     {
758       vtkErrorMacro("Could not find array VERTEX_COORDINATES/X");
759       break;
760     }
761 
762     std::stringstream streamss;
763     streamss << "STREAM_" << std::setw(2) << std::setfill('0') << streamCount;
764     int streamNodeId = hierarchy->AddNode(streamss.str().c_str(), 0 /* root */);
765 
766     hsize_t xCoordsLength = GetDataLength(streamId, "VERTEX_COORDINATES/X");
767 
768     // Temporary buffer for reading vector array components
769     vtkNew<vtkBuffer<float>> floatBuffer;
770 
771     vtkNew<vtkFloatArray> pointArray;
772     pointArray->SetNumberOfComponents(3);
773     pointArray->SetNumberOfTuples(xCoordsLength);
774 
775     floatBuffer->Allocate(xCoordsLength);
776 
777     std::array<char, 3> dimensionNames = { 'X', 'Y', 'Z' };
778 
779     for (size_t c = 0; c < dimensionNames.size(); ++c)
780     {
781       std::stringstream name;
782       name << "VERTEX_COORDINATES/" << dimensionNames[c];
783       if (!ReadArray(streamId, name.str().c_str(), floatBuffer->GetBuffer(), xCoordsLength))
784       {
785         vtkErrorMacro(
786           "No coordinate array " << name.str() << " dataset available in " << streamName.str());
787         return 0;
788       }
789 
790       for (hsize_t j = 0; j < xCoordsLength; ++j)
791       {
792         pointArray->SetTypedComponent(j, static_cast<int>(c), floatBuffer->GetBuffer()[j]);
793       }
794     }
795 
796     // ++++ POLYGON_OFFSET ++++
797     hsize_t polygonOffsetsLength = GetDataLength(streamId, "CONNECTIVITY/POLYGON_OFFSET");
798     std::vector<int> polygonOffsets(polygonOffsetsLength);
799     if (!ReadArray(
800           streamId, "CONNECTIVITY/POLYGON_OFFSET", polygonOffsets.data(), polygonOffsetsLength))
801     {
802       vtkErrorMacro("Could not read CONNECTIVITY/POLYGON_OFFSET");
803       return 0;
804     }
805 
806     // Reduce the number of polygons by one to make up for the fact that the POLYGON_OFFSETS
807     // array is longer by one row.
808     vtkIdType numPolygons = static_cast<vtkIdType>(polygonOffsets.size()) - 1;
809 
810     // ++++ POLYGON_TO_VERTEX ++++
811     hsize_t polygonsLength = GetDataLength(streamId, "CONNECTIVITY/POLYGON_TO_VERTEX");
812     std::vector<int> polygons(polygonsLength);
813     if (!ReadArray(streamId, "CONNECTIVITY/POLYGON_TO_VERTEX", polygons.data(), polygonsLength))
814     {
815       vtkErrorMacro("Could not read CONNECTIVITY/POLYGON_TO_VERTEX");
816       return 0;
817     }
818 
819     // ++++ CONNECTED_CELLS ++++
820     hsize_t connectedCellsLength = GetDataLength(streamId, "CONNECTIVITY/CONNECTED_CELLS");
821     std::vector<int> connectedCells(connectedCellsLength);
822     if (!ReadArray(
823           streamId, "CONNECTIVITY/CONNECTED_CELLS", connectedCells.data(), connectedCellsLength))
824     {
825       vtkErrorMacro("Could not read CONNECTIVITY/CONNECTED_CELLS");
826       return 0;
827     }
828 
829     // ++++ CREATE VTK DATA SETS ++++
830     vtkNew<vtkPoints> points;
831     points->SetData(pointArray);
832 
833     // boundaryIdToIndex must be size of max id... ids are not guaranteed to be sequential,
834     // i.e., 1, 3, 5, 30, 31, 32, 1001 so it's better to use map instead of array lookup
835     std::map<int, int> boundaryIdToIndex;
836 
837     hsize_t numBoundaryNames = GetDataLength(boundaryHandle, "BOUNDARY_NAMES");
838     std::vector<std::string> boundaryNames(numBoundaryNames);
839     ReadStrings(boundaryHandle, "BOUNDARY_NAMES", boundaryNames);
840     hsize_t numBoundaries = GetDataLength(boundaryHandle, "NUM_ELEMENTS");
841     std::vector<int> boundaryNumElements(numBoundaries);
842     ReadArray(
843       boundaryHandle, "NUM_ELEMENTS", boundaryNumElements.data(), boundaryNumElements.size());
844     std::vector<int> boundaryIds(numBoundaries);
845     ReadArray(boundaryHandle, "BOUNDARY_IDS", boundaryIds.data(), boundaryIds.size());
846     if (numBoundaries != numBoundaryNames)
847     {
848       vtkErrorMacro("Number of BOUNDARY_NAMES does not match NUM_ELEMENTS");
849       return 0;
850     }
851 
852     // Make mesh the first node in the stream and put it first in the collection
853     int meshNodeId = hierarchy->AddNode("Mesh", streamNodeId);
854     int meshStartId = outputPDC->GetNumberOfPartitionedDataSets();
855     outputPDC->SetNumberOfPartitionedDataSets(meshStartId + 1);
856 
857     vtkNew<vtkUnstructuredGrid> ugrid;
858     outputPDC->SetPartition(meshStartId, 0, ugrid);
859     outputPDC->GetMetaData(meshStartId)->Set(vtkCompositeDataSet::NAME(), "Mesh");
860     hierarchy->AddDataSetIndex(meshNodeId, meshStartId);
861 
862     // Multiple surfaces can exist in a single file. We create a vtkPolyData for
863     // each one and store them under another group in the partitioned dataset collection.
864     unsigned int streamSurfaceStartId = outputPDC->GetNumberOfPartitionedDataSets();
865     outputPDC->SetNumberOfPartitionedDataSets(
866       streamSurfaceStartId + static_cast<unsigned int>(boundaryIds.size()));
867 
868     int surfaceNodeId = hierarchy->AddNode("Surfaces", streamNodeId);
869     for (int i = 0; i < static_cast<int>(boundaryIds.size()); ++i)
870     {
871       // If boundary index 0 has boundary id == 1, index 1 of boundaryIdToIndex will be 0.
872       boundaryIdToIndex[boundaryIds[i]] = static_cast<int>(i);
873 
874       vtkNew<vtkPolyData> boundarySurface;
875       outputPDC->SetPartition(streamSurfaceStartId + i, 0, boundarySurface);
876       outputPDC->GetMetaData(streamSurfaceStartId + static_cast<unsigned int>(i))
877         ->Set(vtkCompositeDataSet::NAME(), boundaryNames[i]);
878 
879       vtkNew<vtkCellArray> polys;
880       polys->AllocateEstimate(boundaryNumElements[i], 4);
881       boundarySurface->SetPolys(polys);
882       std::string validName = vtkDataAssembly::MakeValidNodeName(boundaryNames[i].c_str());
883       unsigned int boundaryNodeId = hierarchy->AddNode(validName.c_str(), surfaceNodeId);
884       hierarchy->AddDataSetIndex(boundaryNodeId, streamSurfaceStartId + i);
885     }
886 
887     // Create maps from surface point IDs for each block
888     std::vector<std::set<vtkIdType>> blocksSurfacePointIds(boundaryIds.size());
889     for (int polyId = 0; polyId < numPolygons; ++polyId)
890     {
891       if (connectedCells[2 * polyId + 0] >= 0 && connectedCells[2 * polyId + 1] >= 0)
892       {
893         // Polygon is not part of a surface, so skip.
894         continue;
895       }
896 
897       int boundaryId = -(connectedCells[2 * polyId + 0] + 1);
898       int boundaryIndex = boundaryIdToIndex[boundaryId];
899       vtkIdType numCellPts =
900         static_cast<vtkIdType>(polygonOffsets[polyId + 1] - polygonOffsets[polyId]);
901       for (vtkIdType id = 0; id < numCellPts; ++id)
902       {
903         vtkIdType ptId = polygons[polygonOffsets[polyId] + id];
904         blocksSurfacePointIds[boundaryIndex].insert(ptId);
905       }
906     }
907 
908     // Create maps from original point IDs to surface point IDs for each block
909     std::vector<std::map<vtkIdType, vtkIdType>> blocksOriginalToBlockPointId(numBoundaryNames);
910     for (hsize_t boundaryIndex = 0; boundaryIndex < numBoundaryNames; ++boundaryIndex)
911     {
912       // Create a map from original point ID in the global points list
913       vtkIdType newIndex = 0;
914       for (vtkIdType id : blocksSurfacePointIds[boundaryIndex])
915       {
916         blocksOriginalToBlockPointId[boundaryIndex][id] = newIndex++;
917       }
918 
919       // Clear some memory
920       blocksSurfacePointIds[boundaryIndex].clear();
921 
922       // Create localized points for this block
923       vtkNew<vtkPoints> blockPoints;
924       blockPoints->SetDataType(points->GetDataType());
925       blockPoints->SetNumberOfPoints(newIndex);
926       vtkFloatArray* toArray = vtkFloatArray::SafeDownCast(blockPoints->GetData());
927       for (auto it = blocksOriginalToBlockPointId[boundaryIndex].begin();
928            it != blocksOriginalToBlockPointId[boundaryIndex].end(); ++it)
929       {
930         vtkIdType from = it->first;
931         vtkIdType to = it->second;
932         float xyz[3];
933         pointArray->GetTypedTuple(from, xyz);
934         toArray->SetTypedTuple(to, xyz);
935       }
936 
937       vtkPolyData* boundarySurface =
938         vtkPolyData::SafeDownCast(outputPDC->GetPartition(streamSurfaceStartId + boundaryIndex, 0));
939       boundarySurface->SetPoints(blockPoints);
940     }
941 
942     // Go through polygons again and add them to the polydata blocks
943     vtkIdType numSurfacePolys = 0;
944     for (int polyId = 0; polyId < numPolygons; ++polyId)
945     {
946       if (connectedCells[2 * polyId + 0] >= 0 && connectedCells[2 * polyId + 1] >= 0)
947       {
948         // Polygon is not part of a surface, so skip.
949         continue;
950       }
951 
952       numSurfacePolys++;
953 
954       int boundaryId = -(connectedCells[2 * polyId + 0] + 1);
955       int boundaryIndex = boundaryIdToIndex[boundaryId];
956       vtkPolyData* polyData =
957         vtkPolyData::SafeDownCast(outputPDC->GetPartition(streamSurfaceStartId + boundaryIndex, 0));
958       vtkIdType numCellPts =
959         static_cast<vtkIdType>(polygonOffsets[polyId + 1] - polygonOffsets[polyId]);
960       std::vector<vtkIdType> ptIds(numCellPts);
961       for (vtkIdType id = 0; id < numCellPts; ++id)
962       {
963         vtkIdType ptId = polygons[polygonOffsets[polyId] + id];
964         ptIds[id] = blocksOriginalToBlockPointId[boundaryIndex][ptId];
965       }
966       polyData->GetPolys()->InsertNextCell(numCellPts, &ptIds[0]);
967     }
968 
969     // Clear some memory
970     blocksOriginalToBlockPointId.clear();
971 
972     // Create a map from cell to polygons
973     std::map<int, std::set<int>> cellToPoly;
974 
975     // Create a map from polygon to the volumetric cell to which it is attached
976     std::vector<int> polyToCell(numSurfacePolys);
977 
978     // Create a map from polygon to boundary
979     std::vector<int> polyToBoundary(numSurfacePolys);
980 
981     vtkIdType surfacePolyCount = 0;
982     for (int polyId = 0; polyId < numPolygons; ++polyId)
983     {
984       int cell0 = connectedCells[2 * polyId + 0];
985       int cell1 = connectedCells[2 * polyId + 1];
986       if (cell0 >= 0)
987       {
988         // Add polyId to cell 0's list of polygons
989         cellToPoly[cell0].insert(polyId);
990       }
991       if (cell1 >= 0)
992       {
993         // Add polyId to cell 1's list of polygons
994         cellToPoly[cell1].insert(polyId);
995       }
996 
997       if (cell0 < 0 || cell1 < 0)
998       {
999         assert(polyToBoundary.size() > static_cast<size_t>(surfacePolyCount));
1000         polyToBoundary[surfacePolyCount] = cell0 >= 0 ? -(cell1 + 1) : -(cell0 + 1);
1001         polyToCell[surfacePolyCount] = cell0 >= 0 ? cell0 : cell1;
1002         surfacePolyCount++;
1003       }
1004     }
1005 
1006     // Set the points in the unstructured grid
1007     ugrid->SetPoints(points);
1008 
1009     // Create polyhedra from their faces
1010     vtkNew<vtkIdList> faces;
1011     for (const auto& cellIdAndPolys : cellToPoly)
1012     {
1013       faces->Reset();
1014       // Number of faces
1015       faces->InsertNextId(static_cast<vtkIdType>(cellIdAndPolys.second.size()));
1016       for (auto polyId : cellIdAndPolys.second)
1017       {
1018         // Get polygon
1019         int numPts = polygonOffsets[polyId + 1] - polygonOffsets[polyId];
1020 
1021         // Number of points in face
1022         faces->InsertNextId(numPts);
1023         for (int i = 0; i < numPts; ++i)
1024         {
1025           // Polygon vertex
1026           faces->InsertNextId(polygons[polygonOffsets[polyId] + i]);
1027         }
1028       }
1029 
1030       ugrid->InsertNextCell(VTK_POLYHEDRON, faces);
1031     }
1032 
1033     // ++++ CELL DATA ++++
1034     for (int i = 0; i < this->CellDataArraySelection->GetNumberOfArrays(); ++i)
1035     {
1036       std::string varName(this->CellDataArraySelection->GetArrayName(i));
1037       if (this->GetCellDataArraySelection()->ArrayIsEnabled(varName.c_str()) == 0)
1038       {
1039         continue;
1040       }
1041 
1042       auto begin = this->Internal->CellDataVectorVariables.begin();
1043       auto end = this->Internal->CellDataVectorVariables.end();
1044       bool isVector = std::find(begin, end, varName) != end;
1045 
1046       // This would be a lot simpler using a vtkSOADataArrayTemplate<float>, but
1047       // until GetVoidPointer() is removed from more of the VTK code base, we
1048       // will use a vtkFloatArray.
1049       vtkNew<vtkFloatArray> dataArray;
1050       bool success = true;
1051       if (isVector)
1052       {
1053         std::string rootPath("CELL_CENTER_DATA/");
1054         rootPath += varName;
1055         std::string pathX = rootPath + "_X";
1056         std::string pathY = rootPath + "_Y";
1057         std::string pathZ = rootPath + "_Z";
1058 
1059         if (!ArrayExists(streamId, pathX.c_str()))
1060         {
1061           // This array just doesn't exist in this stream, skip it.
1062           continue;
1063         }
1064 
1065         hsize_t length = GetDataLength(streamId, pathX.c_str());
1066         dataArray->SetNumberOfComponents(3);
1067         dataArray->SetNumberOfTuples(length);
1068         dataArray->SetName(varName.c_str());
1069 
1070         if (floatBuffer->GetSize() != static_cast<vtkIdType>(length))
1071         {
1072           floatBuffer->Allocate(length);
1073         }
1074 
1075         success = success && ReadArray(streamId, pathX.c_str(), floatBuffer->GetBuffer(), length);
1076         for (hsize_t j = 0; j < length; ++j)
1077         {
1078           dataArray->SetTypedComponent(j, 0, floatBuffer->GetBuffer()[j]);
1079         }
1080         success = success && ReadArray(streamId, pathY.c_str(), floatBuffer->GetBuffer(), length);
1081         for (hsize_t j = 0; j < length; ++j)
1082         {
1083           dataArray->SetTypedComponent(j, 1, floatBuffer->GetBuffer()[j]);
1084         }
1085         success = success && ReadArray(streamId, pathZ.c_str(), floatBuffer->GetBuffer(), length);
1086         for (hsize_t j = 0; j < length; ++j)
1087         {
1088           dataArray->SetTypedComponent(j, 2, floatBuffer->GetBuffer()[j]);
1089         }
1090       }
1091       else
1092       {
1093         std::string path("CELL_CENTER_DATA/");
1094         path += varName;
1095 
1096         if (!ArrayExists(streamId, path.c_str()))
1097         {
1098           // This array just doesn't exist in this stream, skip it.
1099           continue;
1100         }
1101 
1102         hsize_t length = GetDataLength(streamId, path.c_str());
1103         dataArray->SetNumberOfComponents(1);
1104         dataArray->SetNumberOfTuples(length);
1105         dataArray->SetName(varName.c_str());
1106         success = success && ReadArray(streamId, path.c_str(), dataArray->GetPointer(0), length);
1107       }
1108 
1109       if (success)
1110       {
1111         ugrid->GetCellData()->AddArray(dataArray);
1112 
1113         // Now pull out the values needed for the surface geometry
1114         for (int boundaryIndex = 0; boundaryIndex < static_cast<int>(numBoundaryNames);
1115              ++boundaryIndex)
1116         {
1117           vtkPolyData* boundarySurface = vtkPolyData::SafeDownCast(
1118             outputPDC->GetPartition(streamSurfaceStartId + boundaryIndex, 0));
1119           int numBoundaryPolys = boundarySurface->GetNumberOfCells();
1120           vtkNew<vtkFloatArray> surfaceDataArray;
1121           surfaceDataArray->SetNumberOfComponents(dataArray->GetNumberOfComponents());
1122           surfaceDataArray->SetNumberOfTuples(numBoundaryPolys);
1123           surfaceDataArray->SetName(varName.c_str());
1124           vtkIdType localDataCount = 0;
1125           for (vtkIdType id = 0; id < numSurfacePolys; ++id)
1126           {
1127             assert(polyToBoundary.size() > static_cast<size_t>(id));
1128             if (boundaryIdToIndex.find(polyToBoundary[id]) == boundaryIdToIndex.end())
1129             {
1130               vtkErrorMacro(
1131                 "polyToBoundary[id] is not found within boundaryIdToIndex" << polyToBoundary[id]);
1132               return 0;
1133             }
1134             const int polyBoundaryIndex = boundaryIdToIndex[polyToBoundary[id]];
1135             if (polyBoundaryIndex != boundaryIndex)
1136             {
1137               continue;
1138             }
1139             for (int c = 0; c < surfaceDataArray->GetNumberOfComponents(); ++c)
1140             {
1141               assert(polyToCell.size() > static_cast<size_t>(id));
1142               surfaceDataArray->SetTypedComponent(
1143                 localDataCount, c, dataArray->GetTypedComponent(polyToCell[id], c));
1144             }
1145             ++localDataCount;
1146           }
1147           boundarySurface->GetCellData()->AddArray(surfaceDataArray);
1148         }
1149       }
1150     }
1151 
1152     // ++++ PARCEL DATA ++++
1153     bool parcelExists = GroupExists(streamId, "PARCEL_DATA");
1154     if (parcelExists)
1155     {
1156       std::string parcelDataRoot;
1157 
1158       // Branch between pre-3.1 and post-3.1 file formats
1159       H5O_info_t objectInfo;
1160       err = H5Oget_info_by_idx(
1161         streamId, "PARCEL_DATA", H5_INDEX_NAME, H5_ITER_NATIVE, 0, &objectInfo, 0);
1162       if (err < 0 || objectInfo.type == H5O_TYPE_GROUP)
1163       {
1164         // Handle 3.1 or above version
1165 
1166         // Get parcel data type names
1167         ScopedH5GHandle parcelDataTypesHandle = H5Gopen(streamId, "PARCEL_DATA");
1168         // We already checked that the group exists, so no need to check again.
1169 
1170         hsize_t numParcelDataTypes = 0;
1171         err = H5Gget_num_objs(parcelDataTypesHandle, &numParcelDataTypes);
1172         if (err < 0)
1173         {
1174           vtkErrorMacro("Cannot get number of parcel data types from file");
1175           return 0;
1176         }
1177 
1178         int parcelsNodeId = hierarchy->AddNode("Parcels", streamNodeId);
1179 
1180         // Iterate over the parcel data types/data sets
1181         unsigned int parcelDataTypeCount = 0;
1182         for (hsize_t parcelDataTypeIndex = 0; parcelDataTypeIndex < numParcelDataTypes;
1183              ++parcelDataTypeIndex)
1184         {
1185           status = 0;
1186           char groupName[256];
1187           status = H5Lget_name_by_idx(streamId, "PARCEL_DATA", H5_INDEX_NAME, H5_ITER_NATIVE,
1188             parcelDataTypeIndex, groupName, 256, H5P_DEFAULT);
1189           if (status < 0)
1190           {
1191             vtkErrorMacro(<< "error reading parcel variable names");
1192             break;
1193           }
1194 
1195           std::string dataType(groupName);
1196           std::string dataTypeGroupName("PARCEL_DATA/");
1197           dataTypeGroupName += dataType;
1198           ScopedH5GHandle dataTypeHandle = H5Gopen(streamId, dataTypeGroupName.c_str());
1199           if (dataTypeHandle < 0)
1200           {
1201             vtkErrorMacro("Cannot open group " << dataTypeGroupName);
1202             return 0;
1203           }
1204 
1205           // Handle the datasets in each datatype
1206           H5G_info_t groupInfo;
1207           err = H5Gget_info(dataTypeHandle, &groupInfo);
1208           if (err < 0)
1209           {
1210             vtkErrorMacro("Cannot get number of datasets from group " << dataTypeGroupName);
1211             return 0;
1212           }
1213           hsize_t numDataSets = groupInfo.nlinks;
1214 
1215           std::string dataTypeNodeName = vtkDataAssembly::MakeValidNodeName(dataType.c_str());
1216           int parcelDataTypeNodeId = hierarchy->AddNode(dataTypeNodeName.c_str(), parcelsNodeId);
1217 
1218           parcelDataTypeCount++;
1219 
1220           // Iterate over the datasets in the dataset type group
1221           for (hsize_t i = 0; i < numDataSets; ++i)
1222           {
1223             char dataSetGroupName[256];
1224             status = H5Lget_name_by_idx(dataTypeHandle, ".", H5_INDEX_NAME, H5_ITER_NATIVE, i,
1225               dataSetGroupName, 256, H5P_DEFAULT);
1226             if (status < 0)
1227             {
1228               continue;
1229             }
1230 
1231             vtkSmartPointer<vtkPolyData> parcels =
1232               this->Internal->ReadParcelDataSet(dataTypeHandle, dataSetGroupName);
1233             int parcelsId = outputPDC->GetNumberOfPartitionedDataSets();
1234             outputPDC->SetNumberOfPartitionedDataSets(parcelsId + 1);
1235             outputPDC->SetPartition(parcelsId, 0, parcels);
1236             outputPDC->GetMetaData(parcelsId)->Set(vtkCompositeDataSet::NAME(), dataSetGroupName);
1237             std::string validDataSetGroupName =
1238               vtkDataAssembly::MakeValidNodeName(dataSetGroupName);
1239             int parcelNodeId =
1240               hierarchy->AddNode(validDataSetGroupName.c_str(), parcelDataTypeNodeId);
1241             hierarchy->AddDataSetIndex(parcelNodeId, parcelsId);
1242           }
1243         }
1244       }
1245       else
1246       {
1247         vtkSmartPointer<vtkPolyData> parcels =
1248           this->Internal->ReadParcelDataSet(streamId, "PARCEL_DATA");
1249         int parcelsId = outputPDC->GetNumberOfPartitionedDataSets();
1250         outputPDC->SetNumberOfPartitionedDataSets(parcelsId + 1);
1251         outputPDC->SetPartition(parcelsId, 0, parcels);
1252         outputPDC->GetMetaData(parcelsId)->Set(vtkCompositeDataSet::NAME(), "Parcels");
1253         int parcelNodeId = hierarchy->AddNode("Parcels", streamNodeId);
1254         hierarchy->AddDataSetIndex(parcelNodeId, parcelsId);
1255       }
1256     }
1257     streamCount++;
1258   } while (true);
1259 
1260   // Everything succeeded
1261   return 1;
1262 }
1263 
1264 //----------------------------------------------------------------------------
ReadTimeSteps(vtkInformation * outInfo)1265 void vtkCONVERGECFDReader::ReadTimeSteps(vtkInformation* outInfo)
1266 {
1267   if (!this->FileName || this->FileName[0] == '\0')
1268   {
1269     return;
1270   }
1271 
1272   // Scan for other files with the same naming pattern in the same directory.
1273   // We are looking for files with the following convention
1274   //
1275   // <file><zero-padded index>_<time>.h5
1276   //
1277   // We load each file and extract the time from within.
1278   this->FileNames.clear();
1279   std::string originalFile = this->FileName;
1280   std::string path;
1281   std::string baseName;
1282   std::string::size_type dirseppos = originalFile.find_last_of("/\\");
1283   if (dirseppos == std::string::npos)
1284   {
1285     path = "./";
1286     baseName = originalFile;
1287   }
1288   else
1289   {
1290     path = originalFile.substr(0, dirseppos + 1);
1291     baseName = originalFile.substr(dirseppos + 1);
1292   }
1293 
1294   vtksys::RegularExpression regEx("^([^0-9]*)([0-9]*)[_]?(.*).h5$");
1295   if (!regEx.find(baseName))
1296   {
1297     this->FileNames.emplace_back(originalFile);
1298     return;
1299   }
1300 
1301   std::string prefix = regEx.match(1);
1302   std::string indexString = regEx.match(2);
1303 
1304   vtkNew<vtkDirectory> dir;
1305   if (!dir->Open(path.c_str()))
1306   {
1307     vtkWarningMacro(<< "Could not open directory " << originalFile.c_str()
1308                     << " is supposed to be from (" << path.c_str() << ")");
1309     this->FileNames.emplace_back(originalFile);
1310     return;
1311   }
1312 
1313   for (vtkIdType i = 0; i < dir->GetNumberOfFiles(); i++)
1314   {
1315     const char* file = dir->GetFile(i);
1316     if (!regEx.find(file))
1317     {
1318       continue;
1319     }
1320     if (regEx.match(1) != prefix)
1321     {
1322       continue;
1323     }
1324     this->FileNames.emplace_back(path + file);
1325   }
1326 
1327   std::vector<double> times;
1328   double timeRange[2] = { VTK_DOUBLE_MAX, VTK_DOUBLE_MIN };
1329   for (const auto& file : this->FileNames)
1330   {
1331     double time = 0.0;
1332     bool timeRead = this->ReadOutputTime(file, time);
1333     if (timeRead)
1334     {
1335       timeRange[0] = std::min(timeRange[0], time);
1336       timeRange[1] = std::max(timeRange[1], time);
1337       times.emplace_back(time);
1338     }
1339   }
1340 
1341   std::sort(times.begin(), times.end());
1342 
1343   if (!times.empty())
1344   {
1345     outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), timeRange, 2);
1346     outInfo->Set(
1347       vtkStreamingDemandDrivenPipeline::TIME_STEPS(), &times[0], static_cast<int>(times.size()));
1348   }
1349 }
1350 
1351 //----------------------------------------------------------------------------
ReadOutputTime(const std::string & filePath,double & time)1352 bool vtkCONVERGECFDReader::ReadOutputTime(const std::string& filePath, double& time)
1353 {
1354   if (filePath[0] == '\0')
1355   {
1356     return false;
1357   }
1358 
1359   ScopedH5FHandle fileId = H5Fopen(filePath.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
1360   if (fileId < 0)
1361   {
1362     return false;
1363   }
1364 
1365   if (H5Aexists(fileId, "OUTPUT_TIME"))
1366   {
1367     ScopedH5AHandle outputTimeId =
1368       H5Aopen_by_name(fileId, ".", "OUTPUT_TIME", H5P_DEFAULT, H5P_DEFAULT);
1369     ScopedH5THandle rawType = H5Aget_type(outputTimeId);
1370     ScopedH5THandle dataType = H5Tget_native_type(rawType, H5T_DIR_ASCEND);
1371 
1372     double outputTime = 0.0;
1373     if (H5Aread(outputTimeId, dataType, &outputTime) >= 0)
1374     {
1375       time = outputTime;
1376       return true;
1377     }
1378   }
1379 
1380   return false;
1381 }
1382 
1383 //----------------------------------------------------------------------------
SelectTimeStepIndex(vtkInformation * info)1384 size_t vtkCONVERGECFDReader::SelectTimeStepIndex(vtkInformation* info)
1385 {
1386   if (!info->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS()) ||
1387     !info->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()))
1388   {
1389     return 0;
1390   }
1391 
1392   double* times = info->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
1393   int nTimes = info->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS());
1394   double t = info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP());
1395 
1396   double resultDiff = VTK_DOUBLE_MAX;
1397   size_t result = 0;
1398   for (int i = 0; i < nTimes; ++i)
1399   {
1400     double diff = std::fabs(times[i] - t);
1401     if (diff < resultDiff)
1402     {
1403       resultDiff = diff;
1404       result = static_cast<size_t>(i);
1405     }
1406   }
1407 
1408   return result;
1409 }
1410 
1411 //----------------------------------------------------------------------------
CanReadFile(const char * fname)1412 int vtkCONVERGECFDReader::CanReadFile(const char* fname)
1413 {
1414   if (H5Fis_hdf5(fname) == 0)
1415   {
1416     return 0;
1417   }
1418 
1419   ScopedH5FHandle fileId = H5Fopen(fname, H5F_ACC_RDONLY, H5P_DEFAULT);
1420   if (fileId < 0)
1421   {
1422     return 0;
1423   }
1424 
1425   // Require a /BOUNDARIES group and at least one STREAM_00 group
1426   if (H5Lexists(fileId, "/BOUNDARIES", H5P_DEFAULT) == 0 ||
1427     H5Lexists(fileId, "/STREAM_00", H5P_DEFAULT) == 0)
1428   {
1429     return 0;
1430   }
1431 
1432   // Everything succeeded
1433   return 1;
1434 }
1435