diff --git a/.hooks/pre-commit.example b/.hooks/pre-commit.example new file mode 100755 index 00000000..f4f188d7 --- /dev/null +++ b/.hooks/pre-commit.example @@ -0,0 +1,23 @@ +#!/bin/bash -e + +function run_precommit_checks +{ + echo "running mypy" + python -m mypy --config-file ./.mypy.ini --check-untyped-defs $1 + echo "running ruff" + python -m ruff check --unsafe-fixes --config .ruff.toml $1 + echo "running yapf" + python -m yapf -r -i --style .style.yapf $1 + + return 0 +} + + +source ${ENV_PYTHON}/bin/activate + +#sphinx-build -b html docs/ docs/_build -W +for file in $(git diff --name-only --cached | grep -e "modified\|added" | grep py) +do + run_precommit_checks $file +done + diff --git a/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py b/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py index 8c0cfdb2..cb29430f 100644 --- a/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py +++ b/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py @@ -4,24 +4,16 @@ # ruff: noqa: E402 # disable Module level import not at top of file import sys from pathlib import Path -from typing import Union from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, + VTKPythonAlgorithmBase, ) # 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, - vtkUnstructuredGrid, -) - -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, -) + vtkMultiBlockDataSet, ) # update sys.path to load all GEOS Python Package dependencies geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent @@ -30,6 +22,7 @@ update_paths() +from geos.pv.utils.details import SISOFilter, FilterCategory from geos.mesh.processing.ClipToMainFrame import ClipToMainFrame __doc__ = """ @@ -43,67 +36,28 @@ """ -@smproxy.filter( name="PVClipToMainFrame", label="Clip to the main frame" ) -@smhint.xml( '' ) -@smproperty.input( name="Input", port_index=0 ) -@smdomain.datatype( - dataTypes=[ "vtkMultiBlockDataSet", "vtkUnstructuredGrid" ], - composite_data_supported=True, -) +@SISOFilter( category=FilterCategory.GEOS_UTILS, + decoratedLabel="Clip to the main frame", + decoratedType=[ "vtkMultiBlockDataSet", "vtkDataSet" ] ) class PVClipToMainFrame( VTKPythonAlgorithmBase ): def __init__( self ) -> None: """Init motherclass, filter and logger.""" - VTKPythonAlgorithmBase.__init__( self, - nInputPorts=1, - nOutputPorts=1, - inputType="vtkDataObject", - outputType="vtkDataObject" ) - self._realFilter = ClipToMainFrame( speHandler=True ) if not self._realFilter.logger.hasHandlers(): self._realFilter.SetLoggerHandler( VTKHandler() ) - #ensure I/O consistency - def RequestDataObject( self, request: vtkInformation, inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + def Filter( self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMultiBlockDataSet ) -> None: + """Is applying CreateConstantAttributePerRegion filter. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + inputMesh : A mesh to transform. + outputMesh : A mesh transformed. """ - 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, request: vtkInformation, inInfo: list[ vtkInformationVector ], - outInfo: vtkInformationVector ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. Apply ClipToMainFrame filter. - - Args: - request (vtkInformation): Request - inInfo (list[vtkInformationVector]): Input objects - outInfo (vtkInformationVector): Output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - inputMesh: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid ] = self.GetInputData( inInfo, 0, 0 ) - outputMesh: Union[ vtkMultiBlockDataSet, vtkUnstructuredGrid ] = self.GetOutputData( outInfo, 0 ) - # struct self._realFilter.SetInputData( inputMesh ) self._realFilter.ComputeTransform() self._realFilter.Update() outputMesh.ShallowCopy( self._realFilter.GetOutputDataObject( 0 ) ) - return 1 + return diff --git a/geos-pv/src/geos/pv/plugins/PVCreateConstantAttributePerRegion.py b/geos-pv/src/geos/pv/plugins/PVCreateConstantAttributePerRegion.py index fe6a61b4..aabb2e56 100644 --- a/geos-pv/src/geos/pv/plugins/PVCreateConstantAttributePerRegion.py +++ b/geos-pv/src/geos/pv/plugins/PVCreateConstantAttributePerRegion.py @@ -3,36 +3,31 @@ # SPDX-FileContributor: Martin Lemay, Romain Baville # ruff: noqa: E402 # disable Module level import not at top of file import sys +import numpy as np from pathlib import Path -from typing import Union, Any +from typing import Any from typing_extensions import Self from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - smdomain, smhint, smproperty, smproxy, + VTKPythonAlgorithmBase, smdomain, smproperty, ) # 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.util.vtkAlgorithm import VTKPythonAlgorithmBase -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, -) +import vtkmodules.util.numpy_support as vnp + from vtkmodules.vtkCommonDataModel import ( - vtkMultiBlockDataSet, - vtkDataSet, -) + vtkDataSet, ) # 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.CreateConstantAttributePerRegion import ( CreateConstantAttributePerRegion ) -from geos.mesh.processing.CreateConstantAttributePerRegion import CreateConstantAttributePerRegion, vnp, np +from geos.pv.utils.details import SISOFilter, FilterCategory __doc__ = """ PVCreateConstantAttributePerRegion is a Paraview plugin that allows to create an attribute @@ -42,7 +37,7 @@ Input mesh is either vtkMultiBlockDataSet or vtkDataSet and the region attribute must have one component. The relation index/values is given by a dictionary. Its keys are the indexes and its items are the list of values for each component. -.. Warning:: +.. Warning:: The input mesh should contain an attribute corresponding to the regions. To use it: @@ -56,22 +51,13 @@ """ -@smproxy.filter( - name="PVCreateConstantAttributePerRegion", - label="Create Constant Attribute Per Region", -) -@smhint.xml( """""" ) -@smproperty.input( name="Input", port_index=0 ) -@smdomain.datatype( - dataTypes=[ "vtkMultiBlockDataSet", "vtkDataSet" ], - composite_data_supported=True, -) +@SISOFilter( category=FilterCategory.GEOS_PROP, + decoratedLabel="Create Constant Attribute Per Region", + decoratedType=[ "vtkMultiBlockDataSet", "vtkDataSet" ] ) class PVCreateConstantAttributePerRegion( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: """Create an attribute with constant value per region.""" - super().__init__( nInputPorts=1, nOutputPorts=1, inputType="vtkDataObject", outputType="vtkDataObject" ) - self.clearDictRegionValues: bool = True # Region attribute settings. @@ -111,7 +97,7 @@ def __init__( self: Self ) -> None: """ ) - def _setRegionAttributeName( self: Self, regionName: str ) -> None: + def setRegionAttributeName( self: Self, regionName: str ) -> None: """Set region attribute name. Args: @@ -124,7 +110,7 @@ def _setRegionAttributeName( self: Self, regionName: str ) -> None: @@ -142,7 +128,7 @@ def _setRegionAttributeName( self: Self, regionName: str ) -> None: """ ) - def _setDictRegionValues( self: Self, regionIndex: str, value: str ) -> None: + def setDictRegionValues( self: Self, regionIndex: str, value: str ) -> None: """Set the dictionary with the region indexes and its corresponding list of values for each components. Args: @@ -166,7 +152,7 @@ def _setDictRegionValues( self: Self, regionIndex: str, value: str ) -> None: """ ) - def _groupRegionAttributeSettingsWidgets( self: Self ) -> None: + def groupRegionAttributeSettingsWidgets( self: Self ) -> None: """Group the widgets to set the settings of the region attribute.""" self.Modified() @@ -183,7 +169,7 @@ def _groupRegionAttributeSettingsWidgets( self: Self ) -> None: """ ) - def _setAttributeName( self: Self, newAttributeName: str ) -> None: + def setAttributeName( self: Self, newAttributeName: str ) -> None: """Set attribute name. Args: @@ -216,7 +202,7 @@ def _setAttributeName( self: Self, newAttributeName: str ) -> None: The requested numpy scalar type for values of the new attribute. """ ) - def _setValueType( self: Self, valueType: int ) -> None: + def setValueType( self: Self, valueType: int ) -> None: """Set the type for the value used to create the new attribute. Args: @@ -238,7 +224,7 @@ def _setValueType( self: Self, valueType: int ) -> None: The number of components for the new attribute to create. """ ) - def _setNbComponent( self: Self, nbComponents: int ) -> None: + def setNbComponent( self: Self, nbComponents: int ) -> None: """Set the number of components of the attribute to create. Args: @@ -261,7 +247,7 @@ def _setNbComponent( self: Self, nbComponents: int ) -> None: Names of components: X, Y, Z """ ) - def _setComponentNames( self: Self, componentNames: str ) -> None: + def setComponentNames( self: Self, componentNames: str ) -> None: """Set the names of the components of the attribute to create. Args: @@ -283,57 +269,17 @@ def _setComponentNames( self: Self, componentNames: str ) -> None: """ ) - def _groupNewAttributeSettingsWidgets( self: Self ) -> None: + def groupNewAttributeSettingsWidgets( self: Self ) -> None: """Group the widgets to set the settings of the new attribute.""" self.Modified() - def RequestDataObject( - self: Self, - request: vtkInformation, - inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + def Filter( self, inputMesh: vtkDataSet, outputMesh: vtkDataSet ) -> None: + """Is applying CreateConstantAttributePerRegion filter. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + inputMesh : A mesh to transform + outputMesh : A mesh transformed. """ - 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 ], # noqa: F841 - outInfoVec: vtkInformationVector, # noqa: F841 - ) -> 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: Union[ vtkDataSet, vtkMultiBlockDataSet ] = self.GetInputData( inInfoVec, 0, 0 ) - outputMesh: Union[ vtkDataSet, vtkMultiBlockDataSet ] = self.GetOutputData( outInfoVec, 0 ) - - assert inputMesh is not None, "Input mesh is null." - assert outputMesh is not None, "Output pipeline is null." - - outputMesh.ShallowCopy( inputMesh ) filter: CreateConstantAttributePerRegion = CreateConstantAttributePerRegion( outputMesh, self.regionName, @@ -352,4 +298,4 @@ def RequestData( self.clearDictRegion = True - return 1 + return diff --git a/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py b/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py index 218d6bfc..b5c60dc8 100644 --- a/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py +++ b/geos-pv/src/geos/pv/plugins/PVFillPartialArrays.py @@ -8,20 +8,15 @@ 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 + VTKPythonAlgorithmBase, smproperty, +) # 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 +) # 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" ) ) @@ -29,6 +24,7 @@ update_paths() +from geos.pv.utils.details import SISOFilter, FilterCategory from geos.mesh.processing.FillPartialArrays import FillPartialArrays __doc__ = """ @@ -47,31 +43,21 @@ """ -@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, -) +@SISOFilter( category=FilterCategory.GEOS_UTILS, + decoratedLabel="Fill Partial Arrays", + decoratedType="vtkMultiBlockDataSet" ) 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(""" + @smproperty.xml( """ @@ -79,7 +65,7 @@ def __init__( self: Self, ) -> None: 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. - + @@ -89,7 +75,7 @@ def __init__( self: Self, ) -> None: """ ) - def _setDictAttributesValues( self: Self, attributeName: str, values: str ) -> None: + 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: @@ -99,72 +85,34 @@ def _setDictAttributesValues( self: Self, attributeName: str, values: str ) -> N if self.clearDictAttributesValues: self.dictAttributesValues = {} self.clearDictAttributesValues = False - + if attributeName is not None: - if values 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. + self.dictAttributesValues[ attributeName ] = None #ignore : type[unreachable] - Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + self.Modified() - 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. + def Filter( self, inputMesh: vtkMultiBlockDataSet, outputMesh: vtkMultiBlockDataSet ) -> None: + """Is applying FillPartialArrays to the mesh and return with the class's dictionnary for attributes values. Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + inputMesh : A mesh to transform. + outputMesh : A mesh transformed. - 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, + filter: FillPartialArrays = FillPartialArrays( + outputMesh, + self.dictAttributesValues, + speHandler=True, ) - + if not filter.logger.hasHandlers(): filter.setLoggerHandler( VTKHandler() ) - + filter.applyFilter() self.clearDictAttributesValues = True - return 1 + return diff --git a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py index a82b0a49..2145fc1d 100644 --- a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py @@ -7,13 +7,10 @@ from typing_extensions import Self, Optional from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, + VTKPythonAlgorithmBase, smdomain, smproperty, ) from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, - vtkDataArraySelection, -) + vtkDataArraySelection, ) from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, ) @@ -44,6 +41,8 @@ createModifiedCallback, ) from geos.pv.utils.paraviewTreatments import getArrayChoices +from geos.pv.utils.details import SISOFilter, FilterCategory + __doc__ = """ The ``Mesh Quality Enhanced`` filter computes requested mesh quality metrics on meshes. Both surfaces and volumic metrics can be computed with this plugin. @@ -67,19 +66,13 @@ """ -@smproxy.filter( name="PVMeshQualityEnhanced", label="Mesh Quality Enhanced" ) -@smhint.xml( '' ) -@smproperty.input( name="Input", port_index=0 ) -@smdomain.datatype( - dataTypes=[ "vtkUnstructuredGrid" ], - composite_data_supported=True, -) +@SISOFilter( category=FilterCategory.GEOS_QC, + decoratedLabel="Mesh Quality Enhanced", + decoratedType="vtkUnstructuredGrid" ) class PVMeshQualityEnhanced( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: """Merge collocated points.""" - super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) - self._filename: Optional[ str ] = None self._saveToFile: bool = True self._blockIndex: int = 0 @@ -172,7 +165,7 @@ def b01SetSaveToFile( self: Self, saveToFile: bool ) -> None: """ if self._saveToFile != saveToFile: self._saveToFile = saveToFile - self.Modified() + PVMeshQualityEnhanced.Modified( self ) @smproperty.stringvector( name="FilePath", label="File Path" ) @smdomain.xml( """ @@ -191,7 +184,7 @@ def b02SetFileName( self: Self, fname: str ) -> None: """ if self._filename != fname: self._filename = fname - self.Modified() + PVMeshQualityEnhanced.Modified( self ) @smproperty.xml( """ None: """ ) def b03GroupAdvancedOutputParameters( self: Self ) -> None: """Organize groups.""" - self.Modified() + PVMeshQualityEnhanced.Modified( self ) def Modified( self: Self ) -> None: """Overload Modified method to reset _blockIndex.""" self._blockIndex = 0 - super().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 ) + VTKPythonAlgorithmBase.Modified( self ) def _getQualityMetricsToUse( self: Self, selection: vtkDataArraySelection ) -> set[ int ]: """Get mesh quality metric indexes from user selection. @@ -247,27 +216,14 @@ def _getQualityMetricsToUse( self: Self, selection: vtkDataArraySelection ) -> s metricsNames: set[ str ] = getArrayChoices( selection ) return { getQualityMeasureIndexFromName( name ) for name in metricsNames } - def RequestData( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + def Filter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructuredGrid ) -> None: + """Is applying MeshQualityEnhanced to the input Mesh. Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + inputMesh : A mesh to transform. + outputMesh : A mesh transformed. - Returns: - int: 1 if calculation successfully ended, 0 otherwise. """ - inputMesh: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) - outputMesh: vtkUnstructuredGrid = vtkUnstructuredGrid.GetData( outInfoVec, 0 ) - assert inputMesh is not None, "Input server mesh is null." - assert outputMesh is not None, "Output pipeline is null." - triangleMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonCellSurfaceQualityMetric ).union( self._getQualityMetricsToUse( self._triangleQualityMetric ) ) quadMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonCellSurfaceQualityMetric ).union( @@ -302,7 +258,7 @@ def RequestData( stats: QualityMetricSummary = filter.GetQualityMetricSummary() self.saveFile( stats ) self._blockIndex += 1 - return 1 + return def saveFile( self: Self, stats: QualityMetricSummary ) -> None: """Export mesh quality metric summary file.""" diff --git a/geos-pv/src/geos/pv/plugins/PVPythonViewConfigurator.py b/geos-pv/src/geos/pv/plugins/PVPythonViewConfigurator.py index 5ba12bd1..a2467a88 100755 --- a/geos-pv/src/geos/pv/plugins/PVPythonViewConfigurator.py +++ b/geos-pv/src/geos/pv/plugins/PVPythonViewConfigurator.py @@ -16,6 +16,7 @@ update_paths() +from geos.mesh.utils.multiblockModifiers import mergeBlocks import geos.pv.utils.paraviewTreatments as pvt from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] createModifiedCallback, ) @@ -34,14 +35,22 @@ GetActiveSource, GetActiveView, Render, Show, servermanager, ) from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy, + smdomain, smproperty, ) from vtkmodules.vtkCommonCore import ( vtkDataArraySelection, vtkInformation, - vtkInformationVector, ) +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + VTKPythonAlgorithmBase ) + +from vtkmodules.vtkCommonDataModel import ( + vtkDataObject, + vtkMultiBlockDataSet, +) +from geos.pv.utils.details import SISOFilter, FilterCategory + __doc__ = """ PVPythonViewConfigurator is a Paraview plugin that allows to create cross-plots from input data using the PythonView. @@ -59,10 +68,9 @@ """ -@smproxy.filter( name="PVPythonViewConfigurator", label="Python View Configurator" ) -@smhint.xml( '' ) -@smproperty.input( name="Input" ) -@smdomain.datatype( dataTypes=[ "vtkDataObject" ], composite_data_supported=True ) +@SISOFilter( category=FilterCategory.GEOS_UTILS, + decoratedLabel="Python View Configurator", + decoratedType="vtkDataObject" ) class PVPythonViewConfigurator( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: @@ -70,7 +78,7 @@ def __init__( self: Self ) -> None: Input is a vtkDataObject. """ - super().__init__( nInputPorts=1, nOutputPorts=1 ) + # super().__init__( nInputPorts=1, nOutputPorts=1 ) # Python view layout and object. self.m_layoutName: str = "" self.m_pythonView: Any @@ -80,6 +88,9 @@ def __init__( self: Self ) -> None: # Input source and curve names. inputSource = GetActiveSource() dataset = servermanager.Fetch( inputSource ) + # Handle vtkMultiBlockDataSet by merging blocks first + if isinstance( dataset, vtkMultiBlockDataSet ): + dataset = mergeBlocks( dataset, keepPartialAttributes=True ) dataframe: pd.DataFrame = pvt.vtkToDataframe( dataset ) self.m_pathPythonViewScript: Path = geos_pv_path / "src/geos/pv/pythonViewUtils/mainPythonView.py" @@ -806,47 +817,14 @@ def FillInputPortInformation( self: Self, port: int, info: vtkInformation ) -> i info.Set( self.INPUT_REQUIRED_DATA_TYPE(), "vtkDataObject" ) return 1 - 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 ], # noqa: F841 - outInfoVec: vtkInformationVector, # noqa: F841 - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + def Filter( self, inputMesh: vtkDataObject, outputMesh: vtkDataObject ) -> None: + """Dummy interface for plugin to fit decorator reqs. Args: - request (vtkInformation): Request. - inInfoVec (list[vtkInformationVector]): Input objects. - outInfoVec (vtkInformationVector): Output objects. + inputMesh : A dummy mesh to transform. + outputMesh : A dummy mesh transformed. - Returns: - int: 1 if calculation successfully ended, 0 otherwise. """ - # pythonViewGeneration assert self.m_pythonView is not None, "No Python View was found." viewSize = GetActiveView().ViewSize self.m_userChoices[ "ratio" ] = viewSize[ 0 ] / viewSize[ 1 ] @@ -855,4 +833,4 @@ def RequestData( self.defineCurvesAspect() self.m_pythonView.Script = self.buildPythonViewScript() Render() - return 1 + return diff --git a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py index e6e3d55e..089e6a8f 100644 --- a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py +++ b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py @@ -7,8 +7,7 @@ from typing_extensions import Self from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - smdomain, smhint, smproperty, smproxy, -) + VTKPythonAlgorithmBase ) from vtkmodules.vtkCommonDataModel import ( vtkPointSet, ) @@ -21,7 +20,7 @@ update_paths() from geos.mesh.processing.SplitMesh import SplitMesh -from geos.pv.utils.AbstractPVPluginVtkWrapper import AbstractPVPluginVtkWrapper +from geos.pv.utils.details import SISOFilter, FilterCategory __doc__ = """ Split each cell of input mesh to smaller cells. @@ -37,32 +36,23 @@ """ -@smproxy.filter( name="PVSplitMesh", label="Split Mesh" ) -@smhint.xml( '' ) -@smproperty.input( name="Input", port_index=0 ) -@smdomain.datatype( - dataTypes=[ "vtkPointSet" ], - composite_data_supported=True, -) -class PVSplitMesh( AbstractPVPluginVtkWrapper ): +@SISOFilter( category=FilterCategory.GEOS_UTILS, + decoratedLabel="Split Mesh", + decoratedType="vtkPointSet" ) +class PVSplitMesh( VTKPythonAlgorithmBase ): def __init__( self: Self ) -> None: """Split mesh cells.""" - super().__init__() + pass - def applyVtkFilter( - self: Self, - input: vtkPointSet, - ) -> vtkPointSet: + def Filter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) -> None: """Apply vtk filter. Args: - input (vtkPointSet): input mesh - - Returns: - vtkPointSet: output mesh + inputMesh(vtkPointSet): Input mesh. + outputMesh: Output mesh. """ filter: SplitMesh = SplitMesh() - filter.SetInputDataObject( input ) + filter.SetInputDataObject( inputMesh ) filter.Update() - return filter.GetOutputDataObject( 0 ) + outputMesh.ShallowCopy( filter.GetOutputDataObject( 0 ) ) diff --git a/geos-pv/src/geos/pv/utils/AbstractPVPluginVtkWrapper.py b/geos-pv/src/geos/pv/utils/AbstractPVPluginVtkWrapper.py deleted file mode 100644 index 8ae8c27f..00000000 --- a/geos-pv/src/geos/pv/utils/AbstractPVPluginVtkWrapper.py +++ /dev/null @@ -1,97 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Martin Lemay -# ruff: noqa: E402 # disable Module level import not at top of file -from typing import Any -from typing_extensions import Self - -from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase, ) - -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, -) - -__doc__ = """ -AbstractPVPluginVtkWrapper module defines the parent Paraview plugin from which inheritates PV plugins that directly wrap a vtk filter. - -To use it, make children PV plugins inherited from AbstractPVPluginVtkWrapper. Output mesh is of same type as input mesh. -If output type needs to be specified, this must be done in the child class. -""" - - -class AbstractPVPluginVtkWrapper( VTKPythonAlgorithmBase ): - - def __init__( self: Self ) -> None: - """Abstract Paraview Plugin class.""" - super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkPointSet" ) - - 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 ) - - 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. - """ - try: - inputMesh: Any = self.GetInputData( inInfoVec, 0, 0 ) - outputMesh: Any = self.GetOutputData( outInfoVec, 0 ) - assert inputMesh is not None, "Input server mesh is null." - assert outputMesh is not None, "Output pipeline is null." - - tmpMesh = self.applyVtkFilter( inputMesh ) - assert tmpMesh is not None, "Output mesh is null." - outputMesh.ShallowCopy( tmpMesh ) - print( "Filter was successfully applied." ) - except ( AssertionError, Exception ) as e: - print( f"Filter failed due to: {e}" ) - return 0 - return 1 - - def applyVtkFilter( - self: Self, - input: Any, - ) -> Any: - """Apply vtk filter. - - Args: - input (Any): input object - - Returns: - Any: output mesh - """ - pass diff --git a/geos-pv/src/geos/pv/utils/details.py b/geos-pv/src/geos/pv/utils/details.py new file mode 100644 index 00000000..44cb102c --- /dev/null +++ b/geos-pv/src/geos/pv/utils/details.py @@ -0,0 +1,195 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay, Romain Baville, Jacques Franc +# ruff: noqa: E402 # disable Module level import not at top of file +# mypy: disable-error-code="misc" +import sys +from pathlib import Path +# Add Enum for filter categories +from functools import update_wrapper +from typing import Protocol, Any, Type, TypeVar, Callable, runtime_checkable, Union +from abc import abstractmethod +from enum import Enum + +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 vtkmodules.vtkCommonDataModel import ( + vtkMultiBlockDataSet, + vtkDataObject, +) + +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() + +__doc__ = """ +Set of decorators that allows quicker generation of DataSet derived to the same DataSet derived filters. If it a list of type is provided, then the unique output type is set to vtkDataObject. + +Usage is: + + from geos.pv.utils.details import SISOFilter, FilterCategory + + @SISO(category=FilterCategory.GEOS_UTILS,decoratedLabel='Awesome Filter',decoratedType='vtkMultiBlockDataSet') + class PVMyFilter: + ... + +""" + + +# Enum for filter categories +class FilterCategory( str, Enum ): + """String Enum to sort into category in PV task bar under Plugins.""" + GEOS_PROP = '0- Geos Pre-processing' + GEOS_MESH = '1- Geos Mesh' + GEOS_GEOMECHANICS = '2- Geos Geomechanics' + GEOS_PV = '3- Geos PV' + GEOS_UTILS = '4- Geos Utils' + GEOS_QC = '5- Geos QC' + # Add more as needed + + +U = TypeVar( 'U', bound='vtkDataObject' ) + +@runtime_checkable +class IsSISOFilter( Protocol[ U ] ): + """Protocol to ensure that the wrapped filter defines the correct Filter core function.""" + + @abstractmethod + def Filter( + self, + inputMesh: U, + outputMesh: U, + ) -> None: + """Define filter here. + + Args: + inputMesh : A mesh to transform + outputMesh : A mesh transformed + + """ + raise NotImplementedError + + +T = TypeVar( 'T', bound='IsSISOFilter' ) + + +def SISOFilter( category: FilterCategory, decoratedLabel: str, + decoratedType: Union[ str, list ] ) -> Callable[ [ Type[ T ] ], Type[ T ] ]: + """Decorate Single Input Single Output (SISO) filter.""" + + def decoratedClass( cls: Type[ T ] ) -> Type[ T ]: + """Outer wrapper function. All is in the WrappingClass below.""" + originalInit = cls.__init__ + + class WrappingClass( cls ): # type: ignore[valid-type] + + def __init__( self, *ar: Any, **kw: Any ) -> None: + """Pre-init the filter with the Base algo and I/O single type (usually vtkMultiBlockDataSet). + + Args: + ar : Fowarded arguments + kw : Forwarded keywords args + """ + VTKPythonAlgorithmBase.__init__( + self, + nInputPorts=1, + nOutputPorts=1, + inputType=decoratedType if isinstance( decoratedType, str ) else "vtkDataObject", + outputType=decoratedType if isinstance( decoratedType, str ) else "vtkDataObject" ) + + #If wrapped class has more to init there it is applied + #avoid the overwritten init by decorator taking place of the cls + if originalInit is not object.__init__: + originalInit( self, *ar, **kw ) + + def RequestDataObject( + 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 VTKPythonAlgorithmBase.RequestDataObject( self, request, inInfoVec, + outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + 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 ) + + cls.Filter( self, inputMesh, outputMesh ) + return 1 + + # Copy all methods and attributes from cls, including decorator metadata + for attrName in dir( cls ): + if attrName.startswith( '_' ): + continue # Skip private/magic methods (already handled or inherited) + + attr = getattr( cls, attrName ) + # Copy methods with their decorators + if callable( attr ) and attrName not in WrappingClass.__dict__: + setattr( WrappingClass, attrName, attr ) + + # Copy metadata + WrappingClass.__name__ = cls.__name__ + WrappingClass.__qualname__ = cls.__qualname__ + WrappingClass.__module__ = cls.__module__ + WrappingClass.__doc__ = cls.__doc__ + update_wrapper( WrappingClass, cls, updated=[] ) + + #decorate it old fashion way + WrappingClass = smdomain.datatype( + dataTypes=[ decoratedType ] if isinstance( decoratedType, str ) else decoratedType, + composite_data_supported=True, + )( WrappingClass ) + WrappingClass = smproperty.input( name="Input", port_index=0 )( WrappingClass ) + # Use enum value for category + WrappingClass = smhint.xml( f'' )( WrappingClass ) + WrappingClass = smproxy.filter( name=getattr( cls, '__name__', str( cls ) ), + label=decoratedLabel )( WrappingClass ) + return WrappingClass + + return decoratedClass diff --git a/geos-pv/src/geos/pv/utils/paraviewTreatments.py b/geos-pv/src/geos/pv/utils/paraviewTreatments.py index 41894e4f..2856aee7 100644 --- a/geos-pv/src/geos/pv/utils/paraviewTreatments.py +++ b/geos-pv/src/geos/pv/utils/paraviewTreatments.py @@ -37,6 +37,7 @@ vtkTable, vtkUnstructuredGrid, ) +from vtkmodules.vtkFiltersParallelDIY2 import vtkGenerateGlobalIds from geos.utils.GeosOutputsConstants import ( ComponentNameEnum, @@ -122,8 +123,18 @@ def vtkUnstructuredGridCellsToDataframe( grid: vtkUnstructuredGrid ) -> pd.DataF Returns: pd.DataFrame: Pandas dataframe. """ - cellIdAttributeName = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName + cellIdAttributeName: str = GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName cellData = grid.GetCellData() + if not cellData.HasArray( GeosMeshOutputsEnum.VTK_ORIGINAL_CELL_ID.attributeName ): + print( "We have to create global ids." ) + idFilter = vtkGenerateGlobalIds() + idFilter.SetInputData( grid ) + idFilter.Update() + grid = idFilter.GetOutput() + cellData = grid.GetCellData() # Update cellData to point to the new grid's cell data + cellIdAttributeName = "GlobalCellIds" + assert cellData.HasArray(cellIdAttributeName), "Invalid global ids array name selected." + numberCells: int = grid.GetNumberOfCells() data: dict[ str, Any ] = {} for i in range( cellData.GetNumberOfArrays() ):