Skip to content

Commit af2532c

Browse files
authored
Added volume, self intersecting and wrong support nodes checks to mesh_doctor (#2158)
1 parent e8806a5 commit af2532c

19 files changed

+550
-163
lines changed
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
COLLOCATES_NODES = "collocated_nodes"
2-
GENERATE_FRACTURES = "generate_fractures"
3-
GENERATE_GLOBAL_IDS = "generate_global_ids"
4-
5-
all_checks = dict()
1+
# Empty

geosx_mesh_doctor/checks/collocated_nodes.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from collections import defaultdict
22
from dataclasses import dataclass
33
import logging
4-
from typing import Tuple
4+
from typing import List
55

66
import numpy
77

@@ -22,7 +22,8 @@ class Options:
2222

2323
@dataclass(frozen=True)
2424
class Result:
25-
nodes_buckets: Tuple[Tuple[int]]
25+
nodes_buckets: List[List[int]] # Each bucket contains the duplicated node indices.
26+
wrong_support_elements: List[int] # Element indices with support node indices appearing more than once.
2627

2728

2829
def __check(mesh, options: Options) -> Result:
@@ -34,7 +35,6 @@ def __check(mesh, options: Options) -> Result:
3435
locator.InitPointInsertion(output, points.GetBounds())
3536

3637
# original ids to/from filtered ids.
37-
# original_to_filtered = numpy.ones(points.GetNumberOfPoints(), dtype=int) * -1
3838
filtered_to_original = numpy.ones(points.GetNumberOfPoints(), dtype=int) * -1
3939

4040
rejected_points = defaultdict(list)
@@ -59,7 +59,16 @@ def __check(mesh, options: Options) -> Result:
5959
for n, ns in rejected_points.items():
6060
tmp.append((n, *ns))
6161

62-
return Result(nodes_buckets=tmp)
62+
# Checking that the support node indices appear only once per element.
63+
wrong_support_elements = []
64+
for c in range(mesh.GetNumberOfCells()):
65+
cell = mesh.GetCell(c)
66+
num_points_per_cell = cell.GetNumberOfPoints()
67+
if len({cell.GetPointId(i) for i in range(num_points_per_cell)}) != num_points_per_cell:
68+
wrong_support_elements.append(c)
69+
70+
return Result(nodes_buckets=tmp,
71+
wrong_support_elements=wrong_support_elements)
6372

6473

6574
def check(vtk_input_file: str, options: Options) -> Result:
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from dataclasses import dataclass
2+
import logging
3+
from typing import List, Tuple
4+
import uuid
5+
6+
from vtkmodules.vtkFiltersVerdict import (
7+
vtkCellSizeFilter
8+
)
9+
from vtk.util.numpy_support import (
10+
vtk_to_numpy,
11+
)
12+
13+
14+
from . import vtk_utils
15+
16+
17+
@dataclass(frozen=True)
18+
class Options:
19+
min_volume: float
20+
21+
22+
@dataclass(frozen=True)
23+
class Result:
24+
element_volumes: List[Tuple[int, float]]
25+
26+
27+
def __check(mesh, options: Options) -> Result:
28+
f = vtkCellSizeFilter()
29+
30+
f.ComputeAreaOff()
31+
f.ComputeLengthOff()
32+
f.ComputeSumOff()
33+
f.ComputeVertexCountOff()
34+
f.ComputeVolumeOn()
35+
volume_array_name = "__MESH_DOCTOR_VOLUME-" + str(uuid.uuid4()) # Making the name unique
36+
f.SetVolumeArrayName(volume_array_name)
37+
38+
f.SetInputData(mesh)
39+
f.Update()
40+
output = f.GetOutput()
41+
cd = output.GetCellData()
42+
for i in range(cd.GetNumberOfArrays()):
43+
if cd.GetArrayName(i) == volume_array_name:
44+
volume = vtk_to_numpy(cd.GetArray(i))
45+
assert volume is not None
46+
small_volumes: List[Tuple[int, float]] = []
47+
for i, v in enumerate(volume):
48+
if v < options.min_volume:
49+
small_volumes.append((i, v))
50+
return Result(element_volumes=small_volumes)
51+
52+
53+
def check(vtk_input_file: str, options: Options) -> Result:
54+
mesh = vtk_utils.read_mesh(vtk_input_file)
55+
return __check(mesh, options)

geosx_mesh_doctor/checks/register.py

Lines changed: 0 additions & 55 deletions
This file was deleted.
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from dataclasses import dataclass
2+
import logging
3+
from typing import List
4+
5+
from vtkmodules.vtkFiltersGeneral import (
6+
vtkCellValidator
7+
)
8+
from vtkmodules.vtkCommonCore import (
9+
vtkOutputWindow,
10+
vtkFileOutputWindow
11+
)
12+
from vtk.util.numpy_support import (
13+
vtk_to_numpy,
14+
)
15+
16+
from . import vtk_utils
17+
18+
19+
@dataclass(frozen=True)
20+
class Options:
21+
tolerance: float
22+
23+
24+
@dataclass(frozen=True)
25+
class Result:
26+
wrong_number_of_points_elements: List[int]
27+
intersecting_edges_elements: List[int]
28+
intersecting_faces_elements: List[int]
29+
non_contiguous_edges_elements: List[int]
30+
non_convex_elements: List[int]
31+
faces_are_oriented_incorrectly_elements: List[int]
32+
33+
34+
def __check(mesh, options: Options) -> Result:
35+
err_out = vtkFileOutputWindow()
36+
err_out.SetFileName("/dev/null") # vtkCellValidator outputs loads for each cell...
37+
vtk_std_err_out = vtkOutputWindow()
38+
vtk_std_err_out.SetInstance(err_out)
39+
40+
valid = 0x0
41+
wrong_number_of_points = 0x01
42+
intersecting_edges = 0x02
43+
intersecting_faces = 0x04
44+
non_contiguous_edges = 0x08
45+
non_convex = 0x10
46+
faces_are_oriented_incorrectly = 0x20
47+
48+
wrong_number_of_points_elements: List[int] = []
49+
intersecting_edges_elements: List[int] = []
50+
intersecting_faces_elements: List[int] = []
51+
non_contiguous_edges_elements: List[int] = []
52+
non_convex_elements: List[int] = []
53+
faces_are_oriented_incorrectly_elements: List[int] = []
54+
55+
f = vtkCellValidator()
56+
f.SetTolerance(options.tolerance)
57+
58+
f.SetInputData(mesh)
59+
f.Update()
60+
output = f.GetOutput()
61+
62+
cd = output.GetCellData()
63+
for i in range(cd.GetNumberOfArrays()):
64+
if cd.GetArrayName(i) == "ValidityState": # Could not change name using the vtk interface.
65+
validity = vtk_to_numpy(cd.GetArray(i))
66+
assert validity is not None
67+
for i, v in enumerate(validity):
68+
if not v & valid:
69+
if v & wrong_number_of_points:
70+
wrong_number_of_points_elements.append(i)
71+
if v & intersecting_edges:
72+
intersecting_edges_elements.append(i)
73+
if v & intersecting_faces:
74+
intersecting_faces_elements.append(i)
75+
if v & non_contiguous_edges:
76+
non_contiguous_edges_elements.append(i)
77+
if v & non_convex:
78+
non_convex_elements.append(i)
79+
if v & faces_are_oriented_incorrectly:
80+
faces_are_oriented_incorrectly_elements.append(i)
81+
return Result(wrong_number_of_points_elements=wrong_number_of_points_elements,
82+
intersecting_edges_elements=intersecting_edges_elements,
83+
intersecting_faces_elements=intersecting_faces_elements,
84+
non_contiguous_edges_elements=non_contiguous_edges_elements,
85+
non_convex_elements=non_convex_elements,
86+
faces_are_oriented_incorrectly_elements=faces_are_oriented_incorrectly_elements)
87+
88+
89+
def check(vtk_input_file: str, options: Options) -> Result:
90+
mesh = vtk_utils.read_mesh(vtk_input_file)
91+
return __check(mesh, options)

geosx_mesh_doctor/checks/vtk_utils.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
import os.path
22
import logging
3+
import sys
34

45
from vtkmodules.vtkCommonDataModel import (
5-
vtkUnstructuredGrid, )
6+
vtkUnstructuredGrid,
7+
)
68
from vtkmodules.vtkIOLegacy import (
79
vtkUnstructuredGridWriter,
810
vtkUnstructuredGridReader,
911
)
1012
from vtkmodules.vtkIOXML import (
11-
vtkXMLUnstructuredGridReader, )
13+
vtkXMLUnstructuredGridReader,
14+
)
1215

