Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
06eca95
Modify isAttributeGlobal function to take into account recursive mult…
paloma-martinez Sep 2, 2025
8651018
Add bool output to mergeBlocks function for better logging and error …
paloma-martinez Sep 2, 2025
fbe6bf2
Adding a VTK filter for enhanced merge block
paloma-martinez Sep 2, 2025
b9855ce
Move and update plugin
paloma-martinez Sep 2, 2025
21d8b7d
Merge branch 'main' into pmartinez/refactor/createMergeBlockEnhancedV…
paloma-martinez Sep 12, 2025
54c70eb
Clean
paloma-martinez Sep 17, 2025
2de326a
Fix import format
paloma-martinez Sep 17, 2025
3d20b99
Typing
paloma-martinez Sep 17, 2025
22828f7
Adding tests and data for test for merge blocks
paloma-martinez Sep 17, 2025
29a038b
Documentation
paloma-martinez Sep 17, 2025
3d153cd
Yapf
paloma-martinez Sep 17, 2025
af3990e
Yapf again
paloma-martinez Sep 17, 2025
d9f289d
Yapf
paloma-martinez Sep 17, 2025
14bcb85
Fix docstring and typing
paloma-martinez Sep 17, 2025
05a1c17
Fix from Romain review
paloma-martinez Sep 18, 2025
df24923
Merge branch 'main' into pmartinez/refactor/createMergeBlockEnhancedV…
paloma-martinez Sep 18, 2025
f771dd6
Modification of mergeBlocks function following review's comment
paloma-martinez Sep 29, 2025
4d9c3d0
Merge branch 'main' into pmartinez/refactor/createMergeBlockEnhancedV…
paloma-martinez Sep 29, 2025
b3112f9
yapf
paloma-martinez Sep 29, 2025
b1b681b
bad merge fix
paloma-martinez Sep 29, 2025
682f50e
first error handling version
jafranc Oct 3, 2025
7da34e9
adding Fails test
jafranc Oct 6, 2025
43dd2d1
First completed attempt to capture errors from VTK
jafranc Oct 7, 2025
396ed11
last fixes
jafranc Oct 7, 2025
e0ab6fc
ruff + yapf
jafranc Oct 7, 2025
ba9e2b1
yapf tests
jafranc Oct 7, 2025
f8d2b1a
Update python-package.yml as order might matters
jafranc Oct 7, 2025
b8b1481
clean up
jafranc Oct 7, 2025
cd48656
Merge branch 'pmartinez/refactor/createMergeBlockEnhancedVTKFilter' o…
jafranc Oct 8, 2025
b839532
CI is testing vtk9.5.1 so skip the failing test for now
jafranc Oct 8, 2025
262932a
mypy and yapf
jafranc Oct 8, 2025
b98b49e
wrong version dispatch
jafranc Oct 8, 2025
073f4cd
Clean and formatting
paloma-martinez Oct 16, 2025
3d0c57c
Merge branch 'main' into pmartinez/refactor/createMergeBlockEnhancedV…
paloma-martinez Oct 16, 2025
3a7b061
bad merge
paloma-martinez Oct 16, 2025
907b2d6
Force int type to avoid errors
alexbenedicto Oct 16, 2025
24a8d27
Add comments to new Logger functionalities
alexbenedicto Oct 16, 2025
f9b5cf0
Correct invalid pattern
alexbenedicto Oct 16, 2025
df766cd
Fix error in doc
alexbenedicto Oct 17, 2025
d572700
Fix ruff error
alexbenedicto Oct 17, 2025
18b13f7
Add child logger to prevent to much verbosity in parent logger
paloma-martinez Oct 17, 2025
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
12 changes: 11 additions & 1 deletion docs/geos_mesh_docs/processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,20 @@ geos.mesh.processing.SplitMesh filter
:undoc-members:
:show-inheritance:


geos.mesh.processing.MergeBlockEnhanced filter
------------------------------------------------

.. automodule:: geos.mesh.processing.MergeBlockEnhanced
:members:
:undoc-members:
:show-inheritance:


geos.mesh.processing.ClipToMainFrame filter
--------------------------------------------

.. automodule:: geos.mesh.processing.ClipToMainFrame
:members:
:undoc-members:
:show-inheritance:
:show-inheritance:
5 changes: 0 additions & 5 deletions docs/geos_posp_docs/PVplugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ PVGeomechanicsWorkflowVolumeWell plugin

.. automodule:: PVplugins.PVGeomechanicsWorkflowVolumeWell

