Files
ResInsight/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp
T
Kristian Bendiksen a4dad97024 #13425 Add time-dependent well events and schedule data generation
Implement timeline-based event system for managing temporal changes in
well completions, perforations, valves, and production controls.

Features:
- Event timeline container with date-based querying
- Six event types: PERF, VALVE, TUBING, WSTATE, WTYPE, WCONTROL
- Schedule keyword generation (DATES, COMPDAT, WELSEGS, WCONPROD, WCONINJE)
- Multi-well schedule generation support
- Python GRPC API for event management
- YAML configuration file support
- Comprehensive test suite

Implementation:
- New directory: ApplicationLibCode/ProjectDataModel/WellEvents/
- Event classes inherit from RimWellEvent base class
- RimWellEventTimeline integrated into RimWellPath
- RicScheduleDataGenerator for multi-well schedule export
- Python API: event_timeline(), add_perf_event(), add_valve_event(), etc.
- Tests verify all event types and YAML config parsing

#13425 Add date tracking to valves and diameter/roughness intervals

Add creation date fields to RimWellPathValve and RimDiameterRoughnessInterval
to support filtering objects by date during completion data export.

- Add m_useCustomStartDate and m_startDate fields
- Add enableCustomStartDate(), setCustomStartDate(), isActiveOnDate() methods
- Set creation dates when applying valve and tubing events in RimWellEventTimeline

#13425 Add date-based filtering for schedule data export

Add optional export date parameter to MSW data collection functions to filter
valves and perforations based on their creation dates during schedule export.

- Add exportDate parameter to collectWsegvalvData, collectWsegAicdData,
  collectWsegSicdData, and collectCompsegData functions
- Filter valves by checking isActiveOnDate() on the associated RimWellPathValve
- Add RimPerforationInterval reference to RicMswPerforation for date filtering
- Pass export date from RicScheduleDataGenerator through the extraction chain

This ensures segment numbers reflect the full model while only including
objects active on the export date in output keywords.

Add test to prove compsegs changes over time.

#13425 Fix mypy type checking errors in well_events.py

- Add type annotations to all function parameters
- Fix generic type hints (dict -> Dict[str, Any])
- Import Any type for proper type safety
- Add type: ignore for method reassignment
- All 31 tests pass

#13425 Add schedule-level keyword events for non-well-specific keywords

Add a new event type called "Keyword Event" for schedule-level Eclipse
keywords (RPTRST, GRUPTREE, RPTSCHED, etc.) that are NOT tied to a
specific well path. This complements the existing RimWellEventKeyword
which operates at the well level.

- Add RimKeywordEvent class inheriting from RimWellEvent with null wellPath
- Add SCHEDULE_KEYWORD enum value to EventType
- Add addKeywordEvent() method to RimWellEventTimeline
- Add AddKeywordEvent Python API method to RimcWellEventTimeline
- Update RicScheduleDataGenerator to output global keywords after well sections
- Add Python wrapper add_keyword_event() with type inference
- Add tests and examples for RPTRST, GRUPTREE keywords
2026-02-12 08:15:22 +01:00

