diff --git a/docs/geos_mesh_docs/processing.rst b/docs/geos_mesh_docs/processing.rst index 8a448e31..fec7f4b7 100644 --- a/docs/geos_mesh_docs/processing.rst +++ b/docs/geos_mesh_docs/processing.rst @@ -4,6 +4,15 @@ Processing filters The `processing` module of `geos-mesh` package contains filters to process meshes. +geos.mesh.processing.FillPartialArrays filter +---------------------------------------------- + +.. automodule:: geos.mesh.processing.FillPartialArrays + :members: + :undoc-members: + :show-inheritance: + + geos.mesh.processing.meshQualityMetricHelpers module ----------------------------------------------------- @@ -12,6 +21,7 @@ geos.mesh.processing.meshQualityMetricHelpers module :undoc-members: :show-inheritance: + geos.mesh.processing.SplitMesh filter -------------------------------------- diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst index 13a6d04d..43920078 100644 --- a/docs/geos_pv_docs/processing.rst +++ b/docs/geos_pv_docs/processing.rst @@ -1,6 +1,11 @@ Post-/Pre-processing ========================= +PVFillPartialArrays +-------------------- +.. automodule:: geos.pv.plugins.PVFillPartialArrays + + PVSplitMesh ---------------------------------- diff --git a/geos-mesh/src/geos/mesh/processing/FillPartialArrays.py b/geos-mesh/src/geos/mesh/processing/FillPartialArrays.py new file mode 100644 index 00000000..b89df611 --- /dev/null +++ b/geos-mesh/src/geos/mesh/processing/FillPartialArrays.py @@ -0,0 +1,154 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Romain Baville, Martin Lemay + +from typing_extensions import Self +from typing import Union, Any + +from geos.utils.Logger import logging, Logger, getLogger +from geos.mesh.utils.arrayModifiers import fillPartialAttributes +from geos.mesh.utils.arrayHelpers import isAttributeInObject + +from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet + +__doc__ = """ +Fill partial attributes of the input mesh with constant values per component. + +Input mesh is vtkMultiBlockDataSet and attributes to fill must be partial. + +The list of filling values per attribute is given by a dictionary. +Its keys are the attribute names and its items are the list of filling values for each component. + +If the list of filling value is None, attributes are filled with the same constant value for each component; +0 for uint data, -1 for int data and nan for float data. + +To use a handler of yours for the logger, set the variable 'speHandler' to True and add it to the filter +with the member function addLoggerHandler. + +To use it: + +.. code-block:: python + + from geos.mesh.processing.FillPartialArrays import FillPartialArrays + + # Filter inputs. + multiBlockDataSet: vtkMultiBlockDataSet + dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] + # Optional inputs. + speHandler: bool + + # Instantiate the filter. + filter: FillPartialArrays = FillPartialArrays( multiBlockDataSet, dictAttributesValues, speHandler ) + + # Set the handler of yours (only if speHandler is True). + yourHandler: logging.Handler + filter.addLoggerHandler( yourHandler ) + + # Do calculations. + filter.applyFilter() +""" + +loggerTitle: str = "Fill Partial Attribute" + + +class FillPartialArrays: + + def __init__( + self: Self, + multiBlockDataSet: vtkMultiBlockDataSet, + dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ], + speHandler: bool = False, + ) -> None: + """Fill partial attributes with constant value per component. + + If the list of filling values for an attribute is None, it will filled with the default value for each component: + 0 for uint data. + -1 for int data. + nan for float data. + + Args: + multiBlockDataSet (vtkMultiBlockDataSet): The mesh where to fill the attribute. + dictAttributesValues (dict[str, Any]): The dictionary with the attribute to fill as keys and the list of filling values as items. + speHandler (bool, optional): True to use a specific handler, False to use the internal handler. + Defaults to False. + """ + self.multiBlockDataSet: vtkMultiBlockDataSet = multiBlockDataSet + self.dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] = dictAttributesValues + + # Logger. + self.logger: Logger + if not speHandler: + self.logger = getLogger( loggerTitle, True ) + else: + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: + """Set a specific handler for the filter logger. + + In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels. + + Args: + handler (logging.Handler): The handler to add. + """ + if not self.logger.hasHandlers(): + self.logger.addHandler( handler ) + else: + self.logger.warning( + "The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization." + ) + + def applyFilter( self: Self ) -> bool: + """Create a constant attribute per region in the mesh. + + Returns: + boolean (bool): True if calculation successfully ended, False otherwise. + """ + self.logger.info( f"Apply filter { self.logger.name }." ) + + for attributeName in self.dictAttributesValues: + self._setPieceRegionAttribute( attributeName ) + if self.onPoints is None: + self.logger.error( f"{ attributeName } is not in the mesh." ) + self.logger.error( f"The attribute { attributeName } has not been filled." ) + self.logger.error( f"The filter { self.logger.name } failed." ) + return False + + if self.onBoth: + self.logger.error( + f"Their is two attribute named { attributeName }, one on points and the other on cells. The attribute must be unique." + ) + self.logger.error( f"The attribute { attributeName } has not been filled." ) + self.logger.error( f"The filter { self.logger.name } failed." ) + return False + + if not fillPartialAttributes( self.multiBlockDataSet, + attributeName, + onPoints=self.onPoints, + listValues=self.dictAttributesValues[ attributeName ], + logger=self.logger ): + self.logger.error( f"The filter { self.logger.name } failed." ) + return False + + self.logger.info( f"The filter { self.logger.name } succeed." ) + + return True + + def _setPieceRegionAttribute( self: Self, attributeName: str ) -> None: + """Set the attribute self.onPoints and self.onBoth. + + self.onPoints is True if the region attribute is on points, False if it is on cells, None otherwise. + + self.onBoth is True if a region attribute is on points and on cells, False otherwise. + + Args: + attributeName (str): The name of the attribute to verify. + """ + self.onPoints: Union[ bool, None ] = None + self.onBoth: bool = False + if isAttributeInObject( self.multiBlockDataSet, attributeName, False ): + self.onPoints = False + if isAttributeInObject( self.multiBlockDataSet, attributeName, True ): + if self.onPoints is False: + self.onBoth = True + self.onPoints = True diff --git a/geos-mesh/src/geos/mesh/utils/arrayModifiers.py b/geos-mesh/src/geos/mesh/utils/arrayModifiers.py index 79a1b6b6..e5c8b0a6 100644 --- a/geos-mesh/src/geos/mesh/utils/arrayModifiers.py +++ b/geos-mesh/src/geos/mesh/utils/arrayModifiers.py @@ -41,6 +41,7 @@ isAttributeGlobal, getVtkArrayTypeInObject, getVtkArrayTypeInMultiBlock, + getNumberOfComponentsMultiBlock, ) from geos.mesh.utils.multiblockHelpers import ( getBlockElementIndexesFlatten, @@ -61,18 +62,18 @@ def fillPartialAttributes( multiBlockDataSet: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet, vtkDataObject ], attributeName: str, onPoints: bool = False, - value: Any = np.nan, + listValues: Union[ list[ Any ], None ] = None, logger: Union[ Logger, None ] = None, ) -> bool: - """Fill input partial attribute of multiBlockDataSet with the same value for all the components. + """Fill input partial attribute of multiBlockDataSet with a constant value per component. Args: multiBlockDataSet (vtkMultiBlockDataSet | vtkCompositeDataSet | vtkDataObject): MultiBlockDataSet where to fill the attribute. attributeName (str): Attribute name. onPoints (bool, optional): True if attributes are on points, False if they are on cells. Defaults to False. - value (Any, optional): Filling value. It is recommended to use numpy scalar type for the values. - Defaults to: + listValues (list[Any], optional): List of filling value for each component. + Defaults to None, the filling value is for all components: -1 for int VTK arrays. 0 for uint VTK arrays. nan for float VTK arrays. @@ -98,40 +99,53 @@ def fillPartialAttributes( # Get information of the attribute to fill. vtkDataType: int = getVtkArrayTypeInMultiBlock( multiBlockDataSet, attributeName, onPoints ) - infoAttributes: dict[ str, int ] = getAttributesWithNumberOfComponents( multiBlockDataSet, onPoints ) - nbComponents: int = infoAttributes[ attributeName ] + nbComponents: int = getNumberOfComponentsMultiBlock( multiBlockDataSet, attributeName, onPoints ) componentNames: tuple[ str, ...] = () if nbComponents > 1: componentNames = getComponentNames( multiBlockDataSet, attributeName, onPoints ) + typeMapping: dict[ int, type ] = vnp.get_vtk_to_numpy_typemap() + valueType: type = typeMapping[ vtkDataType ] # Set the default value depending of the type of the attribute to fill - if np.isnan( value ): - typeMapping: dict[ int, type ] = vnp.get_vtk_to_numpy_typemap() - valueType: type = typeMapping[ vtkDataType ] + if listValues is None: + defaultValue: Any + logger.warning( f"The attribute { attributeName } is filled with the default value for each component." ) # Default value for float types is nan. if vtkDataType in ( VTK_FLOAT, VTK_DOUBLE ): - value = valueType( value ) + defaultValue = valueType( np.nan ) logger.warning( - f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to nan." + f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to nan." ) # Default value for int types is -1. elif vtkDataType in ( VTK_CHAR, VTK_SIGNED_CHAR, VTK_SHORT, VTK_LONG, VTK_INT, VTK_LONG_LONG, VTK_ID_TYPE ): - value = valueType( -1 ) + defaultValue = valueType( -1 ) logger.warning( - f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to -1." + f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to -1." ) # Default value for uint types is 0. elif vtkDataType in ( VTK_BIT, VTK_UNSIGNED_CHAR, VTK_UNSIGNED_SHORT, VTK_UNSIGNED_LONG, VTK_UNSIGNED_INT, VTK_UNSIGNED_LONG_LONG ): - value = valueType( 0 ) + defaultValue = valueType( 0 ) logger.warning( - f"{ attributeName } vtk data type is { vtkDataType } corresponding to { value.dtype } numpy type, default value is automatically set to 0." + f"{ attributeName } vtk data type is { vtkDataType } corresponding to { defaultValue.dtype } numpy type, default value is automatically set to 0." ) else: logger.error( f"The type of the attribute { attributeName } is not compatible with the function." ) return False - values: list[ Any ] = [ value for _ in range( nbComponents ) ] + listValues = [ defaultValue ] * nbComponents + + else: + if len( listValues ) != nbComponents: + return False + + for idValue in range( nbComponents ): + value: Any = listValues[ idValue ] + if type( value ) is not valueType: + listValues[ idValue ] = valueType( listValues[ idValue ] ) + logger.warning( + f"The filling value { value } for the attribute { attributeName } has not the correct type, it is convert to the numpy scalar type { valueType().dtype }." + ) # Parse the multiBlockDataSet to create and fill the attribute on blocks where it is not. iterator: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator() @@ -141,7 +155,7 @@ def fillPartialAttributes( while iterator.GetCurrentDataObject() is not None: dataSet: vtkDataSet = vtkDataSet.SafeDownCast( iterator.GetCurrentDataObject() ) if not isAttributeInObjectDataSet( dataSet, attributeName, onPoints ) and \ - not createConstantAttributeDataSet( dataSet, values, attributeName, componentNames, onPoints, vtkDataType, logger ): + not createConstantAttributeDataSet( dataSet, listValues, attributeName, componentNames, onPoints, vtkDataType, logger ): return False iterator.GoToNextItem() @@ -172,7 +186,7 @@ def fillAllPartialAttributes( infoAttributes: dict[ str, int ] = getAttributesWithNumberOfComponents( multiBlockDataSet, onPoints ) for attributeName in infoAttributes: if not isAttributeGlobal( multiBlockDataSet, attributeName, onPoints ) and \ - not fillPartialAttributes( multiBlockDataSet, attributeName, onPoints, logger=logger ): + not fillPartialAttributes( multiBlockDataSet, attributeName, onPoints=onPoints, logger=logger ): return False return True @@ -384,7 +398,7 @@ def createConstantAttributeDataSet( if valueType in ( int, float ): npType: type = type( np.array( listValues )[ 0 ] ) logger.warning( - f"During the creation of the constant attribute { attributeName }, values will be converted from { valueType } to { npType }." + f"During the creation of the constant attribute { attributeName }, values have been converted from { valueType } to { npType }." ) logger.warning( "To avoid any issue with the conversion, please use directly numpy scalar type for the values" ) valueType = npType diff --git a/geos-mesh/tests/test_FillPartialArrays.py b/geos-mesh/tests/test_FillPartialArrays.py new file mode 100644 index 00000000..b1fd8f3a --- /dev/null +++ b/geos-mesh/tests/test_FillPartialArrays.py @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Romain Baville +# SPDX-License-Identifier: Apache 2.0 +# ruff: noqa: E402 # disable Module level import not at top of file +# mypy: disable-error-code="operator" +import pytest + +from typing import Any +from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet + +from geos.mesh.processing.FillPartialArrays import FillPartialArrays + + +@pytest.mark.parametrize( "dictAttributesValues", [ + ( { + "PORO": None + } ), + ( { + "PERM": None + } ), + ( { + "PORO": None, + "PERM": None + } ), + ( { + "PORO": [ 4 ] + } ), + ( { + "PERM": [ 4, 4, 4 ] + } ), + ( { + "PORO": [ 4 ], + "PERM": [ 4, 4, 4 ] + } ), + ( { + "PORO": None, + "PERM": [ 4, 4, 4 ] + } ), + ( { + "PORO": [ 4 ], + "PERM": None + } ), +] ) +def test_FillPartialArrays( + dataSetTest: vtkMultiBlockDataSet, + dictAttributesValues: dict[ str, Any ], +) -> None: + """Test FillPartialArrays vtk filter.""" + multiBlockDataSet: vtkMultiBlockDataSet = dataSetTest( "multiblock" ) + + filter: FillPartialArrays = FillPartialArrays( multiBlockDataSet, dictAttributesValues ) + assert filter.applyFilter() diff --git a/geos-mesh/tests/test_arrayModifiers.py b/geos-mesh/tests/test_arrayModifiers.py index cf9b6311..91ecc423 100644 --- a/geos-mesh/tests/test_arrayModifiers.py +++ b/geos-mesh/tests/test_arrayModifiers.py @@ -5,7 +5,7 @@ # ruff: noqa: E402 # disable Module level import not at top of file # mypy: disable-error-code="operator" import pytest -from typing import Union, Any, cast +from typing import Union, Any import numpy as np import numpy.typing as npt @@ -45,24 +45,25 @@ @pytest.mark.parametrize( - "idBlock, attributeName, nbComponentsTest, componentNamesTest, onPoints, value, valueTest, vtkDataTypeTest", + "idBlock, attributeName, nbComponentsTest, componentNamesTest, onPoints, listValues, listValuesTest, vtkDataTypeTest", [ # Test fill an attribute on point and on cell. - ( 1, "CellAttribute", 3, ( "AX1", "AX2", "AX3" ), False, np.nan, np.nan, VTK_DOUBLE ), - ( 1, "PointAttribute", 3, ( "AX1", "AX2", "AX3" ), True, np.nan, np.nan, VTK_DOUBLE ), - # Test fill attributes with different number of component. - ( 1, "PORO", 1, (), False, np.nan, np.float32( np.nan ), VTK_FLOAT ), - ( 1, "PERM", 3, ( "AX1", "AX2", "AX3" ), False, np.nan, np.float32( np.nan ), VTK_FLOAT ), - # Test fill an attribute with default value. - ( 1, "FAULT", 1, (), False, np.nan, np.int32( -1 ), VTK_INT ), - ( 0, "collocated_nodes", 2, ( None, None ), True, np.nan, np.int64( -1 ), VTK_ID_TYPE ), - # Test fill an attribute with specified value. - ( 1, "PORO", 1, (), False, np.float32( 4 ), np.float32( 4 ), VTK_FLOAT ), - ( 1, "CellAttribute", 3, ( "AX1", "AX2", "AX3" ), False, 4., np.float64( 4 ), VTK_DOUBLE ), - ( 1, "CellAttribute", 3, ( "AX1", "AX2", "AX3" ), False, np.float64( 4 ), np.float64( 4 ), VTK_DOUBLE ), - ( 1, "FAULT", 1, (), False, np.int32( 4 ), np.int32( 4 ), VTK_INT ), - ( 0, "collocated_nodes", 2, ( None, None ), True, 4, np.int64( 4 ), VTK_ID_TYPE ), - ( 0, "collocated_nodes", 2, ( None, None ), True, np.int64( 4 ), np.int64( 4 ), VTK_ID_TYPE ), + ( 1, "PointAttribute", 3, + ( "AX1", "AX2", "AX3" ), True, None, [ np.float64( + np.nan ), np.float64( np.nan ), np.float64( np.nan ) ], VTK_DOUBLE ), + ( 1, "CellAttribute", 3, + ( "AX1", "AX2", "AX3" ), False, None, [ np.float64( + np.nan ), np.float64( np.nan ), np.float64( np.nan ) ], VTK_DOUBLE ), + # Test fill attributes with different number of component with or without component names. + ( 1, "PORO", 1, (), False, None, [ np.float32( np.nan ) ], VTK_FLOAT ), + ( 0, "collocated_nodes", 2, ( None, None ), True, None, [ np.int64( -1 ), np.int64( -1 ) ], VTK_ID_TYPE ), + # Test fill an attribute with different type of value. + ( 1, "FAULT", 1, (), False, None, [ np.int32( -1 ) ], VTK_INT ), + ( 1, "FAULT", 1, (), False, [ 4 ], [ np.int32( 4 ) ], VTK_INT ), + ( 1, "PORO", 1, (), False, [ 4 ], [ np.float32( 4 ) ], VTK_FLOAT ), + ( 0, "collocated_nodes", 2, ( None, None ), True, [ 4, 4 ], [ np.int64( 4 ), np.int64( 4 ) ], VTK_ID_TYPE ), + ( 1, "CellAttribute", 3, ( "AX1", "AX2", "AX3" ), False, [ 4, 4, 4 ], + [ np.float64( 4 ), np.float64( 4 ), np.float64( 4 ) ], VTK_DOUBLE ), ] ) def test_fillPartialAttributes( dataSetTest: vtkMultiBlockDataSet, @@ -71,18 +72,20 @@ def test_fillPartialAttributes( nbComponentsTest: int, componentNamesTest: tuple[ str, ...], onPoints: bool, - value: Any, - valueTest: Any, + listValues: Union[ list[ Any ], None ], + listValuesTest: list[ Any ], vtkDataTypeTest: int, ) -> None: """Test filling a partial attribute from a multiblock with values.""" multiBlockDataSetTest: vtkMultiBlockDataSet = dataSetTest( "multiblock" ) - # Fill the attribute in the multiBlockDataSet. - assert arrayModifiers.fillPartialAttributes( multiBlockDataSetTest, attributeName, onPoints, value ) + assert arrayModifiers.fillPartialAttributes( multiBlockDataSetTest, + attributeName, + onPoints=onPoints, + listValues=listValues ) # Get the dataSet where the attribute has been filled. - dataSet: vtkDataSet = cast( vtkDataSet, multiBlockDataSetTest.GetBlock( idBlock ) ) + dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSetTest.GetBlock( idBlock ) ) # Get the filled attribute. data: Union[ vtkPointData, vtkCellData ] @@ -107,19 +110,19 @@ def test_fillPartialAttributes( ## Create the constant array test from the value. npArrayTest: npt.NDArray[ Any ] if nbComponentsTest > 1: - npArrayTest = np.array( [ [ valueTest for _ in range( nbComponentsTest ) ] for _ in range( nbElements ) ] ) + npArrayTest = np.array( [ listValuesTest for _ in range( nbElements ) ] ) else: - npArrayTest = np.array( [ valueTest for _ in range( nbElements ) ] ) + npArrayTest = np.array( [ listValuesTest[ 0 ] for _ in range( nbElements ) ] ) npArrayFilled: npt.NDArray[ Any ] = vnp.vtk_to_numpy( attributeFilled ) assert npArrayFilled.dtype == npArrayTest.dtype - if np.isnan( value ) and vtkDataTypeTest in ( VTK_FLOAT, VTK_DOUBLE ): + if listValues is None and vtkDataTypeTest in ( VTK_FLOAT, VTK_DOUBLE ): assert np.isnan( npArrayFilled ).all() else: assert ( npArrayFilled == npArrayTest ).all() vtkDataTypeFilled: int = attributeFilled.GetDataType() - assert vtkDataTypeTest == vtkDataTypeFilled + assert vtkDataTypeFilled == vtkDataTypeTest @pytest.mark.parametrize( "multiBlockDataSetName", [ "multiblock" ] ) @@ -133,7 +136,7 @@ def test_FillAllPartialAttributes( nbBlock: int = multiBlockDataSetTest.GetNumberOfBlocks() for idBlock in range( nbBlock ): - dataSet: vtkDataSet = cast( vtkDataSet, multiBlockDataSetTest.GetBlock( idBlock ) ) + dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSetTest.GetBlock( idBlock ) ) attributeExist: int for attributeNameOnPoint in [ "PointAttribute", "collocated_nodes" ]: attributeExist = dataSet.GetPointData().HasArray( attributeNameOnPoint ) @@ -190,7 +193,7 @@ def test_createConstantAttributeMultiBlock( nbBlock = multiBlockDataSetTest.GetNumberOfBlocks() for idBlock in range( nbBlock ): - dataSet: vtkDataSet = cast( vtkDataSet, multiBlockDataSetTest.GetBlock( idBlock ) ) + dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSetTest.GetBlock( idBlock ) ) data: Union[ vtkPointData, vtkCellData ] data = dataSet.GetPointData() if onPoints else dataSet.GetCellData() @@ -409,8 +412,8 @@ def test_copyAttribute( # Parse the two multiBlockDataSet and test if the attribute has been copied. nbBlocks: int = multiBlockDataSetFrom.GetNumberOfBlocks() for idBlock in range( nbBlocks ): - dataSetFrom: vtkDataSet = cast( vtkDataSet, multiBlockDataSetFrom.GetBlock( idBlock ) ) - dataSetTo: vtkDataSet = cast( vtkDataSet, multiBlockDataSetTo.GetBlock( idBlock ) ) + dataSetFrom: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSetFrom.GetBlock( idBlock ) ) + dataSetTo: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSetTo.GetBlock( idBlock ) ) dataFrom: Union[ vtkPointData, vtkCellData ] dataTo: Union[ vtkPointData, vtkCellData ] if onPoints: @@ -494,7 +497,7 @@ def test_renameAttributeMultiblock( newAttributeName, onPoints, ) - block: vtkDataSet = cast( vtkDataSet, vtkMultiBlockDataSetTest.GetBlock( 0 ) ) + block: vtkDataSet = vtkDataSet.SafeDownCast( vtkMultiBlockDataSetTest.GetBlock( 0 ) ) data: Union[ vtkPointData, vtkCellData ] if onPoints: data = block.GetPointData() diff --git a/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py b/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py new file mode 100644 index 00000000..218d6bfc --- /dev/null +++ b/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py @@ -0,0 +1,170 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay, Romain Baville +# ruff: noqa: E402 # disable Module level import not at top of file +import sys +from pathlib import Path +from typing import Union, Any +from typing_extensions import Self + +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/util/vtkAlgorithm.py +from paraview.detail.loghandler import ( # type: ignore[import-not-found] + VTKHandler, +) # source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py + +from vtkmodules.vtkCommonDataModel import ( + vtkMultiBlockDataSet, ) + +from vtkmodules.vtkCommonCore import ( + vtkInformation, + vtkInformationVector, +) + +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths + +update_paths() + +from geos.mesh.processing.FillPartialArrays import FillPartialArrays + +__doc__ = """ +Fill partial arrays of input mesh. + +Input and output are vtkMultiBlockDataSet. + +To use it: + +* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVFillPartialArrays. +* Select the input mesh. +* Select the partial arrays to fill. +* Set the filling value (defaults to nan). +* Apply. + +""" + + +@smproxy.filter( name="PVFillPartialArrays", label="Fill Partial Arrays" ) +@smhint.xml( '' ) +@smproperty.input( name="Input", port_index=0 ) +@smdomain.datatype( + dataTypes=[ "vtkMultiBlockDataSet" ], + composite_data_supported=True, +) +class PVFillPartialArrays( VTKPythonAlgorithmBase ): + + def __init__( self: Self, ) -> None: + """Fill a partial attribute with constant value per component.""" + super().__init__( nInputPorts=1, + nOutputPorts=1, + inputType="vtkMultiBlockDataSet", + outputType="vtkMultiBlockDataSet" ) + + self.clearDictAttributesValues: bool = True + self.dictAttributesValues: dict[ str, Union[ list[ Any ], None ] ] = {} + + + @smproperty.xml(""" + + + Set the filling values for each partial attribute, use a coma between the value of each components:\n + attributeName | fillingValueComponent1 fillingValueComponent2 ...\n + To fill the attribute with the default value, live a blanc. The default value is:\n + 0 for uint type, -1 for int type and nan for float type. + + + + + + + + + + """ ) + def _setDictAttributesValues( self: Self, attributeName: str, values: str ) -> None: + """Set the dictionary with the region indexes and its corresponding list of value for each components. + + Args: + attributeName (str): Name of the attribute to consider. + values (str): List of the filing values. If multiple components use a comma between the value of each component. + """ + if self.clearDictAttributesValues: + self.dictAttributesValues = {} + self.clearDictAttributesValues = False + + if attributeName is not None: + if values is not None : + self.dictAttributesValues[ attributeName ] = list( values.split( "," ) ) + else: + self.dictAttributesValues[ attributeName ] = None + + self.Modified() + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inData = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData is not None + if outData is None or ( not outData.IsA( inData.GetClassName() ) ): + outData = inData.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inputMesh: vtkMultiBlockDataSet = self.GetInputData( inInfoVec, 0, 0 ) + outputMesh: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 ) + assert inputMesh is not None, "Input server mesh is null." + assert outputMesh is not None, "Output pipeline is null." + + outputMesh.ShallowCopy( inputMesh ) + + filter: FillPartialArrays = FillPartialArrays( outputMesh, + self.dictAttributesValues, + True, + ) + + if not filter.logger.hasHandlers(): + filter.setLoggerHandler( VTKHandler() ) + + filter.applyFilter() + + self.clearDictAttributesValues = True + + return 1