2222
2323"""Provides for vtk helper functions."""
2424
25+ from dataclasses import dataclass
2526from typing import Union
27+ import warnings
2628
2729import numpy as np
2830import pyvista as pv
@@ -363,6 +365,7 @@ def dpf_mesh_to_vtk(
363365 mesh : dpf .MeshedRegion ,
364366 nodes : Union [dpf .Field , None ] = None ,
365367 as_linear : bool = True ,
368+ check_validity : bool = False ,
366369) -> pv .UnstructuredGrid :
367370 """Return a pyvista UnstructuredGrid given a pydpf MeshedRegion.
368371
@@ -377,18 +380,159 @@ def dpf_mesh_to_vtk(
377380 as_linear:
378381 Export quadratic surface elements as linear.
379382
380- export_faces :
381- Whether to export face elements along with volume elements for fluid meshes .
383+ check_validity :
384+ Whether to run the VTK cell validity check on the generated mesh and warn if not valid .
382385
383386 Returns
384387 -------
385388 grid:
386389 UnstructuredGrid corresponding to the DPF mesh.
387390 """
388391 try :
389- return dpf_mesh_to_vtk_op (mesh , nodes , as_linear )
392+ grid = dpf_mesh_to_vtk_op (mesh , nodes , as_linear )
390393 except (AttributeError , KeyError , errors .DPFServerException ):
391- return dpf_mesh_to_vtk_py (mesh , nodes , as_linear )
394+ grid = dpf_mesh_to_vtk_py (mesh , nodes , as_linear )
395+ if check_validity :
396+ validity = vtk_mesh_is_valid (grid )
397+ if not validity .valid :
398+ warnings .warn (f"\n VTK mesh validity check\n { validity .msg } " )
399+ return grid
400+
401+
402+ @dataclass
403+ class VTKMeshValidity :
404+ """Dataclass containing the results of a call to vtk_mesh_is_valid.
405+
406+ valid:
407+ Whether the vtk mesh is valid according to the vtkCellValidator.
408+ message:
409+ Output message.
410+ validity_grid:
411+ A copy of the original grid, with validity fields.
412+ wrong_number_of_points:
413+ List of indexes of elements with the wrong number of points.
414+ intersecting_edges:
415+ List of indexes of elements with intersecting edges.
416+ intersecting_faces:
417+ List of indexes of elements with intersecting faces.
418+ non_contiguous_edges:
419+ List of indexes of elements with non-contiguous edges.
420+ non_convex:
421+ List of indexes of elements with non-convex shape.
422+ inverted_faces:
423+ List of indexes of elements with inverted faces.
424+ """
425+
426+ valid : bool
427+ msg : str
428+ grid : pv .UnstructuredGrid
429+ wrong_number_of_points : np .ndarray
430+ intersecting_edges : np .ndarray
431+ intersecting_faces : np .ndarray
432+ non_contiguous_edges : np .ndarray
433+ non_convex : np .ndarray
434+ inverted_faces : np .ndarray
435+
436+
437+ def vtk_mesh_is_valid (grid : pv .UnstructuredGrid , verbose : bool = False ) -> VTKMeshValidity :
438+ """Run a vtk.CellValidator filter on the input grid.
439+
440+ Parameters
441+ ----------
442+ grid:
443+ A vtk mesh to validate.
444+ verbose:
445+ Whether to print the complete validation.
446+
447+ Returns
448+ -------
449+ validity:
450+ A dataclass containing the results of the validator.
451+ """
452+ from enum import Enum
453+
454+ from vtkmodules .util .numpy_support import vtk_to_numpy
455+ from vtkmodules .vtkFiltersGeneral import vtkCellValidator
456+
457+ # Prepare the Enum of possible validity states
458+ class State (Enum ):
459+ Valid = 0
460+ WrongNumberOfPoints = (1 ,)
461+ IntersectingEdges = (2 ,)
462+ IntersectingFaces = (4 ,)
463+ NoncontiguousEdges = (8 ,)
464+ Nonconvex = (16 ,)
465+ FacesAreOrientedIncorrectly = (32 ,)
466+
467+ # Run the cell validator
468+ cell_validator = vtkCellValidator ()
469+ cell_validator .SetInputData (grid )
470+ cell_validator .Update ()
471+ # Get the states for all cells as a numpy array
472+ validity_grid = cell_validator .GetUnstructuredGridOutput ()
473+ cell_states = vtk_to_numpy (validity_grid .GetCellData ().GetArray ("ValidityState" ))
474+ # Check for invalid states
475+ elem_with_wrong_number_of_nodes = np .where (cell_states & State .WrongNumberOfPoints .value )[0 ]
476+ elem_with_intersecting_edges = np .where (cell_states & State .IntersectingEdges .value )[0 ]
477+ elem_with_intersecting_faces = np .where (cell_states & State .IntersectingFaces .value )[0 ]
478+ elem_with_non_contiguous_edges = np .where (cell_states & State .NoncontiguousEdges .value )[0 ]
479+ elem_with_non_convex_shape = np .where (cell_states & State .Nonconvex .value )[0 ]
480+ elem_with_badly_oriented_faces = np .where (
481+ cell_states & State .FacesAreOrientedIncorrectly .value
482+ )[0 ]
483+
484+ # Build list of number of elements failing each test
485+ failing_elements_number = [
486+ len (elem_with_wrong_number_of_nodes ),
487+ len (elem_with_intersecting_edges ),
488+ len (elem_with_intersecting_faces ),
489+ len (elem_with_non_contiguous_edges ),
490+ len (elem_with_non_convex_shape ),
491+ len (elem_with_badly_oriented_faces ),
492+ ]
493+ # Define whether mesh is valid
494+ mesh_is_valid = np .sum (failing_elements_number ) == 0
495+ # Build output message
496+ out_msg = ""
497+ if mesh_is_valid :
498+ out_msg += "Mesh is valid."
499+ else :
500+ out_msg += "Mesh is invalid because of (by index):\n "
501+ if failing_elements_number [0 ] > 0 :
502+ out_msg += (
503+ f" - { failing_elements_number [0 ]} elements with the wrong number of points:\n "
504+ )
505+ out_msg += f" { elem_with_wrong_number_of_nodes } \n "
506+ if failing_elements_number [1 ] > 0 :
507+ out_msg += f" - { failing_elements_number [1 ]} elements with intersecting edges:\n "
508+ out_msg += f" { elem_with_intersecting_edges } \n "
509+ if failing_elements_number [2 ] > 0 :
510+ out_msg += f" - { failing_elements_number [2 ]} elements with intersecting faces:\n "
511+ out_msg += f" { elem_with_intersecting_faces } \n "
512+ if failing_elements_number [3 ] > 0 :
513+ out_msg += f" - { failing_elements_number [3 ]} elements with non contiguous edges:\n "
514+ out_msg += f" { elem_with_non_contiguous_edges } \n "
515+ if failing_elements_number [4 ] > 0 :
516+ out_msg += f" - { failing_elements_number [4 ]} elements with non convex shape:\n "
517+ out_msg += f" { elem_with_non_convex_shape } \n "
518+ if failing_elements_number [5 ] > 0 :
519+ out_msg += f" - { failing_elements_number [5 ]} elements with bad face orientations:\n "
520+ out_msg += f" { elem_with_badly_oriented_faces } \n "
521+ if verbose :
522+ print (out_msg )
523+ validity_grid = pv .UnstructuredGrid (validity_grid )
524+ validity_grid .set_active_scalars ("ValidityState" )
525+ return VTKMeshValidity (
526+ valid = mesh_is_valid ,
527+ msg = out_msg ,
528+ grid = validity_grid ,
529+ wrong_number_of_points = elem_with_wrong_number_of_nodes ,
530+ intersecting_edges = elem_with_intersecting_edges ,
531+ intersecting_faces = elem_with_intersecting_faces ,
532+ non_contiguous_edges = elem_with_non_contiguous_edges ,
533+ non_convex = elem_with_non_convex_shape ,
534+ inverted_faces = elem_with_badly_oriented_faces ,
535+ )
392536
393537
394538def vtk_update_coordinates (vtk_grid , coordinates_array ):
0 commit comments