Files
ResInsight/ApplicationLibCode/GeoMech/GeoMechFileInterface/RifVtkReader.cpp
T
Magne Sjaastad fb6a0624c8 VTK: Invert IJK indices to match ResInsight convention
VTK coordinates are negated (z positive downward) to match ResInsight
internal convention, which flips the IJK directions relative to what the
structured grid algorithm expects. Add invertIJK flag to RigFemPartGrid
and RigFemPart so the inversion is applied during struct grid construction.
2026-04-14 14:56:43 +02:00

612 lines
25 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- Equinor ASA
//
// ResInsight 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.
//
// ResInsight 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 at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RifVtkReader.h"
#include "RifVtkImportUtil.h"
#include "RigFemPart.h"
#include "RigFemPartCollection.h"
#include "RigFemResultPosEnum.h"
#include "RigFemTypes.h"
#include "RiaLogging.h"
#include "cafProgressInfo.h"
#include <QString>
#include <map>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RifVtkReader::RifVtkReader()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RifVtkReader::~RifVtkReader()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::close()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifVtkReader::populateDerivedResultNames() const
{
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifVtkReader::openFile( const std::string& fileName, std::string* errorMessage )
{
std::filesystem::path filePath( fileName );
auto dataset = RifVtkImportUtil::parsePvdDatasets( filePath );
if ( dataset.empty() )
{
RiaLogging::error( "No timesteps found" );
m_inputPath.clear();
return false;
}
else
{
RiaLogging::info( QString( "Found %1 timesteps" ).arg( dataset.size() ) );
m_inputPath = filePath;
return true;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifVtkReader::isOpen() const
{
return !m_inputPath.empty();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifVtkReader::readFemParts( RigFemPartCollection* femParts )
{
CVF_ASSERT( femParts );
// The key in the maps is the part ID
std::map<int, std::string> parts;
std::map<int, std::vector<std::pair<int, cvf::Vec3d>>> nodes;
std::map<int, std::vector<std::pair<int, std::vector<int>>>> elements;
std::map<int, std::vector<std::pair<std::string, std::vector<size_t>>>> elementSets;
std::map<int, std::vector<std::map<std::string, std::vector<float>>>> properties;
auto result = read( m_inputPath, parts, nodes, elements, elementSets, properties, m_displacements, m_stepNames );
if ( !result )
{
RiaLogging::error( QString::fromStdString( result.error() ) );
return false;
}
auto elementType = result.value();
if ( m_stepNames.empty() ) m_stepNames.push_back( "Geostatic" );
RiaLogging::debug( QString( "Read FEM parts: %1, steps: %2, element type: %3" )
.arg( parts.size() )
.arg( m_stepNames.size() )
.arg( QString::fromStdString( RigFemTypes::elementTypeText( elementType ) ) ) );
if ( !RigFemTypes::is8NodeElement( elementType ) )
{
RiaLogging::error( QString( "Unsupported element type." ) );
return false;
}
caf::ProgressInfo modelProgress( parts.size() * 2, "Reading Inp Parts" );
for ( const auto& [partId, partName] : parts )
{
modelProgress.setProgressDescription( QString::fromStdString( partName ) + ": Reading Nodes" );
RigFemPart* femPart = new RigFemPart;
femPart->setName( partName );
// Extract nodes
std::vector<std::pair<int, cvf::Vec3d>> partNodes = nodes[partId];
std::map<int, int> nodeIdToIdxMap;
int nodeCount = static_cast<int>( partNodes.size() );
femPart->nodes().nodeIds.resize( nodeCount );
femPart->nodes().coordinates.resize( nodeCount );
for ( int nIdx = 0; nIdx < nodeCount; ++nIdx )
{
auto [nodeId, pos] = partNodes[nIdx];
femPart->nodes().nodeIds[nIdx] = nodeId;
femPart->nodes().coordinates[nIdx].set( pos[0], pos[1], pos[2] );
nodeIdToIdxMap[nodeId] = nIdx;
}
modelProgress.incrementProgress();
modelProgress.setProgressDescription( QString::fromStdString( partName ) + ": Reading Elements" );
// Extract elements
std::vector<std::pair<int, std::vector<int>>> partElements = elements[partId];
int elmCount = static_cast<int>( partElements.size() );
femPart->preAllocateElementStorage( elmCount );
std::vector<int> indexBasedConnectivities;
std::map<int, int> elementIdToIdxMap;
for ( int elmIdx = 0; elmIdx < elmCount; ++elmIdx )
{
auto [elmId, nodesInElement] = partElements[elmIdx];
elementIdToIdxMap[elmId] = elmIdx;
int nodeCount = RigFemTypes::elementNodeCount( elementType );
indexBasedConnectivities.resize( nodeCount );
for ( int lnIdx = 0; lnIdx < nodeCount; ++lnIdx )
{
indexBasedConnectivities[lnIdx] = nodeIdToIdxMap[nodesInElement[lnIdx]];
}
femPart->appendElement( elementType, elmId, indexBasedConnectivities.data() );
}
// read element sets
auto& elementSetsForPart = elementSets[partId];
for ( auto& [setName, elementSet] : elementSetsForPart )
{
femPart->addElementSet( setName, elementSet );
}
// VTK coordinates are negated to match ResInsight internal convention (z positive downward),
// which flips the IJK directions. Invert all so IJK=0 is at the correct corner.
femPart->setInvertIJKInStructGrid( true );
femPart->setElementPartId( femParts->partCount() );
femParts->addFemPart( femPart );
modelProgress.incrementProgress();
}
size_t elementCount = femParts->part( 0 )->elementCount();
readScalarData( femParts, parts, properties, elementCount );
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<RigElementType, std::string>
RifVtkReader::read( const std::filesystem::path& filePath,
std::map<int, std::string>& parts,
std::map<int, std::vector<std::pair<int, cvf::Vec3d>>>& nodes,
std::map<int, std::vector<std::pair<int, std::vector<int>>>>& elements,
std::map<int, std::vector<std::pair<std::string, std::vector<size_t>>>>& elementSets,
std::map<int, std::vector<std::map<std::string, std::vector<float>>>>& properties,
std::map<int, std::vector<std::vector<cvf::Vec3f>>>& displacements,
std::vector<std::string>& stepNames )
{
RigElementType elementType = RigElementType::UNKNOWN_ELM_TYPE;
int partId = 0;
std::string partName;
int stepId = -1;
std::string stepName;
int timeSteps = 0;
auto dataset = RifVtkImportUtil::parsePvdDatasets( filePath );
// Only one part supported
parts[partId] = "Model";
//
elementSets[partId] = {};
bool isFirst = true;
for ( const RifVtkImportUtil::PvdDataset& d : dataset )
{
stepNames.push_back( "Dataset " + std::to_string( d.timestep ) );
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file( d.filepath.string().c_str() );
if ( !result ) return std::unexpected( "File did load: " + d.filepath.string() );
auto root = doc.child( "VTKFile" );
if ( !root ) return std::unexpected( "Missing VTKFile tag: " + d.filepath.string() );
auto grid = root.child( "UnstructuredGrid" );
if ( !grid ) return std::unexpected( "Missing UnstructedGrid tag: " + d.filepath.string() );
auto piece = grid.child( "Piece" );
if ( !piece ) return std::unexpected( "Missing Piece tag: " + d.filepath.string() );
// Read points
std::vector<cvf::Vec3d> vertices = RifVtkImportUtil::readPoints( piece );
if ( vertices.empty() ) return std::unexpected( "No points found: " + d.filepath.string() );
// Read connectivity
std::vector<unsigned> connectivity = RifVtkImportUtil::readConnectivity( piece );
if ( connectivity.empty() ) return std::unexpected( "No connectivity found: " + d.filepath.string() );
// Read connectivity
std::vector<cvf::Vec3f> partDisplacements = RifVtkImportUtil::readDisplacements( piece );
if ( partDisplacements.empty() ) return std::unexpected( "No displacements found: " + d.filepath.string() );
RiaLogging::info( QString( "Found %1 vertices, %2 connectivities, and %3 displacements." )
.arg( vertices.size() )
.arg( connectivity.size() )
.arg( partDisplacements.size() ) );
// Use the geometry from the first time step only.
// The geometry is assumed to not change between time steps.
if ( isFirst )
{
std::vector<std::pair<int, cvf::Vec3d>> partNodes;
for ( int i = 0; i < static_cast<int>( vertices.size() ); i++ )
{
partNodes.push_back( { i, vertices[i] } );
}
nodes[partId] = partNodes;
std::vector<std::pair<int, std::vector<int>>> partElements;
int numNodesPerElement = 8;
int numCells = static_cast<int>( connectivity.size() / numNodesPerElement );
for ( int i = 0; i < numCells; i++ )
{
std::vector<int> els( numNodesPerElement );
for ( int x = 0; x < numNodesPerElement; x++ )
{
els[x] = connectivity[i * numNodesPerElement + x];
}
partElements.emplace_back( i, els );
}
elements[partId] = partElements;
elementType = RigElementType::HEX8P;
}
const std::map<std::string, std::vector<float>> partProperties = RifVtkImportUtil::readProperties( piece );
properties[partId].push_back( partProperties );
displacements[partId].push_back( partDisplacements );
isFirst = false;
}
return elementType;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::string> RifVtkReader::allStepNames() const
{
return m_stepNames;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::string> RifVtkReader::filteredStepNames() const
{
// no filter supported
return RifVtkReader::allStepNames();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RifVtkReader::frameTimes( int stepIndex ) const
{
// only one frame from INP file
std::vector<double> frameValues( { 1.0 } );
return frameValues;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RifVtkReader::frameCount( int stepIndex ) const
{
return static_cast<int>( frameTimes( stepIndex ).size() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::string> RifVtkReader::elementSetNames( int partIndex, std::string partName )
{
if ( partIndex >= (int)m_partElementSetNames.size() ) return {};
return m_partElementSetNames.at( partIndex );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<size_t> RifVtkReader::elementSet( int partIndex, std::string partName, int setIndex )
{
// TODO: not implemented yet
std::vector<size_t> elementIndexes;
return elementIndexes;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<std::string>> RifVtkReader::scalarNodeFieldAndComponentNames()
{
std::map<std::string, std::vector<std::string>> retVal;
for ( auto& entry : m_propertyPartDataNodes )
{
retVal[entry.first] = {};
}
retVal["U"] = { "U1", "U2", "U3" };
return retVal;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<std::string>> RifVtkReader::scalarElementFieldAndComponentNames()
{
std::map<std::string, std::vector<std::string>> retVal;
for ( auto& entry : m_propertyPartDataElements )
{
retVal[entry.first] = {};
}
return retVal;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<std::string>> RifVtkReader::scalarElementNodeFieldAndComponentNames()
{
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<std::string>> RifVtkReader::scalarIntegrationPointFieldAndComponentNames()
{
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readDisplacements( int partIndex, int stepIndex, int frameIndex, std::vector<cvf::Vec3f>* displacements )
{
CVF_ASSERT( displacements );
if ( auto it = m_displacements.find( partIndex ); it != m_displacements.end() )
{
*displacements = it->second[stepIndex];
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readField( RigFemResultPosEnum resultType,
const std::string& fieldName,
int partIndex,
int stepIndex,
std::vector<std::vector<float>*>* resultValues )
{
CVF_ASSERT( resultValues );
auto dataMap = propertyDataMap( resultType );
if ( dataMap == nullptr ) return;
if ( dataMap->count( fieldName ) == 0 ) return;
// is there only a static result? Use it for all steps.
if ( ( *dataMap )[fieldName].count( stepIndex ) == 0 )
{
if ( ( *dataMap )[fieldName].count( -1 ) == 0 ) return;
stepIndex = -1;
}
if ( ( *dataMap )[fieldName][stepIndex].count( partIndex ) == 0 ) return;
auto dataSize = ( *dataMap )[fieldName][stepIndex][partIndex].size();
( *resultValues )[0]->resize( dataSize );
std::vector<float>* singleComponentValues = ( *resultValues )[0];
for ( size_t i = 0; i < dataSize; i++ )
{
( *singleComponentValues )[i] = (float)( *dataMap )[fieldName][stepIndex][partIndex][i];
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readNodeField( const std::string& fieldName,
int partIndex,
int stepIndex,
int frameIndex,
std::vector<std::vector<float>*>* resultValues )
{
CVF_ASSERT( resultValues );
if ( fieldName == "U" )
{
std::vector<cvf::Vec3f> disp;
readDisplacements( partIndex, stepIndex, frameIndex, &disp );
if ( disp.empty() ) return;
size_t n = disp.size();
for ( int comp = 0; comp < 3 && comp < static_cast<int>( resultValues->size() ); comp++ )
{
if ( ( *resultValues )[comp] == nullptr ) continue;
( *resultValues )[comp]->resize( n );
for ( size_t i = 0; i < n; i++ )
{
( *( *resultValues )[comp] )[i] = disp[i][comp];
}
}
return;
}
readField( RigFemResultPosEnum::RIG_NODAL, fieldName, partIndex, stepIndex, resultValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readElementField( const std::string& fieldName,
int partIndex,
int stepIndex,
int frameIndex,
std::vector<std::vector<float>*>* resultValues )
{
readField( RigFemResultPosEnum::RIG_ELEMENT, fieldName, partIndex, stepIndex, resultValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readElementNodeField( const std::string& fieldName,
int partIndex,
int stepIndex,
int frameIndex,
std::vector<std::vector<float>*>* resultValues )
{
readField( RigFemResultPosEnum::RIG_ELEMENT_NODAL, fieldName, partIndex, stepIndex, resultValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readIntegrationPointField( const std::string& fieldName,
int partIndex,
int stepIndex,
int frameIndex,
std::vector<std::vector<float>*>* resultValues )
{
readField( RigFemResultPosEnum::RIG_INTEGRATION_POINT, fieldName, partIndex, stepIndex, resultValues );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RifVtkReader::readScalarData( RigFemPartCollection* femParts,
std::map<int, std::string>& parts,
const std::map<int, std::vector<std::map<std::string, std::vector<float>>>>& properties,
size_t numElements )
{
for ( auto [partId, partName] : parts )
{
if ( auto partProperties = properties.find( partId ); partProperties != properties.end() )
{
int stepId = 0;
for ( const auto& propertiesPerTimeStep : partProperties->second )
{
for ( const auto& [propertyName, values] : propertiesPerTimeStep )
{
RigFemResultPosEnum resultType = values.size() == numElements ? RigFemResultPosEnum::RIG_ELEMENT
: RigFemResultPosEnum::RIG_NODAL;
// TODO: handle other result types...
if ( resultType == RigFemResultPosEnum::RIG_ELEMENT )
{
auto map = propertyDataMap( resultType );
if ( map == nullptr ) continue;
if ( map->count( propertyName ) == 0 )
{
( *map )[propertyName] = {};
}
if ( ( *map )[propertyName].count( stepId ) == 0 )
{
( *map )[propertyName][stepId] = {};
}
for ( int partId = 0; partId < femParts->partCount(); partId++ )
{
( *map )[propertyName][stepId][partId] = {};
size_t dataSize = 0;
if ( resultType == RigFemResultPosEnum::RIG_NODAL )
{
dataSize = femParts->part( partId )->nodeCount();
}
if ( resultType == RigFemResultPosEnum::RIG_ELEMENT )
{
dataSize = femParts->part( partId )->elementCount();
}
std::vector<double> valuesAsDouble( dataSize, 0.0 );
// TODO: fix annoying conversion from float to double
for ( size_t i = 0; i < dataSize; i++ )
{
valuesAsDouble[i] = values[i];
}
( *map )[propertyName][stepId][partId] = valuesAsDouble;
}
}
}
stepId++;
}
}
}
}
//--------------------------------------------------------------------------------------------------
/// Map keys: result name / time step / part id
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::map<int, std::map<int, std::vector<double>>>>* RifVtkReader::propertyDataMap( RigFemResultPosEnum resultType )
{
if ( resultType == RigFemResultPosEnum::RIG_ELEMENT ) return &m_propertyPartDataElements;
if ( resultType == RigFemResultPosEnum::RIG_NODAL ) return &m_propertyPartDataNodes;
return nullptr;
}