1250 lines
46 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011- Statoil ASA
// Copyright (C) 2013- Ceetron Solutions AS
// Copyright (C) 2011-2012 Ceetron AS
//
// 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 "RimWellPathCollection.h"
#include "Cloud/RiaOsduConnector.h"
#include "RiaColorTables.h"
#include "RiaDateStringParser.h"
#include "RiaGuiApplication.h"
#include "RiaLogging.h"
#include "RiaPreferences.h"
#include "RiaTextStringTools.h"
#include "RiaWellNameComparer.h"
#include "RifOsduWellLogReader.h"
#include "RifOsduWellPathReader.h"
#include "RifWellPathFormationsImporter.h"
#include "RifWellPathImporter.h"
#include "RigEclipseCaseData.h"
#include "RigMainGrid.h"
#include "Well/RigOsduWellLogData.h"
#include "Well/RigWellPath.h"
#include "RimEclipseCase.h"
#include "RimEclipseCaseCollection.h"
#include "RimEclipseCaseTools.h"
#include "RimEclipseView.h"
#include "RimFileWellPath.h"
#include "RimModeledWellPath.h"
#include "RimMswSegmentCollection.h"
#include "RimOilField.h"
#include "RimOsduWellLog.h"
#include "RimOsduWellPath.h"
#include "RimPerforationCollection.h"
#include "RimProject.h"
#include "RimStimPlanModel.h"
#include "RimStimPlanModelCollection.h"
#include "RimWellLogLasFile.h"
#include "RimWellMeasurementCollection.h"
#include "RimWellPath.h"
#include "RimWellPathCompletionSettings.h"
#include "RimWellPathCompletions.h"
#include "RimWellPathTieIn.h"
#include "WellEvents/RimWellEventTimeline.h"
#include "RiuMainWindow.h"
#include "cafTreeNode.h" // TODO: Move to caf
#include "Riu3DMainWindowTools.h"
#include "cafDataLoadController.h"
#include "cafDataLoader.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiEditorHandle.h"
#include "cafPdmUiTreeOrdering.h"
#include "cafProgressInfo.h"
#include <QCollator>
#include <QFile>
#include <QFileInfo>
#include <QString>
#include <cmath>
#include <fstream>
#include <memory>
namespace caf
{
template <>
void RimWellPathCollection::WellVisibilityEnum::setUp()
{
addItem( RimWellPathCollection::FORCE_ALL_OFF, "FORCE_ALL_OFF", "Off" );
addItem( RimWellPathCollection::ALL_ON, "ALL_ON", "Individual" );
addItem( RimWellPathCollection::FORCE_ALL_ON, "FORCE_ALL_ON", "On" );
}
template <>
void RimWellPathCollection::MswGroupingEnum::setUp()
{
addItem( RimWellPathCollection::MswGroupingMode::USE_PREFERENCES, "USE_PREFERENCES", "Use Preferences" );
addItem( RimWellPathCollection::MswGroupingMode::CUSTOM, "CUSTOM", "Custom" );
addItem( RimWellPathCollection::MswGroupingMode::DISABLED, "DISABLED", "Disabled" );
setDefault( RimWellPathCollection::MswGroupingMode::USE_PREFERENCES );
}
} // namespace caf
CAF_PDM_SOURCE_INIT( RimWellPathCollection, "WellPaths" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellPathCollection::RimWellPathCollection()
{
CAF_PDM_InitScriptableObjectWithNameAndComment( "Wells", ":/WellCollection.png", "", "", "WellPathCollection", "Collection of Well Paths" );
CAF_PDM_InitField( &isActive, "Active", true, "Active" );
isActive.uiCapability()->setUiHidden( true );
CAF_PDM_InitField( &showWellPathLabel, "ShowWellPathLabel", true, "Show Well Path Labels" );
cvf::Color3f defWellLabelColor = RiaPreferences::current()->defaultWellLabelColor();
CAF_PDM_InitField( &wellPathLabelColor, "WellPathLabelColor", defWellLabelColor, "Well label color" );
CAF_PDM_InitField( &wellPathVisibility, "GlobalWellPathVisibility", WellVisibilityEnum( ALL_ON ), "Global Well Path Visibility" );
CAF_PDM_InitField( &wellPathRadiusScaleFactor, "WellPathRadiusScale", 0.1, "Well Path Radius Scale" );
CAF_PDM_InitField( &wellPathCrossSectionVertexCount, "WellPathVertexCount", 12, "Well Path Vertex Count" );
wellPathCrossSectionVertexCount.xmlCapability()->disableIO();
wellPathCrossSectionVertexCount.uiCapability()->setUiHidden( true );
CAF_PDM_InitField( &wellPathClip, "WellPathClip", true, "Clip Well Paths" );
CAF_PDM_InitField( &wellPathClipZDistance, "WellPathClipZDistance", 100, "Well Path Clipping Depth Distance" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPaths, "WellPaths", "Well Paths" );
m_wellPaths.uiCapability()->setUiTreeChildrenHidden( true );
CAF_PDM_InitFieldNoDefault( &m_wellMeasurements, "WellMeasurements", "Measurements" );
m_wellMeasurements = new RimWellMeasurementCollection;
CAF_PDM_InitScriptableFieldNoDefault( &m_eventTimeline, "EventTimeline", "Event Timeline" );
m_eventTimeline = new RimWellEventTimeline;
CAF_PDM_InitFieldNoDefault( &m_wellPathNodes, "WellPathNodes", "Well Path Nodes" );
m_wellPathNodes.xmlCapability()->disableIO();
CAF_PDM_InitFieldNoDefault( &m_mswNameGrouping, "MswWellNameGrouping", "Grouping" );
CAF_PDM_InitFieldNoDefault( &m_mswNameGroupingPattern, "MswWellNameGroupingPattern", "Grouping Pattern" );
CAF_PDM_InitFieldNoDefault( &m_mswEclipseCase, "MswEclipseCase", "Eclipse Case" );
CAF_PDM_InitField( &m_mswShowBands, "MseShowBands", true, "Show Bands" );
m_wellPathImporter = std::make_unique<RifWellPathImporter>();
m_wellPathFormationsImporter = std::make_unique<RifWellPathFormationsImporter>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellPathCollection::~RimWellPathCollection()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
scheduleRedrawAffectedViews();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo> RimWellPathCollection::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_mswEclipseCase )
{
options.push_back( caf::PdmOptionItemInfo( "None", nullptr ) );
auto cases = RimEclipseCaseTools::eclipseCases();
for ( auto* c : cases )
{
options.push_back( caf::PdmOptionItemInfo( c->caseUserDescription(), c, false, c->uiIconProvider() ) );
}
}
return options;
}
//--------------------------------------------------------------------------------------------------
/// Read files containing well path data, or create geometry based on the targets
//--------------------------------------------------------------------------------------------------
bool RimWellPathCollection::loadDataAndUpdate()
{
auto hasOsduData = []( const std::vector<RimWellPath*>& wellPaths ) -> bool
{
for ( RimWellPath* wellPath : wellPaths )
{
if ( dynamic_cast<RimOsduWellPath*>( wellPath ) )
{
return true;
}
}
return false;
};
auto countWellLogs = []( const std::vector<RimWellPath*>& wellPaths ) -> bool
{
size_t count = 0;
for ( RimWellPath* wellPath : wellPaths )
{
count += wellPath->wellLogs().size();
}
return count;
};
caf::ProgressInfoEventProcessingBlocker blocker;
caf::ProgressInfo progress( allWellPaths().size() + countWellLogs( allWellPaths() ) + 2, "Reading well paths from file", false, true );
readWellPathFormationFiles();
progress.incrementProgress();
if ( hasOsduData( allWellPaths() ) )
{
if ( auto osduConnector = RiaApplication::instance()->makeOsduConnector() )
{
osduConnector->requestTokenWithCancelButton();
}
}
caf::DataLoadController* dataLoadController = caf::DataLoadController::instance();
const QString wellPathGeometryKeyword = "WELL_PATH_GEOMETRY";
const QString wellLogKeyword = "WELL_LOG";
progress.setProgressDescription( QString( "Reading well path geometry." ) );
for ( RimWellPath* wellPath : allWellPaths() )
{
dataLoadController->loadData( *wellPath, wellPathGeometryKeyword, progress );
}
dataLoadController->blockUntilDone( wellPathGeometryKeyword );
if ( progress.isCancelled() )
{
return false;
}
progress.setProgressDescription( QString( "Reading well logs." ) );
for ( RimWellPath* wellPath : allWellPaths() )
{
for ( RimWellLog* wellLog : wellPath->wellLogs() )
{
dataLoadController->loadData( *wellLog, wellLogKeyword, progress );
}
}
dataLoadController->blockUntilDone( wellLogKeyword );
progress.setProgressDescription( QString( "Reading additional data." ) );
for ( RimWellPath* wellPath : allWellPaths() )
{
RimStimPlanModelCollection* stimPlanModelCollection = wellPath->stimPlanModelCollection();
if ( stimPlanModelCollection )
{
for ( RimStimPlanModel* stimPlanModel : stimPlanModelCollection->allStimPlanModels() )
{
stimPlanModel->loadDataAndUpdate();
}
}
}
progress.incrementProgress();
rebuildWellPathNodes();
sortWellsByName();
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RimWellPathCollection::addWellPaths( QStringList filePaths, QStringList* errorMessages )
{
CAF_ASSERT( errorMessages );
std::vector<RimFileWellPath*> wellPathArray;
for ( const QString& filePath : filePaths )
{
std::expected<std::vector<RimFileWellPath*>, QString> result = addWellPathsForFile( filePath );
if ( result )
{
wellPathArray.insert( wellPathArray.end(), result.value().begin(), result.value().end() );
}
else if ( errorMessages )
{
errorMessages->append( result.error() );
}
}
std::vector<RimWellPath*> addedWellPaths = readAndAddWellPaths( wellPathArray );
CAF_ASSERT( wellPathArray.empty() );
scheduleRedrawAffectedViews();
updateAllRequiredEditors();
return addedWellPaths;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<std::vector<RimFileWellPath*>, QString> RimWellPathCollection::addWellPathsForFile( const QString& filePath )
{
auto isAlreadyLoaded = []( const QString& filePath, const std::vector<RimWellPath*>& wellPaths ) -> bool
{
for ( const auto& wellPath : wellPaths )
{
if ( auto* fWPath = dynamic_cast<RimFileWellPath*>( wellPath ) )
{
QFile f1;
f1.setFileName( filePath );
QFile f2;
f2.setFileName( fWPath->filePath() );
if ( f1.fileName() == f2.fileName() ) return true;
}
}
return false;
};
// Check if this file is already open
bool isJsonFile = filePath.endsWith( "json", Qt::CaseInsensitive );
if ( isJsonFile && isAlreadyLoaded( filePath, m_wellPaths.childrenByType() ) )
{
return std::unexpected( QString( "%1 is already loaded" ).arg( filePath ) );
}
std::vector<RimFileWellPath*> wellPaths;
if ( isJsonFile )
{
auto* wellPath = new RimFileWellPath();
wellPath->setFilepath( filePath );
wellPaths.push_back( wellPath );
}
else
{
// Create Well path objects for all the paths in the assumed ascii file
size_t wellPathCount = m_wellPathImporter->wellDataCount( filePath );
for ( size_t i = 0; i < wellPathCount; ++i )
{
auto* wellPath = new RimFileWellPath();
wellPath->setFilepath( filePath );
wellPath->setWellPathIndexInFile( static_cast<int>( i ) );
wellPaths.push_back( wellPath );
}
}
return wellPaths;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::addWellPath( gsl::not_null<RimWellPath*> wellPath )
{
m_wellPaths.push_back( wellPath );
rebuildWellPathNodes();
m_mostRecentlyUpdatedWellPath = wellPath;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RimWellPathCollection::allWellPaths() const
{
return m_wellPaths.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RimWellPathCollection::readAndAddWellPaths( std::vector<RimFileWellPath*>& wellPathArray )
{
caf::ProgressInfo progress( wellPathArray.size(), "Reading well paths from file" );
// Keep track of the well paths that were actually added.
std::vector<RimWellPath*> addedWellPaths;
for ( RimFileWellPath* wellPath : wellPathArray )
{
wellPath->readWellPathFile( nullptr, m_wellPathImporter.get(), true );
progress.setProgressDescription( QString( "Reading file %1" ).arg( wellPath->name() ) );
wellPath->setWellPathColor( RiaColorTables::wellPathsPaletteColors().cycledColor3f( m_wellPaths.size() ) );
wellPath->setUnitSystem( findUnitSystemForWellPath( wellPath ) );
addWellPath( wellPath );
addedWellPaths.push_back( wellPath );
progress.incrementProgress();
}
wellPathArray.clear(); // This should not be used again. We may have deleted items
groupWellPaths( allWellPaths() );
sortWellsByName();
rebuildWellPathNodes();
return addedWellPaths;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::addWellPaths( const std::vector<RimWellPath*> incomingWellPaths )
{
for ( const auto& wellPath : incomingWellPaths )
{
addWellPath( wellPath );
}
groupWellPaths( allWellPaths() );
sortWellsByName();
rebuildWellPathNodes();
updateAllRequiredEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellLogLasFile*> RimWellPathCollection::addWellLogs( const QStringList& filePaths, QStringList* errorMessages )
{
CAF_ASSERT( errorMessages );
std::vector<RimWellLogLasFile*> logFileInfos;
for ( const QString& filePath : filePaths )
{
QString errorMessage;
RimWellLogLasFile* logFileInfo = RimWellLogLasFile::readWellLogFile( filePath, &errorMessage );
if ( !errorMessage.isEmpty() )
{
errorMessages->push_back( errorMessage );
}
if ( logFileInfo )
{
RimWellPath* wellPath = tryFindMatchingWellPath( logFileInfo->wellName() );
if ( !wellPath )
{
wellPath = new RimWellPath();
addWellPath( wellPath );
}
wellPath->addWellLog( logFileInfo );
logFileInfos.push_back( logFileInfo );
}
}
sortWellsByName();
updateAllRequiredEditors();
return logFileInfos;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::addWellLog( RimWellLog* wellLog, RimWellPath* wellPath )
{
wellPath->addWellLog( wellLog );
sortWellsByName();
updateAllRequiredEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::addWellPathFormations( const QStringList& filePaths )
{
QString outputMessage = "Well Picks Import\n";
outputMessage += "-----------------------------------------------\n";
outputMessage += "Well Name \tDetected Well Path \tCount\n";
bool fileReadSuccess = false;
for ( const QString& filePath : filePaths )
{
std::map<QString, cvf::ref<RigWellPathFormations>> newFormations =
m_wellPathFormationsImporter->readWellPathFormationsFromPath( filePath );
for ( const auto& newFormation : newFormations )
{
fileReadSuccess = true;
RimWellPath* wellPath = tryFindMatchingWellPath( newFormation.first );
if ( !wellPath )
{
wellPath = new RimWellPath();
wellPath->setName( newFormation.first );
addWellPath( wellPath );
RiaLogging::info( QString( "Created new well: %1" ).arg( wellPath->name() ) );
}
wellPath->setFormationsGeometry( newFormation.second );
QString wellFormationsCount = QString( "%1" ).arg( newFormation.second->formationNamesCount() );
m_mostRecentlyUpdatedWellPath = wellPath;
outputMessage += newFormation.first + "\t\t";
outputMessage += wellPath->name() + " \t\t\t";
outputMessage += wellFormationsCount + "\n";
}
}
outputMessage += "-----------------------------------------------";
if ( fileReadSuccess )
{
RiaLogging::errorInMessageBox( Riu3DMainWindowTools::mainWindowWidget(), "Well Picks Import", outputMessage );
}
sortWellsByName();
updateAllRequiredEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
caf::PdmUiGroup* wellHeadGroup = uiOrdering.addNewGroup( "Well labels" );
wellHeadGroup->add( &showWellPathLabel );
wellHeadGroup->add( &wellPathLabelColor );
caf::PdmUiGroup* wellPipe = uiOrdering.addNewGroup( "Well pipe" );
wellPipe->add( &wellPathVisibility );
wellPipe->add( &wellPathRadiusScaleFactor );
caf::PdmUiGroup* advancedGroup = uiOrdering.addNewGroup( "Clipping" );
advancedGroup->add( &wellPathClip );
advancedGroup->add( &wellPathClipZDistance );
caf::PdmUiGroup* wellGrouping = uiOrdering.addNewGroup( "MSW Grouping" );
wellGrouping->add( &m_mswNameGrouping );
if ( m_mswNameGrouping() != MswGroupingMode::DISABLED )
{
if ( m_mswNameGrouping() == MswGroupingMode::USE_PREFERENCES )
{
m_mswNameGroupingPattern = RiaPreferences::current()->multiLateralWellNamePattern();
}
wellGrouping->add( &m_mswNameGroupingPattern );
m_mswNameGroupingPattern.uiCapability()->setUiReadOnly( m_mswNameGrouping() == MswGroupingMode::USE_PREFERENCES );
}
caf::PdmUiGroup* mswSegmentGroup = uiOrdering.addNewGroup( "MSW Segment Visualization" );
mswSegmentGroup->add( &m_mswShowBands );
mswSegmentGroup->add( &m_mswEclipseCase );
mswSegmentGroup->addNewButton( "Update Segments", [this]() { updateMswSegments(); } );
uiOrdering.skipRemainingFields();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName /*= ""*/ )
{
if ( !m_wellMeasurements->isEmpty() )
{
uiTreeOrdering.add( &m_wellMeasurements );
}
if ( m_eventTimeline() && m_eventTimeline()->eventCount() > 0 )
{
uiTreeOrdering.add( &m_eventTimeline );
}
for ( const auto& wellPathNode : m_wellPathNodes() )
{
RimWellPathCollection::buildUiTreeOrdering( wellPathNode, &uiTreeOrdering, uiConfigName );
}
uiTreeOrdering.skipRemainingChildren( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimWellPathCollection::objectToggleField()
{
return &isActive;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::updateMswSegments()
{
for ( const auto& wellPath : m_wellPaths )
{
if ( !wellPath ) continue;
auto* completions = wellPath->completions();
if ( !completions ) continue;
auto* segmentCollection = completions->mswSegmentCollection();
if ( !segmentCollection ) continue;
segmentCollection->updateSegments( m_mswEclipseCase() );
}
if ( RimProject* project = RimProject::current() )
{
project->scheduleCreateDisplayModelAndRedrawAllViews();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimWellPathCollection::showMswSegmentBands() const
{
return m_mswShowBands();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::scheduleRedrawAffectedViews()
{
RimProject* proj = RimProject::current();
if ( proj ) proj->reloadCompletionTypeResultsInAllViews();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimWellPathCollection::anyWellsContainingPerforationIntervals() const
{
for ( const auto& wellPath : m_wellPaths )
{
if ( !wellPath->perforationIntervalCollection()->perforations().empty() ) return true;
}
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimWellPathCollection::modelledWellPathCount() const
{
size_t count = 0;
for ( const auto& wellPath : m_wellPaths )
{
if ( dynamic_cast<const RimModeledWellPath*>( wellPath.p() ) )
{
count++;
}
}
return count;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellPath* RimWellPathCollection::wellPathByName( const QString& wellPathName ) const
{
for ( const auto& wellPath : m_wellPaths )
{
if ( wellPath->name() == wellPathName )
{
return wellPath;
}
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellPath* RimWellPathCollection::tryFindMatchingWellPath( const QString& wellName ) const
{
QString matchedWellPath = RiaWellNameComparer::tryFindMatchingWellPath( wellName );
return !matchedWellPath.isEmpty() ? wellPathByName( matchedWellPath ) : nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::deleteAllWellPaths()
{
m_wellPaths.deleteChildren();
m_wellPathNodes.deleteChildren();
m_wellPathImporter->clear();
updateAllRequiredEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::deleteWell( RimWellPath* wellPath )
{
removeWellPath( wellPath );
m_wellPaths.removeChild( wellPath );
delete wellPath;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::setMswWellPattern( const QString& pattern )
{
if ( pattern.isEmpty() )
{
m_mswNameGrouping = MswGroupingMode::DISABLED;
m_mswNameGroupingPattern = "";
}
else
{
m_mswNameGrouping = MswGroupingMode::CUSTOM;
m_mswNameGroupingPattern = pattern;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::groupWellPaths( const std::vector<RimWellPath*>& wellPaths )
{
const auto& rootWells = wellPathsForWellNameStem( wellPaths );
for ( const auto& [groupName, wellPathsInGroup] : rootWells )
{
if ( groupName == unGroupedText() ) continue;
for ( const auto& wellPathToConnect : wellPathsInGroup )
{
// Assign the group names as well name for export
wellPathToConnect->completionSettings()->setWellNameForExport( groupName );
const auto wellPathGeometry = wellPathToConnect->wellPathGeometry();
if ( wellPathGeometry )
{
std::map<RimWellPath*, double> sharedWellPathLengths;
for ( const auto& otherWellPath : wellPathsInGroup )
{
if ( otherWellPath == wellPathToConnect ) continue;
if ( otherWellPath && otherWellPath->wellPathGeometry() )
{
const double sharedWellPathLength = otherWellPath->wellPathGeometry()->identicalTubeLength( *wellPathGeometry );
const double eps = 1.0e-2;
if ( sharedWellPathLength > eps )
{
sharedWellPathLengths[otherWellPath] = sharedWellPathLength;
}
}
}
RimWellPath* longestSharedWellPath = nullptr;
double longestSharedWellPathLength = 0.0;
for ( const auto& [wellPathCandidate, sharedWellPathLength] : sharedWellPathLengths )
{
if ( wellPathCandidate )
{
const double distanceDifference = fabs( sharedWellPathLength - longestSharedWellPathLength );
const double differenceThreshold = 1.0;
if ( longestSharedWellPath && ( distanceDifference < differenceThreshold ) )
{
// If the main well is named WELL_A, each side steps of a MSW can be given the following names
// WELL_A_Y1
// WELL_A_Y2
// WELL_A_Y3
//
// If Y3 has equal shared geometry with both Y2 and Y1, make sure that Y3 is connected to Y1.
if ( wellPathCandidate->name() < longestSharedWellPath->name() )
{
longestSharedWellPath = wellPathCandidate;
longestSharedWellPathLength = sharedWellPathLength;
}
}
else if ( sharedWellPathLength > longestSharedWellPathLength )
{
longestSharedWellPath = wellPathCandidate;
longestSharedWellPathLength = sharedWellPathLength;
}
}
}
if ( longestSharedWellPath )
{
if ( wellPathToConnect->name() > longestSharedWellPath->name() )
{
wellPathToConnect->connectWellPaths( longestSharedWellPath, longestSharedWellPathLength );
}
else
{
longestSharedWellPath->connectWellPaths( wellPathToConnect, longestSharedWellPathLength );
}
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellPath* RimWellPathCollection::mostRecentlyUpdatedWellPath()
{
return m_mostRecentlyUpdatedWellPath;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::readWellPathFormationFiles()
{
for ( const auto& wellPath : m_wellPaths )
{
QString errorMessage;
if ( !wellPath->readWellPathFormationsFile( &errorMessage, m_wellPathFormationsImporter.get() ) )
{
RiaLogging::errorInMessageBox( Riu3DMainWindowTools::mainWindowWidget(), "File open error", errorMessage );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::reloadAllWellPathFormations()
{
caf::ProgressInfo progress( m_wellPaths.size(), "Reloading well picks from file" );
for ( const auto& wellPath : m_wellPaths )
{
QString errorMessage;
if ( !wellPath->reloadWellPathFormationsFile( &errorMessage, m_wellPathFormationsImporter.get() ) )
{
RiaLogging::errorInMessageBox( Riu3DMainWindowTools::mainWindowWidget(), "File open error", errorMessage );
}
progress.setProgressDescription( QString( "Reloading formation file for %1" ).arg( wellPath->name() ) );
progress.incrementProgress();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::removeWellPath( gsl::not_null<RimWellPath*> wellToRemove )
{
auto* fileWellToRemove = dynamic_cast<RimFileWellPath*>( wellToRemove.get() );
if ( fileWellToRemove )
{
bool isFilePathUsed = false;
for ( const auto& well : m_wellPaths )
{
auto fileWell = dynamic_cast<RimFileWellPath*>( well.p() );
if ( fileWell == fileWellToRemove ) continue;
if ( fileWell && fileWell->filePath() == fileWellToRemove->filePath() )
{
isFilePathUsed = true;
break;
}
}
if ( !isFilePathUsed )
{
// One file can have multiple well paths
// If no other well paths are referencing the filepath, remove cached data from the file reader
m_wellPathImporter->removeFilePath( fileWellToRemove->filePath() );
}
}
updateAllRequiredEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool lessWellPath( const caf::PdmPointer<RimWellPath>& w1, const caf::PdmPointer<RimWellPath>& w2 )
{
if ( w1.notNull() && w2.notNull() )
{
// Use QCollator to sort integer values in addition to text
QCollator collator;
collator.setNumericMode( true );
return collator.compare( w1->name(), w2->name() ) < 0;
}
if ( w1.notNull() ) return true;
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::sortWellsByName()
{
std::sort( m_wellPaths.begin(), m_wellPaths.end(), lessWellPath );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::AppEnum<RiaDefines::EclipseUnitSystem> RimWellPathCollection::findUnitSystemForWellPath( const RimWellPath* wellPath )
{
if ( !wellPath || !wellPath->wellPathGeometry() ) return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
RimProject* project = RimProject::current();
if ( project->activeOilField()->analysisModels->cases.empty() )
{
return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
}
const RigEclipseCaseData* eclipseCaseData = project->activeOilField()->analysisModels->cases()[0]->eclipseCaseData();
if ( !eclipseCaseData || !eclipseCaseData->mainGrid() )
{
return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
}
cvf::BoundingBox caseBoundingBox = eclipseCaseData->mainGrid()->boundingBox();
cvf::BoundingBox wellPathBoundingBox;
for ( const auto& wellPathPoint : wellPath->wellPathGeometry()->wellPathPoints() )
{
wellPathBoundingBox.add( wellPathPoint );
}
if ( wellPathBoundingBox.isValid() && caseBoundingBox.isValid() && caseBoundingBox.intersects( wellPathBoundingBox ) )
{
return eclipseCaseData->unitsType();
}
return RiaDefines::EclipseUnitSystem::UNITS_UNKNOWN;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::rebuildWellPathNodes()
{
m_wellPathNodes.deleteChildren();
std::map<QString, std::vector<RimWellPath*>> rootWells = wellPathsForWellNameStem( m_wellPaths.childrenByType() );
for ( auto [groupName, wellPathGroup] : rootWells )
{
if ( groupName == unGroupedText() )
{
// For single wells, create well paths directly in the well collection folder
for ( auto wellPath : wellPathGroup )
{
if ( !wellPath ) continue;
if ( !wellPath->isTopLevelWellPath() ) continue;
auto node = addWellToWellNode( nullptr, wellPath );
m_wellPathNodes.push_back( node );
}
}
else
{
// Create a group node with group name and put related wells into this group
auto rootNode = new cafNamedTreeNode;
rootNode->setName( groupName );
rootNode->setIcon( ":/WellPathGroup.svg" );
m_wellPathNodes.push_back( rootNode );
for ( auto wellPath : wellPathsWithNoParent( wellPathGroup ) )
{
if ( !wellPath ) continue;
addWellToWellNode( rootNode, wellPath );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cafTreeNode* RimWellPathCollection::addWellToWellNode( cafTreeNode* parent, RimWellPath* wellPath )
{
if ( wellPath == nullptr ) return nullptr;
std::vector<RimWellPath*> wellPaths = connectedWellPathLaterals( wellPath );
auto node = new cafObjectReferenceTreeNode;
node->setReferencedObject( wellPath );
if ( parent ) parent->addChild( node );
for ( auto w : wellPaths )
{
addWellToWellNode( node, w );
}
return node;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RimWellPathCollection::wellPathsWithNoParent( const std::vector<RimWellPath*>& sourceWellPaths ) const
{
std::vector<RimWellPath*> wellPaths;
for ( const auto& w : sourceWellPaths )
{
if ( !w ) continue;
if ( w->isTopLevelWellPath() )
{
wellPaths.push_back( w );
}
}
return wellPaths;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellPath*> RimWellPathCollection::connectedWellPathLaterals( const RimWellPath* parentWellPath ) const
{
if ( !parentWellPath ) return {};
std::vector<RimWellPath*> wellPaths;
for ( const auto& w : m_wellPaths )
{
if ( !w ) continue;
if ( w->wellPathTieIn() && ( w->wellPathTieIn()->parentWell() == parentWellPath ) )
{
wellPaths.push_back( w );
}
}
return wellPaths;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::map<QString, std::vector<RimWellPath*>>
RimWellPathCollection::wellPathsForWellNameStem( const std::vector<RimWellPath*>& sourceWellPaths ) const
{
auto getMultiLateralWellPathPattern = [this]() -> QString
{
if ( m_mswNameGrouping() == MswGroupingMode::DISABLED )
{
return "";
}
else if ( m_mswNameGrouping() == MswGroupingMode::USE_PREFERENCES )
{
return RiaPreferences::current()->multiLateralWellNamePattern();
}
else
{
return m_mswNameGroupingPattern();
}
};
QString multiLateralWellPathPattern = getMultiLateralWellPathPattern();
QString regexPattern = QRegularExpression::wildcardToRegularExpression( multiLateralWellPathPattern );
QRegularExpression re( regexPattern, QRegularExpression::CaseInsensitiveOption );
std::map<QString, std::vector<RimWellPath*>> rootWells;
for ( auto wellPath : sourceWellPaths )
{
QString name = wellPath->name();
if ( re.match( name ).hasMatch() )
{
int indexOfLateralStart = name.indexOf( 'Y' );
if ( indexOfLateralStart > 0 )
{
QString rootWellName = wellPath->name().left( indexOfLateralStart );
rootWells[rootWellName].push_back( wellPath );
continue;
}
}
rootWells[RimWellPathCollection::unGroupedText()].push_back( wellPath );
}
for ( auto [groupName, wellPaths] : rootWells )
{
std::sort( wellPaths.begin(), wellPaths.end(), lessWellPath );
}
return rootWells;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::buildUiTreeOrdering( cafTreeNode* treeNode, caf::PdmUiTreeOrdering* parentUiTreeNode, const QString& uiConfigName )
{
CAF_ASSERT( treeNode );
CAF_ASSERT( parentUiTreeNode );
if ( auto obj = treeNode->referencedObject() )
{
// Create the standard uiTreeOrdering with no relationship to parent using the helper function in
// PdmUiObjectHandle. This will ensure that any child objects will be created correctly including any
// recursive children. If a PdmUiTreeOrdering is created directly based on the PdmObject, andy child objects
// must be added manually.
auto uiTreeNode = obj->uiCapability()->uiTreeOrdering( uiConfigName );
parentUiTreeNode->appendChild( uiTreeNode );
// Build additional child nodes recursively
for ( auto childNode : treeNode->childNodes() )
{
buildUiTreeOrdering( childNode, uiTreeNode, uiConfigName );
}
}
else
{
// If no referenced object is attached, fallback to the node. Not sure if this is the best solution
auto uiTreeNode = parentUiTreeNode->add( treeNode );
// Build additional child nodes recursively
for ( auto childNode : treeNode->childNodes() )
{
buildUiTreeOrdering( childNode, uiTreeNode, uiConfigName );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimWellPathCollection::unGroupedText()
{
return "UnGrouped";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellMeasurementCollection* RimWellPathCollection::measurementCollection()
{
return m_wellMeasurements;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RimWellMeasurementCollection* RimWellPathCollection::measurementCollection() const
{
return m_wellMeasurements;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimWellEventTimeline* RimWellPathCollection::eventTimeline()
{
return m_eventTimeline;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RimWellEventTimeline* RimWellPathCollection::eventTimeline() const
{
return m_eventTimeline;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector<caf::PdmObjectHandle*>& referringObjects )
{
rebuildWellPathNodes();
scheduleRedrawAffectedViews();
uiCapability()->updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::onChildAdded( caf::PdmFieldHandle* containerForNewObject )
{
rebuildWellPathNodes();
scheduleRedrawAffectedViews();
uiCapability()->updateConnectedEditors();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<cvf::ref<RigWellPath>, QString> RimWellPathCollection::loadWellPathGeometryFromOsdu( RiaOsduConnector* osduConnector,
const QString& wellboreTrajectoryId,
double datumElevation )
{
auto [fileContents, errorMessage] = osduConnector->requestWellboreTrajectoryParquetDataByIdBlocking( wellboreTrajectoryId );
if ( !errorMessage.isEmpty() )
{
return { nullptr, errorMessage };
}
return RifOsduWellPathReader::readWellPathData( fileContents, datumElevation );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathCollection::initAfterRead()
{
if ( m_mswNameGrouping() == MswGroupingMode::USE_PREFERENCES )
{
m_mswNameGroupingPattern = RiaPreferences::current()->multiLateralWellNamePattern();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<cvf::ref<RigOsduWellLogData>, QString> RimWellPathCollection::loadWellLogFromOsdu( RiaOsduConnector* osduConnector,
const QString& wellLogId )
{
auto [fileContents, errorMessage] = osduConnector->requestWellLogParquetDataByIdBlocking( wellLogId );
if ( !errorMessage.isEmpty() )
{
return { nullptr, errorMessage };
}
return RifOsduWellLogReader::readWellLogData( fileContents );
}