PVplugins.PVMergeBlocksEnhanced module
--------------------------------------

.. automodule:: PVplugins.PVMergeBlocksEnhanced


PVMohrCirclePlot plugin
---------------------------------
Expand Down
6 changes: 6 additions & 0 deletions docs/geos_pv_docs/processing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ PVSplitMesh
.. automodule:: geos.pv.plugins.PVSplitMesh


PVMergeBlocksEnhanced module
--------------------------------------

.. automodule:: geos.pv.plugins.PVMergeBlocksEnhanced


PVClipToMainFrame
----------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Romain Baville
import numpy as np
import numpy.typing as npt
import logging

import numpy.typing as npt
from typing import Union, Any
from typing_extensions import Self

Expand All @@ -13,7 +14,7 @@
vtkDataSet,
)

from geos.utils.Logger import ( getLogger, Logger, logging, CountWarningHandler )
from geos.utils.Logger import ( getLogger, Logger, CountWarningHandler )
from geos.mesh.utils.arrayHelpers import ( getArrayInObject, getComponentNames, getNumberOfComponents,
getVtkDataTypeInObject, isAttributeGlobal, getAttributePieceInfo,
checkValidValuesInDataSet, checkValidValuesInMultiBlock )
Expand Down
132 changes: 132 additions & 0 deletions geos-mesh/src/geos/mesh/processing/MergeBlockEnhanced.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Paloma Martinez
# ruff: noqa: E402 # disable Module level import not at top of file
import logging

from typing_extensions import Self

from geos.utils.Logger import Logger, getLogger
from geos.utils.Errors import VTKError
from geos.mesh.utils.multiblockModifiers import mergeBlocks

from vtkmodules.vtkCommonDataModel import (
vtkMultiBlockDataSet,
vtkUnstructuredGrid,
)

__doc__ = """
Merge Blocks Keeping Partial Attributes is a filter that allows to merge blocks from a multiblock dataset while keeping partial attributes.

Input is a vtkMultiBlockDataSet and output is a vtkUnstructuredGrid.

.. Note::
- You may encounter issues if two datasets of the input multiblock dataset have duplicated cell IDs.
- Partial attributes are filled with default values depending on their types.
- 0 for uint data.
- -1 for int data.
- nan for float data.


To use it:

.. code-block:: python

from geos.mesh.processing.MergeBlockEnhanced import MergeBlockEnhanced
import logging
from geos.utils.Errors import VTKError

# Define filter inputs
multiblockdataset: vtkMultiblockDataSet
speHandler: bool # optional

# Instantiate the filter
filter: MergeBlockEnhanced = MergeBlockEnhanced( multiblockdataset, speHandler )

# Use your own handler (if speHandler is True)
yourHandler: logging.Handler
filter.setLoggerHandler( yourHandler )

# Do calculations
try:
filter.applyFilter()
except VTKError:
logging.error("Something went wrong in VTK")

# Get the merged mesh
filter.getOutput()
"""

loggerTitle: str = "Merge Block Enhanced"


class MergeBlockEnhanced:

def __init__(
self: Self,
inputMesh: vtkMultiBlockDataSet,
speHandler: bool = False,
) -> None:
"""Merge a multiblock dataset and keep the partial attributes in the output mesh.

Partial attributes are filled with default values depending on the data type such that:
- 0 for uint data.
- -1 for int data.
- nan for float data.

Args:
inputMesh (vtkMultiBlockDataSet): The input multiblock dataset to merge.
speHandler (bool, optional) : True to use a specific handler, False to use the internal handler.
Defaults to False.
"""
self.inputMesh: vtkMultiBlockDataSet = inputMesh
self.outputMesh: vtkUnstructuredGrid = vtkUnstructuredGrid()

# Logger
self.logger: Logger
if not speHandler:
self.logger = getLogger( loggerTitle, True )
else:
self.logger = logging.getLogger( loggerTitle )
self.logger.setLevel( logging.INFO )

def setLoggerHandler( self: Self, handler: logging.Handler ) -> None:
"""Set a specific handler for the filter logger.

In this filter 4 log levels are use, .info, .error, .warning and .critical, be sure to have at least the same 4 levels.

Args:
handler (logging.Handler): The handler to add.
"""
if not self.logger.hasHandlers():
self.logger.addHandler( handler )
else:
self.logger.warning(
"The logger already has an handler, to use yours set the argument 'speHandler' to True during the filter initialization."
)

