From c57864df51e50ba597a8e0b3fef9e0d1a432cc4a Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 12 Oct 2026 18:50:57 +0200 Subject: [PATCH] #14889: RFT Ensemble: Additional UI fixes * #14889: Do not change main window when importing well picks * #14889: Fix clipped bottom of plots in dock windows * #14889: Add Create RFT Correlation Report to Ensemble Correlation Plots menu * #14889: Show imported observed FMU RFT data as table * #14889: Use time step from source RFT plot in RFT correlation report * #14889: Show observed data in RFT cross plot when no RFT samples are available * #14889: Keep RFT plot zoom when toggling formation in RFT correlation report * #14889: Do not change main window when importing formations * #14889: Use general delete for observed data and improve FMU RFT data name --- .../Commands/CMakeLists_files.cmake | 1 - .../Commands/RicCloseObservedDataFeature.cpp | 132 ------------------ .../RicImportWellFormationsFeature.cpp | 9 +- .../RicCreateRftCorrelationReportFeature.cpp | 12 +- .../WellPathCommands/RicImportWellPaths.cpp | 5 +- ...RicWellPathFormationsImportFileFeature.cpp | 16 --- .../FileInterface/RifReaderFmuRft.cpp | 21 +++ .../FileInterface/RifReaderFmuRft.h | 3 + .../RimCorrelationPlotCollection.cpp | 32 +++-- .../RimCorrelationPlotCollection.h | 2 + .../RimParameterRftCrossPlot.cpp | 58 +++++--- .../RimParameterRftCrossPlot.h | 3 +- .../RimRftCorrelationReportPlot.cpp | 23 ++- .../Flow/RimWellPlotTools.cpp | 21 +++ .../ProjectDataModel/Flow/RimWellPlotTools.h | 1 + .../ProjectDataModel/Flow/RimWellRftPlot.cpp | 24 ---- .../ProjectDataModel/Flow/RimWellRftPlot.h | 2 - .../Formations/RimWellFormationsFile.cpp | 27 +--- .../RimContextCommandBuilder.cpp | 9 -- .../RimObservedDataCollection.cpp | 49 ++++--- .../RimObservedDataCollection.h | 2 - .../RimObservedFmuRftData.cpp | 44 +++++- .../ProjectDataModel/RimObservedFmuRftData.h | 12 +- .../ProjectDataModel/RimPlotCurve.cpp | 8 ++ .../ProjectDataModel/RimPlotCurve.h | 13 +- .../Summary/RimObservedSummaryData.cpp | 1 + .../Summary/RimSummaryCurve.cpp | 15 ++ .../Summary/RimSummaryCurve.h | 2 + .../Tools/CMakeLists_files.cmake | 1 + .../Tools/RimCsvPreviewTools.cpp | 66 +++++++++ .../Tools/RimCsvPreviewTools.h} | 42 +++--- .../WellLog/RimWellLogRftCurve.cpp | 15 ++ .../WellLog/RimWellLogRftCurve.h | 2 + .../WellPath/RimWellPathCollection.cpp | 12 -- .../WellPath/RimWellPathCollection.h | 3 - .../UserInterface/RiuMultiPlotPage.cpp | 4 + 36 files changed, 373 insertions(+), 319 deletions(-) delete mode 100644 ApplicationLibCode/Commands/RicCloseObservedDataFeature.cpp create mode 100644 ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.cpp rename ApplicationLibCode/{Commands/RicCloseObservedDataFeature.h => ProjectDataModel/Tools/RimCsvPreviewTools.h} (52%) diff --git a/ApplicationLibCode/Commands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CMakeLists_files.cmake index a0dab89a32..ce08e45218 100644 --- a/ApplicationLibCode/Commands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CMakeLists_files.cmake @@ -3,7 +3,6 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicCloseCaseFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicCloseSummaryCaseFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicCloseSummaryCaseInCollectionFeature.cpp - ${CMAKE_CURRENT_LIST_DIR}/RicCloseObservedDataFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewViewFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewGenericDataViewFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewContourMapViewFeature.cpp diff --git a/ApplicationLibCode/Commands/RicCloseObservedDataFeature.cpp b/ApplicationLibCode/Commands/RicCloseObservedDataFeature.cpp deleted file mode 100644 index 3c3c0bad19..0000000000 --- a/ApplicationLibCode/Commands/RicCloseObservedDataFeature.cpp +++ /dev/null @@ -1,132 +0,0 @@ -///////////////////////////////////////////////////////////////////////////////// -// -// Copyright (C) 2017- Statoil 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 -// for more details. -// -///////////////////////////////////////////////////////////////////////////////// - -#include "RicCloseObservedDataFeature.h" - -#include "Summary/RiaSummaryTools.h" - -#include "RimMainPlotCollection.h" -#include "RimObservedDataCollection.h" -#include "RimObservedFmuRftData.h" -#include "RimObservedSummaryData.h" -#include "RimRftPlotCollection.h" -#include "RimSummaryMultiPlot.h" -#include "RimSummaryMultiPlotCollection.h" -#include "RimSummaryPlot.h" -#include "RimWellRftPlot.h" - -#include "cafSelectionManager.h" - -#include "cafAssert.h" - -#include - -CAF_CMD_SOURCE_INIT( RicCloseObservedDataFeature, "RicCloseObservedDataFeature" ); - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicCloseObservedDataFeature::setupActionLook( QAction* actionToSetup ) -{ - actionToSetup->setText( "Close" ); - actionToSetup->setIcon( QIcon( ":/Close.svg" ) ); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicCloseObservedDataFeature::deleteObservedSummaryData( const std::vector& data ) -{ - RimSummaryMultiPlotCollection* summaryPlotColl = RiaSummaryTools::summaryMultiPlotCollection(); - - for ( RimObservedSummaryData* observedData : data ) - { - for ( RimSummaryMultiPlot* multiPlot : summaryPlotColl->multiPlots() ) - { - for ( RimSummaryPlot* summaryPlot : multiPlot->summaryPlots() ) - { - summaryPlot->deleteCurvesAssosiatedWithCases( { observedData } ); - } - multiPlot->updateConnectedEditors(); - } - - RimObservedDataCollection* observedDataCollection = observedData->firstAncestorOrThisOfTypeAsserted(); - - observedDataCollection->removeObservedSummaryData( observedData ); - delete observedData; - observedDataCollection->updateConnectedEditors(); - } -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicCloseObservedDataFeature::deleteObservedRmuRftData( const std::vector& data ) -{ - RimRftPlotCollection* rftPlotColl = RimMainPlotCollection::current()->rftPlotCollection(); - - for ( RimObservedFmuRftData* observedData : data ) - { - RimObservedDataCollection* observedDataCollection = observedData->firstAncestorOrThisOfTypeAsserted(); - - for ( RimWellRftPlot* plot : rftPlotColl->rftPlots() ) - { - plot->deleteCurvesAssosicatedWithObservedData( observedData ); - } - observedDataCollection->removeObservedFmuRftData( observedData ); - delete observedData; - observedDataCollection->updateConnectedEditors(); - } -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -bool RicCloseObservedDataFeature::isCommandEnabled() const -{ - const auto summarySelection = caf::SelectionManager::instance()->objectsByType(); - const auto fmuRftSelection = caf::SelectionManager::instance()->objectsByType(); - - if ( summarySelection.empty() && fmuRftSelection.empty() ) - { - return false; - } - for ( RimObservedSummaryData* data : summarySelection ) - { - if ( !data->isObservedData() ) - { - return false; - } - } - - return true; -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RicCloseObservedDataFeature::onActionTriggered( bool isChecked ) -{ - const auto summarySelection = caf::SelectionManager::instance()->objectsByType(); - const auto fmuRftSelection = caf::SelectionManager::instance()->objectsByType(); - - CAF_ASSERT( !( summarySelection.empty() && fmuRftSelection.empty() ) ); - - RicCloseObservedDataFeature::deleteObservedSummaryData( summarySelection ); - RicCloseObservedDataFeature::deleteObservedRmuRftData( fmuRftSelection ); -} diff --git a/ApplicationLibCode/Commands/RicImportWellFormationsFeature.cpp b/ApplicationLibCode/Commands/RicImportWellFormationsFeature.cpp index 0cb41aa6d4..66c0957c66 100644 --- a/ApplicationLibCode/Commands/RicImportWellFormationsFeature.cpp +++ b/ApplicationLibCode/Commands/RicImportWellFormationsFeature.cpp @@ -19,14 +19,15 @@ #include "RicImportWellFormationsFeature.h" #include "RiaApplication.h" +#include "RiaGuiApplication.h" #include "Formations/RimWellFormationsCollection.h" #include "Formations/RimWellFormationsFile.h" #include "RimOilField.h" #include "RimProject.h" -#include "Riu3DMainWindowTools.h" #include "RiuFileDialogTools.h" +#include "RiuPlotMainWindowTools.h" #include #include @@ -41,7 +42,9 @@ void RicImportWellFormationsFeature::onActionTriggered( bool isChecked ) std::vector importedFiles = importFilesWithDialog(); if ( !importedFiles.empty() ) { - Riu3DMainWindowTools::selectAsCurrentItem( importedFiles.back() ); + // Well formations are shown in the plot window data sources tree + RiuPlotMainWindowTools::onObjectAppended( importedFiles.back(), + importedFiles.back()->firstAncestorOrThisOfType() ); } } @@ -52,7 +55,7 @@ std::vector RicImportWellFormationsFeature::importFilesW { RiaApplication* app = RiaApplication::instance(); QString defaultDir = app->lastUsedDialogDirectory( "WELLPATHFORMATIONS_DIR" ); - QStringList filePaths = RiuFileDialogTools::getOpenFileNames( Riu3DMainWindowTools::mainWindowWidget(), + QStringList filePaths = RiuFileDialogTools::getOpenFileNames( RiaGuiApplication::widgetToUseAsParent(), "Import Well Formations", defaultDir, "Well Formations (*.csv);;All Files (*.*)" ); diff --git a/ApplicationLibCode/Commands/WellLogCommands/RicCreateRftCorrelationReportFeature.cpp b/ApplicationLibCode/Commands/WellLogCommands/RicCreateRftCorrelationReportFeature.cpp index ef185dacc1..257043209a 100644 --- a/ApplicationLibCode/Commands/WellLogCommands/RicCreateRftCorrelationReportFeature.cpp +++ b/ApplicationLibCode/Commands/WellLogCommands/RicCreateRftCorrelationReportFeature.cpp @@ -21,6 +21,7 @@ #include "RimCorrelationPlotCollection.h" #include "RimMainPlotCollection.h" #include "RimRftCorrelationReportPlot.h" +#include "RimWellPlotTools.h" #include "RimWellRftPlot.h" #include "RiuPlotMainWindowTools.h" @@ -36,7 +37,14 @@ CAF_CMD_SOURCE_INIT( RicCreateRftCorrelationReportFeature, "RicCreateRftCorrelat //-------------------------------------------------------------------------------------------------- bool RicCreateRftCorrelationReportFeature::isCommandEnabled() const { - return dynamic_cast( caf::SelectionManager::instance()->selectedItem() ) != nullptr; + if ( dynamic_cast( caf::SelectionManager::instance()->selectedItem() ) ) return true; + + if ( dynamic_cast( caf::SelectionManager::instance()->selectedItem() ) ) + { + return !RimWellPlotTools::firstWellNameWithEnsembleRftData().isEmpty(); + } + + return false; } //-------------------------------------------------------------------------------------------------- @@ -44,8 +52,8 @@ bool RicCreateRftCorrelationReportFeature::isCommandEnabled() const //-------------------------------------------------------------------------------------------------- void RicCreateRftCorrelationReportFeature::onActionTriggered( bool /*isChecked*/ ) { + // Without a source plot, a default report is created for the first available ensemble RFT data auto* sourcePlot = dynamic_cast( caf::SelectionManager::instance()->selectedItem() ); - if ( !sourcePlot ) return; auto* correlationColl = RimMainPlotCollection::current()->correlationPlotCollection(); if ( !correlationColl ) return; diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp index 78d28e8da7..bc521638f3 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp +++ b/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp @@ -145,10 +145,9 @@ std::vector RicImportWellPaths::importWellPaths( const QStringList { RicNewGenericDataViewFeature::createInitialViewIfNeeded(); - RimWellPath* wellPath = oilField->wellPathCollection->mostRecentlyUpdatedWellPath(); - if ( wellPath ) + if ( !wellPaths.empty() && wellPaths.back() ) { - Riu3DMainWindowTools::selectAsCurrentItem( wellPath ); + Riu3DMainWindowTools::selectAsCurrentItem( wellPaths.back() ); } } } diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicWellPathFormationsImportFileFeature.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicWellPathFormationsImportFileFeature.cpp index 2f6235bd64..239724f729 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/RicWellPathFormationsImportFileFeature.cpp +++ b/ApplicationLibCode/Commands/WellPathCommands/RicWellPathFormationsImportFileFeature.cpp @@ -21,10 +21,7 @@ #include "RiaApplication.h" #include "RimMainPlotCollection.h" -#include "RimOilField.h" #include "RimProject.h" -#include "RimWellPath.h" -#include "RimWellPathCollection.h" #include "Riu3DMainWindowTools.h" #include "RiuFileDialogTools.h" @@ -60,19 +57,6 @@ void RicWellPathFormationsImportFileFeature::onActionTriggered( bool isChecked ) { project->scheduleCreateDisplayModelAndRedrawAllViews(); RimMainPlotCollection::current()->updatePlotsWithFormations(); - - RimOilField* oilField = project->activeOilField(); - - if ( !oilField ) return; - - if ( !oilField->wellPathCollection->allWellPaths().empty() ) - { - RimWellPath* wellPath = oilField->wellPathCollection->mostRecentlyUpdatedWellPath(); - if ( wellPath ) - { - Riu3DMainWindowTools::selectAsCurrentItem( wellPath ); - } - } } } diff --git a/ApplicationLibCode/FileInterface/RifReaderFmuRft.cpp b/ApplicationLibCode/FileInterface/RifReaderFmuRft.cpp index 744db74afd..1c9244a3bb 100644 --- a/ApplicationLibCode/FileInterface/RifReaderFmuRft.cpp +++ b/ApplicationLibCode/FileInterface/RifReaderFmuRft.cpp @@ -284,6 +284,27 @@ void RifReaderFmuRft::importData() } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RifReaderFmuRft::csvText() const +{ + QString text; + QTextStream stream( &text ); + + stream << "WELL,DATE,MEASUREMENT_ID,UTM_X,UTM_Y,MD,TVD,ZONE,PRESSURE,PRESSURE_ERROR\n"; + for ( const auto& obs : m_observations ) + { + stream << obs.wellDate.wellName << "," << obs.wellDate.dateTime.toString( "yyyy-MM-dd" ) << "," << obs.wellDate.measurementId << "," + << QString::number( obs.location.utmx, 'f', 2 ) << "," << QString::number( obs.location.utmy, 'f', 2 ) << "," + << QString::number( obs.location.mdrkb, 'f', 2 ) << "," << QString::number( obs.location.tvdmsl, 'f', 2 ) << "," + << obs.location.formation << "," << QString::number( obs.pressure, 'f', 2 ) << "," + << QString::number( obs.pressureError, 'f', 2 ) << "\n"; + } + + return text; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/FileInterface/RifReaderFmuRft.h b/ApplicationLibCode/FileInterface/RifReaderFmuRft.h index 0f74de4099..d7d464d005 100644 --- a/ApplicationLibCode/FileInterface/RifReaderFmuRft.h +++ b/ApplicationLibCode/FileInterface/RifReaderFmuRft.h @@ -81,6 +81,9 @@ public: void importData(); + // Imported observations as comma separated text with a header row + QString csvText() const; + private: static std::vector importWellDates( const QString& fileName ); static std::vector importLocations( const QString& fileName ); diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.cpp b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.cpp index 7c6b2b1ab0..7a86eae03f 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.cpp @@ -36,6 +36,8 @@ #include "RimWellRftEnsembleCurveSet.h" #include "RimWellRftPlot.h" +#include "cafCmdFeatureMenuBuilder.h" + CAF_PDM_SOURCE_INIT( RimCorrelationPlotCollection, "CorrelationPlotCollection" ); namespace @@ -211,12 +213,12 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR { auto* report = new RimRftCorrelationReportPlot; report->dockAsPlotWindow(); + report->initializeFromSourcePlot( source ); - if ( source ) + auto* rftPlot = report->wellRftPlot(); + if ( rftPlot ) { - report->initializeFromSourcePlot( source ); - - const auto ensembles = source->selectedEnsembles(); + const auto ensembles = rftPlot->selectedEnsembles(); if ( !ensembles.empty() ) { auto* ensemble = ensembles.front(); @@ -229,7 +231,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR { report->crossPlot()->setEnsembleParameter( param.name ); - if ( auto* curveSet = report->wellRftPlot()->findEnsembleCurveSet( ensemble ) ) + if ( auto* curveSet = rftPlot->findEnsembleCurveSet( ensemble ) ) { curveSet->setColorByEnsembleParameter( param.name ); } @@ -237,7 +239,8 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR } } } - report->crossPlot()->setWellName( source->simWellOrWellPathName() ); + const QString wellName = rftPlot->simWellOrWellPathName(); + report->crossPlot()->setWellName( wellName ); // Default to filtering by formation when a formations file has data for the well if ( auto* project = RimProject::current(); @@ -245,7 +248,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR { for ( RimWellFormationsFile* file : project->activeOilField()->wellFormationsCollection()->wellFormationsFiles() ) { - if ( file->formationsForWell( source->simWellOrWellPathName() ) ) + if ( file->formationsForWell( wellName ) ) { report->crossPlot()->setWellFormations( file ); report->crossPlot()->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES ); @@ -254,7 +257,7 @@ RimRftCorrelationReportPlot* RimCorrelationPlotCollection::createRftCorrelationR } } - const auto timeSteps = source->selectedTimeSteps(); + const auto timeSteps = rftPlot->selectedTimeSteps(); if ( !timeSteps.empty() ) report->crossPlot()->setTimeStep( timeSteps.front() ); } @@ -270,6 +273,19 @@ std::vector RimCorrelationPlotCollection::rftRepor return m_rftCorrelationReports.childrenByType(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCorrelationPlotCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicNewCorrelationPlotFeature"; + menuBuilder << "RicNewCorrelationMatrixPlotFeature"; + menuBuilder << "RicNewParameterResultCrossPlotFeature"; + menuBuilder << "RicNewCorrelationReportPlotFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicCreateRftCorrelationReportFeature"; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.h b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.h index 936da9e7fb..9a97bfa941 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.h +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimCorrelationPlotCollection.h @@ -81,6 +81,8 @@ public: std::vector rftReports() const; private: + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + void applyFirstEnsembleFieldAddressesToPlot( RimAbstractCorrelationPlot* plot, const std::vector& quantityNames = {} ); void applyEnsembleFieldAndTimeStepToPlot( RimAbstractCorrelationPlot* plot, RimSummaryEnsemble* ensemble, diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.cpp b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.cpp index 9616447611..3d2abebf50 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.cpp @@ -755,7 +755,6 @@ void RimParameterRftCrossPlot::createPoints() caf::ColorTable colorTable = RiaColorTables::categoryPaletteColors(); auto caseData = createCaseData(); - if ( caseData.empty() ) return; std::set selectedSummaryCases; auto selectedTreeViewItems = RiuDockWidgetTools::selectedItemsInTreeView( RiuDockWidgetTools::plotMainWindowDataSourceTreeName() ); @@ -806,21 +805,14 @@ void RimParameterRftCrossPlot::createPoints() //-------------------------------------------------------------------------------------------------- void RimParameterRftCrossPlot::attachObservedPressure() { - const auto observedPressures = - RimRftCrossPlotTools::computeObservedPressures( m_wellName(), - m_selectedTimeStep(), - depthIntervals(), - m_depthType(), - RimRftCrossPlotTools::buildAllZoneIntervals( m_wellFormations(), - m_wellName(), - m_depthType() ) ); - if ( observedPressures.empty() ) return; + const auto pressures = observedPressures(); + if ( pressures.empty() ) return; auto attachLine = [this]( double value, Qt::PenStyle style, const QString& label, const QColor& color ) { RiuRftCorrelationPlotTools::attachHorizontalLine( m_plotWidget, value, style, label, color ); }; bool isFirst = true; - for ( const auto& observed : observedPressures ) + for ( const auto& observed : pressures ) { // Use the formation color when known; otherwise fall back to black lines and no shaded band std::optional zoneColor; @@ -850,6 +842,20 @@ void RimParameterRftCrossPlot::attachObservedPressure() } } +//-------------------------------------------------------------------------------------------------- +/// Shared by drawing and range computation so the axis range matches the drawn zone averages +//-------------------------------------------------------------------------------------------------- +std::vector RimParameterRftCrossPlot::observedPressures() const +{ + return RimRftCrossPlotTools::computeObservedPressures( m_wellName(), + m_selectedTimeStep(), + depthIntervals(), + m_depthType(), + RimRftCrossPlotTools::buildAllZoneIntervals( m_wellFormations(), + m_wellName(), + m_depthType() ) ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -895,25 +901,35 @@ void RimParameterRftCrossPlot::updateValueRanges() yMax = std::max( yMax, pressureValue ); } - for ( const auto& observed : - RimRftCrossPlotTools::computeObservedPressures( m_wellName(), m_selectedTimeStep(), depthIntervals(), m_depthType() ) ) + for ( const auto& observed : observedPressures() ) { yMin = std::min( yMin, observed.rangeMin ); yMax = std::max( yMax, observed.rangeMax ); } - if ( xMin == std::numeric_limits::infinity() ) + // Without RFT samples, use the parameter range so the X axis does not keep the range of a previous time step + if ( xMin == std::numeric_limits::infinity() && m_ensemble() && !m_ensembleParameter().isEmpty() ) { - m_xValueRange = std::nullopt; - m_yValueRange = std::nullopt; - return; + const RigEnsembleParameter parameter = m_ensemble->ensembleParameter( m_ensembleParameter ); + if ( parameter.isNumeric() && parameter.isValid() ) + { + xMin = parameter.minValue; + xMax = parameter.maxValue; + } } - const double xRange = xMax - xMin; - const double yRange = yMax - yMin; + // Observed data is shown even when there are no RFT samples, so the ranges are computed independently + auto paddedRange = []( double minValue, double maxValue ) -> std::optional> + { + if ( minValue == std::numeric_limits::infinity() ) return std::nullopt; - m_xValueRange = std::make_pair( xMin - xRange * 0.1, xMax + xRange * 0.1 ); - m_yValueRange = std::make_pair( yMin - yRange * 0.1, yMax + yRange * 0.1 ); + const double range = maxValue - minValue; + const double padding = range > 0.0 ? range * 0.1 : std::max( std::abs( minValue ) * 0.01, 1.0 ); + return std::make_pair( minValue - padding, maxValue + padding ); + }; + + m_xValueRange = paddedRange( xMin, xMax ); + m_yValueRange = paddedRange( yMin, yMax ); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.h b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.h index 8fb4ada3bf..41757d6ccb 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.h +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimParameterRftCrossPlot.h @@ -132,7 +132,8 @@ private: void cleanupBeforeClose(); RimSummaryCase* findClosestCase( const QPoint& canvasPos ); - std::vector depthIntervals() const; + std::vector depthIntervals() const; + std::vector observedPressures() const; private: caf::PdmPtrField m_ensemble; diff --git a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp index c518a35cc9..fbdccfea3d 100644 --- a/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/CorrelationPlots/RimRftCorrelationReportPlot.cpp @@ -25,6 +25,7 @@ #include "RimRftCrossPlotTools.h" #include "RimRftTornadoPlot.h" #include "RimWellLogTrack.h" +#include "RimWellPlotTools.h" #include "RimWellRftEnsembleCurveSet.h" #include "RimWellRftPlot.h" @@ -207,14 +208,13 @@ RimParameterRftCrossPlot* RimRftCorrelationReportPlot::crossPlot() const /// Initialize the owned RimWellRftPlot from the source plot's selected data sources. /// We keep the fresh (curve-free) child plot and call initializeDataSources(source) so /// syncCurvesFromUiSelection builds curves from scratch without touching unresolved copies. +/// Without a source plot, the first well with ensemble RFT data is used with all available data sources. //-------------------------------------------------------------------------------------------------- void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* source ) { - if ( !source ) return; - applyDepthTypeToSubPlots(); - m_wellRftPlot->setSimWellOrWellPathName( source->simWellOrWellPathName() ); + m_wellRftPlot->setSimWellOrWellPathName( source ? source->simWellOrWellPathName() : RimWellPlotTools::firstWellNameWithEnsembleRftData() ); // A fresh RimWellRftPlot has no tracks; syncCurvesFromUiSelection exits early without one. // Guard against duplicate track creation if this is called more than once. @@ -227,10 +227,14 @@ void RimRftCorrelationReportPlot::initializeFromSourcePlot( RimWellRftPlot* sour m_wellRftPlot->initializeDataSources( source ); - // The correlation report operates on a single time step; trim any extras that - // initializeDataSources may have preselected when only a few were available. + // The correlation report operates on a single time step; prefer the time step selected in the + // source plot, and trim any extras that initializeDataSources may have preselected. auto selectedTimeSteps = m_wellRftPlot->selectedTimeSteps(); - if ( selectedTimeSteps.size() > 1 ) + if ( source && !source->selectedTimeSteps().empty() ) + { + m_wellRftPlot->setSelectedTimeSteps( { source->selectedTimeSteps().front() } ); + } + else if ( selectedTimeSteps.size() > 1 ) { m_wellRftPlot->setSelectedTimeSteps( { selectedTimeSteps.front() } ); } @@ -706,7 +710,12 @@ void RimRftCorrelationReportPlot::onRftTrackDepthClicked( RimWellLogTrack* track m_parameterRftCrossPlot->setFilterMode( RimRftCrossPlotTools::DepthFilterMode::ZONES ); m_parameterRftCrossPlot->setSelectedZones( zones ); - loadDataAndUpdate(); + + // The zone filter does not affect the RFT curves; skip reloading the RFT plot to keep its zoom + updateSelectedZoneHighlight(); + syncTornadoInputsFromCrossPlot(); + m_tornadoPlot->loadDataAndUpdate(); + m_parameterRftCrossPlot->loadDataAndUpdate(); updateConnectedEditors(); return; } diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.cpp index 8184a3aa37..a2992d4bf1 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.cpp @@ -490,6 +490,27 @@ std::vector rftEnsembles() return rftEnsembles; } +//-------------------------------------------------------------------------------------------------- +/// Stops at the first realization with RFT data, as querying the RFT reader of every realization is expensive +//-------------------------------------------------------------------------------------------------- +QString firstWellNameWithEnsembleRftData() +{ + for ( RimSummaryEnsemble* ensemble : RimProject::current()->summaryEnsembles() ) + { + if ( !ensemble || !ensemble->isEnsemble() ) continue; + + for ( RimSummaryCase* summaryCase : ensemble->allSummaryCases() ) + { + auto reader = summaryCase->rftReader(); + if ( !reader ) continue; + + const auto wellNames = reader->wellNames(); + if ( !wellNames.empty() ) return *wellNames.begin(); + } + } + return {}; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.h b/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.h index 7f0c145a49..50d12c56d1 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.h +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellPlotTools.h @@ -85,6 +85,7 @@ std::vector gridCasesForWell( const QString& simWellName std::vector rftCasesForWell( const QString& simWellName ); std::vector rftEnsemblesForWell( const QString& simWellName ); std::vector rftEnsembles(); +QString firstWellNameWithEnsembleRftData(); std::vector observedFmuRftDataForWell( const QString& simWellName ); std::vector observedFmuRftData(); QString simWellName( const QString& wellPathNameOrSimWellName ); diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp index deb40450e1..dddd3a0157 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.cpp @@ -946,30 +946,6 @@ const char* RimWellRftPlot::plotNameFormatString() return PLOT_NAME_QFORMAT_STRING; } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimWellRftPlot::deleteCurvesAssosicatedWithObservedData( const RimObservedFmuRftData* observedFmuRftData ) -{ - for ( auto plot : plots() ) - { - RimWellLogTrack* const track = dynamic_cast( plot ); - if ( track ) - { - auto curves = track->curves(); - for ( auto curve : curves ) - { - RimWellLogRftCurve* rftCurve = dynamic_cast( curve ); - if ( rftCurve && rftCurve->observedFmuRftData() == observedFmuRftData ) - { - track->removeCurve( rftCurve ); - delete rftCurve; - } - } - } - } -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.h b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.h index fec87f710c..cbc79fc550 100644 --- a/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.h +++ b/ApplicationLibCode/ProjectDataModel/Flow/RimWellRftPlot.h @@ -93,8 +93,6 @@ public: static const char* plotNameFormatString(); - void deleteCurvesAssosicatedWithObservedData( const RimObservedFmuRftData* observedFmuRftData ); - bool showErrorBarsForObservedData() const; void onLegendDefinitionChanged(); diff --git a/ApplicationLibCode/ProjectDataModel/Formations/RimWellFormationsFile.cpp b/ApplicationLibCode/ProjectDataModel/Formations/RimWellFormationsFile.cpp index c6c6982882..7e1254226c 100644 --- a/ApplicationLibCode/ProjectDataModel/Formations/RimWellFormationsFile.cpp +++ b/ApplicationLibCode/ProjectDataModel/Formations/RimWellFormationsFile.cpp @@ -19,14 +19,13 @@ #include "RimWellFormationsFile.h" #include "RiaLogging.h" -#include "RifCsvHtmlTableTools.h" #include "RimMainPlotCollection.h" #include "RimProject.h" #include "RimWellPath.h" +#include "Tools/RimCsvPreviewTools.h" #include "cafPdmUiFilePathEditor.h" -#include "cafPdmUiTextEditor.h" #include "cafPdmUiTreeOrdering.h" #include @@ -44,10 +43,7 @@ RimWellFormationsFile::RimWellFormationsFile() m_filePath.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() ); CAF_PDM_InitFieldNoDefault( &m_contentTable, "ContentTable", "Content" ); - m_contentTable.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() ); - m_contentTable.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); - m_contentTable.uiCapability()->setUiReadOnly( true ); - m_contentTable.xmlCapability()->disableIO(); + RimCsvPreviewTools::initPreviewField( m_contentTable ); setDeletable( true ); } @@ -223,12 +219,7 @@ void RimWellFormationsFile::defineEditorAttribute( const caf::PdmFieldHandle* fi { if ( field == &m_contentTable ) { - auto myAttr = dynamic_cast( attribute ); - if ( myAttr ) - { - myAttr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap; - myAttr->textMode = caf::PdmUiTextEditorAttribute::HTML; - } + RimCsvPreviewTools::setPreviewEditorAttribute( attribute ); } } @@ -259,15 +250,5 @@ void RimWellFormationsFile::updateUiTreeName() //-------------------------------------------------------------------------------------------------- void RimWellFormationsFile::updateContentTable() { - const int maxRowCount = 1000; - - auto result = RifCsvHtmlTableTools::generateHtmlTableFromFile( filePath(), maxRowCount ); - if ( result ) - { - m_contentTable = *result; - } - else - { - m_contentTable = result.error(); - } + m_contentTable = RimCsvPreviewTools::htmlTableFromFile( filePath() ); } diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index 284366bba3..36bf64c217 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -49,7 +49,6 @@ #include "RimColorLegendItem.h" #include "RimCorrelationMatrixPlot.h" #include "RimCorrelationPlot.h" -#include "RimCorrelationPlotCollection.h" #include "RimCustomObjectiveFunction.h" #include "RimCustomObjectiveFunctionCollection.h" #include "RimEclipseCase.h" @@ -603,13 +602,6 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder << "RicNewAnalysisPlotFeature"; menuBuilder << "RicNewPlotDataFilterFeature"; } - else if ( dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicNewCorrelationPlotFeature"; - menuBuilder << "RicNewCorrelationMatrixPlotFeature"; - menuBuilder << "RicNewParameterResultCrossPlotFeature"; - menuBuilder << "RicNewCorrelationReportPlotFeature"; - } else if ( dynamic_cast( firstUiItem ) ) { menuBuilder << "RicNewCorrelationPlotFeature"; @@ -1072,7 +1064,6 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() menuBuilder << "RicCloseSummaryCaseFeature"; menuBuilder << "RicCloseSummaryCaseInCollectionFeature"; menuBuilder << "RicDeleteSummaryCaseCollectionFeature"; - menuBuilder << "RicCloseObservedDataFeature"; menuBuilder << "RicDeleteSubPlotFeature"; menuBuilder << "RicNewMultiPlotFeature"; diff --git a/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.cpp index b1ae0b5c1c..162b3416f0 100644 --- a/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.cpp @@ -30,6 +30,7 @@ #include "RimObservedEclipseUserData.h" #include "RimObservedFmuRftData.h" #include "RimObservedSummaryData.h" +#include "RimPlotCurve.h" #include "RimPressureDepthData.h" #include "RimProject.h" #include "RimSummaryObservedDataFile.h" @@ -42,7 +43,9 @@ #include "cafPdmUiPropertyViewDialog.h" #include "cafUtils.h" +#include #include +#include CAF_PDM_SOURCE_INIT( RimObservedDataCollection, "ObservedDataCollection" ); @@ -58,24 +61,6 @@ RimObservedDataCollection::RimObservedDataCollection() CAF_PDM_InitFieldNoDefault( &m_observedPressureDepthArray, "PressureDepthDataArray", "" ); } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimObservedDataCollection::removeObservedSummaryData( RimObservedSummaryData* observedData ) -{ - m_observedDataArray.removeChild( observedData ); - caf::PdmUiObjectEditorHandle::updateUiAllObjectEditors(); -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimObservedDataCollection::removeObservedFmuRftData( RimObservedFmuRftData* observedFmuRftData ) -{ - m_observedFmuRftArray.removeChild( observedFmuRftData ); - caf::PdmUiObjectEditorHandle::updateUiAllObjectEditors(); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -105,6 +90,16 @@ std::vector RimObservedDataCollection::allPressureDepthDa //-------------------------------------------------------------------------------------------------- void RimObservedDataCollection::onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector& referringObjects ) { + // Delete curves that referenced the deleted observed data + std::set uniqueReferringObjects( referringObjects.begin(), referringObjects.end() ); + for ( caf::PdmObjectHandle* obj : uniqueReferringObjects ) + { + if ( auto curve = dynamic_cast( obj ) ) + { + curve->deleteIfDataSourceIsMissing(); + } + } + RimMainPlotCollection::current()->loadDataAndUpdateAllPlots(); } @@ -240,7 +235,23 @@ RimObservedSummaryData* RimObservedDataCollection::createAndAddCvsObservedSummar //-------------------------------------------------------------------------------------------------- RimObservedFmuRftData* RimObservedDataCollection::createAndAddFmuRftDataFromPath( const QString& directoryPath ) { - QString name = QString( "Imported FMU RFT Data %1" ).arg( m_observedFmuRftArray.size() + 1 ); + // The data folder is often named e.g. "rft", so include the parent folder to make the name descriptive + const QDir dir( directoryPath ); + QStringList pathParts = QDir::cleanPath( dir.absolutePath() ).split( '/', Qt::SkipEmptyParts ); + QString baseName = pathParts.size() >= 2 ? pathParts.mid( pathParts.size() - 2 ).join( '/' ) : dir.dirName(); + if ( baseName.isEmpty() ) baseName = "Observed FMU Data"; + + QSet existingNames; + for ( const auto& existing : m_observedFmuRftArray ) + { + existingNames.insert( existing->name() ); + } + + QString name = baseName; + for ( int i = 2; existingNames.contains( name ); ++i ) + { + name = QString( "%1 (%2)" ).arg( baseName ).arg( i ); + } RimObservedFmuRftData* fmuRftData = new RimObservedFmuRftData; fmuRftData->setDirectoryPath( directoryPath ); diff --git a/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.h b/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.h index c6b573c782..78680c7036 100644 --- a/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.h +++ b/ApplicationLibCode/ProjectDataModel/RimObservedDataCollection.h @@ -35,8 +35,6 @@ class RimObservedDataCollection : public caf::PdmObject public: RimObservedDataCollection(); - void removeObservedSummaryData( RimObservedSummaryData* observedSummaryData ); - void removeObservedFmuRftData( RimObservedFmuRftData* observedFmuRftData ); RimObservedSummaryData* createAndAddRsmObservedSummaryDataFromFile( const QString& fileName, QString* errorText = nullptr ); RimObservedSummaryData* createAndAddCvsObservedSummaryDataFromFile( const QString& fileName, bool useSavedFieldsValuesInDialog, QString* errorText = nullptr ); diff --git a/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.cpp b/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.cpp index ebcf1e24a9..5e77cb8d3d 100644 --- a/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.cpp @@ -17,6 +17,8 @@ ///////////////////////////////////////////////////////////////////////////////// #include "RimObservedFmuRftData.h" +#include "Tools/RimCsvPreviewTools.h" + //================================================================================================== // // @@ -30,6 +32,7 @@ CAF_PDM_SOURCE_INIT( RimObservedFmuRftData, "ObservedFmuRftData" ); RimObservedFmuRftData::RimObservedFmuRftData() { CAF_PDM_InitObject( "Observed FMU Data", ":/ObservedRFTDataFile16x16.png" ); + setDeletable( true ); CAF_PDM_InitFieldNoDefault( &m_directoryPath, "ObservedFolder", "Directory" ); m_directoryPath.uiCapability()->setUiReadOnly( true ); @@ -38,9 +41,8 @@ RimObservedFmuRftData::RimObservedFmuRftData() m_directoryPath_OBSOLETE.uiCapability()->setUiReadOnly( true ); m_directoryPath_OBSOLETE.xmlCapability()->setIOWritable( false ); - CAF_PDM_InitFieldNoDefault( &m_wells, "Wells", "Wells" ); - m_wells.uiCapability()->setUiReadOnly( true ); - m_wells.registerGetMethod( this, &RimObservedFmuRftData::wells ); + CAF_PDM_InitFieldNoDefault( &m_contentTable, "ContentTable", "Content" ); + RimCsvPreviewTools::initPreviewField( m_contentTable ); } //-------------------------------------------------------------------------------------------------- @@ -58,6 +60,7 @@ void RimObservedFmuRftData::createRftReaderInterface() { m_fmuRftReader = std::make_unique( m_directoryPath().path() ); m_fmuRftReader->importData(); + m_contentTable = ""; } //-------------------------------------------------------------------------------------------------- @@ -124,3 +127,38 @@ void RimObservedFmuRftData::initAfterRead() m_directoryPath = m_directoryPath_OBSOLETE(); } } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimObservedFmuRftData::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) +{ + // Built on demand, as the table is only needed when the data is shown in the property editor + if ( m_contentTable().isEmpty() ) updateContentTable(); + + uiOrdering.add( nameField() ); + uiOrdering.add( &m_directoryPath ); + uiOrdering.add( &m_contentTable ); + uiOrdering.skipRemainingFields(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimObservedFmuRftData::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) +{ + if ( field == &m_contentTable ) + { + RimCsvPreviewTools::setPreviewEditorAttribute( attribute ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimObservedFmuRftData::updateContentTable() +{ + if ( !rftReader() ) return; + + m_contentTable = RimCsvPreviewTools::htmlTableFromText( m_fmuRftReader->csvText() ); +} diff --git a/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.h b/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.h index 54d964b0fa..1a5b23942e 100644 --- a/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.h +++ b/ApplicationLibCode/ProjectDataModel/RimObservedFmuRftData.h @@ -24,7 +24,6 @@ #include "cafPdmField.h" #include "cafPdmObject.h" -#include "cafPdmProxyValueField.h" #include @@ -45,11 +44,16 @@ public: protected: void initAfterRead() override; + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override; + +private: + void updateContentTable(); private: std::unique_ptr m_fmuRftReader; - caf::PdmField m_directoryPath; - caf::PdmField m_directoryPath_OBSOLETE; - caf::PdmProxyValueField> m_wells; + caf::PdmField m_directoryPath; + caf::PdmField m_directoryPath_OBSOLETE; + caf::PdmField m_contentTable; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.cpp b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.cpp index 9b85400b3e..f5b235f3a2 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.cpp @@ -1241,6 +1241,14 @@ void RimPlotCurve::deletePlotCurve() m_plotCurve = nullptr; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimPlotCurve::deleteIfDataSourceIsMissing() +{ + return false; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h index ef132ed7ba..8787b160dd 100644 --- a/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h +++ b/ApplicationLibCode/ProjectDataModel/RimPlotCurve.h @@ -133,11 +133,14 @@ public: void setParentPlotNoReplot( RiuPlotWidget* ); void setParentPlotAndReplot( RiuPlotWidget* ); - void attach( RiuPlotWidget* ); - void detach( bool deletePlotCurve = false ); - void reattach( bool updateParentPlot = true ); - bool isSameCurve( const RiuPlotCurve* plotCurve ) const; - void deletePlotCurve(); + void attach( RiuPlotWidget* ); + void detach( bool deletePlotCurve = false ); + void reattach( bool updateParentPlot = true ); + bool isSameCurve( const RiuPlotCurve* plotCurve ) const; + void deletePlotCurve(); + + // Deletes this curve if its data source is missing. Returns true if deleted; the object must not be accessed afterwards. + virtual bool deleteIfDataSourceIsMissing(); RiuPlotCurve* plotCurve() const; std::vector rectAnnotations() const; diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimObservedSummaryData.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimObservedSummaryData.cpp index 0ba56df7ec..b39b01ffbd 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimObservedSummaryData.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimObservedSummaryData.cpp @@ -33,6 +33,7 @@ CAF_PDM_ABSTRACT_SOURCE_INIT( RimObservedSummaryData, "ObservedData" ); RimObservedSummaryData::RimObservedSummaryData() { m_isObservedData = true; + setDeletable( true ); CAF_PDM_InitFieldNoDefault( &m_importedSummaryData, "ImportedSummaryData", "Imported Summary Data" ); m_importedSummaryData.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() ); diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp index 0853f78942..8c8aa9989d 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.cpp @@ -1398,6 +1398,21 @@ void RimSummaryCurve::updateTimeAnnotations() { } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimSummaryCurve::deleteIfDataSourceIsMissing() +{ + const bool isXCaseMissing = m_xAxisType() == RiaDefines::HorizontalAxisType::SUMMARY_VECTOR && !m_xValuesSummaryCase(); + if ( m_yValuesSummaryCase() && !isXCaseMissing ) return false; + + auto summaryPlot = firstAncestorOrThisOfType(); + if ( !summaryPlot ) return false; + + summaryPlot->deleteCurves( { this } ); + return true; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h index 13ef31016a..15d17dcda0 100644 --- a/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h +++ b/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurve.h @@ -108,6 +108,8 @@ public: virtual bool isRegressionCurve() const; void updateLegendEntryVisibilityNoPlotUpdate() override; + bool deleteIfDataSourceIsMissing() override; + protected: // RimPlotCurve overrides QString createCurveAutoName() override; diff --git a/ApplicationLibCode/ProjectDataModel/Tools/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/Tools/CMakeLists_files.cmake index 0ee78acc35..2ef3444623 100644 --- a/ApplicationLibCode/ProjectDataModel/Tools/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/Tools/CMakeLists_files.cmake @@ -3,6 +3,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimPlotAxisTools.cpp ${CMAKE_CURRENT_LIST_DIR}/RimAutomationSettings.cpp ${CMAKE_CURRENT_LIST_DIR}/RimEclipseViewTools.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimCsvPreviewTools.cpp ) list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.cpp b/ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.cpp new file mode 100644 index 0000000000..1cf3d1ef21 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.cpp @@ -0,0 +1,66 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimCsvPreviewTools.h" + +#include "RifCsvHtmlTableTools.h" + +#include "cafPdmUiFieldHandle.h" +#include "cafPdmUiTextEditor.h" +#include "cafPdmXmlFieldHandle.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCsvPreviewTools::initPreviewField( caf::PdmField& field ) +{ + field.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() ); + field.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::LabelPosition::HIDDEN ); + field.uiCapability()->setUiReadOnly( true ); + field.xmlCapability()->disableIO(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimCsvPreviewTools::setPreviewEditorAttribute( caf::PdmUiEditorAttribute* attribute ) +{ + if ( auto myAttr = dynamic_cast( attribute ) ) + { + myAttr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap; + myAttr->textMode = caf::PdmUiTextEditorAttribute::HTML; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimCsvPreviewTools::htmlTableFromText( const QString& csvText, int maxRowCount ) +{ + auto result = RifCsvHtmlTableTools::generateHtmlTableFromText( csvText, maxRowCount ); + return result ? *result : result.error(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RimCsvPreviewTools::htmlTableFromFile( const QString& filePath, int maxRowCount ) +{ + auto result = RifCsvHtmlTableTools::generateHtmlTableFromFile( filePath, maxRowCount ); + return result ? *result : result.error(); +} diff --git a/ApplicationLibCode/Commands/RicCloseObservedDataFeature.h b/ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.h similarity index 52% rename from ApplicationLibCode/Commands/RicCloseObservedDataFeature.h rename to ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.h index caa39c1f0e..1699a72944 100644 --- a/ApplicationLibCode/Commands/RicCloseObservedDataFeature.h +++ b/ApplicationLibCode/ProjectDataModel/Tools/RimCsvPreviewTools.h @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////////// // -// Copyright (C) 2017- Statoil ASA +// 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 @@ -18,26 +18,30 @@ #pragma once -#include "cafCmdFeature.h" +#include "cafPdmField.h" -#include +#include -class RimObservedFmuRftData; -class RimObservedSummaryData; - -//================================================================================================== -/// -//================================================================================================== -class RicCloseObservedDataFeature : public caf::CmdFeature +namespace caf { - CAF_CMD_HEADER_INIT; +class PdmUiEditorAttribute; +} -public: - static void deleteObservedSummaryData( const std::vector& data ); - static void deleteObservedRmuRftData( const std::vector& data ); +//================================================================================================== +/// Helpers for showing CSV data as a read-only HTML table in the property editor +//================================================================================================== +namespace RimCsvPreviewTools +{ +const int defaultMaxRowCount = 1000; -protected: - bool isCommandEnabled() const override; - void onActionTriggered( bool isChecked ) override; - void setupActionLook( QAction* actionToSetup ) override; -}; +// Configures the field as a read-only, non-persistent HTML text editor without label +void initPreviewField( caf::PdmField& field ); + +// Call from defineEditorAttribute() for the preview field +void setPreviewEditorAttribute( caf::PdmUiEditorAttribute* attribute ); + +// Returns the HTML table, or the error message if the content could not be parsed +QString htmlTableFromText( const QString& csvText, int maxRowCount = defaultMaxRowCount ); +QString htmlTableFromFile( const QString& filePath, int maxRowCount = defaultMaxRowCount ); + +} // namespace RimCsvPreviewTools diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp index f20633ecc7..175c571c7b 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.cpp @@ -327,6 +327,21 @@ RimObservedFmuRftData* RimWellLogRftCurve::observedFmuRftData() const return m_observedFmuRftData; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimWellLogRftCurve::deleteIfDataSourceIsMissing() +{ + if ( m_eclipseCase || m_summaryCase || m_ensemble || m_observedFmuRftData || m_pressureDepthData ) return false; + + auto track = firstAncestorOrThisOfType(); + if ( !track ) return false; + + track->removeCurve( this ); + delete this; + return true; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.h b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.h index 83cef38356..9a5852dcbf 100644 --- a/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.h +++ b/ApplicationLibCode/ProjectDataModel/WellLog/RimWellLogRftCurve.h @@ -96,6 +96,8 @@ public: void setScaleFactor( double factor ); + bool deleteIfDataSourceIsMissing() override; + protected: QString createCurveAutoName() override; QString createCurveNameFromTemplate( const QString& templateText ) override; diff --git a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp index 53625b8cf0..0f64abc3d2 100644 --- a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp @@ -392,8 +392,6 @@ void RimWellPathCollection::addWellPath( RimWellPath* wellPath ) m_wellPaths.push_back( wellPath ); rebuildWellPathNodes(); - - m_mostRecentlyUpdatedWellPath = wellPath; } //-------------------------------------------------------------------------------------------------- @@ -569,8 +567,6 @@ void RimWellPathCollection::addWellPathFormations( const QStringList& filePaths QString wellFormationsCount = QString( "%1" ).arg( formations->formationNamesCount() ); - m_mostRecentlyUpdatedWellPath = wellPath; - outputMessage += wellName + "\t\t"; outputMessage += wellPath->name() + " \t\t\t"; outputMessage += wellFormationsCount + "\n"; @@ -925,14 +921,6 @@ void RimWellPathCollection::groupWellPaths( const std::vector& wel } } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimWellPath* RimWellPathCollection::mostRecentlyUpdatedWellPath() -{ - return m_mostRecentlyUpdatedWellPath; -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h index 5e8c2c42c3..bbd97ecdd0 100644 --- a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h +++ b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h @@ -124,8 +124,6 @@ public: bool showMswSegmentBands() const; - RimWellPath* mostRecentlyUpdatedWellPath(); - void readWellPathFormationFiles(); void reloadAllWellPathFormations(); RimWellPath* wellPathByName( const QString& wellPathName ) const; @@ -189,7 +187,6 @@ private: private: std::unique_ptr m_wellPathImporter; - caf::PdmPointer m_mostRecentlyUpdatedWellPath; caf::PdmChildField m_wellMeasurements; caf::PdmChildField m_eventTimeline; caf::PdmChildArrayField m_wellPaths; diff --git a/ApplicationLibCode/UserInterface/RiuMultiPlotPage.cpp b/ApplicationLibCode/UserInterface/RiuMultiPlotPage.cpp index 8d3b8c61bf..3fd5b22e4b 100644 --- a/ApplicationLibCode/UserInterface/RiuMultiPlotPage.cpp +++ b/ApplicationLibCode/UserInterface/RiuMultiPlotPage.cpp @@ -483,6 +483,10 @@ bool RiuMultiPlotPage::hasHeightForWidth() const //-------------------------------------------------------------------------------------------------- int RiuMultiPlotPage::heightForWidth( int width ) const { + // The page aspect ratio is only relevant in page preview mode. Otherwise, a parent scroll area (e.g. a dock widget) will size the + // page taller than the visible area, and the bottom of the plot is clipped. + if ( !m_previewMode ) return -1; + QPageLayout pageLayout = m_plotDefinition->pageLayout(); QRectF rect = pageLayout.fullRectPoints(); double aspectRatio = rect.height() / rect.width();