mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-27 15:41:00 -05:00
The ensemble file set removed the file extension by searching for the last dot in the complete path. When the path pattern was already without extension, the last dot was found in a folder name, truncating the path pattern and causing the search for SMSPEC files to fail. Add RiaFilePathTools::removeFileExtension based on std::filesystem::path::replace_extension, and use it both in RimEnsembleFileSet and in the grid and summary ensemble import, replacing a duplicated lambda doing the same operation.
490 lines
19 KiB
C++
490 lines
19 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",
|
|
"",
|
|
"Specify realization numbers. Example: 1-5, 8, 11-20, !4 will include 1, 2, 3, 5, 8, 11-20" );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_ensembleInfo, "EnsembleInfo", "Info" );
|
|
m_ensembleInfo.registerGetMethod( this, &RimEnsembleFileSet::ensembleInfo );
|
|
m_ensembleInfo.uiCapability()->setUiReadOnly( true );
|
|
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() );
|
|
|
|
// The file paths can be given with or without a file extension. Remove the extension if present, without
|
|
// truncating a folder name containing a dot.
|
|
auto noExtension = RiaFilePathTools::removeFileExtension( pattern );
|
|
|
|
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-10,!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";
|
|
menuBuilder << "RicCreateReservoirGridEnsembleFromFileSetFeature";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
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();
|
|
}
|