Files
ResInsight/GrpcInterface/Python/rips/project.py
T
Kristian Bendiksen e3e09c0d1f #14772 Python: Add WellPath.export_geometry as object method and forward exportWellPaths to it
RimWellPath_exportGeometry writes the well path to a .dev file in an
explicit folder. RicfExportWellPaths resolves the well path names (empty
means all) and the WELLPATHS export folder from the command file
executor, then forwards to it per well path.
Project.export_well_paths() in Python is kept and emits a
DeprecationWarning.
2026-09-22 14:19:23 +02:00

627 lines
18 KiB
Python

# pylint: disable=too-many-arguments
# pylint: disable=no-member
"""
The ResInsight project module
"""
import grpc
import uuid
import warnings
from .case import Case
from .gridcasegroup import GridCaseGroup
from .pdmobject import add_method, add_static_method
from .plot import Plot as Plot
from .view import View
import Commands_pb2
from Definitions_pb2 import Empty
import Project_pb2_grpc
import KeyValueStore_pb2_grpc
import KeyValueStore_pb2
from .exception import RipsError
from .resinsight_classes import (
ColorLegendCollection,
FormationNames,
FractureTemplate,
PlotWindow,
Project,
Reservoir,
SnapshotContentType,
SnapshotFileFormat,
SummaryCase,
WellPath,
)
from typing import Optional, List
@add_method(Project)
def __custom_init__(self, pb2_object, channel: grpc.Channel) -> None:
self._project_stub = Project_pb2_grpc.ProjectStub(self._channel)
self.__key_value_store_stub = KeyValueStore_pb2_grpc.KeyValueStoreStub(
self._channel
)
# Public properties
self.chunk_size = 8160
@add_static_method(Project)
def create(channel: grpc.Channel) -> Project:
project_stub = Project_pb2_grpc.ProjectStub(channel)
pb2_object = project_stub.GetPdmObject(Empty())
return Project(pb2_object, channel)
@add_method(Project)
def open(self, path):
"""Open a new project from the given path
Arguments:
path(str): path to project file
"""
self._execute_command(openProject=Commands_pb2.FilePathRequest(path=path))
return self
@add_method(Project)
def save(self, path=""):
"""Save the project to the existing project file, or to a new file
Arguments:
path(str): File path to the file to save the project to. If empty, saves to the active project file
"""
self._execute_command(saveProject=Commands_pb2.SaveProjectRequest(filePath=path))
return self
@add_method(Project)
def close(self) -> None:
"""Close the current project (and open new blank project)"""
self._execute_command(closeProject=Empty())
@add_method(Project)
def selected_cases(self) -> List[Case]:
"""Get a list of all grid cases selected in the project tree
Returns:
A list of :class:`rips.generated.generated_classes.Case`
"""
case_infos = self._project_stub.GetSelectedCases(Empty())
cases = []
for case_info in case_infos.data:
cases.append(self.case(case_info.id))
return cases
@add_method(Project)
def cases(self: Project) -> List[Reservoir]:
"""Get a list of all grid cases in the project
Returns:
A list of :class:`rips.generated.generated_classes.Case`
"""
return self.descendants(Reservoir)
@add_method(Project)
def case(self: Project, case_id: int) -> Optional[Reservoir]:
"""Get a specific grid case from the provided case Id
Arguments:
id(int): case id
Returns:
:class:`rips.generated.resinsight_classes.Case`
"""
allCases = self.cases()
for case in allCases:
if case.id == case_id:
return case
return None
@add_method(Project)
def replace_source_cases(self, grid_list_file, case_group_id=0):
"""Replace all source grid cases within a case group
Deprecated: use GridCaseGroup.replace_source_cases(grid_files=[...]) on the group object instead,
which takes the grid file paths directly.
Arguments:
grid_list_file (str): path to file containing a list of cases
case_group_id (int): id of the case group to replace
"""
warnings.warn(
"Project.replace_source_cases() is deprecated, use GridCaseGroup.replace_source_cases(grid_files=[...]) instead",
DeprecationWarning,
stacklevel=3,
)
return self._execute_command(
replaceSourceCases=Commands_pb2.ReplaceSourceCasesRequest(
gridListFile=grid_list_file, caseGroupId=case_group_id
)
)
@add_method(Project)
def summary_cases(self):
"""Get a list of all summary cases in the Project
Returns: A list of :class:`rips.generated.resinsight_classes.SummaryCase`
"""
return self.descendants(SummaryCase)
@add_method(Project)
def color_legend_collection(self: Project) -> Optional[ColorLegendCollection]:
"""Get the project's color legend collection
Returns:
:class:`rips.generated.generated_classes.ColorLegendCollection`
"""
collections = self.descendants(ColorLegendCollection)
return collections[0] if collections else None
@add_method(Project)
def views(self):
"""Get a list of views belonging to a project"""
return self.descendants(View)
@add_method(Project)
def view(self, view_id):
"""Get a particular view belonging to a case by providing view id
Arguments:
view_id(int): view id
Returns:
:class:`rips.generated.generated_classes.View`
"""
views = self.views()
for view_object in views:
if view_object.id == view_id:
return view_object
return None
@add_method(Project)
def plots(self):
"""Get a list of all plots belonging to a project
Returns:
List of :class:`rips.generated.generated_classes.Plot`
"""
resinsight_classes = self.descendants(PlotWindow)
plot_list = []
for pdm_object in resinsight_classes:
if pdm_object.id != -1:
plot_list.append(pdm_object)
return plot_list
@add_method(Project)
def plot(self, view_id):
"""Get a particular plot by providing view id
Arguments:
view_id(int): view id
Returns:
:class:`rips.generated.generated_classes.Plot`
"""
plots = self.plots()
for plot_object in plots:
if plot_object.id == view_id:
return plot_object
return None
@add_method(Project)
def grid_case_groups(self):
"""Get a list of all grid case groups in the project
Returns:
List of :class:`rips.generated.generated_classes.GridCaseGroup`
"""
case_groups = self.descendants(GridCaseGroup)
return case_groups
@add_method(Project)
def grid_case_group(self, group_id):
"""Get a particular grid case group belonging to a project
Arguments:
groupId(int): group id
Returns:
:class:`rips.generated.generated_classes.GridCaseGroup`
"""
case_groups = self.grid_case_groups()
for case_group in case_groups:
if case_group.group_id == group_id:
return case_group
return None
@add_method(Project)
def export_multi_case_snapshots(self, grid_list_file):
"""Export snapshots for a set of cases
Arguments:
grid_list_file (str): Path to a file containing a list of grids to export snapshot for
"""
return self._execute_command(
exportMultiCaseSnapshot=Commands_pb2.ExportMultiCaseRequest(
gridListFile=grid_list_file
)
)
@add_method(Project)
def export_snapshots(
self,
snapshot_type="ALL",
prefix="",
plot_format="PNG",
width=-1,
height=-1,
content_type=None,
export_folder="",
plot_file_format=None,
):
"""Export all snapshots of a given type
Arguments:
snapshot_type (str): Deprecated, use content_type. Enum string ('ALL', 'VIEWS' or 'PLOTS')
prefix (str): Exported file name prefix
plot_format(str): Deprecated, use plot_file_format. Enum string, 'PNG' or 'PDF'
width (int): The width of the exported snapshots. By default will use the existing size.
height (int): The height of the exported snapshots. By default will use the existing size.
content_type (SnapshotContentType): 'ALL', 'VIEWS' or 'PLOTS'
export_folder (str): The path to export to. By default will use the 'snapshots' folder next to the project file.
plot_file_format (SnapshotFileFormat): 'PNG' or 'PDF'
"""
if snapshot_type != "ALL":
warnings.warn(
"Project.export_snapshots(snapshot_type=...) is deprecated, use content_type=...",
DeprecationWarning,
stacklevel=3,
)
if plot_format != "PNG":
warnings.warn(
"Project.export_snapshots(plot_format=...) is deprecated, use plot_file_format=...",
DeprecationWarning,
stacklevel=3,
)
if content_type is None:
content_type = SnapshotContentType(snapshot_type)
if plot_file_format is None:
plot_file_format = SnapshotFileFormat(plot_format)
self._call_pdm_method_void(
"exportSnapshots",
content_type=content_type,
export_folder=export_folder,
prefix=prefix,
width=width,
height=height,
plot_file_format=plot_file_format,
)
@add_method(Project)
def export_well_paths(self, well_paths=None, md_step_size=5.0):
"""Export a set of well paths
Deprecated: use WellPath.export_geometry(export_folder=..., md_step_size=...) on each well path
instead. This method writes to the WELLPATHS folder set by Instance.set_export_folder().
Arguments:
well_paths(list): List of strings of well paths. If none, export all.
md_step_size(double): resolution of the exported well path
"""
warnings.warn(
"Project.export_well_paths() is deprecated, use WellPath.export_geometry() with an explicit export_folder instead",
DeprecationWarning,
stacklevel=3,
)
if well_paths is None:
well_paths = []
elif isinstance(well_paths, str):
well_paths = [well_paths]
return self._execute_command(
exportWellPaths=Commands_pb2.ExportWellPathRequest(
wellPathNames=well_paths, mdStepSize=md_step_size
)
)
def _fracture_template_by_id(project, template_id):
for template in project.descendants(FractureTemplate):
if template.id == template_id:
return template
raise RipsError(f"Could not find fracture template with ID {template_id}")
@add_method(Project)
def scale_fracture_template(
self, template_id, half_length, height, d_factor, conductivity
):
"""Scale fracture template parameters
Deprecated: use FractureTemplate.set_scale_factors(...) on the template object instead.
Arguments:
template_id(int): ID of fracture template
half_length (double): Half Length scale factor
height (double): Height scale factor
d_factor (double): D-factor scale factor
conductivity (double): Conductivity scale factor
"""
warnings.warn(
"Project.scale_fracture_template(template_id=...) is deprecated, use FractureTemplate.set_scale_factors() instead",
DeprecationWarning,
stacklevel=3,
)
template = _fracture_template_by_id(self, template_id)
template.set_scale_factors(
half_length=half_length,
height=height,
d_factor=d_factor,
conductivity=conductivity,
)
@add_method(Project)
def set_fracture_containment(self, template_id, top_layer, base_layer):
"""Set fracture template containment parameters
Deprecated: use FractureTemplate.set_containment(...) on the template object instead.
Arguments:
template_id(int): ID of fracture template
top_layer (int): Top layer containment
base_layer (int): Base layer containment
"""
warnings.warn(
"Project.set_fracture_containment(template_id=...) is deprecated, use FractureTemplate.set_containment() instead",
DeprecationWarning,
stacklevel=3,
)
template = _fracture_template_by_id(self, template_id)
template.set_containment(top_layer=top_layer, base_layer=base_layer)
@add_method(Project)
def import_well_paths(self, well_path_files=None, well_path_folder=""):
"""Import well paths into project
Arguments:
well_path_files(list): list of file paths to import
well_path_folder(str): A folder path containing files to import
Returns:
List of :class:`rips.generated.generated_classes.WellPath`
"""
if well_path_files is None:
well_path_files = []
names = self.well_path_collection().import_well_paths(
well_path_files=well_path_files, well_path_folder=well_path_folder
)
well_paths = []
for well_path_name in names.values:
well_paths.append(self.well_path_by_name(well_path_name))
return well_paths
@add_method(Project)
def well_paths(self):
"""Get a list of all well paths in the project
Returns:
List of :class:`rips.generated.generated_classes.WellPath`
"""
return self.descendants(WellPath)
@add_method(Project)
def well_path_by_name(self, well_path_name):
"""Get a specific well path by name from the project
Returns:
:class:`rips.generated.generated_classes.WellPath`
"""
all_well_paths = self.well_paths()
for well_path in all_well_paths:
if well_path.name == well_path_name:
return well_path
return None
@add_method(Project)
def import_well_log_files(self, well_log_files=None, well_log_folder=""):
"""Import well log files into project
Arguments:
well_log_files(list): list of file paths to import
well_log_folder(str): A folder path containing files to import
Returns:
A list of well path names (strings)
"""
if well_log_files is None:
well_log_files = []
names = self.well_path_collection().import_well_log_files(
well_log_files=well_log_files, well_log_folder=well_log_folder
)
return list(names.values)
@add_method(Project)
def import_formation_names(self, formation_files=None, apply_to_all_cases=True):
"""Import formation names into project and apply it to all grid cases in the project
Arguments:
formation_files(list): list of files to import
apply_to_all_cases(bool): assign the imported formation names to all grid cases.
Use Case.set_formation_names() for finer control.
Returns:
:class:`rips.generated.generated_classes.FormationNames`
"""
if formation_files is None:
formation_files = []
elif isinstance(formation_files, str):
formation_files = [formation_files]
formation_names = self._call_pdm_method_return_value(
"importFormationNames", FormationNames, formation_files=formation_files
)
if apply_to_all_cases:
for case in self.descendants(Case):
case.set_formation_names(formation_names=formation_names)
return formation_names
@add_method(Project)
def create_corner_point_grid(
self,
name: str,
nx: int,
ny: int,
nz: int,
coord: List[float],
zcorn: List[float],
actnum: List[int],
):
"""Creates a corner point grid from given parameters.
Arguments:
name(str): Name of the grid.
nx(int): Number of cells in x direction
ny(int): Number of cells in y direction
nz(int): Number of cells in z direction
coord(list[float]): Coordinate lines as COORD keyword in Eclipse.
Each coordinate line is defined by two points (top and bottom).
Size: (nx+1) * (ny+1) * 2 * 3. Points are ordered as in Eclipse.
zcorn(list[float]): Corner depths as defined by the Eclipse keyword ZCORN.
Size: nx * ny * nz * 8
actnum(list[int]): Active cell info: cells with values > 0 are active.
Size: nx * ny * nz
"""
# Generate unique keys for three arrays
coord_key = "{}_{}".format(uuid.uuid4(), "coord")
zcorn_key = "{}_{}".format(uuid.uuid4(), "zcorn")
actnum_key = "{}_{}".format(uuid.uuid4(), "actnum")
self.set_key_values(coord_key, coord)
self.set_key_values(zcorn_key, zcorn)
self.set_key_values(actnum_key, actnum)
return self.create_grid_from_key_values(
name=name,
nx=nx,
ny=ny,
nz=nz,
coord_key=coord_key,
zcorn_key=zcorn_key,
actnum_key=actnum_key,
)
@add_method(Project)
def set_key_values(self, key, values):
"""Sets values for a given key in the key-value store.
Arguments:
key(str): The key (should be unique).
values(list): a list of double precision floating point numbers
"""
request_iterator = self.__generate_key_value_store_input_chunks(values, key)
reply = self.__key_value_store_stub.SetValue(request_iterator)
if reply.accepted_value_count < len(values):
raise IndexError
@add_method(Project)
def __generate_key_value_store_input_chunks(self, array, name):
index = -1
while index < len(array):
chunk = KeyValueStore_pb2.KeyValueStoreInputChunk()
if index == -1:
parameters = KeyValueStore_pb2.KeyValueInputParameters(
name=name, num_elements=len(array)
)
chunk.parameters.CopyFrom(parameters)
index += 1
else:
actual_chunk_size = min(len(array) - index + 1, self.chunk_size)
chunk.values.CopyFrom(
KeyValueStore_pb2.KeyValueStoreChunk(
values=array[index : index + actual_chunk_size]
)
)
index += actual_chunk_size
yield chunk
# Final empty message to signal completion
chunk = KeyValueStore_pb2.KeyValueStoreInputChunk()
yield chunk
@add_method(Project)
def key_values_async(
self,
key,
):
"""Get float values for a given key. Async: returns an iterator.
Arguments:
key(str): The key to get.
Returns:
An iterator to a chunk object containing an array of float values
Loop through the chunks and then the values within the chunk to get all values.
"""
request = KeyValueStore_pb2.KeyValueStoreOutputRequest(name=key)
for chunk in self.__key_value_store_stub.GetValue(request):
yield chunk
@add_method(Project)
def key_values(self, key):
"""Get float values for a given key. Synchronous: returns a list. `
Arguments:
key(str): The key to get.
Returns:
A list of float values
"""
all_values = []
generator = self.key_values_async(key)
for chunk in generator:
for value in chunk.values:
all_values.append(value)
return all_values
@add_method(Project)
def remove_key_values(self, key):
"""Remove the values for a given key in the key-value store.
Arguments:
key(str): The key.
"""
request = KeyValueStore_pb2.KeyValueStoreRemoveRequest(name=key)
self.__key_value_store_stub.RemoveValue(request)