From ee0947b6931e4e61251681c6383513b6cf5716af Mon Sep 17 00:00:00 2001 From: Mark Olesen <Mark.Olesen@esi-group.com> Date: Sat, 29 Sep 2018 13:14:50 +0200 Subject: [PATCH] ENH: vtk::fileWriter base class for geometry/field writers (issue #926) - Output formats such as vtp, vtu follow a particular internal data structure (HEAD, FIELD_DATA, PIECE, CELL_DATA/POINT_DATA) and other output conventions. This writer base tracks these expected output states internally to help avoid logic errors in the callers. --- src/fileFormats/Make/files | 1 + src/fileFormats/vtk/file/foamVtkFileWriter.C | 474 ++++++++++++++++++ src/fileFormats/vtk/file/foamVtkFileWriter.H | 289 +++++++++++ src/fileFormats/vtk/file/foamVtkFileWriterI.H | 108 ++++ 4 files changed, 872 insertions(+) create mode 100644 src/fileFormats/vtk/file/foamVtkFileWriter.C create mode 100644 src/fileFormats/vtk/file/foamVtkFileWriter.H create mode 100644 src/fileFormats/vtk/file/foamVtkFileWriterI.H diff --git a/src/fileFormats/Make/files b/src/fileFormats/Make/files index baa36aca051..678c9c418ee 100644 --- a/src/fileFormats/Make/files +++ b/src/fileFormats/Make/files @@ -16,6 +16,7 @@ stl/STLAsciiParseFlex.L stl/STLAsciiParseManual.C stl/STLAsciiParseRagel.C +vtk/file/foamVtkFileWriter.C vtk/core/foamVtkCore.C vtk/core/foamVtkPTraits.C diff --git a/src/fileFormats/vtk/file/foamVtkFileWriter.C b/src/fileFormats/vtk/file/foamVtkFileWriter.C new file mode 100644 index 00000000000..b2e5d398045 --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkFileWriter.C @@ -0,0 +1,474 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / 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 "foamVtkFileWriter.H" +#include "globalIndex.H" +#include "OSspecific.H" + +// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // + +const Foam::Enum +< + Foam::vtk::fileWriter::outputState +> +Foam::vtk::fileWriter::stateNames +{ + { outputState::CLOSED, "closed" }, + { outputState::OPENED, "opened" }, + { outputState::DECLARED, "declared" }, + { outputState::FIELD_DATA, "FieldData" }, + { outputState::PIECE, "Piece" }, + { outputState::CELL_DATA, "CellData" }, + { outputState::POINT_DATA, "PointData" }, +}; + + +// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // + +bool Foam::vtk::fileWriter::enter_Piece() +{ + // Finish other output + endFieldData(); + + if (isState(outputState::OPENED)) + { + beginFile(); + } + if (notState(outputState::DECLARED)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::DECLARED] << ')' + << exit(FatalError); + } + state_ = outputState::PIECE; + nCellData_ = nPointData_ = 0; + + return true; +} + + +bool Foam::vtk::fileWriter::endPiece() +{ + // Finish other output + endCellData(); + endPointData(); + + if (notState(outputState::PIECE)) + { + // Skip if not in Piece + return false; + } + state_ = outputState::DECLARED; // Mark as having been flushed + + if (format_) + { + format().endPiece(); + } + + return true; +} + + +bool Foam::vtk::fileWriter::enter_CellData(label nEntries, label nFields) +{ + // Already in CellData? + if (isState(outputState::CELL_DATA)) return false; + + // Finish other output + endPointData(); + + if (notState(outputState::PIECE)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::PIECE] << ')' + << exit(FatalError); + } + + nCellData_ = 0; + + // Do nothing for legacy when nFields == 0 + if (legacy() && !nFields) return false; + + state_ = outputState::CELL_DATA; + + if (format_) + { + if (legacy()) + { + legacy::beginCellData(format(), nEntries, nFields); + } + else + { + format().beginCellData(); + } + } + + return true; +} + + +bool Foam::vtk::fileWriter::enter_PointData(label nEntries, label nFields) +{ + // Already in PointData? + if (isState(outputState::POINT_DATA)) return false; + + // Finish other output + endCellData(); + + if (notState(outputState::PIECE)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::PIECE] << ')' + << exit(FatalError); + } + + nPointData_ = 0; + + // Do nothing for legacy when nFields == 0 + if (legacy() && !nFields) return false; + + state_ = outputState::POINT_DATA; + + if (format_) + { + if (legacy()) + { + legacy::beginPointData(format(), nEntries, nFields); + } + else + { + format().beginPointData(); + } + } + + return true; +} + + +bool Foam::vtk::fileWriter::exit_File() +{ + // Finish other output + endFieldData(); + endPiece(); + + if (isState(outputState::DECLARED)) + { + if (format_ && !legacy()) + { + format().endTag(contentType_).endVTKFile(); + } + state_ = outputState::OPENED; // Mark as having been flushed + } + + // Must now be in CLOSED or OPENED states only + + if (isState(outputState::CLOSED) || isState(outputState::OPENED)) + { + return true; + } + + WarningInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::CLOSED] + << ") or (" << stateNames[outputState::OPENED] + << ") for contentType (" << vtk::fileTagNames[contentType_] + << nl << endl; + + return false; +} + + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::vtk::fileWriter::fileWriter +( + const vtk::fileTag contentType, + const vtk::outputOptions opts +) +: + contentType_(contentType), + opts_(opts), + parallel_(false), + state_(outputState::CLOSED), + nCellData_(0), + nPointData_(0), + outputFile_(), + format_(), + os_() +{ + // We do not currently support append mode at all + opts_.append(false); +} + + +// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // + +Foam::vtk::fileWriter::~fileWriter() +{ + close(); +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +bool Foam::vtk::fileWriter::open(const fileName& file, bool parallel) +{ + if (notState(outputState::CLOSED)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::CLOSED] << ')' + << exit(FatalError); + } + + if (format_) + { + format_.clear(); + os_.close(); + } + nCellData_ = nPointData_ = 0; + outputFile_ = file; + + if + ( + legacy() + ? outputFile_.hasExt(vtk::fileExtension[contentType_]) + : outputFile_.hasExt(vtk::legacy::fileExtension) + ) + { + // Inappropriate extension. Legacy instead of xml, or vice versa. + + outputFile_.removeExt(); + } + + if (!outputFile_.hasExt(ext())) + { + // Add extension if required + outputFile_.ext(ext()); + } + + + // Only set parallel flag if really is a parallel run. + parallel_ = parallel && Pstream::parRun(); + + // Open a file and attach a formatter + // - on master (always) + // - on slave if not parallel + // + // This means we can always check if format_ is defined to know if output + // is desired on any particular process. + + if (Pstream::master() || !parallel_) + { + mkDir(outputFile_.path()); + + os_.open(outputFile_); + + format_ = opts_.newFormatter(os_); + } + + state_ = outputState::OPENED; + return true; +} + + +void Foam::vtk::fileWriter::close() +{ + exit_File(); + + if (format_) + { + format_.clear(); + os_.close(); + } + + state_ = outputState::CLOSED; + outputFile_.clear(); + nCellData_ = nPointData_ = 0; +} + + +bool Foam::vtk::fileWriter::beginFile(std::string title) +{ + if (isState(outputState::DECLARED)) + { + // Skip if already emitted + return false; + } + if (notState(outputState::OPENED)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::OPENED] << ')' + << exit(FatalError); + } + state_ = outputState::DECLARED; + + if (format_) + { + if (legacy()) + { + legacy::fileHeader(format(), title, contentType_); + } + else + { + // XML (inline) + + format().xmlHeader(); + + if (title.size()) + { + format().xmlComment(title); + } + + format().beginVTKFile(contentType_); + } + } + + return true; +} + + +bool Foam::vtk::fileWriter::beginFieldData(label nFields) +{ + // Do nothing for legacy when nFields == 0 + if (legacy() && !nFields) return false; + + if (isState(outputState::OPENED)) + { + beginFile(); + } + if (notState(outputState::DECLARED)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::DECLARED] << ')' + << exit(FatalError); + } + state_ = outputState::FIELD_DATA; + + if (format_) + { + if (legacy()) + { + legacy::beginFieldData(format(), nFields); + } + else + { + format().beginFieldData(); + } + } + + return true; +} + + +bool Foam::vtk::fileWriter::endFieldData() +{ + if (notState(outputState::FIELD_DATA)) + { + // Skip if not in FieldData + return false; + } + state_ = outputState::DECLARED; // Toggle back to DECLARED + + if (format_ && !legacy()) + { + format().endFieldData(); + } + + return true; +} + + +bool Foam::vtk::fileWriter::endCellData() +{ + if (notState(outputState::CELL_DATA)) + { + // Skip if not in CellData + return false; + } + state_ = outputState::PIECE; // Toggle back to PIECE + + if (format_ && !legacy()) + { + format().endCellData(); + } + + return true; +} + + +bool Foam::vtk::fileWriter::endPointData() +{ + if (notState(outputState::POINT_DATA)) + { + // Skip if not in PointData + return false; + } + state_ = outputState::PIECE; // Toggle back to PIECE + + if (format_ && !legacy()) + { + format().endPointData(); + } + + return true; +} + + +void Foam::vtk::fileWriter::writeTimeValue(scalar timeValue) +{ + // Convenience - switch to FieldData + if (isState(outputState::OPENED) || isState(outputState::DECLARED)) + { + beginFieldData(1); + } + if (notState(outputState::FIELD_DATA)) + { + FatalErrorInFunction + << "Bad writer state (" << stateNames[state_] + << ") - should be (" << stateNames[outputState::FIELD_DATA] << ')' + << exit(FatalError); + } + + // No collectives - can skip on slave processors + if (!format_) return; + + if (legacy()) + { + legacy::writeTimeValue(format(), timeValue); + } + else + { + format().writeTimeValue(timeValue); + } +} + + +// ************************************************************************* // diff --git a/src/fileFormats/vtk/file/foamVtkFileWriter.H b/src/fileFormats/vtk/file/foamVtkFileWriter.H new file mode 100644 index 00000000000..e11c5f9091d --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkFileWriter.H @@ -0,0 +1,289 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / 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::fileWriter + +Description + Base class for VTK output writers that handle geometry and fields + (eg, vtp, vtu data). + These output formats are structured as DECLARED, FIELD_DATA, PIECE + followed by any CELL_DATA or POINT_DATA. + + This writer base tracks these expected output states internally + to help avoid logic errors in the callers. + + The FieldData element must be placed prior to writing any geometry + Piece. This moves the information to the front of the output file + for visibility and simplifies the logic when creating + multi-piece geometries. + +SourceFiles + foamVtkFileWriter.C + +\*---------------------------------------------------------------------------*/ + +#ifndef foamVtkFileWriter_H +#define foamVtkFileWriter_H + +#include <fstream> +#include "Enum.H" +#include "UPstream.H" +#include "foamVtkOutputOptions.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ +namespace vtk +{ + +/*---------------------------------------------------------------------------*\ + Class vtk::fileWriter Declaration +\*---------------------------------------------------------------------------*/ + +class fileWriter +{ +protected: + + // Protected Member Data + + //- Internal tracking of the output state. + enum outputState + { + CLOSED = 0, //!< File is closed + OPENED, //!< File is opened + DECLARED, //!< File contents declared (VTKFile header written) + FIELD_DATA, //!< Inside FieldData + PIECE, //!< Inside Piece (after geometry write) + CELL_DATA, //!< Inside CellData + POINT_DATA //!< Inside PointData + }; + + //- Names for the output state (for messages, not for file output). + static const Enum<outputState> stateNames; + + + //- The content type + vtk::fileTag contentType_; + + //- The requested output options + outputOptions opts_; + + //- Writing in parallel (via master) + bool parallel_; + + //- The output state + outputState state_; + + //- The number of CellData written for the Piece thus far. + label nCellData_; + + //- The number of PointData written for the Piece thus far. + label nPointData_; + + //- The output file name + fileName outputFile_; + + //- The VTK formatter in use (master process) + autoPtr<vtk::formatter> format_; + + //- The backend ostream in use (master process) + std::ofstream os_; + + + // Protected Member Functions + + //- The backend ostream in use + inline std::ofstream& os(); + + //- The VTK formatter in use + inline vtk::formatter& format(); + + //- True if the output state corresponds to the test state. + inline bool isState(outputState test) const; + + //- True if the output state does not correspond to the test state. + inline bool notState(outputState test) const; + + + //- Trigger change state to Piece. Resets nCellData_, nPointData_. + bool enter_Piece(); + + //- Explicitly end Piece output and switch to DECLARED state + // Ignored (no-op) if not currently in the PIECE state. + bool endPiece(); + + //- Trigger change state to CellData. + // Legacy requires both parameters. XML doesn't require either. + // + // \return True if the state changed + bool enter_CellData(label nEntries, label nFields); + + //- Trigger change state to PointData + // Legacy requires both parameters. XML doesn't require either. + // + // \return True if the state changed + bool enter_PointData(label nEntries, label nFields); + + //- Emit file footer (end data, end piece, end file) + bool exit_File(); + + + //- No copy construct + fileWriter(const fileWriter&) = delete; + + //- No copy assignment + void operator=(const fileWriter&) = delete; + + +public: + + // Constructors + + //- Construct from components + fileWriter + ( + const vtk::fileTag contentType, + const vtk::outputOptions opts + ); + + + //- Destructor + virtual ~fileWriter(); + + + // Member Functions + + //- The content type + inline vtk::fileTag contentType() const; + + //- The output options in use + inline vtk::outputOptions opts() const; + + //- File extension for current format type. + inline word ext() const; + + //- Commonly used query + inline bool legacy() const; + + //- Parallel output requested? + inline bool parallel() const; + + //- The output state in printable format + inline const word& state() const; + + //- The current output file name + inline const fileName& output() const; + + + //- Open file for writing (creates parent directory). + // The file name is normally without an extension, this will be added + // according to the content-type and the output format (legacy/xml). + // If the file name has an extension, it will be used where if + // appropriate or changed to suit the format (legacy/xml) type. + // \note Expected calling states: (CLOSED). + bool open(const fileName& file, bool parallel=Pstream::parRun()); + + //- End the file contents and close the file after writing. + // \note Expected calling states: (PIECE | CELL_DATA | POINT_DATA). + void close(); + + + //- Write file header (non-collective) + // \note Expected calling states: (OPENED) + virtual bool beginFile(std::string title = ""); + + //- Begin FieldData output section for specified number of fields. + // \param nFields is for legacy format only. + // When nFields=0, this a no-op for legacy format. + // \note Expected calling states: (OPENED | DECLARED). + bool beginFieldData(label nFields = 0); + + //- Write mesh topology. + // Also writes the file header if not previously written. + // \note Must be called prior to writing CellData or PointData + virtual bool writeGeometry() = 0; + + + //- Begin CellData output section for specified number of fields. + // Must be called prior to writing any cell data fields. + // \param nFields is for legacy format only. + // When nFields=0, this a no-op for legacy format. + // \note Expected calling states: (PIECE | POINT_DATA). + // + // \return True if the state changed + virtual bool beginCellData(label nFields = 0) = 0; + + //- Begin PointData for specified number of fields. + // Must be called prior to writing any point data fields. + // \param nFields is for legacy format only. + // When nFields=0, this a no-op for legacy format. + // \note Expected calling states: (PIECE | CELL_DATA). + // + // \return True if the state changed + virtual bool beginPointData(label nFields = 0) = 0; + + //- Return the number of CellData written for the Piece thus far. + inline label nCellData() const; + + //- Return the number of PointData written for the Piece thus far. + inline label nPointData() const; + + + //- Explicitly end FieldData output and switch to DECLARED state + // Ignored (no-op) if not currently in the FIELD_DATA state. + bool endFieldData(); + + //- Explicitly end CellData output and switch to PIECE state + // Ignored (no-op) if not currently in the CELL_DATA state. + bool endCellData(); + + //- Explicitly end PointData output and switch to PIECE state + // Ignored (no-op) if not currently in the POINT_DATA state. + bool endPointData(); + + //- Write "TimeValue" FieldData (name as per Catalyst output) + // Must be called within the FIELD_DATA state. + // \note As a convenience this can also be called from + // (OPENED | DECLARED) states, in which case it invokes + // beginFieldData(1) internally. + void writeTimeValue(scalar timeValue); + +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace vtk +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#include "foamVtkFileWriterI.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // diff --git a/src/fileFormats/vtk/file/foamVtkFileWriterI.H b/src/fileFormats/vtk/file/foamVtkFileWriterI.H new file mode 100644 index 00000000000..f50541eced6 --- /dev/null +++ b/src/fileFormats/vtk/file/foamVtkFileWriterI.H @@ -0,0 +1,108 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / 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/>. + +\*---------------------------------------------------------------------------*/ + +// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // + +inline std::ofstream& Foam::vtk::fileWriter::os() +{ + return os_; +} + + +inline Foam::vtk::formatter& Foam::vtk::fileWriter::format() +{ + return *format_; +} + + +inline bool Foam::vtk::fileWriter::isState(outputState test) const +{ + return (test == state_); +} + + +inline bool Foam::vtk::fileWriter::notState(outputState test) const +{ + return (test != state_); +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +inline Foam::vtk::fileTag Foam::vtk::fileWriter::contentType() const +{ + return contentType_; +} + + +inline Foam::vtk::outputOptions Foam::vtk::fileWriter::opts() const +{ + return opts_; +} + + +inline Foam::word Foam::vtk::fileWriter::ext() const +{ + return opts_.ext(contentType_); +} + + +inline bool Foam::vtk::fileWriter::legacy() const +{ + return opts_.legacy(); +} + + +inline bool Foam::vtk::fileWriter::parallel() const +{ + return parallel_; +} + + +inline const Foam::word& Foam::vtk::fileWriter::state() const +{ + return stateNames[state_]; +} + + +inline const Foam::fileName& Foam::vtk::fileWriter::output() const +{ + return outputFile_; +} + + +inline Foam::label Foam::vtk::fileWriter::nCellData() const +{ + return nCellData_; +} + + +inline Foam::label Foam::vtk::fileWriter::nPointData() const +{ + return nPointData_; +} + + +// ************************************************************************* // -- GitLab