Files
ResInsight/ApplicationLibCode/FileInterface/RifVtkImportUtil.cpp
T
hnilandClaude Opus 5.5 d7b3e60520 VTK GeoMech: pore pressure from node data, derived results
Read the VTK point data, not only "disp" (now found by name, not
position). "pressure" becomes the nodal pore pressure POR, other scalar
point arrays keep their name. OPM Flow writes them with
--vtk-write-geo-mech-nodal, recovered from its cell values.

Stress and strain are cell values in the simulator, so S and E at
element-nodal (and integration-point) position give every element corner
its cell's value. S is effective stress, tension positive, while OPM
writes total stress, compression positive: S = POR * I - stress at each
corner, so ST = -S + POR returns the simulator's stress exactly.

With S present, populateDerivedResultNames() returns true, so SE, ST,
principal stresses, SFI, DSM, FOS, NE etc. are available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:59:38 +02:00

286 lines
9.9 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 "RifVtkImportUtil.h"
#include "RiaStdStringTools.h"
#include "fast_float/fast_float.h"
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
#include <version>
// std::ispanstream is C++23. libc++ on Homebrew llvm@19 does not ship
// <spanstream>; fall back to std::istringstream when unavailable. Only
// difference: the fallback copies the string_view into an owning string.
// Use the feature-test macro from <version> rather than __has_include,
// since the header may be present without a usable implementation.
// See #14045.
#if defined( __cpp_lib_spanstream ) && __cpp_lib_spanstream >= 202106L
#include <spanstream>
#define RIA_HAS_STD_SPANSTREAM 1
#else
#include <sstream>
#define RIA_HAS_STD_SPANSTREAM 0
#endif
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::Vec3d> RifVtkImportUtil::readPoints( const pugi::xml_node& piece )
{
auto points = piece.child( "Points" );
if ( !points ) return {};
auto coords = points.child( "DataArray" );
if ( !coords || std::string( coords.attribute( "Name" ).value() ) != "Coordinates" ) return {};
std::string_view text = coords.text().get();
std::vector<cvf::Vec3d> vertices = parseVec3ds( text );
// Update to internal z direction
for ( cvf::Vec3d& v : vertices )
v.z() = -v.z();
return vertices;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::Vec3f> RifVtkImportUtil::readDisplacements( const pugi::xml_node& piece )
{
auto pointData = piece.child( "PointData" );
if ( !pointData ) return {};
auto dataArray = pointData.find_child_by_attribute( "DataArray", "Name", "disp" );
if ( !dataArray ) return {};
std::string_view text = dataArray.text().get();
std::vector<cvf::Vec3f> displacements = parseVec3fs( text );
for ( cvf::Vec3f& d : displacements )
d.z() = -d.z();
return displacements;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<unsigned> RifVtkImportUtil::readConnectivity( const pugi::xml_node& piece )
{
auto cells = piece.child( "Cells" );
if ( !cells ) return {};
std::vector<unsigned> connectivity;
auto connectivityArray = cells.child( "DataArray" );
while ( connectivityArray )
{
if ( std::string( connectivityArray.attribute( "Name" ).value() ) == "connectivity" )
{
std::string connectivityText = connectivityArray.text().get();
std::istringstream iss( connectivityText );
unsigned index;
while ( iss >> index )
{
connectivity.push_back( index );
}
break;
}
connectivityArray = connectivityArray.next_sibling( "DataArray" );
}
return connectivity;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<float>> RifVtkImportUtil::readProperties( const pugi::xml_node& piece )
{
return readDataArrays( piece.child( "CellData" ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<float>> RifVtkImportUtil::readPointProperties( const pugi::xml_node& piece )
{
return readDataArrays( piece.child( "PointData" ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<std::string, std::vector<float>> RifVtkImportUtil::readDataArrays( const pugi::xml_node& data )
{
if ( !data ) return {};
std::map<std::string, std::vector<float>> properties;
for ( const auto& dataArray : data.children( "DataArray" ) )
{
const char* name = dataArray.attribute( "Name" ).value();
if ( name && *name )
{
std::vector<float> values;
const char* text = dataArray.text().get();
const char* ptr = text;
const char* textEnd = text + std::strlen( text );
while ( *ptr != '\0' )
{
// Skip whitespace
while ( *ptr == ' ' || *ptr == '\t' || *ptr == '\n' || *ptr == '\r' )
++ptr;
if ( *ptr == '\0' ) break;
// Check for NaN (case-insensitive). ptr[3] is safe because text is null-terminated,
// so if ptr points to the last three chars "nan", ptr[3] == '\0' which matches the condition.
if ( ( *ptr == 'n' || *ptr == 'N' ) && ( ptr[1] == 'a' || ptr[1] == 'A' ) && ( ptr[2] == 'n' || ptr[2] == 'N' ) &&
( ptr[3] == '\0' || ptr[3] == ' ' || ptr[3] == '\t' || ptr[3] == '\n' || ptr[3] == '\r' ) )
{
values.push_back( std::numeric_limits<float>::quiet_NaN() );
ptr += 3;
continue;
}
// Parse float directly using fast_float
float fValue = 0.0f;
auto result = fast_float::from_chars( ptr, textEnd, fValue );
auto* parsedEndPtr = result.ptr;
if ( result.ec != std::errc() || parsedEndPtr == ptr )
{
// Failed to parse
return {};
}
values.push_back( fValue );
ptr = parsedEndPtr;
}
if ( !values.empty() )
{
properties[name] = std::move( values );
}
}
}
return properties;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RifVtkImportUtil::PvdDataset> RifVtkImportUtil::parsePvdDatasets( const std::filesystem::path& filepath )
{
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file( filepath.string().c_str() );
if ( !result ) return {};
auto root = doc.child( "VTKFile" );
if ( !root ) return {};
auto collection = root.child( "Collection" );
if ( !collection ) return {};
std::filesystem::path baseDir = filepath.parent_path();
std::vector<PvdDataset> datasets;
for ( const auto& datasetElem : collection.children( "DataSet" ) )
{
const char* file = datasetElem.attribute( "file" ).value();
const char* timestepStr = datasetElem.attribute( "timestep" ).value();
if ( file && timestepStr )
{
double timestep = std::stod( timestepStr );
std::filesystem::path fullPath = std::filesystem::absolute( baseDir / file );
datasets.push_back( { timestep, fullPath } );
}
}
return datasets;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::Vec3d> RifVtkImportUtil::parseVec3ds( std::string_view text )
{
#if RIA_HAS_STD_SPANSTREAM
std::ispanstream iss( text );
#else
std::istringstream iss{ std::string{ text } };
#endif
std::vector<cvf::Vec3d> vecs;
std::string xStr, yStr, zStr;
while ( iss >> xStr >> yStr >> zStr )
{
double x, y, z;
if ( !RiaStdStringTools::toDouble( xStr, x ) || !RiaStdStringTools::toDouble( yStr, y ) || !RiaStdStringTools::toDouble( zStr, z ) )
{
return {};
}
vecs.emplace_back( x, y, z );
}
return vecs;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::Vec3f> RifVtkImportUtil::parseVec3fs( std::string_view text )
{
#if RIA_HAS_STD_SPANSTREAM
std::ispanstream iss( text );
#else
std::istringstream iss{ std::string{ text } };
#endif
std::vector<cvf::Vec3f> vecs;
std::string xStr, yStr, zStr;
while ( iss >> xStr >> yStr >> zStr )
{
double x, y, z;
if ( !RiaStdStringTools::toDouble( xStr, x ) || !RiaStdStringTools::toDouble( yStr, y ) || !RiaStdStringTools::toDouble( zStr, z ) )
{
return {};
}
vecs.emplace_back( static_cast<float>( x ), static_cast<float>( y ), static_cast<float>( z ) );
}
return vecs;
}