From 597a7775d969cb35776aef9e2f5e643fd33d0d68 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Fri, 16 Oct 2026 08:53:06 +0200 Subject: [PATCH] #8370: Address review comments Keep original additional summary files on Save As, reject cases combined with ensembles, keep unrestored additional file mappings, support growing file sets, update short names on ensemble import, simplify aggregator and example. --- .../RifSummaryReaderAggregator.cpp | 8 +-- .../Ensemble/RimSummaryFileSetEnsemble.cpp | 54 ++++++++++++++----- .../Summary/RimFileSummaryCase.cpp | 6 ++- .../ProjectDataModelCommands/RimcProject.cpp | 5 ++ .../RimcSummaryMultiPlot.cpp | 5 ++ .../RimcSummaryPlotCollection.cpp | 14 ++++- .../multiple_ensemble_summary_plot.py | 2 +- 7 files changed, 73 insertions(+), 21 deletions(-) diff --git a/ApplicationLibCode/FileInterface/RifSummaryReaderAggregator.cpp b/ApplicationLibCode/FileInterface/RifSummaryReaderAggregator.cpp index 962fac1127..07d42f6b7f 100644 --- a/ApplicationLibCode/FileInterface/RifSummaryReaderAggregator.cpp +++ b/ApplicationLibCode/FileInterface/RifSummaryReaderAggregator.cpp @@ -52,9 +52,11 @@ std::pair> RifSummaryReaderAggregator::values( const R { auto [isOk, readerValues] = reader->values( resultAddress ); - if ( !readerValues.empty() ) anyReaderHasValues = true; - - if ( readerValues.empty() ) + if ( !readerValues.empty() ) + { + anyReaderHasValues = true; + } + else { // When a well is introduced, no data is present before the time step the well is introduced // Add values of zero for this interval diff --git a/ApplicationLibCode/ProjectDataModel/Summary/Ensemble/RimSummaryFileSetEnsemble.cpp b/ApplicationLibCode/ProjectDataModel/Summary/Ensemble/RimSummaryFileSetEnsemble.cpp index dc8540369f..123f74a385 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/Ensemble/RimSummaryFileSetEnsemble.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/Ensemble/RimSummaryFileSetEnsemble.cpp @@ -63,6 +63,35 @@ QString commonHeaderFolder( const std::vector& fileCases ) return folder; } + +// Path of a summary header file relative to the common folder. A single realization has no distinguishing folder, so the parent folder +// name is included to allow a unique suffix match if realizations are added to the file set later. +QString headerStorageKey( const QString& header, const QString& commonFolder ) +{ + QString key = header.mid( commonFolder.size() ); + if ( !key.contains( '/' ) ) + { + const int lastSeparator = header.lastIndexOf( '/' ); + const int prevSeparator = lastSeparator > 0 ? header.lastIndexOf( '/', lastSeparator - 1 ) : -1; + if ( prevSeparator >= 0 ) key = header.mid( prevSeparator + 1 ); + } + return key; +} + +std::map parseAdditionalSummaryFiles( const QString& text ) +{ + std::map additionalFileForHeader; + for ( const auto& line : text.split( '\n', Qt::SkipEmptyParts ) ) + { + // The additional file name is generated and does not contain separator characters + auto separatorIndex = line.lastIndexOf( '|' ); + if ( separatorIndex > 0 ) + { + additionalFileForHeader[line.left( separatorIndex )] = line.mid( separatorIndex + 1 ); + } + } + return additionalFileForHeader; +} } // namespace internal //-------------------------------------------------------------------------------------------------- @@ -250,13 +279,21 @@ void RimSummaryFileSetEnsemble::storeAdditionalSummaryFiles() const QString commonFolder = internal::commonHeaderFolder( fileCases ); - QStringList lines; + // Keep entries that could not be restored (for example when the additional files were not copied with the project), so the mapping + // is not lost on the next save + auto entries = internal::parseAdditionalSummaryFiles( m_additionalSummaryFiles() ); for ( auto fileSummaryCase : fileCases ) { QString additionalFile = fileSummaryCase->additionalSummaryFilePath(); if ( additionalFile.isEmpty() ) continue; - lines.push_back( fileSummaryCase->summaryHeaderFilename().mid( commonFolder.size() ) + "|" + QFileInfo( additionalFile ).fileName() ); + entries[internal::headerStorageKey( fileSummaryCase->summaryHeaderFilename(), commonFolder )] = QFileInfo( additionalFile ).fileName(); + } + + QStringList lines; + for ( const auto& [key, additionalFileName] : entries ) + { + lines.push_back( key + "|" + additionalFileName ); } m_additionalSummaryFiles = lines.join( '\n' ); @@ -269,16 +306,7 @@ void RimSummaryFileSetEnsemble::restoreAdditionalSummaryFiles( const std::vector { if ( m_additionalSummaryFiles().isEmpty() ) return; - std::map additionalFileForHeader; - for ( const auto& line : m_additionalSummaryFiles().split( '\n', Qt::SkipEmptyParts ) ) - { - // The additional file name is generated and does not contain separator characters - auto separatorIndex = line.lastIndexOf( '|' ); - if ( separatorIndex > 0 ) - { - additionalFileForHeader[line.left( separatorIndex )] = line.mid( separatorIndex + 1 ); - } - } + const auto additionalFileForHeader = internal::parseAdditionalSummaryFiles( m_additionalSummaryFiles() ); std::vector fileCases; for ( auto summaryCase : summaryCases ) @@ -302,7 +330,7 @@ void RimSummaryFileSetEnsemble::restoreAdditionalSummaryFiles( const std::vector // stored path to avoid losing the user-defined vectors. auto findAdditionalFile = [&]( const QString& header ) -> QString { - auto it = additionalFileForHeader.find( header.mid( commonFolder.size() ) ); + auto it = additionalFileForHeader.find( internal::headerStorageKey( header, commonFolder ) ); if ( it != additionalFileForHeader.end() ) return it->second; QString match; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp index f39dfc8e3c..3bbfdcae3c 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimFileSummaryCase.cpp @@ -478,7 +478,8 @@ void RimFileSummaryCase::setAdditionalSummaryFilePath( const QString& filePath ) //-------------------------------------------------------------------------------------------------- void RimFileSummaryCase::onProjectBeingSaved() { - // If additional data is stored in temp folder, copy to project folder and remove file in temp folder. The project file is not written + // If additional data is stored in temp folder, copy to project folder and remove file in temp folder. Files in other folders are + // copied and kept, so a project saved with Save As does not lose the data of the original project. The project file is not written // yet, so additionalSummaryDataFilePath() would still point to the temp folder on the first save. const QString projectFileName = RimProject::current() ? RimProject::current()->fileName() : QString(); if ( projectFileName.isEmpty() ) return; @@ -493,7 +494,8 @@ void RimFileSummaryCase::onProjectBeingSaved() if ( QFile::copy( existingFilePath, currentFilePath ) ) { - QFile::remove( existingFilePath ); + const bool isInTempFolder = QFileInfo( existingFilePath ).absoluteDir() == QDir( QDir::tempPath() ); + if ( isInTempFolder ) QFile::remove( existingFilePath ); m_additionalSummaryFilePath = currentFilePath; } else diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp index fc43a526a3..650e285c01 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp @@ -175,6 +175,11 @@ std::expected RimProject_importSummaryEnsemble:: auto ensemble = mainCollection->addEnsemble( newCases, m_ensembleName(), true ); + for ( auto summaryCase : ensemble->allSummaryCases() ) + { + summaryCase->updateAutoShortName(); + } + // The name template is only used when auto naming is disabled if ( !m_ensembleName().isEmpty() ) ensemble->setAutoName( false ); RiaSummaryTools::updateSummaryEnsembleNames(); diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp index d359192132..a491a3f206 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryMultiPlot.cpp @@ -55,6 +55,11 @@ std::expected RimcSummaryMultiPlot_appendSubplot auto multiPlot = self(); + if ( !m_summaryCases().empty() && !m_ensembles().empty() ) + { + return std::unexpected( "Specify either summary cases or ensembles, not both." ); + } + RimSummaryPlot* newPlot = RimcSummaryPlotCollection_newSummaryPlot::createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), m_ensembles.ptrReferencedObjectsByType(), m_addressString() ); diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp index aafa428e12..c53cdbb02f 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcSummaryPlotCollection.cpp @@ -62,8 +62,13 @@ RimcSummaryPlotCollection_newSummaryPlot::RimcSummaryPlotCollection_newSummaryPl //-------------------------------------------------------------------------------------------------- std::expected RimcSummaryPlotCollection_newSummaryPlot::execute() { - RimSummaryPlot* newPlot = - createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), allEnsembles( m_ensemble(), m_ensembles ), m_addressString() ); + auto ensembles = allEnsembles( m_ensemble(), m_ensembles ); + if ( !m_summaryCases().empty() && !ensembles.empty() ) + { + return std::unexpected( "Specify either summary cases or ensembles, not both." ); + } + + RimSummaryPlot* newPlot = createSummaryPlot( m_summaryCases.ptrReferencedObjectsByType(), ensembles, m_addressString() ); // Plots created without an address are already added to a multi plot if ( newPlot && !newPlot->firstAncestorOrThisOfType() ) @@ -173,6 +178,11 @@ std::expected RimcSummaryPlotCollection_newSumma { if ( m_addressStrings().empty() ) return std::unexpected( "Specify at least one address." ); + if ( !m_summaryCases().empty() && !m_ensembles().empty() ) + { + return std::unexpected( "Specify either summary cases or ensembles, not both." ); + } + // A subplot without an address is created in a separate multi plot for ( const QString& address : m_addressStrings() ) { diff --git a/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/multiple_ensemble_summary_plot.py b/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/multiple_ensemble_summary_plot.py index 7b15b51a7a..080f36cd24 100644 --- a/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/multiple_ensemble_summary_plot.py +++ b/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/multiple_ensemble_summary_plot.py @@ -23,7 +23,7 @@ summary_plot_collection = project.summary_plot_collection() plot = summary_plot_collection.new_summary_plot(ensembles=ensembles, address="FOPT") # Append more subplots to the multi plot containing the first subplot -multi_plot = project.descendants(rips.MultiSummaryPlot)[-1] +multi_plot = plot.ancestor(rips.MultiSummaryPlot) multi_plot.append_subplot(ensembles=ensembles, address="FWPT") multi_plot.append_subplot(ensembles=ensembles, address="FGPT")