ENH: extended runTimePostProcessing (#1206)

- Extended runTimePostProcessing to include access to "live"
  simulation objects such a geometry patches and sampled surfaces
  stored on the "functionObjectObjects" registry.

- Add 'live' runTimePostProcessing of cloud data.
  Extracts position and fields from the cloud via its objectRegistry writer

- For the "live" simulation objects, there are two new volume filters
  that work directly with the OpenFOAM volume fields:
      * iso-surface
      * cutting planes
  Both use the VTK algorithms directly and support multiple values.
  Eg, can make multiple iso-levels or multiple planes parallel to each
  other.

- When VTK has been compiled with MPI-support, parallel rendering will
  be used.

- Additional title text properties (shadow, italic etc)

- Simplified handling of scalar-bar and visibility switches

- Support multiple text positions. Eg, for adding watermark text.
This commit is contained in:
Mark Olesen
2019-02-13 11:22:46 +01:00
committed by Andrew Heather
parent 03e6aa1a6d
commit 42fbf6d38c
68 changed files with 7123 additions and 847 deletions

View File

@ -2,10 +2,8 @@
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2015-2016 OpenCFD Ltd.
\\ / A nd | Copyright (C) 2015-2019 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
| Copyright (C) 2015 OpenFOAM Foundation
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@ -32,6 +30,13 @@ License
// VTK includes
#include "vtkActor.h"
#include "vtkCellData.h"
#include "vtkCellDataToPointData.h"
#include "vtkCompositeDataGeometryFilter.h"
#include "vtkCompositeDataSet.h"
#include "vtkCompositePolyDataMapper.h"
#include "vtkMultiPieceDataSet.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProperty.h"
@ -50,7 +55,7 @@ namespace functionObjects
{
namespace runTimePostPro
{
defineTypeNameAndDebug(functionObjectSurface, 0);
defineTypeName(functionObjectSurface);
addToRunTimeSelectionTable(surface, functionObjectSurface, dictionary);
}
}
@ -67,7 +72,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
// Not extremely elegant...
vtkSmartPointer<vtkPolyData> dataset;
if (fName.ext() == "vtk")
if ("vtk" == fName.ext())
{
auto reader = vtkSmartPointer<vtkPolyDataReader>::New();
@ -78,7 +83,7 @@ static vtkSmartPointer<vtkPolyData> getPolyDataFile(const Foam::fileName& fName)
return dataset;
}
if (fName.ext() == "vtp")
if ("vtp" == fName.ext())
{
auto reader = vtkSmartPointer<vtkXMLPolyDataReader>::New();
@ -110,17 +115,10 @@ functionObjectSurface
{}
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
Foam::functionObjects::runTimePostPro::functionObjectSurface::
~functionObjectSurface()
{}
// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
void Foam::functionObjects::runTimePostPro::functionObjectSurface::
addGeometryToScene
bool Foam::functionObjects::runTimePostPro::functionObjectSurface::
addGeometry
(
const scalar position,
vtkRenderer* renderer
@ -128,39 +126,291 @@ addGeometryToScene
{
if (!visible_)
{
return;
return false;
}
fileName fName = getFileName("file", fieldName_);
if (fName.empty())
DebugInfo << " Resolve surface " << functionObjectName_ << endl;
const polySurface* surf =
(
geometryBase::parent_.storedObjects()
.cfindObject<polySurface>(functionObjectName_)
);
// Treat surface with no faces/points like a missing surface
surf = ((surf && surf->nPoints()) ? surf : nullptr);
bool hasSurface = surf;
// Retrieve the field association (CELL, POINT) for the given field
unsigned fieldAssociation(0u);
if (surf)
{
unsigned queried = surf->queryFieldAssociation(fieldName_);
if (queried & polySurface::FACE_DATA)
{
fieldAssociation |= FieldAssociation::CELL_DATA;
}
if (queried & polySurface::POINT_DATA)
{
fieldAssociation |= FieldAssociation::POINT_DATA;
}
}
// Reduce the information
if (Pstream::parRun())
{
if (!hasSurface)
{
// No geometry - set all field association bits ON to ensure
// it does not affect bitwise reduction.
fieldAssociation = (~0u);
}
reduce(hasSurface, orOp<bool>());
reduce(fieldAssociation, bitAndOp<unsigned>());
}
if (!hasSurface)
{
WarningInFunction
<< "Unable to read file name from function object "
<< functionObjectName_ << " for field " << fieldName_
<< ". Surface will not be processed"
<< "No functionObject surface, or has no faces: "
<< functionObjectName_
<< endl;
return;
if (debug)
{
Info<< " Available surfaces:" << nl
<< geometryBase::parent_.storedObjects()
.sortedNames<polySurface>() << endl;
}
return false;
}
//// Pout<< "local surface = " << (surf ? surf->nFaces() : 0) << nl;
auto polyData = getPolyDataFile(fName);
if (!polyData || polyData->GetNumberOfPoints() == 0)
// Create a vtkMultiPieceDataSet with vtkPolyData on the leaves
vtkSmartPointer<vtkMultiPieceDataSet> multiPiece;
// Requesting glyphs on the surface AND only have face data?
// - just use the faceCentres directly and attach fields as CellData
// (not PointData).
if
(
representation_ == rtGlyph
&& (fieldAssociation == FieldAssociation::CELL_DATA)
)
{
WarningInFunction
<< "Could not read "<< fName << nl
<< "Only VTK (.vtp, .vtk) files are supported"
<< endl;
return;
multiPiece = gatherFaceCentres(surf);
}
else
{
multiPiece = gatherSurfacePieces(surf);
}
// Add the field (the information is consistent after last reduction).
// Need field(s) for glyphs or colourByField:
if (representation_ == rtGlyph || colourBy_ == cbField)
{
if (fieldAssociation == FieldAssociation::CELL_DATA)
{
addDimField<polySurfaceGeoMesh>
(
multiPiece,
surf,
fieldName_
);
}
else if (fieldAssociation & FieldAssociation::POINT_DATA)
{
addDimField<polySurfacePointGeoMesh>
(
multiPiece,
surf,
fieldName_
);
}
}
// Now have a multi-piece dataset that is one of the following:
//
// - one-piece per processor (OpenFOAM = parallel, VTK=parallel)
// - all pieces on master only (OpenFOAM = parallel, VTK=serial)
// Re-query field information - we may have stored it differently
// than the original source.
fieldSummary fieldInfo = queryFieldSummary(fieldName_, multiPiece);
fieldInfo.reduce();
// Not rendered on this processor?
// This is where we stop, but could also have an MPI barrier
if (!renderer)
{
return true;
}
// Rendering
{
auto polyData = vtkSmartPointer<vtkCompositeDataGeometryFilter>::New();
polyData->SetInputData(multiPiece);
polyData->Update();
if (representation_ == rtGlyph)
{
addGlyphs
(
position,
fieldName_, fieldInfo, // scaling
fieldName_, fieldInfo, // colouring
maxGlyphLength_,
polyData->GetOutput(),
surfaceActor_,
renderer
);
}
else
{
vtkSmartPointer<vtkCellDataToPointData> cellToPoint;
// CellData - Need a cell->point filter
if (smooth_ && !fieldInfo.hasPointData())
{
cellToPoint = vtkSmartPointer<vtkCellDataToPointData>::New();
cellToPoint->SetInputData(multiPiece);
polyData->SetInputConnection(cellToPoint->GetOutputPort());
}
else
{
polyData->SetInputData(multiPiece);
}
polyData->Update();
if (!smooth_)
{
addFeatureEdges(renderer, polyData);
}
auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(polyData->GetOutputPort());
setField
(
position,
fieldName_,
(
smooth_
? FieldAssociation::POINT_DATA
: FieldAssociation(fieldInfo.association_)
),
mapper,
renderer
);
surfaceActor_->SetMapper(mapper);
setRepresentation(surfaceActor_);
renderer->AddActor(surfaceActor_);
}
}
return true;
}
bool Foam::functionObjects::runTimePostPro::functionObjectSurface::
addGeometryFromFile
(
const scalar position,
vtkRenderer* renderer
)
{
if (!visible_)
{
return false;
}
vtkSmartPointer<vtkPolyData> polyData;
bool good = true;
// File reading is serial (master only)
if (Pstream::master())
{
fileName fName = getFileName("file", fieldName_);
if (fName.size())
{
polyData = getPolyDataFile(fName);
if (!polyData || polyData->GetNumberOfPoints() == 0)
{
good = false;
WarningInFunction
<< "Could not read "<< fName << nl
<< "Only VTK (.vtp, .vtk) files are supported"
<< endl;
}
else
{
DebugInfo << " Resolved surface " << fName << endl;
}
}
else
{
good = false;
WarningInFunction
<< "Unable to read file name from function object "
<< functionObjectName_ << " for field " << fieldName_
<< ". Surface will not be processed"
<< endl;
}
}
reduce(good, andOp<bool>());
if (!good)
{
return false;
}
// Only render on master
if (!renderer || !Pstream::master())
{
return true;
}
fieldSummary fieldInfo = queryFieldSummary(fieldName_, polyData);
// No reduction (serial)
// Render
if (representation_ == rtGlyph)
{
addGlyphs
(
position,
fieldName_,
fieldName_,
fieldName_, fieldInfo, // scaling
fieldName_, fieldInfo, // colouring
maxGlyphLength_,
polyData,
surfaceActor_,
@ -174,7 +424,14 @@ addGeometryToScene
auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(polyData);
setField(position, fieldName_, mapper, renderer, polyData);
setField
(
position,
fieldName_,
queryFieldAssociation(fieldName_, polyData),
mapper,
renderer
);
surfaceActor_->SetMapper(mapper);
@ -182,6 +439,44 @@ addGeometryToScene
renderer->AddActor(surfaceActor_);
}
return true;
}
void Foam::functionObjects::runTimePostPro::functionObjectSurface::
addGeometryToScene
(
const scalar position,
vtkRenderer* renderer
)
{
if (!visible_)
{
return;
}
if (liveObject_)
{
// Live source
if (addGeometry(position, renderer))
{
return;
}
WarningInFunction
<< "No functionObject live source, or is empty: "
<< functionObjectName_
<< " ... attempting with file source"
<< endl;
}
else
{
DebugInfo << "Using file source only" << nl;
}
// File source
addGeometryFromFile(position, renderer);
}
@ -189,6 +484,8 @@ bool Foam::functionObjects::runTimePostPro::functionObjectSurface::clear()
{
if (functionObjectBase::clear())
{
// Even for a "live" data source we allow file cleanup
// (eg, from a previous run, etc)
return removeFile("file", fieldName_);
}