def applyFilter( self: Self ) -> None:
"""Merge the blocks of a multiblock dataset mesh.

Returns:
bool: True if the blocks were successfully merged, False otherwise.

Raises:
VTKError (geos.utils.Errors) : error captured if any from the VTK log
"""
self.logger.info( f"Applying filter { self.logger.name }." )

outputMesh: vtkUnstructuredGrid
outputMesh = mergeBlocks( self.inputMesh, keepPartialAttributes=True, logger=self.logger )
self.logger.info( f"The filter {self.logger.name} failed." )
self.outputMesh = outputMesh
self.logger.info( f"The filter {self.logger.name} succeeded." )


def getOutput( self: Self ) -> vtkUnstructuredGrid:
"""Get the merged mesh.

Returns:
vtkUnstructuredGrid: The merged mesh.
"""
return self.outputMesh
1 change: 0 additions & 1 deletion geos-mesh/src/geos/mesh/utils/arrayHelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,6 @@ def isAttributeGlobal( multiBlockDataSet: vtkMultiBlockDataSet, attributeName: s
dataSet: vtkDataSet = vtkDataSet.SafeDownCast( multiBlockDataSet.GetDataSet( blockIndex ) )
if not isAttributeInObjectDataSet( dataSet, attributeName, onPoints ):
return False

return True


Expand Down
110 changes: 81 additions & 29 deletions geos-mesh/src/geos/mesh/utils/multiblockModifiers.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,97 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Martin Lemay
# SPDX-FileContributor: Martin Lemay, Paloma Martinez
from typing import Union
import logging
# import re

from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkDataObjectTreeIterator, vtkMultiBlockDataSet,
vtkDataSet )
from vtkmodules.vtkFiltersCore import vtkAppendDataSets
vtkUnstructuredGrid, vtkDataSet )
from packaging.version import Version
from vtkmodules.vtkCommonCore import vtkLogger

# TODO: remove this condition when all codes are adapted for VTK newest version.
import vtk
if Version( vtk.__version__ ) >= Version( "9.5" ):
from vtkmodules.vtkFiltersParallel import vtkMergeBlocks
else:
from vtkmodules.vtkFiltersCore import vtkAppendDataSets

from geos.mesh.utils.arrayModifiers import fillAllPartialAttributes
from geos.utils.Errors import VTKError
from geos.utils.Logger import ( getLogger, Logger, VTKCaptureLog, RegexExceptionFilter)

__doc__ = """Contains a method to merge blocks of a VTK multiblock dataset."""


# TODO : fix function for keepPartialAttributes = True
def mergeBlocks(
input: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet ],
inputMesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet ],
keepPartialAttributes: bool = False,
) -> vtkDataSet:
"""Merge all blocks of a multi block mesh.
logger: Union[ Logger, None ] = None,
) -> vtkUnstructuredGrid:
"""Merge all blocks of a multiblock dataset mesh with the possibility of keeping all partial attributes present in the initial mesh.

Args:
input (vtkMultiBlockDataSet | vtkCompositeDataSet ): composite
object to merge blocks
keepPartialAttributes (bool): if True, keep partial attributes after merge.

Defaults to False.
inputMesh (vtkMultiBlockDataSet | vtkCompositeDataSet ): The input multiblock dataset to merge.
keepPartialAttributes (bool): If False (default), only global attributes are kept during the merge. If True, partial attributes are filled with default values and kept in the output mesh.
logger (Union[Logger, None], optional): A logger to manage the output messages.
Defaults to None, an internal logger is used.

Returns:
vtkUnstructuredGrid: merged block object
vtkUnstructuredGrid: Merged dataset or input mesh if it's already a single block

Raises:
geos.utilsVTKError ():

.. Note::
Default filling values:
- 0 for uint data.
- -1 for int data.
- nan for float data.

.. Warning:: This function will not work properly if there are duplicated cell IDs in the different blocks of the input mesh.

