/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2015-2019 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 . \*---------------------------------------------------------------------------*/ // OpenFOAM includes #include "fieldVisualisationBase.H" #include "runTimePostProcessing.H" #include "doubleVector.H" #include "foamVtkTools.H" // VTK includes #include "vtkArrowSource.h" #include "vtkCellDataToPointData.h" #include "vtkCellData.h" #include "vtkColorTransferFunction.h" #include "vtkCompositeDataSet.h" #include "vtkDataObjectTreeIterator.h" #include "vtkFieldData.h" #include "vtkGlyph3D.h" #include "vtkLookupTable.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkPolyDataMapper.h" #include "vtkRenderer.h" #include "vtkSmartPointer.h" #include "vtkSphereSource.h" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // const Foam::Enum < Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: colourByType > Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: colourByTypeNames ({ { colourByType::cbColour, "colour" }, { colourByType::cbField, "field" }, }); const Foam::Enum < Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: colourMapType > Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: colourMapTypeNames ({ { colourMapType::cmCoolToWarm, "coolToWarm" }, { colourMapType::cmCoolToWarm, "blueWhiteRed" }, { colourMapType::cmColdAndHot, "coldAndHot" }, { colourMapType::cmFire, "fire" }, { colourMapType::cmRainbow, "rainbow" }, { colourMapType::cmGreyscale, "greyscale" }, { colourMapType::cmGreyscale, "grayscale" }, { colourMapType::cmXray, "xray" }, }); // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * // Foam::functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: queryFieldSummary ( const word& fieldName, vtkDataSet* dataset ) { fieldSummary queried; if (dataset) { vtkDataArray* array; array = vtkDataArray::SafeDownCast ( dataset->GetCellData()->GetAbstractArray(fieldName.c_str()) ); if (array) { queried.nComponents_ = array->GetNumberOfComponents(); queried.association_ |= FieldAssociation::CELL_DATA; queried.range_ += vtk::Tools::rangeOf(array); } array = vtkDataArray::SafeDownCast ( dataset->GetPointData()->GetAbstractArray(fieldName.c_str()) ); if (array) { queried.nComponents_ = array->GetNumberOfComponents(); queried.association_ |= FieldAssociation::POINT_DATA; queried.range_ += vtk::Tools::rangeOf(array); } } return queried; } Foam::functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: queryFieldSummary ( const word& fieldName, vtkCompositeDataSet* data ) { fieldSummary queried; auto iter = vtkSmartPointer::New(); iter->SetDataSet(data); iter->VisitOnlyLeavesOn(); iter->SkipEmptyNodesOn(); for ( iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem() ) { vtkDataSet* dataset = vtkDataSet::SafeDownCast ( iter->GetCurrentDataObject() ); if (dataset) { fieldSummary local(queryFieldSummary(fieldName, dataset)); if (!queried.nComponents_) { queried.nComponents_ = local.nComponents_; } queried.association_ |= local.association_; queried.range_ += local.range_; } } return queried; } Foam::functionObjects::runTimePostPro::fieldVisualisationBase::FieldAssociation Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: queryFieldAssociation ( const word& fieldName, vtkDataSet* dataset ) { unsigned where(FieldAssociation::NO_DATA); if (dataset) { if (dataset->GetCellData()->HasArray(fieldName.c_str())) { where |= FieldAssociation::CELL_DATA; } if (dataset->GetPointData()->HasArray(fieldName.c_str())) { where |= FieldAssociation::POINT_DATA; } } return FieldAssociation(where); } Foam::functionObjects::runTimePostPro::fieldVisualisationBase::FieldAssociation Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: queryFieldAssociation ( const word& fieldName, vtkCompositeDataSet* data ) { unsigned where(FieldAssociation::NO_DATA); auto iter = vtkSmartPointer::New(); iter->SetDataSet(data); iter->VisitOnlyLeavesOn(); iter->SkipEmptyNodesOn(); for ( iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem() ) { vtkDataSet* dataset = vtkDataSet::SafeDownCast ( iter->GetCurrentDataObject() ); where |= queryFieldAssociation(fieldName, dataset); } return FieldAssociation(where); } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField ( const word& fieldName, vtkFieldData* fieldData ) { if (!fieldData) { return; } vtkDataArray* input = vtkDataArray::SafeDownCast ( fieldData->GetAbstractArray(fieldName.c_str()) ); if (!input) { return; } const word magFieldName = "mag(" + fieldName + ")"; vtkDataArray* output = vtkDataArray::SafeDownCast ( fieldData->GetAbstractArray(magFieldName.c_str()) ); if (output) { return; } // Simplfy and only handle scalar/vector input const int nCmpt = input->GetNumberOfComponents(); const vtkIdType len = input->GetNumberOfTuples(); if (nCmpt == 1) { auto data = vtkSmartPointer::New(); data->SetName(magFieldName.c_str()); data->SetNumberOfComponents(1); data->SetNumberOfTuples(len); double scratch; for (vtkIdType i=0; i < len; ++i) { input->GetTuple(i, &scratch); scratch = Foam::mag(scratch); data->SetTuple(i, &scratch); } fieldData->AddArray(data); } else if (nCmpt == 3) { auto data = vtkSmartPointer::New(); data->SetName(magFieldName.c_str()); data->SetNumberOfComponents(1); data->SetNumberOfTuples(len); doubleVector scratch; for (vtkIdType i=0; i < len; ++i) { input->GetTuple(i, scratch.v_); scratch.x() = Foam::mag(scratch); data->SetTuple(i, scratch.v_); } fieldData->AddArray(data); } } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField ( const word& fieldName, vtkDataSet* dataset ) { if (dataset) { addMagField(fieldName, dataset->GetCellData()); addMagField(fieldName, dataset->GetPointData()); } } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase::addMagField ( const word& fieldName, vtkCompositeDataSet* data ) { auto iter = vtkSmartPointer::New(); iter->SetDataSet(data); iter->VisitOnlyLeavesOn(); iter->SkipEmptyNodesOn(); for ( iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem() ) { vtkDataSet* dataset = vtkDataSet::SafeDownCast ( iter->GetCurrentDataObject() ); addMagField(fieldName, dataset); } } // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // void Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: fieldSummary::reduce() { if (Pstream::parRun()) { Foam::reduce(nComponents_, maxOp()); Foam::reduce(association_, bitOrOp()); Foam::reduce(range_, minMaxOp()); } } Foam::Ostream& Foam::operator<< ( Ostream& os, const InfoProxy < functionObjects::runTimePostPro::fieldVisualisationBase::fieldSummary >& proxy ) { os << "nComponents:" << proxy.t_.nComponents_ << " association:" << label(proxy.t_.association_) << " min/max:" << proxy.t_.range_; return os; } // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // void Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: setColourMap ( vtkLookupTable* lut ) const { constexpr label nColours = 256; lut->SetNumberOfColors(nColours); auto ctf = vtkSmartPointer::New(); switch (colourMap_) { case cmCoolToWarm: // ParaView: "Cool To Warm" { ctf->SetColorSpaceToDiverging(); ctf->AddRGBPoint(0.0, 0.231372, 0.298039, 0.752941); ctf->AddRGBPoint(0.5, 0.865003, 0.865003, 0.865003); ctf->AddRGBPoint(1.0, 0.705882, 0.0156863, 0.14902); // ctf->SetNanColor(1, 1, 0); break; } case cmColdAndHot: // ParaView : "Cold and Hot" { ctf->SetColorSpaceToRGB(); ctf->AddRGBPoint(0, 0, 1, 1); ctf->AddRGBPoint(0.45, 0, 0, 1); ctf->AddRGBPoint(0.5, 0, 0, 0.5019608); ctf->AddRGBPoint(0.55, 1, 0, 0); ctf->AddRGBPoint(1, 1, 1, 0); break; } case cmFire: // ParaView: Black-Body Radiation { ctf->SetColorSpaceToRGB(); ctf->AddRGBPoint(0, 0, 0, 0); ctf->AddRGBPoint(0.4, 0.901961, 0, 0); ctf->AddRGBPoint(0.8, 0.901961, 0.901961, 0); ctf->AddRGBPoint(1, 1, 1, 1); // ctf->SetNanColor(0, 0.49804, 1); break; } case cmRainbow: { ctf->SetColorSpaceToHSV(); ctf->AddRGBPoint(0, 0, 0, 1); ctf->AddRGBPoint(0.5, 0, 1, 0); ctf->AddRGBPoint(1, 1, 0, 0); // ctf->SetNanColor(0.498039, 0.498039, 0.498039); break; } case cmGreyscale: // ParaView: grayscale { ctf->SetColorSpaceToRGB(); ctf->AddRGBPoint(0, 0, 0, 0); ctf->AddRGBPoint(1, 1, 1, 1); // ctf->SetNanColor(1, 0, 0); break; } case cmXray: // ParaView: "X ray" { ctf->SetColorSpaceToRGB(); ctf->AddRGBPoint(0, 1, 1, 1); ctf->AddRGBPoint(1, 0, 0, 0); // ctf->SetNanColor(1, 0, 0); break; } } double rgba[4] = { 0, 0, 0, 1 }; for (label i = 0; i < nColours; ++i) { ctf->GetColor(scalar(i)/scalar(nColours), rgba); lut->SetTableValue(i, rgba); } } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: addScalarBar ( const scalar position, vtkRenderer* renderer, vtkLookupTable* lut ) const { // Add the scalar bar - only once! if (renderer && Pstream::master()) { scalarBar_.add(colours_["text"]->value(position), renderer, lut); } } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: setField ( const scalar position, const word& colourFieldName, const FieldAssociation fieldAssociation, vtkMapper* mapper, vtkRenderer* renderer ) const { mapper->InterpolateScalarsBeforeMappingOn(); switch (colourBy_) { case cbColour: { mapper->ScalarVisibilityOff(); break; } case cbField: { // Create look-up table for colours auto lut = vtkSmartPointer::New(); setColourMap(lut); lut->SetVectorMode(vtkScalarsToColors::MAGNITUDE); lut->SetTableRange(range_.first(), range_.second()); // Configure the mapper const char* fieldName = colourFieldName.c_str(); mapper->SelectColorArray(fieldName); // Use either point or cell data // - if both point and cell data exists, preferentially choose // point data. This is often the case when using glyphs. if (fieldAssociation & FieldAssociation::POINT_DATA) { mapper->SetScalarModeToUsePointFieldData(); } else if (fieldAssociation & FieldAssociation::CELL_DATA) { mapper->SetScalarModeToUseCellFieldData(); } else { WarningInFunction << "Unable to determine cell or point data type " << "- assuming point data"; mapper->SetScalarModeToUsePointFieldData(); } mapper->SetScalarRange(range_.first(), range_.second()); mapper->SetColorModeToMapScalars(); mapper->SetLookupTable(lut); mapper->ScalarVisibilityOn(); // Add the scalar bar addScalarBar(position, renderer, lut); break; } } mapper->Modified(); } void Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: addGlyphs ( const scalar position, const word& scaleFieldName, const fieldSummary& scaleFieldInfo, const word& colourFieldName, const fieldSummary& colourFieldInfo, const scalar maxGlyphLength, vtkPolyData* data, vtkActor* actor, vtkRenderer* renderer ) const { // Determine whether we have CellData/PointData and (scalar/vector) // or if we need to a cell->point data filter. if (!scaleFieldInfo.exists()) { WarningInFunction << "Cannot add glyphs. No such cell or point field: " << scaleFieldName << endl; return; } if (!scaleFieldInfo.isScalar() && !scaleFieldInfo.isVector()) { WarningInFunction << "Glyphs can only be added to scalar or vector data. " << "Unable to process field " << scaleFieldName << endl; return; } // Setup glyphs // The min/max data range for the input data (cell or point), // which will be slightly less after using a cell->point filter // (since it averages), but is still essentially OK. auto glyph = vtkSmartPointer::New(); glyph->ScalingOn(); auto glyphMapper = vtkSmartPointer::New(); glyphMapper->SetInputConnection(glyph->GetOutputPort()); vtkSmartPointer cellToPoint; // The data source is filtered or original (PointData) if (!scaleFieldInfo.hasPointData() || !colourFieldInfo.hasPointData()) { // CellData - Need a cell->point filter cellToPoint = vtkSmartPointer::New(); cellToPoint->SetInputData(data); glyph->SetInputConnection(cellToPoint->GetOutputPort()); } else { glyph->SetInputData(data); } if (scaleFieldInfo.nComponents_ == 1) { auto sphere = vtkSmartPointer::New(); sphere->SetCenter(0, 0, 0); sphere->SetRadius(0.5); // Setting higher resolution slows the rendering significantly // sphere->SetPhiResolution(20); // sphere->SetThetaResolution(20); glyph->SetSourceConnection(sphere->GetOutputPort()); if (maxGlyphLength > 0) { // Using range from the data: // glyph->SetRange // ( // scaleFieldInfo.range_.first(), // scaleFieldInfo.range_.second() // ); // Set range according to user-supplied limits glyph->ClampingOn(); glyph->SetRange(range_.first(), range_.second()); // If range[0] != min(value), maxGlyphLength behaviour will not // be correct... glyph->SetScaleFactor(maxGlyphLength); } else { glyph->SetScaleFactor(1); } glyph->SetScaleModeToScaleByScalar(); glyph->OrientOff(); glyph->SetColorModeToColorByScalar(); glyph->SetInputArrayToProcess ( 0, // index (0) = scalars 0, // port 0, // connection vtkDataObject::FIELD_ASSOCIATION_POINTS, scaleFieldName.c_str() ); } else if (scaleFieldInfo.nComponents_ == 3) { auto arrow = vtkSmartPointer::New(); arrow->SetTipResolution(10); arrow->SetTipRadius(0.1); arrow->SetTipLength(0.35); arrow->SetShaftResolution(10); arrow->SetShaftRadius(0.03); glyph->SetSourceConnection(arrow->GetOutputPort()); if (maxGlyphLength > 0) { // Set range according data limits glyph->ClampingOn(); glyph->SetRange ( scaleFieldInfo.range_.first(), scaleFieldInfo.range_.second() ); glyph->SetScaleFactor(maxGlyphLength); } else { glyph->SetScaleFactor(1); } glyph->SetScaleModeToScaleByVector(); glyph->OrientOn(); glyph->SetVectorModeToUseVector(); glyph->SetColorModeToColorByVector(); glyph->SetInputArrayToProcess ( 1, // index (1) = vectors 0, // port 0, // connection vtkDataObject::FIELD_ASSOCIATION_POINTS, scaleFieldName.c_str() ); } // Apply colouring etc. // We already established PointData, which as either in the original, // or generated with vtkCellDataToPointData filter. { glyph->Update(); setField ( position, colourFieldName, FieldAssociation::POINT_DATA, // Original or after filter glyphMapper, renderer ); glyphMapper->Update(); actor->SetMapper(glyphMapper); renderer->AddActor(actor); } } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: fieldVisualisationBase ( const dictionary& dict, const HashPtrTable>& colours ) : colours_(colours), fieldName_(dict.get("field")), smooth_(dict.getOrDefault("smooth", false)), colourBy_(cbColour), colourMap_(cmRainbow), range_(), scalarBar_() { colourByTypeNames.readEntry("colourBy", dict, colourBy_); switch (colourBy_) { case cbColour: { scalarBar_.hide(); break; } case cbField: { dict.readEntry("range", range_); colourMapTypeNames.readIfPresent("colourMap", dict, colourMap_); const dictionary* sbar = dict.findDict("scalarBar"); if (sbar) { scalarBar_.read(*sbar); } else { scalarBar_.hide(); } break; } } } // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * // Foam::functionObjects::runTimePostPro::fieldVisualisationBase:: ~fieldVisualisationBase() {} // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * // const Foam::HashPtrTable, Foam::word>& Foam::functionObjects::runTimePostPro::fieldVisualisationBase::colours() const { return colours_; } const Foam::word& Foam::functionObjects::runTimePostPro::fieldVisualisationBase::fieldName() const { return fieldName_; } // ************************************************************************* //