Skip to content

Commit 43dd2d1

Browse files
committed
First completed attempt to capture errors from VTK
1 parent 7da34e9 commit 43dd2d1

File tree

7 files changed

+130
-22
lines changed

7 files changed

+130
-22
lines changed

geos-mesh/src/geos/mesh/processing/MergeBlockEnhanced.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing_extensions import Self
88

99
from geos.utils.Logger import Logger, getLogger
10+
from geos.utils.Errors import VTKError
1011
from geos.mesh.utils.multiblockModifiers import mergeBlocks
1112

1213
from vtkmodules.vtkCommonDataModel import (
@@ -32,6 +33,8 @@
3233
.. code-block:: python
3334
3435
from geos.mesh.processing.MergeBlockEnhanced import MergeBlockEnhanced
36+
import logging
37+
from geos.utils.Errors import VTKError
3538
3639
# Define filter inputs
3740
multiblockdataset: vtkMultiblockDataSet
@@ -45,7 +48,10 @@
4548
filter.setLoggerHandler( yourHandler )
4649
4750
# Do calculations
48-
filter.applyFilter()
51+
try:
52+
filter.applyFilter()
53+
except VTKError:
54+
logging.error("Something went wrong in VTK")
4955
5056
# Get the merged mesh
5157
filter.getOutput()
@@ -110,7 +116,7 @@ def applyFilter( self: Self ) -> None:
110116
outputMesh: vtkUnstructuredGrid
111117
try:
112118
outputMesh = mergeBlocks( self.inputMesh, keepPartialAttributes=True, logger=self.logger )
113-
except ( TypeError, ValueError ):
119+
except ( VTKError ):
114120
self.logger.info( f"The filter {self.logger.name} failed." )
115121
raise
116122
else:

geos-mesh/src/geos/mesh/utils/multiblockModifiers.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
33
# SPDX-FileContributor: Martin Lemay, Paloma Martinez
44
from typing import Union
5+
56
from vtkmodules.vtkCommonDataModel import ( vtkCompositeDataSet, vtkDataObjectTreeIterator, vtkMultiBlockDataSet,
67
vtkUnstructuredGrid, vtkDataSet )
78
from packaging.version import Version
9+
from vtkmodules.vtkCommonCore import vtkLogger
810

911
# TODO: remove this condition when all codes are adapted for VTK newest version.
1012
import vtk
@@ -14,11 +16,10 @@
1416
from vtkmodules.vtkFiltersCore import vtkAppendDataSets
1517

1618
from geos.mesh.utils.arrayModifiers import fillAllPartialAttributes
17-
from geos.utils.Logger import ( getLogger, Logger )
19+
from geos.utils.Logger import ( getLogger, Logger, VTKCaptureLog, RegexExceptionFilter )
1820

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

21-
2223
def mergeBlocks(
2324
inputMesh: Union[ vtkMultiBlockDataSet, vtkCompositeDataSet ],
2425
keepPartialAttributes: bool = False,
@@ -36,7 +37,7 @@ def mergeBlocks(
3637
vtkUnstructuredGrid: Merged dataset or input mesh if it's already a single block
3738
3839
Raises:
39-
ValueError:
40+
geos.utilsVTKError ():
4041
4142
.. Note::
4243
Default filling values:
@@ -50,6 +51,9 @@ def mergeBlocks(
5051
if logger is None:
5152
logger: Logger = getLogger( "mergeBlocks", True )
5253

54+
vtkLogger.SetStderrVerbosity(vtkLogger.VERBOSITY_TRACE)
55+
logger.addFilter(RegexExceptionFilter())
56+
5357
# Fill the partial attributes with default values to keep them during the merge.
5458
if keepPartialAttributes and not fillAllPartialAttributes( inputMesh, logger ):
5559
logger.warning( "Failed to fill partial attributes. Merging without keeping partial attributes." )
@@ -66,18 +70,24 @@ def mergeBlocks(
6670
logger.warning( "Input mesh is already a single block." )
6771
outputMesh = inputMesh
6872
else:
69-
af: vtkAppendDataSets = vtkAppendDataSets()
70-
af.MergePointsOn()
71-
iterator: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
72-
iterator.SetDataSet( inputMesh )
73-
iterator.VisitOnlyLeavesOn()
74-
iterator.GoToFirstItem()
75-
while iterator.GetCurrentDataObject() is not None:
76-
block: vtkDataSet = vtkDataSet.SafeDownCast( iterator.GetCurrentDataObject() )
77-
af.AddInputData( block )
78-
iterator.GoToNextItem()
79-
af.Update()
80-
73+
with VTKCaptureLog() as captured_log:
74+
75+
af: vtkAppendDataSets = vtkAppendDataSets()
76+
af.MergePointsOn()
77+
iterator: vtkDataObjectTreeIterator = vtkDataObjectTreeIterator()
78+
iterator.SetDataSet( inputMesh )
79+
iterator.VisitOnlyLeavesOn()
80+
iterator.GoToFirstItem()
81+
while iterator.GetCurrentDataObject() is not None:
82+
block: vtkDataSet = vtkDataSet.SafeDownCast( iterator.GetCurrentDataObject() )
83+
af.AddInputData( block )
84+
iterator.GoToNextItem()
85+
86+
af.Update()
87+
captured_log.seek(0) #be kind let's just rewind
88+
captured = captured_log.read().decode()
89+
90+
logger.error(captured.strip())
8191
outputMesh: vtkUnstructuredGrid = af.GetOutputDataObject( 0 )
8292

8393
return outputMesh

geos-mesh/tests/test_MergeBlocksEnhanced.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from vtkmodules.vtkCommonDataModel import vtkMultiBlockDataSet
99
from geos.mesh.processing.MergeBlockEnhanced import MergeBlockEnhanced
1010
from unittest import TestCase
11+
from geos.utils.Errors import VTKError
1112

1213
def test_MergeBlocksEnhancedFilter( dataSetTest: vtkMultiBlockDataSet, ) -> None:
1314
"""Test MergeBlockEnhanced vtk filter."""
@@ -16,7 +17,8 @@ def test_MergeBlocksEnhancedFilter( dataSetTest: vtkMultiBlockDataSet, ) -> None
1617
filter.applyFilter()
1718

1819
class RaiseMergeBlocksEnhanced(TestCase):
19-
def test_TypeError(self):
20+
"""Test failure on empty multiBlockDataSet."""
21+
def test_TypeError(self) -> None:
2022
multiBlockDataset = vtkMultiBlockDataSet()
2123
filter: MergeBlockEnhanced = MergeBlockEnhanced( multiBlockDataset )
22-
self.assertRaises((TypeError,ValueError), filter.applyFilter )
24+
self.assertRaises(VTKError, filter.applyFilter )

geos-posp/src/PVplugins/PVMohrCirclePlot.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
GeosMeshOutputsEnum,
3939
)
4040
from geos.utils.Logger import Logger, getLogger
41+
from geos.utils.Errors import VTKError
4142
from geos.utils.PhysicalConstants import (
4243
DEFAULT_FRICTION_ANGLE_DEG,
4344
DEFAULT_FRICTION_ANGLE_RAD,
@@ -805,7 +806,7 @@ def createMohrCirclesAtTimeStep(
805806
# get mesh and merge if needed
806807
try:
807808
meshMerged: vtkUnstructuredGrid = mergeBlocks( mesh )
808-
except (ValueError,TypeError):
809+
except (VTKError):
809810
raise
810811

811812
# assert meshMerged is not None, "Input data is undefined"

geos-posp/src/geos_posp/filters/GeosBlockMerge.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
getRockSuffixRenaming,
1212
)
1313
from geos.utils.Logger import Logger, getLogger
14+
from geos.utils.Errors import VTKError
1415
from typing_extensions import Self
1516
from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase
1617
from vtkmodules.vtkCommonCore import (
@@ -367,7 +368,7 @@ def mergeChildBlocks( self: Self, compositeBlock: vtkMultiBlockDataSet ) -> vtkU
367368
# merge blocks
368369
try:
369370
mergedBlocks = mergeBlocks( compositeBlock )
370-
except (ValueError,TypeError):
371+
except (VTKError):
371372
raise
372373
else:
373374
return mergedBlocks
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
class VTKError(Exception):
3+
""" Captured and adapted VTKError from log (see Logger.py)"""
4+
pass

geos-utils/src/geos/utils/Logger.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,99 @@
22
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
33
# SPDX-FileContributor: Martin Lemay
44
import logging
5-
from typing import Any, Union
5+
from typing import Any, Union, Generator
66
from typing_extensions import Self
77

8+
import sys
9+
import os
10+
import re
11+
import tempfile
12+
from contextlib import contextmanager
13+
14+
from geos.utils.Errors import VTKError
15+
816
__doc__ = """
917
Logger module manages logging tools.
1018
1119
Code was modified from <https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output>
20+
21+
It also include adaptor strategy to make vtkLogger behave as a logging's logger.
22+
Indeed, C++ adapted class is based on private Callback assignement which is not compatible
23+
with logging python's logic.
24+
25+
usage:
26+
#near logger definition
27+
from vtkmodules.vtkCommonCore import vtkLogger
28+
29+
vtkLogger.SetStderrVerbosity(vtkLogger.VERBOSITY_TRACE)
30+
logger.addFilter(RegexExceptionFilter())
31+
32+
...
33+
34+
#near VTK calls
35+
with VTKCaptureLog() as captured_log:
36+
vtkcalls..
37+
captured_log.seek(0) # be kind let's just rewind
38+
captured = captured_log.read().decode()
39+
40+
logger.error(captured.strip())
41+
1242
"""
1343

44+
class RegexExceptionFilter(logging.Filter):
45+
"""
46+
Class to regexp VTK messages rethrown into logger by VTKCaptureLog.
47+
"""
48+
49+
pattern : str = r"vtkExecutive.cxx" #pattern captured that will raise a vtkError
50+
51+
def __init__(self):
52+
super().__init__()
53+
self.regex = re.compile(self.pattern)
54+
55+
def filter(self, record : logging.LogRecord):
56+
"""
57+
Filter VTK Error from stdErr
58+
59+
Args:
60+
record(loggging.LogRecord)
61+
"""
62+
message = record.getMessage()
63+
if self.regex.search(message):
64+
raise VTKError(f"Log message matched forbidden pattern: {message}")
65+
return True # Allow other messages to pass
66+
67+
68+
@contextmanager
69+
def VTKCaptureLog()->Generator[Any,Any,Any]:
70+
"""
71+
Hard way of adapting C-like vtkLogger to logging class by throwing in
72+
stderr and reading back from it.
73+
74+
Return:
75+
Generator buffering os.stderr
76+
77+
"""
78+
#equiv to pyvista's
79+
# from pyvista.utilities import VtkErrorCatcher
80+
# with VtkErrorCatcher() as err:
81+
# append_filter.Update()
82+
# print(err)
83+
84+
# Save original stderr file descriptor
85+
original_stderr_fd = sys.stderr.fileno()
86+
saved_stderr_fd = os.dup(original_stderr_fd)
87+
88+
# Create a temporary file to capture stderr
89+
with tempfile.TemporaryFile(mode='w+b') as tmp:
90+
os.dup2(tmp.fileno(), original_stderr_fd)
91+
try:
92+
yield tmp
93+
finally:
94+
# Restore original stderr
95+
os.dup2(saved_stderr_fd, original_stderr_fd)
96+
os.close(saved_stderr_fd)
97+
1498

1599
class CountWarningHandler( logging.Handler ):
16100
"""Create an handler to count the warnings logged."""

0 commit comments

Comments
 (0)