mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
Add support for connecting a lateral to a parent well from Python (#12613)
* Updates well path connection logic. Refactors well path connection to ensure tie-in location is updated correctly after connecting to a parent well. This ensures the visualization and data consistency are maintained. * Updates connected editors after target manipulation Ensures that connected editors are updated whenever the target is manipulated through the 3D manipulator. * #12613 Adds functionality to append lateral well paths
This commit is contained in:
1 parent
0255d98a2b
commit
01ad456a3b
8 files changed
+315
-1
No files matched your search
+1
@@ -234,6 +234,7 @@ void RicWellTarget3dEditor::slotUpdated( const cvf::Vec3d& origin, const cvf::Ve
|
||||
relativePositionXYD.z() = -relativePositionXYD.z();
|
||||
|
||||
manipulatedTarget->updateFrom3DManipulator( relativePositionXYD );
|
||||
manipulatedTarget->updateConnectedEditors();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -267,6 +267,8 @@ void RimModeledWellPath::updateTieInLocationFromParentWell()
|
||||
m_geometryDefinition->setIsAttachedToParentWell( false );
|
||||
m_geometryDefinition->setFixedWellPathPoints( {} );
|
||||
m_geometryDefinition->setFixedMeasuredDepths( {} );
|
||||
|
||||
updateWellPathVisualization();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,3 +288,13 @@ void RimModeledWellPath::updateReferencePoint()
|
||||
auto refPoint = topLevelModelledWell->geometryDefinition()->anchorPointXyz();
|
||||
m_geometryDefinition->setReferencePointXyz( refPoint );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimModeledWellPath::connectWellPaths( RimWellPath* parentWell, double tieInMeasuredDepth )
|
||||
{
|
||||
RimWellPath::connectWellPaths( parentWell, tieInMeasuredDepth );
|
||||
|
||||
updateTieInLocationFromParentWell();
|
||||
}
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
void updateTieInLocationFromParentWell();
|
||||
void updateReferencePoint();
|
||||
|
||||
virtual void connectWellPaths( RimWellPath* parentWell, double tieInMeasuredDepth ) override;
|
||||
|
||||
private:
|
||||
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) override;
|
||||
void onGeometryDefinitionChanged( const caf::SignalEmitter* emitter, bool fullUpdate );
|
||||
|
||||
@@ -172,7 +172,7 @@ public:
|
||||
std::vector<RimWellPath*> wellPathLaterals() const;
|
||||
|
||||
RimWellPathTieIn* wellPathTieIn() const;
|
||||
void connectWellPaths( RimWellPath* parentWell, double tieInMeasuredDepth );
|
||||
virtual void connectWellPaths( RimWellPath* parentWell, double tieInMeasuredDepth );
|
||||
|
||||
protected:
|
||||
// Override PdmObject
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
|
||||
#include "RigDoglegTools.h"
|
||||
#include "RigStimPlanModelTools.h"
|
||||
#include "Well/RigWellPath.h"
|
||||
#include "Well/RigWellPathGeometryExporter.h"
|
||||
#include "Well/RigWellPathGeometryTools.h"
|
||||
|
||||
@@ -614,3 +615,70 @@ QString RimcWellPath_addWellLogInternal::classKeywordReturnedType() const
|
||||
{
|
||||
return RimImportedWellLog::classKeywordStatic();
|
||||
}
|
||||
|
||||
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellPath, RimcWellPath_appendLateral, "AppendLateral" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimcWellPath_appendLateral::RimcWellPath_appendLateral( caf::PdmObjectHandle* self )
|
||||
: caf::PdmVoidObjectMethod( self )
|
||||
{
|
||||
CAF_PDM_InitObject( "Append Well Path Lateral" );
|
||||
|
||||
CAF_PDM_InitScriptableFieldNoDefault( &m_lateral, "WellPathLateral", "", "", "", "Well Path Lateral" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<caf::PdmObjectHandle*, QString> RimcWellPath_appendLateral::execute()
|
||||
{
|
||||
auto wellPath = self<RimWellPath>();
|
||||
|
||||
const auto mainWellPathGeometry = wellPath->wellPathGeometry();
|
||||
if ( !mainWellPathGeometry )
|
||||
{
|
||||
QString txt = "No geometry available for main well path. Cannot append lateral to main well path.";
|
||||
return std::unexpected( txt );
|
||||
}
|
||||
|
||||
if ( !m_lateral )
|
||||
{
|
||||
QString txt = "No lateral specified. Cannot append lateral to well path.";
|
||||
return std::unexpected( txt );
|
||||
}
|
||||
|
||||
auto lateralGeometry = m_lateral->wellPathGeometry();
|
||||
if ( !lateralGeometry )
|
||||
{
|
||||
QString txt = "No geometry available for lateral. Cannot append lateral to main well path.";
|
||||
return std::unexpected( txt );
|
||||
}
|
||||
|
||||
const double sharedWellPathLength = lateralGeometry->identicalTubeLength( *mainWellPathGeometry );
|
||||
const double epsilon = 1.0e-8;
|
||||
if ( sharedWellPathLength > epsilon )
|
||||
{
|
||||
m_lateral->connectWellPaths( wellPath, sharedWellPathLength );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if ( lateralGeometry->wellPathPoints().empty() )
|
||||
{
|
||||
QString txt = "No points available for lateral. Cannot append lateral to main well path.";
|
||||
return std::unexpected( txt );
|
||||
}
|
||||
|
||||
auto lateralStartPoint = lateralGeometry->wellPathPoints().front();
|
||||
auto closestMdOnMainWell = mainWellPathGeometry->closestMeasuredDepth( lateralStartPoint );
|
||||
if ( closestMdOnMainWell < 0.0 )
|
||||
{
|
||||
QString txt = "Failed to find closest point on main well path. Cannot append lateral to main well path.";
|
||||
return std::unexpected( txt );
|
||||
}
|
||||
|
||||
m_lateral->connectWellPaths( wellPath, closestMdOnMainWell );
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
class RimStimPlanFractureTemplate;
|
||||
class RimThermalFractureTemplate;
|
||||
class RimEclipseCase;
|
||||
class RimWellPath;
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
@@ -165,3 +166,19 @@ private:
|
||||
caf::PdmField<QString> m_tvdMslKey;
|
||||
caf::PdmField<QString> m_tvdRkbKey;
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
class RimcWellPath_appendLateral : public caf::PdmVoidObjectMethod
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
RimcWellPath_appendLateral( caf::PdmObjectHandle* self );
|
||||
|
||||
std::expected<caf::PdmObjectHandle*, QString> execute() override;
|
||||
|
||||
private:
|
||||
caf::PdmPtrField<RimWellPath*> m_lateral;
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import rips
|
||||
|
||||
resinsight = rips.Instance.find()
|
||||
if resinsight is None:
|
||||
raise RuntimeError("No running ResInsight instance found on the expected ports.")
|
||||
|
||||
# Create a modeled well path and add well path targets
|
||||
# The coordinates are based on the Norne case
|
||||
|
||||
well_path_coll = resinsight.project.well_path_collection()
|
||||
well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
well_path.name = "Test Well-1"
|
||||
well_path.update()
|
||||
|
||||
geometry = well_path.well_path_geometry()
|
||||
|
||||
reference_point = geometry.reference_point
|
||||
reference_point[0] = 457196
|
||||
reference_point[1] = 7322270
|
||||
reference_point[2] = 2742
|
||||
geometry.update() # Commit updates back to ResInsight
|
||||
|
||||
# Create the first well target at the reference point
|
||||
coord = [0, 0, 0]
|
||||
geometry.append_well_target(coord)
|
||||
|
||||
# Append new well targets relative the the reference point
|
||||
coord = [454.28, 250, -10]
|
||||
target = geometry.append_well_target(coord)
|
||||
|
||||
coord = [1054.28, 250, -50]
|
||||
target = geometry.append_well_target(coord)
|
||||
|
||||
################################
|
||||
## Create a lateral well path and append it to the main well path
|
||||
################################
|
||||
|
||||
lateral_well = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
lateral_well.name = "Lateral-1"
|
||||
lateral_well.update()
|
||||
|
||||
lateral_geometry = lateral_well.well_path_geometry()
|
||||
|
||||
# Only create geometry starting from the first target of the lateral
|
||||
lateral_geometry.use_auto_generated_target_at_sea_level = False
|
||||
|
||||
reference_point = lateral_geometry.reference_point
|
||||
reference_point[0] = 457550
|
||||
reference_point[1] = 7322481
|
||||
reference_point[2] = 2735
|
||||
lateral_geometry.update() # Commit updates back to ResInsight
|
||||
|
||||
coord = [0, 0, 0]
|
||||
lateral_geometry.append_well_target(coord)
|
||||
|
||||
coord = [454.28, 250, -10]
|
||||
target = lateral_geometry.append_well_target(coord)
|
||||
|
||||
coord = [1054.28, 250, -50]
|
||||
target = lateral_geometry.append_well_target(coord)
|
||||
|
||||
# Append the lateral to the main well path
|
||||
well_path.append_lateral(lateral_well)
|
||||
@@ -236,3 +236,154 @@ def test_perforation_settings(rips_instance, initialize_test):
|
||||
assert non_darcy_parameters_updated.porosity_scaling_factor == 0.59
|
||||
assert non_darcy_parameters_updated.relative_gas_density == 0.23
|
||||
assert non_darcy_parameters_updated.well_radius == 12.12
|
||||
|
||||
|
||||
def test_append_lateral(rips_instance, initialize_test):
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Create main well path
|
||||
main_well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
main_well_path.name = "main_well"
|
||||
main_well_path.update()
|
||||
|
||||
# Create lateral well path
|
||||
lateral_well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
lateral_well_path.name = "lateral_well"
|
||||
lateral_well_path.update()
|
||||
|
||||
# Test appending lateral to main well path
|
||||
# This should fail because well paths don't have geometry defined
|
||||
try:
|
||||
main_well_path.append_lateral(well_path_lateral=lateral_well_path)
|
||||
# If we get here, the operation succeeded unexpectedly
|
||||
assert False, "Expected append_lateral to fail due to missing geometry"
|
||||
except rips.RipsError as e:
|
||||
# Expected error message when no geometry is available
|
||||
expected_msg = "No geometry available for main well path. Cannot append lateral to main well path."
|
||||
assert str(e) == expected_msg
|
||||
|
||||
# Verify the lateral well path objects still exist and have correct names
|
||||
assert lateral_well_path is not None
|
||||
assert lateral_well_path.name == "lateral_well"
|
||||
assert main_well_path is not None
|
||||
assert main_well_path.name == "main_well"
|
||||
|
||||
|
||||
def test_append_lateral_with_geometry(rips_instance, initialize_test):
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Create main well path with geometry using coordinates
|
||||
main_coordinates = [
|
||||
[1000.0, 2000.0, 0.0], # Surface point
|
||||
[1000.0, 2000.0, -100.0], # 100m down
|
||||
[1000.0, 2000.0, -500.0], # 500m down
|
||||
[1000.0, 2000.0, -1000.0], # 1000m down
|
||||
]
|
||||
|
||||
main_well_path = well_path_coll.import_well_path_from_points(
|
||||
name="main_well_with_geometry", coordinates=main_coordinates
|
||||
)
|
||||
|
||||
# Create lateral well path with geometry that starts along the main well path
|
||||
lateral_coordinates = [
|
||||
[1000.0, 2000.0, -300.0], # Start along main well at 300m depth
|
||||
[1020.0, 2020.0, -320.0], # Deviate slightly
|
||||
[1050.0, 2050.0, -350.0], # Continue deviation
|
||||
[1100.0, 2100.0, -400.0], # Final lateral point
|
||||
]
|
||||
|
||||
lateral_well_path = well_path_coll.import_well_path_from_points(
|
||||
name="lateral_well_with_geometry", coordinates=lateral_coordinates
|
||||
)
|
||||
|
||||
# Test appending lateral to main well path - this should work with geometry
|
||||
# Note: The actual connection may still fail if the geometries don't align properly,
|
||||
# but we're testing that the function can be called with valid geometry objects
|
||||
try:
|
||||
main_well_path.append_lateral(well_path_lateral=lateral_well_path)
|
||||
# If successful, verify both well paths still exist
|
||||
assert main_well_path.name == "main_well_with_geometry"
|
||||
assert lateral_well_path.name == "lateral_well_with_geometry"
|
||||
except rips.RipsError as e:
|
||||
# If it fails, it should be for geometric reasons, not missing geometry
|
||||
error_msg = str(e)
|
||||
assert (
|
||||
"No geometry available" not in error_msg
|
||||
), f"Unexpected geometry error: {error_msg}"
|
||||
# Other geometric errors (like alignment issues) are acceptable for this test
|
||||
print(f"Geometric alignment error (expected): {error_msg}")
|
||||
|
||||
# Verify the well path objects are still valid regardless of connection result
|
||||
assert main_well_path is not None
|
||||
assert lateral_well_path is not None
|
||||
|
||||
|
||||
def test_append_lateral_check_connection(rips_instance, initialize_test):
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Create main well path using ModeledWellPath with targets
|
||||
main_well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
main_well_path.name = "main_well_check_connection"
|
||||
main_well_path.update()
|
||||
|
||||
# Add geometry targets to main well path
|
||||
main_geometry = main_well_path.well_path_geometry()
|
||||
main_geometry.append_well_target([1000.0, 2000.0, 0.0]) # Surface
|
||||
main_geometry.append_well_target([1000.0, 2000.0, -100.0]) # 100m down
|
||||
main_geometry.append_well_target([1000.0, 2000.0, -300.0]) # 300m down
|
||||
main_geometry.append_well_target([1000.0, 2000.0, -500.0]) # 500m down
|
||||
main_geometry.update()
|
||||
|
||||
# Create lateral well path using ModeledWellPath with targets
|
||||
lateral_well_path = well_path_coll.add_new_object(rips.ModeledWellPath)
|
||||
lateral_well_path.name = "lateral_well_check_connection"
|
||||
lateral_well_path.update()
|
||||
|
||||
# Add geometry targets to lateral well path
|
||||
lateral_geometry = lateral_well_path.well_path_geometry()
|
||||
lateral_geometry.append_well_target(
|
||||
[1000.0, 2000.0, -150.0]
|
||||
) # Start at 150m on main
|
||||
lateral_geometry.append_well_target([1020.0, 2020.0, -200.0]) # Deviate
|
||||
lateral_geometry.append_well_target([1050.0, 2050.0, -250.0]) # Continue
|
||||
lateral_geometry.update()
|
||||
|
||||
# Get initial geometry state
|
||||
initial_attached_state = lateral_geometry.attached_to_parent_well
|
||||
|
||||
# Test appending lateral to main well path
|
||||
try:
|
||||
main_well_path.append_lateral(well_path_lateral=lateral_well_path)
|
||||
|
||||
# Check if connection was successful by examining the geometry properties
|
||||
lateral_geometry.update() # Refresh from server
|
||||
final_attached_state = lateral_geometry.attached_to_parent_well
|
||||
|
||||
# If connection was successful, verify the parent relationship
|
||||
if final_attached_state and not initial_attached_state:
|
||||
print("Successfully connected lateral to parent well path")
|
||||
assert lateral_well_path.name == "lateral_well_check_connection"
|
||||
assert main_well_path.name == "main_well_check_connection"
|
||||
|
||||
# The lateral should now be marked as attached to parent
|
||||
assert lateral_geometry.attached_to_parent_well
|
||||
else:
|
||||
print("Connection did not change attachment state - may be geometric issue")
|
||||
# Still a valid test if the function completed without error
|
||||
|
||||
except rips.RipsError as e:
|
||||
# Log the error for debugging but don't fail the test
|
||||
# Different geometric configurations may result in different connection outcomes
|
||||
error_msg = str(e)
|
||||
print(f"Connection attempt resulted in error: {error_msg}")
|
||||
|
||||
# Ensure it's not a "no geometry" error since we provided geometry
|
||||
assert (
|
||||
"No geometry available" not in error_msg
|
||||
), f"Unexpected geometry error: {error_msg}"
|
||||
|
||||
# Verify both well paths are still valid
|
||||
assert main_well_path is not None
|
||||
assert lateral_well_path is not None
|
||||
assert main_well_path.name == "main_well_check_connection"
|
||||
assert lateral_well_path.name == "lateral_well_check_connection"
|
||||
Reference in new issue
Block a user