1316

1417
def read_mesh(vtk_input_file: str) -> vtkUnstructuredGrid:
15-
reader = vtkXMLUnstructuredGridReader() # TODO Find a generic way to read the vtk mesh.
18+
# Testing legacy file format.
19+
reader = vtkUnstructuredGridReader()
1620
reader.SetFileName(vtk_input_file)
17-
reader.Update()
18-
return reader.GetOutput()
21+
if reader.IsFileUnstructuredGrid():
22+
logging.debug(f"Reading file \"{vtk_input_file}\" using legacy format reader.")
23+
reader.Update()
24+
return reader.GetOutput()
25+
# Now it's xml time!
26+
reader = vtkXMLUnstructuredGridReader()
27+
if reader.CanReadFile(vtk_input_file):
28+
logging.debug(f"Reading file \"{vtk_input_file}\" using XML format reader.")
29+
reader.SetFileName(vtk_input_file)
30+
reader.Update()
31+
return reader.GetOutput()
32+
logging.critical(f"Could not find the appropriate VTK reader for file \"{vtk_input_file}\". Dying...")
33+
sys.exit(1)
1934

2035

2136
def write_mesh(mesh: vtkUnstructuredGrid, output: str) -> None:

geosx_mesh_doctor/mesh_doctor.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import logging
22
import sys
33

