#7818 Modeled Well Path : Add support for creation of N laterals based on a template

This commit is contained in:
Magne Sjaastad 2021-06-28 14:17:46 +02:00
parent 95463c6521
commit 73f8046353
15 changed files with 970 additions and 16 deletions

View File

@ -20,6 +20,8 @@ ${CMAKE_CURRENT_LIST_DIR}/RicImportWellMeasurementsFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralAtDepthFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralAtDepthFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicPasteModeledWellPathFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicPasteModeledWellPathFeature.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateMultipleWellPathLaterals.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateMultipleWellPathLateralsUi.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.h
@ -50,6 +52,8 @@ ${CMAKE_CURRENT_LIST_DIR}/RicImportWellMeasurementsFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralAtDepthFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralAtDepthFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicPasteModeledWellPathFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicPasteModeledWellPathFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateMultipleWellPathLaterals.cpp
${CMAKE_CURRENT_LIST_DIR}/RicCreateMultipleWellPathLateralsUi.cpp
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/Ric3dObjectEditorHandle.cpp
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPointTangentManipulator.cpp
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.cpp ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.cpp
@ -73,6 +77,7 @@ ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellTarget3dEditor.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellPathGeometry3dEditor.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicWellPathGeometry3dEditor.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPolyline3dEditor.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPolyline3dEditor.h
${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPolylineTarget3dEditor.h ${CMAKE_CURRENT_LIST_DIR}/PointTangentManipulator/RicPolylineTarget3dEditor.h
${CMAKE_CURRENT_LIST_DIR}/RicCreateMultipleWellPathLaterals.h
) )
source_group( "CommandFeature\\WellPath" FILES ${SOURCE_GROUP_HEADER_FILES} ${SOURCE_GROUP_SOURCE_FILES} ${CMAKE_CURRENT_LIST_DIR}/CMakeLists_files.cmake ) source_group( "CommandFeature\\WellPath" FILES ${SOURCE_GROUP_HEADER_FILES} ${SOURCE_GROUP_SOURCE_FILES} ${CMAKE_CURRENT_LIST_DIR}/CMakeLists_files.cmake )

View File

