22
22
23
23
"""Provides for vtk helper functions."""
24
24
25
+ from dataclasses import dataclass
25
26
from typing import Union
27
+ import warnings
26
28
27
29
import numpy as np
28
30
import pyvista as pv
@@ -363,6 +365,7 @@ def dpf_mesh_to_vtk(
363
365
mesh : dpf .MeshedRegion ,
364
366
nodes : Union [dpf .Field , None ] = None ,
365
367
as_linear : bool = True ,
368
+ check_validity : bool = False ,
366
369
) -> pv .UnstructuredGrid :
367
370
"""Return a pyvista UnstructuredGrid given a pydpf MeshedRegion.
368
371
@@ -377,18 +380,159 @@ def dpf_mesh_to_vtk(
377
380
as_linear:
378
381
Export quadratic surface elements as linear.
379
382
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 .
382
385
383
386
Returns
384
387
-------
385
388
grid:
386
389
UnstructuredGrid corresponding to the DPF mesh.
387
390
"""
388
391
try :
389
- return dpf_mesh_to_vtk_op (mesh , nodes , as_linear )
392
+ grid = dpf_mesh_to_vtk_op (mesh , nodes , as_linear )
390
393
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
+ )
392
536
393
537
394
538
def vtk_update_coordinates (vtk_grid , coordinates_array ):
0 commit comments