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(), ×[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