Skip to content

Commit b33ce6a

Browse files
committed
Add a vtk_mesh_is_valid helper to check validity of VTK meshes. Add a 'check_validity' argument to the dpf_mesh_to_vtk function.
Signed-off-by: paul.profizi <[email protected]>
1 parent bdca119 commit b33ce6a

File tree

1 file changed

+104
-4
lines changed

1 file changed

+104
-4
lines changed

src/ansys/dpf/core/vtk_helper.py

Lines changed: 104 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
# SOFTWARE.
22+
import warnings
2223

2324
import numpy as np
25+
import pyvista
2426
import pyvista as pv
2527
from typing import Union
2628
from vtk import (
@@ -362,6 +364,7 @@ def dpf_mesh_to_vtk(
362364
mesh: dpf.MeshedRegion,
363365
nodes: Union[dpf.Field, None] = None,
364366
as_linear: bool = True,
367+
check_validity: bool = False,
365368
) -> pv.UnstructuredGrid:
366369
"""Return a pyvista UnstructuredGrid given a pydpf MeshedRegion.
367370
@@ -376,18 +379,115 @@ def dpf_mesh_to_vtk(
376379
as_linear:
377380
Export quadratic surface elements as linear.
378381
379-
export_faces:
380-
Whether to export face elements along with volume elements for fluid meshes.
382+
check_validity:
383+
Whether to run the VTK cell validity check on the generated mesh and warn if not valid.
381384
382385
Returns
383386
-------
384387
grid:
385388
UnstructuredGrid corresponding to the DPF mesh.
386389
"""
387390
try:
388-
return dpf_mesh_to_vtk_op(mesh, nodes, as_linear)
391+
grid = dpf_mesh_to_vtk_op(mesh, nodes, as_linear)
389392
except (AttributeError, KeyError, errors.DPFServerException):
390-
return dpf_mesh_to_vtk_py(mesh, nodes, as_linear)
393+
grid = dpf_mesh_to_vtk_py(mesh, nodes, as_linear)
394+
if check_validity:
395+
valid, msg = vtk_mesh_is_valid(grid)
396+
if not valid:
397+
warnings.warn(f"\nVTK mesh validity check\n{msg}")
398+
return grid
399+
400+
401+
def vtk_mesh_is_valid(grid: pv.UnstructuredGrid, verbose: bool = False) -> (bool, str):
402+
"""Runs a vtk.CellValidator filter on the input grid.
403+
404+
Parameters
405+
----------
406+
grid:
407+
A vtk mesh to validate.
408+
verbose:
409+
Whether to print the complete validation.
410+
411+
Returns
412+
-------
413+
valid:
414+
Whether the vtk mesh is valid according to the vtkCellValidator.
415+
message:
416+
Output message.
417+
418+
"""
419+
from enum import Enum
420+
421+
from vtkmodules.vtkFiltersGeneral import vtkCellValidator
422+
from vtkmodules.util.numpy_support import vtk_to_numpy
423+
424+
# Prepare the Enum of possible validity states
425+
class State(Enum):
426+
Valid = 0
427+
WrongNumberOfPoints = (1,)
428+
IntersectingEdges = (2,)
429+
IntersectingFaces = (4,)
430+
NoncontiguousEdges = (8,)
431+
Nonconvex = (16,)
432+
FacesAreOrientedIncorrectly = (32,)
433+
434+
# Run the cell validator
435+
cell_validator = vtkCellValidator()
436+
cell_validator.SetInputData(grid)
437+
cell_validator.Update()
438+
# Get the states for all cells as a numpy array
439+
cell_states = vtk_to_numpy(
440+
cell_validator.GetUnstructuredGridOutput().GetCellData().GetArray("ValidityState")
441+
)
442+
# Check for invalid states
443+
elem_with_wrong_number_of_nodes = np.where(cell_states == State.WrongNumberOfPoints.value)[0]
444+
elem_with_intersecting_edges = np.where(cell_states == State.IntersectingEdges.value)[0]
445+
elem_with_intersecting_faces = np.where(cell_states == State.IntersectingFaces.value)[0]
446+
elem_with_noncontiguous_edges = np.where(cell_states == State.NoncontiguousEdges.value)[0]
447+
elem_with_nonconvex_shape = np.where(cell_states == State.Nonconvex.value)[0]
448+
elem_with_badly_oriented_faces = np.where(
449+
cell_states == State.FacesAreOrientedIncorrectly.value
450+
)[0]
451+
# Build list of number of elements failing each test
452+
failing_elements_number = [
453+
len(elem_with_wrong_number_of_nodes),
454+
len(elem_with_intersecting_edges),
455+
len(elem_with_intersecting_faces),
456+
len(elem_with_noncontiguous_edges),
457+
len(elem_with_nonconvex_shape),
458+
len(elem_with_badly_oriented_faces),
459+
]
460+
# Define whether mesh is valid
461+
mesh_is_valid = np.sum(failing_elements_number) == 0
462+
# Build output message
463+
out_msg = ""
464+
if mesh_is_valid:
465+
out_msg += "Mesh is valid."
466+
else:
467+
out_msg += "Mesh is invalid because of (by index):\n"
468+
if failing_elements_number[0] > 0:
469+
out_msg += (
470+
f" - {failing_elements_number[0]} elements with the wrong number of points:\n"
471+
)
472+
out_msg += f" {elem_with_wrong_number_of_nodes}\n"
473+
if failing_elements_number[1] > 0:
474+
out_msg += f" - {failing_elements_number[1]} elements with intersecting edges:\n"
475+
out_msg += f" {elem_with_intersecting_edges}\n"
476+
if failing_elements_number[2] > 0:
477+
out_msg += f" - {failing_elements_number[2]} elements with intersecting faces:\n"
478+
out_msg += f" {elem_with_intersecting_faces}\n"
479+
if failing_elements_number[3] > 0:
480+
out_msg += f" - {failing_elements_number[3]} elements with non contiguous edges:\n"
481+
out_msg += f" {elem_with_noncontiguous_edges}\n"
482+
if failing_elements_number[4] > 0:
483+
out_msg += f" - {failing_elements_number[4]} elements with non convex shape:\n"
484+
out_msg += f" {elem_with_nonconvex_shape}\n"
485+
if failing_elements_number[5] > 0:
486+
out_msg += f" - {failing_elements_number[5]} elements with bad face orientations:\n"
487+
out_msg += f" {elem_with_badly_oriented_faces}\n"
488+
if verbose:
489+
print(out_msg)
490+
return mesh_is_valid, out_msg
391491

392492

393493
def vtk_update_coordinates(vtk_grid, coordinates_array):

0 commit comments

Comments
 (0)