Files
ResInsight/ApplicationLibCode/ProjectDataModel/EnsembleFileSet/RimEnsembleFileSet.cpp
T
Magne Sjaastad e455011199 #12808 Improves ensemble data handling and error reporting
Enhances the robustness of ensemble data processing by:

- Displaying a tooltip when data is missing for a realization.
- Checks if an enhanced summary file exists before attempting to open it.
- Refines the creation of the realization filter to accurately represent the range of available data.
2025-08-22 06:47:07 +02:00

489 lines
18 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 "RimEnsembleFileSet.h"
#include "Ensemble/RiaEnsembleImportTools.h"
#include "RiaEnsembleNameTools.h"
#include "RiaFilePathTools.h"
#include "RiaStdStringTools.h"
#include "RiaTextStringTools.h"
#include "RimEnsembleFileSetCollection.h"
#include "RimProject.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmUiComboBoxEditor.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiTextEditor.h"
CAF_PDM_SOURCE_INIT( RimEnsembleFileSet, "EnsembleFileSet" );
namespace internal
{
QString placeholderString()
{
return "*";
}
bool isEmptyOrWildcard( const QString& pattern )
{
return pattern.isEmpty() || pattern.contains( placeholderString() );
}
} // namespace internal
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimEnsembleFileSet::RimEnsembleFileSet()
: fileSetChanged( this )
, nameChanged( this )
, m_useKey1( false )
, m_useKey2( false )
{
CAF_PDM_InitObject( "Ensemble", ":/SummaryEnsemble.svg", "", "" );
CAF_PDM_InitField( &m_pathPattern, "PathPattern", QString(), "Path Pattern", "", "", "" );
CAF_PDM_InitField( &m_realizationSubSet, "RealizationSubSet", QString(), "Realization Filter", "", "", "" );
CAF_PDM_InitFieldNoDefault( &m_ensembleInfo, "EnsembleInfo", "Info" );
m_ensembleInfo.registerGetMethod( this, &RimEnsembleFileSet::ensembleInfo );
m_ensembleInfo.uiCapability()->setUiReadOnly( true );
m_ensembleInfo.xmlCapability()->disableIO();
m_ensembleInfo.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_groupingMode, "GroupingMode", "Grouping Mode" );
CAF_PDM_InitScriptableField( &m_autoName, "CreateAutoName", true, "Auto Name" );
QString defaultText = RiaDefines::key1VariableName() + "-" + RiaDefines::key2VariableName();
QString tooltipText = QString( "Variables in template is supported, and will be replaced to create name. Example '%1'" ).arg( defaultText );
CAF_PDM_InitField( &m_nameTemplateString, "NameTemplateString", defaultText, "Name Template", "", tooltipText );
nameField()->uiCapability()->setUiReadOnly( true );
nameField()->xmlCapability()->disableIO();
setDeletable( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QStringList RimEnsembleFileSet::createPaths( const QString& extension ) const
{
if ( m_pathPattern().isEmpty() ) return {};
// Append extension to the path pattern and return list of files matching the pattern
QString pathPatternWithExtension = m_pathPattern() + extension;
auto realizationFilter = m_realizationSubSet();
if ( internal::isEmptyOrWildcard( realizationFilter ) )
{
if ( m_realizationNumbersReadFromFiles.isEmpty() )
{
QStringList paths;
if ( m_groupingMode() == RiaDefines::EnsembleGroupingMode::RESINSIGHT_OPMFLOW_STRUCTURE )
{
paths = RiaEnsembleImportTools::createPathsBySearchingFileSystem_obsolete( pathPatternWithExtension,
internal::placeholderString(),
"run" );
}
else
{
paths = RiaEnsembleImportTools::createPathsBySearchingFileSystem( m_pathPattern(), extension, internal::placeholderString() );
}
const auto [pattern, range] = RiaEnsembleImportTools::findPathPattern( paths, internal::placeholderString() );
m_realizationNumbersReadFromFiles = range;
}
auto numbers = RiaStdStringTools::valuesFromRangeSelection( m_realizationNumbersReadFromFiles.toStdString() );
if ( !numbers.empty() )
{
auto minNumber = *std::min_element( numbers.begin(), numbers.end() );
auto maxNumber = *std::max_element( numbers.begin(), numbers.end() );
// Use the min and max numbers to create a realization filter, as this will the wildcard pattern. This makes it easy
// to see the realizations with missing or incomplete data
realizationFilter = QString( "%1-%2" ).arg( minNumber ).arg( maxNumber );
}
else
{
realizationFilter = m_realizationNumbersReadFromFiles;
}
}
return RiaEnsembleImportTools::createPathsFromPattern( pathPatternWithExtension, realizationFilter, internal::placeholderString() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::findAndSetPathPatternAndRangeString( const QStringList& filePaths )
{
QStringList normalizedPaths;
for ( const auto& path : filePaths )
{
normalizedPaths.append( RiaFilePathTools::toInternalSeparator( path ) );
}
const auto& [pattern, rangeString] = RiaEnsembleImportTools::findPathPattern( normalizedPaths, internal::placeholderString() );
// find the pattern without extension by finding . and remove rest of string
auto noExtension = pattern;
auto dotIndex = noExtension.lastIndexOf( '.' );
if ( dotIndex != -1 )
{
noExtension = noExtension.left( dotIndex );
}
m_pathPattern = noExtension;
m_realizationNumbersReadFromFiles = rangeString;
// When all files in an ensemble are selected, we could get the same behaviour if set the range string to a wildcard. Avoid this now, as
// the performance is worse using a wildcard.
// https://github.com/OPM/ResInsight/issues/12660
// https://github.com/OPM/ResInsight/issues/12663
m_realizationSubSet = rangeString;
sendFileSetChangedSignal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimEnsembleFileSet::pathPattern() const
{
return m_pathPattern;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setNameTemplate( const QString& name )
{
m_nameTemplateString = name;
nameChanged.send();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::updateName( const std::set<QString>& existingEnsembleNames )
{
const auto [key1, key2] = nameKeys();
QString templateText;
if ( m_autoName )
{
templateText = nameTemplateText();
}
else
{
templateText = m_nameTemplateString();
}
std::map<QString, QString> keyValues = {
{ RiaDefines::key1VariableName(), QString::fromStdString( key1 ) },
{ RiaDefines::key2VariableName(), QString::fromStdString( key2 ) },
};
auto candidateName = RiaTextStringTools::replaceTemplateTextWithValues( templateText, keyValues );
if ( m_autoName )
{
candidateName = candidateName.trimmed();
// When using auto name, remove leading and trailing commas that may occur if key1 or key2 is empty
if ( candidateName.startsWith( "," ) )
{
candidateName = candidateName.mid( 1 );
}
if ( candidateName.endsWith( "," ) )
{
candidateName = candidateName.left( candidateName.length() - 1 );
}
auto generateUniqueName = []( const QString& baseName, const std::set<QString>& existingNames )
{
// Avoid identical ensemble names by appending a number
QString uniqueName = baseName;
int counter = 1;
while ( existingNames.find( uniqueName ) != existingNames.end() )
{
uniqueName = QString( "%1_%2" ).arg( baseName ).arg( counter++ );
}
return uniqueName;
};
candidateName = generateUniqueName( candidateName, existingEnsembleNames );
}
if ( name() == candidateName ) return;
setName( candidateName );
nameChanged.send();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setUsePathKey1( bool useKey1 )
{
m_useKey1 = useKey1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setUsePathKey2( bool useKey2 )
{
m_useKey2 = useKey2;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setAutoName( bool autoName )
{
m_autoName = autoName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_pathPattern )
{
if ( auto lineEdAttr = dynamic_cast<caf::PdmUiLineEditorAttribute*>( attribute ) )
{
lineEdAttr->placeholderText = "Enter path pattern...";
}
}
else if ( field == &m_realizationSubSet )
{
if ( auto lineEdAttr = dynamic_cast<caf::PdmUiLineEditorAttribute*>( attribute ) )
{
lineEdAttr->placeholderText = "E.g. 0,1,4-6. Use '*' for all.";
}
}
else if ( field == &m_ensembleInfo )
{
if ( auto* myAttr = dynamic_cast<caf::PdmUiTextEditorAttribute*>( attribute ) )
{
myAttr->heightHint = -1;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_autoName );
if ( !m_autoName() )
{
uiOrdering.add( &m_nameTemplateString );
}
uiOrdering.add( nameField() );
uiOrdering.add( &m_pathPattern );
uiOrdering.add( &m_realizationSubSet );
uiOrdering.add( &m_ensembleInfo );
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
if ( changedField == &m_nameTemplateString || changedField == &m_autoName )
{
RimProject::current()->ensembleFileSetCollection()->updateFileSetNames();
return;
}
sendFileSetChangedSignal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
menuBuilder << "RicCreateEnsembleFromFileSetFeature";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setPathPattern( const QString& pathPattern )
{
m_pathPattern = pathPattern;
m_realizationNumbersReadFromFiles.clear();
sendFileSetChangedSignal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setRangeString( const QString& rangeString )
{
sendFileSetChangedSignal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimEnsembleFileSet::ensembleInfo() const
{
QString text = "Realization numbers detected on file system : " + m_realizationNumbersReadFromFiles;
return text;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<std::string, std::string> RimEnsembleFileSet::nameKeys() const
{
std::string key1 = "Undefined KEY1";
std::string key2 = "Undefined KEY2";
if ( m_groupingMode() == RiaDefines::EnsembleGroupingMode::FMU_FOLDER_STRUCTURE )
{
auto pathPattern = m_pathPattern + ".SMSPEC";
auto paths = RiaEnsembleImportTools::createPathsFromPattern( pathPattern, "0", internal::placeholderString() );
if ( !paths.empty() )
{
auto fileNames = RiaEnsembleNameTools::groupFilePathsFmu( { paths.front().toStdString() } );
if ( !fileNames.empty() )
{
key1 = fileNames.begin()->first.first;
key2 = fileNames.begin()->first.second;
}
}
}
else if ( m_groupingMode() == RiaDefines::EnsembleGroupingMode::EVEREST_FOLDER_STRUCTURE )
{
auto pathPattern = m_pathPattern + ".SMSPEC";
auto paths = RiaEnsembleImportTools::createPathsFromPattern( pathPattern, "0-1", internal::placeholderString() );
if ( paths.size() > 1 )
{
auto name1 = RiaFilePathTools::toInternalSeparator( paths[0] ).toStdString();
auto name2 = RiaFilePathTools::toInternalSeparator( paths[1] ).toStdString();
auto parts1 = RiaStdStringTools::splitString( name1, '/' );
auto parts2 = RiaStdStringTools::splitString( name2, '/' );
size_t commonParts = 0;
for ( size_t i = 0; i < std::min( parts1.size(), parts2.size() ); i++ )
{
if ( parts1[i] == parts2[i] )
{
commonParts++;
}
else
{
break;
}
}
if ( commonParts == 1 )
{
key2 = parts1[commonParts - 1];
}
else if ( commonParts > 1 )
{
key1 = parts1[commonParts - 2];
key2 = parts1[commonParts - 1];
}
}
}
else if ( m_groupingMode() == RiaDefines::EnsembleGroupingMode::RESINSIGHT_OPMFLOW_STRUCTURE )
{
auto pathPattern = m_pathPattern + ".SMSPEC";
auto paths = RiaEnsembleImportTools::createPathsFromPattern( pathPattern, "0", internal::placeholderString() );
if ( !paths.empty() )
{
auto fileNames = RiaEnsembleNameTools::groupFilePathsOpm( { paths.front().toStdString() } );
if ( !fileNames.empty() )
{
key1 = fileNames.begin()->first.first;
key2 = fileNames.begin()->first.second;
}
}
}
return { key1, key2 };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimEnsembleFileSet::nameTemplateText() const
{
QString text;
if ( m_useKey1 ) text += RiaDefines::key1VariableName();
if ( m_useKey2 )
{
if ( !text.isEmpty() ) text += ", ";
text += RiaDefines::key2VariableName();
}
return text;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::setGroupingMode( RiaDefines::EnsembleGroupingMode groupingMode )
{
m_groupingMode = groupingMode;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiaDefines::EnsembleGroupingMode RimEnsembleFileSet::groupingMode() const
{
return m_groupingMode();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::sendFileSetChangedSignal() const
{
fileSetChanged.send();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimEnsembleFileSet::reload()
{
m_realizationNumbersReadFromFiles.clear();
sendFileSetChangedSignal();
}