"""
if keepPartialAttributes:
fillAllPartialAttributes( input )

af = vtkAppendDataSets()
af.MergePointsOn()
iter: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
iter.SetDataSet( input )
iter.VisitOnlyLeavesOn()
iter.GoToFirstItem()
while iter.GetCurrentDataObject() is not None:
block: vtkDataSet = vtkDataSet.SafeDownCast( iter.GetCurrentDataObject() )
af.AddInputData( block )
iter.GoToNextItem()
af.Update()
return af.GetOutputDataObject( 0 )
if logger is None:
logger: Logger = getLogger( "mergeBlocks", True )

vtkLogger.SetStderrVerbosity(vtkLogger.VERBOSITY_TRACE)
logger.addFilter(RegexExceptionFilter()) # will raise VTKError if captured VTK Error
logger.setLevel(logging.DEBUG)

# Fill the partial attributes with default values to keep them during the merge.
if keepPartialAttributes and not fillAllPartialAttributes( inputMesh, logger ):
logger.warning( "Failed to fill partial attributes. Merging without keeping partial attributes." )

if Version( vtk.__version__ ) >= Version( "9.5" ):
filter: vtkMergeBlocks = vtkMergeBlocks()
filter.SetInputData( inputMesh )
filter.Update()
Comment on lines +71 to +73
Copy link
Collaborator

Choose a reason for hiding this comment

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

Genuinely asking, should we also call the VTKCaptureLog here because we are using the vtkMergeBlocks like done in the else block ?

with VTKCaptureLog() as captured_log:
    filter: vtkMergeBlocks = vtkMergeBlocks()
    filter.SetInputData( inputMesh )
    filter.Update()

    captured_log.seek( 0 )
    captured = captured_log.read().decode()

logger.debug( captured.strip() )

And in a more general view, should we use the VTKCaptureLog anytime we use a vtk filter ?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is a short and a long answer, but IMO yes.
I will incorporate it in #144 and I think from there we can decide if we want to inc. it everywhere.


outputMesh: vtkUnstructuredGrid = filter.GetOutputDataObject( 0 )

else:
if inputMesh.IsA( "vtkDataSet" ):
logger.warning( "Input mesh is already a single block." )
outputMesh = inputMesh
else:
with VTKCaptureLog() as captured_log:

af: vtkAppendDataSets = vtkAppendDataSets()
af.MergePointsOn()
iterator: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
iterator.SetDataSet( inputMesh )
iterator.VisitOnlyLeavesOn()
iterator.GoToFirstItem()
while iterator.GetCurrentDataObject() is not None:
block: vtkDataSet = vtkDataSet.SafeDownCast( iterator.GetCurrentDataObject() )
af.AddInputData( block )
iterator.GoToNextItem()

af.Update()
captured_log.seek(0) #be kind let's just rewind
captured = captured_log.read().decode()

logger.debug(captured.strip())
outputMesh: vtkUnstructuredGrid = af.GetOutputDataObject( 0 )

return outputMesh
6 changes: 5 additions & 1 deletion geos-mesh/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import numpy.typing as npt

from vtkmodules.vtkCommonDataModel import vtkDataSet, vtkMultiBlockDataSet, vtkPolyData
from vtkmodules.vtkIOXML import vtkXMLGenericDataObjectReader
from vtkmodules.vtkIOXML import vtkXMLGenericDataObjectReader, vtkXMLMultiBlockDataReader


@pytest.fixture
Expand Down Expand Up @@ -163,6 +163,10 @@ def _get_dataset( datasetType: str ) -> Union[ vtkMultiBlockDataSet, vtkPolyData
vtkFilename = "data/displacedFault.vtm"
elif datasetType == "emptymultiblock":
vtkFilename = "data/displacedFaultempty.vtm"
elif datasetType == "multiblockGeosOutput":
# adapted from example GEOS/inputFiles/compositionalMultiphaseWell/simpleCo2InjTutorial_smoke.xml
reader: vtkXMLMultiBlockDataReader = vtkXMLMultiBlockDataReader()
vtkFilename = "data/simpleReservoirViz_small_000478.vtm"
elif datasetType == "fracture":
vtkFilename = "data/fracture_res5_id.vtu"
elif datasetType == "emptyFracture":
Expand Down
20 changes: 20 additions & 0 deletions geos-mesh/tests/data/simpleReservoirViz_small_000478.vtm
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<VTKFile type="vtkMultiBlockDataSet" version="1.0">
<vtkMultiBlockDataSet>
<Block name="cartesianMesh">
<Block name="Level0">
<Block name="CellElementRegion">
<Block name="reservoir">
<DataSet name="rank_0" file="simpleReservoirViz_small_000478/cartesianMesh/Level0/reservoir/rank_0.vtu" />
<DataSet name="rank_1" file="simpleReservoirViz_small_000478/cartesianMesh/Level0/reservoir/rank_1.vtu" />
</Block>
</Block>
<Block name="WellElementRegion">
<Block name="wellRegion">
<DataSet name="rank_1" file="simpleReservoirViz_small_000478/cartesianMesh/Level0/wellRegion/rank_1.vtu" />
</Block>
</Block>
</Block>
</Block>
</vtkMultiBlockDataSet>
</VTKFile>
Loading
Loading