diff --git a/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygon.cpp b/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygon.cpp index 725fab5676..ed546d4203 100644 --- a/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygon.cpp +++ b/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygon.cpp @@ -30,11 +30,14 @@ #include "RiuGuiTheme.h" #include "cafCmdFeatureMenuBuilder.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmFieldScriptingCapabilityCvfVec3d.h" +#include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiColorEditor.h" #include "cafPdmUiPushButtonEditor.h" #include "cafPdmUiTreeAttributes.h" -CAF_PDM_SOURCE_INIT( RimPolygon, "RimPolygon" ); +CAF_PDM_SOURCE_INIT( RimPolygon, "Polygon", "RimPolygon" ); //-------------------------------------------------------------------------------------------------- /// @@ -43,10 +46,10 @@ RimPolygon::RimPolygon() : objectChanged( this ) , coordinatesChanged( this ) { - CAF_PDM_InitObject( "Polygon", ":/PolylinesFromFile16x16.png" ); + CAF_PDM_InitScriptableObject( "Polygon", ":/PolylinesFromFile16x16.png" ); CAF_PDM_InitField( &m_isReadOnly, "IsReadOnly", false, "Read Only" ); - CAF_PDM_InitFieldNoDefault( &m_pointsInDomainCoords, "PointsInDomainCoords", "Points" ); + CAF_PDM_InitScriptableFieldWithScriptKeywordNoDefault( &m_pointsInDomainCoords, "PointsInDomainCoords", "Coordinates", "Points" ); CAF_PDM_InitField( &m_editPolygonButton, "EditPolygonButton", false, "Edit" ); caf::PdmUiPushButtonEditor::configureEditorLabelHidden( &m_editPolygonButton ); diff --git a/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygonCollection.cpp b/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygonCollection.cpp index 36eb785bdc..4214033e9c 100644 --- a/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygonCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Polygons/RimPolygonCollection.cpp @@ -25,18 +25,21 @@ #include "RimPolygonFile.h" #include "RimProject.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmObjectScriptingCapability.h" + #include "cafCmdFeatureMenuBuilder.h" -CAF_PDM_SOURCE_INIT( RimPolygonCollection, "RimPolygonCollection" ); +CAF_PDM_SOURCE_INIT( RimPolygonCollection, "PolygonCollection", "RimPolygonCollection" ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimPolygonCollection::RimPolygonCollection() { - CAF_PDM_InitObject( "Polygons", ":/PolylinesFromFile16x16.png" ); + CAF_PDM_InitScriptableObject( "Polygons", ":/PolylinesFromFile16x16.png" ); - CAF_PDM_InitFieldNoDefault( &m_polygons, "Polygons", "Polygons" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_polygons, "Polygons", "Polygons" ); CAF_PDM_InitFieldNoDefault( &m_polygonFiles, "PolygonFiles", "Polygon Files" ); } diff --git a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake index 57573884ce..c54efdf46c 100644 --- a/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModelCommands/CMakeLists_files.cmake @@ -28,6 +28,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.h ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.h ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.h + ${CMAKE_CURRENT_LIST_DIR}/RimcPolygonCollection.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -60,6 +61,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimcIdenticalGridCaseGroup.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcPressureTable.cpp ${CMAKE_CURRENT_LIST_DIR}/RimcFishbonesCollection.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimcPolygonCollection.cpp ) list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES}) diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.cpp b/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.cpp new file mode 100644 index 0000000000..dd1873f3e0 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.cpp @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimcPolygonCollection.h" + +#include "Polygons/RimPolygon.h" +#include "Polygons/RimPolygonCollection.h" + +#include "cafPdmAbstractFieldScriptingCapability.h" +#include "cafPdmFieldScriptingCapability.h" +#include "cafPdmFieldScriptingCapabilityCvfVec3d.h" + +CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimPolygonCollection, RimcPolygonCollection_createPolygon, "CreatePolygon" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimcPolygonCollection_createPolygon::RimcPolygonCollection_createPolygon( caf::PdmObjectHandle* self ) + : caf::PdmObjectMethod( self ) +{ + CAF_PDM_InitObject( "Create and Add New Polygon", "", "", "Create and Add New Polygon" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_name, "Name", "Name" ); + CAF_PDM_InitScriptableFieldNoDefault( &m_coordinates, "Coordinates", "Coordinates" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmObjectHandle* RimcPolygonCollection_createPolygon::execute() +{ + auto polygonCollection = self(); + + QString name = m_name(); + std::vector coords = m_coordinates(); + + RimPolygon* polygon = polygonCollection->appendUserDefinedPolygon(); + if ( !name.isEmpty() ) + { + polygon->setName( name ); + } + + polygon->setPointsInDomainCoords( coords ); + + polygonCollection->updateConnectedEditors(); + + return polygon; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimcPolygonCollection_createPolygon::resultIsPersistent() const +{ + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::unique_ptr RimcPolygonCollection_createPolygon::defaultResult() const +{ + return std::unique_ptr( new RimPolygon ); +} diff --git a/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.h b/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.h new file mode 100644 index 0000000000..6d16a0fb18 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModelCommands/RimcPolygonCollection.h @@ -0,0 +1,46 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafPdmField.h" +#include "cafPdmObjectHandle.h" +#include "cafPdmObjectMethod.h" + +#include "cvfVector3.h" + +#include + +//================================================================================================== +/// +//================================================================================================== +class RimcPolygonCollection_createPolygon : public caf::PdmObjectMethod +{ + CAF_PDM_HEADER_INIT; + +public: + RimcPolygonCollection_createPolygon( caf::PdmObjectHandle* self ); + + caf::PdmObjectHandle* execute() override; + bool resultIsPersistent() const override; + std::unique_ptr defaultResult() const override; + +private: + caf::PdmField m_name; + caf::PdmField> m_coordinates; +}; diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp index 7d193465c1..fd6e1a5cdb 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.cpp @@ -183,3 +183,33 @@ void PdmFieldScriptingCapabilityIOHandler::readFromField( const bool& fie // Lower-case true/false is used in the documentation. outputStream << ( fieldValue ? "true" : "false" ); } + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmFieldScriptingCapabilityIOHandler::writeToField( double& fieldValue, + QTextStream& inputStream, + PdmScriptIOMessages* errorMessageContainer, + bool stringsAreQuoted ) +{ + inputStream >> fieldValue; + if ( inputStream.status() == QTextStream::ReadCorruptData ) + { + errorMessageContainer->addError( "Argument value is unreadable in the argument: \"" + + errorMessageContainer->currentArgument + "\" in the command: \"" + + errorMessageContainer->currentCommand + "\"" ); + + inputStream.setStatus( QTextStream::Ok ); + } +} + +void PdmFieldScriptingCapabilityIOHandler::readFromField( const double& fieldValue, + QTextStream& outputStream, + bool quoteStrings, + bool quoteNonBuiltins ) +{ + // Use scientific for better precision + outputStream.setRealNumberPrecision( 15 ); + outputStream.setRealNumberNotation( QTextStream::ScientificNotation ); + outputStream << fieldValue; +} diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.h b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.h index 04aafef68c..90679f98bb 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.h +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapability.h @@ -254,6 +254,19 @@ struct PdmFieldScriptingCapabilityIOHandler bool quoteNonBuiltins = false ); }; +template <> +struct PdmFieldScriptingCapabilityIOHandler +{ + static void writeToField( double& fieldValue, + QTextStream& inputStream, + PdmScriptIOMessages* errorMessageContainer, + bool stringsAreQuoted = true ); + static void readFromField( const double& fieldValue, + QTextStream& outputStream, + bool quoteStrings = true, + bool quoteNonBuiltins = false ); +}; + template struct PdmFieldScriptingCapabilityIOHandler> { diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.cpp b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.cpp index 39492f1caf..244a85355d 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.cpp +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.cpp @@ -81,6 +81,83 @@ void PdmFieldScriptingCapabilityIOHandler>::readFromField( PdmFieldScriptingCapabilityIOHandler>::readFromField( fieldVectorValue, outputStream, quoteStrings ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmFieldScriptingCapabilityIOHandler>>::writeToField( + std::vector>& fieldValue, + QTextStream& inputStream, + caf::PdmScriptIOMessages* errorMessageContainer, + bool stringsAreQuoted ) +{ + errorMessageContainer->skipWhiteSpaceWithLineNumberCount( inputStream ); + QChar chr = errorMessageContainer->readCharWithLineNumberCount( inputStream ); + if ( chr == QChar( '[' ) ) + { + while ( !inputStream.atEnd() ) + { + std::vector fieldVectorValue; + + PdmFieldScriptingCapabilityIOHandler>::writeToField( fieldVectorValue, + inputStream, + errorMessageContainer, + stringsAreQuoted ); + if ( fieldVectorValue.size() == 3u ) + { + fieldValue.push_back( + cvf::Vector3( fieldVectorValue[0], fieldVectorValue[1], fieldVectorValue[2] ) ); + } + else + { + QString errMsg = + QString( "Expected three dimensions in the vector, got %1" ).arg( fieldVectorValue.size() ); + errorMessageContainer->addError( errMsg ); + return; + } + + errorMessageContainer->skipWhiteSpaceWithLineNumberCount( inputStream ); + QChar nextChar = errorMessageContainer->peekNextChar( inputStream ); + if ( nextChar == QChar( ']' ) ) + { + nextChar = errorMessageContainer->readCharWithLineNumberCount( inputStream ); + break; + } + else if ( nextChar == QChar( ',' ) ) + { + nextChar = errorMessageContainer->readCharWithLineNumberCount( inputStream ); + errorMessageContainer->skipWhiteSpaceWithLineNumberCount( inputStream ); + } + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void PdmFieldScriptingCapabilityIOHandler>>::readFromField( + const std::vector>& fieldValue, + QTextStream& outputStream, + bool quoteStrings, + bool quoteNonBuiltin ) +{ + outputStream << "["; + + for ( size_t i = 0; i < fieldValue.size(); ++i ) + { + const auto& vec3 = fieldValue[i]; + std::vector fieldVectorValue = { vec3.x(), vec3.y(), vec3.z() }; + PdmFieldScriptingCapabilityIOHandler>::readFromField( fieldVectorValue, + outputStream, + quoteStrings ); + if ( i < fieldValue.size() - 1 ) + { + outputStream << ", "; + } + } + + outputStream << "]"; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.h b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.h index 7f239bc619..1023c3acda 100644 --- a/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.h +++ b/Fwk/AppFwk/cafPdmScripting/cafPdmFieldScriptingCapabilityCvfVec3d.h @@ -55,6 +55,19 @@ struct PdmFieldScriptingCapabilityIOHandler> bool quoteNonBuiltins = false ); }; +template <> +struct PdmFieldScriptingCapabilityIOHandler>> +{ + static void writeToField( std::vector>& fieldValue, + QTextStream& inputStream, + PdmScriptIOMessages* errorMessageContainer, + bool stringsAreQuoted = true ); + static void readFromField( const std::vector>& fieldValue, + QTextStream& outputStream, + bool quoteStrings = true, + bool quoteNonBuiltins = false ); +}; + template <> struct PdmFieldScriptingCapabilityIOHandler> { diff --git a/GrpcInterface/Python/rips/PythonExamples/create_polygon.py b/GrpcInterface/Python/rips/PythonExamples/create_polygon.py new file mode 100644 index 0000000000..e8491b9c9f --- /dev/null +++ b/GrpcInterface/Python/rips/PythonExamples/create_polygon.py @@ -0,0 +1,35 @@ +################################################################################### +# This example will connect to ResInsight, find bounding box and add a polygon +# +################################################################################### + +# Import the ResInsight Processing Server Module +import rips + +# Connect to ResInsight +resinsight = rips.Instance.find() +if resinsight is not None: + # Get a list of all cases + cases = resinsight.project.cases() + + for c in cases: + print("Case name: " + c.name) + + # create a polygon which is same a the bounding box in x and y. + # depth is set to middle of the bounding box + bbox = c.reservoir_boundingbox() + depth = bbox.max_z - ((bbox.max_z - bbox.min_z) / 2.0) + + coordinates = [] + coordinates.append([bbox.min_x, bbox.min_y, depth]) + coordinates.append([bbox.max_x, bbox.min_y, depth]) + coordinates.append([bbox.max_x, bbox.max_y, depth]) + coordinates.append([bbox.min_x, bbox.max_y, depth]) + + polygon_collection = resinsight.project.descendants(rips.PolygonCollection)[0] + p = polygon_collection.create_polygon( + name="{} bounding box".format(c.name), coordinates=coordinates + ) + print("Coordinates for {}:".format(p.name)) + for coord in p.coordinates: + print(coord) diff --git a/GrpcInterface/Python/rips/pdmobject.py b/GrpcInterface/Python/rips/pdmobject.py index 73b2724b07..336882948b 100644 --- a/GrpcInterface/Python/rips/pdmobject.py +++ b/GrpcInterface/Python/rips/pdmobject.py @@ -242,16 +242,25 @@ class PdmObjectBase: return value.startswith("[") and value.endswith("]") def __makelist(self, list_string: str) -> Value: - list_string = list_string.lstrip("[") - list_string = list_string.rstrip("]") + list_string = list_string.removeprefix("[") + list_string = list_string.removesuffix("]") if not list_string: # Return empty list if empty string. Otherwise, the split function will return [''] return [] - strings = list_string.split(", ") - values = [] - for string in strings: - values.append(self.__convert_from_grpc_value(string)) - return values + + # Check if it's a nested list or single list + if "], [" in list_string: + # Nested list + # Split by ], [ to get each sublist + sublists = re.split(r"\], \[", list_string) + return [self.__makelist(sublist) for sublist in sublists] + else: + # Single list + strings = list_string.split(", ") + values = [] + for string in strings: + values.append(self.__convert_from_grpc_value(string)) + return values D = TypeVar("D") diff --git a/GrpcInterface/Python/rips/tests/test_polygon.py b/GrpcInterface/Python/rips/tests/test_polygon.py new file mode 100644 index 0000000000..b8a5312231 --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_polygon.py @@ -0,0 +1,35 @@ +import sys +import os +import pytest +import math + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) +import rips + +import dataroot + + +def test_well_log_plots(rips_instance, initialize_test): + project = rips_instance.project.open( + dataroot.PATH + "/TEST10K_FLT_LGR_NNC/10KWithWellLog.rsp" + ) + + c = project.cases()[0] + bbox = c.reservoir_boundingbox() + + polygon_collection = rips_instance.project.descendants(rips.PolygonCollection)[0] + + coordinates = [] + coordinates.append([bbox.min_x, bbox.min_y, -1000.0]) + coordinates.append([bbox.max_x, bbox.min_y, -1000.0]) + coordinates.append([bbox.max_x, bbox.max_y, -1500.0]) + coordinates.append([bbox.min_x, bbox.max_y, -1500.0]) + + name = "{} bounding box".format(c.name) + p = polygon_collection.create_polygon(name=name, coordinates=coordinates) + assert p.name == name + assert len(coordinates) == len(p.coordinates) + for expected, actual in zip(coordinates, p.coordinates): + assert len(expected) == len(actual) + for e, a in zip(expected, actual): + assert math.isclose(e, a, rel_tol=1e-9, abs_tol=0.0)