diff --git a/ApplicationLibCode/Commands/WellPathCommands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/WellPathCommands/CMakeLists_files.cmake index 169d79b8c3..8eb2c80473 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/WellPathCommands/CMakeLists_files.cmake @@ -20,6 +20,8 @@ ${CMAKE_CURRENT_LIST_DIR}/RicImportWellMeasurementsFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralAtDepthFeature.h ${CMAKE_CURRENT_LIST_DIR}/RicNewWellPathLateralFeature.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/RicPointTangentManipulator.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}/RicNewWellPathLateralFeature.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/RicPointTangentManipulator.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/RicPolyline3dEditor.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 ) diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.cpp new file mode 100644 index 0000000000..ffef2df698 --- /dev/null +++ b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.cpp @@ -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 +// 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 +#include +#include + +#include + +CAF_CMD_SOURCE_INIT( RicCreateMultipleWellPathLaterals, "RicCreateMultipleWellPathLaterals" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicCreateMultipleWellPathLaterals::isCommandEnabled() +{ + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicCreateMultipleWellPathLaterals::onActionTriggered( bool isChecked ) +{ + m_ui = std::make_unique(); + + if ( m_ui.get() == nullptr ) m_ui = std::make_unique(); + + auto selected = dynamic_cast( 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( + 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 ); + } +} diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.h b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.h new file mode 100644 index 0000000000..780a6551fe --- /dev/null +++ b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLaterals.h @@ -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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +#include "RicCreateMultipleWellPathLateralsUi.h" + +#include "cvfVector3.h" + +#include + +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 m_ui; +}; diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.cpp new file mode 100644 index 0000000000..cbcf78f434 --- /dev/null +++ b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.cpp @@ -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 +// 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 + +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 + RicCreateMultipleWellPathLateralsUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, + bool* useOptionsOnly ) +{ + QList 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; +} diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.h b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.h new file mode 100644 index 0000000000..0c7af4671b --- /dev/null +++ b/ApplicationLibCode/Commands/WellPathCommands/RicCreateMultipleWellPathLateralsUi.h @@ -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 +// 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 + +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 calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, + bool* useOptionsOnly ) override; + +private: + caf::PdmPtrField m_sourceLateral; + + caf::PdmChildField m_locations; +}; diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.cpp index bda7fae355..3642511c45 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.cpp +++ b/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.cpp @@ -25,6 +25,7 @@ CAF_CMD_SOURCE_INIT( RicPasteModeledWellPathFeature, "RicPasteModeledWellPathFea #include "RimModeledWellPath.h" #include "RimOilField.h" #include "RimWellPathCollection.h" +#include "RimWellPathTieIn.h" #include "cafPdmObjectGroup.h" #include "cafSelectionManager.h" @@ -36,7 +37,7 @@ CAF_CMD_SOURCE_INIT( RicPasteModeledWellPathFeature, "RicPasteModeledWellPathFea //-------------------------------------------------------------------------------------------------- bool RicPasteModeledWellPathFeature::isCommandEnabled() { - if ( !modeledWellPaths().empty() ) return true; + if ( !modeledWellPathsFromClipboard().empty() ) return true; { std::vector objects; caf::SelectionManager::instance()->objectsByType( &objects ); @@ -55,7 +56,7 @@ bool RicPasteModeledWellPathFeature::isCommandEnabled() //-------------------------------------------------------------------------------------------------- void RicPasteModeledWellPathFeature::onActionTriggered( bool isChecked ) { - if ( modeledWellPaths().empty() ) return; + if ( modeledWellPathsFromClipboard().empty() ) return; RimProject* proj = RimProject::current(); @@ -65,21 +66,28 @@ void RicPasteModeledWellPathFeature::onActionTriggered( bool isChecked ) if ( wellPathCollection ) { - for ( auto souceWellPath : modeledWellPaths() ) + RimModeledWellPath* wellPathToSelect = nullptr; + for ( auto sourceWellPath : modeledWellPathsFromClipboard() ) { - RimModeledWellPath* newModeledWellPath = dynamic_cast( - souceWellPath->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) ); + RimModeledWellPath* destinationWellPath = dynamic_cast( + sourceWellPath->xmlCapability()->copyByXmlSerialization( caf::PdmDefaultObjectFactory::instance() ) ); - QString name = souceWellPath->name() + " (copy)"; - newModeledWellPath->setName( name ); + QString name = sourceWellPath->name() + " (copy)"; + destinationWellPath->setName( name ); - wellPathCollection->addWellPath( newModeledWellPath, false ); - wellPathCollection->uiCapability()->updateConnectedEditors(); + wellPathCollection->addWellPath( destinationWellPath, false ); + wellPathToSelect = destinationWellPath; - proj->scheduleCreateDisplayModelAndRedrawAllViews(); - - Riu3DMainWindowTools::selectAsCurrentItem( newModeledWellPath ); + duplicateLaterals( sourceWellPath, destinationWellPath ); } + + RimTools::wellPathCollection()->rebuildWellPathNodes(); + + wellPathCollection->uiCapability()->updateConnectedEditors(); + + proj->scheduleCreateDisplayModelAndRedrawAllViews(); + + Riu3DMainWindowTools::selectAsCurrentItem( wellPathToSelect ); } } } @@ -96,7 +104,7 @@ void RicPasteModeledWellPathFeature::setupActionLook( QAction* actionToSetup ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::vector RicPasteModeledWellPathFeature::modeledWellPaths() +std::vector RicPasteModeledWellPathFeature::modeledWellPathsFromClipboard() { caf::PdmObjectGroup objectGroup; RicPasteFeatureImpl::findObjectsFromClipboardRefs( &objectGroup ); @@ -112,3 +120,32 @@ std::vector RicPasteModeledWellPathFeature::modeledWellPath 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( lateral ); + if ( !sourceLateral ) continue; + + auto* destinationLateral = dynamic_cast( + 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 ); + } +} diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.h b/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.h index 08504fd737..ce6e553365 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.h +++ b/ApplicationLibCode/Commands/WellPathCommands/RicPasteModeledWellPathFeature.h @@ -34,5 +34,7 @@ protected: void setupActionLook( QAction* actionToSetup ) override; private: - static std::vector modeledWellPaths(); + static std::vector modeledWellPathsFromClipboard(); + + void duplicateLaterals( RimModeledWellPath* source, RimModeledWellPath* destination ); }; diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index 0a298bdc7c..567b8c996e 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -154,6 +154,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RimTimeAxisAnnotation.h ${CMAKE_CURRENT_LIST_DIR}/RimPolylinesDataInterface.h ${CMAKE_CURRENT_LIST_DIR}/RimWellPathTieIn.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}/RimWellPathTieIn.cpp ${CMAKE_CURRENT_LIST_DIR}/cafTreeNode.cpp +${CMAKE_CURRENT_LIST_DIR}/RimMultipleLocations.cpp ) if(Qt5Charts_FOUND) diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index ad0900f045..972e0b60aa 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -404,6 +404,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() if ( dynamic_cast( firstUiItem ) ) { menuBuilder << "RicShowWellPlanFeature"; + menuBuilder << "RicCreateMultipleWellPathLaterals"; } } else if ( dynamic_cast( firstUiItem ) ) diff --git a/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.cpp b/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.cpp new file mode 100644 index 0000000000..ab497bdc82 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.cpp @@ -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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimMultipleLocations.h" + +#include "cafPdmUiDoubleValueEditor.h" +#include "cafPdmUiListEditor.h" + +#include + +CAF_PDM_SOURCE_INIT( RimMultipleLocations, "RimMultipleLocations" ); + +namespace caf +{ +template <> +void AppEnum::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::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& 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 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& locationOfValves ) +{ + if ( locationType != LocationType::UNDEFINED ) + { + m_locationType = locationType; + } + if ( rangeStart != std::numeric_limits::infinity() ) + { + m_rangeStart = rangeStart; + } + if ( rangeEnd != std::numeric_limits::infinity() ) + { + m_rangeEnd = rangeEnd; + } + if ( valveSpacing != std::numeric_limits::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 RimMultipleLocations::locationsFromStartSpacingAndCount( double start, double spacing, size_t count ) +{ + std::vector measuredDepths; + + for ( size_t i = 0; i < count; i++ ) + { + measuredDepths.push_back( start + spacing * i ); + } + + return measuredDepths; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.h b/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.h new file mode 100644 index 0000000000..6b9b85810b --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimMultipleLocations.h @@ -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 +// 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& locations() const; + + void setLocationType( LocationType locationType ); + void computeRangesAndLocations(); + + void initFields( LocationType locationType, + double rangeStart, + double rangeEnd, + double valveSpacing, + int valveCount, + const std::vector& 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 locationsFromStartSpacingAndCount( double start, double spacing, size_t count ); + +private: + caf::PdmField> m_locationType; + caf::PdmField m_rangeStart; + caf::PdmField m_rangeEnd; + caf::PdmField m_rangeSpacing; + caf::PdmField m_rangeCount; + + caf::PdmField m_minimumMD; + caf::PdmField m_maximumMD; + + caf::PdmField> m_locations; // Given in measured depth +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.cpp index 52d2b8e329..7cf65884cd 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.cpp @@ -60,6 +60,7 @@ #include "cafPdmUiTreeOrdering.h" #include "cafProgressInfo.h" +#include #include #include #include @@ -729,7 +730,13 @@ void RimWellPathCollection::removeWellPath( gsl::not_null wellPath bool lessWellPath( const caf::PdmPointer& w1, const caf::PdmPointer& w2 ) { 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() ) return true; else @@ -906,6 +913,11 @@ std::map> rootWells[RimWellPathCollection::unGroupedText()].push_back( wellPath ); } + for ( auto [groupName, wellPaths] : rootWells ) + { + std::sort( wellPaths.begin(), wellPaths.end(), lessWellPath ); + } + return rootWells; } diff --git a/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.h b/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.h index 2f22495b2d..4c3d4b9294 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.h +++ b/ApplicationLibCode/ProjectDataModel/RimWellPathCollection.h @@ -102,6 +102,8 @@ public: void groupWellPaths( const std::vector& wellPaths ); void rebuildWellPathNodes(); + std::vector connectedWellPathLaterals( const RimWellPath* parentWellPath ) const; + RimWellPath* mostRecentlyUpdatedWellPath(); void readWellPathFormationFiles(); @@ -146,7 +148,6 @@ private: cafTreeNode* addWellToWellNode( cafTreeNode* parent, RimWellPath* wellPath ); std::vector wellPathsWithNoParent( const std::vector& sourceWellPaths ) const; - std::vector connectedWellPathLaterals( const RimWellPath* parentWellPath ) const; std::map> wellPathsForWellNameStem( const std::vector& sourceWellPaths ) const; diff --git a/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.cpp b/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.cpp index 2c16124f7f..b825359d27 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.cpp @@ -79,6 +79,14 @@ double RimWellPathTieIn::tieInMeasuredDepth() const return m_tieInMeasuredDepth(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimWellPathTieIn::setTieInMeasuredDepth( double measuredDepth ) +{ + m_tieInMeasuredDepth = measuredDepth; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.h b/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.h index 0edc980799..3405e94aa2 100644 --- a/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.h +++ b/ApplicationLibCode/ProjectDataModel/RimWellPathTieIn.h @@ -36,6 +36,7 @@ public: void connectWellPaths( RimWellPath* parentWell, RimWellPath* childWell, double tieInMeasuredDepth ); RimWellPath* parentWell() const; double tieInMeasuredDepth() const; + void setTieInMeasuredDepth( double measuredDepth ); RimWellPath* childWell() const; void updateChildWellGeometry();