Improvements to existing functionality -------------------------------------- - MPI is initialised without thread support if it is not needed e.g. uncollated - Use native c++11 threading; avoids problem with static destruction order. - etc/cellModels now only read if needed. - etc/controlDict can now be read from the environment variable FOAM_CONTROLDICT - Uniform files (e.g. '0/uniform/time') are now read only once on the master only (with the masterUncollated or collated file handlers) - collated format writes to 'processorsNNN' instead of 'processors'. The file format is unchanged. - Thread buffer and file buffer size are no longer limited to 2Gb. The global controlDict file contains parameters for file handling. Under some circumstances, e.g. running in parallel on a system without NFS, the user may need to set some parameters, e.g. fileHandler, before the global controlDict file is read from file. To support this, OpenFOAM now allows the global controlDict to be read as a string set to the FOAM_CONTROLDICT environment variable. The FOAM_CONTROLDICT environment variable can be set to the content the global controlDict file, e.g. from a sh/bash shell: export FOAM_CONTROLDICT=$(foamDictionary $FOAM_ETC/controlDict) FOAM_CONTROLDICT can then be passed to mpirun using the -x option, e.g.: mpirun -np 2 -x FOAM_CONTROLDICT simpleFoam -parallel Note that while this avoids the need for NFS to read the OpenFOAM configuration the executable still needs to load shared libraries which must either be copied locally or available via NFS or equivalent. New: Multiple IO ranks ---------------------- The masterUncollated and collated fileHandlers can now use multiple ranks for writing e.g.: mpirun -np 6 simpleFoam -parallel -ioRanks '(0 3)' In this example ranks 0 ('processor0') and 3 ('processor3') now handle all the I/O. Rank 0 handles 0,1,2 and rank 3 handles 3,4,5. The set of IO ranks should always include 0 as first element and be sorted in increasing order. The collated fileHandler uses the directory naming processorsNNN_XXX-YYY where NNN is the total number of processors and XXX and YYY are first and last processor in the rank, e.g. in above example the directories would be processors6_0-2 processors6_3-5 and each of the collated files in these contains data of the local ranks only. The same naming also applies when e.g. running decomposePar: decomposePar -fileHandler collated -ioRanks '(0 3)' New: Distributed data --------------------- The individual root directories can be placed on different hosts with different paths if necessary. In the current framework it is necessary to specify the root per slave process but this has been simplified with the option of specifying the root per host with the -hostRoots command line option: mpirun -np 6 simpleFoam -parallel -ioRanks '(0 3)' \ -hostRoots '("machineA" "/tmp/" "machineB" "/tmp")' The hostRoots option is followed by a list of machine name + root directory, the machine name can contain regular expressions. New: hostCollated ----------------- The new hostCollated fileHandler automatically sets the 'ioRanks' according to the host name with the lowest rank e.g. to run simpleFoam on 6 processors with ranks 0-2 on machineA and ranks 3-5 on machineB with the machines specified in the hostfile: mpirun -np 6 --hostfile hostfile simpleFoam -parallel -fileHandler hostCollated This is equivalent to mpirun -np 6 --hostfile hostfile simpleFoam -parallel -fileHandler collated -ioRanks '(0 3)' This example will write directories: processors6_0-2/ processors6_3-5/ A typical example would use distributed data e.g. no two nodes, machineA and machineB, each with three processes: decomposePar -fileHandler collated -case cavity # Copy case (constant/*, system/*, processors6/) to master: rsync -a cavity machineA:/tmp/ # Create root on slave: ssh machineB mkdir -p /tmp/cavity # Run mpirun --hostfile hostfile icoFoam \ -case /tmp/cavity -parallel -fileHandler hostCollated \ -hostRoots '("machineA" "/tmp" "machineB" "/tmp")' Contributed by Mattijs Janssens
997 lines
26 KiB
C
997 lines
26 KiB
C
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration |
|
|
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
|
|
\\/ M anipulation | Copyright (C) 2017 OpenCFD Ltd.
|
|
-------------------------------------------------------------------------------
|
|
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 "vtkPVFoam.H"
|
|
#include "vtkPVFoamReader.h"
|
|
|
|
// OpenFOAM includes
|
|
#include "areaFaMesh.H"
|
|
#include "faMesh.H"
|
|
#include "fvMesh.H"
|
|
#include "Time.H"
|
|
#include "patchZones.H"
|
|
#include "IOobjectList.H"
|
|
#include "collatedFileOperation.H"
|
|
|
|
// VTK includes
|
|
#include "vtkDataArraySelection.h"
|
|
#include "vtkMultiBlockDataSet.h"
|
|
#include "vtkRenderer.h"
|
|
#include "vtkTextActor.h"
|
|
#include "vtkTextProperty.h"
|
|
#include "vtkSmartPointer.h"
|
|
#include "vtkInformation.h"
|
|
|
|
// Templates (only needed here)
|
|
#include "vtkPVFoamUpdateTemplates.C"
|
|
|
|
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
|
|
|
|
namespace Foam
|
|
{
|
|
defineTypeNameAndDebug(vtkPVFoam, 0);
|
|
|
|
// file-scope
|
|
static word updateStateName(polyMesh::readUpdateState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case polyMesh::UNCHANGED: return "UNCHANGED";
|
|
case polyMesh::POINTS_MOVED: return "POINTS_MOVED";
|
|
case polyMesh::TOPO_CHANGE: return "TOPO_CHANGE";
|
|
case polyMesh::TOPO_PATCH_CHANGE: return "TOPO_PATCH_CHANGE";
|
|
};
|
|
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
|
|
|
|
namespace Foam
|
|
{
|
|
// file-scope
|
|
|
|
//- Create a text actor
|
|
vtkSmartPointer<vtkTextActor> createTextActor
|
|
(
|
|
const std::string& s,
|
|
const Foam::point& pt
|
|
)
|
|
{
|
|
vtkSmartPointer<vtkTextActor> txt =
|
|
vtkSmartPointer<vtkTextActor>::New();
|
|
|
|
txt->SetInput(s.c_str());
|
|
|
|
// Set text properties
|
|
vtkTextProperty* tprop = txt->GetTextProperty();
|
|
tprop->SetFontFamilyToArial();
|
|
tprop->BoldOn();
|
|
tprop->ShadowOff();
|
|
tprop->SetLineSpacing(1.0);
|
|
tprop->SetFontSize(14);
|
|
tprop->SetColor(1.0, 0.0, 1.0);
|
|
tprop->SetJustificationToCentered();
|
|
|
|
txt->GetPositionCoordinate()->SetCoordinateSystemToWorld();
|
|
txt->GetPositionCoordinate()->SetValue(pt.x(), pt.y(), pt.z());
|
|
|
|
return txt;
|
|
}
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
|
|
|
|
void Foam::vtkPVFoam::resetCounters()
|
|
{
|
|
// Reset array range information (ids and sizes)
|
|
rangeVolume_.reset();
|
|
rangePatches_.reset();
|
|
rangeClouds_.reset();
|
|
rangeCellZones_.reset();
|
|
rangeFaceZones_.reset();
|
|
rangePointZones_.reset();
|
|
rangeCellSets_.reset();
|
|
rangeFaceSets_.reset();
|
|
rangePointSets_.reset();
|
|
}
|
|
|
|
|
|
template<class Container>
|
|
bool Foam::vtkPVFoam::addOutputBlock
|
|
(
|
|
vtkMultiBlockDataSet* output,
|
|
const HashTable<Container, string>& cache,
|
|
const arrayRange& selector,
|
|
const bool singleDataset
|
|
) const
|
|
{
|
|
const auto blockNo = output->GetNumberOfBlocks();
|
|
vtkSmartPointer<vtkMultiBlockDataSet> block;
|
|
int datasetNo = 0;
|
|
|
|
const List<label> partIds = selector.intersection(selectedPartIds_);
|
|
|
|
for (const auto partId : partIds)
|
|
{
|
|
const auto& longName = selectedPartIds_[partId];
|
|
const word shortName = getFoamName(longName);
|
|
|
|
auto iter = cache.find(longName);
|
|
if (iter.found() && iter.object().dataset)
|
|
{
|
|
auto dataset = iter.object().dataset;
|
|
|
|
if (singleDataset)
|
|
{
|
|
output->SetBlock(blockNo, dataset);
|
|
output->GetMetaData(blockNo)->Set
|
|
(
|
|
vtkCompositeDataSet::NAME(),
|
|
shortName.c_str()
|
|
);
|
|
|
|
++datasetNo;
|
|
break;
|
|
}
|
|
else if (datasetNo == 0)
|
|
{
|
|
block = vtkSmartPointer<vtkMultiBlockDataSet>::New();
|
|
output->SetBlock(blockNo, block);
|
|
output->GetMetaData(blockNo)->Set
|
|
(
|
|
vtkCompositeDataSet::NAME(),
|
|
selector.name()
|
|
);
|
|
}
|
|
|
|
block->SetBlock(datasetNo, dataset);
|
|
block->GetMetaData(datasetNo)->Set
|
|
(
|
|
vtkCompositeDataSet::NAME(),
|
|
shortName.c_str()
|
|
);
|
|
|
|
++datasetNo;
|
|
}
|
|
}
|
|
|
|
return datasetNo;
|
|
}
|
|
|
|
|
|
int Foam::vtkPVFoam::setTime(const std::vector<double>& requestTimes)
|
|
{
|
|
if (requestTimes.empty())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
Time& runTime = dbPtr_();
|
|
|
|
// Get times list
|
|
instantList Times = runTime.times();
|
|
|
|
int nearestIndex = timeIndex_;
|
|
for (const double& timeValue : requestTimes)
|
|
{
|
|
const int index = Time::findClosestTimeIndex(Times, timeValue);
|
|
if (index >= 0 && index != timeIndex_)
|
|
{
|
|
nearestIndex = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nearestIndex < 0)
|
|
{
|
|
nearestIndex = 0;
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "<beg> setTime(";
|
|
unsigned reqi = 0;
|
|
for (const double& timeValue : requestTimes)
|
|
{
|
|
if (reqi) Info<< ", ";
|
|
Info<< timeValue;
|
|
++reqi;
|
|
}
|
|
Info<< ") - previousIndex = " << timeIndex_
|
|
<< ", nearestIndex = " << nearestIndex << nl;
|
|
}
|
|
|
|
// See what has changed
|
|
if (timeIndex_ != nearestIndex)
|
|
{
|
|
timeIndex_ = nearestIndex;
|
|
runTime.setTime(Times[nearestIndex], nearestIndex);
|
|
|
|
// When mesh changes, so do fields
|
|
meshState_ =
|
|
(
|
|
volMeshPtr_
|
|
? volMeshPtr_->readUpdate()
|
|
: polyMesh::TOPO_CHANGE
|
|
);
|
|
|
|
reader_->UpdateProgress(0.05);
|
|
|
|
// this seems to be needed for catching Lagrangian fields
|
|
updateInfo();
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "<end> setTime() - selectedTime="
|
|
<< Times[nearestIndex].name() << " index=" << timeIndex_
|
|
<< "/" << Times.size()
|
|
<< " meshUpdateState=" << updateStateName(meshState_) << nl;
|
|
}
|
|
|
|
return nearestIndex;
|
|
}
|
|
|
|
|
|
Foam::word Foam::vtkPVFoam::getReaderPartName(const int partId) const
|
|
{
|
|
return getFoamName(reader_->GetPartArrayName(partId));
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
|
|
|
|
Foam::vtkPVFoam::vtkPVFoam
|
|
(
|
|
const char* const vtkFileName,
|
|
vtkPVFoamReader* reader
|
|
)
|
|
:
|
|
reader_(reader),
|
|
dbPtr_(nullptr),
|
|
volMeshPtr_(nullptr),
|
|
areaMeshPtr_(nullptr),
|
|
meshRegion_(polyMesh::defaultRegion),
|
|
meshDir_(polyMesh::meshSubDir),
|
|
timeIndex_(-1),
|
|
decomposePoly_(false),
|
|
meshState_(polyMesh::TOPO_CHANGE),
|
|
rangeVolume_("volMesh"),
|
|
rangeArea_("areaMesh"),
|
|
rangePatches_("patch"),
|
|
rangeClouds_("lagrangian"),
|
|
rangeCellZones_("cellZone"),
|
|
rangeFaceZones_("faceZone"),
|
|
rangePointZones_("pointZone"),
|
|
rangeCellSets_("cellSet"),
|
|
rangeFaceSets_("faceSet"),
|
|
rangePointSets_("pointSet")
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "vtkPVFoam - " << vtkFileName << nl;
|
|
printMemory();
|
|
}
|
|
|
|
fileName FileName(vtkFileName);
|
|
|
|
// Make sure not to use the threaded version - it does not like
|
|
// being loaded as a shared library - static cleanup order is problematic.
|
|
// For now just disable the threaded writer.
|
|
fileOperations::collatedFileOperation::maxThreadFileBufferSize = 0;
|
|
|
|
// avoid argList and get rootPath/caseName directly from the file
|
|
fileName fullCasePath(FileName.path());
|
|
|
|
if (!isDir(fullCasePath))
|
|
{
|
|
return;
|
|
}
|
|
if (fullCasePath == ".")
|
|
{
|
|
fullCasePath = cwd();
|
|
}
|
|
|
|
// The name of the executable, unless already present in the environment
|
|
setEnv("FOAM_EXECUTABLE", "paraview", false);
|
|
|
|
// Set the case as an environment variable - some BCs might use this
|
|
if (fullCasePath.name().find("processors", 0) == 0)
|
|
{
|
|
// FileName e.g. "cavity/processors256/processor1.OpenFOAM
|
|
// Remove the processors section so it goes into processorDDD
|
|
// checking below.
|
|
fullCasePath = fullCasePath.path()/fileName(FileName.name()).lessExt();
|
|
}
|
|
|
|
|
|
if (fullCasePath.name().find("processor", 0) == 0)
|
|
{
|
|
// Give filehandler opportunity to analyse number of processors
|
|
(void)fileHandler().filePath(fullCasePath);
|
|
|
|
const fileName globalCase = fullCasePath.path();
|
|
|
|
setEnv("FOAM_CASE", globalCase, true);
|
|
setEnv("FOAM_CASENAME", globalCase.name(), true);
|
|
}
|
|
else
|
|
{
|
|
setEnv("FOAM_CASE", fullCasePath, true);
|
|
setEnv("FOAM_CASENAME", fullCasePath.name(), true);
|
|
}
|
|
|
|
// look for 'case{region}.OpenFOAM'
|
|
// could be stringent and insist the prefix match the directory name...
|
|
// Note: cannot use fileName::name() due to the embedded '{}'
|
|
string caseName(fileName(FileName).lessExt());
|
|
const auto beg = caseName.find_last_of("/{");
|
|
const auto end = caseName.find('}', beg);
|
|
|
|
if
|
|
(
|
|
beg != std::string::npos && caseName[beg] == '{'
|
|
&& end != std::string::npos && end == caseName.size()-1
|
|
)
|
|
{
|
|
meshRegion_ = caseName.substr(beg+1, end-beg-1);
|
|
|
|
// some safety
|
|
if (meshRegion_.empty())
|
|
{
|
|
meshRegion_ = polyMesh::defaultRegion;
|
|
}
|
|
|
|
if (meshRegion_ != polyMesh::defaultRegion)
|
|
{
|
|
meshDir_ = meshRegion_/polyMesh::meshSubDir;
|
|
}
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "fullCasePath=" << fullCasePath << nl
|
|
<< "FOAM_CASE=" << getEnv("FOAM_CASE") << nl
|
|
<< "FOAM_CASENAME=" << getEnv("FOAM_CASENAME") << nl
|
|
<< "region=" << meshRegion_ << nl;
|
|
}
|
|
|
|
// Create time object
|
|
dbPtr_.reset
|
|
(
|
|
new Time
|
|
(
|
|
Time::controlDictName,
|
|
fileName(fullCasePath.path()),
|
|
fileName(fullCasePath.name())
|
|
)
|
|
);
|
|
|
|
dbPtr_().functionObjects().off();
|
|
|
|
updateInfo();
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
|
|
|
|
Foam::vtkPVFoam::~vtkPVFoam()
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "~vtkPVFoam" << nl;
|
|
}
|
|
|
|
delete volMeshPtr_;
|
|
delete areaMeshPtr_;
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
|
|
|
|
void Foam::vtkPVFoam::updateInfo()
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "<beg> updateInfo"
|
|
<< " [volMeshPtr=" << (volMeshPtr_ ? "set" : "nullptr")
|
|
<< "] timeIndex="
|
|
<< timeIndex_ << nl;
|
|
}
|
|
|
|
resetCounters();
|
|
|
|
// Part selection
|
|
{
|
|
vtkDataArraySelection* select = reader_->GetPartSelection();
|
|
|
|
// There are two ways to ensure we have the correct list of parts:
|
|
// 1. remove everything and then set particular entries 'on'
|
|
// 2. build a 'char **' list and call SetArraysWithDefault()
|
|
//
|
|
// Nr. 2 has the potential advantage of not touching the modification
|
|
// time of the vtkDataArraySelection, but the qt/paraview proxy
|
|
// layer doesn't care about that anyhow.
|
|
|
|
HashSet<string> enabled;
|
|
if (!select->GetNumberOfArrays() && !volMeshPtr_)
|
|
{
|
|
// Fake enable 'internalMesh' on the first call
|
|
enabled = { "internalMesh" };
|
|
}
|
|
else
|
|
{
|
|
// Preserve the enabled selections
|
|
enabled = getSelectedArraySet(select);
|
|
}
|
|
|
|
select->RemoveAllArrays(); // Clear existing list
|
|
|
|
// Update mesh parts list - add Lagrangian at the bottom
|
|
updateInfoInternalMesh(select);
|
|
updateInfoPatches(select, enabled);
|
|
updateInfoSets(select);
|
|
updateInfoZones(select);
|
|
updateInfoAreaMesh(select);
|
|
updateInfoLagrangian(select);
|
|
|
|
setSelectedArrayEntries(select, enabled); // Adjust/restore selected
|
|
}
|
|
|
|
// Volume and area fields - includes save/restore of selected
|
|
updateInfoContinuumFields(reader_->GetVolFieldSelection());
|
|
|
|
// Point fields - includes save/restore of selected
|
|
updateInfoPointFields(reader_->GetPointFieldSelection());
|
|
|
|
// Lagrangian fields - includes save/restore of selected
|
|
updateInfoLagrangianFields(reader_->GetLagrangianFieldSelection());
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "<end> updateInfo" << nl;
|
|
}
|
|
}
|
|
|
|
|
|
void Foam::vtkPVFoam::Update
|
|
(
|
|
vtkMultiBlockDataSet* output,
|
|
vtkMultiBlockDataSet* outputLagrangian
|
|
)
|
|
{
|
|
if (debug)
|
|
{
|
|
cout<< "<beg> Foam::vtkPVFoam::Update\n";
|
|
output->Print(cout);
|
|
if (outputLagrangian) outputLagrangian->Print(cout);
|
|
printMemory();
|
|
}
|
|
reader_->UpdateProgress(0.1);
|
|
|
|
const int caching = reader_->GetMeshCaching();
|
|
const bool oldDecomp = decomposePoly_;
|
|
decomposePoly_ = !reader_->GetUseVTKPolyhedron();
|
|
|
|
// Set up mesh parts selection(s)
|
|
// Update cached, saved, unneed values.
|
|
{
|
|
vtkDataArraySelection* selection = reader_->GetPartSelection();
|
|
const int n = selection->GetNumberOfArrays();
|
|
|
|
selectedPartIds_.clear();
|
|
HashSet<string> nowActive;
|
|
|
|
for (int id=0; id < n; ++id)
|
|
{
|
|
const string str(selection->GetArrayName(id));
|
|
const bool status = selection->GetArraySetting(id);
|
|
|
|
if (status)
|
|
{
|
|
selectedPartIds_.set(id, str); // id -> name
|
|
nowActive.set(str);
|
|
}
|
|
|
|
if (debug > 1)
|
|
{
|
|
Info<< " part[" << id << "] = " << status
|
|
<< " : " << str << nl;
|
|
}
|
|
}
|
|
|
|
// Dispose of unneeded components
|
|
cachedVtp_.retain(nowActive);
|
|
cachedVtu_.retain(nowActive);
|
|
|
|
if
|
|
(
|
|
!caching
|
|
|| meshState_ == polyMesh::TOPO_CHANGE
|
|
|| meshState_ == polyMesh::TOPO_PATCH_CHANGE
|
|
)
|
|
{
|
|
// Eliminate cached values that would be unreliable
|
|
forAllIters(cachedVtp_, iter)
|
|
{
|
|
iter.object().clearGeom();
|
|
iter.object().clear();
|
|
}
|
|
forAllIters(cachedVtu_, iter)
|
|
{
|
|
iter.object().clearGeom();
|
|
iter.object().clear();
|
|
}
|
|
}
|
|
else if (oldDecomp != decomposePoly_)
|
|
{
|
|
// poly-decompose changed - dispose of cached values
|
|
forAllIters(cachedVtu_, iter)
|
|
{
|
|
iter.object().clearGeom();
|
|
iter.object().clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
reader_->UpdateProgress(0.15);
|
|
|
|
// Update the OpenFOAM mesh
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "<beg> updateFoamMesh" << nl;
|
|
printMemory();
|
|
}
|
|
|
|
if (!caching)
|
|
{
|
|
delete volMeshPtr_;
|
|
delete areaMeshPtr_;
|
|
|
|
volMeshPtr_ = nullptr;
|
|
areaMeshPtr_ = nullptr;
|
|
}
|
|
|
|
// Check to see if the OpenFOAM mesh has been created
|
|
if (!volMeshPtr_)
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "Creating OpenFOAM mesh for region " << meshRegion_
|
|
<< " at time=" << dbPtr_().timeName() << nl;
|
|
}
|
|
|
|
volMeshPtr_ = new fvMesh
|
|
(
|
|
IOobject
|
|
(
|
|
meshRegion_,
|
|
dbPtr_().timeName(),
|
|
dbPtr_(),
|
|
IOobject::MUST_READ
|
|
)
|
|
);
|
|
|
|
meshState_ = polyMesh::TOPO_CHANGE; // New mesh
|
|
}
|
|
else
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "Using existing OpenFOAM mesh" << nl;
|
|
}
|
|
}
|
|
|
|
if (rangeArea_.intersects(selectedPartIds_))
|
|
{
|
|
if (!areaMeshPtr_)
|
|
{
|
|
areaMeshPtr_ = new faMesh(*volMeshPtr_);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
delete areaMeshPtr_;
|
|
|
|
areaMeshPtr_ = nullptr;
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "<end> updateFoamMesh" << nl;
|
|
printMemory();
|
|
}
|
|
}
|
|
|
|
reader_->UpdateProgress(0.4);
|
|
|
|
convertMeshVolume();
|
|
convertMeshPatches();
|
|
reader_->UpdateProgress(0.6);
|
|
|
|
if (reader_->GetIncludeZones())
|
|
{
|
|
convertMeshCellZones();
|
|
convertMeshFaceZones();
|
|
convertMeshPointZones();
|
|
reader_->UpdateProgress(0.65);
|
|
}
|
|
|
|
if (reader_->GetIncludeSets())
|
|
{
|
|
convertMeshCellSets();
|
|
convertMeshFaceSets();
|
|
convertMeshPointSets();
|
|
reader_->UpdateProgress(0.7);
|
|
}
|
|
|
|
convertMeshArea();
|
|
|
|
convertMeshLagrangian();
|
|
|
|
reader_->UpdateProgress(0.8);
|
|
|
|
// Update fields
|
|
convertVolFields();
|
|
convertPointFields();
|
|
convertAreaFields();
|
|
|
|
convertLagrangianFields();
|
|
|
|
// Assemble multiblock output
|
|
addOutputBlock(output, cachedVtu_, rangeVolume_, true); // One dataset
|
|
addOutputBlock(output, cachedVtp_, rangePatches_);
|
|
addOutputBlock(output, cachedVtu_, rangeCellZones_);
|
|
addOutputBlock(output, cachedVtp_, rangeFaceZones_);
|
|
addOutputBlock(output, cachedVtp_, rangePointZones_);
|
|
addOutputBlock(output, cachedVtu_, rangeCellSets_);
|
|
addOutputBlock(output, cachedVtp_, rangeFaceSets_);
|
|
addOutputBlock(output, cachedVtp_, rangePointSets_);
|
|
addOutputBlock(output, cachedVtp_, rangeArea_);
|
|
addOutputBlock
|
|
(
|
|
(outputLagrangian ? outputLagrangian : output),
|
|
cachedVtp_,
|
|
rangeClouds_
|
|
);
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "done reader part" << nl << nl;
|
|
}
|
|
reader_->UpdateProgress(0.95);
|
|
|
|
meshState_ = polyMesh::UNCHANGED;
|
|
|
|
if (caching & 2)
|
|
{
|
|
// Suppress caching of Lagrangian since it normally always changes.
|
|
cachedVtp_.filterKeys
|
|
(
|
|
[](const word& k){ return k.startsWith("lagrangian/"); },
|
|
true // prune
|
|
);
|
|
}
|
|
else
|
|
{
|
|
cachedVtp_.clear();
|
|
cachedVtu_.clear();
|
|
}
|
|
}
|
|
|
|
|
|
void Foam::vtkPVFoam::UpdateFinalize()
|
|
{
|
|
if (!reader_->GetMeshCaching())
|
|
{
|
|
delete volMeshPtr_;
|
|
delete areaMeshPtr_;
|
|
|
|
volMeshPtr_ = nullptr;
|
|
areaMeshPtr_ = nullptr;
|
|
}
|
|
|
|
reader_->UpdateProgress(1.0);
|
|
}
|
|
|
|
|
|
std::vector<double> Foam::vtkPVFoam::findTimes(const bool skipZero) const
|
|
{
|
|
std::vector<double> times;
|
|
|
|
if (dbPtr_.valid())
|
|
{
|
|
const Time& runTime = dbPtr_();
|
|
instantList timeLst = runTime.times();
|
|
|
|
// find the first time for which this mesh appears to exist
|
|
label begIndex = timeLst.size();
|
|
forAll(timeLst, timei)
|
|
{
|
|
if
|
|
(
|
|
IOobject
|
|
(
|
|
"points",
|
|
timeLst[timei].name(),
|
|
meshDir_,
|
|
runTime
|
|
).typeHeaderOk<pointIOField>(false, false)
|
|
)
|
|
{
|
|
begIndex = timei;
|
|
break;
|
|
}
|
|
}
|
|
|
|
label nTimes = timeLst.size() - begIndex;
|
|
|
|
// skip "constant" time whenever possible
|
|
if (begIndex == 0 && nTimes > 1)
|
|
{
|
|
if (timeLst[begIndex].name() == runTime.constant())
|
|
{
|
|
++begIndex;
|
|
--nTimes;
|
|
}
|
|
}
|
|
|
|
// skip "0/" time if requested and possible
|
|
if (skipZero && nTimes > 1 && timeLst[begIndex].name() == "0")
|
|
{
|
|
++begIndex;
|
|
--nTimes;
|
|
}
|
|
|
|
times.reserve(nTimes);
|
|
while (nTimes-- > 0)
|
|
{
|
|
times.push_back(timeLst[begIndex++].value());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (debug)
|
|
{
|
|
cout<< "no valid dbPtr:\n";
|
|
}
|
|
}
|
|
|
|
return times;
|
|
}
|
|
|
|
|
|
void Foam::vtkPVFoam::renderPatchNames
|
|
(
|
|
vtkRenderer* renderer,
|
|
const bool show
|
|
)
|
|
{
|
|
// Always remove old actors first
|
|
|
|
for (auto& actor : patchTextActors_)
|
|
{
|
|
renderer->RemoveViewProp(actor);
|
|
}
|
|
patchTextActors_.clear();
|
|
|
|
if (show && volMeshPtr_)
|
|
{
|
|
// get the display patches, strip off any prefix/suffix
|
|
hashedWordList selectedPatches = getSelected
|
|
(
|
|
reader_->GetPartSelection(),
|
|
rangePatches_
|
|
);
|
|
|
|
if (selectedPatches.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const polyBoundaryMesh& pbMesh = volMeshPtr_->boundaryMesh();
|
|
|
|
// Find the total number of zones
|
|
// Each zone will take the patch name
|
|
// Number of zones per patch ... zero zones should be skipped
|
|
labelList nZones(pbMesh.size(), 0);
|
|
|
|
// Per global zone number the average face centre position
|
|
List<DynamicList<point>> zoneCentre(pbMesh.size());
|
|
|
|
|
|
// Loop through all patches to determine zones, and centre of each zone
|
|
forAll(pbMesh, patchi)
|
|
{
|
|
const polyPatch& pp = pbMesh[patchi];
|
|
|
|
// Only include the patch if it is selected
|
|
if (!selectedPatches.found(pp.name()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const labelListList& edgeFaces = pp.edgeFaces();
|
|
const vectorField& n = pp.faceNormals();
|
|
|
|
boolList featEdge(pp.nEdges(), false);
|
|
|
|
forAll(edgeFaces, edgei)
|
|
{
|
|
const labelList& eFaces = edgeFaces[edgei];
|
|
|
|
if (eFaces.size() == 1)
|
|
{
|
|
// Note: could also do ones with > 2 faces but this gives
|
|
// too many zones for baffles
|
|
featEdge[edgei] = true;
|
|
}
|
|
else if (mag(n[eFaces[0]] & n[eFaces[1]]) < 0.5)
|
|
{
|
|
featEdge[edgei] = true;
|
|
}
|
|
}
|
|
|
|
// Do topological analysis of patch, find disconnected regions
|
|
patchZones pZones(pp, featEdge);
|
|
|
|
nZones[patchi] = pZones.nZones();
|
|
|
|
labelList zoneNFaces(pZones.nZones(), 0);
|
|
|
|
// Create storage for additional zone centres
|
|
forAll(zoneNFaces, zonei)
|
|
{
|
|
zoneCentre[patchi].append(Zero);
|
|
}
|
|
|
|
// Do averaging per individual zone
|
|
forAll(pp, facei)
|
|
{
|
|
const label zonei = pZones[facei];
|
|
zoneCentre[patchi][zonei] += pp[facei].centre(pp.points());
|
|
zoneNFaces[zonei]++;
|
|
}
|
|
|
|
forAll(zoneCentre[patchi], zonei)
|
|
{
|
|
zoneCentre[patchi][zonei] /= zoneNFaces[zonei];
|
|
}
|
|
}
|
|
|
|
// Count number of zones we're actually going to display.
|
|
// This is truncated to a max per patch
|
|
|
|
const label MAXPATCHZONES = 20;
|
|
|
|
label displayZoneI = 0;
|
|
|
|
forAll(pbMesh, patchi)
|
|
{
|
|
displayZoneI += min(MAXPATCHZONES, nZones[patchi]);
|
|
}
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "displayed zone centres = " << displayZoneI << nl
|
|
<< "zones per patch = " << nZones << nl;
|
|
}
|
|
|
|
// Set the size of the patch labels to max number of zones
|
|
patchTextActors_.setSize(displayZoneI);
|
|
|
|
if (debug)
|
|
{
|
|
Info<< "constructing patch labels" << nl;
|
|
}
|
|
|
|
// Actor index
|
|
displayZoneI = 0;
|
|
|
|
forAll(pbMesh, patchi)
|
|
{
|
|
const polyPatch& pp = pbMesh[patchi];
|
|
|
|
// Only selected patches will have a non-zero number of zones
|
|
const label nDisplayZones = min(MAXPATCHZONES, nZones[patchi]);
|
|
label increment = 1;
|
|
if (nZones[patchi] >= MAXPATCHZONES)
|
|
{
|
|
increment = nZones[patchi]/MAXPATCHZONES;
|
|
}
|
|
|
|
label globalZoneI = 0;
|
|
for (label i = 0; i < nDisplayZones; ++i, globalZoneI += increment)
|
|
{
|
|
if (debug)
|
|
{
|
|
Info<< "patch name = " << pp.name() << nl
|
|
<< "anchor = " << zoneCentre[patchi][globalZoneI] << nl
|
|
<< "globalZoneI = " << globalZoneI << nl;
|
|
}
|
|
|
|
// Into a list for later removal
|
|
patchTextActors_[displayZoneI++] = createTextActor
|
|
(
|
|
pp.name(),
|
|
zoneCentre[patchi][globalZoneI]
|
|
);
|
|
}
|
|
}
|
|
|
|
// Resize the patch names list to the actual number of patch names added
|
|
patchTextActors_.setSize(displayZoneI);
|
|
}
|
|
|
|
// Add text to each renderer
|
|
for (auto& actor : patchTextActors_)
|
|
{
|
|
renderer->AddViewProp(actor);
|
|
}
|
|
}
|
|
|
|
|
|
void Foam::vtkPVFoam::PrintSelf(ostream& os, vtkIndent indent) const
|
|
{
|
|
os << indent << "Number of nodes: "
|
|
<< (volMeshPtr_ ? volMeshPtr_->nPoints() : 0) << "\n";
|
|
|
|
os << indent << "Number of cells: "
|
|
<< (volMeshPtr_ ? volMeshPtr_->nCells() : 0) << "\n";
|
|
|
|
os << indent << "Number of available time steps: "
|
|
<< (dbPtr_.valid() ? dbPtr_().times().size() : 0) << "\n";
|
|
|
|
os << indent << "mesh region: " << meshRegion_ << "\n";
|
|
}
|
|
|
|
|
|
void Foam::vtkPVFoam::printInfo() const
|
|
{
|
|
std::cout
|
|
<< "Region: " << meshRegion_ << "\n"
|
|
<< "nPoints: " << (volMeshPtr_ ? volMeshPtr_->nPoints() : 0) << "\n"
|
|
<< "nCells: " << (volMeshPtr_ ? volMeshPtr_->nCells() : 0) << "\n"
|
|
<< "nTimes: "
|
|
<< (dbPtr_.valid() ? dbPtr_().times().size() : 0) << "\n";
|
|
|
|
std::vector<double> times = this->findTimes(reader_->GetSkipZeroTime());
|
|
|
|
std::cout<<" " << times.size() << "(";
|
|
for (const double& val : times)
|
|
{
|
|
std::cout<< ' ' << val;
|
|
}
|
|
std::cout << " )" << std::endl;
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|