ensightCase.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2016-2021 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "ensightCase.H"
29 #include "ensightGeoFile.H"
30 #include "Time.H"
31 #include "cloud.H"
32 #include "IOmanip.H"
33 
34 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
35 
36 const char* Foam::ensightCase::dataDirName = "data";
37 const char* Foam::ensightCase::geometryName = "geometry";
38 
39 
40 // * * * * * * * * * * * * * Private Functions * * * * * * * * * * * * * * //
41 
42 Foam::fileName Foam::ensightCase::dataDir() const
43 {
44  return ensightDir_/dataDirName;
45 }
46 
47 
48 void Foam::ensightCase::initialize()
49 {
50  if (Pstream::master())
51  {
52  // EnSight and EnSight/data directories must exist
53 
54  // We may wish to retain old data
55  // eg, convert new results or a particular time interval
56  // OR remove everything
57 
58  if (isDir(ensightDir_))
59  {
60  if (options_->overwrite())
61  {
62  Foam::rmDir(ensightDir_);
63  }
64  else
65  {
67  << "Warning: re-using existing directory" << nl
68  << " " << ensightDir_ << endl;
69  }
70  }
71 
72  // Create ensight and data directories
73  mkDir(dataDir());
74 
75  // The case file is always ASCII
76  os_.reset(new OFstream(ensightDir_/caseName_, IOstream::ASCII));
77 
78  // Format options
79  os_->setf(ios_base::left);
80  os_->setf(ios_base::scientific, ios_base::floatfield);
81  os_->precision(5);
82 
83  writeHeader();
84  }
85 }
86 
87 
88 Foam::label Foam::ensightCase::checkTimeset(const labelHashSet& lookup) const
89 {
90  // assume the worst
91  label ts = -1;
92 
93  // work on a copy
94  labelHashSet tsTimes(lookup);
95  tsTimes.erase(-1);
96 
97  if (tsTimes.empty())
98  {
99  // no times needed
100  ts = 0;
101  }
102  else if (tsTimes.size() == timesUsed_.size())
103  {
104  forAllConstIters(timesUsed_, iter)
105  {
106  tsTimes.erase(iter.key());
107  }
108 
109  // OR
110  // tsTimes.unset(timesUsed_.toc());
111 
112  if (tsTimes.empty())
113  {
114  ts = 1; // can use timeset 1
115  }
116  }
117 
118  return ts;
119 }
120 
121 
122 void Foam::ensightCase::writeHeader() const
123 {
124  if (os_) // True on master only
125  {
126  this->rewind();
127  *os_
128  << "FORMAT" << nl
129  << "type: ensight gold" << nl;
130  }
131 }
132 
133 
134 Foam::scalar Foam::ensightCase::writeTimeset() const
135 {
136  const label ts = 1;
137 
138  const labelList indices(timesUsed_.sortedToc());
139  label count = indices.size();
140 
141  // correct for negative starting values
142  scalar timeCorrection = timesUsed_[indices[0]];
143  if (timeCorrection < 0)
144  {
145  timeCorrection = -timeCorrection;
146  Info<< "Correcting time values. Adding " << timeCorrection << endl;
147  }
148  else
149  {
150  timeCorrection = 0;
151  }
152 
153 
154  *os_
155  << "time set: " << ts << nl
156  << "number of steps: " << count << nl;
157 
158  if (indices[0] == 0 && indices[count-1] == count-1)
159  {
160  // looks to be contiguous numbering
161  *os_
162  << "filename start number: " << 0 << nl
163  << "filename increment: " << 1 << nl;
164  }
165  else
166  {
167  *os_
168  << "filename numbers:" << nl;
169 
170  count = 0;
171  for (const label idx : indices)
172  {
173  *os_ << ' ' << setw(12) << idx;
174 
175  if (++count % 6 == 0)
176  {
177  *os_ << nl;
178  }
179  }
180 
181  if (count)
182  {
183  *os_ << nl;
184  }
185  }
186 
187 
188  *os_ << "time values:" << nl;
189 
190  count = 0;
191  for (const label idx : indices)
192  {
193  *os_ << ' ' << setw(12) << timesUsed_[idx] + timeCorrection;
194 
195  if (++count % 6 == 0)
196  {
197  *os_ << nl;
198  }
199  }
200  if (count)
201  {
202  *os_ << nl;
203  }
204 
205  return timeCorrection;
206 }
207 
208 
209 void Foam::ensightCase::writeTimeset
210 (
211  const label ts,
212  const labelHashSet& lookup,
213  const scalar timeCorrection
214 ) const
215 {
216  // Make a copy
217  labelHashSet hashed(lookup);
218  hashed.erase(-1);
219 
220  const labelList indices(hashed.sortedToc());
221  label count = indices.size();
222 
223  *os_
224  << "time set: " << ts << nl
225  << "number of steps: " << count << nl
226  << "filename numbers:" << nl;
227 
228  count = 0;
229  for (const label idx : indices)
230  {
231  *os_ << ' ' << setw(12) << idx;
232 
233  if (++count % 6 == 0)
234  {
235  *os_ << nl;
236  }
237  }
238 
239  if (count)
240  {
241  *os_ << nl;
242  }
243 
244  *os_ << "time values:" << nl;
245 
246  count = 0;
247  for (const label idx : indices)
248  {
249  *os_ << ' ' << setw(12) << timesUsed_[idx] + timeCorrection;
250 
251  if (++count % 6 == 0)
252  {
253  *os_ << nl;
254  }
255  }
256  if (count)
257  {
258  *os_ << nl;
259  }
260 }
261 
262 
263 void Foam::ensightCase::noteGeometry(const bool moving) const
264 {
265  if (moving)
266  {
267  geomTimes_.insert(timeIndex_);
268  }
269  else
270  {
271  geomTimes_.insert(-1);
272  }
273 
274  changed_ = true;
275 }
276 
277 
278 void Foam::ensightCase::noteCloud(const word& cloudName) const
279 {
280  // Force into existence
281  if (!cloudVars_.found(cloudName))
282  {
283  cloudVars_.emplace(cloudName);
284  }
285  cloudTimes_.insert(timeIndex_);
286 
287  changed_ = true;
288 }
289 
290 
291 void Foam::ensightCase::noteCloud
292 (
293  const word& cloudName,
294  const word& varName,
295  const char* ensightType
296 ) const
297 {
298  if (cloudVars_.found(cloudName))
299  {
300  if (cloudVars_[cloudName].insert(varName, ensightType))
301  {
302  changed_ = true;
303  }
304  }
305  else
306  {
308  << "Tried to add a cloud variable for writing"
309  << " - without having added a cloud"
310  << abort(FatalError);
311  }
312 }
313 
314 
315 void Foam::ensightCase::noteVariable
316 (
317  const word& varName,
318  const char* ensightType
319 ) const
320 {
321  if (variables_.insert(varName, ensightType))
322  {
323  changed_ = true;
324  }
325 }
326 
327 
329 Foam::ensightCase::createDataFile
330 (
331  const word& name
332 ) const
333 {
334  if (Pstream::master())
335  {
336  // The data/ITER subdirectory must exist
337  // Note that data/ITER is indeed a valid ensight::FileName
338  const fileName outdir = dataDir()/padded(timeIndex_);
339  mkDir(outdir);
340 
341  return autoPtr<ensightFile>::New(outdir, name, format());
342  }
343 
344  return nullptr;
345 }
346 
347 
349 Foam::ensightCase::createCloudFile
350 (
351  const word& cloudName,
352  const word& name
353 ) const
354 {
355  if (Pstream::master())
356  {
357  // Write
358  // eg -> "data/********/lagrangian/<cloudName>/positions"
359  // or -> "lagrangian/<cloudName>/********/positions"
360  // TODO? check that cloudName is a valid ensight filename
361  const fileName outdir =
362  (
363  separateCloud()
364  ? (ensightDir_ / cloud::prefix / cloudName / padded(timeIndex_))
365  : (dataDir() / padded(timeIndex_) / cloud::prefix / cloudName)
366  );
367 
368  mkDir(outdir); // should be unnecessary after newCloud()
369 
370  return autoPtr<ensightFile>::New(outdir, name, format());
371  }
372 
373  return nullptr;
374 }
375 
376 
377 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
378 
379 Foam::ensightCase::ensightCase
380 (
381  const fileName& ensightDir,
382  const word& caseName,
383  const ensightCase::options& opts
384 )
385 :
386  options_(new options(opts)),
387  os_(nullptr),
388  ensightDir_(ensightDir),
389  caseName_(caseName + ".case"),
390  changed_(false),
391  timeIndex_(0),
392  timeValue_(0),
393  timesUsed_(),
394  geomTimes_(),
395  cloudTimes_(),
396  variables_(),
397  nodeVariables_(),
398  cloudVars_()
399 {
400  initialize();
401 }
402 
403 
404 Foam::ensightCase::ensightCase
405 (
406  const fileName& ensightDir,
407  const word& caseName,
409 )
410 :
411  options_(new options(format)),
412  os_(nullptr),
413  ensightDir_(ensightDir),
414  caseName_(caseName + ".case"),
415  changed_(false),
416  timeIndex_(0),
417  timeValue_(0),
418  timesUsed_(),
419  geomTimes_(),
420  cloudTimes_(),
421  variables_(),
422  nodeVariables_(),
423  cloudVars_()
424 {
425  initialize();
426 }
427 
428 
429 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
430 
431 void Foam::ensightCase::nextTime(const scalar value)
432 {
433  // use next available index
434  setTime(value, timesUsed_.size());
435 }
436 
437 
439 {
440  nextTime(t.value());
441 }
442 
443 
444 void Foam::ensightCase::setTime(const scalar value, const label index)
445 {
446  timeIndex_ = index;
447  timeValue_ = value;
448 
449  if (Pstream::master())
450  {
451  // The data/ITER subdirectory must exist
452  // Note that data/ITER is indeed a valid ensight::FileName
453 
454  const fileName outdir = dataDir()/padded(timeIndex_);
455  mkDir(outdir);
456 
457  // place a timestamp in the directory for future reference
458  OFstream timeStamp(outdir/"time");
459  timeStamp
460  << "# index time" << nl
461  << outdir.name() << ' ' << timeValue_ << nl;
462  }
463 
464  // Record of time index/value used
465  timesUsed_.set(index, value);
466 }
467 
468 
469 void Foam::ensightCase::setTime(const instant& t, const label index)
470 {
471  setTime(t.value(), index);
472 }
473 
474 
476 {
477  if (!os_) return; // master only
478 
479  // geometry timeset
480  const bool staticGeom = (geomTimes_.size() == 1 && geomTimes_.found(-1));
481  label tsGeom = staticGeom ? 0 : checkTimeset(geomTimes_);
482 
483  // geometry index, when mesh is not moving but stored under data/XXX/
484  label meshIndex = -1;
485 
486  // cloud timeset
487  label tsCloud = checkTimeset(cloudTimes_);
488 
489  // Increment time-sets to the correct indices
490  if (tsGeom < 0)
491  {
492  tsGeom = 2; // Next available timeset
493 
494  // Saved under data/XXX/geometry, but not actually moving
495  if (geomTimes_.size() == 1)
496  {
497  tsGeom = 0;
498  meshIndex = *(geomTimes_.begin());
499  }
500  }
501  if (tsCloud < 0)
502  {
503  tsCloud = 1 + std::max(label(1), tsGeom); // Next available timeset
504  }
505 
506  writeHeader();
507 
508 
509  // data mask: eg "data/******"
510  const fileName dataMask = (dataDirName/mask());
511 
512  //
513  // GEOMETRY
514  //
515  if (!geomTimes_.empty() || !cloudTimes_.empty())
516  {
517  // start of variables
518  *os_
519  << nl
520  << "GEOMETRY" << nl;
521  }
522 
523  if (staticGeom)
524  {
525  // Static mesh: store under data/constant/geometry
526  *os_
527  << setw(16) << "model:"
528  << (dataDirName/word("constant")/geometryName).c_str()
529  << nl;
530  }
531  else if (meshIndex >= 0)
532  {
533  // Not really moving, but stored under data/XXXX/geometry
534  *os_
535  << setw(16) << "model:"
536  << (dataDirName/padded(meshIndex)/geometryName).c_str()
537  << nl;
538  }
539  else if (!geomTimes_.empty())
540  {
541  // Moving
542  *os_
543  << word::printf("model: %-9d", tsGeom) // width 16 (no quotes)
544  << (dataMask/geometryName).c_str()
545  << nl;
546  }
547 
548  // Clouds and cloud variables
549  const wordList cloudNames(cloudVars_.sortedToc());
550 
551  for (const word& cloudName : cloudNames)
552  {
553  const fileName masked =
554  (
555  separateCloud()
556  ? (cloud::prefix / cloudName / mask())
557  : (dataMask / cloud::prefix / cloudName)
558  );
559 
560  *os_
561  << word::printf("measured: %-6d", tsCloud) // width 16 (no quotes)
562  << (masked/"positions").c_str()
563  << nl;
564  }
565 
566 
567  //
568  // VARIABLE
569  //
570  if (variables_.size() || cloudVars_.size())
571  {
572  // Start of variables
573  *os_
574  << nl
575  << "VARIABLE" << nl;
576  }
577 
578 
579  // Field variables (always use timeset 1)
580  const wordList varNames(variables_.sortedToc());
581 
582  for (const word& varName : varNames)
583  {
584  const string& ensType = variables_[varName];
585 
586  *os_
587  << ensType.c_str()
588  <<
589  (
590  (nodeVariables_.found(varName) || nodeValues())
591  ? " per node: 1 " // time-set 1
592  : " per element: 1 " // time-set 1
593  )
594  << setw(15) << varName << ' '
595  << (dataMask/varName).c_str() << nl;
596  }
597 
598 
599  // Clouds and cloud variables (using cloud timeset)
600  // Write
601  // as -> "data/********/lagrangian/<cloudName>/positions"
602  // or -> "lagrangian/<cloudName>/********/positions"
603 
604  label cloudNo = 0;
605  for (const word& cloudName : cloudNames)
606  {
607  const fileName masked =
608  (
609  separateCloud()
610  ? (cloud::prefix / cloudName / mask())
611  : (dataMask / cloud::prefix / cloudName)
612  );
613 
614  const HashTable<string>& vars = cloudVars_[cloudName];
615 
616  for (const word& varName : vars.sortedToc())
617  {
618  const string& ensType = vars[varName];
619 
620  // prefix variables with 'c' (cloud) and cloud index
621  *os_
622  << ensType.c_str() << " per "
623  << word::printf("measured node: %-5d", tsCloud) // width 20
624  << setw(15)
625  << ("c" + Foam::name(cloudNo) + varName).c_str() << ' '
626  << (masked/varName).c_str()
627  << nl;
628  }
629 
630  ++cloudNo;
631  }
632 
633 
634  //
635  // TIME
636  //
637 
638  if (!timesUsed_.empty())
639  {
640  *os_
641  << nl << "TIME" << nl;
642 
643  // timeset 1
644  const scalar timeCorrection = writeTimeset();
645 
646  // timeset geometry
647  if (tsGeom > 1)
648  {
649  writeTimeset(tsGeom, geomTimes_, timeCorrection);
650  }
651 
652  // timeset cloud
653  if (tsCloud > 1)
654  {
655  writeTimeset(tsCloud, cloudTimes_, timeCorrection);
656  }
657 
658  *os_
659  << "# end" << nl;
660  }
661 
662  *os_ << flush;
663  changed_ = false;
664 }
665 
666 
669 (
670  bool moving
671 ) const
672 {
674 
675  if (Pstream::master())
676  {
677  // Set the path of the ensight file
678  fileName path;
679 
680  if (moving)
681  {
682  // Moving mesh: write as "data/********/geometry"
683  path = dataDir()/padded(timeIndex_);
684  }
685  else
686  {
687  // Static mesh: write as "data/constant/geometry"
688  path = dataDir()/word("constant");
689  }
690  mkDir(path);
691 
692  noteGeometry(moving); // note for later use
693 
694  return autoPtr<ensightGeoFile>::New(path, geometryName, format());
695  }
696 
697  return nullptr;
698 }
699 
700 
703 (
704  const word& cloudName
705 ) const
706 {
708 
709  if (Pstream::master())
710  {
711  output = createCloudFile(cloudName, "positions");
712 
713  // Tag binary format (just like geometry files)
714  output().writeBinaryHeader();
715 
716  // Description
718  output().newline();
719 
720  noteCloud(cloudName); // note for later use
721  }
722 
723  return output;
724 }
725 
726 
728 {
729  if (os_) // master only
730  {
731  os_->stdStream().seekp(0, std::ios_base::beg);
732  }
733 }
734 
735 
737 {
738  os << "Ensight case:" << nl
739  << " path: " << ensightDir_ << nl
740  << " name: " << caseName_ << nl
741  << " format: " << format() << nl;
742 
743  if (nodeValues())
744  {
745  os << " values per node" << nl;
746  }
747 
748  return os;
749 }
750 
751 
752 // ************************************************************************* //
Foam::autoPtr::New
static autoPtr< T > New(Args &&... args)
Construct autoPtr of T with forwarding arguments.
Foam::labelList
List< label > labelList
A List of labels.
Definition: List.H:67
insert
srcOptions insert("case", fileName(rootDirSource/caseDirSource))
Foam::cloud::prefix
static const word prefix
The prefix to local: lagrangian.
Definition: cloud.H:87
Foam::ensightCase::nextTime
void nextTime(const scalar t)
Set time for time-set 1, using next available index.
Definition: ensightCase.C:431
Foam::output
static Ostream & output(Ostream &os, const IntRange< T > &range)
Definition: IntRanges.C:66
ensightCase.H
Foam::word
A class for handling words, derived from Foam::string.
Definition: word.H:65
cloudName
const word cloudName(propsDict.get< word >("cloud"))
Foam::fileName
A class for handling file names.
Definition: fileName.H:73
Foam::ensightCase::setTime
void setTime(const scalar t, const label index)
Set current index and time for time-set 1.
Definition: ensightCase.C:444
Foam::ensightCase::printInfo
Ostream & printInfo(Ostream &os) const
Print some general information.
Definition: ensightCase.C:736
Foam::ensightCase::write
void write() const
Write the case file.
Definition: ensightCase.C:475
Foam::fileName::name
static std::string name(const std::string &str)
Return basename (part beyond last /), including its extension.
Definition: fileNameI.H:199
cloud.H
Foam::ensightCase::rewind
void rewind() const
Rewind the output stream (master only).
Definition: ensightCase.C:727
Foam::UPstream::master
static bool master(const label communicator=worldComm)
Am I the master process.
Definition: UPstream.H:457
Foam::endl
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:369
Foam::writeHeader
static void writeHeader(Ostream &os, const word &fieldName)
Definition: rawSurfaceWriterImpl.C:66
setTime
runTimeSource setTime(sourceTimes[sourceTimeIndex], sourceTimeIndex)
Foam::flush
Ostream & flush(Ostream &os)
Flush stream.
Definition: Ostream.H:361
format
word format(conversionProperties.get< word >("format"))
Foam::Info
messageStream Info
Information stream (stdout output on master, null elsewhere)
Foam::ensightCase::dataDirName
static const char * dataDirName
The name for data subdirectory: "data".
Definition: ensightCase.H:72
IOmanip.H
Istream and Ostream manipulators taking arguments.
DetailInfo
#define DetailInfo
Definition: evalEntry.C:37
Foam::max
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
Foam::IOstreamOption::streamFormat
streamFormat
Data format (ascii | binary)
Definition: IOstreamOption.H:70
Foam::FatalError
error FatalError
os
OBJstream os(runTime.globalPath()/outputName)
Foam::Ostream::write
virtual bool write(const token &tok)=0
Write token to stream or otherwise handle it.
Foam::abort
errorManip< error > abort(error &err)
Definition: errorManip.H:144
Foam::setw
Omanip< int > setw(const int i)
Definition: IOmanip.H:199
Foam::HashTable::sortedToc
List< Key > sortedToc() const
The table of contents (the keys) in sorted order.
Definition: HashTable.C:136
Foam::scientific
IOstream & scientific(IOstream &io)
Definition: IOstream.H:464
Foam::OFstream
Output to file stream, using an OSstream.
Definition: OFstream.H:53
Foam::HashTable
A HashTable similar to std::unordered_map.
Definition: HashTable.H:105
Time.H
Foam::autoPtr
Pointer management similar to std::unique_ptr, with some additional methods and type checking.
Definition: HashPtrTable.H:53
ensightGeoFile.H
Foam::IOstreamOption::ASCII
"ascii" (normal default)
Definition: IOstreamOption.H:72
FatalErrorInFunction
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
Foam::nl
constexpr char nl
Definition: Ostream.H:404
forAllConstIters
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
Foam::rmDir
bool rmDir(const fileName &directory, const bool silent=false)
Remove a directory and its contents (optionally silencing warnings)
Definition: MSwindows.C:1028
Foam::BitOps::count
unsigned int count(const UList< bool > &bools, const bool val=true)
Count number of 'true' entries.
Definition: BitOps.H:77
Foam::List< word >
Foam::Instant::value
scalar value() const
The value (const access)
Definition: Instant.H:99
Foam::ensightCase::newGeometry
autoPtr< ensightGeoFile > newGeometry(bool moving=false) const
Open stream for new geometry file (on master).
Definition: ensightCase.C:669
path
fileName path(UMean.rootPath()/UMean.caseName()/"graphs"/UMean.instance())
Foam::ensightCase::options
Configuration options for the ensightCase.
Definition: ensightCase.H:328
Foam::name
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
Foam::word::printf
static word printf(const char *fmt, const PrimitiveType &val)
Use a printf-style formatter for a primitive.
Foam::ensightCase::newCloud
autoPtr< ensightFile > newCloud(const word &cloudName) const
Open stream for new cloud positions (on master).
Definition: ensightCase.C:703
Foam::instant
An instant of time. Contains the time value and name.
Definition: instant.H:52
Foam::Ostream
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:56
Foam::labelHashSet
HashSet< label, Hash< label > > labelHashSet
A HashSet of labels, uses label hasher.
Definition: HashSet.H:85
Foam::mkDir
bool mkDir(const fileName &pathName, mode_t mode=0777)
Make a directory and return an error if it could not be created.
Definition: MSwindows.C:507
Foam::ensightCase::geometryName
static const char * geometryName
The name for geometry files: "geometry".
Definition: ensightCase.H:80
Foam::isDir
bool isDir(const fileName &name, const bool followLink=true)
Does the name exist as a DIRECTORY in the file system?
Definition: MSwindows.C:643