@ -0,0 +1,193 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCreateMultipleWellPathLaterals.h"
#include "RigWellPath.h"
#include "RimModeledWellPath.h"
#include "RimProject.h"
#include "RimTools.h"
#include "RimWellPathCollection.h"
#include "RimWellPathGeometryDef.h"
#include "RimWellPathTarget.h"
#include "RimWellPathTieIn.h"
#include "Riu3DMainWindowTools.h"
#include "cafPdmUiPropertyViewDialog.h"
#include "cafSelectionManager.h"
#include "cafSelectionManagerTools.h"
#include <QAction>
#include <QDialogButtonBox>
#include <QPushButton>
#include <memory>
CAF_CMD_SOURCE_INIT( RicCreateMultipleWellPathLaterals, "RicCreateMultipleWellPathLaterals" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicCreateMultipleWellPathLaterals::isCommandEnabled()
{
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLaterals::onActionTriggered( bool isChecked )
{
m_ui = std::make_unique<RicCreateMultipleWellPathLateralsUi>();
if ( m_ui.get() == nullptr ) m_ui = std::make_unique<RicCreateMultipleWellPathLateralsUi>();
auto selected = dynamic_cast<RimModeledWellPath*>( caf::SelectionManager::instance()->selectedItem() );
if ( selected )
{
m_ui->setSourceLateral( selected );
double startMD = 0.0;
double endMD = 0.0;
if ( auto tieIn = selected->wellPathTieIn() )
{
startMD = selected->wellPathTieIn()->tieInMeasuredDepth() + 50.0;
endMD = startMD + 50.0;
if ( auto parentWell = selected->wellPathTieIn()->parentWell() )
{
if ( !parentWell->wellPathGeometry()->measuredDepths().empty() )
{
double candidate = parentWell->wellPathGeometry()->measuredDepths().back() - 50.0;
if ( candidate > startMD ) endMD = candidate;
}
}
}
m_ui->setDefaultValues( startMD, endMD );
}
{
caf::PdmUiPropertyViewDialog propertyDialog( Riu3DMainWindowTools::mainWindowWidget(),
m_ui.get(),
"Create Multiple Well Path Laterals",
"" );
propertyDialog.resize( QSize( 700, 450 ) );
QDialogButtonBox* dialogButtonBox = propertyDialog.dialogButtonBox();
dialogButtonBox->clear();
{
QPushButton* pushButton = dialogButtonBox->addButton( "Create Laterals", QDialogButtonBox::ActionRole );
connect( pushButton, SIGNAL( clicked() ), this, SLOT( slotAppendFractures() ) );
pushButton->setDefault( false );
pushButton->setAutoDefault( false );
pushButton->setToolTip( "Add new fractures" );
}
{
QPushButton* pushButton = dialogButtonBox->addButton( "Close", QDialogButtonBox::ActionRole );
connect( pushButton, SIGNAL( clicked() ), &propertyDialog, SLOT( close() ) );
pushButton->setDefault( false );
pushButton->setAutoDefault( false );
}
propertyDialog.exec();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLaterals::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setText( "Create Multiple Well Path Laterals" );
actionToSetup->setIcon( QIcon( ":/Well.svg" ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLaterals::slotAppendFractures()
{
RimModeledWellPath* sourceLateral = m_ui->sourceLateral();
if ( !sourceLateral ) return;
if ( !sourceLateral->wellPathTieIn()->parentWell() ) return;
auto sourceLocationOfFirstWellTarget = sourceLateral->geometryDefinition()->firstActiveTarget()->targetPointXYZ();
RimWellPathCollection* wellPathCollection = RimTools::wellPathCollection();
if ( wellPathCollection )
{
int index = 0;
for ( auto measuredDepth : m_ui->locationConfig()->locations() )
{
RimModeledWellPath* newModeledWellPath = dynamic_cast<RimModeledWellPath*>(
sourceLateral->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) );
QString name = sourceLateral->name() + QString( " (# %1)" ).arg( index++ );
newModeledWellPath->setName( name );
newModeledWellPath->wellPathTieIn()->setTieInMeasuredDepth( measuredDepth );
wellPathCollection->addWellPath( newModeledWellPath, false );
newModeledWellPath->resolveReferencesRecursively();
newModeledWellPath->updateReferencePoint();
updateLocationOfTargets( newModeledWellPath, sourceLocationOfFirstWellTarget );
newModeledWellPath->updateWellPathVisualization();
}
wellPathCollection->uiCapability()->updateConnectedEditors();
RimProject::current()->scheduleCreateDisplayModelAndRedrawAllViews();
m_ui->updateConnectedEditors();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLaterals::updateLocationOfTargets( RimModeledWellPath* newModeledWellPath,
const cvf::Vec3d& sourceLocationOfFirstWellTarget )
{
newModeledWellPath->updateTieInLocationFromParentWell();
newModeledWellPath->wellPathTieIn()->updateFirstTargetFromParentWell();
auto firstTarget = newModeledWellPath->geometryDefinition()->firstActiveTarget();
auto locationOfFirstWellTarget = firstTarget->targetPointXYZ();
auto offsetFirstTarget = locationOfFirstWellTarget - sourceLocationOfFirstWellTarget;
auto targets = newModeledWellPath->geometryDefinition()->activeWellTargets();
for ( auto wellTarget : targets )
{
// Skip first target, as this is already updated by wellPathTieIn()->updateFirstTargetFromParentWell()
if ( wellTarget == firstTarget ) continue;
auto newTargetLocationXYZ = wellTarget->targetPointXYZ() + offsetFirstTarget;
wellTarget->setPointXYZ( newTargetLocationXYZ );
}
}

View File

@ -0,0 +1,52 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
#include "RicCreateMultipleWellPathLateralsUi.h"
#include "cvfVector3.h"
#include <memory>
class RimModeledWellPath;
//==================================================================================================
///
//==================================================================================================
class RicCreateMultipleWellPathLaterals : public caf::CmdFeature
{
Q_OBJECT
CAF_CMD_HEADER_INIT;
protected:
bool isCommandEnabled() override;
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
private slots:
void slotAppendFractures();
void updateLocationOfTargets( RimModeledWellPath* newModeledWellPath,
const cvf::Vec3d& sourceLocationOfFirstWellTarget );
private:
std::unique_ptr<RicCreateMultipleWellPathLateralsUi> m_ui;
};

View File

@ -0,0 +1,124 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RicCreateMultipleWellPathLateralsUi.h"
#include "RifTextDataTableFormatter.h"
#include "RigMainGrid.h"
#include "RigWellPath.h"
#include "RimModeledWellPath.h"
#include "RimTools.h"
#include "RimWellPathCollection.h"
#include "RimWellPathTieIn.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmUiPropertyViewDialog.h"
#include "cafPdmUiTableViewEditor.h"
#include "cafPdmUiTextEditor.h"
#include "cafSelectionManagerTools.h"
#include "cvfBoundingBox.h"
#include <algorithm>
CAF_PDM_SOURCE_INIT( RicCreateMultipleWellPathLateralsUi, "RicCreateMultipleWellPathLateralsUi" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RicCreateMultipleWellPathLateralsUi::RicCreateMultipleWellPathLateralsUi()
{
CAF_PDM_InitFieldNoDefault( &m_sourceLateral, "SourceLaterals", "Source Well Path Lateral", "", "", "" );
CAF_PDM_InitFieldNoDefault( &m_locations, "Locations", "Locations", "", "", "" );
m_locations = new RimMultipleLocations;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLateralsUi::setSourceLateral( RimModeledWellPath* lateral )
{
m_sourceLateral = lateral;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLateralsUi::setDefaultValues( double start, double end )
{
m_locations->setRange( start, end );
m_locations->computeRangesAndLocations();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimModeledWellPath* RicCreateMultipleWellPathLateralsUi::sourceLateral() const
{
return m_sourceLateral;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimMultipleLocations* RicCreateMultipleWellPathLateralsUi::locationConfig() const
{
return m_locations;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCreateMultipleWellPathLateralsUi::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_sourceLateral );
{
auto group = uiOrdering.addNewGroup( "Locations" );
m_locations->uiOrdering( uiConfigName, *group );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QList<caf::PdmOptionItemInfo>
RicCreateMultipleWellPathLateralsUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions,
bool* useOptionsOnly )
{
QList<caf::PdmOptionItemInfo> options;
if ( fieldNeedingOptions == &m_sourceLateral )
{
if ( sourceLateral()->wellPathTieIn() && sourceLateral()->wellPathTieIn()->parentWell() )
{
auto parentWell = sourceLateral()->wellPathTieIn()->parentWell();
auto laterals = RimTools::wellPathCollection()->connectedWellPathLaterals( parentWell );
for ( auto lateral : laterals )
{
options.push_back( caf::PdmOptionItemInfo( lateral->name(), lateral ) );
}
}
}
return options;
}

View File

@ -0,0 +1,65 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RimMultipleLocations.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmChildField.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "cafPdmProxyValueField.h"
#include "cafPdmPtrField.h"
#include <QPointer>
class RimModeledWellPath;
namespace caf
{
class PdmUiPropertyViewDialog;
}
//==================================================================================================
///
//==================================================================================================
class RicCreateMultipleWellPathLateralsUi : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RicCreateMultipleWellPathLateralsUi();
void setSourceLateral( RimModeledWellPath* lateral );
void setDefaultValues( double start, double end );
RimModeledWellPath* sourceLateral() const;
RimMultipleLocations* locationConfig() const;
private:
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions,
bool* useOptionsOnly ) override;
private:
caf::PdmPtrField<RimModeledWellPath*> m_sourceLateral;
caf::PdmChildField<RimMultipleLocations*> m_locations;
};

View File

@ -25,6 +25,7 @@ CAF_CMD_SOURCE_INIT( RicPasteModeledWellPathFeature, "RicPasteModeledWellPathFea
#include "RimModeledWellPath.h" #include "RimModeledWellPath.h"
#include "RimOilField.h" #include "RimOilField.h"
#include "RimWellPathCollection.h" #include "RimWellPathCollection.h"
#include "RimWellPathTieIn.h"
#include "cafPdmObjectGroup.h" #include "cafPdmObjectGroup.h"
#include "cafSelectionManager.h" #include "cafSelectionManager.h"
@ -36,7 +37,7 @@ CAF_CMD_SOURCE_INIT( RicPasteModeledWellPathFeature, "RicPasteModeledWellPathFea
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
bool RicPasteModeledWellPathFeature::isCommandEnabled() bool RicPasteModeledWellPathFeature::isCommandEnabled()
{ {
if ( !modeledWellPaths().empty() ) return true; if ( !modeledWellPathsFromClipboard().empty() ) return true;
{ {
std::vector<RimWellPathCollection*> objects; std::vector<RimWellPathCollection*> objects;
caf::SelectionManager::instance()->objectsByType( &objects ); caf::SelectionManager::instance()->objectsByType( &objects );
@ -55,7 +56,7 @@ bool RicPasteModeledWellPathFeature::isCommandEnabled()
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
void RicPasteModeledWellPathFeature::onActionTriggered( bool isChecked ) void RicPasteModeledWellPathFeature::onActionTriggered( bool isChecked )
{ {
if ( modeledWellPaths().empty() ) return; if ( modeledWellPathsFromClipboard().empty() ) return;
RimProject* proj = RimProject::current(); RimProject* proj = RimProject::current();
@ -65,21 +66,28 @@ void RicPasteModeledWellPathFeature::onActionTriggered( bool isChecked )
if ( wellPathCollection ) if ( wellPathCollection )
{ {
for ( auto souceWellPath : modeledWellPaths() ) RimModeledWellPath* wellPathToSelect = nullptr;
for ( auto sourceWellPath : modeledWellPathsFromClipboard() )
{ {
RimModeledWellPath* newModeledWellPath = dynamic_cast<RimModeledWellPath*>( RimModeledWellPath* destinationWellPath = dynamic_cast<RimModeledWellPath*>(
souceWellPath->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) ); sourceWellPath->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) );
QString name = souceWellPath->name() + " (copy)"; QString name = sourceWellPath->name() + " (copy)";
newModeledWellPath->setName( name ); destinationWellPath->setName( name );
wellPathCollection->addWellPath( newModeledWellPath, false ); wellPathCollection->addWellPath( destinationWellPath, false );
wellPathCollection->uiCapability()->updateConnectedEditors(); wellPathToSelect = destinationWellPath;
proj->scheduleCreateDisplayModelAndRedrawAllViews(); duplicateLaterals( sourceWellPath, destinationWellPath );
Riu3DMainWindowTools::selectAsCurrentItem( newModeledWellPath );
} }
RimTools::wellPathCollection()->rebuildWellPathNodes();
wellPathCollection->uiCapability()->updateConnectedEditors();
proj->scheduleCreateDisplayModelAndRedrawAllViews();
Riu3DMainWindowTools::selectAsCurrentItem( wellPathToSelect );
} }
} }
} }
@ -96,7 +104,7 @@ void RicPasteModeledWellPathFeature::setupActionLook( QAction* actionToSetup )
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
/// ///
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
std::vector<RimModeledWellPath*> RicPasteModeledWellPathFeature::modeledWellPaths() std::vector<RimModeledWellPath*> RicPasteModeledWellPathFeature::modeledWellPathsFromClipboard()
{ {
caf::PdmObjectGroup objectGroup; caf::PdmObjectGroup objectGroup;
RicPasteFeatureImpl::findObjectsFromClipboardRefs( &objectGroup ); RicPasteFeatureImpl::findObjectsFromClipboardRefs( &objectGroup );
@ -112,3 +120,32 @@ std::vector<RimModeledWellPath*> RicPasteModeledWellPathFeature::modeledWellPath
return wellPaths; return wellPaths;
} }
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicPasteModeledWellPathFeature::duplicateLaterals( RimModeledWellPath* source, RimModeledWellPath* destination )
{
auto wpc = RimTools::wellPathCollection();
auto sourceLaterals = wpc->connectedWellPathLaterals( source );
destination->createWellPathGeometry();
for ( auto lateral : sourceLaterals )
{
auto sourceLateral = dynamic_cast<RimModeledWellPath*>( lateral );
if ( !sourceLateral ) continue;
auto* destinationLateral = dynamic_cast<RimModeledWellPath*>(
sourceLateral->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) );
QString name = sourceLateral->name() + " (copy)";
destinationLateral->setName( name );
wpc->addWellPath( destinationLateral, false );
destinationLateral->connectWellPaths( destination, sourceLateral->wellPathTieIn()->tieInMeasuredDepth() );
duplicateLaterals( sourceLateral, destinationLateral );
}
}

View File

@ -34,5 +34,7 @@ protected:
void setupActionLook( QAction* actionToSetup ) override; void setupActionLook( QAction* actionToSetup ) override;
private: private:
static std::vector<RimModeledWellPath*> modeledWellPaths(); static std::vector<RimModeledWellPath*> modeledWellPathsFromClipboard();
void duplicateLaterals( RimModeledWellPath* source, RimModeledWellPath* destination );
}; };

View File

@ -154,6 +154,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RimTimeAxisAnnotation.h
${CMAKE_CURRENT_LIST_DIR}/RimPolylinesDataInterface.h ${CMAKE_CURRENT_LIST_DIR}/RimPolylinesDataInterface.h
${CMAKE_CURRENT_LIST_DIR}/RimWellPathTieIn.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPathTieIn.h
${CMAKE_CURRENT_LIST_DIR}/cafTreeNode.h ${CMAKE_CURRENT_LIST_DIR}/cafTreeNode.h
${CMAKE_CURRENT_LIST_DIR}/RimMultipleLocations.h
) )
@ -308,6 +309,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RimEquilibriumAxisAnnotation.cpp
${CMAKE_CURRENT_LIST_DIR}/RimTimeAxisAnnotation.cpp ${CMAKE_CURRENT_LIST_DIR}/RimTimeAxisAnnotation.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWellPathTieIn.cpp ${CMAKE_CURRENT_LIST_DIR}/RimWellPathTieIn.cpp
${CMAKE_CURRENT_LIST_DIR}/cafTreeNode.cpp ${CMAKE_CURRENT_LIST_DIR}/cafTreeNode.cpp
${CMAKE_CURRENT_LIST_DIR}/RimMultipleLocations.cpp
) )
if(Qt5Charts_FOUND) if(Qt5Charts_FOUND)

View File

@ -404,6 +404,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
if ( dynamic_cast<RimModeledWellPath*>( firstUiItem ) ) if ( dynamic_cast<RimModeledWellPath*>( firstUiItem ) )
{ {
menuBuilder << "RicShowWellPlanFeature"; menuBuilder << "RicShowWellPlanFeature";
menuBuilder << "RicCreateMultipleWellPathLaterals";
} }
} }
else if ( dynamic_cast<RimWellPathCompletions*>( firstUiItem ) ) else if ( dynamic_cast<RimWellPathCompletions*>( firstUiItem ) )

View File

@ -0,0 +1,369 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimMultipleLocations.h"
#include "cafPdmUiDoubleValueEditor.h"
#include "cafPdmUiListEditor.h"
#include <cmath>
CAF_PDM_SOURCE_INIT( RimMultipleLocations, "RimMultipleLocations" );
namespace caf
{
template <>
void AppEnum<RimMultipleLocations::LocationType>::setUp()
{
addItem( RimMultipleLocations::LocationType::COUNT, "COUNT", "Start/End/Number" );
addItem( RimMultipleLocations::LocationType::SPACING, "SPACING", "Start/End/Spacing" );
addItem( RimMultipleLocations::LocationType::CUSTOM, "CUSTOM", "User Specification" );
setDefault( RimMultipleLocations::LocationType::COUNT );
}
} // namespace caf
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimMultipleLocations::RimMultipleLocations()
{
CAF_PDM_InitObject( "RimMultipleLocations", ":/FishBoneGroup16x16.png", "", "" );
CAF_PDM_InitField( &m_locationType,
"LocationMode",
caf::AppEnum<LocationType>( LocationType::COUNT ),
"Location Defined By",
"",
"",
"" );
CAF_PDM_InitField( &m_rangeStart, "RangeStart", 100.0, "Start MD", "", "", "" );
m_rangeStart.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_rangeEnd, "RangeEnd", 250.0, "End MD", "", "", "" );
m_rangeEnd.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_rangeSpacing, "Spacing", "Spacing", "", "", "" );
m_rangeSpacing.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_minimumMD, "MinimumMD", "Minimum MD", "", "", "" );
m_minimumMD.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitFieldNoDefault( &m_maximumMD, "MaximumMD", "Maximum MD", "", "", "" );
m_maximumMD.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleValueEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_rangeCount, "RangeValveCount", 10, "Number of Items", "", "", "" );
CAF_PDM_InitFieldNoDefault( &m_locations, "Locations", "Measured Depths", "", "", "" );
m_locations.uiCapability()->setUiEditorTypeName( caf::PdmUiListEditor::uiEditorTypeName() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::setRange( double minimumMD, double maximumMD )
{
m_minimumMD = minimumMD;
m_maximumMD = maximumMD;
m_rangeStart = minimumMD;
m_rangeEnd = maximumMD;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::updateRangesAndLocations()
{
double existingRangeStart = m_rangeStart();
double existingRangeEnd = m_rangeEnd();
m_rangeStart = std::clamp( m_rangeStart(), minimumMD(), maximumMD() );
m_rangeEnd = std::clamp( m_rangeEnd(), minimumMD(), maximumMD() );
if ( existingRangeStart != m_rangeStart() || existingRangeEnd != m_rangeEnd() )
{
computeRangesAndLocations();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::measuredDepth( size_t valveIndex ) const
{
return m_locations()[valveIndex];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::rangeStart() const
{
return m_rangeStart;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::rangeEnd() const
{
return m_rangeEnd;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<double>& RimMultipleLocations::locations() const
{
return m_locations();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::setLocationType( LocationType locationType )
{
m_locationType = locationType;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::computeRangesAndLocations()
{
if ( m_locationType == LocationType::COUNT )
{
int divisor = 1;
if ( m_rangeCount > 2 ) divisor = m_rangeCount - 1;
m_rangeSpacing = std::abs( m_rangeStart - m_rangeEnd ) / divisor;
if ( m_rangeSpacing < minimumSpacingMeters() )
{
m_rangeSpacing = minimumSpacingMeters();
m_rangeCount = rangeCountFromSpacing();
}
}
else if ( m_locationType == LocationType::SPACING )
{
m_rangeCount = rangeCountFromSpacing();
}
if ( m_locationType == LocationType::COUNT || m_locationType == LocationType::SPACING )
{
std::vector<double> validMeasuredDepths;
for ( auto md : locationsFromStartSpacingAndCount( m_rangeStart(), m_rangeSpacing, m_rangeCount ) )
{
validMeasuredDepths.push_back( md );
}
m_locations = validMeasuredDepths;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::initFields( LocationType locationType,
double rangeStart,
double rangeEnd,
double valveSpacing,
int valveCount,
const std::vector<double>& locationOfValves )
{
if ( locationType != LocationType::UNDEFINED )
{
m_locationType = locationType;
}
if ( rangeStart != std::numeric_limits<double>::infinity() )
{
m_rangeStart = rangeStart;
}
if ( rangeEnd != std::numeric_limits<double>::infinity() )
{
m_rangeEnd = rangeEnd;
}
if ( valveSpacing != std::numeric_limits<double>::infinity() )
{
m_rangeSpacing = valveSpacing;
}
if ( valveCount != -1 )
{
m_rangeCount = valveCount;
}
if ( !locationOfValves.empty() )
{
m_locations = locationOfValves;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
{
m_locations.uiCapability()->setUiName( "Measured Depths" );
m_rangeStart.uiCapability()->setUiName( "Start MD" );
m_rangeEnd.uiCapability()->setUiName( "End MD" );
m_rangeSpacing.uiCapability()->setUiName( "Spacing" );
}
{
uiOrdering.add( &m_locationType );
if ( m_locationType() != LocationType::CUSTOM )
{
uiOrdering.add( &m_rangeStart );
uiOrdering.add( &m_rangeEnd );
if ( m_locationType() == LocationType::COUNT )
{
uiOrdering.add( &m_rangeCount );
uiOrdering.add( &m_rangeSpacing );
}
else if ( m_locationType() == LocationType::SPACING )
{
uiOrdering.add( &m_rangeSpacing );
uiOrdering.add( &m_rangeCount );
}
}
uiOrdering.add( &m_locations );
}
if ( m_locationType() == LocationType::CUSTOM )
{
m_locations.uiCapability()->setUiReadOnly( false );
m_rangeSpacing.uiCapability()->setUiReadOnly( true );
m_rangeCount.uiCapability()->setUiReadOnly( true );
m_rangeStart.uiCapability()->setUiReadOnly( true );
m_rangeEnd.uiCapability()->setUiReadOnly( true );
}
else
{
m_locations.uiCapability()->setUiReadOnly( true );
m_rangeSpacing.uiCapability()->setUiReadOnly( false );
m_rangeCount.uiCapability()->setUiReadOnly( false );
m_rangeStart.uiCapability()->setUiReadOnly( false );
m_rangeEnd.uiCapability()->setUiReadOnly( false );
if ( m_locationType() == LocationType::COUNT )
{
m_rangeSpacing.uiCapability()->setUiReadOnly( true );
m_rangeCount.uiCapability()->setUiReadOnly( false );
}
else
{
m_rangeSpacing.uiCapability()->setUiReadOnly( false );
m_rangeCount.uiCapability()->setUiReadOnly( true );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimMultipleLocations::fieldChangedByUi( const caf::PdmFieldHandle* changedField,
const QVariant& oldValue,
const QVariant& newValue )
{
bool recomputeLocations = false;
if ( changedField == &m_locationType )
{
if ( m_locationType == LocationType::COUNT || m_locationType == LocationType::SPACING )
{
recomputeLocations = true;
}
}
if ( changedField == &m_rangeStart || changedField == &m_rangeEnd || changedField == &m_rangeCount ||
changedField == &m_rangeSpacing )
{
recomputeLocations = true;
m_rangeStart = std::clamp( m_rangeStart(), minimumMD(), maximumMD() );
m_rangeEnd = std::clamp( m_rangeEnd(), minimumMD(), maximumMD() );
}
if ( changedField == &m_rangeSpacing )
{
double minimumDistanceMeter = minimumSpacingMeters();
m_rangeSpacing =
std::clamp( m_rangeSpacing(), minimumDistanceMeter, std::max( m_rangeSpacing(), minimumDistanceMeter ) );
}
if ( recomputeLocations )
{
computeRangesAndLocations();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimMultipleLocations::rangeCountFromSpacing() const
{
int rangeCount = ( std::fabs( m_rangeStart - m_rangeEnd ) / m_rangeSpacing ) + 1;
if ( rangeCount < 1 )
{
rangeCount = 1;
}
return rangeCount;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::minimumSpacingMeters() const
{
// Minimum distance between fishbones is 13.0m
// Use 10.0m to allow for some flexibility
return 10.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::minimumMD() const
{
return m_rangeStart();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimMultipleLocations::maximumMD() const
{
return m_rangeEnd();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimMultipleLocations::locationsFromStartSpacingAndCount( double start, double spacing, size_t count )
{
std::vector<double> measuredDepths;
for ( size_t i = 0; i < count; i++ )
{
measuredDepths.push_back( start + spacing * i );
}
return measuredDepths;
}

View File

@ -0,0 +1,82 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafAppEnum.h"
#include "cafPdmBase.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
class RimMultipleLocations : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
enum class LocationType
{
COUNT,
SPACING,
CUSTOM,
UNDEFINED
};
public:
RimMultipleLocations();
void setRange( double minimumMD, double maximumMD );
void updateRangesAndLocations();
double measuredDepth( size_t valveIndex ) const;
double rangeStart() const;
double rangeEnd() const;
const std::vector<double>& locations() const;
void setLocationType( LocationType locationType );
void computeRangesAndLocations();
void initFields( LocationType locationType,
double rangeStart,
double rangeEnd,
double valveSpacing,
int valveCount,
const std::vector<double>& locationOfValves );
protected:
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
private:
int rangeCountFromSpacing() const;
double minimumSpacingMeters() const;
double minimumMD() const;
double maximumMD() const;
static std::vector<double> locationsFromStartSpacingAndCount( double start, double spacing, size_t count );
private:
caf::PdmField<caf::AppEnum<LocationType>> m_locationType;
caf::PdmField<double> m_rangeStart;
caf::PdmField<double> m_rangeEnd;
caf::PdmField<double> m_rangeSpacing;
caf::PdmField<int> m_rangeCount;
caf::PdmField<double> m_minimumMD;
caf::PdmField<double> m_maximumMD;
caf::PdmField<std::vector<double>> m_locations; // Given in measured depth
};

View File

@ -60,6 +60,7 @@
#include "cafPdmUiTreeOrdering.h" #include "cafPdmUiTreeOrdering.h"
#include "cafProgressInfo.h" #include "cafProgressInfo.h"
#include <QCollator>
#include <QFile> #include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QString> #include <QString>
@ -729,7 +730,13 @@ void RimWellPathCollection::removeWellPath( gsl::not_null<RimWellPath*> wellPath
bool lessWellPath( const caf::PdmPointer<RimWellPath>& w1, const caf::PdmPointer<RimWellPath>& w2 ) bool lessWellPath( const caf::PdmPointer<RimWellPath>& w1, const caf::PdmPointer<RimWellPath>& w2 )
{ {
if ( w1.notNull() && w2.notNull() ) if ( w1.notNull() && w2.notNull() )
return ( w1->name() < w2->name() ); {
// Use QCollator to sort integer values in addition to text
QCollator collator;
collator.setNumericMode( true );
return collator.compare( w1->name(), w2->name() ) < 0;
}
else if ( w1.notNull() ) else if ( w1.notNull() )
return true; return true;
else else
@ -906,6 +913,11 @@ std::map<QString, std::vector<RimWellPath*>>
rootWells[RimWellPathCollection::unGroupedText()].push_back( wellPath ); rootWells[RimWellPathCollection::unGroupedText()].push_back( wellPath );
} }
for ( auto [groupName, wellPaths] : rootWells )
{
std::sort( wellPaths.begin(), wellPaths.end(), lessWellPath );
}
return rootWells; return rootWells;
} }

View File

@ -102,6 +102,8 @@ public:
void groupWellPaths( const std::vector<RimWellPath*>& wellPaths ); void groupWellPaths( const std::vector<RimWellPath*>& wellPaths );
void rebuildWellPathNodes(); void rebuildWellPathNodes();
std::vector<RimWellPath*> connectedWellPathLaterals( const RimWellPath* parentWellPath ) const;
RimWellPath* mostRecentlyUpdatedWellPath(); RimWellPath* mostRecentlyUpdatedWellPath();
void readWellPathFormationFiles(); void readWellPathFormationFiles();
@ -146,7 +148,6 @@ private:
cafTreeNode* addWellToWellNode( cafTreeNode* parent, RimWellPath* wellPath ); cafTreeNode* addWellToWellNode( cafTreeNode* parent, RimWellPath* wellPath );
std::vector<RimWellPath*> wellPathsWithNoParent( const std::vector<RimWellPath*>& sourceWellPaths ) const; std::vector<RimWellPath*> wellPathsWithNoParent( const std::vector<RimWellPath*>& sourceWellPaths ) const;
std::vector<RimWellPath*> connectedWellPathLaterals( const RimWellPath* parentWellPath ) const;
std::map<QString, std::vector<RimWellPath*>> std::map<QString, std::vector<RimWellPath*>>
wellPathsForWellNameStem( const std::vector<RimWellPath*>& sourceWellPaths ) const; wellPathsForWellNameStem( const std::vector<RimWellPath*>& sourceWellPaths ) const;

View File

@ -79,6 +79,14 @@ double RimWellPathTieIn::tieInMeasuredDepth() const
return m_tieInMeasuredDepth(); return m_tieInMeasuredDepth();
} }
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellPathTieIn::setTieInMeasuredDepth( double measuredDepth )
{
m_tieInMeasuredDepth = measuredDepth;
}
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------
/// ///
//-------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------

View File

@ -36,6 +36,7 @@ public:
void connectWellPaths( RimWellPath* parentWell, RimWellPath* childWell, double tieInMeasuredDepth ); void connectWellPaths( RimWellPath* parentWell, RimWellPath* childWell, double tieInMeasuredDepth );
RimWellPath* parentWell() const; RimWellPath* parentWell() const;
double tieInMeasuredDepth() const; double tieInMeasuredDepth() const;
void setTieInMeasuredDepth( double measuredDepth );
RimWellPath* childWell() const; RimWellPath* childWell() const;
void updateChildWellGeometry(); void updateChildWellGeometry();