From 0e47b0717d54cc24ab1bcdba4d8e5eb7fb10bd0c Mon Sep 17 00:00:00 2001 From: Mark Olesen <Mark.Olesen@esi-group.com> Date: Tue, 2 Oct 2018 14:06:34 +0200 Subject: [PATCH] ENH: vtk::seriesWriter to encapsulate writing file series (issue #926) --- applications/test/vtkSeriesWriter/Make/files | 3 + .../test/vtkSeriesWriter/Make/options | 5 + .../vtkSeriesWriter/Test-vtkSeriesWriter.C | 118 +++ applications/test/vtkSeriesWriter/file.vtm | 4 + .../test/vtkSeriesWriter/file_00000679.vtm | 4 + .../test/vtkSeriesWriter/file_00001025.vtm | 4 + .../test/vtkSeriesWriter/file_00001234.vtm | 4 + .../test/vtkSeriesWriter/file_00005680.vtm | 4 + applications/test/vtkSeriesWriter/file_23.vtm | 4 + .../test/vtkSeriesWriter/test1.vtm.series | 20 + src/fileFormats/Make/files | 1 + .../vtk/file/foamVtkSeriesWriter.C | 753 ++++++++++++++++++ .../vtk/file/foamVtkSeriesWriter.H | 276 +++++++ .../vtk/file/foamVtkSeriesWriterI.H | 99 +++ 14 files changed, 1299 insertions(+) create mode 100644 applications/test/vtkSeriesWriter/Make/files create mode 100644 applications/test/vtkSeriesWriter/Make/options create mode 100644 applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C create mode 100644 applications/test/vtkSeriesWriter/file.vtm create mode 100644 applications/test/vtkSeriesWriter/file_00000679.vtm create mode 100644 applications/test/vtkSeriesWriter/file_00001025.vtm create mode 100644 applications/test/vtkSeriesWriter/file_00001234.vtm create mode 100644 applications/test/vtkSeriesWriter/file_00005680.vtm create mode 100644 applications/test/vtkSeriesWriter/file_23.vtm create mode 100644 applications/test/vtkSeriesWriter/test1.vtm.series create mode 100644 src/fileFormats/vtk/file/foamVtkSeriesWriter.C create mode 100644 src/fileFormats/vtk/file/foamVtkSeriesWriter.H create mode 100644 src/fileFormats/vtk/file/foamVtkSeriesWriterI.H diff --git a/applications/test/vtkSeriesWriter/Make/files b/applications/test/vtkSeriesWriter/Make/files new file mode 100644 index 00000000000..367611d7ece --- /dev/null +++ b/applications/test/vtkSeriesWriter/Make/files @@ -0,0 +1,3 @@ +Test-vtkSeriesWriter.C + +EXE = $(FOAM_APPBIN)/Test-vtkSeriesWriter diff --git a/applications/test/vtkSeriesWriter/Make/options b/applications/test/vtkSeriesWriter/Make/options new file mode 100644 index 00000000000..7ce182425d9 --- /dev/null +++ b/applications/test/vtkSeriesWriter/Make/options @@ -0,0 +1,5 @@ +EXE_INC = \ + -I$(LIB_SRC)/fileFormats/lnInclude + +EXE_LIBS = \ + -lfileFormats diff --git a/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C b/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C new file mode 100644 index 00000000000..043e42e0349 --- /dev/null +++ b/applications/test/vtkSeriesWriter/Test-vtkSeriesWriter.C @@ -0,0 +1,118 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2018 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +Application + Test-vtkSeriesWriter + +Description + Basic functionality tests for vtk::seriesWriter + +\*---------------------------------------------------------------------------*/ + +#include "foamVtkSeriesWriter.H" +#include "argList.H" + +using namespace Foam; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +int main(int argc, char *argv[]) +{ + argList::addBoolOption("sort", "Sort value / name"); + argList::addBoolOption("check", "Check for existence of files"); + argList::addOption("time", "value", "Filter based on given time"); + argList::addOption("scan", "series", "Scan directory to create series"); + + argList args(argc, argv, false, true); + + const scalar currTime = args.lookupOrDefault<scalar>("time", GREAT); + + Info<< "Using currTime = " << currTime << nl + << "when loading " << (args.size()-1) << " files" << nl << nl; + + for (label argi=1; argi < args.size(); ++argi) + { + const auto& input = args[argi]; + + Info << "load from " << input << nl; + + vtk::seriesWriter writer; + writer.load(input); + + writer.print(Info); + Info<< nl << nl; + + if (writer.removeNewer(currTime)) + { + Info<< "removed entries with time >= " << currTime << nl; + writer.print(Info); + Info<< nl << nl; + } + + if (args.found("sort")) + { + writer.sort(); + + Info<< "sorted" << nl; + writer.print(Info); + Info<< nl << nl; + } + + if (args.found("check")) + { + writer.load(input, true); + + Info<< "reload, checking the existance of files" << nl; + writer.print(Info); + Info<< nl << nl; + } + + if (writer.empty()) + { + Info<< "No entries" << nl; + } + else + { + Info<< writer.size() << " entries" << nl; + } + } + + if (args.found("scan")) + { + vtk::seriesWriter writer; + + writer.scan(args.opt<fileName>("scan")); + + Info<< "scanned for files" << nl; + writer.print(Info); + Info<< nl << nl; + } + + + Info<< "\nEnd\n" << nl; + + return 0; +} + + +// ************************************************************************* // diff --git a/applications/test/vtkSeriesWriter/file.vtm b/applications/test/vtkSeriesWriter/file.vtm new file mode 100644 index 00000000000..28338dea7ac --- /dev/null +++ b/applications/test/vtkSeriesWriter/file.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- case='abc' time='0' index='0' --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/file_00000679.vtm b/applications/test/vtkSeriesWriter/file_00000679.vtm new file mode 100644 index 00000000000..4b00bcbe8f8 --- /dev/null +++ b/applications/test/vtkSeriesWriter/file_00000679.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- case='abc' time='0.00125' index='679' --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/file_00001025.vtm b/applications/test/vtkSeriesWriter/file_00001025.vtm new file mode 100644 index 00000000000..3d4cc1c6df7 --- /dev/null +++ b/applications/test/vtkSeriesWriter/file_00001025.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- case='abc' time="0.5250" index='1025' --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/file_00001234.vtm b/applications/test/vtkSeriesWriter/file_00001234.vtm new file mode 100644 index 00000000000..b49c580b1ac --- /dev/null +++ b/applications/test/vtkSeriesWriter/file_00001234.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- note="no time entry" --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/file_00005680.vtm b/applications/test/vtkSeriesWriter/file_00005680.vtm new file mode 100644 index 00000000000..feab3b1cff7 --- /dev/null +++ b/applications/test/vtkSeriesWriter/file_00005680.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- case='abc' time= index='1234' note="degenerate time entry" --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/file_23.vtm b/applications/test/vtkSeriesWriter/file_23.vtm new file mode 100644 index 00000000000..43ff669bd21 --- /dev/null +++ b/applications/test/vtkSeriesWriter/file_23.vtm @@ -0,0 +1,4 @@ +<?xml version='1.0'?> +<!-- case='abc' time=0.0145 index='1234' note="unquoted time entry" --> +<VTKFile type='vtkMultiBlockDataSet' version='1.0' byte_order='LittleEndian' header_type='UInt64'> + <vtkMultiBlockDataSet> diff --git a/applications/test/vtkSeriesWriter/test1.vtm.series b/applications/test/vtkSeriesWriter/test1.vtm.series new file mode 100644 index 00000000000..a5931a31ac9 --- /dev/null +++ b/applications/test/vtkSeriesWriter/test1.vtm.series @@ -0,0 +1,20 @@ +{ + "file-series-version" : "1.0", + "files": [ + { "name" : "file_00001742.vtm", "time" : 50 }, + { "name" : "file_00001025.vtm", "time" : 30 }, + { + "time" : 40, + "name" : "file_00001380.vtm", ,,,, + }, + { "name" : "file_00000679.vtm", "time" : 20 }, + { "name" : "file_00002110.vtm", "time" : 60 }, + { "name" : "file_00001234.vtm", "time" : 60 }, + { "name" : "file_00001742.vtm", "time" : 150 }, + { "name" : "file_00001742.vtm", "time" : 5 }, + + { "name" : "file_1000.vtm", "badTime" : 60 }, + { "" : "", "" : 60, "" : false }, + { "name" : "", "time" : 10 }, + ] +} diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files index 640d98882bf..2ed0cc373ab 100644 --- a/src/fileFormats/Make/files +++ b/src/fileFormats/Make/files @@ -17,6 +17,7 @@ stl/STLAsciiParseManual.C stl/STLAsciiParseRagel.C vtk/file/foamVtkFileWriter.C +vtk/file/foamVtkSeriesWriter.C vtk/file/foamVtmWriter.C vtk/core/foamVtkCore.C vtk/core/foamVtkPTraits.C diff --git a/src/fileFormats/vtk/file/foamVtkSeriesWriter.C b/src/fileFormats/vtk/file/foamVtkSeriesWriter.C new file mode 100644 index 00000000000..c7d243f578e --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkSeriesWriter.C @@ -0,0 +1,753 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2018 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +#include "foamVtkSeriesWriter.H" +#include "Fstream.H" +#include "ListOps.H" +#include "stringOpsSort.H" +#include "OSspecific.H" + +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace Foam +{ + // Get any single token. + static inline bool getToken(ISstream& is, token& tok) + { + return (!is.read(tok).bad() && tok.good()); + } + + // Get two tokens. + // The first one must be a ':' token, the second one is any value + // + // This corrsponds to the JSON "key" : value syntax, + // we trigger after reading the "key". + static inline bool getValueToken(ISstream& is, token& tok) + { + return + ( + // Token 1 = ':' separator + ( + getToken(is, tok) + && tok.isPunctuation() && tok.pToken() == token::COLON + ) + + // Token 2 is the value + && getToken(is, tok) + ); + } + + + // Sorting for fileNameInstant + // 1. sort by value (time) + // 2. natural sort (name) + struct seriesLess + { + bool operator()(const fileNameInstant a, const fileNameInstant b) const + { + scalar val = compareOp<scalar>()(a.value(), b.value()); + if (val == 0) + { + return + stringOps::natural_sort::compare(a.name(), b.name()) < 0; + } + return val < 0; + } + }; + + + // Check if value is less than upper, with some tolerance. + static inline bool lessThan(const scalar& val, const scalar& upper) + { + return (val < upper && Foam::mag(val - upper) > ROOTVSMALL); + } +} + + +// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * // + +Foam::fileName Foam::vtk::seriesWriter::base +( + const fileName& outputName, + char sep +) +{ + const auto dash = outputName.rfind(sep); + + // No separator? Or separator in path() instead of name()? + if + ( + std::string::npos == dash + || std::string::npos != outputName.find('/', dash) + ) + { + // Warn? + return outputName; + } + + const auto dot = outputName.find('.', dash); + + if (std::string::npos == dot) + { + return outputName.substr(0, dash); + } + + return outputName.substr(0, dash) + outputName.substr(dot); +} + + +Foam::word Foam::vtk::seriesWriter::suffix +( + const fileName& file, + char sep +) +{ + const auto dash = file.rfind(sep); + + // No separator? Or separator in path() instead of name()? + if + ( + std::string::npos == dash + || std::string::npos != file.find('/', dash) + ) + { + // Warn? + return ""; + } + + const auto dot = file.find('.', dash); + + if (std::string::npos == dot) + { + return file.substr(dash); + } + + return file.substr(dash, (dot-dash)); +} + + +Foam::Ostream& Foam::vtk::seriesWriter::print +( + Ostream& os, + const fileName& base, + const UList<instant>& series, + const char sep +) +{ + // Split the base into (stem, ext) components + // + // base = "path/file.vtm" + // + // stem = "file" + // ext = ".vtm" + + const word stem = base.nameLessExt(); + const word ext = "." + base.ext(); + + // Begin file-series (JSON) + os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n"; + + // Track how many entries are remaining + // - trailing commas on all but the final entry (JSON requirement) + label nremain = series.size(); + + // Each entry + // { "name" : "<stem><sep>name<ext>", "time" : value } + + for (const instant& inst : series) + { + os << " { \"name\" : \"" + << stem << sep << inst.name() << ext + << "\", \"time\" : " << inst.value() << " }"; + + if (--nremain) + { + os << ','; + } + os << nl; + } + + os << " ]\n}\n"; + + return os; +} + + +Foam::Ostream& Foam::vtk::seriesWriter::print +( + Ostream& os, + const UList<fileNameInstant>& series +) +{ + // Begin file-series (JSON) + os << "{\n \"file-series-version\" : \"1.0\",\n \"files\" : [\n"; + + // Track how many entries are remaining + // - trailing commas on all but the final entry (JSON requirement) + label nremain = series.size(); + + // Each entry + // { "name" : "<file>", "time" : <value> } + + for (const fileNameInstant& inst : series) + { + os << " { \"name\" : \"" + << inst.name().name() + << "\", \"time\" : " << inst.value() << " }"; + + if (--nremain) + { + os << ','; + } + os << nl; + } + + os << " ]\n}\n"; + + return os; +} + + +void Foam::vtk::seriesWriter::write +( + const fileName& seriesName, + const UList<instant>& series, + const char sep +) +{ + mkDir(seriesName.path()); + + autoPtr<OFstream> osPtr = + ( + seriesName.hasExt("series") + ? autoPtr<OFstream>::New(seriesName) + : autoPtr<OFstream>::New(seriesName + ".series") + ); + + print(*osPtr, seriesName, series, sep); +} + + + +void Foam::vtk::seriesWriter::write +( + const fileName& seriesName, + const UList<fileNameInstant>& series +) +{ + mkDir(seriesName.path()); + + autoPtr<OFstream> osPtr = + ( + seriesName.hasExt("series") + ? autoPtr<OFstream>::New(seriesName) + : autoPtr<OFstream>::New(seriesName + ".series") + ); + + print(*osPtr, series); +} + + +// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // + +bool Foam::vtk::seriesWriter::appendCheck(fileNameInstant inst) +{ + if (inst.name().empty()) + { + return false; + } + + const auto iter = existing_.find(inst.name()); + + if (iter.found()) + { + for (fileNameInstant& dst : entries_) + { + if (dst.name() == inst.name()) + { + // Replace value + dst.value() = inst.value(); + return true; + } + } + } + + entries_.append(inst); + existing_.insert(inst.name()); + + return true; +} + + +bool Foam::vtk::seriesWriter::removeDuplicates() +{ + const label nElem = entries_.size(); + + HashTable<label, fileName> filesSeen(2*nElem); + + bool changed = false; + + for (label elemi=0; elemi < nElem; ++elemi) + { + fileNameInstant& inst = entries_[elemi]; + + if (inst.name().empty()) + { + changed = true; + } + else + { + auto iter = filesSeen.find(inst.name()); + + if (iter.found()) + { + // Mark previous location as being superseded + entries_[*iter].name().clear(); + changed = true; + + *iter = elemi; // The latest with this name + } + else + { + filesSeen.insert(inst.name(), elemi); + } + } + } + + + if (changed) + { + label dsti = 0; + for (label elemi=0; elemi < nElem; ++elemi) + { + fileNameInstant& src = entries_[elemi]; + + if (!src.name().empty()) + { + if (dsti != elemi) + { + entries_[dsti] = std::move(src); + } + ++dsti; + } + } + + entries_.resize(dsti); + } + + return (nElem != entries_.size()); +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +Foam::label Foam::vtk::seriesWriter::load +( + const fileName& seriesName, + const bool checkFiles, + const scalar restartTime +) +{ + clear(); + + fileName seriesFile(seriesName); + if (!seriesFile.hasExt("series")) + { + seriesFile.ext("series"); + } + + if (!isFile(seriesFile)) + { + return size(); + } + + HashSet<fileName> filesOnDisk; + + if (checkFiles) + { + filesOnDisk.insert(Foam::readDir(seriesFile.path())); + } + + + // Parse JSON content: + // + // { + // "file-series-version" : "1.0", + // "files" : [ + // { "name" : "abc", "time" : 123 }, + // { "name" : "def", "time" : 345 } + // ] + // } + + // Parsing states + enum parse + { + NONE, // Looking for "files" + FILES_ARRAY, // Saw "file" : '[' + ENTRY, // Parsing in { "name" : ... } + DONE, // Saw a ']' while in FILES_ARRAY + FAIL // Something bad happened + }; + + // Track if "file" and "time" keys have been located + unsigned instStatus = 0; + fileNameInstant inst; + + token tok; + + IFstream is(seriesFile); + + for + ( + parse state = parse::NONE; + (state != parse::DONE && state != parse::FAIL) + && getToken(is, tok); + /*nil*/ + ) + { + switch (state) + { + // Still scanning for initial "files" entry + case parse::NONE : + { + if (tok.isString() && tok.stringToken() == "files") + { + // Expect "files" : [ ... + + if + ( + getValueToken(is, tok) + && tok.isPunctuation() + && tok.pToken() == token::BEGIN_SQR + ) + { + state = parse::FILES_ARRAY; + } + else + { + state = parse::FAIL; + } + } + } + break; + + // Parsing entries within "files" array + case parse::FILES_ARRAY : + { + if (tok.isPunctuation()) + { + switch (tok.pToken()) + { + // ',' - keep going (another entry) + case token::COMMA : + break; + + // '{' - begin entry + case token::BEGIN_BLOCK : + state = parse::ENTRY; + instStatus = 0; + break; + + // ']' - done array + case token::END_SQR : + state = parse::DONE; + break; + + default: + state = parse::FAIL; + break; + } + } + else + { + state = parse::FAIL; + } + } + break; + + // Parsing an individual entry within "files" + case parse::ENTRY : + { + if (tok.isPunctuation()) + { + switch (tok.pToken()) + { + // ',' - keep going (another key/value pair) + case token::COMMA : + break; + + // '}' + case token::END_BLOCK : + { + // Verify instant was properly parsed and + // is also valid + if + ( + instStatus == 0x03 + && lessThan(inst.value(), restartTime) + && + ( + checkFiles + ? filesOnDisk.found(inst.name()) + : true + ) + ) + { + appendCheck(inst); + } + + state = parse::FILES_ARRAY; + instStatus = 0; + } + break; + + default: + state = parse::FAIL; + break; + } + } + else if (tok.isString()) + { + // Expect "key" : value + + const string key(tok.stringToken()); + + if (getValueToken(is, tok)) + { + if ("name" == key) + { + if (tok.isString()) + { + inst.name() = tok.stringToken(); + instStatus |= 0x01; + } + else + { + state = parse::FAIL; + } + } + else if ("time" == key) + { + if (tok.isNumber()) + { + inst.value() = tok.number(); + instStatus |= 0x02; + } + else + { + state = parse::FAIL; + } + } + } + else + { + state = parse::FAIL; + } + } + else + { + state = parse::FAIL; + } + } + break; + + default: + break; + } + } + + return size(); +} + + +Foam::label Foam::vtk::seriesWriter::scan +( + const fileName& seriesName, + const scalar restartTime +) +{ + clear(); + + const fileName path = seriesName.path(); + + if (!isDir(path)) + { + return size(); + } + + fileName seriesFile(seriesName); + + if (seriesName.hasExt("series")) + { + seriesFile.removeExt(); + } + + const word stem = seriesFile.nameLessExt(); + const word ext = seriesFile.ext(); + + // Accept "fileN.ext", "fileNN.ext", but reject "file.ext" + const auto minLen = stem.length() + ext.length() + 1; + + const auto acceptName = + [=](const fileName& file) -> bool + { + return + ( + minLen < file.length() + && file.hasExt(ext) && file.startsWith(stem) + ); + }; + + + fileNameList files = subsetList(Foam::readDir(path), acceptName); + + // Names sorted so warnings appear less random + Foam::sort(files, stringOps::natural_sort()); + + // Scratch space for reading some of the file + std::string header; + + scalar timeValue; + + bool warnings = false; + + for (const fileName& file : files) + { + std::ifstream is(path/file); + + if (!is) + { + continue; + } + + // Read directly into the string + // 1024 (12 lines of 80 chars) is plenty for all comments + + header.resize(1024); + is.read(&(header.front()), header.size()); + header.resize(is.gcount()); + + // DebugInfo + // << "got header:\n=====\n" << header << "\n=====\n" << nl; + + + // Look for time="...", time='...', or even time=... attribute + + auto begAttr = header.find("time="); + + if (string::npos == begAttr) + { + if (!warnings) + { + Info<< "No 'time=' comment attribute found:\n(" << nl; + warnings = true; + } + Info<< " " << file << nl; + continue; + } + + // Skip past the 'time=' + begAttr += 5; + const char quote = header[begAttr]; + + // Info<< "have time=" << int(begAttr) << nl; + + auto endAttr = + ( + (quote == '"' || quote == '\'') + ? + // Quoted + header.find(quote, ++begAttr) + : + // Unquoted + header.find_first_of("\t\n\v\f\r ", begAttr) + ); + + + if + ( + string::npos != endAttr && begAttr < endAttr + && readScalar + ( + header.substr(begAttr, endAttr-begAttr), + timeValue + ) + && lessThan(timeValue, restartTime) + ) + { + // Success + append(timeValue, file); + } + } + + if (warnings) + { + Info<< ")" << nl << nl; + } + + // Don't trust the order. Sort by time and name instead. + this->sort(); + + return size(); +} + + +bool Foam::vtk::seriesWriter::removeNewer(const scalar timeValue) +{ + // Rebuild hash as side-effect + existing_.clear(); + + label dsti = 0; + + const label nElem = entries_.size(); + + for (label elemi=0; elemi < nElem; ++elemi) + { + fileNameInstant& src = entries_[elemi]; + + if (!src.name().empty() && lessThan(src.value(), timeValue)) + { + if (dsti != elemi) + { + entries_[dsti] = std::move(src); + existing_.insert(entries_[dsti].name()); + } + ++dsti; + } + } + + entries_.resize(dsti); + + return (nElem != entries_.size()); +} + + +void Foam::vtk::seriesWriter::sort() +{ + Foam::sort(entries_, seriesLess()); +} + + +// ************************************************************************* // diff --git a/src/fileFormats/vtk/file/foamVtkSeriesWriter.H b/src/fileFormats/vtk/file/foamVtkSeriesWriter.H new file mode 100644 index 00000000000..a62340f5f22 --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkSeriesWriter.H @@ -0,0 +1,276 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2018 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +Class + Foam::vtk::seriesWriter + +Description + Provides a means of accumulating and generating VTK file series. + + The VTK file series format is a simple JSON format with the following + type of content: + \verbatim + { + "file-series-version" : "1.0", + "files": [ + { "name" : "file1.vtk", "time" : 10 }, + { "name" : "file2.vtk", "time" : 20 }, + { "name" : "file3.vtk", "time" : 30 }, + ] + } + \endverbatim + + The append() operations include various sanity checks. + Entries with an empty name are ignored. + If an entry with an identical name already exists, its place + will be overwritten with the new time value. + +SourceFiles + foamVtkSeriesWriter.C + +\*---------------------------------------------------------------------------*/ + +#ifndef foamVtkSeriesWriter_H +#define foamVtkSeriesWriter_H + +#include <fstream> +#include "foamVtkOutputOptions.H" +#include "instant.H" +#include "fileNameInstant.H" +#include "DynamicList.H" +#include "HashSet.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ +namespace vtk +{ + +/*---------------------------------------------------------------------------*\ + Class vtk::seriesWriter Declaration +\*---------------------------------------------------------------------------*/ + +class seriesWriter +{ + // Private Member Data + + //- A list of file/time entries + DynamicList<fileNameInstant> entries_; + + //- Hash of existing (known) file names + HashSet<fileName> existing_; + + //- Append the specified file/time instant. + // Overwrites existing entry that has the same name, + // does not append empty names. + bool appendCheck(fileNameInstant inst); + + //- Remove duplicate filename entries. Keeping the last one seen. + bool removeDuplicates(); + + +public: + + // Constructors + + //- Construct an empty series + seriesWriter() = default; + + //- Copy construct + seriesWriter(const seriesWriter&) = default; + + //- Move construct + seriesWriter(seriesWriter&&) = default; + + //- Copy assignment + seriesWriter& operator=(const seriesWriter&) = default; + + //- Move assignment + seriesWriter& operator=(seriesWriter&&) = default; + + + //- Destructor + ~seriesWriter() = default; + + + // Static Member Functions + + //- Extract the base name for a file series + // + // \param outputName The name of the data output file + // Eg, "somefile_0001.vtk" would extract to "somefile.vtk" + // \param sep The separator used between file stem and suffix. + static fileName base(const fileName& outputName, char sep = '_'); + + //- Extract the time-varying ending of files + // + // \param file The name of the file + // Eg, "somefile_0001.vtk" would extract to "0001" + // \param sep The separator used between file stem and suffix. + static word suffix(const fileName& file, char sep = '_'); + + //- Print file series (JSON format) for specified time instances + // + // \param os The output stream + // \param base The name for the series (eg, "path/file.vtk") + // \param series The list of suffix/value entries + // \param sep The separator used between file stem and suffix. + static Ostream& print + ( + Ostream& os, + const fileName& seriesName, + const UList<instant>& series, + const char sep = '_' + ); + + //- Write file series (JSON format) to disk, for specified instances + // + // \param base The name for the series (eg, "path/file.vtk") + // \param series The list of suffix/value entries + // \param sep The separator used between file stem and suffix. + static void write + ( + const fileName& base, + const UList<instant>& series, + const char sep = '_' + ); + + //- Print file series (JSON format) for specified time instances. + // Since the VTK file series does not currently (OCT-2018) support + // sub-directories, these will be stripped on output. + // + // \param os The output stream + // \param series The list of filename/value entries + static Ostream& print + ( + Ostream& os, + const UList<fileNameInstant>& series + ); + + //- Write file series (JSON format) to disk, for specified instances + // + // \param seriesName The name for the series (eg, "path/file.vtk") + // \param series The list of filename/value entries + static void write + ( + const fileName& seriesName, + const UList<fileNameInstant>& series + ); + + + // Member Functions + + //- True if there are no data sets + inline bool empty() const; + + //- The number of data sets + inline label size() const; + + + // Content Management + + //- Clear entries + inline void clear(); + + //- Append the specified file instant + inline bool append(const fileNameInstant& inst); + + //- Append the specified file instant + inline bool append(fileNameInstant&& inst); + + //- Append the specified file instant. + inline bool append(scalar timeValue, const fileName& file); + + //- Append the specified file instant. + inline bool append(scalar timeValue, fileName&& file); + + //- Clear contents and reload by parsing the specified file. + // + // \param seriesName the base name of the series to scan, without + // the ".series" ending. + // \param checkFiles verify that the files also exist + // \param restartTime ignore entries with a time greater/equal + // to the specified restart time. + // + // \return the number of entries + label load + ( + const fileName& seriesName, + const bool checkFiles = false, + const scalar restartTime = ROOTVGREAT + ); + + //- Clear contents and scan directory for files. + // + // The expected xml header content is a comment with the following: + // \verbatim + // <!-- ... time='3.14159' ... --> + // \endverbatim + // + // \param seriesName the base name of the series to scan, without + // the ".series" ending. + // \param restartTime ignore entries with a time greater/equal + // to the specified restart time. + // + // \return the number of entries + label scan + ( + const fileName& seriesName, + const scalar restartTime = ROOTVGREAT + ); + + //- Remove entries that are greater_equal the time value. + // + // \return True if the contents changed + bool removeNewer(const scalar timeValue); + + //- Sort by time value and by file name + void sort(); + + + // Writing + + //- Print file series as (JSON format) + inline void print(Ostream& os) const; + + //- Write file series as (JSON format) to disk + inline void write(const fileName& seriesName) const; +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace vtk +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "foamVtkSeriesWriterI.H" + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/fileFormats/vtk/file/foamVtkSeriesWriterI.H b/src/fileFormats/vtk/file/foamVtkSeriesWriterI.H new file mode 100644 index 00000000000..263e860b65c --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkSeriesWriterI.H @@ -0,0 +1,99 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | Copyright (C) 2018 OpenCFD Ltd. + \\/ M anipulation | +------------------------------------------------------------------------------- +License + This file is part of OpenFOAM. + + OpenFOAM is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + OpenFOAM is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>. + +\*---------------------------------------------------------------------------*/ + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +inline bool Foam::vtk::seriesWriter::empty() const +{ + return entries_.empty(); +} + + +inline Foam::label Foam::vtk::seriesWriter::size() const +{ + return entries_.size(); +} + + +inline void Foam::vtk::seriesWriter::clear() +{ + entries_.clear(); + existing_.clear(); +} + + +inline bool Foam::vtk::seriesWriter::append(const fileNameInstant& inst) +{ + // Strip out path before saving + return appendCheck(fileNameInstant(inst.value(), inst.name().name())); +} + + +inline bool Foam::vtk::seriesWriter::append(fileNameInstant&& inst) +{ + // Strip out path before saving + inst.name().removePath(); + + return appendCheck(inst); +} + + +inline bool Foam::vtk::seriesWriter::append +( + scalar timeValue, + const fileName& file +) +{ + // Strip out path before saving + return appendCheck(fileNameInstant(timeValue, file.name())); +} + + +inline bool Foam::vtk::seriesWriter::append +( + scalar timeValue, + fileName&& file +) +{ + // Strip out path before saving + file.removePath(); + + return appendCheck(fileNameInstant(timeValue, std::move(file))); +} + + +inline void Foam::vtk::seriesWriter::print(Ostream& os) const +{ + seriesWriter::print(os, entries_); +} + + +inline void Foam::vtk::seriesWriter::write(const fileName& seriesName) const +{ + seriesWriter::write(seriesName, entries_); +} + + +// ************************************************************************* // -- GitLab