From cf9a43770b023bf8496c85c253b64049adf06764 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Thu, 20 Nov 2025 09:23:38 +0100 Subject: [PATCH 1/6] Refactor SplitMesh --- .../generic_processing_tools/SplitMesh.py | 270 ++++++++---------- geos-processing/tests/test_SplitMesh.py | 18 +- geos-pv/src/geos/pv/plugins/PVSplitMesh.py | 21 +- 3 files changed, 138 insertions(+), 171 deletions(-) diff --git a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py index f4a7a6bd..8a59ce4e 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py @@ -1,35 +1,18 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Antoine Mazuyer, Martin Lemay +import logging import numpy as np import numpy.typing as npt from typing_extensions import Self -from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase -from vtkmodules.vtkCommonCore import ( - vtkPoints, - vtkIdTypeArray, - vtkDataArray, - vtkInformation, - vtkInformationVector, -) -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, - vtkCellArray, - vtkCellData, - vtkCell, - vtkCellTypes, - VTK_TRIANGLE, - VTK_QUAD, - VTK_TETRA, - VTK_HEXAHEDRON, - VTK_PYRAMID, - VTK_WEDGE, - VTK_POLYHEDRON, - VTK_POLYGON, -) - -from vtkmodules.util.numpy_support import ( numpy_to_vtk, vtk_to_numpy ) +from vtkmodules.vtkCommonCore import vtkPoints, vtkIdTypeArray, vtkDataArray +from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkCellArray, vtkCellData, vtkCell, vtkCellTypes, + VTK_TRIANGLE, VTK_QUAD, VTK_TETRA, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_WEDGE, + VTK_POLYHEDRON, VTK_POLYGON ) +from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy + +from geos.utils.Logger import ( Logger, getLogger ) from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced from geos.mesh.model.CellTypeCounts import CellTypeCounts @@ -45,145 +28,132 @@ from geos.processing.generic_processing_tools.SplitMesh import SplitMesh # Filter inputs - input: vtkUnstructuredGrid + inputMesh: vtkUnstructuredGrid + speHandler: bool # optional # Instantiate the filter - splitMeshFilter: SplitMesh = SplitMesh() + splitMeshFilter: SplitMesh = SplitMesh( inputMesh, speHandler ) - # Set input data object - splitMeshFilter.SetInputDataObject( input ) + # Use your own handler (if speHandler is True) + yourHandler: logging.Handler + splitMeshFilter.setLoggerHandler( yourHandler ) # Do calculations - splitMeshFilter.Update() + splitMeshFilter.applyFilter() - # Get output object - output :vtkUnstructuredGrid = splitMeshFilter.GetOutputDataObject( 0 ) + # Get splitted mesh + outputMesh: vtkUnstructuredGrid = splitMeshFilter.getOutput() """ +loggerTitle: str = "Split Mesh" + -class SplitMesh( VTKPythonAlgorithmBase ): +class SplitMesh(): - def __init__( self ) -> None: - """SplitMesh filter splits each cell using edge centers.""" - super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + def __init__( self, inputMesh: vtkUnstructuredGrid, speHandler: bool = False ) -> None: + """SplitMesh filter splits each cell using edge centers. - self.inData: vtkUnstructuredGrid + Args: + inputMesh (vtkUnstructuredGrid): The input mesh. + speHandler (bool, optional): True to use a specific handler, False to use the internal handler. + Defaults to False. + """ + self.inputMesh: vtkUnstructuredGrid = inputMesh + self.outputMesh: vtkUnstructuredGrid = inputMesh.NewInstance() self.cells: vtkCellArray self.points: vtkPoints self.originalId: vtkIdTypeArray self.cellTypes: list[ int ] + self.speHandler: bool = speHandler + self.handler: None | logging.Handler = None - def FillInputPortInformation( self: Self, port: int, info: vtkInformation ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. - - Args: - port (int): input port - info (vtkInformationVector): info - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - if port == 0: - info.Set( self.INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid" ) - return 1 - - def RequestDataObject( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + # Logger + self.logger: Logger + if not speHandler: + self.logger = getLogger( loggerTitle, True ) + else: + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + self.logger.propagate = False - Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: + """Set a specific handler for the filter logger. - 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 ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels. Args: - request (vtkInformation): request - inInfoVec (list[vtkInformationVector]): input objects - outInfoVec (vtkInformationVector): output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + handler (logging.Handler): The handler to add. """ - self.inData = self.GetInputData( inInfoVec, 0, 0 ) - output: vtkUnstructuredGrid = self.GetOutputData( outInfoVec, 0 ) - - assert self.inData is not None, "Input mesh is undefined." - assert output is not None, "Output mesh is undefined." - - nbCells: int = self.inData.GetNumberOfCells() - counts: CellTypeCounts = self._getCellCounts() - nbTet: int = counts.getTypeCount( VTK_TETRA ) - nbPyr: int = counts.getTypeCount( VTK_PYRAMID ) - nbHex: int = counts.getTypeCount( VTK_HEXAHEDRON ) - nbTriangles: int = counts.getTypeCount( VTK_TRIANGLE ) - nbQuad: int = counts.getTypeCount( VTK_QUAD ) - nbPolygon: int = counts.getTypeCount( VTK_POLYGON ) - nbPolyhedra: int = counts.getTypeCount( VTK_POLYHEDRON ) - assert counts.getTypeCount( VTK_WEDGE ) == 0, "Input mesh contains wedges that are not currently supported." - assert nbPolyhedra * nbPolygon == 0, ( "Input mesh is composed of both polygons and polyhedra," - " but it must contains only one of the two." ) - nbNewPoints: int = 0 - nbNewPoints = nbHex * 19 + nbTet * 6 + nbPyr * 9 if nbPolyhedra > 0 else nbTriangles * 3 + nbQuad * 5 - nbNewCells: int = nbHex * 8 + nbTet * 8 + nbPyr * 10 * nbTriangles * 4 + nbQuad * 4 - - self.points = vtkPoints() - self.points.DeepCopy( self.inData.GetPoints() ) - self.points.Resize( self.inData.GetNumberOfPoints() + nbNewPoints ) - - self.cells = vtkCellArray() - self.cells.AllocateExact( nbNewCells, 8 ) - self.originalId = vtkIdTypeArray() - self.originalId.SetName( "OriginalID" ) - self.originalId.Allocate( nbNewCells ) - self.cellTypes = [] - for c in range( nbCells ): - cell: vtkCell = self.inData.GetCell( c ) - cellType: int = cell.GetCellType() - if cellType == VTK_HEXAHEDRON: - self._splitHexahedron( cell, c ) - elif cellType == VTK_TETRA: - self._splitTetrahedron( cell, c ) - elif cellType == VTK_PYRAMID: - self._splitPyramid( cell, c ) - elif cellType == VTK_TRIANGLE: - self._splitTriangle( cell, c ) - elif cellType == VTK_QUAD: - self._splitQuad( cell, c ) - else: - raise TypeError( f"Cell type {vtkCellTypes.GetClassNameFromTypeId(cellType)} is not supported." ) - # add points and cells - output.SetPoints( self.points ) - output.SetCells( self.cellTypes, self.cells ) - # add attribute saving original cell ids - cellArrays: vtkCellData = output.GetCellData() - assert cellArrays is not None, "Cell data is undefined." - cellArrays.AddArray( self.originalId ) - # transfer all cell arrays - self._transferCellArrays( output ) - return 1 + self.handler = handler + if len( self.logger.handlers ) == 0: + 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 ) -> None: + """Apply the filter SplitMesh.""" + self.logger.info( f"Applying filter { self.logger.name }." ) + try: + nbCells: int = self.inputMesh.GetNumberOfCells() + counts: CellTypeCounts = self._getCellCounts() + nbTet: int = counts.getTypeCount( VTK_TETRA ) + nbPyr: int = counts.getTypeCount( VTK_PYRAMID ) + nbHex: int = counts.getTypeCount( VTK_HEXAHEDRON ) + nbTriangles: int = counts.getTypeCount( VTK_TRIANGLE ) + nbQuad: int = counts.getTypeCount( VTK_QUAD ) + nbPolygon: int = counts.getTypeCount( VTK_POLYGON ) + nbPolyhedra: int = counts.getTypeCount( VTK_POLYHEDRON ) + assert counts.getTypeCount( VTK_WEDGE ) == 0, "Input mesh contains wedges that are not currently supported." + assert nbPolyhedra * nbPolygon == 0, ( "Input mesh is composed of both polygons and polyhedra," + " but it must contains only one of the two." ) + nbNewPoints: int = 0 + nbNewPoints = nbHex * 19 + nbTet * 6 + nbPyr * 9 if nbPolyhedra > 0 else nbTriangles * 3 + nbQuad * 5 + nbNewCells: int = nbHex * 8 + nbTet * 8 + nbPyr * 10 * nbTriangles * 4 + nbQuad * 4 + + self.points = vtkPoints() + self.points.DeepCopy( self.inputMesh.GetPoints() ) + self.points.Resize( self.inputMesh.GetNumberOfPoints() + nbNewPoints ) + + self.cells = vtkCellArray() + self.cells.AllocateExact( nbNewCells, 8 ) + self.originalId = vtkIdTypeArray() + self.originalId.SetName( "OriginalID" ) + self.originalId.Allocate( nbNewCells ) + self.cellTypes = [] + for c in range( nbCells ): + cell: vtkCell = self.inputMesh.GetCell( c ) + cellType: int = cell.GetCellType() + if cellType == VTK_HEXAHEDRON: + self._splitHexahedron( cell, c ) + elif cellType == VTK_TETRA: + self._splitTetrahedron( cell, c ) + elif cellType == VTK_PYRAMID: + self._splitPyramid( cell, c ) + elif cellType == VTK_TRIANGLE: + self._splitTriangle( cell, c ) + elif cellType == VTK_QUAD: + self._splitQuad( cell, c ) + else: + raise TypeError( f"Cell type {vtkCellTypes.GetClassNameFromTypeId(cellType)} is not supported." ) + # add points and cells + self.outputMesh.SetPoints( self.points ) + self.outputMesh.SetCells( self.cellTypes, self.cells ) + # add attribute saving original cell ids + cellArrays: vtkCellData = self.outputMesh.GetCellData() + assert cellArrays is not None, "Cell data is undefined." + cellArrays.AddArray( self.originalId ) + # transfer all cell arrays + self._transferCellArrays( self.outputMesh ) + self.logger.info( f"The filter { self.logger.name } succeeded." ) + except Exception as e: + self.logger.error( f"The filter {self.logger.name } failed.\n{ e }" ) + + return + + def getOutput( self: Self ) -> vtkUnstructuredGrid: + """Get the splitted mesh computed.""" + return self.outputMesh def _getCellCounts( self: Self ) -> CellTypeCounts: """Get the number of cells of each type. @@ -191,9 +161,11 @@ def _getCellCounts( self: Self ) -> CellTypeCounts: Returns: CellTypeCounts: cell type counts """ - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() - cellTypeCounterEnhancedFilter.SetInputDataObject( self.inData ) - cellTypeCounterEnhancedFilter.Update() + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( + self.inputMesh, self.speHandler ) + if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: + cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) + cellTypeCounterEnhancedFilter.applyFilter() return cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() def _addMidPoint( self: Self, ptA: int, ptB: int ) -> int: @@ -450,11 +422,11 @@ def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> bool splittedMesh (vtkUnstructuredGrid): splitted mesh Returns: - bool: True if arrays were successfully transfered. + bool: True if arrays were successfully transferred. """ cellDataSplitted: vtkCellData = splittedMesh.GetCellData() assert cellDataSplitted is not None, "Cell data of splitted mesh should be defined." - cellData: vtkCellData = self.inData.GetCellData() + cellData: vtkCellData = self.inputMesh.GetCellData() assert cellData is not None, "Cell data of input mesh should be defined." # for each array of input mesh for i in range( cellData.GetNumberOfArrays() ): @@ -476,4 +448,4 @@ def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> bool cellDataSplitted.AddArray( newArray ) cellDataSplitted.Modified() splittedMesh.Modified() - return True + return True \ No newline at end of file diff --git a/geos-processing/tests/test_SplitMesh.py b/geos-processing/tests/test_SplitMesh.py index 0612b729..4fd1c3c4 100644 --- a/geos-processing/tests/test_SplitMesh.py +++ b/geos-processing/tests/test_SplitMesh.py @@ -6,8 +6,7 @@ import numpy as np import numpy.typing as npt import pytest -from typing import ( - Iterator, ) +from typing import Iterator from geos.mesh.utils.genericHelpers import createSingleCellMesh from geos.processing.generic_processing_tools.SplitMesh import SplitMesh @@ -17,11 +16,7 @@ from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkCellArray, vtkCellData, vtkCellTypes, VTK_TRIANGLE, VTK_QUAD, VTK_TETRA, VTK_HEXAHEDRON, VTK_PYRAMID ) -from vtkmodules.vtkCommonCore import ( - vtkPoints, - vtkIdList, - vtkDataArray, -) +from vtkmodules.vtkCommonCore import vtkPoints, vtkIdList, vtkDataArray data_root: str = os.path.join( os.path.dirname( os.path.abspath( __file__ ) ), "data" ) @@ -149,10 +144,9 @@ def test_single_cell_split( test_case: TestCase ) -> None: test_case (TestCase): test case """ cellTypeName: str = vtkCellTypes.GetClassNameFromTypeId( test_case.cellType ) - splitMeshFilter: SplitMesh = SplitMesh() - splitMeshFilter.SetInputDataObject( test_case.mesh ) - splitMeshFilter.Update() - output: vtkUnstructuredGrid = splitMeshFilter.GetOutputDataObject( 0 ) + splitMeshFilter: SplitMesh = SplitMesh( test_case.mesh ) + splitMeshFilter.applyFilter() + output: vtkUnstructuredGrid = splitMeshFilter.getOutput() assert output is not None, "Output mesh is undefined." pointsOut: vtkPoints = output.GetPoints() assert pointsOut is not None, "Points from output mesh are undefined." @@ -202,4 +196,4 @@ def test_single_cell_split( test_case: TestCase ) -> None: nbArraySplited: int = cellData.GetNumberOfArrays() assert nbArraySplited == nbArrayInput + 1, f"Number of arrays should be {nbArrayInput + 1}" - #assert False + #assert False \ No newline at end of file diff --git a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py index 728b398f..04952083 100644 --- a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py +++ b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py @@ -6,11 +6,11 @@ from pathlib import Path from typing_extensions import Self -from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase ) +from paraview.util.vtkAlgorithm import VTKPythonAlgorithmBase # type: ignore[import-not-found] +from paraview.detail.loghandler import VTKHandler # type: ignore[import-not-found] +# source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py -from vtkmodules.vtkCommonDataModel import ( - vtkPointSet, ) +from vtkmodules.vtkCommonDataModel import vtkPointSet # update sys.path to load all GEOS Python Package dependencies geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent @@ -20,7 +20,7 @@ update_paths() from geos.processing.generic_processing_tools.SplitMesh import SplitMesh -from geos.pv.utils.details import SISOFilter, FilterCategory +from geos.pv.utils.details import ( SISOFilter, FilterCategory ) __doc__ = """ Split each cell of input mesh to smaller cells. @@ -50,9 +50,10 @@ def ApplyFilter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) - inputMesh(vtkPointSet): Input mesh. outputMesh: Output mesh. """ - splitMeshFilter: SplitMesh = SplitMesh() - splitMeshFilter.SetInputDataObject( inputMesh ) - splitMeshFilter.Update() - outputMesh.ShallowCopy( splitMeshFilter.GetOutputDataObject( 0 ) ) + splitMeshFilter: SplitMesh = SplitMesh( inputMesh, True ) + if len( splitMeshFilter.logger.handlers ) == 0: + splitMeshFilter.setLoggerHandler( VTKHandler() ) + splitMeshFilter.applyFilter() + outputMesh.ShallowCopy( splitMeshFilter.getOutput() ) - return + return \ No newline at end of file From fe04cb2c82ff3918478670af59b5d39a5595ba73 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Thu, 20 Nov 2025 09:27:58 +0100 Subject: [PATCH 2/6] Refactor CellTypeCounterEnhanced --- .../pre_processing/CellTypeCounterEnhanced.py | 151 ++++++++++-------- .../tests/test_CellTypeCounterEnhanced.py | 34 ++-- .../pv/plugins/PVCellTypeCounterEnhanced.py | 27 ++-- 3 files changed, 104 insertions(+), 108 deletions(-) diff --git a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py index c8f02e8b..e7fc39fd 100644 --- a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py @@ -1,17 +1,15 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Antoine Mazuyer, Martin Lemay +import logging + from typing_extensions import Self -from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, - vtkIntArray, -) -from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkCell, vtkTable, vtkCellTypes, VTK_VERTEX ) +from vtkmodules.vtkCommonCore import vtkIntArray +from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkCell, vtkTable, vtkCellTypes, VTK_VERTEX from geos.mesh.model.CellTypeCounts import CellTypeCounts from geos.mesh.stats.meshQualityMetricHelpers import getAllCellTypes +from geos.utils.Logger import ( Logger, getLogger ) __doc__ = """ CellTypeCounterEnhanced module is a vtk filter that computes cell type counts. @@ -25,86 +23,99 @@ from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced # Filter inputs - input: vtkUnstructuredGrid + inputMesh: vtkUnstructuredGrid + speHandler: bool # defaults to False # Instantiate the filter - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( inputMesh, speHandler ) - # Set input data object - cellTypeCounterEnhancedFilter.SetInputDataObject(input) + # Set the handler of yours (only if speHandler is True). + yourHandler: logging.Handler + cellTypeCounterEnhancedFilter.setLoggerHandler( yourHandler ) # Do calculations - cellTypeCounterEnhancedFilter.Update() + cellTypeCounterEnhancedFilter.applyFilter() - # Get counts + # Get result counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() + outputTable: vtkTable = cellTypeCounterEnhancedFilter.getOutput() """ +loggerTitle: str = "Cell Type Counter Enhanced" -class CellTypeCounterEnhanced( VTKPythonAlgorithmBase ): - def __init__( self ) -> None: - """CellTypeCounterEnhanced filter computes mesh stats.""" - super().__init__( nInputPorts=1, nOutputPorts=1, inputType="vtkUnstructuredGrid", outputType="vtkTable" ) - self._counts: CellTypeCounts = CellTypeCounts() +class CellTypeCounterEnhanced(): - def FillInputPortInformation( self: Self, port: int, info: vtkInformation ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. + def __init__( + self: Self, + inputMesh: vtkUnstructuredGrid, + speHandler: bool = False, + ) -> None: + """CellTypeCounterEnhanced filter computes mesh stats. Args: - port (int): Input port - info (vtkInformationVector): Info - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + inputMesh (vtkUnstructuredGrid): The input mesh. + speHandler (bool, optional): True to use a specific handler, False to use the internal handler. + Defaults to False. """ - if port == 0: - info.Set( self.INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid" ) - return 1 + self.inputMesh: vtkUnstructuredGrid = inputMesh + self.outTable: vtkTable = vtkTable() + self._counts: CellTypeCounts = CellTypeCounts() - def RequestData( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + # Logger. + self.logger: Logger + if not speHandler: + self.logger = getLogger( loggerTitle, True ) + else: + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + self.logger.propagate = False - Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: + """Set a specific handler for the filter logger. - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + 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. """ - inData: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) - outTable: vtkTable = vtkTable.GetData( outInfoVec, 0 ) - assert inData is not None, "Input mesh is undefined." - assert outTable is not None, "Output table is undefined." - - # compute cell type counts - self._counts.reset() - self._counts.setTypeCount( VTK_VERTEX, inData.GetNumberOfPoints() ) - for i in range( inData.GetNumberOfCells() ): - cell: vtkCell = inData.GetCell( i ) - self._counts.addType( cell.GetCellType() ) - - # create output table - # first reset output table - outTable.RemoveAllRows() - outTable.RemoveAllColumns() - outTable.SetNumberOfRows( 1 ) - - # create columns per types - for cellType in getAllCellTypes(): - array: vtkIntArray = vtkIntArray() - array.SetName( vtkCellTypes.GetClassNameFromTypeId( cellType ) ) - array.SetNumberOfComponents( 1 ) - array.SetNumberOfValues( 1 ) - array.SetValue( 0, self._counts.getTypeCount( cellType ) ) - outTable.AddColumn( array ) - return 1 + if len( self.logger.handlers ) == 0: + 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 ) -> None: + """Apply CellTypeCounterEnhanced filter.""" + self.logger.info( f"Apply filter { self.logger.name }." ) + try: + # compute cell type counts + self._counts.reset() + self._counts.setTypeCount( VTK_VERTEX, self.inputMesh.GetNumberOfPoints() ) + for i in range( self.inputMesh.GetNumberOfCells() ): + cell: vtkCell = self.inputMesh.GetCell( i ) + self._counts.addType( cell.GetCellType() ) + + # create output table + # first reset output table + self.outTable.RemoveAllRows() + self.outTable.RemoveAllColumns() + self.outTable.SetNumberOfRows( 1 ) + + # create columns per types + for cellType in getAllCellTypes(): + array: vtkIntArray = vtkIntArray() + array.SetName( vtkCellTypes.GetClassNameFromTypeId( cellType ) ) + array.SetNumberOfComponents( 1 ) + array.SetNumberOfValues( 1 ) + array.SetValue( 0, self._counts.getTypeCount( cellType ) ) + self.outTable.AddColumn( array ) + self.logger.info( f"The filter { self.logger.name } succeeded." ) + except AssertionError as e: + self.logger.error( f"The filter { self.logger.name } failed.\n{ e }" ) + + return def GetCellTypeCountsObject( self: Self ) -> CellTypeCounts: """Get CellTypeCounts object. @@ -113,3 +124,7 @@ def GetCellTypeCountsObject( self: Self ) -> CellTypeCounts: CellTypeCounts: CellTypeCounts object. """ return self._counts + + def getOutput( self: Self ) -> vtkTable: + """Get the computed vtkTable.""" + return self.outTable \ No newline at end of file diff --git a/geos-processing/tests/test_CellTypeCounterEnhanced.py b/geos-processing/tests/test_CellTypeCounterEnhanced.py index 03097775..6350bbf1 100644 --- a/geos-processing/tests/test_CellTypeCounterEnhanced.py +++ b/geos-processing/tests/test_CellTypeCounterEnhanced.py @@ -6,27 +6,15 @@ import numpy as np import numpy.typing as npt import pytest -from typing import ( - Iterator, ) +from typing import Iterator -from geos.mesh.utils.genericHelpers import createSingleCellMesh, createMultiCellMesh +from geos.mesh.utils.genericHelpers import ( createSingleCellMesh, createMultiCellMesh ) from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced from geos.mesh.model.CellTypeCounts import CellTypeCounts -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, - vtkCellTypes, - vtkCell, - VTK_TRIANGLE, - VTK_QUAD, - VTK_TETRA, - VTK_VERTEX, - VTK_POLYHEDRON, - VTK_POLYGON, - VTK_PYRAMID, - VTK_HEXAHEDRON, - VTK_WEDGE, -) +from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkCellTypes, vtkCell, VTK_TRIANGLE, VTK_QUAD, + VTK_TETRA, VTK_VERTEX, VTK_POLYHEDRON, VTK_POLYGON, VTK_PYRAMID, + VTK_HEXAHEDRON, VTK_WEDGE ) data_root: str = os.path.join( os.path.dirname( os.path.abspath( __file__ ) ), "data" ) @@ -71,9 +59,8 @@ def test_CellTypeCounterEnhanced_single( test_case: TestCase ) -> None: Args: test_case (TestCase): Test case """ - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() - cellTypeCounterEnhancedFilter.SetInputDataObject( test_case.mesh ) - cellTypeCounterEnhancedFilter.Update() + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( test_case.mesh ) + cellTypeCounterEnhancedFilter.applyFilter() countsObs: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() assert countsObs is not None, "CellTypeCounts is undefined" @@ -130,9 +117,8 @@ def test_CellTypeCounterEnhanced_multi( test_case: TestCase ) -> None: Args: test_case (TestCase): Test case """ - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() - cellTypeCounterEnhancedFilter.SetInputDataObject( test_case.mesh ) - cellTypeCounterEnhancedFilter.Update() + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( test_case.mesh ) + cellTypeCounterEnhancedFilter.applyFilter() countsObs: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() assert countsObs is not None, "CellTypeCounts is undefined" @@ -156,4 +142,4 @@ def test_CellTypeCounterEnhanced_multi( test_case: TestCase ) -> None: nbPolyhedra: int = np.sum( counts[ 2: ], dtype=int ) assert int( countsObs.getTypeCount( VTK_POLYGON ) ) == nbPolygon, f"The number of faces should be {nbPolygon}." assert int( - countsObs.getTypeCount( VTK_POLYHEDRON ) ) == nbPolyhedra, f"The number of polyhedra should be {nbPolyhedra}." + countsObs.getTypeCount( VTK_POLYHEDRON ) ) == nbPolyhedra, f"The number of polyhedra should be {nbPolyhedra}." \ No newline at end of file diff --git a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py index c5d3f046..4a0b8e99 100644 --- a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py @@ -9,15 +9,10 @@ from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy ) +from paraview.detail.loghandler import VTKHandler # type: ignore[import-not-found] -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, -) -from vtkmodules.vtkCommonDataModel import ( - vtkPointSet, - vtkTable, -) +from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector +from vtkmodules.vtkCommonDataModel import vtkPointSet, vtkTable # update sys.path to load all GEOS Python Package dependencies geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent @@ -137,10 +132,11 @@ def RequestData( assert inputMesh is not None, "Input server mesh is null." assert outputTable is not None, "Output pipeline is null." - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() - cellTypeCounterEnhancedFilter.SetInputDataObject( inputMesh ) - cellTypeCounterEnhancedFilter.Update() - outputTable.ShallowCopy( cellTypeCounterEnhancedFilter.GetOutputDataObject( 0 ) ) + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( inputMesh, True ) + if len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: + cellTypeCounterEnhancedFilter.setLoggerHandler( VTKHandler() ) + cellTypeCounterEnhancedFilter.applyFilter() + outputTable.ShallowCopy( cellTypeCounterEnhancedFilter.getOutput() ) # print counts in Output Messages view counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() @@ -151,8 +147,7 @@ def RequestData( try: with open( self._filename, 'w' ) as fout: fout.write( self._countsAll.print() ) - print( f"File {self._filename} was successfully written." ) + cellTypeCounterEnhancedFilter.logger.info( f"File {self._filename} was successfully written." ) except Exception as e: - print( "Error while exporting the file due to:" ) - print( str( e ) ) - return 1 + cellTypeCounterEnhancedFilter.logger.info( f"Error while exporting the file due to:\n{ e }" ) + return 1 \ No newline at end of file From 380a69316a8bd495b11650b6ed24ccb49c3a5f50 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Thu, 20 Nov 2025 09:31:13 +0100 Subject: [PATCH 3/6] Refactor MeshQualityEnhanced --- .../pre_processing/MeshQualityEnhanced.py | 190 ++++++++---------- .../tests/test_MeshQualityEnhanced.py | 20 +- .../geos/pv/plugins/PVMeshQualityEnhanced.py | 70 +++---- 3 files changed, 125 insertions(+), 155 deletions(-) diff --git a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py index fc8c662b..3f6ac283 100644 --- a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py @@ -1,24 +1,15 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Antoine Mazuyer, Martin Lemay, Paloma Martinez +import logging import numpy as np import numpy.typing as npt from typing import Optional, cast from typing_extensions import Self from vtkmodules.vtkFiltersCore import vtkExtractEdges from vtkmodules.vtkFiltersVerdict import vtkMeshQuality -from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase -from vtkmodules.vtkCommonCore import ( - vtkInformation, - vtkInformationVector, - vtkIdList, - vtkPoints, - vtkDataArray, - vtkIntArray, - vtkDoubleArray, - vtkIdTypeArray, - vtkMath, -) +from vtkmodules.vtkCommonCore import ( vtkIdList, vtkPoints, vtkDataArray, vtkIntArray, vtkDoubleArray, vtkIdTypeArray, + vtkMath ) from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkPolyData, vtkCellData, vtkPointData, vtkFieldData, vtkCell, vtkCell3D, vtkTetra, vtkCellTypes, vtkPolygon, VTK_TRIANGLE, VTK_QUAD, VTK_TETRA, VTK_PYRAMID, VTK_HEXAHEDRON, VTK_WEDGE, VTK_POLYGON, @@ -27,24 +18,17 @@ from geos.processing.pre_processing.CellTypeCounterEnhanced import CellTypeCounterEnhanced from geos.mesh.model.CellTypeCounts import CellTypeCounts -from geos.mesh.model.QualityMetricSummary import QualityMetricSummary, StatTypes +from geos.mesh.model.QualityMetricSummary import ( QualityMetricSummary, StatTypes ) from geos.mesh.utils.arrayHelpers import getAttributesFromDataSet -from geos.mesh.stats.meshQualityMetricHelpers import ( - getQualityMeasureNameFromIndex, - getQualityMetricFromIndex, - VtkCellQualityMetricEnum, - CellQualityMetricAdditionalEnum, - QualityMetricOtherEnum, - MeshQualityMetricEnum, - getAllCellTypesExtended, - getAllCellTypes, - getPolygonCellTypes, - getPolyhedronCellTypes, - getCellQualityMeasureFromCellType, - getChildrenCellTypes, -) +from geos.mesh.stats.meshQualityMetricHelpers import ( getQualityMeasureNameFromIndex, getQualityMetricFromIndex, + VtkCellQualityMetricEnum, CellQualityMetricAdditionalEnum, + QualityMetricOtherEnum, MeshQualityMetricEnum, + getAllCellTypesExtended, getAllCellTypes, getPolygonCellTypes, + getPolyhedronCellTypes, getCellQualityMeasureFromCellType, + getChildrenCellTypes ) import geos.utils.geometryFunctions as geom +from geos.utils.Logger import ( Logger, getLogger ) __doc__ = """ MeshQualityEnhanced module is a vtk filter that computes mesh quality stats. @@ -60,13 +44,15 @@ from geos.processing.pre_processing.MeshQualityEnhanced import MeshQualityEnhanced # Filter inputs - input: vtkUnstructuredGrid + inputMesh: vtkUnstructuredGrid + speHandler: bool # optional # Instantiate the filter - meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced() + meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced( inputMesh, speHandler ) - # Set input data object - meshQualityEnhancedFilter.SetInputDataObject(input) + # Use your own handler (if speHandler is True) + yourHandler: logging.Handler + meshQualityEnhancedFilter.setLoggerHandler( yourHandler ) # Set metrics to use meshQualityEnhancedFilter.SetTriangleMetrics(triangleQualityMetrics) @@ -78,10 +64,10 @@ meshQualityEnhancedFilter.SetOtherMeshQualityMetrics(otherQualityMetrics) # Do calculations - meshQualityEnhancedFilter.Update() + meshQualityEnhancedFilter.applyFilter() # Get output mesh quality report - outputMesh: vtkUnstructuredGrid = meshQualityEnhancedFilter.GetOutputDataObject(0) + outputMesh: vtkUnstructuredGrid = meshQualityEnhancedFilter.getOutput() outputStats: QualityMetricSummary = meshQualityEnhancedFilter.GetQualityMetricSummary() """ @@ -101,12 +87,25 @@ def getQualityMetricArrayName( metric: int ) -> str: return QUALITY_ARRAY_NAME + "_" + "".join( getQualityMeasureNameFromIndex( metric ).split( " " ) ) -class MeshQualityEnhanced( VTKPythonAlgorithmBase ): +loggerTitle: str = "Mesh Quality Enhanced" - def __init__( self: Self ) -> None: - """Enhanced vtkMeshQuality filter.""" - super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) - self._outputMesh: vtkUnstructuredGrid + +class MeshQualityEnhanced(): + + def __init__( + self: Self, + inputMesh: vtkUnstructuredGrid, + speHandler: bool = False, + ) -> None: + """Enhanced vtkMeshQuality filter. + + Args: + inputMesh (vtkUnstructuredGrid): Input mesh + speHandler (bool, optional): True to use a specific handler, False to use the internal handler. + Defaults to False. + """ + self.inputMesh: vtkUnstructuredGrid = inputMesh + self._outputMesh: vtkUnstructuredGrid = vtkUnstructuredGrid() self._cellCounts: CellTypeCounts self._qualityMetricSummary: QualityMetricSummary = QualityMetricSummary() @@ -126,43 +125,32 @@ def __init__( self: Self ) -> None: self._allCellTypesExtended: tuple[ int, ...] = getAllCellTypesExtended() self._allCellTypes: tuple[ int, ...] = getAllCellTypes() - def FillInputPortInformation( self: Self, port: int, info: vtkInformation ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestInformation. + # Logger. + self.speHandler: bool = speHandler + self.handler: None | logging.Handler = None + self.logger: Logger + if not speHandler: + self.logger = getLogger( loggerTitle, True ) + else: + self.logger = logging.getLogger( loggerTitle ) + self.logger.setLevel( logging.INFO ) + self.logger.propagate = False - Args: - port (int): Input port - info (vtkInformationVector): Info + def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: + """Set a specific handler for the filter logger. - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - if port == 0: - info.Set( self.INPUT_REQUIRED_DATA_TYPE(), "vtkUnstructuredGrid" ) - return 1 - - def RequestDataObject( - self: Self, - request: vtkInformation, - inInfoVec: list[ vtkInformationVector ], - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + In this filter 4 log levels are use, .info, .error, .warning and .critical, + be sure to have at least the same 4 levels. Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects - - Returns: - int: 1 if calculation successfully ended, 0 otherwise. + handler (logging.Handler): The handler to add. """ - 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] + self.handler = handler + if len( self.logger.handlers ) == 0: + 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 GetQualityMetricSummary( self: Self ) -> QualityMetricSummary: """Get QualityMetricSummary object. @@ -294,48 +282,42 @@ def getComputedMetricsFromCellType( self: Self, cellType: int ) -> Optional[ set metrics = metrics.intersection( computedMetrics ) return metrics if commonComputedMetricsExists else None - def RequestData( - self: Self, - request: vtkInformation, # noqa: F841 - inInfoVec: list[ vtkInformationVector ], # noqa: F841 - outInfoVec: vtkInformationVector, - ) -> int: - """Inherited from VTKPythonAlgorithmBase::RequestData. + def applyFilter( self: Self ) -> None: + """Apply MeshQualityEnhanced filter.""" + self.logger.info( f"Apply filter { self.logger.name }." ) + try: + self._outputMesh.ShallowCopy( self.inputMesh ) + # Compute cell type counts + self._computeCellTypeCounts() - Args: - request (vtkInformation): Request - inInfoVec (list[vtkInformationVector]): Input objects - outInfoVec (vtkInformationVector): Output objects + # Compute metrics and associated attributes + self._evaluateMeshQualityAll() - Returns: - int: 1 if calculation successfully ended, 0 otherwise. - """ - inData: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) - self._outputMesh = self.GetOutputData( outInfoVec, 0 ) - assert inData is not None, "Input mesh is undefined." - assert self._outputMesh is not None, "Output pipeline is undefined." - self._outputMesh.ShallowCopy( inData ) + # Compute stats summary + self._updateStatsSummary() - # Compute cell type counts - self._computeCellTypeCounts() + # Create field data + self._createFieldDataStatsSummary() - # Compute metrics and associated attributes - self._evaluateMeshQualityAll() + self._outputMesh.Modified() - # Compute stats summary - self._updateStatsSummary() + self.logger.info( f"The filter { self.logger.name } succeeded." ) + except Exception as e: + self.logger.error( f"The filter { self.logger.name } failed.\n{ e }" ) - # Create field data - self._createFieldDataStatsSummary() + return - self._outputMesh.Modified() - return 1 + def getOutput( self: Self ) -> vtkUnstructuredGrid: + """Get the mesh computed with the stats.""" + return self._outputMesh def _computeCellTypeCounts( self: Self ) -> None: """Compute cell type counts.""" - cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced() - cellTypeCounterEnhancedFilter.SetInputDataObject( self._outputMesh ) - cellTypeCounterEnhancedFilter.Update() + cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( + self._outputMesh, self.speHandler ) + if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: + cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) + cellTypeCounterEnhancedFilter.applyFilter() counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() assert counts is not None, "CellTypeCounts is undefined" self._qualityMetricSummary.setCellTypeCounts( counts ) @@ -426,7 +408,7 @@ def _applyMeshQualityFilter( self: Self, metric: int, cellTypes: list[ int ] ) - elif cellType == VTK_HEXAHEDRON: meshQualityFilter.SetHexQualityMeasure( metric ) else: - print( "Cell type is not supported." ) + raise TypeError( "Cell type is not supported." ) meshQualityFilter.Update() return meshQualityFilter.GetOutputDataObject( 0 ) @@ -826,4 +808,4 @@ def _getNormalVector( self: Self, points: vtkPoints, face: vtkCell ) -> npt.NDAr ptsCoords: npt.NDArray[ np.float64 ] = np.zeros( ( 3, 3 ), dtype=float ) for i in range( 3 ): points.GetPoint( facePtsIds.GetId( i ), ptsCoords[ i ] ) - return geom.computeNormalFromPoints( ptsCoords[ 0 ], ptsCoords[ 1 ], ptsCoords[ 2 ] ) + return geom.computeNormalFromPoints( ptsCoords[ 0 ], ptsCoords[ 1 ], ptsCoords[ 2 ] ) \ No newline at end of file diff --git a/geos-processing/tests/test_MeshQualityEnhanced.py b/geos-processing/tests/test_MeshQualityEnhanced.py index a86e2c97..8857175b 100644 --- a/geos-processing/tests/test_MeshQualityEnhanced.py +++ b/geos-processing/tests/test_MeshQualityEnhanced.py @@ -8,13 +8,10 @@ import numpy.typing as npt import pandas as pd import pytest -from typing import ( - Iterator, - Optional, -) +from typing import Iterator, Optional + from geos.mesh.utils.genericHelpers import createMultiCellMesh -from geos.mesh.stats.meshQualityMetricHelpers import ( - getAllCellTypesExtended, ) +from geos.mesh.stats.meshQualityMetricHelpers import getAllCellTypesExtended from geos.processing.pre_processing.MeshQualityEnhanced import MeshQualityEnhanced from geos.mesh.model.QualityMetricSummary import QualityMetricSummary @@ -126,14 +123,13 @@ def __generate_test_data() -> Iterator[ TestCase ]: @pytest.mark.parametrize( "test_case", __generate_test_data(), ids=ids ) def test_MeshQualityEnhanced( test_case: TestCase ) -> None: - """Test of CellTypeCounterEnhanced filter. + """Test of MeshQualityEnhanced filter. Args: test_case (TestCase): Test case """ mesh = test_case.mesh - meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced() - meshQualityEnhancedFilter.SetInputDataObject( mesh ) + meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced( mesh ) if test_case.cellType == VTK_TRIANGLE: meshQualityEnhancedFilter.SetTriangleMetrics( test_case.qualityMetrics ) elif test_case.cellType == VTK_QUAD: @@ -146,7 +142,7 @@ def test_MeshQualityEnhanced( test_case: TestCase ) -> None: meshQualityEnhancedFilter.SetWedgeMetrics( test_case.qualityMetrics ) elif test_case.cellType == VTK_HEXAHEDRON: meshQualityEnhancedFilter.SetHexaMetrics( test_case.qualityMetrics ) - meshQualityEnhancedFilter.Update() + meshQualityEnhancedFilter.applyFilter() # test method getComputedMetricsFromCellType for i, cellType in enumerate( getAllCellTypesExtended() ): @@ -156,7 +152,7 @@ def test_MeshQualityEnhanced( test_case: TestCase ) -> None: assert metrics is not None, f"Metrics from {vtkCellTypes.GetClassNameFromTypeId(cellType)} cells is undefined." # test attributes - outputMesh: vtkUnstructuredGrid = meshQualityEnhancedFilter.GetOutputDataObject( 0 ) + outputMesh: vtkUnstructuredGrid = meshQualityEnhancedFilter.getOutput() cellData: vtkCellData = outputMesh.GetCellData() assert cellData is not None, "Cell data is undefined." @@ -192,4 +188,4 @@ def test_MeshQualityEnhanced( test_case: TestCase ) -> None: test_case.metricsSummary[ j ] ), f"Stats at metric index {j} are wrong." fig: Figure = stats.plotSummaryFigure() - assert len( fig.get_axes() ) == 6, "Number of Axes is expected to be 6." + assert len( fig.get_axes() ) == 6, "Number of Axes is expected to be 6." \ No newline at end of file diff --git a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py index cc3a978d..e6f7160d 100644 --- a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py @@ -2,17 +2,17 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Martin Lemay, Paloma Martinez # ruff: noqa: E402 # disable Module level import not at top of file +import logging import sys from pathlib import Path from typing_extensions import Self, Optional -from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] - VTKPythonAlgorithmBase, smdomain, smproperty, -) -from vtkmodules.vtkCommonCore import ( - vtkDataArraySelection, ) -from vtkmodules.vtkCommonDataModel import ( - vtkUnstructuredGrid, ) +from paraview.util.vtkAlgorithm import VTKPythonAlgorithmBase, smdomain, smproperty # type: ignore[import-not-found] +from paraview.detail.loghandler import VTKHandler # type: ignore[import-not-found] +# source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py + +from vtkmodules.vtkCommonCore import vtkDataArraySelection +from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid # update sys.path to load all GEOS Python Package dependencies geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent @@ -23,24 +23,14 @@ from geos.mesh.model.QualityMetricSummary import QualityMetricSummary from geos.processing.pre_processing.MeshQualityEnhanced import MeshQualityEnhanced - -from geos.mesh.stats.meshQualityMetricHelpers import ( - getQualityMetricsOther, - getQualityMeasureNameFromIndex, - getQualityMeasureIndexFromName, - getQuadQualityMeasure, - getTriangleQualityMeasure, - getCommonPolygonQualityMeasure, - getTetQualityMeasure, - getPyramidQualityMeasure, - getWedgeQualityMeasure, - getHexQualityMeasure, - getCommonPolyhedraQualityMeasure, -) -from geos.pv.utils.checkboxFunction import ( # type: ignore[attr-defined] - createModifiedCallback, ) +from geos.mesh.stats.meshQualityMetricHelpers import ( getQualityMetricsOther, getQualityMeasureNameFromIndex, + getQualityMeasureIndexFromName, getQuadQualityMeasure, + getTriangleQualityMeasure, getCommonPolygonQualityMeasure, + getTetQualityMeasure, getPyramidQualityMeasure, + getWedgeQualityMeasure, getHexQualityMeasure, + getCommonPolyhedraQualityMeasure ) +from geos.pv.utils.checkboxFunction import createModifiedCallback # type: ignore[attr-defined] from geos.pv.utils.paraviewTreatments import getArrayChoices - from geos.pv.utils.details import ( SISOFilter, FilterCategory ) __doc__ = """ @@ -228,7 +218,6 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur self._getQualityMetricsToUse( self._triangleQualityMetric ) ) quadMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonCellSurfaceQualityMetric ).union( self._getQualityMetricsToUse( self._quadsQualityMetric ) ) - tetraMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonCellVolumeQualityMetric ).union( self._getQualityMetricsToUse( self._tetQualityMetric ) ) pyrMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonCellVolumeQualityMetric ).union( @@ -239,9 +228,9 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur self._getQualityMetricsToUse( self._HexQualityMetric ) ) otherMetrics: set[ int ] = self._getQualityMetricsToUse( self._commonMeshQualityMetric ) - meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced() - - meshQualityEnhancedFilter.SetInputDataObject( inputMesh ) + meshQualityEnhancedFilter: MeshQualityEnhanced = MeshQualityEnhanced( inputMesh, True ) + if len( meshQualityEnhancedFilter.logger.handlers ) == 0: + meshQualityEnhancedFilter.setLoggerHandler( VTKHandler() ) meshQualityEnhancedFilter.SetCellQualityMetrics( triangleMetrics=triangleMetrics, quadMetrics=quadMetrics, tetraMetrics=tetraMetrics, @@ -249,32 +238,35 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur wedgeMetrics=wedgeMetrics, hexaMetrics=hexaMetrics ) meshQualityEnhancedFilter.SetOtherMeshQualityMetrics( otherMetrics ) - meshQualityEnhancedFilter.Update() + meshQualityEnhancedFilter.applyFilter() - outputMesh.ShallowCopy( meshQualityEnhancedFilter.GetOutputDataObject( 0 ) ) + outputMesh.ShallowCopy( meshQualityEnhancedFilter.getOutput() ) # save to file if asked if self._saveToFile: stats: QualityMetricSummary = meshQualityEnhancedFilter.GetQualityMetricSummary() - self.saveFile( stats ) + logger: logging.Logger = meshQualityEnhancedFilter.logger + self.saveFile( stats, logger ) self._blockIndex += 1 return - def saveFile( self: Self, stats: QualityMetricSummary ) -> None: + def saveFile( + self: Self, + stats: QualityMetricSummary, + logger: logging.Logger, + ) -> None: """Export mesh quality metric summary file.""" try: - if self._filename is None: - print( "Mesh quality summary report file path is undefined." ) - return + assert self._filename is not None, "Mesh quality summary report file path is undefined." + # add index for multiblock meshes index: int = self._filename.rfind( '.' ) filename: str = self._filename[ :index ] + f"_{self._blockIndex}" + self._filename[ index: ] fig = stats.plotSummaryFigure() fig.savefig( filename, dpi=150 ) - print( f"File {filename} was successfully written." ) + logger.info( f"File {filename} was successfully written." ) except Exception as e: - print( "Error while exporting the file due to:" ) - print( str( e ) ) + logger.error( f"Error while exporting the file due to:\n{ e }" ) def __initVolumeQualityMetricSelection( self: Self ) -> None: """Initialize the volumic metrics selection.""" @@ -339,4 +331,4 @@ def __initSurfaceQualityMetricSelection( self: Self ) -> None: otherMetrics: set[ int ] = getQualityMetricsOther() for measure in otherMetrics: # TODO: fix issue with incident vertex count metrics - self._commonMeshQualityMetric.AddArray( getQualityMeasureNameFromIndex( measure ), False ) + self._commonMeshQualityMetric.AddArray( getQualityMeasureNameFromIndex( measure ), False ) \ No newline at end of file From 5b7a2af95e4acf4cae82978d8f3385ee7545bd35 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Thu, 20 Nov 2025 11:10:45 +0100 Subject: [PATCH 4/6] clean test --- geos-processing/tests/test_SplitMesh.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/geos-processing/tests/test_SplitMesh.py b/geos-processing/tests/test_SplitMesh.py index 69a0ae5d..7fc8e607 100644 --- a/geos-processing/tests/test_SplitMesh.py +++ b/geos-processing/tests/test_SplitMesh.py @@ -151,12 +151,9 @@ def test_single_cell_split( test_case: TestCase ) -> None: assert pointsOut.GetNumberOfPoints( ) == test_case.pointsExp.shape[ 0 ], f"Number of points is expected to be {test_case.pointsExp.shape[0]}." pointCoords: npt.NDArray[ np.float64 ] = vtk_to_numpy( pointsOut.GetData() ) - print( "Points coords: ", cellTypeName, pointCoords.tolist() ) assert np.array_equal( pointCoords.ravel(), test_case.pointsExp.ravel() ), "Points coordinates mesh are wrong." cellsOut: vtkCellArray = output.GetCells() - typesArray0: npt.NDArray[ np.int64 ] = vtk_to_numpy( output.GetDistinctCellTypesArray() ) - print( "typesArray0", cellTypeName, typesArray0 ) assert cellsOut is not None, "Cells from output mesh are undefined." assert cellsOut.GetNumberOfCells() == len( @@ -167,7 +164,6 @@ def test_single_cell_split( test_case: TestCase ) -> None: assert types is not None, "Cell types must be defined" typesArray: npt.NDArray[ np.int64 ] = vtk_to_numpy( types.GetCellTypesArray() ) - print( "typesArray", cellTypeName, typesArray ) # Pyramid splitting produces both pyramids (first 6 cells) and tetrahedra (last 4 cells) if test_case.cellType == VTK_PYRAMID: assert typesArray.size == 2, "Pyramid splitting should produce 2 distinct cell types" @@ -183,8 +179,6 @@ def test_single_cell_split( test_case: TestCase ) -> None: cellsOutObs: list[ int ] = [ ptIds.GetId( j ) for j in range( ptIds.GetNumberOfIds() ) ] nbPtsExp: int = len( test_case.cellsExp[ i ] ) actualCellType: int = output.GetCellType( i ) - print( "cell type", cellTypeName, i, vtkCellTypes.GetClassNameFromTypeId( actualCellType ) ) - print( "cellsOutObs: ", cellTypeName, i, cellsOutObs ) assert ptIds is not None, "Point ids must be defined" assert ptIds.GetNumberOfIds() == nbPtsExp, f"Cells must be defined by {nbPtsExp} points." assert cellsOutObs == test_case.cellsExp[ i ], "Cell point ids are wrong." @@ -234,7 +228,6 @@ def test_multi_cells_mesh_split() -> None: elif cellType == VTK_PYRAMID: nbPyr += 1 - print( f"Input mesh contains: {nbHex} hexahedra, {nbTet} tetrahedra, {nbPyr} pyramids" ) assert nbHex == 3, "Expected 3 hexahedra in input mesh" assert nbTet == 36, "Expected 36 tetrahedra in input mesh" assert nbPyr == 18, "Expected 18 pyramids in input mesh" @@ -248,8 +241,6 @@ def test_multi_cells_mesh_split() -> None: # Calculate expected number of cells using the formula # 1 hex -> 8 hexes, 1 tet -> 8 tets, 1 pyramid -> 6 pyramids + 4 tets = 10 cells expectedNbCells = nbHex * 8 + nbTet * 8 + nbPyr * 10 - print( f"Expected number of cells: {expectedNbCells} (3*8 + 36*8 + 18*10)" ) - print( f"Actual number of cells: {output.GetNumberOfCells()}" ) assert output.GetNumberOfCells() == expectedNbCells, \ f"Expected {expectedNbCells} cells, got {output.GetNumberOfCells()}" @@ -266,7 +257,6 @@ def test_multi_cells_mesh_split() -> None: elif cellType == VTK_PYRAMID: nbPyrOut += 1 - print( f"Output mesh contains: {nbHexOut} hexahedra, {nbTetOut} tetrahedra, {nbPyrOut} pyramids" ) # Expected output: 3*8=24 hexes, 36*8 + 18*4=360 tets, 18*6=108 pyramids assert nbHexOut == 3 * 8, f"Expected {3*8} hexahedra in output, got {nbHexOut}" assert nbTetOut == 36 * 8 + 18 * 4, f"Expected {36*8 + 18*4} tetrahedra in output, got {nbTetOut}" @@ -301,7 +291,6 @@ def test_multi_polygon_mesh_split() -> None: elif cellType == VTK_TRIANGLE: nbTriangle += 1 - print( f"Input mesh contains: {nbQuad} quads, {nbTriangle} triangles" ) assert nbQuad == 2, "Expected 2 quads in input mesh" assert nbTriangle == 4, "Expected 4 triangles in input mesh" @@ -314,8 +303,6 @@ def test_multi_polygon_mesh_split() -> None: # Calculate expected number of cells using the formula # 1 quad -> 4 quads, 1 triangle -> 4 triangles expectedNbCells = nbQuad * 4 + nbTriangle * 4 - print( f"Expected number of cells: {expectedNbCells} (2*4 + 4*4)" ) - print( f"Actual number of cells: {output.GetNumberOfCells()}" ) assert output.GetNumberOfCells() == expectedNbCells, \ f"Expected {expectedNbCells} cells, got {output.GetNumberOfCells()}" @@ -329,7 +316,6 @@ def test_multi_polygon_mesh_split() -> None: elif cellType == VTK_TRIANGLE: nbTriangleOut += 1 - print( f"Output mesh contains: {nbQuadOut} quads, {nbTriangleOut} triangles" ) # Expected output: 2*4=8 quads, 4*4=16 triangles assert nbQuadOut == 2 * 4, f"Expected {2*4} quads in output, got {nbQuadOut}" assert nbTriangleOut == 4 * 4, f"Expected {4*4} triangles in output, got {nbTriangleOut}" From e14a0655d03bfeaaf0b044ad6590e7492eb264c2 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Thu, 20 Nov 2025 11:25:43 +0100 Subject: [PATCH 5/6] fix ruff --- .../geos/processing/generic_processing_tools/SplitMesh.py | 5 +++-- .../processing/pre_processing/CellTypeCounterEnhanced.py | 2 +- .../geos/processing/pre_processing/MeshQualityEnhanced.py | 2 +- geos-processing/tests/test_CellTypeCounterEnhanced.py | 2 +- geos-processing/tests/test_MeshQualityEnhanced.py | 2 +- geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py | 2 +- geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py | 2 +- geos-pv/src/geos/pv/plugins/PVSplitMesh.py | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py index 1fb85022..99f7dbec 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py @@ -140,7 +140,8 @@ def applyFilter( self: Self ) -> None: if splitMethod is not None: splitMethod( cell, c ) else: - raise TypeError( f"Cell type { vtkCellTypes.GetClassNameFromTypeId( cellType ) } is not supported." ) + raise TypeError( + f"Cell type { vtkCellTypes.GetClassNameFromTypeId( cellType ) } is not supported." ) # add points and cells self.outputMesh.SetPoints( self.points ) self.outputMesh.SetCells( self.cellTypes, self.cells ) @@ -454,4 +455,4 @@ def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> bool cellDataSplitted.AddArray( newArray ) cellDataSplitted.Modified() splittedMesh.Modified() - return True \ No newline at end of file + return True diff --git a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py index e7fc39fd..568146dd 100644 --- a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py @@ -127,4 +127,4 @@ def GetCellTypeCountsObject( self: Self ) -> CellTypeCounts: def getOutput( self: Self ) -> vtkTable: """Get the computed vtkTable.""" - return self.outTable \ No newline at end of file + return self.outTable diff --git a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py index 3f6ac283..a5e94b6d 100644 --- a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py @@ -808,4 +808,4 @@ def _getNormalVector( self: Self, points: vtkPoints, face: vtkCell ) -> npt.NDAr ptsCoords: npt.NDArray[ np.float64 ] = np.zeros( ( 3, 3 ), dtype=float ) for i in range( 3 ): points.GetPoint( facePtsIds.GetId( i ), ptsCoords[ i ] ) - return geom.computeNormalFromPoints( ptsCoords[ 0 ], ptsCoords[ 1 ], ptsCoords[ 2 ] ) \ No newline at end of file + return geom.computeNormalFromPoints( ptsCoords[ 0 ], ptsCoords[ 1 ], ptsCoords[ 2 ] ) diff --git a/geos-processing/tests/test_CellTypeCounterEnhanced.py b/geos-processing/tests/test_CellTypeCounterEnhanced.py index 6350bbf1..7883592c 100644 --- a/geos-processing/tests/test_CellTypeCounterEnhanced.py +++ b/geos-processing/tests/test_CellTypeCounterEnhanced.py @@ -142,4 +142,4 @@ def test_CellTypeCounterEnhanced_multi( test_case: TestCase ) -> None: nbPolyhedra: int = np.sum( counts[ 2: ], dtype=int ) assert int( countsObs.getTypeCount( VTK_POLYGON ) ) == nbPolygon, f"The number of faces should be {nbPolygon}." assert int( - countsObs.getTypeCount( VTK_POLYHEDRON ) ) == nbPolyhedra, f"The number of polyhedra should be {nbPolyhedra}." \ No newline at end of file + countsObs.getTypeCount( VTK_POLYHEDRON ) ) == nbPolyhedra, f"The number of polyhedra should be {nbPolyhedra}." diff --git a/geos-processing/tests/test_MeshQualityEnhanced.py b/geos-processing/tests/test_MeshQualityEnhanced.py index 8857175b..730ec173 100644 --- a/geos-processing/tests/test_MeshQualityEnhanced.py +++ b/geos-processing/tests/test_MeshQualityEnhanced.py @@ -188,4 +188,4 @@ def test_MeshQualityEnhanced( test_case: TestCase ) -> None: test_case.metricsSummary[ j ] ), f"Stats at metric index {j} are wrong." fig: Figure = stats.plotSummaryFigure() - assert len( fig.get_axes() ) == 6, "Number of Axes is expected to be 6." \ No newline at end of file + assert len( fig.get_axes() ) == 6, "Number of Axes is expected to be 6." diff --git a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py index 4a0b8e99..d87bc656 100644 --- a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py @@ -150,4 +150,4 @@ def RequestData( cellTypeCounterEnhancedFilter.logger.info( f"File {self._filename} was successfully written." ) except Exception as e: cellTypeCounterEnhancedFilter.logger.info( f"Error while exporting the file due to:\n{ e }" ) - return 1 \ No newline at end of file + return 1 diff --git a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py index e6f7160d..a7ef8c8b 100644 --- a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py @@ -331,4 +331,4 @@ def __initSurfaceQualityMetricSelection( self: Self ) -> None: otherMetrics: set[ int ] = getQualityMetricsOther() for measure in otherMetrics: # TODO: fix issue with incident vertex count metrics - self._commonMeshQualityMetric.AddArray( getQualityMeasureNameFromIndex( measure ), False ) \ No newline at end of file + self._commonMeshQualityMetric.AddArray( getQualityMeasureNameFromIndex( measure ), False ) diff --git a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py index 04952083..e5461b2c 100644 --- a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py +++ b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py @@ -56,4 +56,4 @@ def ApplyFilter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) - splitMeshFilter.applyFilter() outputMesh.ShallowCopy( splitMeshFilter.getOutput() ) - return \ No newline at end of file + return From 6ac33eced8ac457faf2a33f963e4763f90466cd1 Mon Sep 17 00:00:00 2001 From: Romain Baville Date: Fri, 21 Nov 2025 15:18:38 +0100 Subject: [PATCH 6/6] improve try - except strategy --- .../generic_processing_tools/SplitMesh.py | 70 ++++++++++------ .../pre_processing/CellTypeCounterEnhanced.py | 17 +++- .../pre_processing/MeshQualityEnhanced.py | 79 +++++++++++++------ .../pv/plugins/PVCellTypeCounterEnhanced.py | 30 +++---- .../geos/pv/plugins/PVMeshQualityEnhanced.py | 25 +++--- geos-pv/src/geos/pv/plugins/PVSplitMesh.py | 4 +- 6 files changed, 142 insertions(+), 83 deletions(-) diff --git a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py index 99f7dbec..8cbe9d02 100644 --- a/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py +++ b/geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py @@ -93,24 +93,33 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: 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 ) -> None: - """Apply the filter SplitMesh.""" + def applyFilter( self: Self ) -> bool: + """Apply the filter SplitMesh. + + Returns: + bool: True if the filter succeeded, False otherwise. + """ self.logger.info( f"Applying filter { self.logger.name }." ) try: # Count the number of cells before splitting. Then we will be able to know how many new cells and points # to allocate because each cell type is splitted in a known number of new cells and points. nbCells: int = self.inputMesh.GetNumberOfCells() counts: CellTypeCounts = self._getCellCounts() + if counts.getTypeCount( VTK_WEDGE ) != 0: + raise TypeError( "Input mesh contains wedges that are not currently supported." ) + + nbPolygon: int = counts.getTypeCount( VTK_POLYGON ) + nbPolyhedra: int = counts.getTypeCount( VTK_POLYHEDRON ) + # Current implementation only supports meshes composed of either polygons or polyhedra + if nbPolyhedra * nbPolygon != 0: + raise TypeError( + "Input mesh is composed of both polygons and polyhedra, but it must contains only one of the two." ) + nbTet: int = counts.getTypeCount( VTK_TETRA ) # will divide into 8 tets nbPyr: int = counts.getTypeCount( VTK_PYRAMID ) # will divide into 6 pyramids and 4 tets so 10 new cells nbHex: int = counts.getTypeCount( VTK_HEXAHEDRON ) # will divide into 8 hexes nbTriangles: int = counts.getTypeCount( VTK_TRIANGLE ) # will divide into 4 triangles nbQuad: int = counts.getTypeCount( VTK_QUAD ) # will divide into 4 quads - nbPolygon: int = counts.getTypeCount( VTK_POLYGON ) - nbPolyhedra: int = counts.getTypeCount( VTK_POLYHEDRON ) - assert counts.getTypeCount( VTK_WEDGE ) == 0, "Input mesh contains wedges that are not currently supported." - # Current implementation only supports meshes composed of either polygons or polyhedra - assert nbPolyhedra * nbPolygon == 0, "Input mesh is composed of both polygons and polyhedra, but it must contains only one of the two." nbNewPoints: int = 0 nbNewPoints = nbHex * 19 + nbTet * 6 + nbPyr * 9 if nbPolyhedra > 0 else nbTriangles * 3 + nbQuad * 5 nbNewCells: int = nbHex * 8 + nbTet * 8 + nbPyr * 10 + nbTriangles * 4 + nbQuad * 4 @@ -125,6 +134,7 @@ def applyFilter( self: Self ) -> None: self.originalId.SetName( "OriginalID" ) self.originalId.Allocate( nbNewCells ) self.cellTypes = [] + # Define cell type to splitting method mapping splitMethods = { VTK_HEXAHEDRON: self._splitHexahedron, @@ -142,20 +152,29 @@ def applyFilter( self: Self ) -> None: else: raise TypeError( f"Cell type { vtkCellTypes.GetClassNameFromTypeId( cellType ) } is not supported." ) - # add points and cells + + # Add points and cells self.outputMesh.SetPoints( self.points ) self.outputMesh.SetCells( self.cellTypes, self.cells ) - # add attribute saving original cell ids + + # Add attribute saving original cell ids cellArrays: vtkCellData = self.outputMesh.GetCellData() - assert cellArrays is not None, "Cell data is undefined." + if cellArrays is None: + raise AttributeError( "Cell data is undefined." ) cellArrays.AddArray( self.originalId ) - # transfer all cell arrays + + # Transfer all cell arrays self._transferCellArrays( self.outputMesh ) self.logger.info( f"The filter { self.logger.name } succeeded." ) - except Exception as e: + except ( TypeError, AttributeError ) as e: self.logger.error( f"The filter {self.logger.name } failed.\n{ e }" ) + return False + except Exception as e: + mess: str = f"The filter { self.logger.name } failed.\n{ e }" + self.logger.critical( mess, exc_info=True ) + return False - return + return True def getOutput( self: Self ) -> vtkUnstructuredGrid: """Get the splitted mesh computed.""" @@ -171,7 +190,8 @@ def _getCellCounts( self: Self ) -> CellTypeCounts: self.inputMesh, self.speHandler ) if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) - cellTypeCounterEnhancedFilter.applyFilter() + if not cellTypeCounterEnhancedFilter.applyFilter(): + raise return cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() def _addMidPoint( self: Self, ptA: int, ptB: int ) -> int: @@ -422,23 +442,25 @@ def _splitQuad( self: Self, cell: vtkCell, index: int ) -> None: self.originalId.InsertNextValue( index ) self.cellTypes.extend( [ VTK_QUAD ] * 4 ) - def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> bool: + def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> None: """Transfer arrays from input mesh to splitted mesh. Args: - splittedMesh (vtkUnstructuredGrid): splitted mesh - - Returns: - bool: True if arrays were successfully transferred. + splittedMesh (vtkUnstructuredGrid): Splitted mesh. """ - cellDataSplitted: vtkCellData = splittedMesh.GetCellData() - assert cellDataSplitted is not None, "Cell data of splitted mesh should be defined." cellData: vtkCellData = self.inputMesh.GetCellData() - assert cellData is not None, "Cell data of input mesh should be defined." + if cellData is None: + raise AttributeError( "Cell data of input mesh should be defined." ) + + cellDataSplitted: vtkCellData = splittedMesh.GetCellData() + if cellDataSplitted is None: + raise AttributeError( "Cell data of splitted mesh should be defined." ) + # for each array of input mesh for i in range( cellData.GetNumberOfArrays() ): array: vtkDataArray = cellData.GetArray( i ) - assert array is not None, "Array should be defined." + if array is None: + raise AttributeError( "Array should be defined." ) npArray: npt.NDArray[ np.float64 ] = vtk_to_numpy( array ) # get number of components dims: tuple[ int, ...] = npArray.shape @@ -455,4 +477,4 @@ def _transferCellArrays( self: Self, splittedMesh: vtkUnstructuredGrid ) -> bool cellDataSplitted.AddArray( newArray ) cellDataSplitted.Modified() splittedMesh.Modified() - return True + return diff --git a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py index 568146dd..3aa8953a 100644 --- a/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/CellTypeCounterEnhanced.py @@ -86,8 +86,12 @@ def setLoggerHandler( self: Self, handler: logging.Handler ) -> None: 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 ) -> None: - """Apply CellTypeCounterEnhanced filter.""" + def applyFilter( self: Self ) -> bool: + """Apply CellTypeCounterEnhanced filter. + + Returns: + bool: True if the filter succeeded, False otherwise. + """ self.logger.info( f"Apply filter { self.logger.name }." ) try: # compute cell type counts @@ -112,10 +116,15 @@ def applyFilter( self: Self ) -> None: array.SetValue( 0, self._counts.getTypeCount( cellType ) ) self.outTable.AddColumn( array ) self.logger.info( f"The filter { self.logger.name } succeeded." ) - except AssertionError as e: + except TypeError as e: self.logger.error( f"The filter { self.logger.name } failed.\n{ e }" ) + return False + except Exception as e: + mess: str = f"The filter { self.logger.name } failed.\n{ e }" + self.logger.critical( mess, exc_info=True ) + return False - return + return True def GetCellTypeCountsObject( self: Self ) -> CellTypeCounts: """Get CellTypeCounts object. diff --git a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py index a5e94b6d..6c454c69 100644 --- a/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py +++ b/geos-processing/src/geos/processing/pre_processing/MeshQualityEnhanced.py @@ -282,8 +282,12 @@ def getComputedMetricsFromCellType( self: Self, cellType: int ) -> Optional[ set metrics = metrics.intersection( computedMetrics ) return metrics if commonComputedMetricsExists else None - def applyFilter( self: Self ) -> None: - """Apply MeshQualityEnhanced filter.""" + def applyFilter( self: Self ) -> bool: + """Apply MeshQualityEnhanced filter. + + Returns: + bool: True if the filter succeeded, False otherwise. + """ self.logger.info( f"Apply filter { self.logger.name }." ) try: self._outputMesh.ShallowCopy( self.inputMesh ) @@ -302,10 +306,15 @@ def applyFilter( self: Self ) -> None: self._outputMesh.Modified() self.logger.info( f"The filter { self.logger.name } succeeded." ) - except Exception as e: + except ( ValueError, IndexError, TypeError, AttributeError ) as e: self.logger.error( f"The filter { self.logger.name } failed.\n{ e }" ) + return False + except Exception as e: + mess: str = f"The filter { self.logger.name } failed.\n{ e }" + self.logger.critical( mess, exc_info=True ) + return False - return + return True def getOutput( self: Self ) -> vtkUnstructuredGrid: """Get the mesh computed with the stats.""" @@ -317,10 +326,15 @@ def _computeCellTypeCounts( self: Self ) -> None: self._outputMesh, self.speHandler ) if self.speHandler and len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: cellTypeCounterEnhancedFilter.setLoggerHandler( self.handler ) - cellTypeCounterEnhancedFilter.applyFilter() + if not cellTypeCounterEnhancedFilter.applyFilter(): + raise + counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() - assert counts is not None, "CellTypeCounts is undefined" + if counts is None: + raise AttributeError( "CellTypeCounts is undefined" ) + self._qualityMetricSummary.setCellTypeCounts( counts ) + return def _evaluateMeshQualityAll( self: Self ) -> None: """Compute all mesh quality metrics.""" @@ -335,7 +349,9 @@ def _evaluateMeshQualityAll( self: Self ) -> None: self._countVertexIncidentEdges() else: # TODO: add other metrics - print( "" ) + pass + + return def _evaluateCellQuality( self: Self, metricIndex: int ) -> None: """Compute mesh input quality metric. By default, the metric is computed for all cell types. @@ -375,7 +391,8 @@ def _evaluateCellQuality( self: Self, metricIndex: int ) -> None: else: output = self._applyMeshQualityFilter( metricIndex, cellToApplyTo ) - assert output is not None, "Output mesh from mesh quality calculation is undefined." + if output is None: + raise AttributeError( "Output mesh from mesh quality calculation is undefined." ) # Transfer output cell array to input mesh # TODO: to test if Shallow copy of vtkMeshQualityFilter result # and rename "Quality" array is more efficient than what is done here @@ -422,7 +439,8 @@ def _computeAdditionalMetrics( self: Self, metricIndex: int ) -> None: metricIndex (int): Metric index """ metric = getQualityMetricFromIndex( metricIndex ) - assert metric is not None, f"Additional cell quality metric index {metricIndex} is undefined." + if metric is None: + raise AttributeError( f"Additional cell quality metric index {metricIndex} is undefined." ) # Output array name: str = getQualityMetricArrayName( metric.getMetricIndex() ) newArray: vtkDoubleArray = vtkDoubleArray() @@ -436,7 +454,8 @@ def _computeAdditionalMetrics( self: Self, metricIndex: int ) -> None: newArray.SetValue( i, val ) # Add array cellArrays: vtkCellData = self._outputMesh.GetCellData() - assert cellArrays is not None, "Cell data from output mesh is undefined." + if cellArrays is None: + raise AttributeError( "Cell data from output mesh is undefined." ) cellArrays.AddArray( newArray ) cellArrays.Modified() self._outputMesh.Modified() @@ -448,7 +467,7 @@ def _transferCellAttribute( attributeFromName: str, attributeToName: str, qualityMetric: int, - ) -> bool: + ) -> None: """Transfer quality attribute to the client mesh. The attribute is renamed with quality metric name. Because a default quality @@ -460,14 +479,13 @@ def _transferCellAttribute( attributeFromName (str): The name of the quality attribute in initial mesh attributeToName (str): Name of the attribute in the final mesh qualityMetric (QualityMetricOtherEnum):The quality metric. - - Returns: - bool: True if the attribute was successfully transferred, False otherwise """ cellArrays: vtkCellData = inputMesh.GetCellData() - assert cellArrays is not None, "Cell data from vtkMeshQuality output mesh is undefined." + if cellArrays is None: + raise AttributeError( "Cell data from vtkMeshQuality output mesh is undefined." ) array: vtkDataArray = cellArrays.GetArray( attributeFromName ) - assert array is not None, f"{attributeFromName} attribute is undefined." + if array is None: + raise AttributeError( f"{ attributeFromName } attribute is undefined." ) # Rename array array.SetName( attributeToName ) @@ -478,11 +496,12 @@ def _transferCellAttribute( # Add array to input mesh inputCellArrays: vtkCellData = self._outputMesh.GetCellData() - assert inputCellArrays is not None, "Cell data from input mesh is undefined." + if inputCellArrays is None: + raise AttributeError( "Cell data from input mesh is undefined." ) inputCellArrays.AddArray( array ) inputCellArrays.Modified() - return True + return def _replaceIrrelevantValues( self: Self, array: vtkDataArray, mesh: vtkUnstructuredGrid, metric: MeshQualityMetricEnum ) -> None: @@ -543,7 +562,8 @@ def _updateStatsSummaryByCellType( self: Self, metricIndex: int, cellType: int ) cellType (int): Cell type index """ cellArrays: vtkCellData = self._outputMesh.GetCellData() - assert cellArrays is not None, "Cell data from input mesh is undefined." + if cellArrays is None: + raise AttributeError( "Cell data from input mesh is undefined." ) arrayName: str = getQualityMetricArrayName( metricIndex ) array: vtkDataArray | None = cellArrays.GetArray( arrayName ) @@ -584,7 +604,8 @@ def _initCellTypeMasks( self: Self ) -> None: def _createFieldDataStatsSummary( self: Self ) -> None: """Create field data arrays with quality statistics.""" fieldData: vtkFieldData = self._outputMesh.GetFieldData() - assert fieldData is not None, "Field data is undefined." + if fieldData is None: + raise AttributeError( "Field data is undefined." ) for cellType in self._allCellTypesExtended: count: int = self._qualityMetricSummary.getCellTypeCountsOfCellType( cellType ) metrics: Optional[ set[ int ] ] = self.getComputedMetricsFromCellType( cellType ) @@ -687,7 +708,8 @@ def _countVertexIncidentEdges( self: Self ) -> None: # Create point attribute pointData: vtkPointData = self._outputMesh.GetPointData() - assert pointData is not None, "Point data is undefined." + if pointData is None: + raise AttributeError( "Point data is undefined." ) countArray: vtkIntArray = numpy_to_vtk( incidentCounts, deep=1 ) metricName: str = metric.getMetricName().replace( " ", "" ) name: str = QUALITY_ARRAY_NAME + "_" + metricName @@ -696,7 +718,8 @@ def _countVertexIncidentEdges( self: Self ) -> None: pointData.Modified() fieldData: vtkFieldData = self._outputMesh.GetFieldData() - assert fieldData is not None, "Field data is undefined." + if fieldData is None: + raise AttributeError( "Field data is undefined." ) for statType in list( StatTypes ): name = metricName + "_" + statType.getString() val: float | int = statType.compute( incidentCounts ) @@ -756,7 +779,8 @@ def _computeSquishIndex( self: Self ) -> None: # Add array cellArrays: vtkCellData = self._outputMesh.GetCellData() - assert cellArrays is not None, "Cell data from output mesh is undefined." + if cellArrays is None: + raise AttributeError( "Cell data from output mesh is undefined." ) cellArrays.AddArray( newArray ) cellArrays.Modified() self._outputMesh.Modified() @@ -778,8 +802,10 @@ def _getCellCenter( self: Self, cellCenter: npt.NDArray[ np.float64 ] = np.zeros( 3 ) if cell.GetCellDimension() == 2: # Polygonal cell - assert ptsIds is not None, "Point ids are required for computing polygonal cell center." - assert points is not None, "Points are required for computing polygonal cell center." + if ptsIds is None: + raise ValueError( "Point ids are required for computing polygonal cell center." ) + if points is None: + raise ValueError( "Points are required for computing polygonal cell center." ) cell.GetPointIds() vtkPolygon.ComputeCentroid( ptsIds, points, cellCenter ) # type: ignore[call-overload] elif cell.GetCellDimension() == 3: @@ -802,7 +828,8 @@ def _getNormalVector( self: Self, points: vtkPoints, face: vtkCell ) -> npt.NDAr Returns: npt.NDArray[np.float64]: Coordinates of the normal vector """ - assert face.GetCellDimension() == 2, "Cell must be a planar polygon." + if face.GetCellDimension() != 2: + raise TypeError( "Cell must be a planar polygon." ) facePtsIds: vtkIdList = face.GetPointIds() # Need only 3 points among all to get the normal of the face since we suppose face is a plane ptsCoords: npt.NDArray[ np.float64 ] = np.zeros( ( 3, 3 ), dtype=float ) diff --git a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py index d87bc656..ad237bd8 100644 --- a/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVCellTypeCounterEnhanced.py @@ -135,19 +135,19 @@ def RequestData( cellTypeCounterEnhancedFilter: CellTypeCounterEnhanced = CellTypeCounterEnhanced( inputMesh, True ) if len( cellTypeCounterEnhancedFilter.logger.handlers ) == 0: cellTypeCounterEnhancedFilter.setLoggerHandler( VTKHandler() ) - cellTypeCounterEnhancedFilter.applyFilter() - outputTable.ShallowCopy( cellTypeCounterEnhancedFilter.getOutput() ) - - # print counts in Output Messages view - counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() - - self._countsAll += counts - # save to file if asked - if self._saveToFile and self._filename is not None: - try: - with open( self._filename, 'w' ) as fout: - fout.write( self._countsAll.print() ) - cellTypeCounterEnhancedFilter.logger.info( f"File {self._filename} was successfully written." ) - except Exception as e: - cellTypeCounterEnhancedFilter.logger.info( f"Error while exporting the file due to:\n{ e }" ) + if cellTypeCounterEnhancedFilter.applyFilter(): + outputTable.ShallowCopy( cellTypeCounterEnhancedFilter.getOutput() ) + + # print counts in Output Messages view + counts: CellTypeCounts = cellTypeCounterEnhancedFilter.GetCellTypeCountsObject() + + self._countsAll += counts + # save to file if asked + if self._saveToFile and self._filename is not None: + try: + with open( self._filename, 'w' ) as fout: + fout.write( self._countsAll.print() ) + cellTypeCounterEnhancedFilter.logger.info( f"File {self._filename} was successfully written." ) + except Exception as e: + cellTypeCounterEnhancedFilter.logger.info( f"Error while exporting the file due to:\n{ e }" ) return 1 diff --git a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py index a7ef8c8b..9384f92f 100644 --- a/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py +++ b/geos-pv/src/geos/pv/plugins/PVMeshQualityEnhanced.py @@ -127,7 +127,7 @@ def a07sSetWedgeMetrics( self: Self ) -> vtkDataArraySelection: @smproperty.dataarrayselection( name="HexahedronSpecificQualityMetric" ) def a08sSetHexMetrics( self: Self ) -> vtkDataArraySelection: - """Set Hexahdron quality metrics selection.""" + """Set Hexahedron quality metrics selection.""" return self._HexQualityMetric @smproperty.dataarrayselection( name="OtherMeshQualityMetric" ) @@ -238,16 +238,16 @@ def ApplyFilter( self, inputMesh: vtkUnstructuredGrid, outputMesh: vtkUnstructur wedgeMetrics=wedgeMetrics, hexaMetrics=hexaMetrics ) meshQualityEnhancedFilter.SetOtherMeshQualityMetrics( otherMetrics ) - meshQualityEnhancedFilter.applyFilter() + if meshQualityEnhancedFilter.applyFilter(): - outputMesh.ShallowCopy( meshQualityEnhancedFilter.getOutput() ) + outputMesh.ShallowCopy( meshQualityEnhancedFilter.getOutput() ) - # save to file if asked - if self._saveToFile: - stats: QualityMetricSummary = meshQualityEnhancedFilter.GetQualityMetricSummary() - logger: logging.Logger = meshQualityEnhancedFilter.logger - self.saveFile( stats, logger ) - self._blockIndex += 1 + # save to file if asked + if self._saveToFile: + stats: QualityMetricSummary = meshQualityEnhancedFilter.GetQualityMetricSummary() + logger: logging.Logger = meshQualityEnhancedFilter.logger + self.saveFile( stats, logger ) + self._blockIndex += 1 return def saveFile( @@ -257,14 +257,15 @@ def saveFile( ) -> None: """Export mesh quality metric summary file.""" try: - assert self._filename is not None, "Mesh quality summary report file path is undefined." + if self._filename is None: + raise AttributeError( "Mesh quality summary report file path is undefined." ) # add index for multiblock meshes index: int = self._filename.rfind( '.' ) - filename: str = self._filename[ :index ] + f"_{self._blockIndex}" + self._filename[ index: ] + filename: str = self._filename[ :index ] + f"{ self._blockIndex }" + self._filename[ index: ] fig = stats.plotSummaryFigure() fig.savefig( filename, dpi=150 ) - logger.info( f"File {filename} was successfully written." ) + logger.info( f"File { filename } was successfully written." ) except Exception as e: logger.error( f"Error while exporting the file due to:\n{ e }" ) diff --git a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py index e5461b2c..697cc285 100644 --- a/geos-pv/src/geos/pv/plugins/PVSplitMesh.py +++ b/geos-pv/src/geos/pv/plugins/PVSplitMesh.py @@ -53,7 +53,7 @@ def ApplyFilter( self: Self, inputMesh: vtkPointSet, outputMesh: vtkPointSet ) - splitMeshFilter: SplitMesh = SplitMesh( inputMesh, True ) if len( splitMeshFilter.logger.handlers ) == 0: splitMeshFilter.setLoggerHandler( VTKHandler() ) - splitMeshFilter.applyFilter() - outputMesh.ShallowCopy( splitMeshFilter.getOutput() ) + if splitMeshFilter.applyFilter(): + outputMesh.ShallowCopy( splitMeshFilter.getOutput() ) return