mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#12569 Python: Add API for creating well paths from XYZ coordinates
Add import_fixed_trajectory_well_path() method to WellPathCollection that enables
creation of well paths from coordinate lists in Python. Implements feature request
for programmatic well path creation without file-based imports.
- Add RimFixedTrajectoryWellPath class for coordinate-based well paths
- Add GRPC interface for coordinate data transfer via key-value store
- Add comprehensive Python API with input validation and error handling
- Add example script importing well on Norne.
- Add 7 unit tests covering all functionality and edge cases
The API allows creating well paths like:
well_path = collection.import_fixed_trajectory_well_path(
name="MyWell",
coordinates=[[x1,y1,z1], [x2,y2,z2], ...]
)
This commit is contained in:
1 parent
66756ff21e
commit
83ae9fb7ac
10 files changed
+601
No files matched your search
@@ -2,6 +2,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPath.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathGroup.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimFileWellPath.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimFixedTrajectoryWellPath.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimModeledWellPath.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathGeometryDef.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathGeometryDefTools.h
|
||||
@@ -23,6 +24,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
||||
|
||||
set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimFileWellPath.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimFixedTrajectoryWellPath.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimModeledWellPath.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPath.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimWellPathAttribute.cpp
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025 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 "RimFixedTrajectoryWellPath.h"
|
||||
|
||||
#include "Well/RigWellPath.h"
|
||||
|
||||
// Make sure to include cafPdmFieldScriptingCapabilityCvfVec3d. Include of general cafPdmFieldScriptingCapability causes unity build issues.
|
||||
#include "cafPdmFieldScriptingCapabilityCvfVec3d.h"
|
||||
#include "cafPdmObjectScriptingCapability.h"
|
||||
#include "cafPdmUiOrdering.h"
|
||||
|
||||
CAF_PDM_SOURCE_INIT( RimFixedTrajectoryWellPath, "FixedTrajectoryWellPath" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimFixedTrajectoryWellPath::RimFixedTrajectoryWellPath()
|
||||
{
|
||||
CAF_PDM_InitScriptableObject( "Fixed Trajectory Well Path", ":/Well.svg", "", "FixedTrajectoryWellPath" );
|
||||
|
||||
CAF_PDM_InitScriptableField( &m_trajectoryPoints, "TrajectoryPoints", std::vector<cvf::Vec3d>(), "Trajectory Points" );
|
||||
m_trajectoryPoints.uiCapability()->setUiHidden( true );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimFixedTrajectoryWellPath::~RimFixedTrajectoryWellPath()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimFixedTrajectoryWellPath::setTrajectoryPoints( const std::vector<cvf::Vec3d>& wellTargets )
|
||||
{
|
||||
m_trajectoryPoints = wellTargets;
|
||||
createWellPathGeometry();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<cvf::Vec3d> RimFixedTrajectoryWellPath::trajectoryPoints() const
|
||||
{
|
||||
return m_trajectoryPoints();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimFixedTrajectoryWellPath::createWellPathGeometry()
|
||||
{
|
||||
if ( m_trajectoryPoints().empty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Create well path geometry from the target points
|
||||
std::vector<cvf::Vec3d> wellPathPoints = m_trajectoryPoints();
|
||||
std::vector<double> measuredDepths;
|
||||
|
||||
// Calculate measured depths along the path
|
||||
double totalMD = 0.0;
|
||||
measuredDepths.push_back( totalMD );
|
||||
|
||||
for ( size_t i = 1; i < wellPathPoints.size(); ++i )
|
||||
{
|
||||
cvf::Vec3d segmentVector = wellPathPoints[i] - wellPathPoints[i - 1];
|
||||
double segmentLength = segmentVector.length();
|
||||
totalMD += segmentLength;
|
||||
measuredDepths.push_back( totalMD );
|
||||
}
|
||||
|
||||
auto wellPathGeometry = new RigWellPath( wellPathPoints, measuredDepths );
|
||||
|
||||
setWellPathGeometry( wellPathGeometry );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimFixedTrajectoryWellPath::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
||||
{
|
||||
RimWellPath::defineUiOrdering( uiConfigName, uiOrdering );
|
||||
|
||||
// Hide the well targets field from UI since this is Python-only
|
||||
uiOrdering.skipRemainingFields( true );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimFixedTrajectoryWellPath::initAfterRead()
|
||||
{
|
||||
createWellPathGeometry();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2025 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 "RimWellPath.h"
|
||||
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmFieldCvfVec3d.h"
|
||||
|
||||
#include "cvfVector3.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
class RimFixedTrajectoryWellPath : public RimWellPath
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
RimFixedTrajectoryWellPath();
|
||||
~RimFixedTrajectoryWellPath() override;
|
||||
|
||||
void setTrajectoryPoints( const std::vector<cvf::Vec3d>& wellTargets );
|
||||
std::vector<cvf::Vec3d> trajectoryPoints() const;
|
||||
|
||||
void createWellPathGeometry();
|
||||
|
||||
private:
|
||||
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
|
||||
void initAfterRead() override;
|
||||
|
||||
private:
|
||||
caf::PdmField<std::vector<cvf::Vec3d>> m_trajectoryPoints;
|
||||
};
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
#include "RimcWellPathCollection.h"
|
||||
|
||||
#include "RiaApplication.h"
|
||||
#include "RiaKeyValueStoreUtil.h"
|
||||
#include "RimFixedTrajectoryWellPath.h"
|
||||
#include "RimWellPath.h"
|
||||
#include "RimWellPathCollection.h"
|
||||
|
||||
@@ -25,6 +28,8 @@
|
||||
|
||||
#include "cafPdmFieldScriptingCapability.h"
|
||||
|
||||
#include "cvfVector3.h"
|
||||
|
||||
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellPathCollection, RimcWellPathCollection_importWellPath, "ImportWellPath" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -77,3 +82,98 @@ QString RimcWellPathCollection_importWellPath::classKeywordReturnedType() const
|
||||
{
|
||||
return RimWellPath::classKeywordStatic();
|
||||
}
|
||||
|
||||
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellPathCollection,
|
||||
RimcWellPathCollection_importFixedTrajectoryWellPathInternal,
|
||||
"ImportFixedTrajectoryWellPathInternal" );
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimcWellPathCollection_importFixedTrajectoryWellPathInternal::RimcWellPathCollection_importFixedTrajectoryWellPathInternal( caf::PdmObjectHandle* self )
|
||||
: caf::PdmObjectCreationMethod( self )
|
||||
{
|
||||
CAF_PDM_InitObject( "Import Fixed Trajectory Well Path Internal" );
|
||||
CAF_PDM_InitScriptableFieldNoDefault( &m_name, "Name", "Name" );
|
||||
CAF_PDM_InitScriptableFieldNoDefault( &m_coordinateXKey, "CoordinateXKey", "Coordinate X Key" );
|
||||
CAF_PDM_InitScriptableFieldNoDefault( &m_coordinateYKey, "CoordinateYKey", "Coordinate Y Key" );
|
||||
CAF_PDM_InitScriptableFieldNoDefault( &m_coordinateZKey, "CoordinateZKey", "Coordinate Z Key" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::expected<caf::PdmObjectHandle*, QString> RimcWellPathCollection_importFixedTrajectoryWellPathInternal::execute()
|
||||
{
|
||||
auto wellPathCollection = self<RimWellPathCollection>();
|
||||
if ( !wellPathCollection )
|
||||
{
|
||||
return std::unexpected( QString( "Well path collection is null. Cannot add well path." ) );
|
||||
}
|
||||
|
||||
if ( m_name().isEmpty() )
|
||||
{
|
||||
return std::unexpected( QString( "Name is empty. Cannot add well path." ) );
|
||||
}
|
||||
|
||||
if ( m_coordinateXKey().isEmpty() || m_coordinateYKey().isEmpty() || m_coordinateZKey().isEmpty() )
|
||||
{
|
||||
return std::unexpected( QString( "Coordinate keys are empty. Cannot add well path." ) );
|
||||
}
|
||||
|
||||
// Retrieve coordinates from key-value store
|
||||
auto keyValueStore = RiaApplication::instance()->keyValueStore();
|
||||
|
||||
auto xData = keyValueStore->get( m_coordinateXKey().toStdString() );
|
||||
auto yData = keyValueStore->get( m_coordinateYKey().toStdString() );
|
||||
auto zData = keyValueStore->get( m_coordinateZKey().toStdString() );
|
||||
|
||||
if ( !xData || !yData || !zData )
|
||||
{
|
||||
return std::unexpected( QString( "Failed to retrieve coordinate data from key-value store." ) );
|
||||
}
|
||||
|
||||
std::vector<float> xCoords = RiaKeyValueStoreUtil::convertToFloatVector( xData );
|
||||
std::vector<float> yCoords = RiaKeyValueStoreUtil::convertToFloatVector( yData );
|
||||
std::vector<float> zCoords = RiaKeyValueStoreUtil::convertToFloatVector( zData );
|
||||
|
||||
if ( xCoords.empty() || yCoords.empty() || zCoords.empty() )
|
||||
{
|
||||
return std::unexpected( QString( "Failed to convert coordinate data from key-value store." ) );
|
||||
}
|
||||
|
||||
if ( xCoords.size() != yCoords.size() || yCoords.size() != zCoords.size() )
|
||||
{
|
||||
return std::unexpected(
|
||||
QString( "Coordinate arrays have different sizes: X=%1, Y=%2, Z=%3" ).arg( xCoords.size() ).arg( yCoords.size() ).arg( zCoords.size() ) );
|
||||
}
|
||||
|
||||
// Convert to cvf::Vec3d
|
||||
std::vector<cvf::Vec3d> trajectoryPoints;
|
||||
for ( size_t i = 0; i < xCoords.size(); ++i )
|
||||
{
|
||||
trajectoryPoints.push_back(
|
||||
cvf::Vec3d( static_cast<double>( xCoords[i] ), static_cast<double>( yCoords[i] ), static_cast<double>( zCoords[i] ) ) );
|
||||
}
|
||||
|
||||
// Create the fixed trajectory well path
|
||||
auto fixedTrajectoryWellPath = std::make_unique<RimFixedTrajectoryWellPath>();
|
||||
fixedTrajectoryWellPath->setName( m_name() );
|
||||
fixedTrajectoryWellPath->setTrajectoryPoints( trajectoryPoints );
|
||||
|
||||
auto* wellPath = fixedTrajectoryWellPath.release();
|
||||
wellPathCollection->addWellPath( wellPath );
|
||||
|
||||
wellPathCollection->updateConnectedEditors();
|
||||
wellPathCollection->scheduleRedrawAffectedViews();
|
||||
|
||||
return wellPath;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RimcWellPathCollection_importFixedTrajectoryWellPathInternal::classKeywordReturnedType() const
|
||||
{
|
||||
return RimFixedTrajectoryWellPath::classKeywordStatic();
|
||||
}
|
||||
@@ -40,3 +40,23 @@ public:
|
||||
private:
|
||||
caf::PdmField<QString> m_fileName;
|
||||
};
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
class RimcWellPathCollection_importFixedTrajectoryWellPathInternal : public caf::PdmObjectCreationMethod
|
||||
{
|
||||
CAF_PDM_HEADER_INIT;
|
||||
|
||||
public:
|
||||
RimcWellPathCollection_importFixedTrajectoryWellPathInternal( caf::PdmObjectHandle* self );
|
||||
|
||||
std::expected<caf::PdmObjectHandle*, QString> execute() override;
|
||||
QString classKeywordReturnedType() const override;
|
||||
|
||||
private:
|
||||
caf::PdmField<QString> m_name;
|
||||
caf::PdmField<QString> m_coordinateXKey;
|
||||
caf::PdmField<QString> m_coordinateYKey;
|
||||
caf::PdmField<QString> m_coordinateZKey;
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Example demonstrating how to create well paths from XYZ coordinates
|
||||
using the import_fixed_trajectory_well_path API.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import rips
|
||||
|
||||
|
||||
def create_norne_well():
|
||||
coordinates = [
|
||||
[457121.858, 7322122.992, -371.7],
|
||||
[457121.820, 7322122.956, -404.999],
|
||||
[457121.767, 7322122.946, -434.999],
|
||||
[457123.250, 7322120.048, -644.946],
|
||||
[457132.573, 7322112.750, -914.534],
|
||||
[457132.853, 7322111.663, -1184.492],
|
||||
[457141.541, 7322111.158, -1484.359],
|
||||
[457147.535, 7322109.991, -1694.269],
|
||||
[457152.835, 7322107.659, -1934.198],
|
||||
[457142.201, 7322088.313, -2182.14],
|
||||
[457108.159, 7322025.317, -2335.345],
|
||||
[457077.300, 7321905.985, -2464.557],
|
||||
[457100.629, 7321698.916, -2584.388],
|
||||
[457226.480, 7321457.656, -2629.045],
|
||||
[457374.455, 7321253.227, -2633.377],
|
||||
[457499.332, 7321103.741, -2630.816],
|
||||
[457581.202, 7321019.105, -2630.42],
|
||||
[457661.101, 7320934.536, -2630.212],
|
||||
[457727.784, 7320862.819, -2632.240],
|
||||
]
|
||||
|
||||
return coordinates
|
||||
|
||||
|
||||
resinsight = rips.Instance.find()
|
||||
|
||||
# Get the well path collection
|
||||
well_path_coll = resinsight.project.well_path_collection()
|
||||
|
||||
# Create different types of well paths
|
||||
well_paths_data = [
|
||||
("B 2-H", create_norne_well()),
|
||||
]
|
||||
|
||||
created_wells = []
|
||||
|
||||
for name, coordinates in well_paths_data:
|
||||
print(f"\nCreating well path: {name}")
|
||||
print(f" Number of coordinate points: {len(coordinates)}")
|
||||
print(
|
||||
f" Start: [{coordinates[0][0]:.1f}, {coordinates[0][1]:.1f}, {coordinates[0][2]:.1f}]"
|
||||
)
|
||||
print(
|
||||
f" End: [{coordinates[-1][0]:.1f}, {coordinates[-1][1]:.1f}, {coordinates[-1][2]:.1f}]"
|
||||
)
|
||||
|
||||
# Create the well path using the new API
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name=name, coordinates=coordinates
|
||||
)
|
||||
|
||||
created_wells.append(well_path)
|
||||
@@ -17,6 +17,7 @@ from .contour_map import (
|
||||
)
|
||||
from .well_log_plot import WellLogPlot as WellLogPlot
|
||||
from .well_path import WellPath as WellPath
|
||||
from . import well_path_collection
|
||||
from .simulation_well import SimulationWell as SimulationWell
|
||||
from .exception import RipsError as RipsError
|
||||
from .surface import RegularSurface as RegularSurface
|
||||
|
||||
@@ -46,6 +46,9 @@ disable_error_code = no-untyped-def, no-any-return, attr-defined
|
||||
[mypy-rips.well_log_plot.*]
|
||||
disable_error_code = no-any-return
|
||||
|
||||
[mypy-rips.well_path_collection.*]
|
||||
disable_error_code = attr-defined
|
||||
|
||||
[mypy-rips.contour_map.*]
|
||||
disable_error_code = no-redef
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import sys
|
||||
import os
|
||||
import pytest
|
||||
|
||||
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_basic(rips_instance, initialize_test):
|
||||
"""Test basic creation of a fixed trajectory well path"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Define a simple vertical well path
|
||||
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
|
||||
]
|
||||
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="TestFixedWell", coordinates=coordinates
|
||||
)
|
||||
|
||||
assert well_path is not None
|
||||
assert well_path.name == "TestFixedWell"
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_deviated(rips_instance, initialize_test):
|
||||
"""Test creation of a deviated well path"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Define a deviated well path
|
||||
coordinates = [
|
||||
[1000.0, 2000.0, 0.0], # Surface point
|
||||
[1020.0, 2010.0, -200.0], # Slight deviation
|
||||
[1050.0, 2030.0, -500.0], # More deviation
|
||||
[1100.0, 2080.0, -1000.0], # Final target
|
||||
]
|
||||
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="TestDeviatedWell", coordinates=coordinates
|
||||
)
|
||||
|
||||
assert well_path is not None
|
||||
assert well_path.name == "TestDeviatedWell"
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_validation(rips_instance, initialize_test):
|
||||
"""Test input validation for fixed trajectory well path"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Test empty name
|
||||
with pytest.raises(ValueError, match="Name cannot be empty"):
|
||||
well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="", coordinates=[[1000, 2000, 0]]
|
||||
)
|
||||
|
||||
# Test empty coordinates
|
||||
with pytest.raises(ValueError, match="Coordinates list cannot be empty"):
|
||||
well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="TestWell", coordinates=[]
|
||||
)
|
||||
|
||||
# Test invalid coordinate format - wrong number of values
|
||||
with pytest.raises(ValueError, match="must be a list/tuple of 3 values"):
|
||||
well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="TestWell",
|
||||
coordinates=[[1000, 2000]], # Missing Z coordinate
|
||||
)
|
||||
|
||||
# Test invalid coordinate format - non-numeric values
|
||||
with pytest.raises(ValueError, match="All coordinate values must be numeric"):
|
||||
well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="TestWell", coordinates=[[1000, "invalid", 0]]
|
||||
)
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_multiple(rips_instance, initialize_test):
|
||||
"""Test creating multiple fixed trajectory well paths"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Create first well path
|
||||
coordinates1 = [
|
||||
[1000.0, 2000.0, 0.0],
|
||||
[1000.0, 2000.0, -500.0],
|
||||
]
|
||||
|
||||
well_path1 = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="Well_1", coordinates=coordinates1
|
||||
)
|
||||
|
||||
# Create second well path
|
||||
coordinates2 = [
|
||||
[1500.0, 2500.0, 0.0],
|
||||
[1500.0, 2500.0, -300.0],
|
||||
]
|
||||
|
||||
well_path2 = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="Well_2", coordinates=coordinates2
|
||||
)
|
||||
|
||||
assert well_path1.name == "Well_1"
|
||||
assert well_path2.name == "Well_2"
|
||||
assert well_path1 != well_path2
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_single_point(rips_instance, initialize_test):
|
||||
"""Test creating a well path with a single point"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Single point well path
|
||||
coordinates = [
|
||||
[1000.0, 2000.0, -100.0],
|
||||
]
|
||||
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="SinglePointWell", coordinates=coordinates
|
||||
)
|
||||
|
||||
assert well_path is not None
|
||||
assert well_path.name == "SinglePointWell"
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_various_types(
|
||||
rips_instance, initialize_test
|
||||
):
|
||||
"""Test with various coordinate input types (list, tuple, mixed)"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Mix of list and tuple coordinates
|
||||
coordinates = [
|
||||
[1000.0, 2000.0, 0.0], # List
|
||||
(1010.0, 2010.0, -100.0), # Tuple
|
||||
[1020, 2020, -200], # Integers
|
||||
]
|
||||
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="MixedTypesWell", coordinates=coordinates
|
||||
)
|
||||
|
||||
assert well_path is not None
|
||||
assert well_path.name == "MixedTypesWell"
|
||||
|
||||
|
||||
def test_import_fixed_trajectory_well_path_large_coordinates(
|
||||
rips_instance, initialize_test
|
||||
):
|
||||
"""Test with large coordinate values (typical UTM coordinates)"""
|
||||
well_path_coll = rips_instance.project.well_path_collection()
|
||||
|
||||
# Large UTM-style coordinates
|
||||
coordinates = [
|
||||
[456789.0, 6789012.0, 50.0], # Surface
|
||||
[456790.0, 6789013.0, -1500.0], # Deep point
|
||||
]
|
||||
|
||||
well_path = well_path_coll.import_fixed_trajectory_well_path(
|
||||
name="UTMCoordinatesWell", coordinates=coordinates
|
||||
)
|
||||
|
||||
assert well_path is not None
|
||||
assert well_path.name == "UTMCoordinatesWell"
|
||||
@@ -0,0 +1,84 @@
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
from .pdmobject import add_method
|
||||
from .resinsight_classes import WellPathCollection
|
||||
from .project import Project
|
||||
|
||||
|
||||
@add_method(WellPathCollection)
|
||||
def import_fixed_trajectory_well_path(
|
||||
self: WellPathCollection, name: str, coordinates: List[List[float]]
|
||||
) -> object:
|
||||
"""Create a well path from a list of XYZ coordinates.
|
||||
|
||||
Arguments:
|
||||
name (str): Name of the well path
|
||||
coordinates (List[List[float]]): List of [x, y, z] coordinate triplets
|
||||
|
||||
Returns:
|
||||
RimFixedTrajectoryWellPath: The created well path object
|
||||
|
||||
Raises:
|
||||
ValueError: If coordinates are invalid or have wrong dimensions
|
||||
"""
|
||||
|
||||
if not name:
|
||||
raise ValueError("Name cannot be empty")
|
||||
|
||||
if not coordinates:
|
||||
raise ValueError("Coordinates list cannot be empty")
|
||||
|
||||
# Validate coordinate format
|
||||
for i, coord in enumerate(coordinates):
|
||||
if not isinstance(coord, (list, tuple)) or len(coord) != 3:
|
||||
raise ValueError(
|
||||
f"Coordinate at index {i} must be a list/tuple of 3 values [x, y, z]"
|
||||
)
|
||||
|
||||
# Convert to float to validate numeric values
|
||||
try:
|
||||
float(coord[0])
|
||||
float(coord[1])
|
||||
float(coord[2])
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError(
|
||||
f"All coordinate values must be numeric. Invalid coordinate at index {i}: {coord}"
|
||||
)
|
||||
|
||||
# Extract separate x, y, z arrays
|
||||
x_coords = [float(coord[0]) for coord in coordinates]
|
||||
y_coords = [float(coord[1]) for coord in coordinates]
|
||||
z_coords = [float(coord[2]) for coord in coordinates]
|
||||
|
||||
# Generate temporary keys with shared UUID for key-value store
|
||||
shared_uuid = uuid.uuid4()
|
||||
x_key = f"{shared_uuid}_coordinate_x"
|
||||
y_key = f"{shared_uuid}_coordinate_y"
|
||||
z_key = f"{shared_uuid}_coordinate_z"
|
||||
|
||||
# Store coordinates in key-value store
|
||||
project = self.ancestor(Project)
|
||||
if not project:
|
||||
raise RuntimeError("Could not find parent project")
|
||||
|
||||
project.set_key_values(x_key, x_coords)
|
||||
project.set_key_values(y_key, y_coords)
|
||||
project.set_key_values(z_key, z_coords)
|
||||
|
||||
try:
|
||||
# Call internal method to create the well path
|
||||
well_path = self.import_fixed_trajectory_well_path_internal(
|
||||
name=name,
|
||||
coordinate_x_key=x_key,
|
||||
coordinate_y_key=y_key,
|
||||
coordinate_z_key=z_key,
|
||||
)
|
||||
|
||||
return well_path
|
||||
|
||||
finally:
|
||||
# Clean up temporary keys from key-value store
|
||||
project.remove_key_values(x_key)
|
||||
project.remove_key_values(y_key)
|
||||
project.remove_key_values(z_key)
|
||||
Reference in new issue
Block a user