4-
from checks import all_checks
5-
from parsing import register
6-
from parsing import all_checks_helpers
4+
from parsing import CheckHelper
75
from parsing.cli_parsing import parse, parse_and_set_verbosity
6+
import register
87

98

109
def main():
1110
logging.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s')
1211
parse_and_set_verbosity(sys.argv)
13-
register.register()
14-
args = parse(sys.argv)
12+
all_checks, all_checks_helpers = register.register()
13+
args = parse(all_checks_helpers, sys.argv)
1514
logging.info(f"Checking mesh \"{args.vtk_input_file}\".")
1615
for check_name, check_options in args.checks.items():
1716
# If there is no option, this means that the check was not requested by the user
@@ -22,7 +21,7 @@ def main():
2221
except KeyError as e:
2322
logging.critical(f"Check {check_name} is not a valid check.")
2423
sys.exit(1)
25-
helper = all_checks_helpers[check_name]
24+
helper: CheckHelper = all_checks_helpers[check_name]
2625
result = check(args.vtk_input_file, check_options)
2726
helper.display_results(check_options, result)
2827

geosx_mesh_doctor/parsing/__init__.py

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,15 @@
22
from typing import Callable, Any
33

44

5+
COLLOCATES_NODES = "collocated_nodes"
6+
ELEMENT_VOLUMES = "element_volumes"
7+
GENERATE_FRACTURES = "generate_fractures"
8+
GENERATE_GLOBAL_IDS = "generate_global_ids"
9+
SELF_INTERSECTING_ELEMENTS = "self_intersecting_elements"
10+
11+
512
@dataclass(frozen=True)
613
class CheckHelper:
714
parse_cli_options: Callable[[str], Any]
815
display_results: Callable[[Any, Any], None]
916
get_help: Callable[[None], str]
10-
11-
12-
def build_check_helper(module) -> CheckHelper:
13-
"""
14-
If a module has functions `parse_cli_options`, `display_results` and `get_help`,
15-
the `CheckHelper` built from those functions.
16-
:param module: Any module
17-
:return: The CheckHelper instance.
18-
"""
19-
return CheckHelper(parse_cli_options=module.parse_cli_options,
20-
display_results=module.display_results,
21-
get_help=module.get_help)
22-
23-
24-
all_checks_helpers = dict()

0 commit comments

Comments
 (0)