mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 23:20:29 -05:00
[ri-cloud-api]: Add grid data from sumo using api (#14653)
Add grid data from Sumo using ri-cloud-api Load Eclipse grid data (roff) from Sumo as RimReservoirGridEnsemble, one case per realization, with async fetch of grid properties per timestep. - New: RiaSumoGrid (transport), RifReaderSumoGridProperty (lazy property reader), RimReservoirGridEnsembleSumo, RimRoffCaseSumo, and a "Create Reservoir Grid Ensemble" command. - Shared grid: IJK dimensions compared before download; one RigMainGrid is reused across the ensemble when they match, each realization still parses its own ACTNUM before releasing geometry. - Rename folders: Summary/Sumo -> Summary/Cloud, RimSumoDataSource -> Cloud/ (PDM keywords unchanged, old project files still load). - UI: cloud data sources also shown in the 3D window's Data Sources dock. - Fix: realizations no longer lost on project reload; realizations missing data are now reported instead of silently skipped.
This commit is contained in:
1 parent
71c5b5a97d
commit
c49f298a7d
70 files changed
+5194
-222
No files matched your search
@@ -1,5 +1,6 @@
|
||||
set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimCloudDataSourceCollection.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSumoDataSource.cpp
|
||||
)
|
||||
|
||||
list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES})
|
||||
@@ -18,16 +18,18 @@
|
||||
|
||||
#include "RimCloudDataSourceCollection.h"
|
||||
|
||||
#include "RiaLogging.h"
|
||||
|
||||
#include "Cloud/RiaCloudApiService.h"
|
||||
#include "RiaApplication.h"
|
||||
#include "Summary/RiaSummaryPlotTools.h"
|
||||
#include "Summary/RiaSummaryTools.h"
|
||||
|
||||
#include "Cloud/RimSummaryEnsembleSumo.h"
|
||||
#include "RimOilField.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCaseMainCollection.h"
|
||||
#include "Sumo/RimSummaryEnsembleSumo.h"
|
||||
#include "Sumo/RimSumoDataSource.h"
|
||||
#include "RimSumoDataSource.h"
|
||||
|
||||
#include "RiuPlotMainWindowTools.h"
|
||||
|
||||
@@ -93,6 +95,73 @@ std::vector<RimSumoDataSource*> RimCloudDataSourceCollection::sumoDataSources()
|
||||
return m_sumoDataSources.childrenByType();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimCloudDataSourceCollection::initAfterRead()
|
||||
{
|
||||
refreshDataSourcesFromSumo();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The realization ids, vector names and grid names of a data source are not written to the project file,
|
||||
/// only the identity of the ensemble and the realization filter are. A data source read back from a project
|
||||
/// therefore has none of them, which leaves the realization filter without the set it filters. Ask Sumo for
|
||||
/// them again here, so the data source is as complete as one just added.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimCloudDataSourceCollection::refreshDataSourcesFromSumo()
|
||||
{
|
||||
if ( !m_sumoConnector ) return;
|
||||
|
||||
for ( auto dataSource : sumoDataSources() )
|
||||
{
|
||||
if ( !dataSource ) continue;
|
||||
|
||||
const auto caseId = dataSource->caseId();
|
||||
const auto ensembleName = dataSource->ensembleName();
|
||||
if ( caseId.get().isEmpty() || ensembleName.isEmpty() ) continue;
|
||||
|
||||
// Only what is missing is asked for, and an empty answer is not stored: a failed request must not
|
||||
// replace values that are already there. Note that Sumo answering with nothing is indistinguishable
|
||||
// from the request failing, so an ensemble genuinely without realizations is asked about again on the
|
||||
// next load. That costs a request and is preferable to recording "no realizations" after a failure.
|
||||
if ( !dataSource->hasFetchedRealizations() )
|
||||
{
|
||||
if ( const auto realizationIds = m_sumoConnector->explore().realizationIds( caseId, ensembleName ); !realizationIds.empty() )
|
||||
{
|
||||
dataSource->setAvailableRealizationIds( realizationIds );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Without the realizations the realization filter has nothing to filter, and editing it
|
||||
// cannot be told from deselecting everything. Say so once, here, rather than leaving the
|
||||
// data source looking empty for no stated reason.
|
||||
RiaLogging::warning( QString( "Unable to load the realizations of Sumo ensemble '%1'. Editing the "
|
||||
"realization filter will have no effect until they are available." )
|
||||
.arg( ensembleName )
|
||||
.toStdString() );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( dataSource->vectorNames().empty() )
|
||||
{
|
||||
if ( const auto vectorNames = m_sumoConnector->summary().vectorNames( caseId, ensembleName ); !vectorNames.empty() )
|
||||
{
|
||||
dataSource->setVectorNames( vectorNames );
|
||||
}
|
||||
}
|
||||
|
||||
if ( dataSource->gridNames().empty() )
|
||||
{
|
||||
if ( const auto gridNames = m_sumoConnector->grid().gridNames( caseId, ensembleName ); !gridNames.empty() )
|
||||
{
|
||||
dataSource->setGridNames( gridNames );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -464,6 +533,7 @@ std::vector<RimSumoDataSource*> RimCloudDataSourceCollection::addDataSources()
|
||||
}
|
||||
|
||||
const auto availableRealizationIds = m_sumoConnector->explore().realizationIds( sumoCaseId, ensembleName );
|
||||
const auto gridNames = m_sumoConnector->grid().gridNames( sumoCaseId, ensembleName );
|
||||
const auto vectorNames = m_sumoConnector->summary().vectorNames( sumoCaseId, ensembleName );
|
||||
|
||||
auto dataSource = new RimSumoDataSource();
|
||||
@@ -473,6 +543,13 @@ std::vector<RimSumoDataSource*> RimCloudDataSourceCollection::addDataSources()
|
||||
dataSource->setEnsembleName( ensembleName );
|
||||
dataSource->setAvailableRealizationIds( availableRealizationIds );
|
||||
dataSource->setVectorNames( vectorNames );
|
||||
dataSource->setGridNames( gridNames );
|
||||
|
||||
if ( !gridNames.empty() )
|
||||
{
|
||||
dataSource->setSelectedGridName( gridNames.front() );
|
||||
}
|
||||
|
||||
dataSource->updateName();
|
||||
|
||||
objectToSelect = dataSource;
|
||||
|
||||
@@ -47,6 +47,12 @@ public:
|
||||
static void createEnsemblesFromSelectedDataSources( const std::vector<RimSumoDataSource*>& dataSources );
|
||||
|
||||
private:
|
||||
void initAfterRead() override;
|
||||
|
||||
// Fetch the realization ids, vector names and grid names of the data sources that do not have them. They
|
||||
// are not written to the project file, so a data source restored from one starts out without them.
|
||||
void refreshDataSourcesFromSumo();
|
||||
|
||||
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
|
||||
@@ -0,0 +1,568 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026 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 "RimSumoDataSource.h"
|
||||
|
||||
#include "RiaLogging.h"
|
||||
#include "RiaStdStringTools.h"
|
||||
|
||||
#include "Cloud/RimSummaryEnsembleSumo.h"
|
||||
#include "Rim3dOverlayInfoConfig.h"
|
||||
#include "RimEclipseView.h"
|
||||
#include "RimEclipseViewCollection.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimReservoirGridEnsembleSumo.h"
|
||||
#include "RimRoffCaseSumo.h"
|
||||
|
||||
#include "cafCmdFeatureMenuBuilder.h"
|
||||
#include "cafPdmPointer.h"
|
||||
#include "cafPdmUiLineEditor.h"
|
||||
#include "cafPdmUiTextEditor.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RimSumoDataSource, "RimSumoDataSource", "RimSummarySumoDataSource", "RimSumoGridDataSource" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimSumoDataSource::RimSumoDataSource()
|
||||
{
|
||||
CAF_PDM_InitObject( "Sumo Data Source", ":/CloudBlobs.svg" );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_caseId, "CaseId", "Case Id" );
|
||||
CAF_PDM_InitFieldNoDefault( &m_assetName, "AssetName", "Asset Name" );
|
||||
CAF_PDM_InitFieldNoDefault( &m_caseName, "CaseName", "Case Name" );
|
||||
CAF_PDM_InitFieldNoDefault( &m_ensembleName, "EnsembleName", "Ensemble Name" );
|
||||
CAF_PDM_InitFieldNoDefault( &m_customName, "CustomName", "Custom Name" );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_availableRealizationIds, "RealizationIds", "Available Realization Ids" );
|
||||
m_availableRealizationIds.uiCapability()->setUiHidden( true );
|
||||
m_availableRealizationIds.xmlCapability()->disableIO();
|
||||
|
||||
CAF_PDM_InitField( &m_realizationFilter,
|
||||
"RealizationFilter",
|
||||
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_realizationFilterInfo, "RealizationFilterInfo", "Info" );
|
||||
m_realizationFilterInfo.registerGetMethod( this, &RimSumoDataSource::realizationFilterInfoText );
|
||||
m_realizationFilterInfo.uiCapability()->setUiReadOnly( true );
|
||||
m_realizationFilterInfo.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_vectorNames, "VectorNames", "Vector Names" );
|
||||
m_vectorNames.uiCapability()->setUiHidden( true );
|
||||
m_vectorNames.xmlCapability()->disableIO();
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_gridNames, "GridNames", "Grid Names" );
|
||||
m_gridNames.uiCapability()->setUiHidden( true );
|
||||
m_gridNames.xmlCapability()->disableIO();
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_selectedGridName, "GridName", "Grid Name" );
|
||||
|
||||
CAF_PDM_InitField( &m_doComputeMobileVolumeWeightedMean,
|
||||
"DoComputeMobileVolumeWeightedMean",
|
||||
false,
|
||||
"Compute Mobile Volume Weighted Mean",
|
||||
"",
|
||||
"Show the mobile volume weighted mean in the 3D info box. This downloads the PORV and SWCR grid "
|
||||
"properties for every realization." );
|
||||
|
||||
setDeletable( true );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
SumoCaseId RimSumoDataSource::caseId() const
|
||||
{
|
||||
return SumoCaseId( m_caseId() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setCaseId( const SumoCaseId& caseId )
|
||||
{
|
||||
m_caseId = caseId.get();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::assetName() const
|
||||
{
|
||||
return m_assetName();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setAssetName( const QString& assetName )
|
||||
{
|
||||
m_assetName = assetName;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::caseName() const
|
||||
{
|
||||
return m_caseName();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setCaseName( const QString& caseName )
|
||||
{
|
||||
m_caseName = caseName;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::ensembleName() const
|
||||
{
|
||||
return m_ensembleName();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setEnsembleName( const QString& ensembleName )
|
||||
{
|
||||
m_ensembleName = ensembleName;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<QString> RimSumoDataSource::availableRealizationIds() const
|
||||
{
|
||||
return m_availableRealizationIds();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimSumoDataSource::hasFetchedRealizations() const
|
||||
{
|
||||
return m_hasFetchedRealizations;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The available realizations are the source of truth. An empty filter selects all of them.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setAvailableRealizationIds( const std::vector<QString>& realizationIds )
|
||||
{
|
||||
m_availableRealizationIds = realizationIds;
|
||||
m_hasFetchedRealizations = true;
|
||||
|
||||
// Show the full range instead of an empty field. Only an empty filter is replaced, keeping a user
|
||||
// or project defined filter. Selection is unchanged, as empty and full range both select all.
|
||||
if ( m_realizationFilter().trimmed().isEmpty() )
|
||||
{
|
||||
m_realizationFilter = availableRealizationsRangeText();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The subset of available realizations matching the realization filter. An empty filter (or '*')
|
||||
/// selects all available realizations.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<QString> RimSumoDataSource::selectedRealizationIds() const
|
||||
{
|
||||
const auto& available = m_availableRealizationIds();
|
||||
|
||||
auto filter = m_realizationFilter();
|
||||
if ( filter.trimmed().isEmpty() || filter.contains( '*' ) )
|
||||
{
|
||||
return available;
|
||||
}
|
||||
|
||||
auto selectedValues = RiaStdStringTools::valuesFromRangeSelection( filter.toStdString() );
|
||||
|
||||
std::vector<QString> result;
|
||||
for ( const auto& realizationId : available )
|
||||
{
|
||||
bool ok = false;
|
||||
int value = realizationId.toInt( &ok );
|
||||
if ( ok && selectedValues.count( value ) > 0 )
|
||||
{
|
||||
result.push_back( realizationId );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<QString> RimSumoDataSource::vectorNames() const
|
||||
{
|
||||
return m_vectorNames();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setVectorNames( const std::vector<QString>& vectorNames )
|
||||
{
|
||||
m_vectorNames = vectorNames;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<QString> RimSumoDataSource::gridNames() const
|
||||
{
|
||||
return m_gridNames();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setGridNames( const std::vector<QString>& gridNames )
|
||||
{
|
||||
m_gridNames = gridNames;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::selectedGridName() const
|
||||
{
|
||||
return m_selectedGridName();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::setSelectedGridName( const QString& gridName )
|
||||
{
|
||||
m_selectedGridName = gridName;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimSumoDataSource::doComputeMobileVolumeWeightedMean() const
|
||||
{
|
||||
return m_doComputeMobileVolumeWeightedMean();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::updateName()
|
||||
{
|
||||
if ( !m_customName().isEmpty() )
|
||||
{
|
||||
setName( m_customName() );
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = QString( "%1 (%2)" ).arg( ensembleName(), caseName() );
|
||||
|
||||
setName( name );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
|
||||
{
|
||||
menuBuilder.addCmdFeature( "RicCreateSumoEnsembleFeature" );
|
||||
menuBuilder.addCmdFeature( "RicCreateSumoReservoirGridEnsembleFeature" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
|
||||
{
|
||||
if ( field == &m_realizationFilter )
|
||||
{
|
||||
if ( auto lineEdAttr = dynamic_cast<caf::PdmUiLineEditorAttribute*>( attribute ) )
|
||||
{
|
||||
lineEdAttr->placeholderText = "E.g. 0,1,4-10,!6. Use '*' for all.";
|
||||
}
|
||||
}
|
||||
else if ( field == &m_realizationFilterInfo )
|
||||
{
|
||||
if ( auto* myAttr = dynamic_cast<caf::PdmUiTextEditorAttribute*>( attribute ) )
|
||||
{
|
||||
myAttr->heightHint = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
auto group = uiOrdering.addNewGroup( "General" );
|
||||
|
||||
group->add( nameField() );
|
||||
nameField()->uiCapability()->setUiReadOnly( true );
|
||||
|
||||
group->add( &m_caseId );
|
||||
m_caseId.uiCapability()->setUiReadOnly( true );
|
||||
|
||||
group->add( &m_assetName );
|
||||
m_assetName.uiCapability()->setUiReadOnly( true );
|
||||
|
||||
group->add( &m_caseName );
|
||||
m_caseName.uiCapability()->setUiReadOnly( true );
|
||||
|
||||
group->add( &m_ensembleName );
|
||||
m_ensembleName.uiCapability()->setUiReadOnly( true );
|
||||
|
||||
group->add( &m_customName );
|
||||
|
||||
auto ensembleGroup = uiOrdering.addNewGroup( "Ensemble Selection" );
|
||||
ensembleGroup->add( &m_realizationFilter );
|
||||
ensembleGroup->add( &m_realizationFilterInfo );
|
||||
|
||||
auto gridGroup = uiOrdering.addNewGroup( "Grid Selection" );
|
||||
gridGroup->add( &m_selectedGridName );
|
||||
gridGroup->add( &m_doComputeMobileVolumeWeightedMean );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QList<caf::PdmOptionItemInfo> RimSumoDataSource::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
|
||||
{
|
||||
QList<caf::PdmOptionItemInfo> options;
|
||||
|
||||
if ( fieldNeedingOptions == &m_selectedGridName )
|
||||
{
|
||||
for ( const auto& gridName : m_gridNames() )
|
||||
{
|
||||
options.push_back( caf::PdmOptionItemInfo( gridName, gridName ) );
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
|
||||
{
|
||||
if ( changedField == &m_customName )
|
||||
{
|
||||
updateName();
|
||||
}
|
||||
else if ( changedField == &m_realizationFilter )
|
||||
{
|
||||
onRealizationFilterChanged();
|
||||
}
|
||||
else if ( changedField == &m_doComputeMobileVolumeWeightedMean )
|
||||
{
|
||||
updateGridCaseEnsembles();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Propagate a change in the realization filter to the ensembles created from this data source, so
|
||||
/// editing the filter updates the realization cases (and the connected plots/views).
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::onRealizationFilterChanged()
|
||||
{
|
||||
// Update any summary ensembles created from this data source (same behaviour as summary ensembles
|
||||
// loaded from disk, see RimSummaryFileSetEnsemble::onFileSetChanged).
|
||||
for ( auto ensemble : objectsWithReferringPtrFieldsOfType<RimSummaryEnsembleSumo>() )
|
||||
{
|
||||
ensemble->onRealizationSelectionChanged();
|
||||
}
|
||||
|
||||
// Update any grid case ensembles created from this data source.
|
||||
updateGridCaseEnsembles();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Synchronize the realization grid cases (RimRoffCaseSumo) of every grid ensemble created from this
|
||||
/// data source with the current realization filter: remove cases for deselected realizations and add
|
||||
/// cases for newly selected ones. The grid name of each ensemble is preserved.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSumoDataSource::updateGridCaseEnsembles()
|
||||
{
|
||||
// Synchronizing against realizations that have not been fetched would read "not known yet" as "nothing
|
||||
// selected" and remove the realization cases of every grid ensemble. That is what a project load followed
|
||||
// by an edit of the realization filter would otherwise do.
|
||||
const bool realizationsAreKnown = hasFetchedRealizations();
|
||||
|
||||
std::set<int> selectedRealizations;
|
||||
for ( const auto& realizationId : selectedRealizationIds() )
|
||||
{
|
||||
bool ok = false;
|
||||
int value = realizationId.toInt( &ok );
|
||||
if ( ok ) selectedRealizations.insert( value );
|
||||
}
|
||||
|
||||
// The ensembles hold the back link to this data source, so they are found even when the realization
|
||||
// filter has left them without any cases.
|
||||
for ( auto ensemble : objectsWithReferringPtrFieldsOfType<RimReservoirGridEnsembleSumo>() )
|
||||
{
|
||||
std::vector<RimRoffCaseSumo*> gridCases;
|
||||
for ( auto eclipseCase : ensemble->sourceCases() )
|
||||
{
|
||||
if ( auto gridCase = dynamic_cast<RimRoffCaseSumo*>( eclipseCase ) ) gridCases.push_back( gridCase );
|
||||
}
|
||||
|
||||
// All cases in an ensemble share the same grid, and the ensemble knows which one, so the name is
|
||||
// available also when the filter has left it without any cases.
|
||||
const QString gridName = ensemble->sumoGridName();
|
||||
if ( gridName.isEmpty() ) continue;
|
||||
|
||||
if ( ensemble->doComputeMobileVolumeWeightedMean() != m_doComputeMobileVolumeWeightedMean() )
|
||||
{
|
||||
ensemble->setDoComputeMobileVolumeWeightedMean( m_doComputeMobileVolumeWeightedMean() );
|
||||
|
||||
// The 3D info box reads the setting when it is updated, so refresh it to apply the change immediately.
|
||||
if ( auto viewColl = ensemble->viewCollection() )
|
||||
{
|
||||
for ( auto view : viewColl->views() )
|
||||
{
|
||||
if ( view->overlayInfoConfig() ) view->overlayInfoConfig()->update3DInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Everything above applies whether or not the realizations are known. Adding and removing cases below
|
||||
// does not, so leave the ensemble as it is until the realizations have been fetched.
|
||||
if ( !realizationsAreKnown ) continue;
|
||||
|
||||
// A realization the grid does not exist for would only produce a case failing to load. An empty list
|
||||
// means the realizations of the grid are not known, then do not filter on them.
|
||||
std::set<int> realizationsToSelect = selectedRealizations;
|
||||
if ( const auto gridRealizations = ensemble->gridRealizations(); !gridRealizations.empty() )
|
||||
{
|
||||
std::erase_if( realizationsToSelect,
|
||||
[&gridRealizations]( int realization )
|
||||
{ return std::ranges::find( gridRealizations, realization ) == gridRealizations.end(); } );
|
||||
}
|
||||
|
||||
// The selection can be changed after the ensemble was created, so report the realizations left out
|
||||
// here as well, not only when the ensemble is created. The ensemble reports the current set in its
|
||||
// property panel and marks itself in the tree, see RimReservoirGridEnsembleSumo.
|
||||
if ( const auto missing = ensemble->realizationsWithoutGridData(); !missing.empty() )
|
||||
{
|
||||
RiaLogging::warning( QString( "Grid '%1' has no data for %2 of the selected realizations, no cases created "
|
||||
"for them: %3" )
|
||||
.arg( gridName )
|
||||
.arg( missing.size() )
|
||||
.arg( QString::fromStdString( RiaStdStringTools::formatRangeSelection( missing ) ) )
|
||||
.toStdString() );
|
||||
}
|
||||
|
||||
std::set<int> currentRealizations;
|
||||
for ( auto gridCase : gridCases )
|
||||
{
|
||||
currentRealizations.insert( gridCase->realization() );
|
||||
}
|
||||
|
||||
// Add cases for newly selected realizations first, so a view displaying a removed realization
|
||||
// can be repointed to one of these instead of being deleted.
|
||||
for ( int realization : realizationsToSelect )
|
||||
{
|
||||
if ( currentRealizations.find( realization ) != currentRealizations.end() ) continue;
|
||||
|
||||
if ( auto* gridCase = RimRoffCaseSumo::createFromDataSource( this, gridName, realization ) )
|
||||
{
|
||||
ensemble->addCase( gridCase );
|
||||
|
||||
if ( auto project = RimProject::current() ) project->assignCaseIdToCase( gridCase );
|
||||
}
|
||||
}
|
||||
|
||||
// Remove cases for realizations no longer selected. Remember any views that displayed them, so
|
||||
// they can be repointed to a surviving case below (a deleted case auto-nulls the view's case,
|
||||
// which would otherwise make the view disappear, e.g. when changing 30-40 to 31-40).
|
||||
std::vector<RimEclipseView*> orphanedViews;
|
||||
for ( auto gridCase : gridCases )
|
||||
{
|
||||
if ( realizationsToSelect.find( gridCase->realization() ) != realizationsToSelect.end() ) continue;
|
||||
|
||||
if ( auto viewColl = ensemble->viewCollection() )
|
||||
{
|
||||
for ( auto view : viewColl->views() )
|
||||
{
|
||||
if ( view->eclipseCase() == gridCase ) orphanedViews.push_back( view );
|
||||
}
|
||||
}
|
||||
|
||||
ensemble->removeCase( gridCase );
|
||||
delete gridCase;
|
||||
}
|
||||
|
||||
// Repoint orphaned views to a surviving case, or delete them if no realizations remain.
|
||||
const auto remainingCases = ensemble->sourceCases();
|
||||
for ( auto view : orphanedViews )
|
||||
{
|
||||
if ( !remainingCases.empty() )
|
||||
{
|
||||
view->setEclipseCase( remainingCases.front() );
|
||||
view->loadDataAndUpdate();
|
||||
}
|
||||
else if ( auto viewColl = ensemble->viewCollection() )
|
||||
{
|
||||
viewColl->removeView( view );
|
||||
delete view;
|
||||
}
|
||||
}
|
||||
|
||||
ensemble->updateConnectedEditors();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Info text shown next to the realization filter.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::realizationFilterInfoText() const
|
||||
{
|
||||
return "Available realizations: " + availableRealizationsRangeText();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Compact range text for the available ensemble realizations, e.g. "0-99".
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimSumoDataSource::availableRealizationsRangeText() const
|
||||
{
|
||||
std::vector<int> intValues;
|
||||
for ( const auto& realizationId : m_availableRealizationIds() )
|
||||
{
|
||||
bool ok = false;
|
||||
int value = realizationId.toInt( &ok );
|
||||
if ( ok ) intValues.push_back( value );
|
||||
}
|
||||
|
||||
auto rangeString = RiaStdStringTools::formatRangeSelection( intValues );
|
||||
return QString::fromStdString( rangeString );
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2024 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.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
|
||||
#include "RimNamedObject.h"
|
||||
|
||||
#include "Cloud/RiaSumoConnector.h"
|
||||
|
||||
#include "cafPdmProxyValueField.h"
|
||||
|
||||
//==================================================================================================
|
||||
//
|
||||
// Common data source describing a single Sumo ensemble. Holds the information required both for
|
||||
// summary ensembles (vector names) and for grid case ensembles (asset, grid names). The available
|
||||
// ensemble realizations - fetched from the realizations endpoint - are the source of truth, and the
|
||||
// user selects a subset of them ("ensemble selection"). Both summary and grid consumers listen to
|
||||
// the selected realization id subset. All values are populated by RimCloudDataSourceCollection,
|
||||
// which owns the RiaSumoConnector; this object does not talk to the connector directly.
|
||||
//
|
||||
//==================================================================================================
|
||||
|
||||
class RimSumoDataSource : public RimNamedObject
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
RimSumoDataSource();
|
||||
|
||||
SumoCaseId caseId() const;
|
||||
void setCaseId( const SumoCaseId& caseId );
|
||||
|
||||
QString assetName() const;
|
||||
void setAssetName( const QString& assetName );
|
||||
|
||||
QString caseName() const;
|
||||
void setCaseName( const QString& caseName );
|
||||
|
||||
QString ensembleName() const;
|
||||
void setEnsembleName( const QString& ensembleName );
|
||||
|
||||
// All realizations available for the ensemble (the source of truth).
|
||||
std::vector<QString> availableRealizationIds() const;
|
||||
void setAvailableRealizationIds( const std::vector<QString>& realizationIds );
|
||||
|
||||
// Whether the realizations have been fetched from Sumo. They are not written to the project file, so a
|
||||
// data source read back from one starts out without them, see
|
||||
// RimCloudDataSourceCollection::refreshDataSourcesFromSumo. Ask this rather than testing
|
||||
// availableRealizationIds() for emptiness: an empty list cannot tell "not fetched" from "fetched, and the
|
||||
// ensemble has none", and reading the first as the second removes the realization cases of every ensemble
|
||||
// created from this data source.
|
||||
bool hasFetchedRealizations() const;
|
||||
|
||||
// The subset of realizations matching the realization filter. Both summary and grid creation listen to this.
|
||||
std::vector<QString> selectedRealizationIds() const;
|
||||
|
||||
// Available summary vectors for the ensemble. Not shown in the UI, but used to populate the
|
||||
// ensemble's available result addresses (RimSummaryEnsembleSumo::updateResultAddresses).
|
||||
std::vector<QString> vectorNames() const;
|
||||
void setVectorNames( const std::vector<QString>& vectorNames );
|
||||
|
||||
std::vector<QString> gridNames() const;
|
||||
void setGridNames( const std::vector<QString>& gridNames );
|
||||
|
||||
QString selectedGridName() const;
|
||||
void setSelectedGridName( const QString& gridName );
|
||||
|
||||
bool doComputeMobileVolumeWeightedMean() const;
|
||||
|
||||
void updateName();
|
||||
|
||||
private:
|
||||
void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override;
|
||||
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
|
||||
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
|
||||
|
||||
void onRealizationFilterChanged();
|
||||
void updateGridCaseEnsembles();
|
||||
|
||||
QString realizationFilterInfoText() const;
|
||||
QString availableRealizationsRangeText() const;
|
||||
|
||||
private:
|
||||
caf::PdmField<QString> m_caseId;
|
||||
caf::PdmField<QString> m_assetName;
|
||||
caf::PdmField<QString> m_caseName;
|
||||
caf::PdmField<QString> m_ensembleName;
|
||||
caf::PdmField<QString> m_customName;
|
||||
|
||||
caf::PdmField<std::vector<QString>> m_availableRealizationIds;
|
||||
|
||||
// Runtime only, deliberately not a PdmField: it describes whether this session has fetched the
|
||||
// realizations, which is never true for a data source just read from a project file.
|
||||
bool m_hasFetchedRealizations = false;
|
||||
caf::PdmField<QString> m_realizationFilter;
|
||||
caf::PdmProxyValueField<QString> m_realizationFilterInfo;
|
||||
|
||||
caf::PdmField<std::vector<QString>> m_vectorNames;
|
||||
|
||||
caf::PdmField<std::vector<QString>> m_gridNames;
|
||||
caf::PdmField<QString> m_selectedGridName;
|
||||
caf::PdmField<bool> m_doComputeMobileVolumeWeightedMean;
|
||||
};
|
||||
Reference in new issue
Block a user