Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 125 additions & 151 deletions geos-processing/src/geos/processing/generic_processing_tools/SplitMesh.py
Original file line number Diff line number Diff line change
@@ -1,33 +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

Expand All @@ -45,159 +30,148 @@
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 ):

def __init__( self ) -> None:
"""SplitMesh filter splits each cell using edge centers."""
super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" )
class SplitMesh():

self.inData: vtkUnstructuredGrid
def __init__( self, inputMesh: vtkUnstructuredGrid, speHandler: bool = False ) -> None:
"""SplitMesh filter splits each cell using edge centers.

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.
# 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:
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, # noqa: F841
inInfoVec: list[ vtkInformationVector ], # noqa: F841
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.
"""
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.

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."

# 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.inData.GetNumberOfCells()
counts: CellTypeCounts = self._getCellCounts()
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

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 = []
# Define cell type to splitting method mapping
splitMethods = {
VTK_HEXAHEDRON: self._splitHexahedron,
VTK_TETRA: self._splitTetrahedron,
VTK_PYRAMID: self._splitPyramid,
VTK_TRIANGLE: self._splitTriangle,
VTK_QUAD: self._splitQuad,
}
for c in range( nbCells ):
cell: vtkCell = self.inData.GetCell( c )
cellType: int = cell.GetCellType()
splitMethod = splitMethods.get( cellType )
if splitMethod is not None:
splitMethod( 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:
# 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()
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

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 = []
# Define cell type to splitting method mapping
splitMethods = {
VTK_HEXAHEDRON: self._splitHexahedron,
VTK_TETRA: self._splitTetrahedron,
VTK_PYRAMID: self._splitPyramid,
VTK_TRIANGLE: self._splitTriangle,
VTK_QUAD: self._splitQuad,
}
for c in range( nbCells ):
cell: vtkCell = self.inputMesh.GetCell( c )
cellType: int = cell.GetCellType()
splitMethod = splitMethods.get( cellType )
if splitMethod is not None:
splitMethod( 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 }" )
Comment on lines +103 to +170
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This try / except implementation is weird.

Many things here do not require to be part of it. It can be done in multiple try / except for specific blocks where we would except errors, like the TypeError when parsing all cell types. It will also help to have a more understandable error output.

Copy link
Copy Markdown
Contributor Author

@RomainBaville RomainBaville Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand how to do multiple try/except block. Raising error after each step of the workfllow schould work as well and the error messages raise schould be enough to understand issues.


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.

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:
Expand Down Expand Up @@ -455,11 +429,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() ):
Expand Down
Loading