diff --git a/docs/geos_pv_docs/processing.rst b/docs/geos_pv_docs/processing.rst index f5fcaf243..7655ede61 100644 --- a/docs/geos_pv_docs/processing.rst +++ b/docs/geos_pv_docs/processing.rst @@ -18,3 +18,9 @@ PVSplitMesh ---------------------------------- .. automodule:: geos.pv.plugins.PVSplitMesh + + +PVClipToMainFrame +---------------------------------- + +.. automodule:: geos.pv.plugins.PVClipToMainFrame diff --git a/geos-mesh/src/geos/mesh/processing/ClipToMainFrame.py b/geos-mesh/src/geos/mesh/processing/ClipToMainFrame.py index ecc099701..bc24b0349 100644 --- a/geos-mesh/src/geos/mesh/processing/ClipToMainFrame.py +++ b/geos-mesh/src/geos/mesh/processing/ClipToMainFrame.py @@ -1,6 +1,11 @@ # SPDX-License-Identifier: Apache 2.0 # SPDX-FileCopyrightText: Copyright 2023-2025 TotalEnergies # SPDX-FileContributor: Jacques Franc +from typing import Tuple +import logging + +import numpy as np +import numpy.typing as npt from vtkmodules.numpy_interface import dataset_adapter as dsa from vtkmodules.vtkCommonCore import vtkPoints @@ -11,15 +16,9 @@ from vtkmodules.vtkCommonTransforms import vtkLandmarkTransform from vtkmodules.vtkFiltersGeneral import vtkTransformFilter -from geos.utils.Logger import logging, Logger, getLogger - +from geos.utils.Logger import ( Logger, getLogger ) from geos.mesh.utils.genericHelpers import getMultiBlockBounds -import numpy as np -import numpy.typing as npt - -from typing import Tuple - __doc__ = """ Module to clip a mesh to the main frame using rigid body transformation. @@ -241,6 +240,9 @@ def ComputeTransform( self ) -> None: # dispatch to ClipToMainFrame depending on input type if isinstance( self.GetInput(), vtkMultiBlockDataSet ): #locate reference point + self.logger.info( + "Processing MultiblockDataSet.\n Please make sure your MultiblockDataSet is owning a vtkUnstructured grid as a leaf." + ) try: idBlock = self.__locate_reference_point( self.GetInput() ) except IndexError: @@ -248,6 +250,7 @@ def ComputeTransform( self ) -> None: clip = ClipToMainFrameElement( self.GetInput().GetDataSet( idBlock ) ) else: + self.logger.info( "Processing untructuredGrid" ) clip = ClipToMainFrameElement( self.GetInput() ) clip.Update() diff --git a/geos-mesh/src/geos/mesh/utils/genericHelpers.py b/geos-mesh/src/geos/mesh/utils/genericHelpers.py index de0624fd9..481b391ff 100644 --- a/geos-mesh/src/geos/mesh/utils/genericHelpers.py +++ b/geos-mesh/src/geos/mesh/utils/genericHelpers.py @@ -36,7 +36,7 @@ def to_vtk_id_list( data: List[ int ] ) -> vtkIdList: return result -def vtk_iter( vtkContainer: vtkIdList | vtkCellTypes ) -> Iterator[ Any ]: +def vtk_iter( vtkContainer: Union[ vtkIdList, vtkCellTypes ] ) -> Iterator[ Any ]: """Utility function transforming a vtk "container" into an iterable. Args: diff --git a/geos-mesh/tests/test_clipToMainFrame.py b/geos-mesh/tests/test_clipToMainFrame.py index fecba2704..12f2c1940 100644 --- a/geos-mesh/tests/test_clipToMainFrame.py +++ b/geos-mesh/tests/test_clipToMainFrame.py @@ -6,7 +6,7 @@ import pytest import itertools from dataclasses import dataclass -from typing import Generator, Tuple +from typing import Generator, Tuple, List from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkHexahedron, vtkMultiBlockDataSet from vtkmodules.numpy_interface import dataset_adapter as dsa @@ -14,8 +14,6 @@ import numpy.typing as npt from vtkmodules.util.vtkConstants import VTK_HEXAHEDRON -from typing import List - from geos.mesh.processing.ClipToMainFrame import ClipToMainFrame Lx, Ly, Lz = 5, 2, 8 @@ -76,8 +74,9 @@ def __build_test_mesh( mxx: Tuple[ int, ...] ) -> Generator[ Expected, None, Non # Creating multiple meshes, each time with a different angles mesh = vtkUnstructuredGrid() - ( vtps := vtkPoints() ).SetData( dsa.numpy_support.numpy_to_vtk( pts ) ) - mesh.SetPoints( vtps ) + vpts = vtkPoints() + vpts.SetData( dsa.numpy_support.numpy_to_vtk( pts ) ) + mesh.SetPoints( vpts ) ids = vtkIdList() for i in range( nx - 1 ): @@ -103,7 +102,8 @@ def __build_test_mesh( mxx: Tuple[ int, ...] ) -> Generator[ Expected, None, Non "expected", [ item for t in list( itertools.product( [ -1, 1 ], repeat=3 ) ) for item in __build_test_mesh( t ) ] ) def test_clipToMainFrame_polyhedron( expected: Expected ) -> None: """Test the ClipToMainFrameFilter on a rotated and translated box hexa mesh.""" - ( filter := ClipToMainFrame() ).SetInputData( expected.mesh ) + filter = ClipToMainFrame() + filter.SetInputData( expected.mesh ) filter.ComputeTransform() filter.Update() output_mesh: vtkUnstructuredGrid = filter.GetOutput() @@ -133,10 +133,12 @@ def test_clipToMainFrame_polyhedron( expected: Expected ) -> None: def test_clipToMainFrame_generic( dataSetTest: vtkMultiBlockDataSet ) -> None: """Test the ClipToMainFrameFilter on a MultiBlockDataSet.""" multiBlockDataSet: vtkMultiBlockDataSet = dataSetTest( "multiblock" ) - ( filter := ClipToMainFrame() ).SetInputData( multiBlockDataSet ) + filter = ClipToMainFrame() + filter.SetInputData( multiBlockDataSet ) filter.ComputeTransform() filter.Update() print( filter.GetTransform() ) output_mesh: vtkMultiBlockDataSet = filter.GetOutputDataObject( 0 ) assert output_mesh.GetNumberOfPoints() == multiBlockDataSet.GetNumberOfPoints() assert output_mesh.GetNumberOfCells() == multiBlockDataSet.GetNumberOfCells() + assert output_mesh.IsA( 'vtkMultiBlockDataSet' ) diff --git a/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py b/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py new file mode 100644 index 000000000..8c0cfdb2d --- /dev/null +++ b/geos-pv/src/geos/pv/plugins/PVClipToMainFrame.py @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Jacques Franc +# 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, +) # 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, +) + +# 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.ClipToMainFrame import ClipToMainFrame + +__doc__ = """ +Clip the input mesh to the main frame applying the correct LandmarkTransform + +To use it: + +* Load the module in Paraview: Tools>Manage Plugins...>Load new>PVClipToMainFrame. +* Apply. + +""" + + +@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, +) +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. + + 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, 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