From d5e27b33dfce7edeb65e3536e511e43b9af47f76 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 7 Nov 2025 11:26:22 -0800 Subject: [PATCH 01/15] Create package --- .../geos/mesh/doctor/actions/mainChecks.py | 1 - mesh-doctor/pyproject.toml | 67 +++++++++++++++++++ .../src/geos/mesh_doctor}/__init__.py | 0 .../src/geos/mesh_doctor}/actions/__init__.py | 0 .../geos/mesh_doctor}/actions/allChecks.py | 4 +- .../mesh_doctor}/actions/checkFractures.py | 4 +- .../mesh_doctor}/actions/collocatedNodes.py | 2 +- .../mesh_doctor}/actions/elementVolumes.py | 2 +- .../actions/fixElementsOrderings.py | 2 +- .../geos/mesh_doctor}/actions/generateCube.py | 4 +- .../mesh_doctor}/actions/generateFractures.py | 6 +- .../mesh_doctor}/actions/generateGlobalIds.py | 2 +- .../geos/mesh_doctor/actions/mainChecks.py | 1 + .../geos/mesh_doctor}/actions/nonConformal.py | 4 +- .../geos/mesh_doctor}/actions/reorientMesh.py | 4 +- .../actions/selfIntersectingElements.py | 0 .../mesh_doctor}/actions/supportedElements.py | 4 +- .../mesh_doctor}/actions/triangleDistance.py | 0 .../mesh_doctor}/actions/vtkPolyhedron.py | 0 .../src/geos/mesh_doctor}/meshDoctor.py | 6 +- .../src/geos/mesh_doctor}/parsing/__init__.py | 0 .../parsing/_sharedChecksParsingLogic.py | 6 +- .../mesh_doctor}/parsing/allChecksParsing.py | 16 ++--- .../parsing/checkFracturesParsing.py | 0 .../geos/mesh_doctor}/parsing/cliParsing.py | 0 .../parsing/collocatedNodesParsing.py | 8 +-- .../parsing/elementVolumesParsing.py | 8 +-- .../parsing/fixElementsOrderingsParsing.py | 6 +- .../parsing/generateCubeParsing.py | 8 +-- .../parsing/generateFracturesParsing.py | 4 +- .../parsing/generateGlobalIdsParsing.py | 6 +- .../mesh_doctor}/parsing/mainChecksParsing.py | 12 ++-- .../parsing/nonConformalParsing.py | 8 +-- .../selfIntersectingElementsParsing.py | 8 +-- .../parsing/supportedElementsParsing.py | 8 +-- .../mesh_doctor}/parsing/vtkOutputParsing.py | 2 +- .../src/geos/mesh_doctor}/register.py | 10 +-- .../tests/test_cliParsing.py | 4 +- .../tests/test_collocatedNodes.py | 2 +- .../tests/test_elementVolumes.py | 2 +- .../tests/test_generateCube.py | 2 +- .../tests/test_generateFractures.py | 8 +-- .../tests/test_generateGlobalIds.py | 2 +- .../tests/test_nonConformal.py | 4 +- .../tests/test_reorientMesh.py | 4 +- .../tests/test_selfIntersectingElements.py | 2 +- .../tests/test_sharedChecksParsingLogic.py | 22 +++--- .../tests/test_supportedElements.py | 4 +- .../tests/test_triangleDistance.py | 2 +- 49 files changed, 174 insertions(+), 107 deletions(-) delete mode 100644 geos-mesh/src/geos/mesh/doctor/actions/mainChecks.py create mode 100644 mesh-doctor/pyproject.toml rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/__init__.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/__init__.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/allChecks.py (86%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/checkFractures.py (98%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/collocatedNodes.py (97%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/elementVolumes.py (97%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/fixElementsOrderings.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/generateCube.py (97%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/generateFractures.py (99%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/generateGlobalIds.py (97%) create mode 100644 mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/nonConformal.py (99%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/reorientMesh.py (98%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/selfIntersectingElements.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/supportedElements.py (98%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/triangleDistance.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/actions/vtkPolyhedron.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/meshDoctor.py (80%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/__init__.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/_sharedChecksParsingLogic.py (97%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/allChecksParsing.py (86%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/checkFracturesParsing.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/cliParsing.py (100%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/collocatedNodesParsing.py (88%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/elementVolumesParsing.py (86%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/fixElementsOrderingsParsing.py (93%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/generateCubeParsing.py (93%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/generateFracturesParsing.py (98%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/generateGlobalIdsParsing.py (90%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/mainChecksParsing.py (87%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/nonConformalParsing.py (89%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/selfIntersectingElementsParsing.py (86%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/supportedElementsParsing.py (89%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/parsing/vtkOutputParsing.py (96%) rename {geos-mesh/src/geos/mesh/doctor => mesh-doctor/src/geos/mesh_doctor}/register.py (90%) rename {geos-mesh => mesh-doctor}/tests/test_cliParsing.py (95%) rename {geos-mesh => mesh-doctor}/tests/test_collocatedNodes.py (97%) rename {geos-mesh => mesh-doctor}/tests/test_elementVolumes.py (94%) rename {geos-mesh => mesh-doctor}/tests/test_generateCube.py (92%) rename {geos-mesh => mesh-doctor}/tests/test_generateFractures.py (98%) rename {geos-mesh => mesh-doctor}/tests/test_generateGlobalIds.py (92%) rename {geos-mesh => mesh-doctor}/tests/test_nonConformal.py (94%) rename {geos-mesh => mesh-doctor}/tests/test_reorientMesh.py (96%) rename {geos-mesh => mesh-doctor}/tests/test_selfIntersectingElements.py (95%) rename {geos-mesh => mesh-doctor}/tests/test_sharedChecksParsingLogic.py (90%) rename {geos-mesh => mesh-doctor}/tests/test_supportedElements.py (96%) rename {geos-mesh => mesh-doctor}/tests/test_triangleDistance.py (98%) diff --git a/geos-mesh/src/geos/mesh/doctor/actions/mainChecks.py b/geos-mesh/src/geos/mesh/doctor/actions/mainChecks.py deleted file mode 100644 index f10cd49d..00000000 --- a/geos-mesh/src/geos/mesh/doctor/actions/mainChecks.py +++ /dev/null @@ -1 +0,0 @@ -from geos.mesh.doctor.actions.allChecks import action diff --git a/mesh-doctor/pyproject.toml b/mesh-doctor/pyproject.toml new file mode 100644 index 00000000..603a6702 --- /dev/null +++ b/mesh-doctor/pyproject.toml @@ -0,0 +1,67 @@ +[build-system] +requires = ["setuptools>=61.2", "wheel >= 0.37.1"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src"] +include = ["geos.mesh_doctor*"] +exclude = ['tests*'] + +[project] +name = "mesh-doctor" +version = "0.1.0" +description = "GEOS mesh-doctor" +authors = [{name = "GEOS Contributors" }] +maintainers = [{name = "Alexandre Benedicto", email = "alexandre.benedicto@external.totalenergies.com" }, + {name = "Romain Baville", email = "romain.baville@external.totalenergies.com" }, + {name = "Paloma Martinez", email = "paloma.martinez@external.totalenergies.com" }] +license = {text = "LGPL-2.1"} +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python" +] + +requires-python = ">=3.10" + +dependencies = [ + "geos-utils", + "geos-mesh", +] + +[project.scripts] + mesh-doctor = "geos.mesh_doctor.meshDoctor:main" + meshDoctor = "geos.mesh_doctor.meshDoctor:main" + +[project.urls] +Homepage = "https://github.com/GEOS-DEV/geosPythonPackages" +Documentation = "https://geosx-geosx.readthedocs-hosted.com/projects/geosx-geospythonpackages/en/latest/" +Repository = "https://github.com/GEOS-DEV/geosPythonPackages.git" +"Bug Tracker" = "https://github.com/GEOS-DEV/geosPythonPackages/issues" + +[project.optional-dependencies] +build = [ + "build ~= 1.2" +] +dev = [ + "mypy", + "ruff", + "yapf", +] +test = [ + "pytest-cov", + "pytest" +] + +[tool.pytest.ini_options] +addopts = "--import-mode=importlib" +console_output_style = "count" +pythonpath = ["src"] +python_classes = "Test" +python_files = "test*.py" +python_functions = "test*" +testpaths = ["tests"] +norecursedirs = "bin" +filterwarnings = [] diff --git a/geos-mesh/src/geos/mesh/doctor/__init__.py b/mesh-doctor/src/geos/mesh_doctor/__init__.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/__init__.py rename to mesh-doctor/src/geos/mesh_doctor/__init__.py diff --git a/geos-mesh/src/geos/mesh/doctor/actions/__init__.py b/mesh-doctor/src/geos/mesh_doctor/actions/__init__.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/actions/__init__.py rename to mesh-doctor/src/geos/mesh_doctor/actions/__init__.py diff --git a/geos-mesh/src/geos/mesh/doctor/actions/allChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py similarity index 86% rename from geos-mesh/src/geos/mesh/doctor/actions/allChecks.py rename to mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py index 9b7f7c09..a86cef2d 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/allChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from geos.mesh.doctor.register import __loadModuleAction -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.register import __loadModuleAction @dataclass( frozen=True ) diff --git a/geos-mesh/src/geos/mesh/doctor/actions/checkFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py similarity index 98% rename from geos-mesh/src/geos/mesh/doctor/actions/checkFractures.py rename to mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py index 6966730f..adff6be4 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/checkFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py @@ -6,8 +6,8 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkIOXML import vtkXMLMultiBlockDataReader from vtkmodules.util.numpy_support import vtk_to_numpy -from geos.mesh.doctor.actions.generateFractures import Coordinates3D -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.generateFractures import Coordinates3D +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.utils.genericHelpers import vtkIter diff --git a/geos-mesh/src/geos/mesh/doctor/actions/collocatedNodes.py b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py similarity index 97% rename from geos-mesh/src/geos/mesh/doctor/actions/collocatedNodes.py rename to mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py index 992d23eb..d3bca040 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/collocatedNodes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py @@ -4,7 +4,7 @@ from typing import Collection, Iterable from vtkmodules.vtkCommonCore import reference, vtkPoints from vtkmodules.vtkCommonDataModel import vtkIncrementalOctreePointLocator, vtkUnstructuredGrid -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid diff --git a/geos-mesh/src/geos/mesh/doctor/actions/elementVolumes.py b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py similarity index 97% rename from geos-mesh/src/geos/mesh/doctor/actions/elementVolumes.py rename to mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py index 03430c66..8c3c64b3 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/elementVolumes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py @@ -3,7 +3,7 @@ from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_TETRA, VTK_WEDGE from vtkmodules.vtkFiltersVerdict import vtkCellSizeFilter, vtkMeshQuality from vtkmodules.util.numpy_support import vtk_to_numpy -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid diff --git a/geos-mesh/src/geos/mesh/doctor/actions/fixElementsOrderings.py b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/actions/fixElementsOrderings.py rename to mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py index e560551e..1c00862f 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/fixElementsOrderings.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from vtkmodules.vtkCommonCore import vtkIdList from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid -from geos.mesh.utils.genericHelpers import toVtkIdList from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh +from geos.mesh.utils.genericHelpers import toVtkIdList @dataclass( frozen=True ) diff --git a/geos-mesh/src/geos/mesh/doctor/actions/generateCube.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py similarity index 97% rename from geos-mesh/src/geos/mesh/doctor/actions/generateCube.py rename to mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py index 4247eddf..c842ef54 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/generateCube.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py @@ -5,8 +5,8 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import ( vtkCellArray, vtkHexahedron, vtkRectilinearGrid, vtkUnstructuredGrid, VTK_HEXAHEDRON ) -from geos.mesh.doctor.actions.generateGlobalIds import __buildGlobalIds -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.generateGlobalIds import __buildGlobalIds +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput, writeMesh diff --git a/geos-mesh/src/geos/mesh/doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py similarity index 99% rename from geos-mesh/src/geos/mesh/doctor/actions/generateFractures.py rename to mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index fc8405d1..214eae1d 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -11,11 +11,11 @@ VTK_POLYHEDRON ) from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy from vtkmodules.util.vtkConstants import VTK_ID_TYPE -from geos.mesh.doctor.actions.vtkPolyhedron import FaceStream -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.vtkPolyhedron import FaceStream +from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh from geos.mesh.utils.arrayHelpers import hasArray from geos.mesh.utils.genericHelpers import toVtkIdList, vtkIter -from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh """ TypeAliases cannot be used with Python 3.9. A simple assignment like described there will be used: https://docs.python.org/3/library/typing.html#typing.TypeAlias:~:text=through%20simple%20assignment%3A-,Vector%20%3D%20list%5Bfloat%5D,-Or%20marked%20with diff --git a/geos-mesh/src/geos/mesh/doctor/actions/generateGlobalIds.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py similarity index 97% rename from geos-mesh/src/geos/mesh/doctor/actions/generateGlobalIds.py rename to mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py index 34948350..8f0d3df0 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/generateGlobalIds.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from vtkmodules.vtkCommonCore import vtkIdTypeArray from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py new file mode 100644 index 00000000..52a0d22a --- /dev/null +++ b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py @@ -0,0 +1 @@ +from geos.mesh_doctor.actions.allChecks import action diff --git a/geos-mesh/src/geos/mesh/doctor/actions/nonConformal.py b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py similarity index 99% rename from geos-mesh/src/geos/mesh/doctor/actions/nonConformal.py rename to mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py index b7683540..0487c48d 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/nonConformal.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py @@ -11,9 +11,9 @@ from vtkmodules.vtkFiltersCore import vtkPolyDataNormals from vtkmodules.vtkFiltersGeometry import vtkDataSetSurfaceFilter from vtkmodules.vtkFiltersModeling import vtkCollisionDetectionFilter, vtkLinearExtrusionFilter -from geos.mesh.doctor.actions import reorientMesh, triangleDistance -from geos.mesh.utils.genericHelpers import vtkIter +from geos.mesh_doctor.actions import reorientMesh, triangleDistance from geos.mesh.io.vtkIO import readUnstructuredGrid +from geos.mesh.utils.genericHelpers import vtkIter @dataclass( frozen=True ) diff --git a/geos-mesh/src/geos/mesh/doctor/actions/reorientMesh.py b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py similarity index 98% rename from geos-mesh/src/geos/mesh/doctor/actions/reorientMesh.py rename to mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py index a9fd11a3..26b20e01 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/reorientMesh.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py @@ -6,8 +6,8 @@ from vtkmodules.vtkCommonDataModel import ( VTK_POLYHEDRON, VTK_TRIANGLE, vtkCellArray, vtkPolyData, vtkPolygon, vtkUnstructuredGrid, vtkTetra ) from vtkmodules.vtkFiltersCore import vtkTriangleFilter -from geos.mesh.doctor.actions.vtkPolyhedron import FaceStream, buildFaceToFaceConnectivityThroughEdges -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.vtkPolyhedron import FaceStream, buildFaceToFaceConnectivityThroughEdges +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.utils.genericHelpers import toVtkIdList diff --git a/geos-mesh/src/geos/mesh/doctor/actions/selfIntersectingElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/actions/selfIntersectingElements.py rename to mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py diff --git a/geos-mesh/src/geos/mesh/doctor/actions/supportedElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py similarity index 98% rename from geos-mesh/src/geos/mesh/doctor/actions/supportedElements.py rename to mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py index e760c717..dd11e9db 100644 --- a/geos-mesh/src/geos/mesh/doctor/actions/supportedElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py @@ -8,8 +8,8 @@ from vtkmodules.vtkCommonDataModel import ( vtkCellTypes, vtkUnstructuredGrid, VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_PENTAGONAL_PRISM, VTK_POLYHEDRON, VTK_PYRAMID, VTK_TETRA, VTK_VOXEL, VTK_WEDGE ) -from geos.mesh.doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid from geos.mesh.utils.genericHelpers import vtkIter diff --git a/geos-mesh/src/geos/mesh/doctor/actions/triangleDistance.py b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/actions/triangleDistance.py rename to mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py diff --git a/geos-mesh/src/geos/mesh/doctor/actions/vtkPolyhedron.py b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/actions/vtkPolyhedron.py rename to mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py diff --git a/geos-mesh/src/geos/mesh/doctor/meshDoctor.py b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py similarity index 80% rename from geos-mesh/src/geos/mesh/doctor/meshDoctor.py rename to mesh-doctor/src/geos/mesh_doctor/meshDoctor.py index ab05af4b..2f1dfddc 100644 --- a/geos-mesh/src/geos/mesh/doctor/meshDoctor.py +++ b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py @@ -1,7 +1,7 @@ import sys -from geos.mesh.doctor.parsing import ActionHelper -from geos.mesh.doctor.parsing.cliParsing import parseAndSetVerbosity, setupLogger -from geos.mesh.doctor.register import registerParsingActions +from geos.mesh_doctor.parsing import ActionHelper +from geos.mesh_doctor.parsing.cliParsing import parseAndSetVerbosity, setupLogger +from geos.mesh_doctor.register import registerParsingActions def main(): diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/__init__.py b/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/parsing/__init__.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py similarity index 97% rename from geos-mesh/src/geos/mesh/doctor/parsing/_sharedChecksParsingLogic.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index a8c6cfb1..adccf151 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -2,9 +2,9 @@ from copy import deepcopy from dataclasses import dataclass from typing import Type, Any -from geos.mesh.doctor.actions.allChecks import Options as AllChecksOptions -from geos.mesh.doctor.actions.allChecks import Result as AllChecksResult -from geos.mesh.doctor.parsing.cliParsing import parseCommaSeparatedString, setupLogger +from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions +from geos.mesh_doctor.actions.allChecks import Result as AllChecksResult +from geos.mesh_doctor.parsing.cliParsing import parseCommaSeparatedString, setupLogger # --- Data Structure for Check Features --- diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/allChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py similarity index 86% rename from geos-mesh/src/geos/mesh/doctor/parsing/allChecksParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py index 2411f140..eb764fbc 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/allChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py @@ -1,9 +1,9 @@ import argparse from copy import deepcopy -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser +from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser as sharedFillSubparser, displayResults ) -from geos.mesh.doctor.actions.allChecks import Options as AllChecksOptions -from geos.mesh.doctor.parsing import ( +from geos.mesh_doctor.parsing import ( ALL_CHECKS, COLLOCATES_NODES, ELEMENT_VOLUMES, @@ -11,11 +11,11 @@ SELF_INTERSECTING_ELEMENTS, SUPPORTED_ELEMENTS, ) -from geos.mesh.doctor.parsing import collocatedNodesParsing as cnParser -from geos.mesh.doctor.parsing import elementVolumesParsing as evParser -from geos.mesh.doctor.parsing import nonConformalParsing as ncParser -from geos.mesh.doctor.parsing import selfIntersectingElementsParsing as sieParser -from geos.mesh.doctor.parsing import supportedElementsParsing as seParser +from geos.mesh_doctor.parsing import collocatedNodesParsing as cnParser +from geos.mesh_doctor.parsing import elementVolumesParsing as evParser +from geos.mesh_doctor.parsing import nonConformalParsing as ncParser +from geos.mesh_doctor.parsing import selfIntersectingElementsParsing as sieParser +from geos.mesh_doctor.parsing import supportedElementsParsing as seParser # Ordered list of check names for this configuration ORDERED_CHECK_NAMES = [ diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/checkFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/parsing/checkFracturesParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/cliParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py similarity index 100% rename from geos-mesh/src/geos/mesh/doctor/parsing/cliParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py similarity index 88% rename from geos-mesh/src/geos/mesh/doctor/parsing/collocatedNodesParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index 1b48c6f3..b684a2cc 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -1,7 +1,7 @@ -from geos.mesh.doctor.actions.collocatedNodes import Options, Result -from geos.mesh.doctor.parsing import COLLOCATES_NODES -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.collocatedNodes import Options, Result +from geos.mesh_doctor.parsing import COLLOCATES_NODES +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage +from geos.mesh_doctor.parsing.cliParsing import setupLogger __TOLERANCE = "tolerance" __TOLERANCE_DEFAULT = 0. diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/elementVolumesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py similarity index 86% rename from geos-mesh/src/geos/mesh/doctor/parsing/elementVolumesParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py index 102fbebd..536c7212 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/elementVolumesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py @@ -1,7 +1,7 @@ -from geos.mesh.doctor.actions.elementVolumes import Options, Result -from geos.mesh.doctor.parsing import ELEMENT_VOLUMES -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.elementVolumes import Options, Result +from geos.mesh_doctor.parsing import ELEMENT_VOLUMES +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage +from geos.mesh_doctor.parsing.cliParsing import setupLogger __MIN_VOLUME = "minVolume" __MIN_VOLUME_DEFAULT = 0. diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/fixElementsOrderingsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py similarity index 93% rename from geos-mesh/src/geos/mesh/doctor/parsing/fixElementsOrderingsParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py index 83fbbc34..d3518e8d 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/fixElementsOrderingsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py @@ -8,9 +8,9 @@ VTK_VOXEL, VTK_WEDGE, ) -from geos.mesh.doctor.actions.fixElementsOrderings import Options, Result -from geos.mesh.doctor.parsing import vtkOutputParsing, FIX_ELEMENTS_ORDERINGS -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.fixElementsOrderings import Options, Result +from geos.mesh_doctor.parsing import vtkOutputParsing, FIX_ELEMENTS_ORDERINGS +from geos.mesh_doctor.parsing.cliParsing import setupLogger __CELL_TYPE_MAPPING = { "Hexahedron": VTK_HEXAHEDRON, diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py similarity index 93% rename from geos-mesh/src/geos/mesh/doctor/parsing/generateCubeParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index f0bc8a38..9f6ab08b 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -1,7 +1,7 @@ -from geos.mesh.doctor.actions.generateCube import Options, Result, FieldInfo -from geos.mesh.doctor.parsing import vtkOutputParsing, generateGlobalIdsParsing, GENERATE_CUBE -from geos.mesh.doctor.parsing.cliParsing import setupLogger -from geos.mesh.doctor.parsing.generateGlobalIdsParsing import GlobalIdsInfo +from geos.mesh_doctor.actions.generateCube import Options, Result, FieldInfo +from geos.mesh_doctor.parsing import vtkOutputParsing, generateGlobalIdsParsing, GENERATE_CUBE +from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.generateGlobalIdsParsing import GlobalIdsInfo __X, __Y, __Z, __NX, __NY, __NZ = "x", "y", "z", "nx", "ny", "nz" __FIELDS = "fields" diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/generateFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py similarity index 98% rename from geos-mesh/src/geos/mesh/doctor/parsing/generateFracturesParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py index ffc3f17e..18322dfc 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/generateFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py @@ -1,6 +1,6 @@ import os -from geos.mesh.doctor.actions.generateFractures import Options, Result, FracturePolicy -from geos.mesh.doctor.parsing import vtkOutputParsing, GENERATE_FRACTURES +from geos.mesh_doctor.actions.generateFractures import Options, Result, FracturePolicy +from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_FRACTURES from geos.mesh.io.vtkIO import VtkOutput __POLICY = "policy" diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/generateGlobalIdsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py similarity index 90% rename from geos-mesh/src/geos/mesh/doctor/parsing/generateGlobalIdsParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py index e7e934a5..e704c790 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/generateGlobalIdsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py @@ -1,7 +1,7 @@ from dataclasses import dataclass -from geos.mesh.doctor.actions.generateGlobalIds import Options, Result -from geos.mesh.doctor.parsing import vtkOutputParsing, GENERATE_GLOBAL_IDS -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.generateGlobalIds import Options, Result +from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_GLOBAL_IDS +from geos.mesh_doctor.parsing.cliParsing import setupLogger __CELLS, __POINTS = "cells", "points" diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/mainChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py similarity index 87% rename from geos-mesh/src/geos/mesh/doctor/parsing/mainChecksParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py index d7d124ff..152672d5 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/mainChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py @@ -1,17 +1,17 @@ import argparse from copy import deepcopy -from geos.mesh.doctor.actions.allChecks import Options as AllChecksOptions -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser +from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser as sharedFillSubparser, displayResults ) -from geos.mesh.doctor.parsing import ( +from geos.mesh_doctor.parsing import ( MAIN_CHECKS, COLLOCATES_NODES, ELEMENT_VOLUMES, SELF_INTERSECTING_ELEMENTS, ) -from geos.mesh.doctor.parsing import collocatedNodesParsing as cnParser -from geos.mesh.doctor.parsing import elementVolumesParsing as evParser -from geos.mesh.doctor.parsing import selfIntersectingElementsParsing as sieParser +from geos.mesh_doctor.parsing import collocatedNodesParsing as cnParser +from geos.mesh_doctor.parsing import elementVolumesParsing as evParser +from geos.mesh_doctor.parsing import selfIntersectingElementsParsing as sieParser # Ordered list of check names for this configuration ORDERED_CHECK_NAMES = [ diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py similarity index 89% rename from geos-mesh/src/geos/mesh/doctor/parsing/nonConformalParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index b57ca504..bdeb6a0f 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -1,7 +1,7 @@ -from geos.mesh.doctor.actions.nonConformal import Options, Result -from geos.mesh.doctor.parsing import NON_CONFORMAL -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.nonConformal import Options, Result +from geos.mesh_doctor.parsing import NON_CONFORMAL +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage +from geos.mesh_doctor.parsing.cliParsing import setupLogger __ANGLE_TOLERANCE = "angleTolerance" __POINT_TOLERANCE = "pointTolerance" diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py similarity index 86% rename from geos-mesh/src/geos/mesh/doctor/parsing/selfIntersectingElementsParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index a61e1fbd..2749d5e1 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -1,8 +1,8 @@ import numpy -from geos.mesh.doctor.actions.selfIntersectingElements import Options, Result -from geos.mesh.doctor.parsing import SELF_INTERSECTING_ELEMENTS -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.selfIntersectingElements import Options, Result +from geos.mesh_doctor.parsing import SELF_INTERSECTING_ELEMENTS +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage +from geos.mesh_doctor.parsing.cliParsing import setupLogger __MIN_DISTANCE = "minDistance" __MIN_DISTANCE_DEFAULT = numpy.finfo( float ).eps diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/supportedElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py similarity index 89% rename from geos-mesh/src/geos/mesh/doctor/parsing/supportedElementsParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py index 4ed3b1ef..2b71647c 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/supportedElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py @@ -1,8 +1,8 @@ import multiprocessing -from geos.mesh.doctor.actions.supportedElements import Options, Result -from geos.mesh.doctor.parsing import SUPPORTED_ELEMENTS -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.supportedElements import Options, Result +from geos.mesh_doctor.parsing import SUPPORTED_ELEMENTS +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage +from geos.mesh_doctor.parsing.cliParsing import setupLogger __CHUNK_SIZE = "chunkSize" __NUM_PROC = "nproc" diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py similarity index 96% rename from geos-mesh/src/geos/mesh/doctor/parsing/vtkOutputParsing.py rename to mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index 1431ce4e..fd44e4af 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -1,6 +1,6 @@ import os.path import textwrap -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput __OUTPUT_FILE = "output" diff --git a/geos-mesh/src/geos/mesh/doctor/register.py b/mesh-doctor/src/geos/mesh_doctor/register.py similarity index 90% rename from geos-mesh/src/geos/mesh/doctor/register.py rename to mesh-doctor/src/geos/mesh_doctor/register.py index e265c0dd..4469b885 100644 --- a/geos-mesh/src/geos/mesh/doctor/register.py +++ b/mesh-doctor/src/geos/mesh_doctor/register.py @@ -1,21 +1,21 @@ import argparse import importlib from typing import Callable, Any -import geos.mesh.doctor.parsing as parsing -from geos.mesh.doctor.parsing import ActionHelper, cliParsing -from geos.mesh.doctor.parsing.cliParsing import setupLogger +import geos.mesh_doctor.parsing as parsing +from geos.mesh_doctor.parsing import ActionHelper, cliParsing +from geos.mesh_doctor.parsing.cliParsing import setupLogger __HELPERS: dict[ str, Callable[ [ None ], ActionHelper ] ] = dict() __ACTIONS: dict[ str, Callable[ [ None ], Any ] ] = dict() def __loadModuleAction( moduleName: str, actionFct="action" ): - module = importlib.import_module( "geos.mesh.doctor.actions." + moduleName ) + module = importlib.import_module( "geos.mesh_doctor.actions." + moduleName ) return getattr( module, actionFct ) def __loadModuleActionHelper( moduleName: str, parsingFctSuffix="Parsing" ): - module = importlib.import_module( "geos.mesh.doctor.parsing." + moduleName + parsingFctSuffix ) + module = importlib.import_module( "geos.mesh_doctor.parsing." + moduleName + parsingFctSuffix ) return ActionHelper( fillSubparser=module.fillSubparser, convert=module.convert, displayResults=module.displayResults ) diff --git a/geos-mesh/tests/test_cliParsing.py b/mesh-doctor/tests/test_cliParsing.py similarity index 95% rename from geos-mesh/tests/test_cliParsing.py rename to mesh-doctor/tests/test_cliParsing.py index fdaff897..f4356862 100644 --- a/geos-mesh/tests/test_cliParsing.py +++ b/mesh-doctor/tests/test_cliParsing.py @@ -2,8 +2,8 @@ from dataclasses import dataclass import pytest from typing import Iterator, Sequence -from geos.mesh.doctor.actions.generateFractures import FracturePolicy, Options -from geos.mesh.doctor.parsing.generateFracturesParsing import convert, displayResults, fillSubparser +from geos.mesh_doctor.actions.generateFractures import FracturePolicy, Options +from geos.mesh_doctor.parsing.generateFracturesParsing import convert, displayResults, fillSubparser from geos.mesh.io.vtkIO import VtkOutput diff --git a/geos-mesh/tests/test_collocatedNodes.py b/mesh-doctor/tests/test_collocatedNodes.py similarity index 97% rename from geos-mesh/tests/test_collocatedNodes.py rename to mesh-doctor/tests/test_collocatedNodes.py index 7232a2a8..171ce518 100644 --- a/geos-mesh/tests/test_collocatedNodes.py +++ b/mesh-doctor/tests/test_collocatedNodes.py @@ -2,7 +2,7 @@ from typing import Iterator, Tuple from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkTetra, vtkUnstructuredGrid, VTK_TETRA -from geos.mesh.doctor.actions.collocatedNodes import Options, __action +from geos.mesh_doctor.actions.collocatedNodes import Options, __action def getPoints() -> Iterator[ Tuple[ vtkPoints, int ] ]: diff --git a/geos-mesh/tests/test_elementVolumes.py b/mesh-doctor/tests/test_elementVolumes.py similarity index 94% rename from geos-mesh/tests/test_elementVolumes.py rename to mesh-doctor/tests/test_elementVolumes.py index f62c74ec..9645f9a0 100644 --- a/geos-mesh/tests/test_elementVolumes.py +++ b/mesh-doctor/tests/test_elementVolumes.py @@ -1,7 +1,7 @@ import numpy from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import VTK_TETRA, vtkCellArray, vtkTetra, vtkUnstructuredGrid -from geos.mesh.doctor.actions.elementVolumes import Options, __action +from geos.mesh_doctor.actions.elementVolumes import Options, __action def test_simpleTet(): diff --git a/geos-mesh/tests/test_generateCube.py b/mesh-doctor/tests/test_generateCube.py similarity index 92% rename from geos-mesh/tests/test_generateCube.py rename to mesh-doctor/tests/test_generateCube.py index 069aba5e..6debceb1 100644 --- a/geos-mesh/tests/test_generateCube.py +++ b/mesh-doctor/tests/test_generateCube.py @@ -1,4 +1,4 @@ -from geos.mesh.doctor.actions.generateCube import __build, Options, FieldInfo +from geos.mesh_doctor.actions.generateCube import __build, Options, FieldInfo def test_generateCube(): diff --git a/geos-mesh/tests/test_generateFractures.py b/mesh-doctor/tests/test_generateFractures.py similarity index 98% rename from geos-mesh/tests/test_generateFractures.py rename to mesh-doctor/tests/test_generateFractures.py index cef3f325..7641a2bb 100644 --- a/geos-mesh/tests/test_generateFractures.py +++ b/mesh-doctor/tests/test_generateFractures.py @@ -4,9 +4,9 @@ from typing import Iterable, Iterator, Sequence from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkQuad, VTK_HEXAHEDRON, VTK_POLYHEDRON, VTK_QUAD ) from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy -from geos.mesh.doctor.actions.checkFractures import formatCollocatedNodes -from geos.mesh.doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ -from geos.mesh.doctor.actions.generateFractures import ( __splitMeshOnFractures, Options, FracturePolicy, Coordinates3D, +from geos.mesh_doctor.actions.checkFractures import formatCollocatedNodes +from geos.mesh_doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ +from geos.mesh_doctor.actions.generateFractures import ( __splitMeshOnFractures, Options, FracturePolicy, Coordinates3D, IDMapping ) from geos.mesh.utils.genericHelpers import toVtkIdList @@ -215,7 +215,7 @@ def test_generateFracture( TestCase: TestCase ): def addSimplifiedFieldForCells( mesh: vtkUnstructuredGrid, field_name: str, fieldDimension: int ): - """Reduce functionality obtained from src.geos.mesh.doctor.actions.generateFractures.__add_fields + """Reduce functionality obtained from src.geos.mesh_doctor.actions.generateFractures.__add_fields where the goal is to add a cell data array with incrementing values. Args: diff --git a/geos-mesh/tests/test_generateGlobalIds.py b/mesh-doctor/tests/test_generateGlobalIds.py similarity index 92% rename from geos-mesh/tests/test_generateGlobalIds.py rename to mesh-doctor/tests/test_generateGlobalIds.py index f623ca27..d00dd9a0 100644 --- a/geos-mesh/tests/test_generateGlobalIds.py +++ b/mesh-doctor/tests/test_generateGlobalIds.py @@ -1,6 +1,6 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkUnstructuredGrid, vtkVertex, VTK_VERTEX -from geos.mesh.doctor.actions.generateGlobalIds import __buildGlobalIds +from geos.mesh_doctor.actions.generateGlobalIds import __buildGlobalIds def test_generateGlobalIds(): diff --git a/geos-mesh/tests/test_nonConformal.py b/mesh-doctor/tests/test_nonConformal.py similarity index 94% rename from geos-mesh/tests/test_nonConformal.py rename to mesh-doctor/tests/test_nonConformal.py index 231d01ed..e70ba41a 100644 --- a/geos-mesh/tests/test_nonConformal.py +++ b/mesh-doctor/tests/test_nonConformal.py @@ -1,6 +1,6 @@ import numpy -from geos.mesh.doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ -from geos.mesh.doctor.actions.nonConformal import Options, __action +from geos.mesh_doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ +from geos.mesh_doctor.actions.nonConformal import Options, __action def test_twoCloseHexs(): diff --git a/geos-mesh/tests/test_reorientMesh.py b/mesh-doctor/tests/test_reorientMesh.py similarity index 96% rename from geos-mesh/tests/test_reorientMesh.py rename to mesh-doctor/tests/test_reorientMesh.py index 3f7277a8..23e9c7b7 100644 --- a/geos-mesh/tests/test_reorientMesh.py +++ b/mesh-doctor/tests/test_reorientMesh.py @@ -4,8 +4,8 @@ from typing import Generator from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_POLYHEDRON -from geos.mesh.doctor.actions.reorientMesh import reorientMesh -from geos.mesh.doctor.actions.vtkPolyhedron import FaceStream +from geos.mesh_doctor.actions.reorientMesh import reorientMesh +from geos.mesh_doctor.actions.vtkPolyhedron import FaceStream from geos.mesh.utils.genericHelpers import toVtkIdList, vtkIter diff --git a/geos-mesh/tests/test_selfIntersectingElements.py b/mesh-doctor/tests/test_selfIntersectingElements.py similarity index 95% rename from geos-mesh/tests/test_selfIntersectingElements.py rename to mesh-doctor/tests/test_selfIntersectingElements.py index a1783e59..119b6779 100644 --- a/geos-mesh/tests/test_selfIntersectingElements.py +++ b/mesh-doctor/tests/test_selfIntersectingElements.py @@ -1,6 +1,6 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkHexahedron, vtkUnstructuredGrid, VTK_HEXAHEDRON -from geos.mesh.doctor.actions.selfIntersectingElements import Options, __action +from geos.mesh_doctor.actions.selfIntersectingElements import Options, __action def test_jumbledHex(): diff --git a/geos-mesh/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py similarity index 90% rename from geos-mesh/tests/test_sharedChecksParsingLogic.py rename to mesh-doctor/tests/test_sharedChecksParsingLogic.py index ec6420f5..6df0cc3f 100644 --- a/geos-mesh/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -3,9 +3,9 @@ import pytest from unittest.mock import patch # Import the module to test -from geos.mesh.doctor.actions.allChecks import Options as AllChecksOptions -from geos.mesh.doctor.actions.allChecks import Result as AllChecksResult -from geos.mesh.doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, _generateParametersHelp, +from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions +from geos.mesh_doctor.actions.allChecks import Result as AllChecksResult +from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, _generateParametersHelp, getOptionsUsedMessage, fillSubparser, convert, displayResults, CHECKS_TO_DO_ARG, PARAMETERS_ARG ) @@ -85,7 +85,7 @@ def test_fillSubparser( checkFeaturesConfig, orderedCheckNames ): assert getattr( args, PARAMETERS_ARG ) == "param1:10.5" -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) @@ -95,7 +95,7 @@ def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckName assert options.checksOptions[ "check2" ].param2 == 4.0 -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) @@ -105,7 +105,7 @@ def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNam assert "check2" not in options.checksOptions -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) @@ -116,7 +116,7 @@ def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNam assert options.checksOptions[ "check2" ].param2 == 20.5 -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) @@ -125,7 +125,7 @@ def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedC assert options.checksOptions[ "check1" ].param2 == 20.5 # Updated -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) @@ -135,7 +135,7 @@ def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckN assert "invalid_check" not in options.checksOptions -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithAllInvalidChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } # Should raise ValueError since no valid checks were selected @@ -143,7 +143,7 @@ def test_convertWithAllInvalidChecks( mockLogger, checkFeaturesConfig, orderedCh convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_displayResultsWithChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): options = AllChecksOptions( checksToPerform=[ "check1", "check2" ], checksOptions={ @@ -163,7 +163,7 @@ def test_displayResultsWithChecks( mockLogger, checkFeaturesConfig, orderedCheck assert mockLogger.results.call_count >= 2 -@patch( 'geos.mesh.doctor.parsing._sharedChecksParsingLogic.setupLogger' ) +@patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_displayResultsNoChecks( mockLogger ): options = AllChecksOptions( checksToPerform=[], checksOptions={}, checkDisplays={} ) result = AllChecksResult( checkResults={} ) diff --git a/geos-mesh/tests/test_supportedElements.py b/mesh-doctor/tests/test_supportedElements.py similarity index 96% rename from geos-mesh/tests/test_supportedElements.py rename to mesh-doctor/tests/test_supportedElements.py index 4e8ecc28..d316aea7 100644 --- a/geos-mesh/tests/test_supportedElements.py +++ b/mesh-doctor/tests/test_supportedElements.py @@ -2,8 +2,8 @@ import pytest from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_POLYHEDRON -# from geos.mesh.doctor.actions.supportedElements import Options, action, __action -from geos.mesh.doctor.actions.vtkPolyhedron import parseFaceStream, FaceStream +# from geos.mesh_doctor.actions.supportedElements import Options, action, __action +from geos.mesh_doctor.actions.vtkPolyhedron import parseFaceStream, FaceStream from geos.mesh.utils.genericHelpers import toVtkIdList diff --git a/geos-mesh/tests/test_triangleDistance.py b/mesh-doctor/tests/test_triangleDistance.py similarity index 98% rename from geos-mesh/tests/test_triangleDistance.py rename to mesh-doctor/tests/test_triangleDistance.py index 326585b6..5ff10421 100644 --- a/geos-mesh/tests/test_triangleDistance.py +++ b/mesh-doctor/tests/test_triangleDistance.py @@ -2,7 +2,7 @@ import numpy from numpy.linalg import norm import pytest -from geos.mesh.doctor.actions.triangleDistance import distanceBetweenTwoSegments, distanceBetweenTwoTriangles +from geos.mesh_doctor.actions.triangleDistance import distanceBetweenTwoSegments, distanceBetweenTwoTriangles @dataclass( frozen=True ) From 6e382dd63cefcfff80aec141fd3831379d0fa8a6 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Mon, 17 Nov 2025 09:48:40 -0800 Subject: [PATCH 02/15] Update all files to comply with mypy checking --- .../src/geos/mesh_doctor/actions/allChecks.py | 11 ++--- .../mesh_doctor/actions/checkFractures.py | 27 +++++++------ .../mesh_doctor/actions/collocatedNodes.py | 16 ++++---- .../geos/mesh_doctor/actions/generateCube.py | 9 +++-- .../mesh_doctor/actions/generateFractures.py | 40 +++++++++---------- .../geos/mesh_doctor/actions/nonConformal.py | 39 ++++++++++-------- .../geos/mesh_doctor/actions/reorientMesh.py | 2 +- .../mesh_doctor/actions/supportedElements.py | 5 --- .../mesh_doctor/actions/triangleDistance.py | 12 +++--- .../geos/mesh_doctor/actions/vtkPolyhedron.py | 26 ++++++------ .../parsing/_sharedChecksParsingLogic.py | 31 +++++++------- .../parsing/collocatedNodesParsing.py | 10 ++--- .../parsing/generateFracturesParsing.py | 5 ++- .../parsing/nonConformalParsing.py | 6 +-- mesh-doctor/src/geos/mesh_doctor/register.py | 32 ++++++++------- .../tests/test_sharedChecksParsingLogic.py | 12 +++--- 16 files changed, 147 insertions(+), 136 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py index a86cef2d..9b3a4dc7 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from typing import Any from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh_doctor.register import __loadModuleAction @@ -6,17 +7,17 @@ @dataclass( frozen=True ) class Options: checksToPerform: list[ str ] - checksOptions: dict[ str, any ] - checkDisplays: dict[ str, any ] + checksOptions: dict[ str, Any ] + checkDisplays: dict[ str, Any ] @dataclass( frozen=True ) class Result: - checkResults: dict[ str, any ] + checkResults: dict[ str, Any ] -def action( vtkInputFile: str, options: Options ) -> list[ Result ]: - checkResults: dict[ str, any ] = dict() +def action( vtkInputFile: str, options: Options ) -> Result: + checkResults: dict[ str, Any ] = dict() for checkName in options.checksToPerform: checkAction = __loadModuleAction( checkName ) setupLogger.info( f"Performing check '{checkName}'." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py index adff6be4..23133a84 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py @@ -1,5 +1,6 @@ -import numpy from dataclasses import dataclass +import numpy as np +import numpy.typing as npt from tqdm import tqdm from typing import Collection, Iterable, Sequence from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkCell @@ -52,22 +53,22 @@ def formatCollocatedNodes( fractureMesh: vtkUnstructuredGrid ) -> Sequence[ Iter Returns: Sequence[ Iterable[ int ] ]: An iterable over all the buckets of collocated nodes. """ - collocatedNodes: numpy.ndarray = vtk_to_numpy( fractureMesh.GetPointData().GetArray( "collocatedNodes" ) ) + collocatedNodes: npt.NDArray = vtk_to_numpy( fractureMesh.GetPointData().GetArray( "collocatedNodes" ) ) if len( collocatedNodes.shape ) == 1: - collocatedNodes: numpy.ndarray = collocatedNodes.reshape( ( collocatedNodes.shape[ 0 ], 1 ) ) + collocatedNodes = collocatedNodes.reshape( ( collocatedNodes.shape[ 0 ], 1 ) ) generator = ( tuple( sorted( bucket[ bucket > -1 ] ) ) for bucket in collocatedNodes ) return tuple( generator ) def __checkCollocatedNodesPositions( - matrixPoints: Sequence[ Coordinates3D ], fracturePoints: Sequence[ Coordinates3D ], g2l: Sequence[ int ], + matrixPoints: npt.NDArray, fracturePoints: npt.NDArray, g2l: npt.NDArray[ np.int64 ], collocatedNodes: Iterable[ Iterable[ int ] ] ) -> Collection[ tuple[ int, Iterable[ int ], Iterable[ Coordinates3D ] ] ]: issues = [] for li, bucket in enumerate( collocatedNodes ): matrix_nodes = ( fracturePoints[ li ], ) + tuple( map( lambda gi: matrixPoints[ g2l[ gi ] ], bucket ) ) - m = numpy.array( matrix_nodes ) - rank: int = numpy.linalg.matrix_rank( m ) + m = np.array( matrix_nodes ) + rank: int = np.linalg.matrix_rank( m ) if rank > 1: issues.append( ( li, bucket, tuple( map( lambda gi: matrixPoints[ g2l[ gi ] ], bucket ) ) ) ) return issues @@ -83,7 +84,7 @@ def myIter( ccc ): yield ( i, ) -def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid, g2l: Sequence[ int ], +def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid, g2l: npt.NDArray[ np.int64 ], collocatedNodes: Sequence[ Iterable[ int ] ] ): fractureNodes: set[ int ] = set() for bucket in collocatedNodes: @@ -102,8 +103,8 @@ def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid fractureFaces.add( pointIds ) # Finding the cells for c in tqdm( range( fracture.GetNumberOfCells() ), desc="Finding neighbor cell pairs" ): - cell: vtkCell = fracture.GetCell( c ) - cns: set[ frozenset[ int ] ] = set() # subset of collocatedNodes + cell = fracture.GetCell( c ) + cns: set[ frozenset[ npt.NDArray[ np.int64 ] ] ] = set() # subset of collocatedNodes pointIds = frozenset( vtkIter( cell.GetPointIds() ) ) for pointId in pointIds: bucket = collocatedNodes[ pointId ] @@ -112,8 +113,8 @@ def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid found = 0 tmp = tuple( map( tuple, cns ) ) for nodeCombinations in myIter( tmp ): - f = frozenset( nodeCombinations ) - if f in fractureFaces: + faceCombination = frozenset( nodeCombinations ) + if faceCombination in fractureFaces: found += 1 if found != 2: setupLogger.warning( "Something went wrong since we should have found 2 fractures faces (we found" + @@ -130,7 +131,7 @@ def __action( vtkInputFile: str, options: Options ) -> Result: fracture.GetPointData().GetGlobalIds() and fracture.GetCellData().GetGlobalIds() pointIds = vtk_to_numpy( matrix.GetPointData().GetGlobalIds() ) - g2l = numpy.ones( len( pointIds ), dtype=int ) * -1 + g2l: npt.NDArray[ np.int64 ] = np.ones( len( pointIds ), dtype=np.int64 ) * -1 for loc, glo in enumerate( pointIds ): g2l[ glo ] = loc g2l.flags.writeable = False @@ -146,7 +147,7 @@ def __action( vtkInputFile: str, options: Options ) -> Result: for duplicate in filter( lambda i: i > -1, duplicates ): p0 = matrixPoints.GetPoint( g2l[ duplicate ] ) p1 = fracturePoints.GetPoint( i ) - if numpy.linalg.norm( numpy.array( p1 ) - numpy.array( p0 ) ) > options.tolerance: + if np.linalg.norm( np.array( p1 ) - np.array( p0 ) ) > options.tolerance: errors.append( ( i, g2l[ duplicate ], duplicate ) ) return Result( errors=errors ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py index d3bca040..0bfab5da 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py @@ -20,7 +20,7 @@ class Result: def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: - points = mesh.GetPoints() + points: vtkPoints = mesh.GetPoints() locator = vtkIncrementalOctreePointLocator() locator.SetTolerance( options.tolerance ) @@ -33,19 +33,21 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: rejectedPoints = defaultdict( list ) pointId = reference( 0 ) for i in range( points.GetNumberOfPoints() ): - isInserted = locator.InsertUniquePoint( points.GetPoint( i ), pointId ) + isInserted = locator.InsertUniquePoint( points.GetPoint( i ), pointId ) # type: ignore[arg-type] if not isInserted: # If it's not inserted, `pointId` contains the node that was already at that location. # But in that case, `pointId` is the new numbering in the destination points array. # It's more useful for the user to get the old index in the original mesh so he can look for it in his data. - setupLogger.debug( f"Point {i} at {points.GetPoint(i)} has been rejected, " - f"point {filteredToOriginal[pointId.get()]} is already inserted." ) - rejectedPoints[ pointId.get() ].append( i ) + setupLogger.debug( + f"Point {i} at {points.GetPoint(i)} has been rejected, " + + f"point {filteredToOriginal[pointId.get()]} is already inserted.", # type: ignore[misc, call-overload] + ) + rejectedPoints[ pointId.get() ].append( i ) # type: ignore[misc] else: # If it's inserted, `pointId` contains the new index in the destination array. # We store this information to be able to connect the source and destination arrays. # originalToFiltered[i] = pointId.get() - filteredToOriginal[ pointId.get() ] = i + filteredToOriginal[ pointId.get() ] = i # type: ignore[misc, call-overload] tmp = [] for n, ns in rejectedPoints.items(): @@ -59,7 +61,7 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: if len( { cell.GetPointId( i ) for i in range( numPointsPerCell ) } ) != numPointsPerCell: wrongSupportElements.append( c ) - return Result( nodesBuckets=tmp, wrongSupportElements=wrongSupportElements ) + return Result( nodesBuckets=tmp, wrongSupportElements=wrongSupportElements ) # type: ignore[arg-type] def action( vtkInputFile: str, options: Options ) -> Result: diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py index c842ef54..55c564d5 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py @@ -1,10 +1,10 @@ from dataclasses import dataclass import numpy -from typing import Iterable, Sequence +from typing import Iterable, Sequence, Union from vtkmodules.util.numpy_support import numpy_to_vtk from vtkmodules.vtkCommonCore import vtkPoints -from vtkmodules.vtkCommonDataModel import ( vtkCellArray, vtkHexahedron, vtkRectilinearGrid, vtkUnstructuredGrid, - VTK_HEXAHEDRON ) +from vtkmodules.vtkCommonDataModel import ( vtkCellArray, vtkCellData, vtkHexahedron, vtkPointData, vtkRectilinearGrid, + vtkUnstructuredGrid, VTK_HEXAHEDRON ) from geos.mesh_doctor.actions.generateGlobalIds import __buildGlobalIds from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput, writeMesh @@ -94,12 +94,15 @@ def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: def __addFields( mesh: vtkUnstructuredGrid, fields: Iterable[ FieldInfo ] ) -> vtkUnstructuredGrid: for fieldInfo in fields: + data: Union[ vtkPointData, vtkCellData ] if fieldInfo.support == "CELLS": data = mesh.GetCellData() n = mesh.GetNumberOfCells() elif fieldInfo.support == "POINTS": data = mesh.GetPointData() n = mesh.GetNumberOfPoints() + else: + raise ValueError( f"Unsupported field support: {fieldInfo.support}" ) array = numpy.ones( ( n, fieldInfo.dimension ), dtype=float ) vtkArray = numpy_to_vtk( array ) vtkArray.SetName( fieldInfo.name ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index 214eae1d..a602d579 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -23,7 +23,7 @@ IDMapping = Mapping[ int, int ] CellsPointsCoords = dict[ int, list[ tuple[ float ] ] ] -Coordinates3D = tuple[ float ] +Coordinates3D = tuple[ float, float, float ] class FracturePolicy( Enum ): @@ -54,7 +54,7 @@ class FractureInfo: def buildNodeToCells( mesh: vtkUnstructuredGrid, - faceNodes: Iterable[ Iterable[ int ] ] ) -> dict[ int, Iterable[ int ] ]: + faceNodes: Iterable[ Iterable[ int ] ] ) -> Mapping[ int, Iterable[ int ] ]: # TODO normally, just a list and not a set should be enough. nodeToCells: dict[ int, set[ int ] ] = defaultdict( set ) @@ -103,7 +103,7 @@ def __buildFractureInfoFromFields( mesh: vtkUnstructuredGrid, f: Sequence[ int ] if fnh not in faceNodesHashes: faceNodesHashes.add( fnh ) faceNodes.append( fn ) - nodeToCells: dict[ int, Iterable[ int ] ] = buildNodeToCells( mesh, faceNodes ) + nodeToCells: Mapping[ int, Iterable[ int ] ] = buildNodeToCells( mesh, faceNodes ) faceCellId: list = list() # no cell of the mesh corresponds to that face when fracture policy is 'field' return FractureInfo( nodeToCells=nodeToCells, faceNodes=faceNodes, faceCellId=faceCellId ) @@ -202,16 +202,16 @@ def buildCellToCellGraph( mesh: vtkUnstructuredGrid, fracture: FractureInfo ) -> def _identifySplit( numPoints: int, cellToCell: networkx.Graph, - nodeToCells: dict[ int, Iterable[ int ] ] ) -> dict[ int, IDMapping ]: + nodeToCells: Mapping[ int, Iterable[ int ] ] ) -> Mapping[ int, IDMapping ]: """For each cell, compute the node indices replacements. Args: numPoints (int): The number of points in the whole mesh (not the fracture). cellToCell (networkx.Graph): The cell to cell graph (connection through common faces). - nodeToCells (dict[ int, Iterable[ int ] ]): Maps the nodes of the fracture to the cells relying on this node. + nodeToCells (Mapping[ int, Iterable[ int ] ]): Maps the nodes of the fracture to the cells relying on this node. Returns: - dict[ int, IDMapping ]: For each cell (first key), returns a mapping from the current index + Mapping[ int, IDMapping ]: For each cell (first key), returns a mapping from the current index and the new index that should replace the current index. Note that the current index and the new index can be identical: no replacement should be done then. """ @@ -236,7 +236,7 @@ def __call__( self, index: int ) -> int: return index buildNewIndex = NewIndex( numPoints ) - result: dict[ int, IDMapping ] = defaultdict( dict ) + result: dict[ int, dict[ int, int ] ] = defaultdict( dict ) # Iteration over `sorted` nodes to have a predictable result for tests. for node, cells in tqdm( sorted( nodeToCells.items() ), desc="Identifying the node splits" ): for connectedCells in networkx.connected_components( cellToCell.subgraph( cells ) ): @@ -249,14 +249,14 @@ def __call__( self, index: int ) -> int: def __copyFieldsSplitMesh( oldMesh: vtkUnstructuredGrid, splitMesh: vtkUnstructuredGrid, - addedPointsWithOldId: list[ tuple[ int ] ] ) -> None: + addedPointsWithOldId: list[ tuple[ int, int ] ] ) -> None: """Copies the fields from the old mesh to the new one. Point data will be duplicated for collocated nodes. Args: oldMesh (vtkUnstructuredGrid): The mesh before the split._ splitMesh (vtkUnstructuredGrid): The mesh after the split. Will receive the fields in place. - addedPointsWithOldId (list[ tuple[ int ] ]): _description_ + addedPointsWithOldId (list[ tuple[ int, int ] ]): _description_ """ # Copying the cell data. The cells are the same, just their nodes support have changed. inputCellData = oldMesh.GetCellData() @@ -369,7 +369,7 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in vtkUnstructuredGrid: The main 3d mesh split at the fracture location. """ addedPoints: set[ int ] = set() - addedPointsWithOldId: list[ tuple[ int ] ] = list() + addedPointsWithOldId: list[ tuple[ int, int ] ] = list() for nodeMapping in cellToNodeMapping.values(): for i, o in nodeMapping.items(): if i != o: @@ -387,8 +387,8 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in newPoints.SetPoint( p, oldPoints.GetPoint( p ) ) collocatedNodes[ p ] = p # Creating the new collocated/duplicated points based on the old points positions. - for nodeMapping in cellToNodeMapping.values(): - for i, o in nodeMapping.items(): + for nodeMappingDup in cellToNodeMapping.values(): + for i, o in nodeMappingDup.items(): if i != o: newPoints.SetPoint( o, oldPoints.GetPoint( i ) ) collocatedNodes[ o ] = i @@ -407,7 +407,7 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in newMesh.Allocate( oldMesh.GetNumberOfCells() ) for c in tqdm( range( oldMesh.GetNumberOfCells() ), desc="Performing the mesh split" ): - nodeMapping: IDMapping = cellToNodeMapping.get( c, {} ) + cellNodeMapping: IDMapping = cellToNodeMapping.get( c, {} ) cell: vtkCell = oldMesh.GetCell( c ) cellType: int = cell.GetCellType() # For polyhedron, we'll manipulate the face stream directly. @@ -418,7 +418,7 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in for faceNodes in FaceStream.buildFromVtkIdList( faceStream ).faceNodes: newPointIds = list() for currentPointId in faceNodes: - newPointId: int = nodeMapping.get( currentPointId, currentPointId ) + newPointId: int = cellNodeMapping.get( currentPointId, currentPointId ) newPointIds.append( newPointId ) newFaceNodes.append( newPointIds ) newMesh.InsertNextCell( cellType, toVtkIdList( FaceStream( newFaceNodes ).dump() ) ) @@ -427,9 +427,9 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in # Then the values will be (potentially) overwritten in place, before being sent back into the cell. cellPointIds: vtkIdList = cell.GetPointIds() for i in range( cellPointIds.GetNumberOfIds() ): - currentPointId: int = cellPointIds.GetId( i ) - newPointId: int = nodeMapping.get( currentPointId, currentPointId ) - cellPointIds.SetId( i, newPointId ) + currentPtId: int = cellPointIds.GetId( i ) + newPtId: int = cellNodeMapping.get( currentPtId, currentPtId ) + cellPointIds.SetId( i, newPtId ) newMesh.InsertNextCell( cellType, cellPointIds ) __copyFieldsSplitMesh( oldMesh, newMesh, addedPointsWithOldId ) @@ -491,7 +491,7 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture numPoints: int = len( fractureNodes ) points = vtkPoints() points.SetNumberOfPoints( numPoints ) - node3dToNode2d: IDMapping = dict() # Building the node mapping, from 3d mesh nodes to 2d fracture nodes. + node3dToNode2d: dict[ int, int ] = dict() # Building the node mapping, from 3d mesh nodes to 2d fracture nodes. for i, n in enumerate( fractureNodes ): coords: Coordinates3D = meshPoints.GetPoint( n ) points.SetPoint( i, coords ) @@ -508,8 +508,8 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture polygons.InsertNextCell( polygon ) buckets: dict[ int, set[ int ] ] = defaultdict( set ) - for nodeMapping in cellToNodeMapping.values(): - for i, o in nodeMapping.items(): + for nodeMappingBucket in cellToNodeMapping.values(): + for i, o in nodeMappingBucket.items(): k: Optional[ int ] = node3dToNode2d.get( min( i, o ) ) if k is not None: buckets[ k ].update( ( i, o ) ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py index 0487c48d..eb6054cf 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py @@ -1,6 +1,7 @@ from dataclasses import dataclass import math import numpy +from typing import Union from tqdm import tqdm from vtk import reference as vtkReference from vtkmodules.vtkCommonCore import vtkDataArray, vtkIdList, vtkPoints @@ -46,9 +47,11 @@ def __init__( self, mesh: vtkUnstructuredGrid ): """ # Building the boundary meshes boundaryMesh, __normals, self.__originalCells = BoundaryMesh.__buildBoundaryMesh( mesh ) - cellsToReorient = filter( - lambda c: mesh.GetCell( c ).GetCellType() == VTK_POLYHEDRON, - map( self.__originalCells.GetValue, range( self.__originalCells.GetNumberOfValues() ) ) ) + cellsToReorient: filter[ int ] = filter( + lambda c: mesh.GetCell( c ).GetCellType() == VTK_POLYHEDRON, # type: ignore[arg-type] + map( self.__originalCells.GetValue, # type: ignore[attr-defined] + range( self.__originalCells.GetNumberOfValues() ) ) + ) reorientedMesh = reorientMesh.reorientMesh( mesh, cellsToReorient ) self.reBoundaryMesh, reNormals, _ = BoundaryMesh.__buildBoundaryMesh( reorientedMesh, consistency=False ) numCells = boundaryMesh.GetNumberOfCells() @@ -56,7 +59,7 @@ def __init__( self, mesh: vtkUnstructuredGrid ): self.__isUnderlyingCellTypeAPolyhedron = numpy.zeros( numCells, dtype=bool ) for ic in range( numCells ): self.__isUnderlyingCellTypeAPolyhedron[ ic ] = mesh.GetCell( - self.__originalCells.GetValue( ic ) ).GetCellType() == VTK_POLYHEDRON + self.__originalCells.GetValue( ic ) ).GetCellType() == VTK_POLYHEDRON # type: ignore[attr-defined] # Precomputing the normals self.__normals: numpy.ndarray = numpy.empty( ( numCells, 3 ), dtype=numpy.double, order='C' ) # Do not modify the storage layout @@ -68,7 +71,7 @@ def __init__( self, mesh: vtkUnstructuredGrid ): @staticmethod def __buildBoundaryMesh( mesh: vtkUnstructuredGrid, - consistency=True ) -> tuple[ vtkUnstructuredGrid, vtkDataArray, vtkDataArray ]: + consistency=True ) -> tuple[ vtkPolyData, vtkDataArray, vtkDataArray ]: """From a 3d mesh, build the envelope meshes. Args: @@ -76,8 +79,9 @@ def __buildBoundaryMesh( mesh: vtkUnstructuredGrid, consistency (bool, optional): The vtk option passed to the `vtkDataSetSurfaceFilter`. Defaults to True. Returns: - tuple[ vtkUnstructuredGrid, Any, Any ]: A tuple containing the boundary mesh, the normal vectors array, - an array that maps the id of the boundary element to the id of the 3d cell it touches. + tuple[ vtkPolyData, vtkDataArray, vtkDataArray ]: A tuple containing the boundary mesh, + the normal vectors array, and an array that maps the id of the boundary element + to the id of the 3d cell it touches. """ f = vtkDataSetSurfaceFilter() f.PassThroughCellIdsOn() @@ -240,7 +244,7 @@ class Extruder: """ def __init__( self, boundaryMesh: BoundaryMesh, faceTolerance: float ): - self.__extrusions: list[ vtkPolyData ] = [ + self.__extrusions: list[ Union[ vtkPolyData, None ] ] = [ None, ] * boundaryMesh.GetNumberOfCells() self.__boundaryMesh = boundaryMesh @@ -258,7 +262,7 @@ def __extrude( self, polygonPolyData: vtkPolyData, normal: numpy.ndarray ) -> vt """ extruder = vtkLinearExtrusionFilter() extruder.SetExtrusionTypeToVectorExtrusion() - extruder.SetVector( normal ) + extruder.SetVector( float( normal[ 0 ] ), float( normal[ 1 ] ), float( normal[ 2 ] ) ) extruder.SetScaleFactor( self.__faceTolerance / 2. ) extruder.SetInputData( polygonPolyData ) extruder.Update() @@ -282,7 +286,7 @@ def __getitem__( self, i: int ) -> vtkPolyData: return extrusion -def areFacesConformalUsingExtrusions( extrusions: Extruder, i: int, j: int, boundaryMesh: vtkUnstructuredGrid, +def areFacesConformalUsingExtrusions( extrusions: Extruder, i: int, j: int, boundaryMesh: BoundaryMesh, pointTolerance: float ) -> bool: """ Tests if two boundary faces are conformal, checking for intersection between their normal extruded volumes. @@ -291,7 +295,7 @@ def areFacesConformalUsingExtrusions( extrusions: Extruder, i: int, j: int, boun extrusions (Extruder): The extrusions cache. i (int): The cell index of the first cell. j (int): The cell index of the second cell. - boundaryMesh (vtkUnstructuredGrid): The boundary mesh. + boundaryMesh (BoundaryMesh): The boundary mesh. pointTolerance (float): The point tolerance to consider that two points match. Returns: @@ -339,10 +343,10 @@ def areFacesConformalUsingDistances( i: int, j: int, boundaryMesh: vtkUnstructur def triangulate( cell ): assert cell.GetCellDimension() == 2 - _pointsIds = vtkIdList() + _pointsIdsList = vtkIdList() _points = vtkPoints() - cell.Triangulate( 0, _pointsIds, _points ) - _pointsIds = tuple( vtkIter( _pointsIds ) ) + cell.Triangulate( 0, _pointsIdsList, _points ) + _pointsIds: tuple[ int, ... ] = tuple( vtkIter( _pointsIdsList ) ) assert len( _pointsIds ) % 3 == 0 assert _points.GetNumberOfPoints() % 3 == 0 return _pointsIds, _points @@ -415,7 +419,9 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: bb.Inflate( 2 * options.faceTolerance ) assert boundingBoxes[ i, : ].data.contiguous # Do not modify the storage layout since vtk deals with raw memory here. - bb.GetBounds( boundingBoxes[ i, : ] ) + boundsList: list[ float ] = [ 0.0 ] * 6 + bb.GetBounds( boundsList ) + boundingBoxes[ i, : ] = boundsList nonConformalCells = [] extrusions = Extruder( boundaryMesh, options.faceTolerance ) @@ -436,7 +442,8 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: # Extracting the original 3d element index (and not the index of the boundary mesh). tmp = [] for i, j in nonConformalCells: - tmp.append( ( boundaryMesh.originalCells.GetValue( i ), boundaryMesh.originalCells.GetValue( j ) ) ) + tmp.append( ( boundaryMesh.originalCells.GetValue( i ), # type: ignore[attr-defined] + boundaryMesh.originalCells.GetValue( j ) ) ) # type: ignore[attr-defined] return Result( nonConformalCells=tmp ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py index 26b20e01..88d8ff93 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py @@ -54,7 +54,7 @@ def __computeVolume( meshPoints: vtkPoints, faceStream: FaceStream ) -> float: tmpBarycenter = numpy.empty( ( faceStream.numSupportPoints, 3 ), dtype=float ) for i, pointId in enumerate( faceStream.supportPointIds ): tmpBarycenter[ i, : ] = meshPoints.GetPoint( pointId ) - barycenter = tmpBarycenter[ :, 0 ].mean(), tmpBarycenter[ :, 1 ].mean(), tmpBarycenter[ :, 2 ].mean() + barycenter = [ tmpBarycenter[ :, 0 ].mean(), tmpBarycenter[ :, 1 ].mean(), tmpBarycenter[ :, 2 ].mean() ] # Looping on all the triangles of the envelope of the polyhedron, creating the matching tetra. # Then the volume of all the tetra are added to get the final polyhedron volume. cellVolume = 0. diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py index dd11e9db..5c0c4ca2 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py @@ -137,11 +137,6 @@ def __call__( self, ic: int ) -> int: def __action( vtkInputFile: str, options: Options ) -> Result: # Main process loads the mesh for its own use mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - if mesh is None: - setupLogger.error( f"Main process failed to load mesh from {vtkInputFile}. Aborting." ) - # Return an empty/error result or raise an exception - return Result( unsupportedStdElementsTypes=frozenset(), unsupportedPolyhedronElements=frozenset() ) - if hasattr( mesh, "GetDistinctCellTypesArray" ): cellTypesNumpy = vtk_to_numpy( mesh.GetDistinctCellTypesArray() ) cellTypes = set( cellTypesNumpy.tolist() ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py index a8e309fe..ff69cb80 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py @@ -67,7 +67,7 @@ def distanceBetweenTwoSegments( x0: numpy.ndarray, d0: numpy.ndarray, x1: numpy. # Step 3: compute t1 for point on line 1 closest to point at t0. t1: float = __divClamp( t0 * R - S1, D1 ) # Eq (10, right) sol1: numpy.ndarray = x1 + t1 * d1 # Eq (3) - t0: float = __divClamp( t1 * R + S0, D0 ) # Eq (10, left) + t0 = __divClamp( t1 * R + S0, D0 ) # Eq (10, left) sol0: numpy.ndarray = x0 + t0 * d0 # Eq (4) return sol0, sol1 @@ -105,9 +105,9 @@ def __computeNodesToTriangleDistance( # If so, let's take the closest point. point: int = -1 if numpy.all( tri1Proj > 0 ): - point = numpy.argmin( tri1Proj ) + point = int( numpy.argmin( tri1Proj ) ) elif numpy.all( tri1Proj < 0 ): - point = numpy.argmax( tri1Proj ) + point = int( numpy.argmax( tri1Proj ) ) # So if `tri1` is actually "on one side", # point `tri1[point]` is candidate to be the closest point. @@ -120,7 +120,7 @@ def __computeNodesToTriangleDistance( # It is! sol0 = tri1[ point ] sol1 = tri1[ point ] + ( tri1Proj[ point ] / tri0NormalNorm ) * tri0Normal - return norm( sol1 - sol0 ), sol0, sol1, areDisjoint + return float( norm( sol1 - sol0 ) ), sol0, sol1, areDisjoint return None, None, None, areDisjoint @@ -183,12 +183,12 @@ def distanceBetweenTwoTriangles( tri0: numpy.ndarray, areDisjoint = True # No edge pair contained the closest points. # Checking the node/face situation. - distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri0, edges0, tri1 ) + distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri0, edges0, tri1 ) # type: ignore[ assignment ] if distance: return distance, sol0, sol1 areDisjoint = areDisjoint or areDisjointTmp - distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri1, edges1, tri0 ) + distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri1, edges1, tri0 ) # type: ignore[ assignment ] if distance: return distance, sol0, sol1 areDisjoint = areDisjoint or areDisjointTmp diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py index 3093d96a..4a637aae 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py @@ -170,26 +170,26 @@ def buildFaceToFaceConnectivityThroughEdges( faceStream: FaceStream, addCompatib edgesToFaceIndices: dict[ frozenset[ int ], list[ int ] ] = defaultdict( list ) for faceIndex, faceNodes in enumerate( faceStream.faceNodes ): # Each edge is defined by two nodes. We do a small trick to loop on consecutive points. - faceIndices: tuple[ int, int ] - for faceIndices in zip( faceNodes, faceNodes[ 1: ] + ( faceNodes[ 0 ], ) ): - edgesToFaceIndices[ frozenset( faceIndices ) ].append( faceIndex ) + faceNodesTuple = tuple( faceNodes ) + for edgeNodes in zip( faceNodesTuple, faceNodesTuple[ 1: ] + ( faceNodesTuple[ 0 ], ) ): + edgesToFaceIndices[ frozenset( edgeNodes ) ].append( faceIndex ) # We are doing here some small validations w.r.t. the connections of the faces # which may only make sense in the context of numerical simulations. # As such, an error will be thrown in case the polyhedron is not closed. # So there may be a lack of absolute genericity, and the code may evolve if needed. - for faceIndices in edgesToFaceIndices.values(): - assert len( faceIndices ) == 2 + for connectedFaces in edgesToFaceIndices.values(): + assert len( connectedFaces ) == 2 # Computing the graph degree for validation degrees: dict[ int, int ] = defaultdict( int ) - for faceIndices in edgesToFaceIndices.values(): - for faceIndex in faceIndices: + for connectedFaces in edgesToFaceIndices.values(): + for faceIndex in connectedFaces: degrees[ faceIndex ] += 1 for faceIndex, degree in degrees.items(): assert len( faceStream[ faceIndex ] ) == degree # Validation that there is one unique edge connecting two faces. faceIndicesToEdgeIndex = defaultdict( list ) - for edgeIndex, faceIndices in edgesToFaceIndices.items(): - faceIndicesToEdgeIndex[ frozenset( faceIndices ) ].append( edgeIndex ) + for edgeIndex, connectedFaces in edgesToFaceIndices.items(): + faceIndicesToEdgeIndex[ frozenset( connectedFaces ) ].append( edgeIndex ) for edgeIndices in faceIndicesToEdgeIndex.values(): assert len( edgeIndices ) == 1 # Connecting the faces. Neighbor faces with consistent normals (i.e. facing both inward or outward) @@ -198,10 +198,10 @@ def buildFaceToFaceConnectivityThroughEdges( faceStream: FaceStream, addCompatib # will consistently point outward. graph = networkx.Graph() graph.add_nodes_from( range( faceStream.numFaces ) ) - for edge, faceIndices in edgesToFaceIndices.items(): - faceIndex0, faceIndex1 = faceIndices - faceNodes0 = faceStream[ faceIndex0 ] + ( faceStream[ faceIndex0 ][ 0 ], ) - faceNodes1 = faceStream[ faceIndex1 ] + ( faceStream[ faceIndex1 ][ 0 ], ) + for edge, connectedFaces in edgesToFaceIndices.items(): + faceIndex0, faceIndex1 = connectedFaces + faceNodes0 = tuple( faceStream[ faceIndex0 ] ) + ( faceStream[ faceIndex0 ][ 0 ], ) + faceNodes1 = tuple( faceStream[ faceIndex1 ] ) + ( faceStream[ faceIndex1 ][ 0 ], ) node0, node1 = edge order0 = 1 if faceNodes0[ faceNodes0.index( node0 ) + 1 ] == node1 else -1 order1 = 1 if faceNodes1[ faceNodes1.index( node0 ) + 1 ] == node1 else -1 diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index adccf151..31e096f1 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -7,7 +7,6 @@ from geos.mesh_doctor.parsing.cliParsing import parseCommaSeparatedString, setupLogger -# --- Data Structure for Check Features --- @dataclass( frozen=True ) class CheckFeature: """A container for a check's configuration and associated classes.""" @@ -18,7 +17,6 @@ class CheckFeature: display: Type[ Any ] -# --- Argument Parser Constants --- CHECKS_TO_DO_ARG = "checksToPerform" PARAMETERS_ARG = "setParameters" @@ -34,23 +32,23 @@ def _generateParametersHelp( orderedCheckNames: list[ str ], checkFeaturesConfig return helpText -def getOptionsUsedMessage( optionsUsed: dataclass ) -> str: +def getOptionsUsedMessage( optionsUsed: object ) -> str: """Dynamically generates the description of every parameter used when loaching a check. Args: - optionsUsed (dataclass) + optionsUsed (dataclass object): The options dataclass used for a specific check. Returns: str: A message like "Parameters used: ( param1:value1 param2:value2 )" for as many paramters found. """ optionsMsg: str = "Parameters used: (" - for attrName in optionsUsed.__dataclass_fields__: - attrValue = getattr( optionsUsed, attrName ) - optionsMsg += f" {attrName} = {attrValue}" + if hasattr(optionsUsed, "__dataclass_fields__"): + for attrName in optionsUsed.__dataclass_fields__: + attrValue = getattr( optionsUsed, attrName ) + optionsMsg += f" {attrName} = {attrValue}" return optionsMsg + " )." -# --- Generic Argument Parser Setup --- def fillSubparser( subparsers: argparse._SubParsersAction, subparserName: str, helpMessage: str, orderedCheckNames: list[ str ], checkFeaturesConfig: dict[ str, CheckFeature ] ) -> None: """ @@ -92,18 +90,18 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], Converts parsed command-line arguments into an AllChecksOptions object based on the provided configuration. """ # 1. Determine which checks to perform - if not parsedArgs[ CHECKS_TO_DO_ARG ]: # handles default and if user explicitly provides --checksToPerform "" - finalSelectedCheckNames: list[ str ] = deepcopy( orderedCheckNames ) - setupLogger.info( "All configured checks will be performed by default." ) + checksToDo = getattr( parsedArgs, CHECKS_TO_DO_ARG ) + if not checksToDo: + finalSelectedCheckNames: list[str] = deepcopy( orderedCheckNames ) + setupLogger.info("All configured checks will be performed by default.") else: - userChecks = parseCommaSeparatedString( parsedArgs[ CHECKS_TO_DO_ARG ] ) - finalSelectedCheckNames = list() + userChecks = parseCommaSeparatedString( checksToDo ) + finalSelectedCheckNames = [] for name in userChecks: if name not in checkFeaturesConfig: setupLogger.warning( f"Check '{name}' does not exist. Choose from: {orderedCheckNames}." ) elif name not in finalSelectedCheckNames: finalSelectedCheckNames.append( name ) - if not finalSelectedCheckNames: raise ValueError( "No valid checks were selected. No operations will be configured." ) @@ -111,10 +109,11 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], defaultParams = { name: feature.defaultParams.copy() for name, feature in checkFeaturesConfig.items() } finalCheckParams = { name: defaultParams[ name ] for name in finalSelectedCheckNames } - if not parsedArgs[ PARAMETERS_ARG ]: # handles default and if user explicitly provides --setParameters "" + parametersArg = getattr( parsedArgs, PARAMETERS_ARG ) + if not parametersArg: setupLogger.info( "Default configuration of parameters adopted for every check to perform." ) else: - setParameters = parseCommaSeparatedString( parsedArgs[ PARAMETERS_ARG ] ) + setParameters = parseCommaSeparatedString( parametersArg ) for param in setParameters: if ':' not in param: setupLogger.warning( f"Parameter '{param}' is not in 'name:value' format. Skipping." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index b684a2cc..559dc537 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -29,9 +29,9 @@ def displayResults( options: Options, result: Result ): for bucket in result.nodesBuckets: for node in bucket: allCollocatedNodes.append( node ) - allCollocatedNodes: frozenset[ int ] = frozenset( allCollocatedNodes ) # Surely useless - if allCollocatedNodes: - setupLogger.results( f"You have {len( allCollocatedNodes )} collocated nodes." ) + allCollocatedNodesUnique: frozenset[ int ] = frozenset( allCollocatedNodes ) # Surely useless + if allCollocatedNodesUnique: + setupLogger.results( f"You have {len( allCollocatedNodesUnique )} collocated nodes." ) setupLogger.results( "Here are all the buckets of collocated nodes." ) tmp: list[ str ] = [] for bucket in result.nodesBuckets: @@ -41,8 +41,8 @@ def displayResults( options: Options, result: Result ): setupLogger.results( "You have no collocated node." ) if result.wrongSupportElements: - tmp: str = ", ".join( map( str, result.wrongSupportElements ) ) + wsElements: str = ", ".join( map( str, result.wrongSupportElements ) ) setupLogger.results( f"You have {len(result.wrongSupportElements)} elements with duplicated support nodes.\n" + - tmp ) + wsElements ) else: setupLogger.results( "You have no element with duplicated support nodes." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py index 18322dfc..87413cdf 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py @@ -93,8 +93,9 @@ def convert( parsedOptions ) -> Options: ] fractureNames: list[ str ] = [ "fracture_" + frac.replace( ",", "_" ) + ".vtu" for frac in perFracture ] fracturesOutputDir: str = parsedOptions[ __FRACTURES_OUTPUT_DIR ] - fracturesDataMode: str = parsedOptions[ __FRACTURES_DATA_MODE ] == __FRACTURES_DATA_MODE_DEFAULT - allFracturesVtkOutput: list[ VtkOutput ] = buildAllFracturesVtkOutput( fracturesOutputDir, fracturesDataMode, + fracturesDataMode: str = parsedOptions[ __FRACTURES_DATA_MODE ] + fracturesDataModeResult: bool = fracturesDataMode == __FRACTURES_DATA_MODE_DEFAULT + allFracturesVtkOutput: list[ VtkOutput ] = buildAllFracturesVtkOutput( fracturesOutputDir, fracturesDataModeResult, meshVtkOutput, fractureNames ) return Options( policy=policy, field=field, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index bdeb6a0f..d2a34808 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -50,6 +50,6 @@ def displayResults( options: Options, result: Result ): nonConformalCells: list[ int ] = [] for i, j in result.nonConformalCells: nonConformalCells += i, j - nonConformalCells: frozenset[ int ] = frozenset( nonConformalCells ) - setupLogger.results( f"You have {len( nonConformalCells )} non conformal cells." ) - setupLogger.results( f"{', '.join( map( str, sorted( nonConformalCells ) ) )}" ) + nonConformalCellsUnique: frozenset[ int ] = frozenset( nonConformalCells ) + setupLogger.results( f"You have {len( nonConformalCellsUnique )} non conformal cells." ) + setupLogger.results( f"{', '.join( map( str, sorted( nonConformalCellsUnique ) ) )}" ) diff --git a/mesh-doctor/src/geos/mesh_doctor/register.py b/mesh-doctor/src/geos/mesh_doctor/register.py index 4469b885..43d08d60 100644 --- a/mesh-doctor/src/geos/mesh_doctor/register.py +++ b/mesh-doctor/src/geos/mesh_doctor/register.py @@ -5,16 +5,16 @@ from geos.mesh_doctor.parsing import ActionHelper, cliParsing from geos.mesh_doctor.parsing.cliParsing import setupLogger -__HELPERS: dict[ str, Callable[ [ None ], ActionHelper ] ] = dict() -__ACTIONS: dict[ str, Callable[ [ None ], Any ] ] = dict() +__HELPERS: dict[ str, str ] = dict() +__ACTIONS: dict[ str, str ] = dict() -def __loadModuleAction( moduleName: str, actionFct="action" ): +def __loadModuleAction( moduleName: str, actionFct: str = "action" ) -> Callable[ [ str, Any ], Any ]: module = importlib.import_module( "geos.mesh_doctor.actions." + moduleName ) return getattr( module, actionFct ) -def __loadModuleActionHelper( moduleName: str, parsingFctSuffix="Parsing" ): +def __loadModuleActionHelper( moduleName: str, parsingFctSuffix: str = "Parsing" ) -> ActionHelper: module = importlib.import_module( "geos.mesh_doctor.parsing." + moduleName + parsingFctSuffix ) return ActionHelper( fillSubparser=module.fillSubparser, convert=module.convert, @@ -30,9 +30,9 @@ def __loadActions() -> dict[ str, Callable[ [ str, Any ], Any ] ]: dict[ str, Callable[ [ str, Any ], Any ] ]: The actions. """ loadedActions: dict[ str, Callable[ [ str, Any ], Any ] ] = dict() - for actionName, actionProvider in __ACTIONS.items(): + for actionName, moduleName in __ACTIONS.items(): try: - loadedActions[ actionName ] = actionProvider() + loadedActions[ actionName ] = __loadModuleAction( moduleName ) setupLogger.debug( f"Action \"{actionName}\" is loaded." ) except Exception as e: setupLogger.warning( f"Could not load module \"{actionName}\": {e}" ) @@ -50,21 +50,23 @@ def registerParsingActions( parser = cliParsing.initParser() subparsers = parser.add_subparsers( help="Modules", dest="subparsers" ) - def closureTrick( cn: str ): - __HELPERS[ actionName ] = lambda: __loadModuleActionHelper( cn ) - __ACTIONS[ actionName ] = lambda: __loadModuleAction( cn ) - # Register the modules to load here. for actionName in ( parsing.ALL_CHECKS, parsing.COLLOCATES_NODES, parsing.ELEMENT_VOLUMES, parsing.FIX_ELEMENTS_ORDERINGS, parsing.GENERATE_CUBE, parsing.GENERATE_FRACTURES, parsing.GENERATE_GLOBAL_IDS, parsing.MAIN_CHECKS, parsing.NON_CONFORMAL, parsing.SELF_INTERSECTING_ELEMENTS, parsing.SUPPORTED_ELEMENTS ): - closureTrick( actionName ) + __HELPERS[ actionName ] = actionName + __ACTIONS[ actionName ] = actionName + loadedActions: dict[ str, Callable[ [ str, Any ], Any ] ] = __loadActions() loadedActionsHelpers: dict[ str, ActionHelper ] = dict() for actionName in loadedActions.keys(): - h = __HELPERS[ actionName ]() - h.fillSubparser( subparsers ) - loadedActionsHelpers[ actionName ] = h - setupLogger.debug( f"Parsing for action \"{actionName}\" is loaded." ) + moduleName = __HELPERS[ actionName ] + try: + h = __loadModuleActionHelper( moduleName ) + h.fillSubparser( subparsers ) + loadedActionsHelpers[ actionName ] = h + setupLogger.debug( f"Parsing for action \"{actionName}\" is loaded." ) + except Exception as e: + setupLogger.warning( f"Could not load parsing for action \"{actionName}\": {e}" ) return parser, loadedActions, loadedActionsHelpers diff --git a/mesh-doctor/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py index 6df0cc3f..b32aa5fa 100644 --- a/mesh-doctor/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -87,7 +87,7 @@ def test_fillSubparser( checkFeaturesConfig, orderedCheckNames ): @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames assert len( options.checksOptions ) == 2 @@ -97,7 +97,7 @@ def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckName @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == [ "check1" ] assert len( options.checksOptions ) == 1 @@ -107,7 +107,7 @@ def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNam @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames assert options.checksOptions[ "check1" ].param1 == 10.5 @@ -118,7 +118,7 @@ def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNam @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid parameter should be skipped, but the valid one applied assert options.checksOptions[ "check1" ].param1 == 1.0 # Default maintained @@ -127,7 +127,7 @@ def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedC @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid check should be skipped assert options.checksToPerform == [ "check1" ] @@ -137,7 +137,7 @@ def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckN @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertWithAllInvalidChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): - parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } + parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } ) # Should raise ValueError since no valid checks were selected with pytest.raises( ValueError, match="No valid checks were selected" ): convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) From 2b31534e06b371728005f7b452a7137a04f51836 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Tue, 18 Nov 2025 14:36:37 -0800 Subject: [PATCH 03/15] Update files for ruff checking --- mesh-doctor/src/geos/mesh_doctor/__init__.py | 2 +- .../src/geos/mesh_doctor/actions/allChecks.py | 15 +- .../mesh_doctor/actions/checkFractures.py | 90 ++++-- .../mesh_doctor/actions/collocatedNodes.py | 73 +++-- .../mesh_doctor/actions/elementVolumes.py | 102 +++++-- .../actions/fixElementsOrderings.py | 30 +- .../geos/mesh_doctor/actions/generateCube.py | 200 ++++++++++---- .../mesh_doctor/actions/generateFractures.py | 164 +++++++---- .../mesh_doctor/actions/generateGlobalIds.py | 36 ++- .../geos/mesh_doctor/actions/mainChecks.py | 2 +- .../geos/mesh_doctor/actions/nonConformal.py | 256 +++++++++++------- .../geos/mesh_doctor/actions/reorientMesh.py | 3 +- .../actions/selfIntersectingElements.py | 137 ++++++---- .../mesh_doctor/actions/supportedElements.py | 160 ++++++----- .../mesh_doctor/actions/triangleDistance.py | 18 +- .../geos/mesh_doctor/actions/vtkPolyhedron.py | 25 +- .../src/geos/mesh_doctor/meshDoctor.py | 3 +- .../parsing/_sharedChecksParsingLogic.py | 28 +- .../mesh_doctor/parsing/allChecksParsing.py | 2 +- .../parsing/checkFracturesParsing.py | 2 +- .../geos/mesh_doctor/parsing/cliParsing.py | 15 +- .../parsing/collocatedNodesParsing.py | 68 +++-- .../parsing/elementVolumesParsing.py | 47 +++- .../parsing/fixElementsOrderingsParsing.py | 21 +- .../parsing/generateCubeParsing.py | 44 ++- .../parsing/generateFracturesParsing.py | 66 ++++- .../parsing/generateGlobalIdsParsing.py | 35 ++- .../mesh_doctor/parsing/mainChecksParsing.py | 2 +- .../parsing/nonConformalParsing.py | 54 +++- .../selfIntersectingElementsParsing.py | 76 +++++- .../parsing/supportedElementsParsing.py | 63 ++++- .../mesh_doctor/parsing/vtkOutputParsing.py | 57 +++- mesh-doctor/src/geos/mesh_doctor/register.py | 11 +- mesh-doctor/tests/test_collocatedNodes.py | 6 +- mesh-doctor/tests/test_elementVolumes.py | 6 +- mesh-doctor/tests/test_generateCube.py | 4 +- mesh-doctor/tests/test_generateGlobalIds.py | 4 +- mesh-doctor/tests/test_nonConformal.py | 12 +- .../tests/test_selfIntersectingElements.py | 8 +- mesh-doctor/tests/test_supportedElements.py | 2 +- 40 files changed, 1380 insertions(+), 569 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/__init__.py b/mesh-doctor/src/geos/mesh_doctor/__init__.py index b1cfe267..b7db2541 100644 --- a/mesh-doctor/src/geos/mesh_doctor/__init__.py +++ b/mesh-doctor/src/geos/mesh_doctor/__init__.py @@ -1 +1 @@ -# Empty \ No newline at end of file +# Empty diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py index 9b3a4dc7..3eca1b97 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py @@ -16,12 +16,21 @@ class Result: checkResults: dict[ str, Any ] -def action( vtkInputFile: str, options: Options ) -> Result: - checkResults: dict[ str, Any ] = dict() +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs all checks available on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of all checks performed. + """ + checkResults: dict[ str, Any ] = {} for checkName in options.checksToPerform: checkAction = __loadModuleAction( checkName ) setupLogger.info( f"Performing check '{checkName}'." ) option = options.checksOptions[ checkName ] - checkResult = checkAction( vtkInputFile, option ) + checkResult = checkAction( vtuInputFile, option ) checkResults[ checkName ] = checkResult return Result( checkResults=checkResults ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py index 23133a84..de249588 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py @@ -3,13 +3,13 @@ import numpy.typing as npt from tqdm import tqdm from typing import Collection, Iterable, Sequence -from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, vtkCell +from vtkmodules.vtkCommonDataModel import vtkCell, vtkMultiBlockDataSet, vtkUnstructuredGrid from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkIOXML import vtkXMLMultiBlockDataReader from vtkmodules.util.numpy_support import vtk_to_numpy +from geos.mesh.utils.genericHelpers import vtkIter from geos.mesh_doctor.actions.generateFractures import Coordinates3D from geos.mesh_doctor.parsing.cliParsing import setupLogger -from geos.mesh.utils.genericHelpers import vtkIter @dataclass( frozen=True ) @@ -28,18 +28,28 @@ class Result: errors: Sequence[ tuple[ int, int, int ] ] -def __readMultiblock( vtkInputFile: str, matrixName: str, +def __readMultiblock( vtuInputFile: str, matrixName: str, fractureName: str ) -> tuple[ vtkUnstructuredGrid, vtkUnstructuredGrid ]: + """Reads a multiblock VTU file and extracts the matrix and fracture meshes. + + Args: + vtuInputFile (str): The input VTU file path. + matrixName (str): The name of the matrix mesh block. + fractureName (str): The name of the fracture mesh block. + + Returns: + tuple[ vtkUnstructuredGrid, vtkUnstructuredGrid ]: The matrix and fracture meshes. + """ reader = vtkXMLMultiBlockDataReader() - reader.SetFileName( vtkInputFile ) + reader.SetFileName( vtuInputFile ) reader.Update() - multiBlock = reader.GetOutput() + multiBlock: vtkMultiBlockDataSet = reader.GetOutput() for b in range( multiBlock.GetNumberOfBlocks() ): blockName: str = multiBlock.GetMetaData( b ).Get( multiBlock.NAME() ) if blockName == matrixName: - matrix: vtkUnstructuredGrid = multiBlock.GetBlock( b ) + matrix = vtkUnstructuredGrid.SafeDownCast( multiBlock.GetBlock( b ) ) if blockName == fractureName: - fracture: vtkUnstructuredGrid = multiBlock.GetBlock( b ) + fracture = vtkUnstructuredGrid.SafeDownCast( multiBlock.GetBlock( b ) ) assert matrix and fracture return matrix, fracture @@ -64,17 +74,36 @@ def __checkCollocatedNodesPositions( matrixPoints: npt.NDArray, fracturePoints: npt.NDArray, g2l: npt.NDArray[ np.int64 ], collocatedNodes: Iterable[ Iterable[ int ] ] ) -> Collection[ tuple[ int, Iterable[ int ], Iterable[ Coordinates3D ] ] ]: + """Check that the collocated nodes are indeed collocated in space. + + Args: + matrixPoints (npt.NDArray): The points of the matrix mesh. + fracturePoints (npt.NDArray): The points of the fracture mesh. + g2l (npt.NDArray[ np.int64 ]): Mapping from global to local indices in the matrix mesh. + collocatedNodes (Iterable[ Iterable[ int ] ]): The collocated nodes information. + + Returns: + Collection[ tuple[ int, Iterable[ int ], Iterable[ Coordinates3D ] ] ]: A collection of issues found. + """ issues = [] for li, bucket in enumerate( collocatedNodes ): - matrix_nodes = ( fracturePoints[ li ], ) + tuple( map( lambda gi: matrixPoints[ g2l[ gi ] ], bucket ) ) + matrix_nodes = ( fracturePoints[ li ], ) + tuple( matrixPoints[ g2l[ gi ] ] for gi in bucket ) m = np.array( matrix_nodes ) rank: int = np.linalg.matrix_rank( m ) if rank > 1: - issues.append( ( li, bucket, tuple( map( lambda gi: matrixPoints[ g2l[ gi ] ], bucket ) ) ) ) + issues.append( ( li, bucket, tuple( matrixPoints[ g2l[ gi ] ] for gi in bucket ) ) ) return issues -def myIter( ccc ): +def myIter( ccc: tuple ) -> Iterable[ tuple ]: + """Recursively generates all combinations from nested sequences. + + Args: + ccc (tuple): A tuple of sequences to generate combinations from. + + Yields: + Iterable[ tuple ]: All possible combinations, one element from each sequence. + """ car, cdr = ccc[ 0 ], ccc[ 1: ] for i in car: if cdr: @@ -85,7 +114,15 @@ def myIter( ccc ): def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid, g2l: npt.NDArray[ np.int64 ], - collocatedNodes: Sequence[ Iterable[ int ] ] ): + collocatedNodes: Sequence[ Iterable[ int ] ] ) -> None: + """Check that for each pair of collocated nodes, the corresponding fracture faces have exactly two neighbor cells. + + Args: + matrix (vtkUnstructuredGrid): The matrix mesh. + fracture (vtkUnstructuredGrid): The fracture mesh. + g2l (npt.NDArray[ np.int64 ]): Mapping from global to local indices in the matrix mesh. + collocatedNodes (Sequence[ Iterable[ int ] ]): The collocated nodes information. + """ fractureNodes: set[ int ] = set() for bucket in collocatedNodes: for gi in bucket: @@ -121,8 +158,17 @@ def __checkNeighbors( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid f" {found}) for collocated nodes {cns}." ) -def __action( vtkInputFile: str, options: Options ) -> Result: - matrix, fracture = __readMultiblock( vtkInputFile, options.matrixName, options.fractureName ) +def meshAction( matrix: vtkUnstructuredGrid, fracture: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the check of the fractures and collocated nodes generated. + + Args: + matrix (vtkUnstructuredGrid): The matrix mesh. + fracture (vtkUnstructuredGrid): The fracture mesh. + options (Options): The options for processing. + + Returns: + Result: The result of the self intersecting elements check. + """ matrixPoints: vtkPoints = matrix.GetPoints() fracturePoints: vtkPoints = fracture.GetPoints() @@ -152,9 +198,15 @@ def __action( vtkInputFile: str, options: Options ) -> Result: return Result( errors=errors ) -def action( vtkInputFile: str, options: Options ) -> Result: - try: - return __action( vtkInputFile, options ) - except BaseException as e: - setupLogger.error( e ) - return Result( errors=() ) +def action( vtkMultiBlockInputFile: str, options: Options ) -> Result: + """Reads a vtkMultiblock and performs the check of the fractures and collocated nodes generated. + + Args: + vtkMultiBlockInputFile (str): The path to the input vtkMultiBlock file. + options (Options): The options for processing. + + Returns: + Result: The result of the check of the fractures and collocated nodes generated. + """ + matrix, fracture = __readMultiblock( vtkMultiBlockInputFile, options.matrixName, options.fractureName ) + return meshAction( matrix, fracture, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py index 0bfab5da..d715c35e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py @@ -3,7 +3,7 @@ import numpy from typing import Collection, Iterable from vtkmodules.vtkCommonCore import reference, vtkPoints -from vtkmodules.vtkCommonDataModel import vtkIncrementalOctreePointLocator, vtkUnstructuredGrid +from vtkmodules.vtkCommonDataModel import vtkCell, vtkIncrementalOctreePointLocator, vtkUnstructuredGrid from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid @@ -19,18 +19,26 @@ class Result: wrongSupportElements: Collection[ int ] # Element indices with support node indices appearing more than once. -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: - points: vtkPoints = mesh.GetPoints() +def findCollocatedNodesBuckets( mesh: vtkUnstructuredGrid, tolerance: float ) -> list[ tuple[ int, ... ] ]: + """Check all the nodes of a mesh and returns every bucket of nodes that are collocated within a tolerance. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + tolerance (float): The distance tolerance within which nodes are considered collocated. + Returns: + list[ tuple[ int, ... ] ]: A list of tuples, each containing indices of nodes that are collocated. + """ + points: vtkPoints = mesh.GetPoints() locator = vtkIncrementalOctreePointLocator() - locator.SetTolerance( options.tolerance ) + locator.SetTolerance( tolerance ) output = vtkPoints() locator.InitPointInsertion( output, points.GetBounds() ) # original ids to/from filtered ids. filteredToOriginal = numpy.ones( points.GetNumberOfPoints(), dtype=int ) * -1 - rejectedPoints = defaultdict( list ) + rejectedPoints: defaultdict[ int, list[ int ] ] = defaultdict( list ) pointId = reference( 0 ) for i in range( points.GetNumberOfPoints() ): isInserted = locator.InsertUniquePoint( points.GetPoint( i ), pointId ) # type: ignore[arg-type] @@ -42,28 +50,61 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: f"Point {i} at {points.GetPoint(i)} has been rejected, " + f"point {filteredToOriginal[pointId.get()]} is already inserted.", # type: ignore[misc, call-overload] ) - rejectedPoints[ pointId.get() ].append( i ) # type: ignore[misc] + rejectedPoints[ pointId.get() ].append( i ) # type: ignore[misc, index] else: # If it's inserted, `pointId` contains the new index in the destination array. # We store this information to be able to connect the source and destination arrays. # originalToFiltered[i] = pointId.get() filteredToOriginal[ pointId.get() ] = i # type: ignore[misc, call-overload] - tmp = [] + collocatedNodesBuckets: list[ tuple[ int, ... ] ] = [] for n, ns in rejectedPoints.items(): - tmp.append( ( n, *ns ) ) + collocatedNodesBuckets.append( ( n, *ns ) ) + return collocatedNodesBuckets + + +def findWrongSupportElements( mesh: vtkUnstructuredGrid ) -> list[ int ]: + """Checking that the support node indices appear only once per element. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. - # Checking that the support node indices appear only once per element. - wrongSupportElements = [] + Returns: + list[ int ]: A list of cell indices with support node indices appearing more than once. + """ + wrongSupportElements: list[ int ] = [] for c in range( mesh.GetNumberOfCells() ): - cell = mesh.GetCell( c ) - numPointsPerCell = cell.GetNumberOfPoints() + cell: vtkCell = mesh.GetCell( c ) + numPointsPerCell: int = cell.GetNumberOfPoints() if len( { cell.GetPointId( i ) for i in range( numPointsPerCell ) } ) != numPointsPerCell: wrongSupportElements.append( c ) + return wrongSupportElements + + +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the collocated nodes check on a vtkUnstructuredGrid. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The options for processing. + + Returns: + Result: The result of the collocated nodes check. + """ + nodesBuckets = findCollocatedNodesBuckets( mesh, options.tolerance ) + wrongSupportElements = findWrongSupportElements( mesh ) + return Result( nodesBuckets=nodesBuckets, wrongSupportElements=wrongSupportElements ) # type: ignore[arg-type] + - return Result( nodesBuckets=tmp, wrongSupportElements=wrongSupportElements ) # type: ignore[arg-type] +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the element volumes check on it. + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. -def action( vtkInputFile: str, options: Options ) -> Result: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) + Returns: + Result: The result of the element volumes check. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py index 8c3c64b3..a27c3f3a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py @@ -1,5 +1,8 @@ from dataclasses import dataclass import uuid +import numpy as np +from numpy.typing import NDArray +from vtkmodules.vtkCommonCore import vtkDoubleArray from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_TETRA, VTK_WEDGE from vtkmodules.vtkFiltersVerdict import vtkCellSizeFilter, vtkMeshQuality from vtkmodules.util.numpy_support import vtk_to_numpy @@ -17,23 +20,19 @@ class Result: elementVolumes: list[ tuple[ int, float ] ] -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: - cs = vtkCellSizeFilter() +SUPPORTED_TYPES = [ VTK_HEXAHEDRON, VTK_TETRA ] - cs.ComputeAreaOff() - cs.ComputeLengthOff() - cs.ComputeSumOff() - cs.ComputeVertexCountOff() - cs.ComputeVolumeOn() - volumeArrayName = "__MESH_DOCTOR_VOLUME-" + str( uuid.uuid4() ) # Making the name unique - cs.SetVolumeArrayName( volumeArrayName ) - cs.SetInputData( mesh ) - cs.Update() +def getMeshQuality( mesh: vtkUnstructuredGrid ) -> vtkDoubleArray: + """Get the quality of the mesh. - mq = vtkMeshQuality() - SUPPORTED_TYPES = [ VTK_HEXAHEDRON, VTK_TETRA ] + Args: + mesh (vtkUnstructuredGrid): The input mesh. + Returns: + vtkDoubleArray: The array containing the quality of each cell. + """ + mq = vtkMeshQuality() mq.SetTetQualityMeasureToVolume() mq.SetHexQualityMeasureToVolume() if hasattr( mq, "SetPyramidQualityMeasureToVolume" ): # This feature is quite recent @@ -42,22 +41,62 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: mq.SetWedgeQualityMeasureToVolume() SUPPORTED_TYPES.append( VTK_WEDGE ) else: - setupLogger.warning( - "Your \"pyvtk\" version does not bring pyramid nor wedge support with vtkMeshQuality. Using the fallback solution." - ) - + setupLogger.warning( "Your \"pyvtk\" version does not bring pyramid nor wedge support with vtkMeshQuality." + " Using the fallback solution." ) mq.SetInputData( mesh ) mq.Update() - volume = cs.GetOutput().GetCellData().GetArray( volumeArrayName ) - quality = mq.GetOutput().GetCellData().GetArray( "Quality" ) # Name is imposed by vtk. + return mq.GetOutput().GetCellData().GetArray( "Quality" ) # Name is imposed by vtk. + + +def getMeshVolume( mesh: vtkUnstructuredGrid ) -> vtkDoubleArray: + """Get the volume of the mesh. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + + Returns: + vtkDataArray: The array containing the volume of each cell. + """ + cs = vtkCellSizeFilter() + cs.ComputeAreaOff() + cs.ComputeLengthOff() + cs.ComputeSumOff() + cs.ComputeVertexCountOff() + cs.ComputeVolumeOn() + + volumeArrayName: str = "__MESH_DOCTOR_VOLUME-" + str( uuid.uuid4() ) # Making the name unique + cs.SetVolumeArrayName( volumeArrayName ) + cs.SetInputData( mesh ) + cs.Update() - assert volume is not None - assert quality is not None - volume = vtk_to_numpy( volume ) - quality = vtk_to_numpy( quality ) + return cs.GetOutput().GetCellData().GetArray( volumeArrayName ) + + +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the supported elements check on a vtkUnstructuredGrid. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The options for processing. + + Returns: + Result: The result of the supported elements check. + """ + volume: vtkDoubleArray = getMeshVolume( mesh ) + if not volume: + setupLogger.error( "Volume computation failed." ) + raise RuntimeError( "Volume computation failed." ) + + quality: vtkDoubleArray = getMeshQuality( mesh ) + if not quality: + setupLogger.error( "Quality computation failed." ) + raise RuntimeError( "Quality computation failed." ) + + volumeArray: NDArray[ np.floating ] = vtk_to_numpy( volume ) + qualityArray: NDArray[ np.floating ] = vtk_to_numpy( quality ) smallVolumes: list[ tuple[ int, float ] ] = [] - for i, pack in enumerate( zip( volume, quality ) ): + for i, pack in enumerate( zip( volumeArray, qualityArray ) ): v, q = pack vol = q if mesh.GetCellType( i ) in SUPPORTED_TYPES else v if vol < options.minVolume: @@ -65,6 +104,15 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: return Result( elementVolumes=smallVolumes ) -def action( vtkInputFile: str, options: Options ) -> Result: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the element volumes check on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the element volumes check. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py index 1c00862f..8a9b7829 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py @@ -17,7 +17,16 @@ class Result: unchangedCellTypes: frozenset[ int ] -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the fix elements orderings on a vtkUnstructuredGrid. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to reorder. + options (Options): The options for processing. + + Returns: + Result: The result of the fix elements orderings. + """ # The vtk cell type is an int and will be the key of the following mapping, # that will point to the relevant permutation. cellTypeToOrdering: dict[ int, list[ int ] ] = options.cellTypeToOrdering @@ -38,8 +47,8 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: supportPointIds = vtkIdList() cells.GetCellAtId( cellIdx, supportPointIds ) newSupportPointIds = [] - for i, v in enumerate( newOrdering ): - newSupportPointIds.append( supportPointIds.GetId( newOrdering[ i ] ) ) + for orderingId in newOrdering: + newSupportPointIds.append( supportPointIds.GetId( orderingId ) ) cells.ReplaceCellAtId( cellIdx, toVtkIdList( newSupportPointIds ) ) else: unchangedCellTypes.add( cellType ) @@ -48,6 +57,15 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: unchangedCellTypes=frozenset( unchangedCellTypes ) ) -def action( vtkInputFile: str, options: Options ) -> Result: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the fix elements orderings on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the fix elements orderings. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py index 55c564d5..14ed28c4 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py @@ -1,13 +1,15 @@ from dataclasses import dataclass -import numpy -from typing import Iterable, Sequence, Union +import numpy as np +import numpy.typing as npt +from typing import Iterable, Sequence from vtkmodules.util.numpy_support import numpy_to_vtk from vtkmodules.vtkCommonCore import vtkPoints -from vtkmodules.vtkCommonDataModel import ( vtkCellArray, vtkCellData, vtkHexahedron, vtkPointData, vtkRectilinearGrid, - vtkUnstructuredGrid, VTK_HEXAHEDRON ) -from geos.mesh_doctor.actions.generateGlobalIds import __buildGlobalIds -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from vtkmodules.vtkCommonDataModel import ( vtkCellArray, vtkHexahedron, vtkRectilinearGrid, vtkUnstructuredGrid, + VTK_HEXAHEDRON ) from geos.mesh.io.vtkIO import VtkOutput, writeMesh +from geos.mesh.utils.arrayModifiers import createConstantAttributeDataSet +from geos.mesh_doctor.actions.generateGlobalIds import buildGlobalIds +from geos.mesh_doctor.parsing.cliParsing import setupLogger @dataclass( frozen=True ) @@ -38,9 +40,79 @@ class Options: @dataclass( frozen=True ) class XYZ: - x: numpy.ndarray - y: numpy.ndarray - z: numpy.ndarray + x: npt.NDArray + y: npt.NDArray + z: npt.NDArray + + +def buildCoordinates( positions: Sequence[ float ], numElements: Sequence[ int ] ) -> npt.NDArray: + """Builds the coordinates array from the positions of each mesh block vertex and number of elements in a given direction within each mesh block. + + Args: + positions (Sequence[ float ]): The positions of each mesh block vertex. + numElements (Sequence[ int ]): The number of elements in a given direction within each mesh block. + + Returns: + npt.NDArray: The array of coordinates. + """ + result: list[ npt.NDArray ] = [] + it = zip( zip( positions, positions[ 1: ] ), numElements ) + try: + coords, n = next( it ) + while True: + start, stop = coords + endPoint: bool = False + tmp = np.linspace( start=start, stop=stop, num=n + endPoint, endpoint=endPoint ) + coords, n = next( it ) + result.append( tmp ) + except StopIteration: + endPoint = True + tmp = np.linspace( start=start, stop=stop, num=n + endPoint, endpoint=endPoint ) + result.append( tmp ) + return np.concatenate( result ) + + +def buildRectilinearGrid( x: npt.NDArray, y: npt.NDArray, z: npt.NDArray ) -> vtkUnstructuredGrid: + """Builds an unstructured vtk grid from the x,y,z coordinates. + + Args: + x (npt.NDArray): The x coordinates. + y (npt.NDArray): The y coordinates. + z (npt.NDArray): The z coordinates. + + Returns: + The unstructured mesh, even if it's topologically structured. + """ + rg = vtkRectilinearGrid() + rg.SetDimensions( len( x ), len( y ), len( z ) ) + rg.SetXCoordinates( numpy_to_vtk( x ) ) + rg.SetYCoordinates( numpy_to_vtk( y ) ) + rg.SetZCoordinates( numpy_to_vtk( z ) ) + + numPoints = rg.GetNumberOfPoints() + numCells = rg.GetNumberOfCells() + + points = vtkPoints() + points.Allocate( numPoints ) + for i in range( rg.GetNumberOfPoints() ): + points.InsertNextPoint( rg.GetPoint( i ) ) + + cellTypes = [ VTK_HEXAHEDRON ] * numCells + cells = vtkCellArray() + cells.AllocateExact( numCells, numCells * 8 ) + + m = ( 0, 1, 3, 2, 4, 5, 7, 6 ) # VTK_VOXEL and VTK_HEXAHEDRON do not share the same ordering. + for i in range( rg.GetNumberOfCells() ): + c = rg.GetCell( i ) + newCell = vtkHexahedron() + for j in range( 8 ): + newCell.GetPointIds().SetId( j, c.GetPointId( m[ j ] ) ) + cells.InsertNextCell( newCell ) + + mesh = vtkUnstructuredGrid() + mesh.SetPoints( points ) + mesh.SetCells( cellTypes, cells ) + return mesh def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: @@ -50,9 +122,9 @@ def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: xyzs (Iterable[ XYZ ]): The blocks. Returns: - vtkUnstructuredGrid: The unstructured mesh, even if it's topologically structured. + The unstructured mesh, even if it's topologically structured. """ - rgs = [] + rgs: list[ vtkRectilinearGrid ] = [] for xyz in xyzs: rg = vtkRectilinearGrid() rg.SetDimensions( len( xyz.x ), len( xyz.y ), len( xyz.z ) ) @@ -61,8 +133,8 @@ def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: rg.SetZCoordinates( numpy_to_vtk( xyz.z ) ) rgs.append( rg ) - numPoints = sum( map( lambda r: r.GetNumberOfPoints(), rgs ) ) - numCells = sum( map( lambda r: r.GetNumberOfCells(), rgs ) ) + numPoints: int = sum( r.GetNumberOfPoints() for r in rgs ) + numCells: int = sum( r.GetNumberOfCells() for r in rgs ) points = vtkPoints() points.Allocate( numPoints ) @@ -70,12 +142,12 @@ def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: for i in range( rg.GetNumberOfPoints() ): points.InsertNextPoint( rg.GetPoint( i ) ) - cellTypes = [ VTK_HEXAHEDRON ] * numCells + cellTypes: list[ int ] = [ VTK_HEXAHEDRON ] * numCells cells = vtkCellArray() cells.AllocateExact( numCells, numCells * 8 ) m = ( 0, 1, 3, 2, 4, 5, 7, 6 ) # VTK_VOXEL and VTK_HEXAHEDRON do not share the same ordering. - offset = 0 + offset: int = 0 for rg in rgs: for i in range( rg.GetNumberOfCells() ): c = rg.GetCell( i ) @@ -92,61 +164,73 @@ def buildRectilinearBlocksMesh( xyzs: Iterable[ XYZ ] ) -> vtkUnstructuredGrid: return mesh -def __addFields( mesh: vtkUnstructuredGrid, fields: Iterable[ FieldInfo ] ) -> vtkUnstructuredGrid: +def addFields( mesh: vtkUnstructuredGrid, fields: Iterable[ FieldInfo ] ) -> vtkUnstructuredGrid: + """Add constant fields to the mesh using arrayModifiers utilities. + + Each field is filled with ones (1.0) for all components. + + Args: + mesh (vtkUnstructuredGrid): The mesh to which fields will be added. + fields (Iterable[ FieldInfo ]): The fields to add. + + Returns: + vtkUnstructuredGrid: The mesh with added fields. + """ for fieldInfo in fields: - data: Union[ vtkPointData, vtkCellData ] - if fieldInfo.support == "CELLS": - data = mesh.GetCellData() - n = mesh.GetNumberOfCells() - elif fieldInfo.support == "POINTS": - data = mesh.GetPointData() - n = mesh.GetNumberOfPoints() - else: - raise ValueError( f"Unsupported field support: {fieldInfo.support}" ) - array = numpy.ones( ( n, fieldInfo.dimension ), dtype=float ) - vtkArray = numpy_to_vtk( array ) - vtkArray.SetName( fieldInfo.name ) - data.AddArray( vtkArray ) + onPoints = fieldInfo.support == "POINTS" + # Create list of values (all 1.0) for each component + listValues = [ 1.0 ] * fieldInfo.dimension + # Use the robust createConstantAttributeDataSet function + success = createConstantAttributeDataSet( dataSet=mesh, + listValues=listValues, + attributeName=fieldInfo.name, + onPoints=onPoints, + logger=setupLogger ) + if not success: + setupLogger.warning( f"Failed to create field {fieldInfo.name}" ) return mesh -def __build( options: Options ): +def buildCube( options: Options ) -> vtkUnstructuredGrid: + """Creates a cube vtkUnstructuredGrid from options given. - def buildCoordinates( positions, numElements ): - result = [] - it = zip( zip( positions, positions[ 1: ] ), numElements ) - try: - coords, n = next( it ) - while True: - start, stop = coords - endPoint = False - tmp = numpy.linspace( start=start, stop=stop, num=n + endPoint, endpoint=endPoint ) - coords, n = next( it ) - result.append( tmp ) - except StopIteration: - endPoint = True - tmp = numpy.linspace( start=start, stop=stop, num=n + endPoint, endpoint=endPoint ) - result.append( tmp ) - return numpy.concatenate( result ) + Args: + options (Options): The options for processing. + Returns: + vtkUnstructuredGrid: The created mesh. + """ x = buildCoordinates( options.xs, options.nxs ) y = buildCoordinates( options.ys, options.nys ) z = buildCoordinates( options.zs, options.nzs ) - cube = buildRectilinearBlocksMesh( ( XYZ( x, y, z ), ) ) - cube = __addFields( cube, options.fields ) - __buildGlobalIds( cube, options.generateCellsGlobalIds, options.generatePointsGlobalIds ) - return cube + mesh = buildRectilinearBlocksMesh( ( XYZ( x, y, z ), ) ) + mesh = addFields( mesh, options.fields ) + buildGlobalIds( mesh, options.generateCellsGlobalIds, options.generatePointsGlobalIds ) + return mesh + +def meshAction( options: Options ) -> Result: + """Creates a vtkUnstructuredGrid from options given. -def __action( options: Options ) -> Result: - outputMesh = __build( options ) + Args: + options (Options): The options for processing. + + Returns: + Result: The result of creation of the mesh. + """ + outputMesh = buildCube( options ) writeMesh( outputMesh, options.vtkOutput ) return Result( info=f"Mesh was written to {options.vtkOutput.output}" ) -def action( vtkInputFile: str, options: Options ) -> Result: - try: - return __action( options ) - except BaseException as e: - setupLogger.error( e ) - return Result( info="Something went wrong." ) +def action( vtuInputFile: str, options: Options ) -> Result: + """Creates a vtkUnstructuredGrid from options given. + + Args: + vtuInputFile (str): The path to the input VTU file. This is unused. + options (Options): The options for processing. + + Returns: + Result: The result of the fix elements orderings. + """ + return meshAction( options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index a602d579..18571503 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -55,6 +55,15 @@ class FractureInfo: def buildNodeToCells( mesh: vtkUnstructuredGrid, faceNodes: Iterable[ Iterable[ int ] ] ) -> Mapping[ int, Iterable[ int ] ]: + """Builds the mapping from each fracture node to the cells that use this node. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + faceNodes (Iterable[ Iterable[ int ] ]): The nodes of each face of the fracture. + + Returns: + Mapping[ int, Iterable[ int ] ]: A mapping from each fracture node to the cells that use this node. + """ # TODO normally, just a list and not a set should be enough. nodeToCells: dict[ int, set[ int ] ] = defaultdict( set ) @@ -74,6 +83,16 @@ def buildNodeToCells( mesh: vtkUnstructuredGrid, def __buildFractureInfoFromFields( mesh: vtkUnstructuredGrid, f: Sequence[ int ], fieldValues: frozenset[ int ] ) -> FractureInfo: + """Build the fracture info from field values. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + f (Sequence[ int ]): The field values for each cell. + fieldValues (frozenset[ int ]): The set of field values to consider. + + Returns: + FractureInfo: The fracture information. + """ cellsToFaces: dict[ int, list[ int ] ] = defaultdict( list ) # For each face of each cell, we search for the unique neighbor cell (if it exists). # Then, if the 2 values of the two cells match the field requirements, @@ -93,7 +112,7 @@ def __buildFractureInfoFromFields( mesh: vtkUnstructuredGrid, f: Sequence[ int ] if f[ neighborCellId ] != f[ cellId ] and f[ neighborCellId ] in fieldValues: # TODO add this (cellIds, faceId) information to the fractureInfo? cellsToFaces[ cellId ].append( i ) - faceNodes: list[ Collection[ int ] ] = list() + faceNodes: list[ Collection[ int ] ] = [] faceNodesHashes: set[ frozenset[ int ] ] = set() # A temporary not to add multiple times the same face. for cellId, facesIds in tqdm( cellsToFaces.items(), desc="Extracting the faces of the fractures" ): cell = mesh.GetCell( cellId ) @@ -104,27 +123,36 @@ def __buildFractureInfoFromFields( mesh: vtkUnstructuredGrid, f: Sequence[ int ] faceNodesHashes.add( fnh ) faceNodes.append( fn ) nodeToCells: Mapping[ int, Iterable[ int ] ] = buildNodeToCells( mesh, faceNodes ) - faceCellId: list = list() # no cell of the mesh corresponds to that face when fracture policy is 'field' + faceCellId: list = [] # no cell of the mesh corresponds to that face when fracture policy is 'field' return FractureInfo( nodeToCells=nodeToCells, faceNodes=faceNodes, faceCellId=faceCellId ) def __buildFractureInfoFromInternalSurfaces( mesh: vtkUnstructuredGrid, f: Sequence[ int ], fieldValues: frozenset[ int ] ) -> FractureInfo: + """Build the fracture info from internal surfaces defined by the field values. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + f (Sequence[ int ]): The field values for each cell. + fieldValues (frozenset[ int ]): The set of field values to consider. + + Returns: + FractureInfo: The fracture information. + """ nodeToCells: dict[ int, list[ int ] ] = defaultdict( list ) - faceNodes: list[ Collection[ int ] ] = list() - faceCellId: list[ int ] = list() + faceNodes: list[ Collection[ int ] ] = [] + faceCellId: list[ int ] = [] for cellId in tqdm( range( mesh.GetNumberOfCells() ), desc="Computing the face to nodes mapping" ): cell = mesh.GetCell( cellId ) - if cell.GetCellDimension() == 2: - if f[ cellId ] in fieldValues: - nodes = list() - for v in range( cell.GetNumberOfPoints() ): - pointId: int = cell.GetPointId( v ) - nodeToCells[ pointId ] = list() - nodes.append( pointId ) - faceNodes.append( tuple( nodes ) ) - faceCellId.append( cellId ) + if cell.GetCellDimension() == 2 and f[ cellId ] in fieldValues: + nodes = [] + for v in range( cell.GetNumberOfPoints() ): + pointId: int = cell.GetPointId( v ) + nodeToCells[ pointId ] = [] + nodes.append( pointId ) + faceNodes.append( tuple( nodes ) ) + faceCellId.append( cellId ) for cellId in tqdm( range( mesh.GetNumberOfCells() ), desc="Computing the node to cells mapping" ): cell = mesh.GetCell( cellId ) @@ -140,11 +168,22 @@ def buildFractureInfo( mesh: vtkUnstructuredGrid, options: Options, combinedFractures: bool, fractureId: int = 0 ) -> FractureInfo: + """For a given mesh and options, builds the fracture information. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + options (Options): Options for processing. + combinedFractures (bool): If True, considers all fractures together. Else, only the fracture with id. + fractureId (int, optional): The fracture id to consider if combinedFractures is False. Defaults to 0. + + Raises: + ValueError: If the field does not exist in the mesh. + + Returns: + FractureInfo: The fracture information. + """ field = options.field - if combinedFractures: - fieldValues = options.fieldValuesCombined - else: - fieldValues = options.fieldValuesPerFracture[ fractureId ] + fieldValues = options.fieldValuesCombined if combinedFractures else options.fieldValuesPerFracture[ fractureId ] cellData = mesh.GetCellData() if cellData.HasArray( field ): f = vtk_to_numpy( cellData.GetArray( field ) ) @@ -159,6 +198,7 @@ def buildFractureInfo( mesh: vtkUnstructuredGrid, def buildCellToCellGraph( mesh: vtkUnstructuredGrid, fracture: FractureInfo ) -> networkx.Graph: """Connects all the cells that touch the fracture by at least one node. + Two cells are connected when they share at least a face which is not a face of the fracture. Args: @@ -171,7 +211,7 @@ def buildCellToCellGraph( mesh: vtkUnstructuredGrid, fracture: FractureInfo ) -> """ # Faces are identified by their nodes. But the order of those nodes may vary while referring to the same face. # Therefore we compute some kinds of hashes of those face to easily detect if a face is part of the fracture. - tmp: list[ frozenset[ int ] ] = list() + tmp: list[ frozenset[ int ] ] = [] for fn in fracture.faceNodes: tmp.append( frozenset( fn ) ) faceHashes: frozenset[ frozenset[ int ] ] = frozenset( tmp ) @@ -217,13 +257,13 @@ def _identifySplit( numPoints: int, cellToCell: networkx.Graph, """ class NewIndex: - """ - Returns the next available index. + """Returns the next available index. + Note that the first time an index is met, the index itself is returned: we do not want to change an index if we do not have to. """ - def __init__( self, numNodes: int ): + def __init__( self, numNodes: int ) -> None: self.__currentLastIndex = numNodes - 1 self.__seen: set[ int ] = set() @@ -250,13 +290,12 @@ def __call__( self, index: int ) -> int: def __copyFieldsSplitMesh( oldMesh: vtkUnstructuredGrid, splitMesh: vtkUnstructuredGrid, addedPointsWithOldId: list[ tuple[ int, int ] ] ) -> None: - """Copies the fields from the old mesh to the new one. - Point data will be duplicated for collocated nodes. + """Copies the fields from the old mesh to the new one. Point data will be duplicated for collocated nodes. Args: - oldMesh (vtkUnstructuredGrid): The mesh before the split._ + oldMesh (vtkUnstructuredGrid): The mesh before the split. splitMesh (vtkUnstructuredGrid): The mesh after the split. Will receive the fields in place. - addedPointsWithOldId (list[ tuple[ int, int ] ]): _description_ + addedPointsWithOldId (list[ tuple[ int, int ] ]): List of tuples (newId, oldId) for each duplicated point. """ # Copying the cell data. The cells are the same, just their nodes support have changed. inputCellData = oldMesh.GetCellData() @@ -358,7 +397,7 @@ def __copyFieldsFractureMesh( oldMesh: vtkUnstructuredGrid, fractureMesh: vtkUns def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ int, IDMapping ] ) -> vtkUnstructuredGrid: - """Split the main 3d mesh based on the node duplication information contained in @p cellToNodeMapping + """Split the main 3d mesh based on the node duplication information contained in @p cellToNodeMapping. Args: oldMesh (vtkUnstructuredGrid): The main 3d mesh. @@ -369,7 +408,7 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in vtkUnstructuredGrid: The main 3d mesh split at the fracture location. """ addedPoints: set[ int ] = set() - addedPointsWithOldId: list[ tuple[ int, int ] ] = list() + addedPointsWithOldId: list[ tuple[ int, int ] ] = [] for nodeMapping in cellToNodeMapping.values(): for i, o in nodeMapping.items(): if i != o: @@ -414,9 +453,9 @@ def __performSplit( oldMesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ in if cellType == VTK_POLYHEDRON: faceStream = vtkIdList() oldMesh.GetFaceStream( c, faceStream ) - newFaceNodes: list[ list[ int ] ] = list() + newFaceNodes: list[ list[ int ] ] = [] for faceNodes in FaceStream.buildFromVtkIdList( faceStream ).faceNodes: - newPointIds = list() + newPointIds = [] for currentPointId in faceNodes: newPointId: int = cellNodeMapping.get( currentPointId, currentPointId ) newPointIds.append( newPointId ) @@ -461,10 +500,10 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture # Some elements can have all their nodes not duplicated. # In this case, it's mandatory not get rid of this element because the neighboring 3d elements won't follow. - faceNodes: list[ Collection[ int ] ] = list() + faceNodes: list[ Collection[ int ] ] = [] discardedFaceNodes: set[ Iterable[ int ] ] = set() - if fractureInfo.faceCellId != list(): # The fracture policy is 'internalSurfaces' - faceCellId: list[ int ] = list() + if fractureInfo.faceCellId != []: # The fracture policy is 'internalSurfaces' + faceCellId: list[ int ] = [] for ns, fId in zip( fractureInfo.faceNodes, fractureInfo.faceCellId ): if any( map( isNodeDuplicated.__getitem__, ns ) ): faceNodes.append( ns ) @@ -479,7 +518,7 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture discardedFaceNodes.add( ns ) if discardedFaceNodes: - msg: str = "(" + '), ('.join( map( lambda dfns: ", ".join( map( str, dfns ) ), discardedFaceNodes ) ) + ")" + msg: str = "(" + '), ('.join( ", ".join( map( str, dfns ) ) for dfns in discardedFaceNodes ) + ")" setupLogger.info( f"The faces made of nodes [{msg}] were/was discarded" " from the fracture mesh because none of their/its nodes were duplicated." ) @@ -491,7 +530,7 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture numPoints: int = len( fractureNodes ) points = vtkPoints() points.SetNumberOfPoints( numPoints ) - node3dToNode2d: dict[ int, int ] = dict() # Building the node mapping, from 3d mesh nodes to 2d fracture nodes. + node3dToNode2d: dict[ int, int ] = {} # Building the node mapping, from 3d mesh nodes to 2d fracture nodes. for i, n in enumerate( fractureNodes ): coords: Coordinates3D = meshPoints.GetPoint( n ) points.SetPoint( i, coords ) @@ -531,7 +570,7 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture # The copy of fields from the old mesh to the fracture is only available when using the internalSurfaces policy # because the FractureInfo is linked to 2D elements from the oldMesh - if fractureInfo.faceCellId != list(): + if fractureInfo.faceCellId != []: __copyFieldsFractureMesh( oldMesh, fractureMesh, faceCellId, node3dToNode2d ) return fractureMesh @@ -539,7 +578,16 @@ def __generateFractureMesh( oldMesh: vtkUnstructuredGrid, fractureInfo: Fracture def __splitMeshOnFractures( mesh: vtkUnstructuredGrid, options: Options ) -> tuple[ vtkUnstructuredGrid, list[ vtkUnstructuredGrid ] ]: - allFractureInfos: list[ FractureInfo ] = list() + """Splits the input mesh based on the fracture information contained in options. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to be split. + options (Options): The options for processing. + + Returns: + tuple[ vtkUnstructuredGrid, list[ vtkUnstructuredGrid ] ]: The output mesh and a list of fracture meshes. + """ + allFractureInfos: list[ FractureInfo ] = [] for fractureId in range( len( options.fieldValuesPerFracture ) ): fractureInfo: FractureInfo = buildFractureInfo( mesh, options, False, fractureId ) allFractureInfos.append( fractureInfo ) @@ -548,14 +596,23 @@ def __splitMeshOnFractures( mesh: vtkUnstructuredGrid, cellToNodeMapping: Mapping[ int, IDMapping ] = _identifySplit( mesh.GetNumberOfPoints(), cellToCell, combinedFractures.nodeToCells ) outputMesh: vtkUnstructuredGrid = __performSplit( mesh, cellToNodeMapping ) - fractureMeshes: list[ vtkUnstructuredGrid ] = list() + fractureMeshes: list[ vtkUnstructuredGrid ] = [] for fractureInfoSeparated in allFractureInfos: fractureMesh: vtkUnstructuredGrid = __generateFractureMesh( mesh, fractureInfoSeparated, cellToNodeMapping ) fractureMeshes.append( fractureMesh ) return ( outputMesh, fractureMeshes ) -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the generation of fractures on a vtkUnstructuredGrid if options given to do so. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to generate fractures on. + options (Options): The options for processing. + + Returns: + Result: The result of the fracture generation. + """ outputMesh, fractureMeshes = __splitMeshOnFractures( mesh, options ) writeMesh( outputMesh, options.meshVtkOutput ) for i, fractureMesh in enumerate( fractureMeshes ): @@ -564,16 +621,21 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: return Result( info="OK" ) -def action( vtkInputFile: str, options: Options ) -> Result: - try: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - # Mesh cannot contain global ids before splitting. - if hasArray( mesh, [ "GLOBAL_IDS_POINTS", "GLOBAL_IDS_CELLS" ] ): - errMsg: str = ( "The mesh cannot contain global ids for neither cells nor points. The correct procedure " + - " is to split the mesh and then generate global ids for new split meshes." ) - setupLogger.error( errMsg ) - raise ValueError( errMsg ) - return __action( mesh, options ) - except BaseException as e: - setupLogger.error( e ) - return Result( info="Something went wrong" ) +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtkUnstructuredGrid and generates fractures based on the provided options. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the global ids addition. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + # Mesh cannot contain global ids before splitting. + if hasArray( mesh, [ "GLOBAL_IDS_POINTS", "GLOBAL_IDS_CELLS" ] ): + errMsg: str = ( "The mesh cannot contain global ids for neither cells nor points. The correct procedure " + + " is to split the mesh and then generate global ids for new split meshes." ) + setupLogger.error( errMsg ) + raise ValueError( errMsg ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py index 8f0d3df0..797a120e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from vtkmodules.vtkCommonCore import vtkIdTypeArray from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid -from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh +from geos.mesh_doctor.parsing.cliParsing import setupLogger @dataclass( frozen=True ) @@ -17,7 +17,7 @@ class Result: info: str -def __buildGlobalIds( mesh: vtkUnstructuredGrid, generateCellsGlobalIds: bool, generatePointsGlobalIds: bool ) -> None: +def buildGlobalIds( mesh: vtkUnstructuredGrid, generateCellsGlobalIds: bool, generatePointsGlobalIds: bool ) -> None: """Adds the global ids for cells and points in place into the mesh instance. Args: @@ -48,16 +48,30 @@ def __buildGlobalIds( mesh: vtkUnstructuredGrid, generateCellsGlobalIds: bool, g mesh.GetCellData().SetGlobalIds( cellsGlobalIds ) -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: - __buildGlobalIds( mesh, options.generateCellsGlobalIds, options.generatePointsGlobalIds ) +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the addition of global ids on a vtkUnstructuredGrid if options given to do so. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to add global ids. + options (Options): The options for processing. + + Returns: + Result: The result of the global ids addition. + """ + buildGlobalIds( mesh, options.generateCellsGlobalIds, options.generatePointsGlobalIds ) writeMesh( mesh, options.vtkOutput ) return Result( info=f"Mesh was written to {options.vtkOutput.output}" ) -def action( vtkInputFile: str, options: Options ) -> Result: - try: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) - except BaseException as e: - setupLogger.error( e ) - return Result( info="Something went wrong." ) +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtkUnstructuredGrid and adds global ids as per options. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the global ids addition. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py index 52a0d22a..f0e8d167 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py @@ -1 +1 @@ -from geos.mesh_doctor.actions.allChecks import action +from geos.mesh_doctor.actions.allChecks import action # noqa: F401 diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py index eb6054cf..54c1a522 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py @@ -1,10 +1,11 @@ from dataclasses import dataclass import math -import numpy +import numpy as np +import numpy.typing as npt from typing import Union from tqdm import tqdm from vtk import reference as vtkReference -from vtkmodules.vtkCommonCore import vtkDataArray, vtkIdList, vtkPoints +from vtkmodules.vtkCommonCore import vtkIntArray, vtkFloatArray, vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import ( vtkBoundingBox, vtkCell, vtkCellArray, vtkPointSet, vtkPolyData, vtkStaticCellLocator, vtkStaticPointLocator, vtkUnstructuredGrid, VTK_POLYHEDRON ) @@ -30,8 +31,8 @@ class Result: class BoundaryMesh: - """ - A BoundaryMesh is the envelope of the 3d mesh on which we want to perform the simulations. + """A BoundaryMesh is the envelope of the 3d mesh on which we want to perform the simulations. + It is computed by vtk. But we want to be sure that the normals of the envelope are directed outwards. The `vtkDataSetSurfaceFilter` does not have the same behavior for standard vtk cells (like tets or hexs), and for polyhedron meshes, for which the result is a bit brittle. @@ -39,7 +40,7 @@ class BoundaryMesh: And then we compute the boundary meshes for both meshes, given that the computing options are not identical. """ - def __init__( self, mesh: vtkUnstructuredGrid ): + def __init__( self, mesh: vtkUnstructuredGrid ) -> None: """Builds a boundary mesh. Args: @@ -56,13 +57,13 @@ def __init__( self, mesh: vtkUnstructuredGrid ): self.reBoundaryMesh, reNormals, _ = BoundaryMesh.__buildBoundaryMesh( reorientedMesh, consistency=False ) numCells = boundaryMesh.GetNumberOfCells() # Precomputing the underlying cell type - self.__isUnderlyingCellTypeAPolyhedron = numpy.zeros( numCells, dtype=bool ) + self.__isUnderlyingCellTypeAPolyhedron = np.zeros( numCells, dtype=bool ) for ic in range( numCells ): self.__isUnderlyingCellTypeAPolyhedron[ ic ] = mesh.GetCell( self.__originalCells.GetValue( ic ) ).GetCellType() == VTK_POLYHEDRON # type: ignore[attr-defined] # Precomputing the normals - self.__normals: numpy.ndarray = numpy.empty( ( numCells, 3 ), dtype=numpy.double, - order='C' ) # Do not modify the storage layout + self.__normals: np.ndarray = np.empty( ( numCells, 3 ), dtype=np.double, + order='C' ) # Do not modify the storage layout for ic in range( numCells ): if self.__isUnderlyingCellTypeAPolyhedron[ ic ]: self.__normals[ ic, : ] = reNormals.GetTuple3( ic ) @@ -71,7 +72,7 @@ def __init__( self, mesh: vtkUnstructuredGrid ): @staticmethod def __buildBoundaryMesh( mesh: vtkUnstructuredGrid, - consistency=True ) -> tuple[ vtkPolyData, vtkDataArray, vtkDataArray ]: + consistency: bool = True ) -> tuple[ vtkPolyData, vtkFloatArray, vtkIntArray ]: """From a 3d mesh, build the envelope meshes. Args: @@ -102,11 +103,11 @@ def __buildBoundaryMesh( mesh: vtkUnstructuredGrid, n.ComputeCellNormalsOn() n.SetInputData( boundaryMesh ) n.Update() - normals: vtkDataArray = n.GetOutput().GetCellData().GetArray( "Normals" ) + normals: vtkFloatArray = n.GetOutput().GetCellData().GetArray( "Normals" ) assert normals assert normals.GetNumberOfComponents() == 3 assert normals.GetNumberOfTuples() == boundaryMesh.GetNumberOfCells() - originalCells: vtkDataArray = boundaryMesh.GetCellData().GetArray( originalCellsKey ) + originalCells: vtkIntArray = boundaryMesh.GetCellData().GetArray( originalCellsKey ) assert originalCells return boundaryMesh, normals, originalCells @@ -137,14 +138,14 @@ def bounds( self, i: int ) -> tuple[ float, float, float, float, float, float ]: """ return self.reBoundaryMesh.GetCell( i ).GetBounds() - def normals( self, i: int ) -> numpy.ndarray: - """The normal of cell `i`. This normal will be directed outwards + def normals( self, i: int ) -> np.ndarray: + """The normal of cell `i`. This normal will be directed outwards. Args: i (int): The boundary cell index. Returns: - numpy.ndarray: The normal as a length-3 numpy array. + np.ndarray: The normal as a length-3 numpy array. """ return self.__normals[ i ] @@ -161,6 +162,7 @@ def GetCell( self, i: int ) -> vtkCell: def GetPoint( self, i: int ) -> tuple[ float, float, float ]: """Point i of the boundary mesh. + Args: i (int): The boundary point index. @@ -170,44 +172,15 @@ def GetPoint( self, i: int ) -> tuple[ float, float, float ]: return self.reBoundaryMesh.GetPoint( i ) @property - def originalCells( self ) -> vtkDataArray: + def originalCells( self ) -> vtkIntArray: """Returns the 2d boundary cell to the 3d cell index of the original mesh. Returns: - vtkDataArray: A 1d array. + vtkIntArray: A 1d array. """ return self.__originalCells -def buildPolyDataForExtrusion( i: int, boundaryMesh: BoundaryMesh ) -> vtkPolyData: - """Creates a vtkPolyData containing the unique cell `i` of the boundary mesh. - - Args: - i (int): The boundary cell index that will eventually be extruded. - boundaryMesh (BoundaryMesh): The boundary mesh containing the cell. - - Returns: - vtkPolyData: The created vtkPolyData. - """ - cell = boundaryMesh.GetCell( i ) - copiedCell = cell.NewInstance() - copiedCell.DeepCopy( cell ) - pointsIdsMapping = [] - for i in range( copiedCell.GetNumberOfPoints() ): - copiedCell.GetPointIds().SetId( i, i ) - pointsIdsMapping.append( cell.GetPointId( i ) ) - polygons = vtkCellArray() - polygons.InsertNextCell( copiedCell ) - points = vtkPoints() - points.SetNumberOfPoints( len( pointsIdsMapping ) ) - for i, v in enumerate( pointsIdsMapping ): - points.SetPoint( i, boundaryMesh.GetPoint( v ) ) - polygonPolyData = vtkPolyData() - polygonPolyData.SetPoints( points ) - polygonPolyData.SetPolys( polygons ) - return polygonPolyData - - def arePointsConformal( pointTolerance: float, cellI: vtkCell, cellJ: vtkCell ) -> bool: """Checks if points of cell `i` matches, one by one, the points of cell `j`. @@ -237,25 +210,60 @@ def arePointsConformal( pointTolerance: float, cellI: vtkCell, cellJ: vtkCell ) return foundPoints == set( range( cellI.GetNumberOfPoints() ) ) -class Extruder: +def buildPolyDataForExtrusion( i: int, boundaryMesh: BoundaryMesh ) -> vtkPolyData: + """Creates a vtkPolyData containing the unique cell `i` of the boundary mesh. + + Args: + i (int): The boundary cell index that will eventually be extruded. + boundaryMesh (BoundaryMesh): The boundary mesh containing the cell. + + Returns: + vtkPolyData: The created vtkPolyData. """ - Computes and stores all the extrusions of the boundary faces. + cell = boundaryMesh.GetCell( i ) + copiedCell = cell.NewInstance() + copiedCell.DeepCopy( cell ) + pointsIdsMapping = [] + for i in range( copiedCell.GetNumberOfPoints() ): + copiedCell.GetPointIds().SetId( i, i ) + pointsIdsMapping.append( cell.GetPointId( i ) ) + polygons = vtkCellArray() + polygons.InsertNextCell( copiedCell ) + points = vtkPoints() + points.SetNumberOfPoints( len( pointsIdsMapping ) ) + for i, v in enumerate( pointsIdsMapping ): + points.SetPoint( i, boundaryMesh.GetPoint( v ) ) + polygonPolyData = vtkPolyData() + polygonPolyData.SetPoints( points ) + polygonPolyData.SetPolys( polygons ) + return polygonPolyData + + +class Extruder: + """Computes and stores all the extrusions of the boundary faces. + The main reason for this class is to be lazy and cache the extrusions. """ - def __init__( self, boundaryMesh: BoundaryMesh, faceTolerance: float ): + def __init__( self, boundaryMesh: BoundaryMesh, faceTolerance: float ) -> None: + """Initializes the extruder. + + Args: + boundaryMesh (BoundaryMesh): Boundary mesh. + faceTolerance (float): Tolerance value. + """ self.__extrusions: list[ Union[ vtkPolyData, None ] ] = [ None, ] * boundaryMesh.GetNumberOfCells() self.__boundaryMesh = boundaryMesh self.__faceTolerance = faceTolerance - def __extrude( self, polygonPolyData: vtkPolyData, normal: numpy.ndarray ) -> vtkPolyData: + def __extrude( self, polygonPolyData: vtkPolyData, normal: np.ndarray ) -> vtkPolyData: """Extrude the polygon data to create a surface that will be used for intersection. Args: polygonPolyData (vtkPolyData): The data to extrude - normal (numpy.ndarray): The (uniform) direction of the extrusion. + normal (np.ndarray): The (uniform) direction of the extrusion. Returns: vtkPolyData: The extruded surface. @@ -288,8 +296,7 @@ def __getitem__( self, i: int ) -> vtkPolyData: def areFacesConformalUsingExtrusions( extrusions: Extruder, i: int, j: int, boundaryMesh: BoundaryMesh, pointTolerance: float ) -> bool: - """ - Tests if two boundary faces are conformal, checking for intersection between their normal extruded volumes. + """Tests if two boundary faces are conformal, checking for intersection between their normal extruded volumes. Args: extrusions (Extruder): The extrusions cache. @@ -336,12 +343,12 @@ def areFacesConformalUsingDistances( i: int, j: int, boundaryMesh: vtkUnstructur Returns: bool: True if the faces are conformal, False otherwise. """ - cpI = boundaryMesh.GetCell( i ).NewInstance() + cpI: vtkCell = boundaryMesh.GetCell( i ).NewInstance() cpI.DeepCopy( boundaryMesh.GetCell( i ) ) - cpJ = boundaryMesh.GetCell( j ).NewInstance() + cpJ: vtkCell = boundaryMesh.GetCell( j ).NewInstance() cpJ.DeepCopy( boundaryMesh.GetCell( j ) ) - def triangulate( cell ): + def triangulate( cell: vtkCell ) -> tuple[ tuple[ int, ... ], vtkPoints ]: assert cell.GetCellDimension() == 2 _pointsIdsList = vtkIdList() _points = vtkPoints() @@ -354,19 +361,19 @@ def triangulate( cell ): pointsIdsI, pointsI = triangulate( cpI ) pointsIdsJ, pointsJ = triangulate( cpJ ) - def buildNumpyTriangles( pointsIds ): + def buildNumpyTriangles( pointsIds: tuple[ int, ... ] ) -> list[ npt.NDArray[ np.float64 ] ]: __triangles = [] for __i in range( 0, len( pointsIds ), 3 ): __t = [] for __pi in pointsIds[ __i:__i + 3 ]: __t.append( boundaryMesh.GetPoint( __pi ) ) - __triangles.append( numpy.array( __t, dtype=float ) ) + __triangles.append( np.array( __t, dtype=float ) ) return __triangles trianglesI = buildNumpyTriangles( pointsIdsI ) trianglesJ = buildNumpyTriangles( pointsIdsJ ) - minDist = numpy.inf + minDist = np.inf for ti, tj in [ ( ti, tj ) for ti in trianglesI for tj in trianglesJ ]: # Note that here, we compute the exact distance to compare with the threshold. # We could improve by exiting the iterative distance computation as soon as @@ -382,50 +389,93 @@ def buildNumpyTriangles( pointsIds ): return arePointsConformal( pointTolerance, cpI, cpJ ) -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: - """Checks if the mesh is "conformal" (i.e. if some of its boundary faces may not be too close to each other - without matching nodes). +def computeBoundingBox( boundaryMesh: BoundaryMesh, faceTolerance: float ) -> npt.NDArray: + """Computes the bounding boxes of all boundary cells, inflated by 2 * faceTolerance. Args: - mesh (vtkUnstructuredGrid): The vtk mesh - options (Options): The check options. + boundaryMesh (BoundaryMesh): The boundary mesh. + faceTolerance (float): The tolerance for face proximity. Returns: - Result: The result of the conformity check. + npt.NDArray[ np.float64 ]: An array of bounding boxes for each cell. """ - boundaryMesh = BoundaryMesh( mesh ) - cosTheta = abs( math.cos( numpy.deg2rad( options.angleTolerance ) ) ) - numCells = boundaryMesh.GetNumberOfCells() + # The options are important to directly interact with memory in C++. + boundingBoxes = np.empty( ( boundaryMesh.GetNumberOfCells(), 6 ), dtype=np.double, order="C" ) + for i in range( boundaryMesh.GetNumberOfCells() ): + bb = vtkBoundingBox( boundaryMesh.bounds( i ) ) + bb.Inflate( 2 * faceTolerance ) + assert boundingBoxes[ + i, : ].data.contiguous # Do not modify the storage layout since vtk deals with raw memory here. + boundsList: list[ float ] = [ 0.0 ] * 6 + bb.GetBounds( boundsList ) + boundingBoxes[ i, : ] = boundsList + return boundingBoxes + + +def computeNumberCellsPerNode( boundaryMesh: BoundaryMesh ) -> npt.NDArray[ np.int64 ]: + """Computes the number of cells connected to each node in the boundary mesh. + Args: + boundaryMesh (BoundaryMesh): The boundary mesh. + + Returns: + npt.NDArray[ np.int64 ]: The number of cells per node. + """ # Computing the exact number of cells per node - numCellsPerNode = numpy.zeros( boundaryMesh.GetNumberOfPoints(), dtype=int ) + numCellsPerNode = np.zeros( boundaryMesh.GetNumberOfPoints(), dtype=int ) for ic in range( boundaryMesh.GetNumberOfCells() ): c = boundaryMesh.GetCell( ic ) pointIds = c.GetPointIds() for pointId in vtkIter( pointIds ): numCellsPerNode[ pointId ] += 1 + return numCellsPerNode + +def buildCellLocator( reBoundaryMesh: vtkPolyData, numberMaxCellPerNode: int ) -> vtkStaticCellLocator: + """Builds a vtkStaticCellLocator for the boundary mesh. + + Args: + reBoundaryMesh (vtkPolyData): The reBoundary mesh. + numberMaxCellPerNode (int): The maximum number of cells per node. + + Returns: + vtkStaticCellLocator: The built cell locator. + """ cellLocator = vtkStaticCellLocator() cellLocator.Initialize() - cellLocator.SetNumberOfCellsPerNode( numCellsPerNode.max() ) - cellLocator.SetDataSet( boundaryMesh.reBoundaryMesh ) + cellLocator.SetNumberOfCellsPerNode( numberMaxCellPerNode ) + cellLocator.SetDataSet( reBoundaryMesh ) cellLocator.BuildLocator() + return cellLocator - # Precomputing the bounding boxes. - # The options are important to directly interact with memory in C++. - boundingBoxes = numpy.empty( ( boundaryMesh.GetNumberOfCells(), 6 ), dtype=numpy.double, order="C" ) - for i in range( boundaryMesh.GetNumberOfCells() ): - bb = vtkBoundingBox( boundaryMesh.bounds( i ) ) - bb.Inflate( 2 * options.faceTolerance ) - assert boundingBoxes[ - i, : ].data.contiguous # Do not modify the storage layout since vtk deals with raw memory here. - boundsList: list[ float ] = [ 0.0 ] * 6 - bb.GetBounds( boundsList ) - boundingBoxes[ i, : ] = boundsList - nonConformalCells = [] +def findNonConformalCells( mesh: vtkUnstructuredGrid, options: Options ) -> list[ tuple[ int, int ] ]: + """Finds all pairs of non-conformal boundary faces in the mesh. + + Args: + mesh (vtkUnstructuredGrid): The input mesh. + options (Options): The options for the non-conformal check. + + Returns: + list[ tuple[ int, int ] ]: A list of pairs of non-conformal cell indices. + """ + # Extracts the outer surface of the 3D mesh. + # Ensures that face normals are consistently oriented outward. + boundaryMesh = BoundaryMesh( mesh ) + numCells: int = boundaryMesh.GetNumberOfCells() + + # Used to filter out face pairs that are not facing each other. + cosTheta: float = abs( math.cos( np.deg2rad( options.angleTolerance ) ) ) + + # Prepares extruded volumes of boundary faces for intersection testing. extrusions = Extruder( boundaryMesh, options.faceTolerance ) + + numCellsPerNode = computeNumberCellsPerNode( boundaryMesh ) + boundingBoxes = computeBoundingBox( boundaryMesh, options.faceTolerance ) + cellLocator = buildCellLocator( boundaryMesh.reBoundaryMesh, numCellsPerNode.max() ) + closeCells = vtkIdList() + nonConformalCellsBoundaryId: list[ tuple[ int, int ] ] = [] # Looping on all the pairs of boundary cells. We'll hopefully discard most of the pairs. for i in tqdm( range( numCells ), desc="Non conformal elements" ): cellLocator.FindCellsWithinBounds( boundingBoxes[ i ], closeCells ) @@ -434,20 +484,42 @@ def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: continue # Discarding pairs that are not facing each others (with a threshold). normalI, normalJ = boundaryMesh.normals( i ), boundaryMesh.normals( j ) - if numpy.dot( normalI, normalJ ) > -cosTheta: # opposite directions only (can be facing or not) + if np.dot( normalI, normalJ ) > -cosTheta: # opposite directions only (can be facing or not) continue # At this point, back-to-back and face-to-face pairs of elements are considered. if not areFacesConformalUsingExtrusions( extrusions, i, j, boundaryMesh, options.pointTolerance ): - nonConformalCells.append( ( i, j ) ) + nonConformalCellsBoundaryId.append( ( i, j ) ) # Extracting the original 3d element index (and not the index of the boundary mesh). - tmp = [] - for i, j in nonConformalCells: - tmp.append( ( boundaryMesh.originalCells.GetValue( i ), # type: ignore[attr-defined] - boundaryMesh.originalCells.GetValue( j ) ) ) # type: ignore[attr-defined] + nonConformalCells: list[ tuple[ int, int ] ] = [] + for i, j in nonConformalCellsBoundaryId: + nonConformalCells.append( ( boundaryMesh.originalCells.GetValue( i ), + boundaryMesh.originalCells.GetValue( j ) ) ) + return nonConformalCells + - return Result( nonConformalCells=tmp ) +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Checks if the mesh is "conformal" (i.e. if some of its boundary faces may not be too close to each other without matching nodes). + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The check options. -def action( vtkInputFile: str, options: Options ) -> Result: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) + Returns: + Result: The Result instance. + """ + nonConformalCells = findNonConformalCells( mesh, options ) + return Result( nonConformalCells=nonConformalCells ) + + +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the self intersecting elements check on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the self intersecting elements check. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py index 88d8ff93..69e263ff 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py @@ -68,8 +68,7 @@ def __computeVolume( meshPoints: vtkPoints, faceStream: FaceStream ) -> float: def __selectAndFlipFaces( meshPoints: vtkPoints, colors: dict[ frozenset[ int ], int ], faceStream: FaceStream ) -> FaceStream: - """Given a polyhedra, given that we were able to paint the faces in two colors, - we now need to select which faces/color to flip such that the volume of the element is positive. + """Given a polyhedra, given that we were able to paint the faces in two colors, we now need to select which faces/color to flip such that the volume of the element is positive. Args: meshPoints (vtkPoints): The mesh points, needed to compute the volume. diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py index 55956524..bb8986de 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from typing import Collection from vtkmodules.util.numpy_support import vtk_to_numpy from vtkmodules.vtkFiltersGeneral import vtkCellValidator from vtkmodules.vtkCommonCore import vtkOutputWindow, vtkFileOutputWindow @@ -14,67 +13,101 @@ class Options: @dataclass( frozen=True ) class Result: - wrongNumberOfPointsElements: Collection[ int ] - intersectingEdgesElements: Collection[ int ] - intersectingFacesElements: Collection[ int ] - nonContiguousEdgesElements: Collection[ int ] - nonConvexElements: Collection[ int ] - facesAreOrientedIncorrectlyElements: Collection[ int ] + invalidCellIds: dict[ str, list[ int ] ] -def __action( mesh: vtkUnstructuredGrid, options: Options ) -> Result: +def getInvalidCellIds( mesh: vtkUnstructuredGrid, minDistance: float ) -> dict[ str, list[ int ] ]: + """For every cell element in a vtk mesh, check if the cell is invalid regarding 8 specific criteria. + + "wrongNumberOfPoints", "intersectingEdges", "intersectingFaces", "nonContiguousEdges","nonConvex", + "facesAreOrientedIncorrectly", "nonPlanarFacesElements" and "degenerateFacesElements". + + If any of this criteria was met, the cell index is added to a list corresponding to this specific criteria. + The dict with the complete list of cell indices by criteria is returned. + + Args: + mesh (vtkUnstructuredGrid): A vtk grid. + minDistance (float): Minimum distance in the computation. + + Returns: + dict[ str, list[ int ] ]: + { + "wrongNumberOfPointsElements": [ 10, 34, ... ], + "intersectingEdgesElements": [ ... ], + "intersectingFacesElements": [ ... ], + "nonContiguousEdgesElements": [ ... ], + "nonConvexElements": [ ... ], + "facesOrientedIncorrectlyElements": [ ... ], + "nonPlanarFacesElements": [ ... ], + "degenerateFacesElements": [ ... ] + } + """ errOut = vtkFileOutputWindow() - errOut.SetFileName( "/dev/null" ) # vtkCellValidator outputs loads for each cell... + errOut.SetFileName( "/dev/null" ) vtkStdErrOut = vtkOutputWindow() vtkStdErrOut.SetInstance( errOut ) - valid = 0x0 - wrongNumberOfPoints = 0x01 - intersectingEdges = 0x02 - intersectingFaces = 0x04 - nonContiguousEdges = 0x08 - nonConvex = 0x10 - facesAreOrientedIncorrectly = 0x20 - - wrongNumberOfPointsElements: list[ int ] = [] - intersectingEdgesElements: list[ int ] = [] - intersectingFacesElements: list[ int ] = [] - nonContiguousEdgesElements: list[ int ] = [] - nonConvexElements: list[ int ] = [] - facesAreOrientedIncorrectlyElements: list[ int ] = [] + # Different types of cell invalidity are defined as hexadecimal values, specific to vtkCellValidator + # Complete set of validity checks available in vtkCellValidator + errorMasks: dict[ str, int ] = { + "wrongNumberOfPointsElements": 0x01, # 0000 0001 + "intersectingEdgesElements": 0x02, # 0000 0010 + "intersectingFacesElements": 0x04, # 0000 0100 + "nonContiguousEdgesElements": 0x08, # 0000 1000 + "nonConvexElements": 0x10, # 0001 0000 + "facesOrientedIncorrectlyElements": 0x20, # 0010 0000 + "nonPlanarFacesElements": 0x40, # 0100 0000 + "degenerateFacesElements": 0x80, # 1000 0000 + } - f = vtkCellValidator() - f.SetTolerance( options.minDistance ) + # The results can be stored in a dictionary where keys are the error names + # and values are the lists of cell indices with that error. + # We can initialize it directly from the keys of our errorMasks dictionary. + invalidCellIds: dict[ str, list[ int ] ] = { _errorName: [] for _errorName in errorMasks } + f = vtkCellValidator() + f.SetTolerance( minDistance ) f.SetInputData( mesh ) - f.Update() + f.Update() # executes the filter output = f.GetOutput() - validity = output.GetCellData().GetArray( "ValidityState" ) # Could not change name using the vtk interface. + validity = output.GetCellData().GetArray( "ValidityState" ) assert validity is not None + # array of np.int16 that combines the flags using a bitwise OR operation for each cell index. validity = vtk_to_numpy( validity ) - for i, v in enumerate( validity ): - if not v & valid: - if v & wrongNumberOfPoints: - wrongNumberOfPointsElements.append( i ) - if v & intersectingEdges: - intersectingEdgesElements.append( i ) - if v & intersectingFaces: - intersectingFacesElements.append( i ) - if v & nonContiguousEdges: - nonContiguousEdgesElements.append( i ) - if v & nonConvex: - nonConvexElements.append( i ) - if v & facesAreOrientedIncorrectly: - facesAreOrientedIncorrectlyElements.append( i ) - return Result( wrongNumberOfPointsElements=wrongNumberOfPointsElements, - intersectingEdgesElements=intersectingEdgesElements, - intersectingFacesElements=intersectingFacesElements, - nonContiguousEdgesElements=nonContiguousEdgesElements, - nonConvexElements=nonConvexElements, - facesAreOrientedIncorrectlyElements=facesAreOrientedIncorrectlyElements ) - - -def action( vtkInputFile: str, options: Options ) -> Result: - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - return __action( mesh, options ) + for cellIndex, validityFlag in enumerate( validity ): + if not validityFlag: # Skip valid cells ( validityFlag == 0 or 0000 0000 ) + continue + for errorName, errorMask in errorMasks.items(): # Check only invalid cells against all possible errors. + if validityFlag & errorMask: + invalidCellIds[ errorName ].append( cellIndex ) + + return invalidCellIds + + +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the self intersecting elements check on a vtkUnstructuredGrid. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The options for processing. + + Returns: + Result: The result of the self intersecting elements check. + """ + invalidCellIds: dict[ str, list[ int ] ] = getInvalidCellIds( mesh, options.minDistance ) + return Result( invalidCellIds ) + + +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the self intersecting elements check on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the self intersecting elements check. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py index 5c0c4ca2..466fa91d 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py @@ -1,15 +1,16 @@ from dataclasses import dataclass import multiprocessing import networkx +from numpy import ones from tqdm import tqdm from typing import Iterable, Mapping, Optional from vtkmodules.util.numpy_support import vtk_to_numpy from vtkmodules.vtkCommonCore import vtkIdList from vtkmodules.vtkCommonDataModel import ( vtkCellTypes, vtkUnstructuredGrid, VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, VTK_POLYHEDRON, VTK_PYRAMID, VTK_TETRA, VTK_VOXEL, - VTK_WEDGE ) -from geos.mesh_doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream -from geos.mesh_doctor.parsing.cliParsing import setupLogger + VTK_LINE, VTK_PENTAGONAL_PRISM, VTK_POLYGON, VTK_POLYHEDRON, VTK_PYRAMID, + VTK_QUAD, VTK_TETRA, VTK_TRIANGLE, VTK_VERTEX, VTK_VOXEL, VTK_WEDGE ) +from geos.mesh.doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream +from geos.mesh.doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid from geos.mesh.utils.genericHelpers import vtkIter @@ -22,47 +23,42 @@ class Options: @dataclass( frozen=True ) class Result: - unsupportedStdElementsTypes: frozenset[ int ] # list of unsupported types + unsupportedStdElementsTypes: list[ str ] # list of unsupported types unsupportedPolyhedronElements: frozenset[ int ] # list of polyhedron elements that could not be converted to supported std elements # for multiprocessing, vtkUnstructuredGrid cannot be pickled. Let's use a global variable instead. +# Global variable to be set in each worker process MESH: Optional[ vtkUnstructuredGrid ] = None -def initWorkerMesh( inputFileForWorker: str ): - """Initializer for multiprocessing.Pool to set the global MESH variable in each worker process. - - Args: - inputFileForWorker (str): Filepath to vtk grid - """ +def initWorker( meshToInit: vtkUnstructuredGrid ) -> None: + """Initializer for each worker process to set the global mesh.""" global MESH - setupLogger.debug( - f"Worker process (PID: {multiprocessing.current_process().pid}) initializing MESH from file: {inputFileForWorker}" - ) - mesh: vtkUnstructuredGrid = readUnstructuredGrid( inputFileForWorker ) - if MESH is None: - setupLogger.error( - f"Worker process (PID: {multiprocessing.current_process().pid}) failed to load mesh from {inputFileForWorker}" - ) - # You might want to raise an error here or ensure MESH being None is handled downstream - # For now, the assert MESH is not None in __call__ will catch this. + MESH = meshToInit + + +supportedCellTypes: set[ int ] = { + VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_LINE, VTK_PENTAGONAL_PRISM, VTK_POLYGON, VTK_POLYHEDRON, VTK_PYRAMID, + VTK_QUAD, VTK_TETRA, VTK_TRIANGLE, VTK_VERTEX, VTK_VOXEL, VTK_WEDGE +} class IsPolyhedronConvertible: - def __init__( self ): + def __init__( self ) -> None: + """Initializer for the IsPolyhedronConvertible class.""" def buildPrismGraph( n: int, name: str ) -> networkx.Graph: """Builds the face to face connectivities (through edges) for prism graphs. Args: - n (int): The number of nodes of the basis (i.e. the pentagonal prims gets n = 5) - name (str): A human-readable name for logging purpose. + n (int): The number of nodes of the base (e.g., pentagonal prism gets n = 5). + name (str): A human-readable name for logging purposes. Returns: - networkx.Graph: A graph instance. + networkx.Graph: A graph instance representing the prism. """ tmp = networkx.cycle_graph( n ) for node in range( n ): @@ -89,35 +85,32 @@ def buildPrismGraph( n: int, name: str ) -> networkx.Graph: 13: ( buildPrismGraph( 11, "Prism11" ), ), } - def __isPolyhedronSupported( self, faceStream ) -> str: + def __isPolyhedronSupported( self, faceStream: FaceStream ) -> str: """Checks if a polyhedron can be converted into a supported cell. - If so, returns the name of the type. If not, the returned name will be empty. Args: - faceStream (_type_): The polyhedron. + faceStream: The polyhedron. Returns: str: The name of the supported type or an empty string. """ - cellGraph = buildFaceToFaceConnectivityThroughEdges( faceStream, add_compatibility=True ) - if cellGraph.order() not in self.__referenceGraphs: - return "" + cellGraph = buildFaceToFaceConnectivityThroughEdges( faceStream, addCompatibility=True ) for referenceGraph in self.__referenceGraphs[ cellGraph.order() ]: if networkx.is_isomorphic( referenceGraph, cellGraph ): return str( referenceGraph.name ) return "" def __call__( self, ic: int ) -> int: - """Checks if a vtk polyhedron cell can be converted into a supported GEOS element. + """Check if a vtk polyhedron cell can be converted into a supported GEOS element. Args: - ic (int): The index element. + ic (int): The index of the element. Returns: - int: -1 if the polyhedron vtk element can be converted into a supported element type. The index otherwise. + int: -1 if the polyhedron vtk element can be converted into a supported element type, the index otherwise. """ global MESH - assert MESH is not None, f"MESH global variable not initialized in worker process (PID: {multiprocessing.current_process().pid}). This should have been set by initWorkerMesh." + assert MESH is not None if MESH.GetCellType( ic ) != VTK_POLYHEDRON: return -1 ptIds = vtkIdList() @@ -128,45 +121,80 @@ def __call__( self, ic: int ) -> int: setupLogger.debug( f"Polyhedron cell {ic} can be converted into \"{convertedTypeName}\"" ) return -1 else: - setupLogger.debug( - f"Polyhedron cell {ic} (in PID {multiprocessing.current_process().pid}) cannot be converted into any supported element." - ) + setupLogger.debug( f"Polyhedron cell {ic} cannot be converted into any supported element." ) return ic -def __action( vtkInputFile: str, options: Options ) -> Result: - # Main process loads the mesh for its own use - mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtkInputFile ) - if hasattr( mesh, "GetDistinctCellTypesArray" ): - cellTypesNumpy = vtk_to_numpy( mesh.GetDistinctCellTypesArray() ) - cellTypes = set( cellTypesNumpy.tolist() ) +def findUnsupportedStdElementsTypes( mesh: vtkUnstructuredGrid ) -> list[ str ]: + """Find unsupported standard element types in the mesh. + + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + + Returns: + list[ str ]: List of unsupported element type descriptions. + """ + if hasattr( mesh, "GetDistinctCellTypesArray" ): # For more recent versions of vtk. + uniqueCellTypes = set( vtk_to_numpy( mesh.GetDistinctCellTypesArray() ) ) else: - vtkCellTypesObj = vtkCellTypes() - mesh.GetCellTypes( vtkCellTypesObj ) - cellTypes = set( vtkIter( vtkCellTypesObj ) ) + _vtkCellTypes = vtkCellTypes() + mesh.GetCellTypes( _vtkCellTypes ) + uniqueCellTypes = set( vtkIter( _vtkCellTypes ) ) + resultValues: set[ int ] = uniqueCellTypes - supportedCellTypes + results = [ f"Type {i}: {vtkCellTypes.GetClassNameFromTypeId( i )}" for i in frozenset( resultValues ) ] + return results + + +def findUnsupportedPolyhedronElements( mesh: vtkUnstructuredGrid, options: Options ) -> list[ int ]: + """Find unsupported polyhedron elements in the mesh. - supportedCellTypes = { - VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_PENTAGONAL_PRISM, VTK_POLYHEDRON, VTK_PYRAMID, VTK_TETRA, VTK_VOXEL, - VTK_WEDGE - } - unsupportedStdElementsTypes = cellTypes - supportedCellTypes + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The options for processing. + Returns: + list[ int ]: List of element indices for unsupported polyhedrons. + """ # Dealing with polyhedron elements. - numCells = mesh.GetNumberOfCells() - polyhedronConverter = IsPolyhedronConvertible() + numCells: int = mesh.GetNumberOfCells() + result = ones( numCells, dtype=int ) * -1 + # Use the initializer to set up each worker process + # Pass the mesh to the initializer + with multiprocessing.Pool( processes=options.nproc, initializer=initWorker, initargs=( mesh, ) ) as pool: + # Pass a mesh-free instance of the class to the workers. + # The MESH global will already be set in each worker. + generator = pool.imap_unordered( IsPolyhedronConvertible(), range( numCells ), chunksize=options.chunkSize ) + for i, val in enumerate( tqdm( generator, total=numCells, desc="Testing support for elements" ) ): + result[ i ] = val + + return [ i for i in result if i > -1 ] - unsupportedPolyhedronIndices = [] - # Pass the vtkInputFile to the initializer - with multiprocessing.Pool( processes=options.nproc, initializer=initWorkerMesh, - initargs=( vtkInputFile, ) ) as pool: # Comma makes it a tuple - generator = pool.imap_unordered( polyhedronConverter, range( numCells ), chunksize=options.chunkSize ) - for cellIndexOrNegOne in tqdm( generator, total=numCells, desc="Testing support for elements" ): - if cellIndexOrNegOne != -1: - unsupportedPolyhedronIndices.append( cellIndexOrNegOne ) - return Result( unsupportedStdElementsTypes=frozenset( unsupportedStdElementsTypes ), - unsupportedPolyhedronElements=frozenset( unsupportedPolyhedronIndices ) ) +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs the supported elements check on a vtkUnstructuredGrid. + Args: + mesh (vtkUnstructuredGrid): The input mesh to analyze. + options (Options): The options for processing. + + Returns: + Result: The result of the supported elements check. + """ + unsupportedStdElementsTypes: list[ str ] = findUnsupportedStdElementsTypes( mesh ) + unsupportedPolyhedronElements: list[ int ] = findUnsupportedPolyhedronElements( mesh, options ) + return Result( unsupportedStdElementsTypes=unsupportedStdElementsTypes, + unsupportedPolyhedronElements=frozenset( unsupportedPolyhedronElements ) ) -def action( vtkInputFile: str, options: Options ) -> Result: - return __action( vtkInputFile, options ) + +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs the supported elements check on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of the supported elements check. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py index ff69cb80..6e90c9d8 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py @@ -7,6 +7,7 @@ def __divClamp( num: float, den: float ) -> float: """Computes the division `num / den`. and clamps the result between 0 and 1. + If `den` is zero, the result of the division is set to 0. Args: @@ -74,7 +75,7 @@ def distanceBetweenTwoSegments( x0: numpy.ndarray, d0: numpy.ndarray, x1: numpy. def __computeNodesToTriangleDistance( - tri0: numpy.ndarray, edges0, tri1: numpy.ndarray + tri0: numpy.ndarray, edges0: numpy.ndarray, tri1: numpy.ndarray ) -> tuple[ Union[ float, None ], Union[ numpy.ndarray, None ], Union[ numpy.ndarray, None ], bool ]: """Computes the distance from nodes of `tri1` points onto `tri0`. @@ -114,19 +115,20 @@ def __computeNodesToTriangleDistance( if point > -1: areDisjoint = True # But we must check that its projection is inside `tri0`. - if numpy.dot( tri1[ point ] - tri0[ 0 ], numpy.cross( tri0Normal, edges0[ 0 ] ) ) > 0: - if numpy.dot( tri1[ point ] - tri0[ 1 ], numpy.cross( tri0Normal, edges0[ 1 ] ) ) > 0: - if numpy.dot( tri1[ point ] - tri0[ 2 ], numpy.cross( tri0Normal, edges0[ 2 ] ) ) > 0: - # It is! - sol0 = tri1[ point ] - sol1 = tri1[ point ] + ( tri1Proj[ point ] / tri0NormalNorm ) * tri0Normal - return float( norm( sol1 - sol0 ) ), sol0, sol1, areDisjoint + if numpy.dot( tri1[ point ] - tri0[ 0 ], numpy.cross( tri0Normal, edges0[ 0 ] ) ) > 0 and \ + numpy.dot( tri1[ point ] - tri0[ 1 ], numpy.cross( tri0Normal, edges0[ 1 ] ) ) > 0 and \ + numpy.dot( tri1[ point ] - tri0[ 2 ], numpy.cross( tri0Normal, edges0[ 2 ] ) ) > 0: + # It is! + sol0 = tri1[ point ] + sol1 = tri1[ point ] + ( tri1Proj[ point ] / tri0NormalNorm ) * tri0Normal + return float( norm( sol1 - sol0 ) ), sol0, sol1, areDisjoint return None, None, None, areDisjoint def distanceBetweenTwoTriangles( tri0: numpy.ndarray, tri1: numpy.ndarray ) -> tuple[ float, numpy.ndarray, numpy.ndarray ]: """Returns the minimum distance between two triangles, and the two points where this minimum occurs. + If the two triangles touch, then distance is exactly 0. But the two points are dummy and cannot be used as contact points (they are still though). diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py index 4a637aae..c695be1b 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py @@ -17,8 +17,9 @@ class Result: def parseFaceStream( ids: vtkIdList ) -> Sequence[ Sequence[ int ] ]: - """Parses the face stream raw information and converts it into a tuple of tuple of integers, - each tuple of integer being the nodes of a face. + """Parses the face stream raw information and converts it into a tuple of tuple of integers. + + Each tuple of integer corresponds to the nodes of a face. Args: ids (vtkIdList): The raw vtk face stream. @@ -33,7 +34,7 @@ def parseFaceStream( ids: vtkIdList ) -> Sequence[ Sequence[ int ] ]: while True: numNodes = next( it ) tmp = [] - for i in range( numNodes ): + for _ in range( numNodes ): tmp.append( next( it ) ) result.append( tuple( tmp ) ) except StopIteration: @@ -45,18 +46,17 @@ def parseFaceStream( ids: vtkIdList ) -> Sequence[ Sequence[ int ] ]: class FaceStream: - """ - Helper class to manipulate the vtk face streams. - """ + """Helper class to manipulate the vtk face streams.""" - def __init__( self, data: Sequence[ Sequence[ int ] ] ): + def __init__( self, data: Sequence[ Sequence[ int ] ] ) -> None: + """Initializes the FaceStream instance.""" # self.__data contains the list of faces nodes, like it appears in vtk face streams. # Except that the additional size information is removed # in favor of the __len__ of the containers. self.__data: Sequence[ Sequence[ int ] ] = data @staticmethod - def buildFromVtkIdList( ids: vtkIdList ): + def buildFromVtkIdList( ids: vtkIdList ) -> "FaceStream": """Builds a FaceStream from the raw vtk face stream. Args: @@ -78,7 +78,7 @@ def faceNodes( self ) -> Iterable[ Sequence[ int ] ]: @property def numFaces( self ) -> int: - """Number of faces in the face stream + """Number of faces in the face stream. Returns: int: The number of faces. @@ -133,6 +133,7 @@ def flipFaces( self, faceIndices: Collection[ int ] ) -> "FaceStream": def dump( self ) -> Sequence[ int ]: """Returns the face stream awaited by vtk, but in a python container. + The content can be used, once converted to a vtkIdList, to define another polyhedron in vtk. Returns: @@ -144,7 +145,8 @@ def dump( self ) -> Sequence[ int ]: result += faceNodes return tuple( result ) - def __repr__( self ): + def __repr__( self ) -> str: + """String representation of the face stream.""" result = [ str( len( self.__data ) ) ] for faceNodes in self.__data: result.append( str( len( faceNodes ) ) ) @@ -152,8 +154,9 @@ def __repr__( self ): return ",\n".join( result ) -def buildFaceToFaceConnectivityThroughEdges( faceStream: FaceStream, addCompatibility=False ) -> networkx.Graph: +def buildFaceToFaceConnectivityThroughEdges( faceStream: FaceStream, addCompatibility: bool = False ) -> networkx.Graph: """Given a face stream/polyhedron, builds the connections between the faces. + Those connections happen when two faces share an edge. Args: diff --git a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py index 2f1dfddc..3d2bf3d3 100644 --- a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py +++ b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py @@ -4,7 +4,8 @@ from geos.mesh_doctor.register import registerParsingActions -def main(): +def main() -> None: + """Main function for mesh-doctor CLI.""" parseAndSetVerbosity( sys.argv ) mainParser, allActions, allActionsHelpers = registerParsingActions() args = mainParser.parse_args( sys.argv[ 1: ] ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index 31e096f1..6e0ae626 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -51,8 +51,7 @@ def getOptionsUsedMessage( optionsUsed: object ) -> str: def fillSubparser( subparsers: argparse._SubParsersAction, subparserName: str, helpMessage: str, orderedCheckNames: list[ str ], checkFeaturesConfig: dict[ str, CheckFeature ] ) -> None: - """ - Fills a subparser with arguments for performing a set of checks. + """Fills a subparser with arguments for performing a set of checks. Args: subparsers: The subparsers action from argparse. @@ -86,8 +85,18 @@ def fillSubparser( subparsers: argparse._SubParsersAction, subparserName: str, h def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], checkFeaturesConfig: dict[ str, CheckFeature ] ) -> AllChecksOptions: - """ - Converts parsed command-line arguments into an AllChecksOptions object based on the provided configuration. + """Converts parsed command-line arguments into an AllChecksOptions object based on the provided configuration. + + Args: + parsedArgs (argparse.Namespace): Parsed command-line arguments. + orderedCheckNames (list[ str ]): Ordered list of check names. + checkFeaturesConfig (dict[ str, CheckFeature ]): Configuration dictionary for check features. + + Raises: + ValueError: If no valid checks are selected. + + Returns: + AllChecksOptions: The options for all checks to be performed. """ # 1. Determine which checks to perform checksToDo = getattr( parsedArgs, CHECKS_TO_DO_ARG ) @@ -139,8 +148,8 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], finalCheckParams[ checkNameKey ][ name ] = valueFloat # 3. Instantiate Options objects for the selected checks - individualCheckOptions: dict[ str, Any ] = dict() - individualCheckDisplay: dict[ str, Any ] = dict() + individualCheckOptions: dict[ str, Any ] = {} + individualCheckDisplay: dict[ str, Any ] = {} for checkName in list( finalCheckParams.keys() ): params = finalCheckParams[ checkName ] @@ -159,7 +168,12 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], # Generic display of Results def displayResults( options: AllChecksOptions, result: AllChecksResult ) -> None: - """Displays the results of all the checks that have been performed.""" + """Displays the results of all the checks that have been performed. + + Args: + options (AllChecksOptions): The options used for the checks. + result (AllChecksResult): The result of the checks. + """ if not options.checksToPerform: setupLogger.results( "No checks were performed or all failed during configuration." ) return diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py index eb764fbc..ace87cf2 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py @@ -2,7 +2,7 @@ from copy import deepcopy from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser - as sharedFillSubparser, displayResults ) + as sharedFillSubparser, displayResults ) # noqa: F401 from geos.mesh_doctor.parsing import ( ALL_CHECKS, COLLOCATES_NODES, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py index 3f43bca6..f1f20bf6 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py @@ -1 +1 @@ -# empty: the check is not available yet! \ No newline at end of file +# empty: the check is not available yet! diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py index d185d981..4e96ef38 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py @@ -16,12 +16,19 @@ def parseCommaSeparatedString( value: str ) -> list[ str ]: """Helper to parse comma-separated strings, stripping whitespace and removing empty items.""" if not value or not value.strip(): - return list() + return [] return [ item.strip() for item in value.split( ',' ) if item.strip() ] def parseAndSetVerbosity( cliArgs: list[ str ] ) -> None: """Parse the verbosity flag only and set the root logger's level accordingly. + + The verbosity is controlled via -v and -q flags: + No flags: WARNING level + -v: INFO level + -vv or more: DEBUG level + -q: ERROR level + -qq or more: CRITICAL level Messages from loggers created with `get_custom_logger` will inherit this level if their own level is set to NOTSET. @@ -72,13 +79,17 @@ def parseAndSetVerbosity( cliArgs: list[ str ] ) -> None: def initParser() -> argparse.ArgumentParser: + """Initialize the main argument parser for mesh-doctor.""" vtkInputFileKey = "vtkInputFile" epilogMsg = f"""\ Note that checks are dynamically loaded. An option may be missing because of an unloaded module. Increase verbosity (-{__VERBOSITY_FLAG}, -{__VERBOSITY_FLAG * 2}) to get full information. """ - formatter = lambda prog: argparse.RawTextHelpFormatter( prog, max_help_position=8 ) + + def formatter( prog: str ) -> argparse.RawTextHelpFormatter: + return argparse.RawTextHelpFormatter( prog, max_help_position=8 ) + parser = argparse.ArgumentParser( description='Inspects meshes for GEOS.', epilog=textwrap.dedent( epilogMsg ), formatter_class=formatter ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index 559dc537..43b7a277 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -1,3 +1,5 @@ +from argparse import _SubParsersAction +from typing import Any from geos.mesh_doctor.actions.collocatedNodes import Options, Result from geos.mesh_doctor.parsing import COLLOCATES_NODES from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage @@ -9,11 +11,24 @@ __COLLOCATED_NODES_DEFAULT = { __TOLERANCE: __TOLERANCE_DEFAULT } -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for supported elements check. + """ return Options( parsedOptions[ __TOLERANCE ] ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( COLLOCATES_NODES, help="Checks if nodes are collocated." ) p.add_argument( '--' + __TOLERANCE, type=float, @@ -23,26 +38,47 @@ def fillSubparser( subparsers ) -> None: help="[float]: The absolute distance between two nodes for them to be considered collocated." ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the collocated nodes check. + + Args: + options: The options used for the check. + result: The result of the collocated nodes check. + """ setupLogger.results( getOptionsUsedMessage( options ) ) + loggerResults( setupLogger, result.nodesBuckets, result.wrongSupportElements ) + + +def loggerResults( logger: Any, nodesBuckets: list[ tuple[ int ] ], wrongSupportElements: list[ int ] ) -> None: + """Log the results of the collocated nodes check. + + Args: + logger: Logger instance for output. + nodesBuckets (list[ tuple[ int ] ]): List of collocated nodes buckets. + wrongSupportElements (list[ int ]): List of elements with wrong support nodes. + """ + # Accounts for external logging object that would not contain 'results' attribute + logMethod = logger.info + if hasattr( logger, 'results' ): + logMethod = logger.results + allCollocatedNodes: list[ int ] = [] - for bucket in result.nodesBuckets: + for bucket in nodesBuckets: for node in bucket: allCollocatedNodes.append( node ) - allCollocatedNodesUnique: frozenset[ int ] = frozenset( allCollocatedNodes ) # Surely useless + allCollocatedNodesUnique = list( set( allCollocatedNodes ) ) if allCollocatedNodesUnique: - setupLogger.results( f"You have {len( allCollocatedNodesUnique )} collocated nodes." ) - setupLogger.results( "Here are all the buckets of collocated nodes." ) + logMethod( f"You have {len( allCollocatedNodesUnique )} collocated nodes." ) + logMethod( "Here are all the buckets of collocated nodes." ) tmp: list[ str ] = [] - for bucket in result.nodesBuckets: - tmp.append( f"({', '.join(map(str, bucket))})" ) - setupLogger.results( f"({', '.join(tmp)})" ) + for bucket in nodesBuckets: + tmp.append( f"({', '.join( map( str, bucket ) )})" ) + logMethod( f"({', '.join( tmp )})" ) else: - setupLogger.results( "You have no collocated node." ) + logMethod( "You have no collocated node." ) - if result.wrongSupportElements: - wsElements: str = ", ".join( map( str, result.wrongSupportElements ) ) - setupLogger.results( f"You have {len(result.wrongSupportElements)} elements with duplicated support nodes.\n" + - wsElements ) + if wrongSupportElements: + wsElements: str = ", ".join( map( str, wrongSupportElements ) ) + logMethod( f"You have {len( wrongSupportElements )} elements with duplicated support nodes.\n" + wsElements ) else: - setupLogger.results( "You have no element with duplicated support nodes." ) + logMethod( "You have no element with duplicated support nodes." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py index 536c7212..1481f40e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py @@ -1,3 +1,5 @@ +from argparse import _SubParsersAction +from typing import Any from geos.mesh_doctor.actions.elementVolumes import Options, Result from geos.mesh_doctor.parsing import ELEMENT_VOLUMES from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage @@ -9,7 +11,12 @@ __ELEMENT_VOLUMES_DEFAULT = { __MIN_VOLUME: __MIN_VOLUME_DEFAULT } -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( ELEMENT_VOLUMES, help=f"Checks if the volumes of the elements are greater than \"{__MIN_VOLUME}\"." ) p.add_argument( '--' + __MIN_VOLUME, @@ -20,7 +27,7 @@ def fillSubparser( subparsers ) -> None: help=f"[float]: The minimum acceptable volume. Defaults to {__MIN_VOLUME_DEFAULT}." ) -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: """From the parsed cli options, return the converted options for elements volumes check. Args: @@ -32,13 +39,31 @@ def convert( parsedOptions ) -> Options: return Options( minVolume=parsedOptions[ __MIN_VOLUME ] ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the element volumes check. + + Args: + options: The options used for the check. + result: The result of the element volumes check. + """ setupLogger.results( getOptionsUsedMessage( options ) ) - setupLogger.results( - f"You have {len(result.elementVolumes)} elements with volumes smaller than {options.minVolume}." ) - if result.elementVolumes: - setupLogger.results( "Elements index | Volumes calculated" ) - setupLogger.results( "-----------------------------------" ) - maxLength: int = len( "Elements index " ) - for ( ind, volume ) in result.elementVolumes: - setupLogger.results( f"{ind:<{maxLength}}" + "| " + str( volume ) ) + loggerResults( setupLogger, result.elementVolumes ) + + +def loggerResults( logger: Any, elementVolumes: list[ tuple[ int, float ] ] ) -> None: + """Show the results of the element volumes check. + + Args: + logger: Logger instance for output. + elementVolumes (list[ tuple[ int, float ] ]): List of element volumes. + """ + # Accounts for external logging object that would not contain 'results' attribute + logMethod = logger.info + if hasattr( logger, 'results' ): + logMethod = logger.results + + logMethod( "Elements index | Volumes calculated" ) + logMethod( "-----------------------------------" ) + max_length: int = len( "Elements index " ) + for ( ind, volume ) in elementVolumes: + logMethod( f"{ind:<{max_length}}" + "| " + str( volume ) ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py index d3518e8d..bd87e905 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py @@ -1,3 +1,4 @@ +from argparse import _SubParsersAction import random from vtkmodules.vtkCommonDataModel import ( VTK_HEXAGONAL_PRISM, @@ -8,6 +9,7 @@ VTK_VOXEL, VTK_WEDGE, ) +from typing import Any from geos.mesh_doctor.actions.fixElementsOrderings import Options, Result from geos.mesh_doctor.parsing import vtkOutputParsing, FIX_ELEMENTS_ORDERINGS from geos.mesh_doctor.parsing.cliParsing import setupLogger @@ -33,7 +35,12 @@ } -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( FIX_ELEMENTS_ORDERINGS, help="Reorders the support nodes for the given cell types." ) for key, vtkKey in __CELL_TYPE_MAPPING.items(): tmp = list( range( __CELL_TYPE_SUPPORT_SIZE[ vtkKey ] ) ) @@ -47,7 +54,7 @@ def fillSubparser( subparsers ) -> None: vtkOutputParsing.fillVtkOutputSubparser( p ) -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: """From the parsed cli options, return the converted options for self intersecting elements check. Args: @@ -64,7 +71,7 @@ def convert( parsedOptions ) -> Options: rawMapping = parsedOptions[ key ] if rawMapping: tmp = tuple( map( int, rawMapping.split( "," ) ) ) - if not set( tmp ) == set( range( __CELL_TYPE_SUPPORT_SIZE[ vtkKey ] ) ): + if set( tmp ) != set( range( __CELL_TYPE_SUPPORT_SIZE[ vtkKey ] ) ): errMsg = f"Permutation {rawMapping} for type {key} is not valid." setupLogger.error( errMsg ) raise ValueError( errMsg ) @@ -73,7 +80,13 @@ def convert( parsedOptions ) -> Options: return Options( vtkOutput=vtkOutput, cellTypeToOrdering=cellTypeToOrdering ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the fix elements orderings feature. + + Args: + options: The options used for the fix. + result: The result of the fix elements orderings feature. + """ if result.output: setupLogger.info( f"New mesh was written to file '{result.output}'" ) if result.unchangedCellTypes: diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index 9f6ab08b..8f719c6f 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -1,3 +1,5 @@ +from argparse import _SubParsersAction +from typing import Any from geos.mesh_doctor.actions.generateCube import Options, Result, FieldInfo from geos.mesh_doctor.parsing import vtkOutputParsing, generateGlobalIdsParsing, GENERATE_CUBE from geos.mesh_doctor.parsing.cliParsing import setupLogger @@ -7,9 +9,16 @@ __FIELDS = "fields" -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. - def checkDiscretizations( x, nx, title ): + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for supported elements check. + """ + def checkDiscretizations( x: tuple[ float, ... ], nx: tuple[ int, ... ], title: str ) -> None: if len( x ) != len( nx ) + 1: raise ValueError( f"{title} information (\"{x}\" and \"{nx}\") does not have consistent size." ) @@ -17,18 +26,18 @@ def checkDiscretizations( x, nx, title ): checkDiscretizations( parsedOptions[ __Y ], parsedOptions[ __NY ], __Y ) checkDiscretizations( parsedOptions[ __Z ], parsedOptions[ __NZ ], __Z ) - def parseFields( s ): + def parseFields( s: str ) -> FieldInfo: name, support, dim = s.split( ":" ) if support not in ( "CELLS", "POINTS" ): raise ValueError( f"Support {support} for field \"{name}\" must be one of \"CELLS\" or \"POINTS\"." ) try: - dim = int( dim ) - assert dim > 0 - except ValueError: - raise ValueError( f"Dimension {dim} cannot be converted to an integer." ) - except AssertionError: - raise ValueError( f"Dimension {dim} must be a positive integer" ) - return FieldInfo( name=name, support=support, dimension=dim ) + dimension = int( dim ) + assert dimension > 0 + except ValueError as e: + raise ValueError( f"Dimension {dimension} cannot be converted to an integer." ) from e + except AssertionError as e: + raise ValueError( f"Dimension {dimension} must be a positive integer" ) from e + return FieldInfo( name=name, support=support, dimension=dimension ) gids: GlobalIdsInfo = generateGlobalIdsParsing.convertGlobalIds( parsedOptions ) @@ -44,7 +53,12 @@ def parseFields( s ): fields=tuple( map( parseFields, parsedOptions[ __FIELDS ] ) ) ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( GENERATE_CUBE, help="Generate a cube and its fields." ) p.add_argument( '--' + __X, type=lambda s: tuple( map( float, s.split( ":" ) ) ), @@ -81,5 +95,11 @@ def fillSubparser( subparsers ) -> None: vtkOutputParsing.fillVtkOutputSubparser( p ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the generate cube feature. + + Args: + options: The options used for the check. + result: The result of the generate cube feature. + """ setupLogger.info( result.info ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py index 87413cdf..15b90d0f 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py @@ -1,4 +1,7 @@ +from __future__ import annotations +from argparse import _SubParsersAction import os +from typing import Any from geos.mesh_doctor.actions.generateFractures import Options, Result, FracturePolicy from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_FRACTURES from geos.mesh.io.vtkIO import VtkOutput @@ -19,6 +22,7 @@ def convertToFracturePolicy( s: str ) -> FracturePolicy: """Converts the user input to the proper enum chosen. + I do not want to use the auto conversion already available to force explicit conversion. Args: @@ -37,7 +41,12 @@ def convertToFracturePolicy( s: str ) -> FracturePolicy: raise ValueError( f"Policy {s} is not valid. Please use one of \"{', '.join(map(str, __POLICIES))}\"." ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( GENERATE_FRACTURES, help="Splits the mesh to generate the faults and fractures." ) p.add_argument( '--' + __POLICY, type=convertToFracturePolicy, @@ -58,23 +67,31 @@ def fillSubparser( subparsers ) -> None: help= f"[list of comma separated integers]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, which changes of the field will be considered " f"as a fracture. If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, list of the fracture attributes. " - f"You can create multiple fractures by separating the values with ':' like shown in this example. " + "You can create multiple fractures by separating the values with ':' like shown in this example. " f"--{__FIELD_VALUES} 10,12:13,14,16,18:22 will create 3 fractures identified respectively with the values (10,12), (13,14,16,18) and (22). " - f"If no ':' is found, all values specified will be assumed to create only 1 single fracture." ) + "If no ':' is found, all values specified will be assumed to create only 1 single fracture." ) vtkOutputParsing.fillVtkOutputSubparser( p ) p.add_argument( '--' + __FRACTURES_OUTPUT_DIR, type=str, - help=f"[string]: The output directory for the fractures meshes that will be generated from the mesh." ) + help="[string]: The output directory for the fractures meshes that will be generated from the mesh." ) p.add_argument( '--' + __FRACTURES_DATA_MODE, type=str, metavar=", ".join( __FRACTURES_DATA_MODE_VALUES ), default=__FRACTURES_DATA_MODE_DEFAULT, - help=f'[string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary.' ) + help='[string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary.' ) -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for supported elements check. + """ policy: str = parsedOptions[ __POLICY ] field: str = parsedOptions[ __FIELD_NAME ] allValues: str = parsedOptions[ __FIELD_VALUES ] @@ -105,22 +122,49 @@ def convert( parsedOptions ) -> Options: allFracturesVtkOutput=allFracturesVtkOutput ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the generate fractures feature. + + Args: + options: The options used for the check. + result: The result of the generate fractures feature. + """ pass def areValuesParsable( values: str ) -> bool: + """Check if a string contains parsable values. + + Args: + values (str): The string containing values to be checked. + + Returns: + bool: True if the string contains parsable values, False otherwise. + """ if not all( character.isdigit() or character in { ':', ',' } for character in values ): return False if values.startswith( ":" ) or values.startswith( "," ): return False - if values.endswith( ":" ) or values.endswith( "," ): - return False - return True + return not ( values.endswith( ":" ) or values.endswith( "," ) ) def buildAllFracturesVtkOutput( fractureOutputDir: str, fracturesDataMode: bool, meshVtkOutput: VtkOutput, fractureNames: list[ str ] ) -> list[ VtkOutput ]: + """Create all the VtkOutput objects for every fracture mesh that will be output. + + Args: + fractureOutputDir (str): The directory where fracture output files will be saved. + fracturesDataMode (bool): Whether the fractures data mode is enabled. + meshVtkOutput (VtkOutput): The VtkOutput object for the mesh. + fractureNames (list[ str ]): The list of fracture file names. + + Raises: + ValueError: If the given fracture output directory does not exist. + ValueError: If the given fracture output directory is not writable. + + Returns: + list[ VtkOutput ]: All the VtkOutput objects for every fracture mesh that will be output. + """ if not os.path.exists( fractureOutputDir ): raise ValueError( f"The --{__FRACTURES_OUTPUT_DIR} given directory '{fractureOutputDir}' does not exist." ) @@ -130,7 +174,7 @@ def buildAllFracturesVtkOutput( fractureOutputDir: str, fracturesDataMode: bool, outputName = os.path.basename( meshVtkOutput.output ) splittedNameWithoutExtension: list[ str ] = outputName.split( "." )[ :-1 ] nameWithoutExtension: str = '_'.join( splittedNameWithoutExtension ) + "_" - allFracturesVtkOutput: list[ VtkOutput ] = list() + allFracturesVtkOutput: list[ VtkOutput ] = [] for fractureName in fractureNames: fracturePath = os.path.join( fractureOutputDir, nameWithoutExtension + fractureName ) allFracturesVtkOutput.append( VtkOutput( fracturePath, fracturesDataMode ) ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py index e704c790..016e5178 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py @@ -1,4 +1,6 @@ +from argparse import _SubParsersAction from dataclasses import dataclass +from typing import Any from geos.mesh_doctor.actions.generateGlobalIds import Options, Result from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_GLOBAL_IDS from geos.mesh_doctor.parsing.cliParsing import setupLogger @@ -12,18 +14,28 @@ class GlobalIdsInfo: points: bool -def convertGlobalIds( parsedOptions ) -> GlobalIdsInfo: - return GlobalIdsInfo( cells=parsedOptions[ __CELLS ], points=parsedOptions[ __POINTS ] ) +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + Args: + parsedOptions: Dictionary of parsed command-line options. -def convert( parsedOptions ) -> Options: - gids: GlobalIdsInfo = convertGlobalIds( parsedOptions ) + Returns: + Options: Configuration options for supported elements check. + """ + gids: GlobalIdsInfo = GlobalIdsInfo( cells=parsedOptions[ __CELLS ], points=parsedOptions[ __POINTS ] ) return Options( vtkOutput=vtkOutputParsing.convert( parsedOptions ), generateCellsGlobalIds=gids.cells, generatePointsGlobalIds=gids.points ) -def fillGenerateGlobalIdsSubparser( p ): +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ + p = subparsers.add_parser( GENERATE_GLOBAL_IDS, help="Adds globals ids for points and cells." ) p.add_argument( '--' + __CELLS, action="store_true", help="[bool]: Generate global ids for cells. Defaults to true." ) @@ -40,13 +52,14 @@ def fillGenerateGlobalIdsSubparser( p ): dest=__POINTS, help="[bool]: Don't generate global ids for points." ) p.set_defaults( **{ __POINTS: True } ) - - -def fillSubparser( subparsers ) -> None: - p = subparsers.add_parser( GENERATE_GLOBAL_IDS, help="Adds globals ids for points and cells." ) - fillGenerateGlobalIdsSubparser( p ) vtkOutputParsing.fillVtkOutputSubparser( p ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the generate global ids feature. + + Args: + options: The options used for the check. + result: The result of the generate global ids feature. + """ setupLogger.info( result.info ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py index 152672d5..7e1dbd3f 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py @@ -2,7 +2,7 @@ from copy import deepcopy from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser - as sharedFillSubparser, displayResults ) + as sharedFillSubparser, displayResults ) # noqa: F401 from geos.mesh_doctor.parsing import ( MAIN_CHECKS, COLLOCATES_NODES, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index d2a34808..b6b02bc9 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -1,3 +1,5 @@ +from argparse import _SubParsersAction +from typing import Any from geos.mesh_doctor.actions.nonConformal import Options, Result from geos.mesh_doctor.parsing import NON_CONFORMAL from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage @@ -18,13 +20,26 @@ } -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for supported elements check. + """ return Options( angleTolerance=parsedOptions[ __ANGLE_TOLERANCE ], pointTolerance=parsedOptions[ __POINT_TOLERANCE ], faceTolerance=parsedOptions[ __FACE_TOLERANCE ] ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( NON_CONFORMAL, help="Detects non conformal elements. [EXPERIMENTAL]" ) p.add_argument( '--' + __ANGLE_TOLERANCE, type=float, @@ -45,11 +60,32 @@ def fillSubparser( subparsers ) -> None: help=f"[float]: tolerance for two faces to be considered \"touching\". Defaults to {__FACE_TOLERANCE_DEFAULT}" ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the non conformal elements check. + + Args: + options: The options used for the check. + result: The result of the non conformal elements check. + """ setupLogger.results( getOptionsUsedMessage( options ) ) - nonConformalCells: list[ int ] = [] - for i, j in result.nonConformalCells: - nonConformalCells += i, j - nonConformalCellsUnique: frozenset[ int ] = frozenset( nonConformalCells ) - setupLogger.results( f"You have {len( nonConformalCellsUnique )} non conformal cells." ) - setupLogger.results( f"{', '.join( map( str, sorted( nonConformalCellsUnique ) ) )}" ) + loggerResults( setupLogger, result.nonConformalCells ) + + +def loggerResults( logger: Any, nonConformalCells: list[ tuple[ int, int ] ] ) -> None: + """Log the results of the non-conformal cells check. + + Args: + logger: Logger instance for output. + nonConformalCells (list[ tuple[ int, int ] ]): List of non-conformal cells. + """ + # Accounts for external logging object that would not contain 'results' attribute + logMethod = logger.info + if hasattr( logger, 'results' ): + logMethod = logger.results + + cells: list[ int ] = [] + for i, j in nonConformalCells: + cells += i, j + uniqueCells: frozenset[ int ] = frozenset( cells ) + logMethod( f"You have {len( uniqueCells )} non conformal cells." ) + logMethod( f"{', '.join( map( str, sorted( uniqueCells ) ) )}" ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index 2749d5e1..2ac20285 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -1,4 +1,6 @@ +from argparse import _SubParsersAction import numpy +from typing import Any from geos.mesh_doctor.actions.selfIntersectingElements import Options, Result from geos.mesh_doctor.parsing import SELF_INTERSECTING_ELEMENTS from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage @@ -10,11 +12,23 @@ __SELF_INTERSECTING_ELEMENTS_DEFAULT = { __MIN_DISTANCE: __MIN_DISTANCE_DEFAULT } -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for self intersecting elements check. + + Raises: + ValueError: If minimum distance is negative. + """ minDistance = parsedOptions[ __MIN_DISTANCE ] if minDistance == 0: setupLogger.warning( - "Having minimum distance set to 0 can induce lots of false positive results (adjacent faces may be considered intersecting)." + "Having minimum distance set to 0 can induce lots of false positive results " + "(adjacent faces may be considered intersecting)." ) elif minDistance < 0: raise ValueError( @@ -22,22 +36,66 @@ def convert( parsedOptions ) -> Options: return Options( minDistance=minDistance ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add self intersecting elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( SELF_INTERSECTING_ELEMENTS, help="Checks if the faces of the elements are self intersecting." ) + help_text = ( + "[float]: The minimum distance in the computation. " + f"Defaults to your machine precision {__MIN_DISTANCE_DEFAULT}." + ) p.add_argument( '--' + __MIN_DISTANCE, type=float, required=False, metavar=__MIN_DISTANCE_DEFAULT, default=__MIN_DISTANCE_DEFAULT, - help= - f"[float]: The minimum distance in the computation. Defaults to your machine precision {__MIN_DISTANCE_DEFAULT}." + help=help_text ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the self intersecting elements check. + + Args: + options: The options used for the check. + result: The result of the self intersecting elements check. + """ setupLogger.results( getOptionsUsedMessage( options ) ) - setupLogger.results( f"You have {len(result.intersectingFacesElements)} elements with self intersecting faces." ) - if result.intersectingFacesElements: - setupLogger.results( "The elements indices are:\n" + ", ".join( map( str, result.intersectingFacesElements ) ) ) + loggerResults( setupLogger, result.invalidCellIds ) + + +def loggerResults( logger: Any, invalidCellIds: dict[ str, list[ int ] ] ) -> None: + """Log the results of the self-intersecting elements check. + + Args: + logger: Logger instance for output. + invalidCellIds: Dictionary of invalid cell IDs by error type. + """ + # Accounts for external logging object that would not contain 'results' attribute + logMethod: Any = logger.info + if hasattr( logger, 'results' ): + logMethod = logger.results + + # Human-readable descriptions for each error type + error_descriptions = { + 'wrongNumberOfPointsElements': 'elements with wrong number of points', + 'intersectingEdgesElements': 'elements with intersecting edges', + 'intersectingFacesElements': 'elements with self intersecting faces', + 'nonContiguousEdgesElements': 'elements with non-contiguous edges', + 'nonConvexElements': 'non-convex elements', + 'facesOrientedIncorrectlyElements': 'elements with incorrectly oriented faces', + 'nonPlanarFacesElements': 'elements with non-planar faces', + 'degenerateFacesElements': 'elements with degenerate faces' + } + + # Log results for each error type that has invalid elements + for errorType, invalidIds in invalidCellIds.items(): + if invalidIds: + description = error_descriptions.get( errorType, f'elements with {errorType}' ) + logMethod( f"You have {len(invalidIds)} {description}." ) + logMethod( "The elements indices are:\n" + ", ".join( map( str, invalidIds ) ) ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py index 2b71647c..091954af 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py @@ -1,4 +1,6 @@ import multiprocessing +from argparse import _SubParsersAction +from typing import Any from geos.mesh_doctor.actions.supportedElements import Options, Result from geos.mesh_doctor.parsing import SUPPORTED_ELEMENTS from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage @@ -13,11 +15,24 @@ __SUPPORTED_ELEMENTS_DEFAULT = { __CHUNK_SIZE: __CHUNK_SIZE_DEFAULT, __NUM_PROC: __NUM_PROC_DEFAULT } -def convert( parsedOptions ) -> Options: +def convert( parsedOptions: dict[ str, Any ] ) -> Options: + """Convert parsed command-line options to Options object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + Options: Configuration options for supported elements check. + """ return Options( chunkSize=parsedOptions[ __CHUNK_SIZE ], nproc=parsedOptions[ __NUM_PROC ] ) -def fillSubparser( subparsers ) -> None: +def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: + """Add supported elements check subparser with its arguments. + + Args: + subparsers: The subparsers action to add the parser to. + """ p = subparsers.add_parser( SUPPORTED_ELEMENTS, help="Check that all the elements of the mesh are supported by GEOSX." ) p.add_argument( '--' + __CHUNK_SIZE, @@ -36,17 +51,39 @@ def fillSubparser( subparsers ) -> None: ) -def displayResults( options: Options, result: Result ): +def displayResults( options: Options, result: Result ) -> None: + """Display the results of the supported elements check. + + Args: + options: The options used for the check. + result: The result of the supported elements check. + """ setupLogger.results( getOptionsUsedMessage( options ) ) - if result.unsupportedPolyhedronElements: - setupLogger.results( f"There is/are {len(result.unsupportedPolyhedronElements)} polyhedra that may not be " - f"converted to supported elements." ) - setupLogger.results( - f"The list of the unsupported polyhedra is\n{tuple(sorted(result.unsupportedPolyhedronElements))}." ) + loggerResults( setupLogger, result.unsupportedPolyhedronElements, result.unsupportedStdElementsTypes ) + + +def loggerResults( logger: Any, unsupportedPolyhedronElements: frozenset[ int ], + unsupportedStdElementsTypes: list[ str ] ) -> None: + """Log the results of the supported elements check. + + Args: + logger: Logger instance for output. + unsupportedPolyhedronElements (frozenset[ int ]): List of unsupported polyhedron elements. + unsupportedStdElementsTypes (list[ str ]): List of unsupported standard element types. + """ + # Accounts for external logging object that would not contain 'results' attribute + logMethod: Any = logger.info + if hasattr( logger, 'results' ): + logMethod = logger.results + + if unsupportedPolyhedronElements: + logMethod( f"There is/are {len(unsupportedPolyhedronElements)} polyhedra that may not be converted to" + " supported elements." ) + logMethod( f"The list of the unsupported polyhedra is\n{tuple( sorted( unsupportedPolyhedronElements ) )}." ) else: - setupLogger.results( "All the polyhedra (if any) can be converted to supported elements." ) - if result.unsupportedStdElementsTypes: - setupLogger.results( f"There are unsupported vtk standard element types. The list of those vtk types is " - f"{tuple(sorted(result.unsupportedStdElementsTypes))}." ) + logMethod( "All the polyhedra (if any) can be converted to supported elements." ) + if unsupportedStdElementsTypes: + logMethod( "There are unsupported vtk standard element types. The list of those vtk types is" + f" {tuple( sorted( unsupportedStdElementsTypes ) )}." ) else: - setupLogger.results( "All the standard vtk element types (if any) are supported." ) + logMethod( "All the standard vtk element types (if any) are supported." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index fd44e4af..20ba8048 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -1,5 +1,7 @@ import os.path import textwrap +from argparse import ArgumentParser, _ArgumentGroup +from typing import Any from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput @@ -9,33 +11,68 @@ __OUTPUT_BINARY_MODE_DEFAULT = __OUTPUT_BINARY_MODE_VALUES[ 0 ] -def getVtkOutputHelp(): - msg = \ - f"""{__OUTPUT_FILE} [string]: The vtk output file destination. - {__OUTPUT_BINARY_MODE} [string]: For ".vtu" output format, the data mode can be {" or ".join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}.""" +def getVtkOutputHelp() -> str: + """Get help text for VTK output options. + + Returns: + str: Formatted help text describing output file and data mode options. + """ + msg = ( + f"{__OUTPUT_FILE} [string]: The vtk output file destination.\n" + f" {__OUTPUT_BINARY_MODE} [string]: For \".vtu\" output format, " + f"the data mode can be {' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. " + f"Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." + ) return textwrap.dedent( msg ) -def __buildArg( prefix, main ): +def __buildArg( prefix: str, main: str ) -> str: + """Build an argument name by joining prefix and main with a hyphen. + + Args: + prefix: The prefix string (can be empty). + main: The main argument name. + + Returns: + str: The joined argument name. + """ return "-".join( filter( None, ( prefix, main ) ) ) -def fillVtkOutputSubparser( parser, prefix="" ) -> None: +def fillVtkOutputSubparser( parser: ArgumentParser | _ArgumentGroup, prefix: str = "" ) -> None: + """Add VTK output arguments to an argument parser. + + Args: + parser: The argument parser or argument group to add arguments to. + prefix: Optional prefix for argument names. + """ parser.add_argument( '--' + __buildArg( prefix, __OUTPUT_FILE ), type=str, required=True, - help=f"[string]: The vtk output file destination." ) + help="[string]: The vtk output file destination." ) + help_text = ( + f"[string]: For \".vtu\" output format, the data mode can be " + f"{' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." + ) parser.add_argument( '--' + __buildArg( prefix, __OUTPUT_BINARY_MODE ), type=str, metavar=", ".join( __OUTPUT_BINARY_MODE_VALUES ), default=__OUTPUT_BINARY_MODE_DEFAULT, - help= - f"""[string]: For ".vtu" output format, the data mode can be {" or ".join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}.""" + help=help_text ) -def convert( parsedOptions, prefix="" ) -> VtkOutput: +def convert( parsedOptions: dict[ str, Any ], prefix: str = "" ) -> VtkOutput: + """Convert parsed command-line options to a VtkOutput object. + + Args: + parsedOptions: Dictionary of parsed command-line options. + prefix: Optional prefix used when parsing arguments. + + Returns: + VtkOutput: Configured VTK output object. + """ outputKey = __buildArg( prefix, __OUTPUT_FILE ).replace( "-", "_" ) binaryModeKey = __buildArg( prefix, __OUTPUT_BINARY_MODE ).replace( "-", "_" ) output = parsedOptions[ outputKey ] diff --git a/mesh-doctor/src/geos/mesh_doctor/register.py b/mesh-doctor/src/geos/mesh_doctor/register.py index 43d08d60..c0a39885 100644 --- a/mesh-doctor/src/geos/mesh_doctor/register.py +++ b/mesh-doctor/src/geos/mesh_doctor/register.py @@ -5,8 +5,8 @@ from geos.mesh_doctor.parsing import ActionHelper, cliParsing from geos.mesh_doctor.parsing.cliParsing import setupLogger -__HELPERS: dict[ str, str ] = dict() -__ACTIONS: dict[ str, str ] = dict() +__HELPERS: dict[ str, str ] = {} +__ACTIONS: dict[ str, str ] = {} def __loadModuleAction( moduleName: str, actionFct: str = "action" ) -> Callable[ [ str, Any ], Any ]: @@ -23,13 +23,14 @@ def __loadModuleActionHelper( moduleName: str, parsingFctSuffix: str = "Parsing" def __loadActions() -> dict[ str, Callable[ [ str, Any ], Any ] ]: """Loads all the actions. + This function acts like a protection layer if a module fails to load. A action that fails to load won't stop the process. Returns: dict[ str, Callable[ [ str, Any ], Any ] ]: The actions. """ - loadedActions: dict[ str, Callable[ [ str, Any ], Any ] ] = dict() + loadedActions: dict[ str, Callable[ [ str, Any ], Any ] ] = {} for actionName, moduleName in __ACTIONS.items(): try: loadedActions[ actionName ] = __loadModuleAction( moduleName ) @@ -59,8 +60,8 @@ def registerParsingActions( __ACTIONS[ actionName ] = actionName loadedActions: dict[ str, Callable[ [ str, Any ], Any ] ] = __loadActions() - loadedActionsHelpers: dict[ str, ActionHelper ] = dict() - for actionName in loadedActions.keys(): + loadedActionsHelpers: dict[ str, ActionHelper ] = {} + for actionName in loadedActions: moduleName = __HELPERS[ actionName ] try: h = __loadModuleActionHelper( moduleName ) diff --git a/mesh-doctor/tests/test_collocatedNodes.py b/mesh-doctor/tests/test_collocatedNodes.py index 171ce518..e30c2469 100644 --- a/mesh-doctor/tests/test_collocatedNodes.py +++ b/mesh-doctor/tests/test_collocatedNodes.py @@ -2,7 +2,7 @@ from typing import Iterator, Tuple from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkTetra, vtkUnstructuredGrid, VTK_TETRA -from geos.mesh_doctor.actions.collocatedNodes import Options, __action +from geos.mesh_doctor.actions.collocatedNodes import Options, meshAction def getPoints() -> Iterator[ Tuple[ vtkPoints, int ] ]: @@ -27,7 +27,7 @@ def test_simpleCollocatedPoints( data: Tuple[ vtkPoints, int ] ): mesh = vtkUnstructuredGrid() mesh.SetPoints( points ) - result = __action( mesh, Options( tolerance=1.e-12 ) ) + result = meshAction( mesh, Options( tolerance=1.e-12 ) ) assert len( result.wrongSupportElements ) == 0 assert len( result.nodesBuckets ) == numNodesBucket @@ -58,7 +58,7 @@ def test_wrongSupportElements(): mesh.SetPoints( points ) mesh.SetCells( cellTypes, cells ) - result = __action( mesh, Options( tolerance=1.e-12 ) ) + result = meshAction( mesh, Options( tolerance=1.e-12 ) ) assert len( result.nodesBuckets ) == 0 assert len( result.wrongSupportElements ) == 1 diff --git a/mesh-doctor/tests/test_elementVolumes.py b/mesh-doctor/tests/test_elementVolumes.py index 9645f9a0..2692bfa1 100644 --- a/mesh-doctor/tests/test_elementVolumes.py +++ b/mesh-doctor/tests/test_elementVolumes.py @@ -1,7 +1,7 @@ import numpy from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import VTK_TETRA, vtkCellArray, vtkTetra, vtkUnstructuredGrid -from geos.mesh_doctor.actions.elementVolumes import Options, __action +from geos.mesh_doctor.actions.elementVolumes import Options, meshAction def test_simpleTet(): @@ -28,12 +28,12 @@ def test_simpleTet(): mesh.SetPoints( points ) mesh.SetCells( cellTypes, cells ) - result = __action( mesh, Options( minVolume=1. ) ) + result = meshAction( mesh, Options( minVolume=1. ) ) assert len( result.elementVolumes ) == 1 assert result.elementVolumes[ 0 ][ 0 ] == 0 assert abs( result.elementVolumes[ 0 ][ 1 ] - 1. / 6. ) < 10 * numpy.finfo( float ).eps - result = __action( mesh, Options( minVolume=0. ) ) + result = meshAction( mesh, Options( minVolume=0. ) ) assert len( result.elementVolumes ) == 0 diff --git a/mesh-doctor/tests/test_generateCube.py b/mesh-doctor/tests/test_generateCube.py index 6debceb1..015336d3 100644 --- a/mesh-doctor/tests/test_generateCube.py +++ b/mesh-doctor/tests/test_generateCube.py @@ -1,4 +1,4 @@ -from geos.mesh_doctor.actions.generateCube import __build, Options, FieldInfo +from geos.mesh_doctor.actions.generateCube import Options, FieldInfo, buildCube def test_generateCube(): @@ -12,7 +12,7 @@ def test_generateCube(): nys=( 1, 1 ), nzs=( 1, ), fields=( FieldInfo( name="test", dimension=2, support="CELLS" ), ) ) - output = __build( options ) + output = buildCube( options ) assert output.GetNumberOfCells() == 14 assert output.GetNumberOfPoints() == 48 assert output.GetCellData().GetArray( "test" ).GetNumberOfComponents() == 2 diff --git a/mesh-doctor/tests/test_generateGlobalIds.py b/mesh-doctor/tests/test_generateGlobalIds.py index d00dd9a0..ffed9412 100644 --- a/mesh-doctor/tests/test_generateGlobalIds.py +++ b/mesh-doctor/tests/test_generateGlobalIds.py @@ -1,6 +1,6 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkUnstructuredGrid, vtkVertex, VTK_VERTEX -from geos.mesh_doctor.actions.generateGlobalIds import __buildGlobalIds +from geos.mesh_doctor.actions.generateGlobalIds import buildGlobalIds def test_generateGlobalIds(): @@ -17,7 +17,7 @@ def test_generateGlobalIds(): mesh.SetPoints( points ) mesh.SetCells( [ VTK_VERTEX ], vertices ) - __buildGlobalIds( mesh, True, True ) + buildGlobalIds( mesh, True, True ) globalCellIds = mesh.GetCellData().GetGlobalIds() globalPointIds = mesh.GetPointData().GetGlobalIds() diff --git a/mesh-doctor/tests/test_nonConformal.py b/mesh-doctor/tests/test_nonConformal.py index e70ba41a..2f846baa 100644 --- a/mesh-doctor/tests/test_nonConformal.py +++ b/mesh-doctor/tests/test_nonConformal.py @@ -1,6 +1,6 @@ import numpy from geos.mesh_doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ -from geos.mesh_doctor.actions.nonConformal import Options, __action +from geos.mesh_doctor.actions.nonConformal import Options, meshAction def test_twoCloseHexs(): @@ -12,13 +12,13 @@ def test_twoCloseHexs(): # Close enough, but points tolerance is too strict to consider the faces matching. options = Options( angleTolerance=1., pointTolerance=delta / 2, faceTolerance=delta * 2 ) - results = __action( mesh, options ) + results = meshAction( mesh, options ) assert len( results.nonConformalCells ) == 1 assert set( results.nonConformalCells[ 0 ] ) == { 0, 1 } # Close enough, and points tolerance is loose enough to consider the faces matching. options = Options( angleTolerance=1., pointTolerance=delta * 2, faceTolerance=delta * 2 ) - results = __action( mesh, options ) + results = meshAction( mesh, options ) assert len( results.nonConformalCells ) == 0 @@ -31,7 +31,7 @@ def test_twoDistantHexs(): options = Options( angleTolerance=1., pointTolerance=delta / 2., faceTolerance=delta / 2. ) - results = __action( mesh, options ) + results = meshAction( mesh, options ) assert len( results.nonConformalCells ) == 0 @@ -44,7 +44,7 @@ def test_twoCloseShiftedHexs(): options = Options( angleTolerance=1., pointTolerance=deltaX * 2, faceTolerance=deltaX * 2 ) - results = __action( mesh, options ) + results = meshAction( mesh, options ) assert len( results.nonConformalCells ) == 1 assert set( results.nonConformalCells[ 0 ] ) == { 0, 1 } @@ -58,6 +58,6 @@ def test_bigElemNextToSmallElem(): options = Options( angleTolerance=1., pointTolerance=delta * 2, faceTolerance=delta * 2 ) - results = __action( mesh, options ) + results = meshAction( mesh, options ) assert len( results.nonConformalCells ) == 1 assert set( results.nonConformalCells[ 0 ] ) == { 0, 1 } diff --git a/mesh-doctor/tests/test_selfIntersectingElements.py b/mesh-doctor/tests/test_selfIntersectingElements.py index 119b6779..77c894c9 100644 --- a/mesh-doctor/tests/test_selfIntersectingElements.py +++ b/mesh-doctor/tests/test_selfIntersectingElements.py @@ -1,6 +1,6 @@ from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkHexahedron, vtkUnstructuredGrid, VTK_HEXAHEDRON -from geos.mesh_doctor.actions.selfIntersectingElements import Options, __action +from geos.mesh_doctor.actions.selfIntersectingElements import Options, meshAction def test_jumbledHex(): @@ -35,7 +35,7 @@ def test_jumbledHex(): mesh.SetPoints( points ) mesh.SetCells( cellTypes, cells ) - result = __action( mesh, Options( minDistance=0. ) ) + result = meshAction( mesh, Options( minDistance=0. ) ) - assert len( result.intersectingFacesElements ) == 1 - assert result.intersectingFacesElements[ 0 ] == 0 + assert len( result.invalidCellIds[ "intersectingFacesElements" ] ) == 1 + assert result.invalidCellIds[ "intersectingFacesElements" ][ 0 ] == 0 diff --git a/mesh-doctor/tests/test_supportedElements.py b/mesh-doctor/tests/test_supportedElements.py index d316aea7..e10d6a16 100644 --- a/mesh-doctor/tests/test_supportedElements.py +++ b/mesh-doctor/tests/test_supportedElements.py @@ -2,7 +2,7 @@ import pytest from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_POLYHEDRON -# from geos.mesh_doctor.actions.supportedElements import Options, action, __action +# from geos.mesh_doctor.actions.supportedElements import Options, action, meshAction from geos.mesh_doctor.actions.vtkPolyhedron import parseFaceStream, FaceStream from geos.mesh.utils.genericHelpers import toVtkIdList From 241ea8355615159dfec3e63c597fad0a31de5ee9 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 13:42:51 -0800 Subject: [PATCH 04/15] Make all tests compliant with type checking --- .../mesh_doctor/actions/generateFractures.py | 15 +- mesh-doctor/tests/test_cliParsing.py | 15 +- mesh-doctor/tests/test_collocatedNodes.py | 14 +- mesh-doctor/tests/test_elementVolumes.py | 3 +- mesh-doctor/tests/test_generateCube.py | 3 +- mesh-doctor/tests/test_generateFractures.py | 220 +++++++++--------- mesh-doctor/tests/test_generateGlobalIds.py | 3 +- mesh-doctor/tests/test_nonConformal.py | 12 +- mesh-doctor/tests/test_reorientMesh.py | 6 +- .../tests/test_selfIntersectingElements.py | 3 +- .../tests/test_sharedChecksParsingLogic.py | 72 +++--- mesh-doctor/tests/test_supportedElements.py | 9 +- mesh-doctor/tests/test_triangleDistance.py | 51 ++-- 13 files changed, 235 insertions(+), 191 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index 18571503..dcc4f990 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -4,7 +4,7 @@ import networkx from numpy import empty, ones, zeros from tqdm import tqdm -from typing import Collection, Iterable, Mapping, Optional, Sequence +from typing import Collection, Iterable, Mapping, Optional, Sequence, TypeAlias from vtk import vtkDataArray from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import ( vtkCell, vtkCellArray, vtkPolygon, vtkUnstructuredGrid, VTK_POLYGON, @@ -16,14 +16,11 @@ from geos.mesh.io.vtkIO import VtkOutput, readUnstructuredGrid, writeMesh from geos.mesh.utils.arrayHelpers import hasArray from geos.mesh.utils.genericHelpers import toVtkIdList, vtkIter -""" -TypeAliases cannot be used with Python 3.9. A simple assignment like described there will be used: -https://docs.python.org/3/library/typing.html#typing.TypeAlias:~:text=through%20simple%20assignment%3A-,Vector%20%3D%20list%5Bfloat%5D,-Or%20marked%20with -""" - -IDMapping = Mapping[ int, int ] -CellsPointsCoords = dict[ int, list[ tuple[ float ] ] ] -Coordinates3D = tuple[ float, float, float ] + + +IDMapping: TypeAlias = Mapping[ int, int ] +CellsPointsCoords: TypeAlias = dict[ int, list[ tuple[ float ] ] ] +Coordinates3D: TypeAlias = tuple[ float, float, float ] class FracturePolicy( Enum ): diff --git a/mesh-doctor/tests/test_cliParsing.py b/mesh-doctor/tests/test_cliParsing.py index f4356862..de50db57 100644 --- a/mesh-doctor/tests/test_cliParsing.py +++ b/mesh-doctor/tests/test_cliParsing.py @@ -35,9 +35,8 @@ def __generateGenerateFracturesParsingTestData() -> Iterator[ TestCase ]: yield TestCase( cliArgs, options, exception ) -def __parseAndValidateOptions( testCase: TestCase ): - """ - Parse CLI arguments and validate that the resulting options match expected values. +def __parseAndValidateOptions( testCase: TestCase ) -> None: + """Parse CLI arguments and validate that the resulting options match expected values. This helper function simulates the CLI parsing process by: 1. Creating an argument parser with the generateFractures subparser @@ -61,17 +60,19 @@ def __parseAndValidateOptions( testCase: TestCase ): assert options.fieldValuesCombined == testCase.options.fieldValuesCombined -def test_displayResults(): +def test_displayResults() -> None: + """Test displayResults function for code coverage.""" # Dummy test for code coverage only. Shame on me! displayResults( None, None ) @pytest.mark.parametrize( "testCase", __generateGenerateFracturesParsingTestData() ) -def test( testCase: TestCase ): +def test( testCase: TestCase ) -> None: + """Test CLI parsing for generateFractures action.""" if testCase.exception: with pytest.raises( SystemExit ): pytest.skip( "Test to be fixed" ) - __parseAndValidateOptions( testCase ) + # __parseAndValidateOptions( testCase ) else: pytest.skip( "Test to be fixed" ) - __parseAndValidateOptions( testCase ) + # __parseAndValidateOptions( testCase ) diff --git a/mesh-doctor/tests/test_collocatedNodes.py b/mesh-doctor/tests/test_collocatedNodes.py index e30c2469..49c466b0 100644 --- a/mesh-doctor/tests/test_collocatedNodes.py +++ b/mesh-doctor/tests/test_collocatedNodes.py @@ -7,9 +7,11 @@ def getPoints() -> Iterator[ Tuple[ vtkPoints, int ] ]: """Generates the data for the cases. - One case has two nodes at the exact same position. - The other has two differente nodes - :return: Generator to (vtk points, number of expected duplicated locations) + + One case has two nodes at the exact same position. The other has two different nodes. + + Returns: + Iterator[ Tuple[ vtkPoints, int ] ]: The points and the expected number of buckets of collocated nodes. """ for p0, p1 in ( ( 0, 0, 0 ), ( 1, 1, 1 ) ), ( ( 0, 0, 0 ), ( 0, 0, 0 ) ): points = vtkPoints() @@ -21,7 +23,8 @@ def getPoints() -> Iterator[ Tuple[ vtkPoints, int ] ]: @pytest.mark.parametrize( "data", getPoints() ) -def test_simpleCollocatedPoints( data: Tuple[ vtkPoints, int ] ): +def test_simpleCollocatedPoints( data: Tuple[ vtkPoints, int ] ) -> None: + """Tests the detection of collocated nodes for a simple case.""" points, numNodesBucket = data mesh = vtkUnstructuredGrid() @@ -35,7 +38,8 @@ def test_simpleCollocatedPoints( data: Tuple[ vtkPoints, int ] ): assert len( result.nodesBuckets[ 0 ] ) == points.GetNumberOfPoints() -def test_wrongSupportElements(): +def test_wrongSupportElements() -> None: + """Tests that a tetrahedron with two nodes at the same location is detected.""" points = vtkPoints() points.SetNumberOfPoints( 4 ) points.SetPoint( 0, ( 0, 0, 0 ) ) diff --git a/mesh-doctor/tests/test_elementVolumes.py b/mesh-doctor/tests/test_elementVolumes.py index 2692bfa1..b74de789 100644 --- a/mesh-doctor/tests/test_elementVolumes.py +++ b/mesh-doctor/tests/test_elementVolumes.py @@ -4,7 +4,8 @@ from geos.mesh_doctor.actions.elementVolumes import Options, meshAction -def test_simpleTet(): +def test_simpleTet() -> None: + """Tests the calculation of element volumes for a simple tetrahedron.""" # creating a simple tetrahedron points = vtkPoints() points.SetNumberOfPoints( 4 ) diff --git a/mesh-doctor/tests/test_generateCube.py b/mesh-doctor/tests/test_generateCube.py index 015336d3..2f13fbc5 100644 --- a/mesh-doctor/tests/test_generateCube.py +++ b/mesh-doctor/tests/test_generateCube.py @@ -1,7 +1,8 @@ from geos.mesh_doctor.actions.generateCube import Options, FieldInfo, buildCube -def test_generateCube(): +def test_generateCube() -> None: + """Tests the generation of a cube mesh with specific options.""" options = Options( vtkOutput=None, generateCellsGlobalIds=True, generatePointsGlobalIds=False, diff --git a/mesh-doctor/tests/test_generateFractures.py b/mesh-doctor/tests/test_generateFractures.py index 7641a2bb..e3453cff 100644 --- a/mesh-doctor/tests/test_generateFractures.py +++ b/mesh-doctor/tests/test_generateFractures.py @@ -1,17 +1,19 @@ from dataclasses import dataclass -import numpy +import numpy as np +import numpy.typing as npt import pytest -from typing import Iterable, Iterator, Sequence +from typing import Iterable, Iterator, Optional, Sequence, TypeAlias from vtkmodules.vtkCommonDataModel import ( vtkUnstructuredGrid, vtkQuad, VTK_HEXAHEDRON, VTK_POLYHEDRON, VTK_QUAD ) from vtkmodules.util.numpy_support import numpy_to_vtk, vtk_to_numpy +from geos.mesh.utils.genericHelpers import toVtkIdList from geos.mesh_doctor.actions.checkFractures import formatCollocatedNodes from geos.mesh_doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ from geos.mesh_doctor.actions.generateFractures import ( __splitMeshOnFractures, Options, FracturePolicy, Coordinates3D, IDMapping ) -from geos.mesh.utils.genericHelpers import toVtkIdList -FaceNodesCoords = tuple[ tuple[ float ] ] -IDMatrix = Sequence[ Sequence[ int ] ] +BorderFacesNodesCoords: TypeAlias = tuple[ tuple[ Coordinates3D, ... ], ... ] +FaceNodesCoords: TypeAlias = tuple[ Coordinates3D, ... ] +IDMatrix: TypeAlias = Sequence[ Sequence[ int ] ] @dataclass( frozen=True ) @@ -32,25 +34,22 @@ class TestCase: result: TestResult -def __buildTestCase( xs: tuple[ numpy.ndarray, numpy.ndarray, numpy.ndarray ], +def __buildTestCase( xs: tuple[ npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ], npt.NDArray[ np.float64 ] ], attribute: Iterable[ int ], - fieldValues: Iterable[ int ] = None, - policy: FracturePolicy = FracturePolicy.FIELD ): + fieldValues: Optional[ Iterable[ int ] ] = None, + policy: FracturePolicy = FracturePolicy.FIELD ) -> tuple[ vtkUnstructuredGrid, Options ]: + """Builds a test case mesh and options for fracture generation testing.""" xyz = XYZ( *xs ) - mesh: vtkUnstructuredGrid = buildRectilinearBlocksMesh( ( xyz, ) ) - ref = numpy.array( attribute, dtype=int ) + ref = np.array( attribute, dtype=int ) if policy == FracturePolicy.FIELD: assert len( ref ) == mesh.GetNumberOfCells() attr = numpy_to_vtk( ref ) attr.SetName( "attribute" ) mesh.GetCellData().AddArray( attr ) - if fieldValues is None: - fv = frozenset( attribute ) - else: - fv = frozenset( fieldValues ) + fv = frozenset( attribute ) if fieldValues is None else frozenset( fieldValues ) options = Options( policy=policy, field="attribute", @@ -64,112 +63,115 @@ def __buildTestCase( xs: tuple[ numpy.ndarray, numpy.ndarray, numpy.ndarray ], # Utility class to generate the new indices of the newly created collocated nodes. class Incrementor: - def __init__( self, start ): + def __init__( self, start: int ) -> None: + """Initializes the incrementor with a starting value.""" self.__val = start def next( self, num: int ) -> Iterable[ int ]: + """Generates the next 'num' values in the incrementor sequence.""" self.__val += num return range( self.__val - num, self.__val ) def __generateTestData() -> Iterator[ TestCase ]: - twoNodes = numpy.arange( 2, dtype=float ) - threeNodes = numpy.arange( 3, dtype=float ) - fourNodes = numpy.arange( 4, dtype=float ) + """Generates test data for fracture generation tests.""" + twoNodes = np.arange( 2, dtype=float ) + threeNodes = np.arange( 3, dtype=float ) + fourNodes = np.arange( 4, dtype=float ) # Split in 2 mesh, options = __buildTestCase( ( threeNodes, threeNodes, threeNodes ), ( 0, 1, 0, 1, 0, 1, 0, 1 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=tuple( map( lambda i: ( 1 + 3 * i, 27 + i ), range( 9 ) ) ), + collocatedNodes=tuple( ( 1 + 3 * i, 27 + i ) for i in range( 9 ) ), result=TestResult( 9 * 4, 8, 9, 4 ) ) # Split in 3 inc = Incrementor( 27 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 3, *inc.next( 1 ) ), ( 4, *inc.next( 2 ) ), - ( 7, *inc.next( 1 ) ), ( 1 + 9, *inc.next( 1 ) ), ( 3 + 9, *inc.next( 1 ) ), - ( 4 + 9, *inc.next( 2 ) ), ( 7 + 9, *inc.next( 1 ) ), ( 1 + 18, *inc.next( 1 ) ), - ( 3 + 18, *inc.next( 1 ) ), ( 4 + 18, *inc.next( 2 ) ), ( 7 + 18, *inc.next( 1 ) ) ) + collocatedNodes0: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 3, *inc.next( 1 ) ), ( 4, *inc.next( 2 ) ), + ( 7, *inc.next( 1 ) ), ( 1 + 9, *inc.next( 1 ) ), ( 3 + 9, *inc.next( 1 ) ), + ( 4 + 9, *inc.next( 2 ) ), ( 7 + 9, *inc.next( 1 ) ), ( 1 + 18, *inc.next( 1 ) ), + ( 3 + 18, *inc.next( 1 ) ), ( 4 + 18, *inc.next( 2 ) ), ( 7 + 18, *inc.next( 1 ) ) ) mesh, options = __buildTestCase( ( threeNodes, threeNodes, threeNodes ), ( 0, 1, 2, 1, 0, 1, 2, 1 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes0, result=TestResult( 9 * 4 + 6, 8, 12, 6 ) ) # Split in 8 inc = Incrementor( 27 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 3, *inc.next( 1 ) ), ( 4, *inc.next( 3 ) ), - ( 5, *inc.next( 1 ) ), ( 7, *inc.next( 1 ) ), ( 0 + 9, *inc.next( 1 ) ), - ( 1 + 9, *inc.next( 3 ) ), ( 2 + 9, *inc.next( 1 ) ), ( 3 + 9, *inc.next( 3 ) ), - ( 4 + 9, *inc.next( 7 ) ), ( 5 + 9, *inc.next( 3 ) ), ( 6 + 9, *inc.next( 1 ) ), - ( 7 + 9, *inc.next( 3 ) ), ( 8 + 9, *inc.next( 1 ) ), ( 1 + 18, *inc.next( 1 ) ), - ( 3 + 18, *inc.next( 1 ) ), ( 4 + 18, *inc.next( 3 ) ), ( 5 + 18, *inc.next( 1 ) ), - ( 7 + 18, *inc.next( 1 ) ) ) + collocatedNodes2: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 3, *inc.next( 1 ) ), ( 4, *inc.next( 3 ) ), + ( 5, *inc.next( 1 ) ), ( 7, *inc.next( 1 ) ), ( 0 + 9, *inc.next( 1 ) ), + ( 1 + 9, *inc.next( 3 ) ), ( 2 + 9, *inc.next( 1 ) ), ( 3 + 9, *inc.next( 3 ) ), + ( 4 + 9, *inc.next( 7 ) ), ( 5 + 9, *inc.next( 3 ) ), ( 6 + 9, *inc.next( 1 ) ), + ( 7 + 9, *inc.next( 3 ) ), ( 8 + 9, *inc.next( 1 ) ), ( 1 + 18, *inc.next( 1 ) ), + ( 3 + 18, *inc.next( 1 ) ), ( 4 + 18, *inc.next( 3 ) ), ( 5 + 18, *inc.next( 1 ) ), + ( 7 + 18, *inc.next( 1 ) ) ) mesh, options = __buildTestCase( ( threeNodes, threeNodes, threeNodes ), range( 8 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes2, result=TestResult( 8 * 8, 8, 3 * 3 * 3 - 8, 12 ) ) # Straight notch inc = Incrementor( 27 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 4, ), ( 1 + 9, *inc.next( 1 ) ), ( 4 + 9, ), - ( 1 + 18, *inc.next( 1 ) ), ( 4 + 18, ) ) + collocatedNodes3: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 4, ), ( 1 + 9, *inc.next( 1 ) ), ( 4 + 9, ), + ( 1 + 18, *inc.next( 1 ) ), ( 4 + 18, ) ) mesh, options = __buildTestCase( ( threeNodes, threeNodes, threeNodes ), ( 0, 1, 2, 2, 0, 1, 2, 2 ), fieldValues=( 0, 1 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes3, result=TestResult( 3 * 3 * 3 + 3, 8, 6, 2 ) ) # L-shaped notch inc = Incrementor( 27 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 4, *inc.next( 1 ) ), ( 7, *inc.next( 1 ) ), - ( 1 + 9, *inc.next( 1 ) ), ( 4 + 9, ), ( 7 + 9, ), ( 19, *inc.next( 1 ) ), ( 22, ) ) + collocatedNodes4: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 4, *inc.next( 1 ) ), ( 7, *inc.next( 1 ) ), + ( 1 + 9, *inc.next( 1 ) ), ( 4 + 9, ), ( 7 + 9, ), ( 19, *inc.next( 1 ) ), ( 22, ) ) mesh, options = __buildTestCase( ( threeNodes, threeNodes, threeNodes ), ( 0, 1, 0, 1, 0, 1, 2, 2 ), fieldValues=( 0, 1 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes4, result=TestResult( 3 * 3 * 3 + 5, 8, 8, 3 ) ) # 3x1x1 split inc = Incrementor( 2 * 2 * 4 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 2, *inc.next( 1 ) ), ( 5, *inc.next( 1 ) ), - ( 6, *inc.next( 1 ) ), ( 1 + 8, *inc.next( 1 ) ), ( 2 + 8, *inc.next( 1 ) ), - ( 5 + 8, *inc.next( 1 ) ), ( 6 + 8, *inc.next( 1 ) ) ) + collocatedNodes5: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 2, *inc.next( 1 ) ), ( 5, *inc.next( 1 ) ), + ( 6, *inc.next( 1 ) ), ( 1 + 8, *inc.next( 1 ) ), ( 2 + 8, *inc.next( 1 ) ), + ( 5 + 8, *inc.next( 1 ) ), ( 6 + 8, *inc.next( 1 ) ) ) mesh, options = __buildTestCase( ( fourNodes, twoNodes, twoNodes ), ( 0, 1, 2 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes5, result=TestResult( 6 * 4, 3, 2 * 4, 2 ) ) # Discarded fracture element if no node duplication. - collocatedNodes: IDMatrix = tuple() + collocatedNodes6: IDMatrix = () mesh, options = __buildTestCase( ( threeNodes, fourNodes, fourNodes ), ( 0, ) * 8 + ( 1, 2 ) + ( 0, ) * 8, fieldValues=( 1, 2 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes6, result=TestResult( 3 * 4 * 4, 2 * 3 * 3, 0, 0 ) ) # Fracture on a corner inc = Incrementor( 3 * 4 * 4 ) - collocatedNodes: IDMatrix = ( ( 1 + 12, ), ( 4 + 12, ), ( 7 + 12, ), ( 1 + 12 * 2, *inc.next( 1 ) ), - ( 4 + 12 * 2, *inc.next( 1 ) ), ( 7 + 12 * 2, ), ( 1 + 12 * 3, *inc.next( 1 ) ), - ( 4 + 12 * 3, *inc.next( 1 ) ), ( 7 + 12 * 3, ) ) + collocatedNodes7: IDMatrix = ( ( 1 + 12, ), ( 4 + 12, ), ( 7 + 12, ), ( 1 + 12 * 2, *inc.next( 1 ) ), + ( 4 + 12 * 2, *inc.next( 1 ) ), ( 7 + 12 * 2, ), ( 1 + 12 * 3, *inc.next( 1 ) ), + ( 4 + 12 * 3, *inc.next( 1 ) ), ( 7 + 12 * 3, ) ) mesh, options = __buildTestCase( ( threeNodes, fourNodes, fourNodes ), ( 0, ) * 6 + ( 1, 2, 1, 2, 0, 0, 1, 2, 1, 2, 0, 0 ), fieldValues=( 1, 2 ) ) yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes7, result=TestResult( 3 * 4 * 4 + 4, 2 * 3 * 3, 9, 4 ) ) # Generate mesh with 2 hexs, one being a standard hex, the other a 42 hex. inc = Incrementor( 3 * 2 * 2 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 1 + 3, *inc.next( 1 ) ), ( 1 + 6, *inc.next( 1 ) ), - ( 1 + 9, *inc.next( 1 ) ) ) + collocatedNodes8: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 1 + 3, *inc.next( 1 ) ), ( 1 + 6, *inc.next( 1 ) ), + ( 1 + 9, *inc.next( 1 ) ) ) mesh, options = __buildTestCase( ( threeNodes, twoNodes, twoNodes ), ( 0, 1 ) ) polyhedronMesh = vtkUnstructuredGrid() polyhedronMesh.SetPoints( mesh.GetPoints() ) @@ -182,13 +184,13 @@ def __generateTestData() -> Iterator[ TestCase ]: yield TestCase( inputMesh=polyhedronMesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes8, result=TestResult( 4 * 4, 2, 4, 1 ) ) # Split in 2 using the internal fracture description inc = Incrementor( 3 * 2 * 2 ) - collocatedNodes: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 1 + 3, *inc.next( 1 ) ), ( 1 + 6, *inc.next( 1 ) ), - ( 1 + 9, *inc.next( 1 ) ) ) + collocatedNodes9: IDMatrix = ( ( 1, *inc.next( 1 ) ), ( 1 + 3, *inc.next( 1 ) ), ( 1 + 6, *inc.next( 1 ) ), + ( 1 + 9, *inc.next( 1 ) ) ) mesh, options = __buildTestCase( ( threeNodes, twoNodes, twoNodes ), attribute=( 0, 0, 0 ), fieldValues=( 0, ), @@ -196,12 +198,13 @@ def __generateTestData() -> Iterator[ TestCase ]: mesh.InsertNextCell( VTK_QUAD, toVtkIdList( ( 1, 4, 7, 10 ) ) ) # Add a fracture on the fly yield TestCase( inputMesh=mesh, options=options, - collocatedNodes=collocatedNodes, + collocatedNodes=collocatedNodes9, result=TestResult( 4 * 4, 3, 4, 1 ) ) @pytest.mark.parametrize( "TestCase", __generateTestData() ) -def test_generateFracture( TestCase: TestCase ): +def test_generateFracture( TestCase: TestCase ) -> None: + """Tests the generation of fractures on a mesh according to various test cases.""" mainMesh, fractureMeshes = __splitMeshOnFractures( TestCase.inputMesh, TestCase.options ) fractureMesh: vtkUnstructuredGrid = fractureMeshes[ 0 ] assert mainMesh.GetNumberOfPoints() == TestCase.result.mainMeshNumPoints @@ -214,9 +217,10 @@ def test_generateFracture( TestCase: TestCase ): assert len( res ) == TestCase.result.fractureMeshNumPoints -def addSimplifiedFieldForCells( mesh: vtkUnstructuredGrid, field_name: str, fieldDimension: int ): - """Reduce functionality obtained from src.geos.mesh_doctor.actions.generateFractures.__add_fields - where the goal is to add a cell data array with incrementing values. +def addSimplifiedFieldForCells( mesh: vtkUnstructuredGrid, field_name: str, fieldDimension: int ) -> None: + """Reduce functionality obtained from src.geos.mesh_doctor.actions.generateFractures.__add_fields. + + The goal is to add a cell data array with incrementing values. Args: mesh (vtkUnstructuredGrid): Unstructured mesh. @@ -225,15 +229,16 @@ def addSimplifiedFieldForCells( mesh: vtkUnstructuredGrid, field_name: str, fiel """ data = mesh.GetCellData() n = mesh.GetNumberOfCells() - array = numpy.ones( ( n, fieldDimension ), dtype=float ) - array = numpy.arange( 1, n * fieldDimension + 1 ).reshape( n, fieldDimension ) + array = np.ones( ( n, fieldDimension ), dtype=float ) + array = np.arange( 1, n * fieldDimension + 1 ).reshape( n, fieldDimension ) vtkArray = numpy_to_vtk( array ) vtkArray.SetName( field_name ) data.AddArray( vtkArray ) -def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> tuple[ FaceNodesCoords ]: - """ +def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> BorderFacesNodesCoords: + """For a vtk rectilinear grid, gives the coordinates of each of its borders face nodes. + 6+--------+7 / /| / / | @@ -244,20 +249,18 @@ def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> tuple[ FaceN | |/ 0+--------+1 - For a vtk rectilinear grid, gives the coordinates of each of its borders face nodes. - Args: mesh (vtkUnstructuredGrid): Unstructured mesh. Returns: - tuple[QuadCoords]: For a rectilinear grid, returns a tuple of 6 elements. + BorderFacesNodesCoords: For a rectilinear grid, returns a tuple of 6 faces nodeset. """ - meshBounds: tuple[ float ] = mesh.GetBounds() - minBound: Coordinates3D = [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 0 ] - maxBound: Coordinates3D = [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 1 ] + meshBounds: tuple[ float, float, float, float, float, float ] = mesh.GetBounds() + minBound: tuple[ float, ... ] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 0 ] ) + maxBound: tuple[ float, ... ] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 1 ] ) center: Coordinates3D = mesh.GetCenter() - faceDiag: tuple[ float ] = ( ( maxBound[ 0 ] - minBound[ 0 ] ) / 2, ( maxBound[ 1 ] - minBound[ 1 ] ) / 2, - ( maxBound[ 2 ] - minBound[ 2 ] ) / 2 ) + faceDiag: Coordinates3D = ( ( maxBound[ 0 ] - minBound[ 0 ] ) / 2, ( maxBound[ 1 ] - minBound[ 1 ] ) / 2, + ( maxBound[ 2 ] - minBound[ 2 ] ) / 2 ) node0: Coordinates3D = ( center[ 0 ] - faceDiag[ 0 ], center[ 1 ] - faceDiag[ 1 ], center[ 2 ] - faceDiag[ 2 ] ) node1: Coordinates3D = ( center[ 0 ] + faceDiag[ 0 ], center[ 1 ] - faceDiag[ 1 ], center[ 2 ] - faceDiag[ 2 ] ) node2: Coordinates3D = ( center[ 0 ] - faceDiag[ 0 ], center[ 1 ] + faceDiag[ 1 ], center[ 2 ] - faceDiag[ 2 ] ) @@ -266,17 +269,18 @@ def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> tuple[ FaceN node5: Coordinates3D = ( center[ 0 ] + faceDiag[ 0 ], center[ 1 ] - faceDiag[ 1 ], center[ 2 ] + faceDiag[ 2 ] ) node6: Coordinates3D = ( center[ 0 ] - faceDiag[ 0 ], center[ 1 ] + faceDiag[ 1 ], center[ 2 ] + faceDiag[ 2 ] ) node7: Coordinates3D = ( center[ 0 ] + faceDiag[ 0 ], center[ 1 ] + faceDiag[ 1 ], center[ 2 ] + faceDiag[ 2 ] ) - faces: tuple[ FaceNodesCoords ] = ( ( node0, node1, node3, node2 ), ( node4, node5, node7, node6 ), - ( node0, node2, node6, node4 ), ( node1, node3, node7, node5 ), - ( node0, node1, node5, node4 ), ( node2, node3, node7, node6 ) ) + faces: BorderFacesNodesCoords = ( ( node0, node1, node3, node2 ), ( node4, node5, node7, node6 ), + ( node0, node2, node6, node4 ), ( node1, node3, node7, node5 ), + ( node0, node1, node5, node4 ), ( node2, node3, node7, node6 ) ) return faces -def addQuad( mesh: vtkUnstructuredGrid, face: FaceNodesCoords ): +def addQuad( mesh: vtkUnstructuredGrid, face: FaceNodesCoords ) -> None: """Adds a quad cell to each border of an unstructured mesh. Args: mesh (vtkUnstructuredGrid): Unstructured mesh. + face (FaceNodesCoords): Coordinates of the quad face to add. """ pointsCoords = mesh.GetPoints().GetData() quad: vtkQuad = vtkQuad() @@ -297,31 +301,29 @@ def addQuad( mesh: vtkUnstructuredGrid, face: FaceNodesCoords ): @pytest.mark.skip( "Test to be fixed" ) -def test_copyFieldsWhenSplittingMesh(): - """This test is designed to check the __copyFields method from generateFractures, - that will be called when using __splitMeshOnFractures method from generateFractures. - """ +def test_copyFieldsWhenSplittingMesh() -> None: + """This test is designed to check the __copyFields method from generateFractures, that will be called when using __splitMeshOnFractures method from generateFractures.""" # Generating the rectilinear grid and its quads on all borders - x: numpy.array = numpy.array( [ 0, 1, 2 ] ) - y: numpy.array = numpy.array( [ 0, 1 ] ) - z: numpy.array = numpy.array( [ 0, 1 ] ) + x: npt.NDArray[ np.float64 ] = np.array( [ 0.0, 1.0, 2.0 ] ) + y: npt.NDArray[ np.float64 ] = np.array( [ 0.0, 1.0 ] ) + z: npt.NDArray[ np.float64 ] = np.array( [ 0.0, 1.0 ] ) xyzs: XYZ = XYZ( x, y, z ) - mesh: vtkUnstructuredGrid = buildRectilinearBlocksMesh( [ xyzs ] ) - assert mesh.GetCells().GetNumberOfCells() == 2 - borderFaces: tuple[ FaceNodesCoords ] = findBordersFacesRectilinearGrid( mesh ) - for face in borderFaces: - addQuad( mesh, face ) - assert mesh.GetCells().GetNumberOfCells() == 8 + mesh0: vtkUnstructuredGrid = buildRectilinearBlocksMesh( [ xyzs ] ) + assert mesh0.GetCells().GetNumberOfCells() == 2 + borderFaces0: BorderFacesNodesCoords = findBordersFacesRectilinearGrid( mesh0 ) + for face in borderFaces0: + addQuad( mesh0, face ) + assert mesh0.GetCells().GetNumberOfCells() == 8 # Create a quad cell to represent the fracture surface. fracture: FaceNodesCoords = ( ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 1.0, 1.0, 1.0 ), ( 1.0, 0.0, 1.0 ) ) - addQuad( mesh, fracture ) - assert mesh.GetCells().GetNumberOfCells() == 9 + addQuad( mesh0, fracture ) + assert mesh0.GetCells().GetNumberOfCells() == 9 # Add a "TestField" array - assert mesh.GetCellData().GetNumberOfArrays() == 0 - addSimplifiedFieldForCells( mesh, "TestField", 1 ) - assert mesh.GetCellData().GetNumberOfArrays() == 1 - assert mesh.GetCellData().GetArrayName( 0 ) == "TestField" - testFieldValues: list[ int ] = vtk_to_numpy( mesh.GetCellData().GetArray( 0 ) ).tolist() + assert mesh0.GetCellData().GetNumberOfArrays() == 0 + addSimplifiedFieldForCells( mesh0, "TestField", 1 ) + assert mesh0.GetCellData().GetNumberOfArrays() == 1 + assert mesh0.GetCellData().GetArrayName( 0 ) == "TestField" + testFieldValues: list[ int ] = vtk_to_numpy( mesh0.GetCellData().GetArray( 0 ) ).tolist() assert testFieldValues == [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] # Split the mesh along the fracture surface which is number 9 on TestField options = Options( policy=FracturePolicy.INTERNAL_SURFACES, @@ -330,7 +332,7 @@ def test_copyFieldsWhenSplittingMesh(): fieldValuesPerFracture=[ frozenset( map( int, [ "9" ] ) ) ], meshVtkOutput=None, allFracturesVtkOutput=None ) - mainMesh, fractureMeshes = __splitMeshOnFractures( mesh, options ) + mainMesh, fractureMeshes = __splitMeshOnFractures( mesh0, options ) fractureMesh: vtkUnstructuredGrid = fractureMeshes[ 0 ] assert mainMesh.GetCellData().GetNumberOfArrays() == 1 assert fractureMesh.GetCellData().GetNumberOfArrays() == 1 @@ -342,19 +344,17 @@ def test_copyFieldsWhenSplittingMesh(): assert fractureMeshValues == [ 9 ] # The value for the fracture surface assert mainMeshValues == [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] # Test for invalid point field name - addSimplifiedFieldForCells( mesh, "GLOBAL_IDS_POINTS", 1 ) - with pytest.raises( ValueError ) as pytestWrappedError: - mainMesh, fractureMeshes = __splitMeshOnFractures( mesh, options ) - assert pytestWrappedError.type == ValueError + addSimplifiedFieldForCells( mesh0, "GLOBAL_IDS_POINTS", 1 ) + with pytest.raises( ValueError ): + mainMesh, fractureMeshes = __splitMeshOnFractures( mesh0, options ) # Test for invalid cell field name - mesh: vtkUnstructuredGrid = buildRectilinearBlocksMesh( [ xyzs ] ) - borderFaces: tuple[ FaceNodesCoords ] = findBordersFacesRectilinearGrid( mesh ) - for face in borderFaces: - addQuad( mesh, face ) - addQuad( mesh, fracture ) - addSimplifiedFieldForCells( mesh, "TestField", 1 ) - addSimplifiedFieldForCells( mesh, "GLOBAL_IDS_CELLS", 1 ) - assert mesh.GetCellData().GetNumberOfArrays() == 2 - with pytest.raises( ValueError ) as pytestWrappedError: - mainMesh, fractureMeshes = __splitMeshOnFractures( mesh, options ) - assert pytestWrappedError.type == ValueError + mesh1: vtkUnstructuredGrid = buildRectilinearBlocksMesh( [ xyzs ] ) + borderFaces1: BorderFacesNodesCoords = findBordersFacesRectilinearGrid( mesh1 ) + for face in borderFaces1: + addQuad( mesh1, face ) + addQuad( mesh1, fracture ) + addSimplifiedFieldForCells( mesh1, "TestField", 1 ) + addSimplifiedFieldForCells( mesh1, "GLOBAL_IDS_CELLS", 1 ) + assert mesh1.GetCellData().GetNumberOfArrays() == 2 + with pytest.raises( ValueError ): + mainMesh, fractureMeshes = __splitMeshOnFractures( mesh1, options ) diff --git a/mesh-doctor/tests/test_generateGlobalIds.py b/mesh-doctor/tests/test_generateGlobalIds.py index ffed9412..d16778a8 100644 --- a/mesh-doctor/tests/test_generateGlobalIds.py +++ b/mesh-doctor/tests/test_generateGlobalIds.py @@ -3,7 +3,8 @@ from geos.mesh_doctor.actions.generateGlobalIds import buildGlobalIds -def test_generateGlobalIds(): +def test_generateGlobalIds() -> None: + """Tests the generation of global IDs for a simple mesh with one vertex.""" points = vtkPoints() points.InsertNextPoint( 0, 0, 0 ) diff --git a/mesh-doctor/tests/test_nonConformal.py b/mesh-doctor/tests/test_nonConformal.py index 2f846baa..2b455c83 100644 --- a/mesh-doctor/tests/test_nonConformal.py +++ b/mesh-doctor/tests/test_nonConformal.py @@ -3,7 +3,8 @@ from geos.mesh_doctor.actions.nonConformal import Options, meshAction -def test_twoCloseHexs(): +def test_twoCloseHexs() -> None: + """Tests two close hexahedrons for non-conformality detection.""" delta = 1.e-6 tmp = numpy.arange( 2, dtype=float ) xyz0 = XYZ( tmp, tmp, tmp ) @@ -22,7 +23,8 @@ def test_twoCloseHexs(): assert len( results.nonConformalCells ) == 0 -def test_twoDistantHexs(): +def test_twoDistantHexs() -> None: + """Tests two distant hexahedrons for non-conformality detection.""" delta = 1 tmp = numpy.arange( 2, dtype=float ) xyz0 = XYZ( tmp, tmp, tmp ) @@ -35,7 +37,8 @@ def test_twoDistantHexs(): assert len( results.nonConformalCells ) == 0 -def test_twoCloseShiftedHexs(): +def test_twoCloseShiftedHexs() -> None: + """Tests two close but shifted hexahedrons for non-conformality detection.""" deltaX, deltaY = 1.e-6, 0.5 tmp = numpy.arange( 2, dtype=float ) xyz0 = XYZ( tmp, tmp, tmp ) @@ -49,7 +52,8 @@ def test_twoCloseShiftedHexs(): assert set( results.nonConformalCells[ 0 ] ) == { 0, 1 } -def test_bigElemNextToSmallElem(): +def test_bigElemNextToSmallElem() -> None: + """Tests a big element next to a small element for non-conformality detection.""" delta = 1.e-6 tmp = numpy.arange( 2, dtype=float ) xyz0 = XYZ( tmp, tmp + 1, tmp + 1 ) diff --git a/mesh-doctor/tests/test_reorientMesh.py b/mesh-doctor/tests/test_reorientMesh.py index 23e9c7b7..c9beea8b 100644 --- a/mesh-doctor/tests/test_reorientMesh.py +++ b/mesh-doctor/tests/test_reorientMesh.py @@ -16,6 +16,7 @@ class Expected: def __buildTestMeshes() -> Generator[ Expected, None, None ]: + """Builds test meshes for reorientMesh testing.""" # Creating the support nodes for the polyhedron. # It has a C shape and is actually non-convex, non star-shaped. frontNodes = numpy.array( ( @@ -42,7 +43,7 @@ def __buildTestMeshes() -> Generator[ Expected, None, None ]: points.InsertNextPoint( coords ) # Creating the polyhedron with faces all directed outward. - faces = [] + faces: list[ tuple[ int, ... ] ] = [] # Creating the side faces for i in range( n ): faces.append( ( i % n + n, ( i + 1 ) % n + n, ( i + 1 ) % n, i % n ) ) @@ -76,7 +77,8 @@ def __buildTestMeshes() -> Generator[ Expected, None, None ]: @pytest.mark.parametrize( "expected", __buildTestMeshes() ) -def test_reorientPolyhedron( expected: Expected ): +def test_reorientPolyhedron( expected: Expected ) -> None: + """Tests reorientMesh on polyhedron elements.""" outputMesh = reorientMesh( expected.mesh, range( expected.mesh.GetNumberOfCells() ) ) assert outputMesh.GetNumberOfCells() == 1 assert outputMesh.GetCell( 0 ).GetCellType() == VTK_POLYHEDRON diff --git a/mesh-doctor/tests/test_selfIntersectingElements.py b/mesh-doctor/tests/test_selfIntersectingElements.py index 77c894c9..c1b94852 100644 --- a/mesh-doctor/tests/test_selfIntersectingElements.py +++ b/mesh-doctor/tests/test_selfIntersectingElements.py @@ -3,7 +3,8 @@ from geos.mesh_doctor.actions.selfIntersectingElements import Options, meshAction -def test_jumbledHex(): +def test_jumbledHex() -> None: + """Tests that a hexahedron with intersecting faces is detected.""" # creating a simple hexahedron points = vtkPoints() points.SetNumberOfPoints( 8 ) diff --git a/mesh-doctor/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py index b32aa5fa..2fb5a455 100644 --- a/mesh-doctor/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -1,7 +1,7 @@ import argparse from dataclasses import dataclass import pytest -from unittest.mock import patch +from unittest.mock import MagicMock, patch # Import the module to test from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.actions.allChecks import Result as AllChecksResult @@ -22,46 +22,46 @@ class MockResult: value: str = "testResult" -def mockDisplayFunc( options, result ): +def mockDisplayFunc( options: MockOptions, result: MockResult ) -> None: + """Mock display function for testing.""" pass @pytest.fixture -def checkFeaturesConfig(): +def checkFeaturesConfig() -> dict[ str, CheckFeature ]: + """Provides a mock check features configuration for testing.""" return { "check1": CheckFeature( name="check1", optionsCls=MockOptions, resultCls=MockResult, - defaultParams={ - "param1": 1.0, - "param2": 2.0 - }, + defaultParams={ "param1": 1.0, "param2": 2.0 }, display=mockDisplayFunc ), "check2": CheckFeature( name="check2", optionsCls=MockOptions, resultCls=MockResult, - defaultParams={ - "param1": 3.0, - "param2": 4.0 - }, + defaultParams={ "param1": 3.0, "param2": 4.0 }, display=mockDisplayFunc ) } @pytest.fixture -def orderedCheckNames(): +def orderedCheckNames() -> list[ str ]: + """Provides an ordered list of check names for testing.""" return [ "check1", "check2" ] -def test_generateParametersHelp( checkFeaturesConfig, orderedCheckNames ): +def test_generateParametersHelp( checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests _generateParametersHelp functionality.""" helpText = _generateParametersHelp( orderedCheckNames, checkFeaturesConfig ) assert "For check1: param1:1.0, param2:2.0" in helpText assert "For check2: param1:3.0, param2:4.0" in helpText -def test_getOptionsUsedMessage(): +def test_getOptionsUsedMessage() -> None: + """Tests getOptionsUsedMessage functionality.""" options = MockOptions( param1=10.0, param2=20.0 ) message = getOptionsUsedMessage( options ) assert "Parameters used: (" in message @@ -70,7 +70,8 @@ def test_getOptionsUsedMessage(): assert ")." in message -def test_fillSubparser( checkFeaturesConfig, orderedCheckNames ): +def test_fillSubparser( checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: + """Tests fillSubparser functionality.""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers( dest="command" ) fillSubparser( subparsers, "test-command", "Test help message", orderedCheckNames, checkFeaturesConfig ) @@ -86,7 +87,9 @@ def test_fillSubparser( checkFeaturesConfig, orderedCheckNames ): @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertDefaultChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when no specific checks or parameters are specified.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames @@ -96,7 +99,9 @@ def test_convertDefaultChecks( mockLogger, checkFeaturesConfig, orderedCheckName @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertSpecificChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when specific checks are specified.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == [ "check1" ] @@ -106,7 +111,9 @@ def test_convertSpecificChecks( mockLogger, checkFeaturesConfig, orderedCheckNam @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertWithParameters( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when parameters are specified.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames @@ -117,7 +124,9 @@ def test_convertWithParameters( mockLogger, checkFeaturesConfig, orderedCheckNam @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertWithInvalidParameters( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when some invalid parameters are specified.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid parameter should be skipped, but the valid one applied @@ -126,7 +135,9 @@ def test_convertWithInvalidParameters( mockLogger, checkFeaturesConfig, orderedC @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertWithInvalidCheck( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when an invalid check is specified.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } ) options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid check should be skipped @@ -136,7 +147,9 @@ def test_convertWithInvalidCheck( mockLogger, checkFeaturesConfig, orderedCheckN @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_convertWithAllInvalidChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_convertWithAllInvalidChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests convert when all checks are invalid.""" parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } ) # Should raise ValueError since no valid checks were selected with pytest.raises( ValueError, match="No valid checks were selected" ): @@ -144,16 +157,12 @@ def test_convertWithAllInvalidChecks( mockLogger, checkFeaturesConfig, orderedCh @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_displayResultsWithChecks( mockLogger, checkFeaturesConfig, orderedCheckNames ): +def test_displayResultsWithChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], + orderedCheckNames: list[ str ] ) -> None: + """Tests displayResults when checks were performed.""" options = AllChecksOptions( checksToPerform=[ "check1", "check2" ], - checksOptions={ - "check1": MockOptions(), - "check2": MockOptions() - }, - checkDisplays={ - "check1": mockDisplayFunc, - "check2": mockDisplayFunc - } ) + checksOptions={ "check1": MockOptions(), "check2": MockOptions() }, + checkDisplays={ "check1": mockDisplayFunc, "check2": mockDisplayFunc } ) result = AllChecksResult( checkResults={ "check1": MockResult( value="result1" ), "check2": MockResult( value="result2" ) @@ -164,7 +173,8 @@ def test_displayResultsWithChecks( mockLogger, checkFeaturesConfig, orderedCheck @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) -def test_displayResultsNoChecks( mockLogger ): +def test_displayResultsNoChecks( mockLogger: MagicMock ) -> None: + """Tests displayResults when no checks were performed.""" options = AllChecksOptions( checksToPerform=[], checksOptions={}, checkDisplays={} ) result = AllChecksResult( checkResults={} ) displayResults( options, result ) diff --git a/mesh-doctor/tests/test_supportedElements.py b/mesh-doctor/tests/test_supportedElements.py index e10d6a16..cde51f16 100644 --- a/mesh-doctor/tests/test_supportedElements.py +++ b/mesh-doctor/tests/test_supportedElements.py @@ -26,8 +26,11 @@ def test_supportedElements( baseName: str ) -> None: def makeDodecahedron() -> tuple[ vtkPoints, vtkIdList ]: """Returns the points and faces for a dodecahedron. + This code was adapted from an official vtk example. - :return: The tuple of points and faces (as vtk instances). + + Returns: + The tuple of points and faces (as vtk instances). """ # yapf: disable points = ( @@ -80,8 +83,7 @@ def makeDodecahedron() -> tuple[ vtkPoints, vtkIdList ]: # TODO make this test work def test_dodecahedron() -> None: - """Tests whether a dodecahedron is support by GEOS or not. - """ + """Tests whether a dodecahedron is supported by GEOS or not.""" points, faces = makeDodecahedron() mesh = vtkUnstructuredGrid() mesh.Allocate( 1 ) @@ -95,6 +97,7 @@ def test_dodecahedron() -> None: def test_parseFaceStream() -> None: + """Tests the parsing of a face stream for a dodecahedron.""" _, faces = makeDodecahedron() result = parseFaceStream( faces ) # yapf: disable diff --git a/mesh-doctor/tests/test_triangleDistance.py b/mesh-doctor/tests/test_triangleDistance.py index 5ff10421..6034f721 100644 --- a/mesh-doctor/tests/test_triangleDistance.py +++ b/mesh-doctor/tests/test_triangleDistance.py @@ -1,26 +1,32 @@ from dataclasses import dataclass import numpy +import numpy.typing as npt from numpy.linalg import norm import pytest +from typing import Iterable from geos.mesh_doctor.actions.triangleDistance import distanceBetweenTwoSegments, distanceBetweenTwoTriangles @dataclass( frozen=True ) class ExpectedSeg: - p0: numpy.array - u0: numpy.array - p1: numpy.array - u1: numpy.array - x: numpy.array - y: numpy.array + p0: npt.NDArray[ numpy.float64 ] + u0: npt.NDArray[ numpy.float64 ] + p1: npt.NDArray[ numpy.float64 ] + u1: npt.NDArray[ numpy.float64 ] + x: npt.NDArray[ numpy.float64 ] + y: npt.NDArray[ numpy.float64 ] @classmethod - def fromTuples( cls, p0, u0, p1, u1, x, y ): + def fromTuples( cls, p0: tuple[ float, float, float ], u0: tuple[ float, float, float ], + p1: tuple[ float, float, float ], u1: tuple[ float, float, float ], x: tuple[ float, float, float ], + y: tuple[ float, float, float ] ) -> "ExpectedSeg": + """Creates an ExpectedSeg from tuples.""" return cls( numpy.array( p0 ), numpy.array( u0 ), numpy.array( p1 ), numpy.array( u1 ), numpy.array( x ), numpy.array( y ) ) -def __getSegmentsReferences(): +def __getSegmentsReferences() -> Iterable[ ExpectedSeg ]: + """Provides reference segments for testing.""" # Node to node configuration. yield ExpectedSeg.fromTuples( p0=( 0., 0., 0. ), @@ -80,7 +86,12 @@ def __getSegmentsReferences(): @pytest.mark.parametrize( "expected", __getSegmentsReferences() ) -def test_segments( expected: ExpectedSeg ): +def test_segments( expected: ExpectedSeg ) -> None: + """Tests the distance between two segments. + + Args: + expected (ExpectedSeg): The expected segment data. + """ eps = numpy.finfo( float ).eps x, y = distanceBetweenTwoSegments( expected.p0, expected.u0, expected.p1, expected.u1 ) if norm( expected.x - expected.y ) == 0: @@ -92,18 +103,21 @@ def test_segments( expected: ExpectedSeg ): @dataclass( frozen=True ) class ExpectedTri: - t0: numpy.array - t1: numpy.array + t0: npt.NDArray[ numpy.float64 ] + t1: npt.NDArray[ numpy.float64 ] d: float - p0: numpy.array - p1: numpy.array + p0: npt.NDArray[ numpy.float64 ] + p1: npt.NDArray[ numpy.float64 ] @classmethod - def fromTuples( cls, t0, t1, d, p0, p1 ): + def fromTuples( cls, t0: tuple[ tuple[ float, float, float ], ... ], t1: tuple[ tuple[ float, float, float ], ... ], + d: float, p0: tuple[ float, float, float ], p1: tuple[ float, float, float ] ) -> "ExpectedTri": + """Creates an ExpectedTri from tuples.""" return cls( numpy.array( t0 ), numpy.array( t1 ), float( d ), numpy.array( p0 ), numpy.array( p1 ) ) -def __getTrianglesReferences(): +def __getTrianglesReferences() -> Iterable[ ExpectedTri ]: + """Provides reference triangles for testing.""" # Node to node configuration. yield ExpectedTri.fromTuples( t0=( ( 0., 0., 0. ), ( 1., 0., 0. ), ( 0., 1., 1. ) ), t1=( ( 2., 0., 0. ), ( 3., 0., 0. ), ( 2., 1., 1. ) ), @@ -143,7 +157,12 @@ def __getTrianglesReferences(): @pytest.mark.parametrize( "expected", __getTrianglesReferences() ) -def test_triangles( expected: ExpectedTri ): +def test_triangles( expected: ExpectedTri ) -> None: + """Tests the distance between two triangles. + + Args: + expected (ExpectedTri): The expected triangle data. + """ eps = numpy.finfo( float ).eps d, p0, p1 = distanceBetweenTwoTriangles( expected.t0, expected.t1 ) assert abs( d - expected.d ) < eps From 2048e382337cae309bf6a34cfad956c11795ab5a Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 13:49:47 -0800 Subject: [PATCH 05/15] yapf --- .../mesh_doctor/actions/collocatedNodes.py | 4 +-- .../mesh_doctor/actions/generateFractures.py | 1 - .../geos/mesh_doctor/actions/nonConformal.py | 16 +++++------ .../mesh_doctor/actions/triangleDistance.py | 2 ++ .../parsing/_sharedChecksParsingLogic.py | 6 ++-- .../parsing/generateCubeParsing.py | 3 +- .../selfIntersectingElementsParsing.py | 26 +++++++---------- .../mesh_doctor/parsing/vtkOutputParsing.py | 28 ++++++++----------- mesh-doctor/tests/test_generateFractures.py | 12 ++++---- mesh-doctor/tests/test_reorientMesh.py | 2 +- .../tests/test_sharedChecksParsingLogic.py | 20 ++++++++++--- mesh-doctor/tests/test_triangleDistance.py | 2 +- 12 files changed, 62 insertions(+), 60 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py index d715c35e..e6932e61 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py @@ -19,7 +19,7 @@ class Result: wrongSupportElements: Collection[ int ] # Element indices with support node indices appearing more than once. -def findCollocatedNodesBuckets( mesh: vtkUnstructuredGrid, tolerance: float ) -> list[ tuple[ int, ... ] ]: +def findCollocatedNodesBuckets( mesh: vtkUnstructuredGrid, tolerance: float ) -> list[ tuple[ int, ...] ]: """Check all the nodes of a mesh and returns every bucket of nodes that are collocated within a tolerance. Args: @@ -57,7 +57,7 @@ def findCollocatedNodesBuckets( mesh: vtkUnstructuredGrid, tolerance: float ) -> # originalToFiltered[i] = pointId.get() filteredToOriginal[ pointId.get() ] = i # type: ignore[misc, call-overload] - collocatedNodesBuckets: list[ tuple[ int, ... ] ] = [] + collocatedNodesBuckets: list[ tuple[ int, ...] ] = [] for n, ns in rejectedPoints.items(): collocatedNodesBuckets.append( ( n, *ns ) ) return collocatedNodesBuckets diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index dcc4f990..8b36ae73 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -17,7 +17,6 @@ from geos.mesh.utils.arrayHelpers import hasArray from geos.mesh.utils.genericHelpers import toVtkIdList, vtkIter - IDMapping: TypeAlias = Mapping[ int, int ] CellsPointsCoords: TypeAlias = dict[ int, list[ tuple[ float ] ] ] Coordinates3D: TypeAlias = tuple[ float, float, float ] diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py index 54c1a522..ed77fbfc 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py @@ -50,9 +50,9 @@ def __init__( self, mesh: vtkUnstructuredGrid ) -> None: boundaryMesh, __normals, self.__originalCells = BoundaryMesh.__buildBoundaryMesh( mesh ) cellsToReorient: filter[ int ] = filter( lambda c: mesh.GetCell( c ).GetCellType() == VTK_POLYHEDRON, # type: ignore[arg-type] - map( self.__originalCells.GetValue, # type: ignore[attr-defined] - range( self.__originalCells.GetNumberOfValues() ) ) - ) + map( + self.__originalCells.GetValue, # type: ignore[attr-defined] + range( self.__originalCells.GetNumberOfValues() ) ) ) reorientedMesh = reorientMesh.reorientMesh( mesh, cellsToReorient ) self.reBoundaryMesh, reNormals, _ = BoundaryMesh.__buildBoundaryMesh( reorientedMesh, consistency=False ) numCells = boundaryMesh.GetNumberOfCells() @@ -348,12 +348,12 @@ def areFacesConformalUsingDistances( i: int, j: int, boundaryMesh: vtkUnstructur cpJ: vtkCell = boundaryMesh.GetCell( j ).NewInstance() cpJ.DeepCopy( boundaryMesh.GetCell( j ) ) - def triangulate( cell: vtkCell ) -> tuple[ tuple[ int, ... ], vtkPoints ]: + def triangulate( cell: vtkCell ) -> tuple[ tuple[ int, ...], vtkPoints ]: assert cell.GetCellDimension() == 2 _pointsIdsList = vtkIdList() _points = vtkPoints() cell.Triangulate( 0, _pointsIdsList, _points ) - _pointsIds: tuple[ int, ... ] = tuple( vtkIter( _pointsIdsList ) ) + _pointsIds: tuple[ int, ...] = tuple( vtkIter( _pointsIdsList ) ) assert len( _pointsIds ) % 3 == 0 assert _points.GetNumberOfPoints() % 3 == 0 return _pointsIds, _points @@ -361,7 +361,7 @@ def triangulate( cell: vtkCell ) -> tuple[ tuple[ int, ... ], vtkPoints ]: pointsIdsI, pointsI = triangulate( cpI ) pointsIdsJ, pointsJ = triangulate( cpJ ) - def buildNumpyTriangles( pointsIds: tuple[ int, ... ] ) -> list[ npt.NDArray[ np.float64 ] ]: + def buildNumpyTriangles( pointsIds: tuple[ int, ...] ) -> list[ npt.NDArray[ np.float64 ] ]: __triangles = [] for __i in range( 0, len( pointsIds ), 3 ): __t = [] @@ -492,8 +492,8 @@ def findNonConformalCells( mesh: vtkUnstructuredGrid, options: Options ) -> list # Extracting the original 3d element index (and not the index of the boundary mesh). nonConformalCells: list[ tuple[ int, int ] ] = [] for i, j in nonConformalCellsBoundaryId: - nonConformalCells.append( ( boundaryMesh.originalCells.GetValue( i ), - boundaryMesh.originalCells.GetValue( j ) ) ) + nonConformalCells.append( + ( boundaryMesh.originalCells.GetValue( i ), boundaryMesh.originalCells.GetValue( j ) ) ) return nonConformalCells diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py index 6e90c9d8..8b67197f 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py @@ -185,12 +185,14 @@ def distanceBetweenTwoTriangles( tri0: numpy.ndarray, areDisjoint = True # No edge pair contained the closest points. # Checking the node/face situation. + # yapf: disable distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri0, edges0, tri1 ) # type: ignore[ assignment ] if distance: return distance, sol0, sol1 areDisjoint = areDisjoint or areDisjointTmp distance, sol0, sol1, areDisjointTmp = __computeNodesToTriangleDistance( tri1, edges1, tri0 ) # type: ignore[ assignment ] + # yapf: enable if distance: return distance, sol0, sol1 areDisjoint = areDisjoint or areDisjointTmp diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index 6e0ae626..4b3317e2 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -42,7 +42,7 @@ def getOptionsUsedMessage( optionsUsed: object ) -> str: str: A message like "Parameters used: ( param1:value1 param2:value2 )" for as many paramters found. """ optionsMsg: str = "Parameters used: (" - if hasattr(optionsUsed, "__dataclass_fields__"): + if hasattr( optionsUsed, "__dataclass_fields__" ): for attrName in optionsUsed.__dataclass_fields__: attrValue = getattr( optionsUsed, attrName ) optionsMsg += f" {attrName} = {attrValue}" @@ -101,8 +101,8 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], # 1. Determine which checks to perform checksToDo = getattr( parsedArgs, CHECKS_TO_DO_ARG ) if not checksToDo: - finalSelectedCheckNames: list[str] = deepcopy( orderedCheckNames ) - setupLogger.info("All configured checks will be performed by default.") + finalSelectedCheckNames: list[ str ] = deepcopy( orderedCheckNames ) + setupLogger.info( "All configured checks will be performed by default." ) else: userChecks = parseCommaSeparatedString( checksToDo ) finalSelectedCheckNames = [] diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index 8f719c6f..12e3b555 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -18,7 +18,8 @@ def convert( parsedOptions: dict[ str, Any ] ) -> Options: Returns: Options: Configuration options for supported elements check. """ - def checkDiscretizations( x: tuple[ float, ... ], nx: tuple[ int, ... ], title: str ) -> None: + + def checkDiscretizations( x: tuple[ float, ...], nx: tuple[ int, ...], title: str ) -> None: if len( x ) != len( nx ) + 1: raise ValueError( f"{title} information (\"{x}\" and \"{nx}\") does not have consistent size." ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index 2ac20285..eca95155 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -26,10 +26,8 @@ def convert( parsedOptions: dict[ str, Any ] ) -> Options: """ minDistance = parsedOptions[ __MIN_DISTANCE ] if minDistance == 0: - setupLogger.warning( - "Having minimum distance set to 0 can induce lots of false positive results " - "(adjacent faces may be considered intersecting)." - ) + setupLogger.warning( "Having minimum distance set to 0 can induce lots of false positive results " + "(adjacent faces may be considered intersecting)." ) elif minDistance < 0: raise ValueError( f"Negative minimum distance ({minDistance}) in the {SELF_INTERSECTING_ELEMENTS} check is not allowed." ) @@ -44,18 +42,14 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: """ p = subparsers.add_parser( SELF_INTERSECTING_ELEMENTS, help="Checks if the faces of the elements are self intersecting." ) - help_text = ( - "[float]: The minimum distance in the computation. " - f"Defaults to your machine precision {__MIN_DISTANCE_DEFAULT}." - ) - p.add_argument( - '--' + __MIN_DISTANCE, - type=float, - required=False, - metavar=__MIN_DISTANCE_DEFAULT, - default=__MIN_DISTANCE_DEFAULT, - help=help_text - ) + help_text = ( "[float]: The minimum distance in the computation. " + f"Defaults to your machine precision {__MIN_DISTANCE_DEFAULT}." ) + p.add_argument( '--' + __MIN_DISTANCE, + type=float, + required=False, + metavar=__MIN_DISTANCE_DEFAULT, + default=__MIN_DISTANCE_DEFAULT, + help=help_text ) def displayResults( options: Options, result: Result ) -> None: diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index 20ba8048..2dfd2b32 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -17,12 +17,10 @@ def getVtkOutputHelp() -> str: Returns: str: Formatted help text describing output file and data mode options. """ - msg = ( - f"{__OUTPUT_FILE} [string]: The vtk output file destination.\n" - f" {__OUTPUT_BINARY_MODE} [string]: For \".vtu\" output format, " - f"the data mode can be {' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. " - f"Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." - ) + msg = ( f"{__OUTPUT_FILE} [string]: The vtk output file destination.\n" + f" {__OUTPUT_BINARY_MODE} [string]: For \".vtu\" output format, " + f"the data mode can be {' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. " + f"Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." ) return textwrap.dedent( msg ) @@ -50,17 +48,13 @@ def fillVtkOutputSubparser( parser: ArgumentParser | _ArgumentGroup, prefix: str type=str, required=True, help="[string]: The vtk output file destination." ) - help_text = ( - f"[string]: For \".vtu\" output format, the data mode can be " - f"{' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." - ) - parser.add_argument( - '--' + __buildArg( prefix, __OUTPUT_BINARY_MODE ), - type=str, - metavar=", ".join( __OUTPUT_BINARY_MODE_VALUES ), - default=__OUTPUT_BINARY_MODE_DEFAULT, - help=help_text - ) + help_text = ( f"[string]: For \".vtu\" output format, the data mode can be " + f"{' or '.join(__OUTPUT_BINARY_MODE_VALUES)}. Defaults to {__OUTPUT_BINARY_MODE_DEFAULT}." ) + parser.add_argument( '--' + __buildArg( prefix, __OUTPUT_BINARY_MODE ), + type=str, + metavar=", ".join( __OUTPUT_BINARY_MODE_VALUES ), + default=__OUTPUT_BINARY_MODE_DEFAULT, + help=help_text ) def convert( parsedOptions: dict[ str, Any ], prefix: str = "" ) -> VtkOutput: diff --git a/mesh-doctor/tests/test_generateFractures.py b/mesh-doctor/tests/test_generateFractures.py index e3453cff..abbbc067 100644 --- a/mesh-doctor/tests/test_generateFractures.py +++ b/mesh-doctor/tests/test_generateFractures.py @@ -11,8 +11,8 @@ from geos.mesh_doctor.actions.generateFractures import ( __splitMeshOnFractures, Options, FracturePolicy, Coordinates3D, IDMapping ) -BorderFacesNodesCoords: TypeAlias = tuple[ tuple[ Coordinates3D, ... ], ... ] -FaceNodesCoords: TypeAlias = tuple[ Coordinates3D, ... ] +BorderFacesNodesCoords: TypeAlias = tuple[ tuple[ Coordinates3D, ...], ...] +FaceNodesCoords: TypeAlias = tuple[ Coordinates3D, ...] IDMatrix: TypeAlias = Sequence[ Sequence[ int ] ] @@ -256,8 +256,8 @@ def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> BorderFacesN BorderFacesNodesCoords: For a rectilinear grid, returns a tuple of 6 faces nodeset. """ meshBounds: tuple[ float, float, float, float, float, float ] = mesh.GetBounds() - minBound: tuple[ float, ... ] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 0 ] ) - maxBound: tuple[ float, ... ] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 1 ] ) + minBound: tuple[ float, ...] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 0 ] ) + maxBound: tuple[ float, ...] = tuple( [ meshBounds[ i ] for i in range( len( meshBounds ) ) if i % 2 == 1 ] ) center: Coordinates3D = mesh.GetCenter() faceDiag: Coordinates3D = ( ( maxBound[ 0 ] - minBound[ 0 ] ) / 2, ( maxBound[ 1 ] - minBound[ 1 ] ) / 2, ( maxBound[ 2 ] - minBound[ 2 ] ) / 2 ) @@ -270,8 +270,8 @@ def findBordersFacesRectilinearGrid( mesh: vtkUnstructuredGrid ) -> BorderFacesN node6: Coordinates3D = ( center[ 0 ] - faceDiag[ 0 ], center[ 1 ] + faceDiag[ 1 ], center[ 2 ] + faceDiag[ 2 ] ) node7: Coordinates3D = ( center[ 0 ] + faceDiag[ 0 ], center[ 1 ] + faceDiag[ 1 ], center[ 2 ] + faceDiag[ 2 ] ) faces: BorderFacesNodesCoords = ( ( node0, node1, node3, node2 ), ( node4, node5, node7, node6 ), - ( node0, node2, node6, node4 ), ( node1, node3, node7, node5 ), - ( node0, node1, node5, node4 ), ( node2, node3, node7, node6 ) ) + ( node0, node2, node6, node4 ), ( node1, node3, node7, node5 ), + ( node0, node1, node5, node4 ), ( node2, node3, node7, node6 ) ) return faces diff --git a/mesh-doctor/tests/test_reorientMesh.py b/mesh-doctor/tests/test_reorientMesh.py index c9beea8b..df6d25d5 100644 --- a/mesh-doctor/tests/test_reorientMesh.py +++ b/mesh-doctor/tests/test_reorientMesh.py @@ -43,7 +43,7 @@ def __buildTestMeshes() -> Generator[ Expected, None, None ]: points.InsertNextPoint( coords ) # Creating the polyhedron with faces all directed outward. - faces: list[ tuple[ int, ... ] ] = [] + faces: list[ tuple[ int, ...] ] = [] # Creating the side faces for i in range( n ): faces.append( ( i % n + n, ( i + 1 ) % n + n, ( i + 1 ) % n, i % n ) ) diff --git a/mesh-doctor/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py index 2fb5a455..871a6979 100644 --- a/mesh-doctor/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -35,13 +35,19 @@ def checkFeaturesConfig() -> dict[ str, CheckFeature ]: CheckFeature( name="check1", optionsCls=MockOptions, resultCls=MockResult, - defaultParams={ "param1": 1.0, "param2": 2.0 }, + defaultParams={ + "param1": 1.0, + "param2": 2.0 + }, display=mockDisplayFunc ), "check2": CheckFeature( name="check2", optionsCls=MockOptions, resultCls=MockResult, - defaultParams={ "param1": 3.0, "param2": 4.0 }, + defaultParams={ + "param1": 3.0, + "param2": 4.0 + }, display=mockDisplayFunc ) } @@ -161,8 +167,14 @@ def test_displayResultsWithChecks( mockLogger: MagicMock, checkFeaturesConfig: d orderedCheckNames: list[ str ] ) -> None: """Tests displayResults when checks were performed.""" options = AllChecksOptions( checksToPerform=[ "check1", "check2" ], - checksOptions={ "check1": MockOptions(), "check2": MockOptions() }, - checkDisplays={ "check1": mockDisplayFunc, "check2": mockDisplayFunc } ) + checksOptions={ + "check1": MockOptions(), + "check2": MockOptions() + }, + checkDisplays={ + "check1": mockDisplayFunc, + "check2": mockDisplayFunc + } ) result = AllChecksResult( checkResults={ "check1": MockResult( value="result1" ), "check2": MockResult( value="result2" ) diff --git a/mesh-doctor/tests/test_triangleDistance.py b/mesh-doctor/tests/test_triangleDistance.py index 6034f721..9eabadbf 100644 --- a/mesh-doctor/tests/test_triangleDistance.py +++ b/mesh-doctor/tests/test_triangleDistance.py @@ -110,7 +110,7 @@ class ExpectedTri: p1: npt.NDArray[ numpy.float64 ] @classmethod - def fromTuples( cls, t0: tuple[ tuple[ float, float, float ], ... ], t1: tuple[ tuple[ float, float, float ], ... ], + def fromTuples( cls, t0: tuple[ tuple[ float, float, float ], ...], t1: tuple[ tuple[ float, float, float ], ...], d: float, p0: tuple[ float, float, float ], p1: tuple[ float, float, float ] ) -> "ExpectedTri": """Creates an ExpectedTri from tuples.""" return cls( numpy.array( t0 ), numpy.array( t1 ), float( d ), numpy.array( p0 ), numpy.array( p1 ) ) From ec0b92660f52ca64bd7e82864e1fb0aaf6f5f8ae Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 15:39:02 -0800 Subject: [PATCH 06/15] Add annotations from __future__ --- .../mesh_doctor/actions/supportedElements.py | 4 +- .../parsing/_sharedChecksParsingLogic.py | 1 + .../mesh_doctor/parsing/allChecksParsing.py | 1 + .../parsing/collocatedNodesParsing.py | 1 + .../parsing/elementVolumesParsing.py | 1 + .../parsing/fixElementsOrderingsParsing.py | 1 + .../parsing/generateCubeParsing.py | 5 ++- .../parsing/generateGlobalIdsParsing.py | 44 ++++++++++++------- .../mesh_doctor/parsing/mainChecksParsing.py | 1 + .../parsing/nonConformalParsing.py | 1 + .../selfIntersectingElementsParsing.py | 1 + .../parsing/supportedElementsParsing.py | 1 + .../mesh_doctor/parsing/vtkOutputParsing.py | 1 + 13 files changed, 42 insertions(+), 21 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py index 466fa91d..d3d600d2 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py @@ -9,8 +9,8 @@ from vtkmodules.vtkCommonDataModel import ( vtkCellTypes, vtkUnstructuredGrid, VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_LINE, VTK_PENTAGONAL_PRISM, VTK_POLYGON, VTK_POLYHEDRON, VTK_PYRAMID, VTK_QUAD, VTK_TETRA, VTK_TRIANGLE, VTK_VERTEX, VTK_VOXEL, VTK_WEDGE ) -from geos.mesh.doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream -from geos.mesh.doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.actions.vtkPolyhedron import buildFaceToFaceConnectivityThroughEdges, FaceStream +from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import readUnstructuredGrid from geos.mesh.utils.genericHelpers import vtkIter diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index 4b3317e2..02c8e52b 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -1,3 +1,4 @@ +from __future__ import annotations import argparse from copy import deepcopy from dataclasses import dataclass diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py index ace87cf2..c24dbaa1 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations import argparse from copy import deepcopy from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index 43b7a277..ceffe978 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction from typing import Any from geos.mesh_doctor.actions.collocatedNodes import Options, Result diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py index 1481f40e..319e464a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction from typing import Any from geos.mesh_doctor.actions.elementVolumes import Options, Result diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py index bd87e905..ad9b65e5 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction import random from vtkmodules.vtkCommonDataModel import ( diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index 12e3b555..5bbbfd87 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction from typing import Any from geos.mesh_doctor.actions.generateCube import Options, Result, FieldInfo @@ -40,7 +41,7 @@ def parseFields( s: str ) -> FieldInfo: raise ValueError( f"Dimension {dimension} must be a positive integer" ) from e return FieldInfo( name=name, support=support, dimension=dimension ) - gids: GlobalIdsInfo = generateGlobalIdsParsing.convertGlobalIds( parsedOptions ) + gids: GlobalIdsInfo = generateGlobalIdsParsing.convert( parsedOptions ) return Options( vtkOutput=vtkOutputParsing.convert( parsedOptions ), generateCellsGlobalIds=gids.cells, @@ -92,7 +93,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: required=False, default=(), help="Create fields on CELLS or POINTS, with given dimension (typically 1 or 3)." ) - generateGlobalIdsParsing.fillGenerateGlobalIdsSubparser( p ) + generateGlobalIdsParsing.addArguments( p ) vtkOutputParsing.fillVtkOutputSubparser( p ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py index 016e5178..a4966d28 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py @@ -1,4 +1,5 @@ -from argparse import _SubParsersAction +from __future__ import annotations +from argparse import _SubParsersAction, ArgumentParser, _ArgumentGroup from dataclasses import dataclass from typing import Any from geos.mesh_doctor.actions.generateGlobalIds import Options, Result @@ -29,6 +30,30 @@ def convert( parsedOptions: dict[ str, Any ] ) -> Options: generatePointsGlobalIds=gids.points ) +def addArguments( parser: ArgumentParser | _ArgumentGroup ) -> None: + """Add global IDs arguments to an existing parser. + + Args: + parser: The argument parser or argument group to add arguments to. + """ + parser.add_argument( '--' + __CELLS, + action="store_true", + help="[bool]: Generate global ids for cells. Defaults to true." ) + parser.add_argument( '--no-' + __CELLS, + action="store_false", + dest=__CELLS, + help="[bool]: Don't generate global ids for cells." ) + parser.set_defaults( **{ __CELLS: True } ) + parser.add_argument( '--' + __POINTS, + action="store_true", + help="[bool]: Generate global ids for points. Defaults to true." ) + parser.add_argument( '--no-' + __POINTS, + action="store_false", + dest=__POINTS, + help="[bool]: Don't generate global ids for points." ) + parser.set_defaults( **{ __POINTS: True } ) + + def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: """Add supported elements check subparser with its arguments. @@ -36,22 +61,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( GENERATE_GLOBAL_IDS, help="Adds globals ids for points and cells." ) - p.add_argument( '--' + __CELLS, - action="store_true", - help="[bool]: Generate global ids for cells. Defaults to true." ) - p.add_argument( '--no-' + __CELLS, - action="store_false", - dest=__CELLS, - help="[bool]: Don't generate global ids for cells." ) - p.set_defaults( **{ __CELLS: True } ) - p.add_argument( '--' + __POINTS, - action="store_true", - help="[bool]: Generate global ids for points. Defaults to true." ) - p.add_argument( '--no-' + __POINTS, - action="store_false", - dest=__POINTS, - help="[bool]: Don't generate global ids for points." ) - p.set_defaults( **{ __POINTS: True } ) + addArguments( p ) vtkOutputParsing.fillVtkOutputSubparser( p ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py index 7e1dbd3f..6a90c7a4 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations import argparse from copy import deepcopy from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index b6b02bc9..790f9daf 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction from typing import Any from geos.mesh_doctor.actions.nonConformal import Options, Result diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index eca95155..0d987701 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations from argparse import _SubParsersAction import numpy from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py index 091954af..848ed7d4 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations import multiprocessing from argparse import _SubParsersAction from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index 2dfd2b32..c9882c5d 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os.path import textwrap from argparse import ArgumentParser, _ArgumentGroup From 3d77e144cea169a2a97de7f5265f2d19aa5cafa3 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 15:43:19 -0800 Subject: [PATCH 07/15] Update all documentation and install procedures --- README.md | 4 +- docs/conf.py | 30 +++- docs/geos-mesh.rst | 2 - docs/geos_mesh_docs/doctor.rst | 317 --------------------------------- docs/index.rst | 2 + docs/mesh-doctor.rst | 203 +++++++++++++++++++++ geos-mesh/pyproject.toml | 2 - install_packages.sh | 1 + 8 files changed, 235 insertions(+), 326 deletions(-) delete mode 100644 docs/geos_mesh_docs/doctor.rst create mode 100644 docs/mesh-doctor.rst diff --git a/README.md b/README.md index b0b3f908..5736f9e9 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ GEOS Python packages dependency tree (inter-dependency and main external depende │ └── hdf5-wrapper | ├── mesh-doctor -│ ├── geos-prep -│ └── pyvista +│ ├── geos-utils +│ └── geos-mesh | ├── geos-trame │ ├── geos-xml-tools diff --git a/docs/conf.py b/docs/conf.py index 7f9197b0..91bde12b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,20 +14,44 @@ # import os import sys +import subprocess # Add python modules to be documented python_root = '..' -python_modules = ( 'geos-ats', 'geos-geomechanics', 'geos-mesh', 'geos-posp', 'geos-processing', 'geos-pv', 'geos-timehistory', - 'geos-utils', 'geos-xml-tools', 'geos-xml-viewer', 'hdf5-wrapper', 'pygeos-tools' ) +python_modules = ( 'geos-ats', 'geos-geomechanics', 'geos-mesh', 'geos-posp', 'geos-processing', 'geos-pv', + 'geos-timehistory', 'geos-utils', 'geos-xml-tools', 'geos-xml-viewer', 'hdf5-wrapper', + 'mesh-doctor', 'pygeos-tools' ) for m in python_modules: sys.path.insert( 0, os.path.abspath( os.path.join( python_root, m, 'src' ) ) ) +# Install mesh-doctor in editable mode if not already available +# This ensures mesh-doctor command is available for sphinxcontrib.programoutput +try: + subprocess.run( [ sys.executable, '-m', 'geos.mesh_doctor.cli', '--help' ], + capture_output=True, check=True, timeout=5 ) +except ( subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError ): + # mesh-doctor not available, install it and its dependencies in editable mode + print( "Installing mesh-doctor dependencies for documentation generation..." ) + packages_to_install = [ 'geos-utils', 'geos-mesh', 'mesh-doctor' ] + for pkg in packages_to_install: + pkg_path = os.path.abspath( os.path.join( python_root, pkg ) ) + try: + print( f" Installing {pkg}..." ) + subprocess.run( [ sys.executable, '-m', 'pip', 'install', '-e', pkg_path ], + check=True, capture_output=True ) + except subprocess.CalledProcessError as e: + print( f"Warning: Could not install {pkg}: {e}" ) + print( "Documentation may be incomplete." ) + break + else: + print( "mesh-doctor installed successfully." ) + # -- Project information ----------------------------------------------------- project = u'GEOS Python Packages' -copyright = u'2018-2024 Lawrence Livermore National Security, The Board of Trustees of the Leland Stanford Junior University, TotalEnergies, and GEOSX Contributors.' +copyright = u'2018-2025 Lawrence Livermore National Security, The Board of Trustees of the Leland Stanford Junior University, TotalEnergies, and GEOS Contributors.' author = u'GEOS Contributors' # The short X.Y version diff --git a/docs/geos-mesh.rst b/docs/geos-mesh.rst index cb8a5264..ba604f8f 100644 --- a/docs/geos-mesh.rst +++ b/docs/geos-mesh.rst @@ -7,8 +7,6 @@ GEOS Mesh tools :maxdepth: 1 :caption: Contents: - ./geos_mesh_docs/doctor - ./geos_mesh_docs/converter ./geos_mesh_docs/io diff --git a/docs/geos_mesh_docs/doctor.rst b/docs/geos_mesh_docs/doctor.rst deleted file mode 100644 index 123d673a..00000000 --- a/docs/geos_mesh_docs/doctor.rst +++ /dev/null @@ -1,317 +0,0 @@ -Mesh Doctor ---------------- - -``mesh-doctor`` is a ``python`` executable that can be used through the command line to perform various checks, validations, and tiny fixes to the ``vtk`` mesh that are meant to be used in ``geos``. -``mesh-doctor`` is organized as a collection of modules with their dedicated sets of options. -The current page will introduce those modules, but the details and all the arguments can be retrieved by using the ``--help`` option for each module. - -Prerequisites -^^^^^^^^^^^^^ - -To use mesh-doctor, you first need to have installed the ``geos-mesh`` package using the following command: - -.. code-block:: bash - - python -m pip install --upgrade ./geos-mesh - -Once done, you can call ``mesh-doctor`` or ``meshDoctor`` in your command line as presented in the rest of this documentation. - -Modules -^^^^^^^ - -To list all the modules available through ``mesh-doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. - -.. code-block:: - - $ mesh-doctor --help - usage: meshDoctor.py [-h] [-v] [-q] -i VTK_MESH_FILE - {collocatedNodes,elementVolumes,fixElementsOrderings,generateCube,generateFractures,generateGlobalIds,nonConformal,selfIntersectingElements,supportedElements} - ... - Inspects meshes for GEOSX. - positional arguments: - {collocatedNodes,elementVolumes,fixElementsOrderings,generateCube,generateFractures,generateGlobalIds,nonConformal,selfIntersectingElements,supportedElements} - Modules - collocatedNodes - Checks if nodes are collocated. - elementVolumes - Checks if the volumes of the elements are greater than "min". - fixElementsOrderings - Reorders the support nodes for the given cell types. - generateCube - Generate a cube and its fields. - generateFractures - Splits the mesh to generate the faults and fractures. [EXPERIMENTAL] - generateGlobalIds - Adds globals ids for points and cells. - nonConformal - Detects non conformal elements. [EXPERIMENTAL] - selfIntersectingElements - Checks if the faces of the elements are self intersecting. - supportedElements - Check that all the elements of the mesh are supported by GEOSX. - options: - -h, --help - show this help message and exit - -v Use -v 'INFO', -vv for 'DEBUG'. Defaults to 'WARNING'. - -q Use -q to reduce the verbosity of the output. - -i VTK_MESH_FILE, --vtk-input-file VTK_MESH_FILE - Note that checks are dynamically loaded. - An option may be missing because of an unloaded module. - Increase verbosity (-v, -vv) to get full information. - -Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor module_name --help`` pattern. -For example - -.. code-block:: - - $ mesh-doctor collocatedNodes --help - usage: meshDoctor.py collocatedNodes [-h] --tolerance TOLERANCE - options: - -h, --help show this help message and exit - --tolerance TOLERANCE [float]: The absolute distance between two nodes for them to be considered collocated. - -``mesh-doctor`` loads its module dynamically. -If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. -If you see a message like - -.. code-block:: bash - - [1970-04-14 03:07:15,625][WARNING] Could not load module "collocatedNodes": No module named 'vtkmodules' - -then most likely ``mesh-doctor`` could not load the ``collocatedNodes`` module, because the ``vtk`` python package was not found. -Thereafter, the documentation for module ``collocatedNodes`` will not be displayed. -You can solve this issue by installing the dependencies of ``mesh-doctor`` defined in its ``requirements.txt`` file (``python -m pip install -r requirements.txt``). - -Here is a list and brief description of all the modules available. - -``allChecks`` and ``mainChecks`` -"""""""""""""""""""""""""""""""" - -``mesh-doctor`` modules are called ``actions`` and they can be split into 2 different categories: -``check actions`` that will give you a feedback on a .vtu mesh that you would like to use in GEOS. -``operate actions`` that will either create a new mesh or modify an existing mesh. - -``allChecks`` aims at applying every single ``check`` action in one single command. The available list is of check is: -``collocatedNodes``, ``elementVolumes``, ``nonConformal``, ``selfIntersectingElements``, ``supportedElements``. - -``mainChecks`` does only the fastest checks ``collocatedNodes``, ``elementVolumes`` and ``selfIntersectingElements`` -that can quickly highlight some issues to deal with before investigating the other checks. - -Both ``allChecks`` and ``mainChecks`` have the same keywords and can be operated in the same way. The example below shows -the case of ``allChecks``, but it can be swapped for ``mainChecks``. - -.. code-block:: - - $ mesh-doctor allChecks --help - usage: mesh-doctor allChecks [-h] [--checksToPerform checksToPerform] [--set_parameters SET_PARAMETERS] - - options: - -h, --help show this help message and exit - --checksToPerform checksToPerform - Comma-separated list of mesh-doctor checks to perform. - If no input was given, all of the following checks will be executed by default: ['collocatedNodes', 'elementVolumes', 'selfIntersectingElements']. - The available choices for checks are ['collocatedNodes', 'elementVolumes', 'nonConformal', 'selfIntersectingElements', 'supportedElements']. - If you want to choose only certain of them, you can name them individually. - Example: --checksToPerform collocatedNodes,elementVolumes (default: ) - --setParameters setParameters - Comma-separated list of parameters to set for the checks (e.g., 'param_name:value'). These parameters override the defaults. - Default parameters are: For collocatedNodes: tolerance:0.0. For elementVolumes: minVolume:0.0. - For nonConformal: angleTolerance:10.0, pointTolerance:0.0, faceTolerance:0.0. - For selfIntersectingElements: minDistance:2.220446049250313e-16. For supportedElements: chunkSize:1, nproc:8. - Example: --setParameters parameter_name:10.5,other_param:25 (default: ) - -``collocatedNodes`` -"""""""""""""""""""" - -Displays the neighboring nodes that are closer to each other than a prescribed threshold. -It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. - -.. code-block:: - - $ mesh-doctor collocatedNodes --help - usage: meshDoctor.py collocatedNodes [-h] --tolerance TOLERANCE - options: - -h, --help show this help message and exit - --tolerance TOLERANCE [float]: The absolute distance between two nodes for them to be considered collocated. - -``elementVolumes`` -"""""""""""""""""" - -Computes the volumes of all the cells and displays the ones that are below a prescribed threshold. -Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. - -.. code-block:: - - $ mesh-doctor elementVolumes --help - usage: meshDoctor.py elementVolumes [-h] --minVolume 0.0 - options: - -h, --help show this help message and exit - --minVolume 0.0 [float]: The minimum acceptable volume. Defaults to 0.0. - -``fixElementsOrderings`` -"""""""""""""""""""""""" - -It sometimes happens that an exported mesh does not abide by the ``vtk`` orderings. -The ``fixElementsOrderings`` module can rearrange the nodes of given types of elements. -This can be convenient if you cannot regenerate the mesh. - -.. code-block:: - - $ mesh-doctor fixElementsOrderings --help - usage: meshDoctor.py fixElementsOrderings [-h] [--Hexahedron 1,6,5,4,7,0,2,3] [--Prism5 8,2,0,7,6,9,5,1,4,3] - [--Prism6 11,2,8,10,5,0,9,7,6,1,4,3] [--Pyramid 3,4,0,2,1] - [--Tetrahedron 2,0,3,1] [--Voxel 1,6,5,4,7,0,2,3] - [--Wedge 3,5,4,0,2,1] --output OUTPUT [--data-mode binary, ascii] - options: - -h, --help show this help message and exit - --Hexahedron 1,6,5,4,7,0,2,3 - [list of integers]: node permutation for "Hexahedron". - --Prism5 8,2,0,7,6,9,5,1,4,3 - [list of integers]: node permutation for "Prism5". - --Prism6 11,2,8,10,5,0,9,7,6,1,4,3 - [list of integers]: node permutation for "Prism6". - --Pyramid 3,4,0,2,1 [list of integers]: node permutation for "Pyramid". - --Tetrahedron 2,0,3,1 [list of integers]: node permutation for "Tetrahedron". - --Voxel 1,6,5,4,7,0,2,3 [list of integers]: node permutation for "Voxel". - --Wedge 3,5,4,0,2,1 [list of integers]: node permutation for "Wedge". - --output OUTPUT [string]: The vtk output file destination. - --data-mode binary, ascii - [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. - -``generateCube`` -"""""""""""""""" - -This module conveniently generates cubic meshes in ``vtk``. -It can also generate fields with simple values. -This tool can also be useful to generate a trial mesh that will later be refined or customized. - -.. code-block:: - - $ mesh-doctor generateCube --help - usage: meshDoctor.py generateCube [-h] [--x 0:1.5:3] [--y 0:5:10] [--z 0:1] [--nx 2:2] [--ny 1:1] [--nz 4] - [--fields name:support:dim [name:support:dim ...]] [--cells] [--no-cells] - [--points] [--no-points] --output OUTPUT [--data-mode binary, ascii] - options: - -h, --help show this help message and exit - --x 0:1.5:3 [list of floats]: X coordinates of the points. - --y 0:5:10 [list of floats]: Y coordinates of the points. - --z 0:1 [list of floats]: Z coordinates of the points. - --nx 2:2 [list of integers]: Number of elements in the X direction. - --ny 1:1 [list of integers]: Number of elements in the Y direction. - --nz 4 [list of integers]: Number of elements in the Z direction. - --fields name:support:dim - [name:support:dim ...]: Create fields on CELLS or POINTS, with given dimension (typically 1 or 3). - --cells [bool]: Generate global ids for cells. Defaults to true. - --no-cells [bool]: Don't generate global ids for cells. - --points [bool]: Generate global ids for points. Defaults to true. - --no-points [bool]: Don't generate global ids for points. - --output OUTPUT [string]: The vtk output file destination. - --data-mode binary, ascii - [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. - -``generateFractures`` -""""""""""""""""""""" - -For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to be split at the faces where the fracture gets across the mesh. -The ``generateFractures`` module will split the mesh and generate the multi-block ``vtk`` files. - -.. code-block:: - - $ mesh-doctor generateFractures --help - usage: meshDoctor.py generateFractures [-h] --policy field, internalSurfaces [--name NAME] [--values VALUES] --output OUTPUT - [--data-mode binary, ascii] [--fracturesOutputDir FRACTURES_OUTPUT_DIR] - options: - -h, --help show this help message and exit - --policy field, internalSurfaces - [string]: The criterion to define the surfaces that will be changed into fracture zones. Possible values are "field, internalSurfaces" - --name NAME [string]: If the "field" policy is selected, defines which field will be considered to define the fractures. - If the "internalSurfaces" policy is selected, defines the name of the attribute will be considered to identify the fractures. - --values VALUES [list of comma separated integers]: If the "field" policy is selected, which changes of the field will be considered as a fracture. - If the "internalSurfaces" policy is selected, list of the fracture attributes. - You can create multiple fractures by separating the values with ':' like shown in this example. - --values 10,12:13,14,16,18:22 will create 3 fractures identified respectively with the values (10,12), (13,14,16,18) and (22). - If no ':' is found, all values specified will be assumed to create only 1 single fracture. - --output OUTPUT [string]: The vtk output file destination. - --data-mode binary, ascii - [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. - --fracturesOutputDir FRACTURES_OUTPUT_DIR - [string]: The output directory for the fractures meshes that will be generated from the mesh. - --fracturesDataMode FRACTURES_DATA_MODE - [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. - -``generateGlobalIds`` -""""""""""""""""""""" - -When running ``geos`` in parallel, `global ids` can be used to refer to data across multiple ranks. -The ``generateGlobalIds`` can generate `global ids` for the imported ``vtk`` mesh. - -.. code-block:: - - $ mesh-doctor generateGlobalIds --help - usage: meshDoctor.py generateGlobalIds [-h] [--cells] [--no-cells] [--points] [--no-points] --output OUTPUT - [--data-mode binary, ascii] - options: - -h, --help show this help message and exit - --cells [bool]: Generate global ids for cells. Defaults to true. - --no-cells [bool]: Don't generate global ids for cells. - --points [bool]: Generate global ids for points. Defaults to true. - --no-points [bool]: Don't generate global ids for points. - --output OUTPUT [string]: The vtk output file destination. - --data-mode binary, ascii - [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. - -``nonConformal`` -"""""""""""""""" - -This module will detect elements which are close enough (there's a user defined threshold) but which are not in front of each other (another threshold can be defined). -`Close enough` can be defined in terms or proximity of the nodes and faces of the elements. -The angle between two faces can also be precribed. -This module can be a bit time consuming. - -.. code-block:: - - $ mesh-doctor nonConformal --help - usage: meshDoctor.py nonConformal [-h] [--angleTolerance 10.0] [--pointTolerance POINT_TOLERANCE] - [--faceTolerance FACE_TOLERANCE] - options: - -h, --help show this help message and exit - --angleTolerance 10.0 [float]: angle tolerance in degrees. Defaults to 10.0 - --pointTolerance POINT_TOLERANCE - [float]: tolerance for two points to be considered collocated. - --faceTolerance FACE_TOLERANCE - [float]: tolerance for two faces to be considered "touching". - -``selfIntersectingElements`` -"""""""""""""""""""""""""""" - -Some meshes can have cells that auto-intersect. -This module will display the elements that have faces intersecting. - -.. code-block:: - - $ mesh-doctor selfIntersectingElements --help - usage: meshDoctor.py selfIntersectingElements [-h] [--minDistance 2.220446049250313e-16] - options: - -h, --help show this help message and exit - --minDistance 2.220446049250313e-16 - [float]: The tolerance in the computation. Defaults to your machine precision 2.220446049250313e-16. - -``supportedElements`` -""""""""""""""""""""" - -``geos`` supports a specific set of elements. -Let's cite the standard elements like `tetrahedra`, `wedges`, `pyramids` or `hexahedra`. -But also prismes up to 11 faces. -``geos`` also supports the generic ``VTK_POLYHEDRON``/``42`` elements, which are converted on the fly into one of the elements just described. - -The ``supportedElements`` check will validate that no unsupported element is included in the input mesh. -It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. - -.. code-block:: - - $ mesh-doctor supportedElements --help - usage: meshDoctor.py supportedElements [-h] [--chunkSize 1] [--nproc 8] - options: - -h, --help show this help message and exit - --chunkSize 1 [int]: Defaults chunk size for parallel processing to 1 - --nproc 8 [int]: Number of threads used for parallel processing. Defaults to your CPU count 8. \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index b7175f8d..e763df6f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -100,4 +100,6 @@ Packages geos-xml-viewer + mesh-doctor + pygeos-tools \ No newline at end of file diff --git a/docs/mesh-doctor.rst b/docs/mesh-doctor.rst new file mode 100644 index 00000000..410d6cdd --- /dev/null +++ b/docs/mesh-doctor.rst @@ -0,0 +1,203 @@ +Mesh Doctor +----------- + +| ``mesh-doctor`` is a ``python`` executable that can be used through the command line to perform various checks, validations, and tiny fixes to the ``vtkUnstructuredGrid`` mesh that are meant to be used in ``geos``. + ``mesh-doctor`` is organized as a collection of modules with their dedicated sets of options. +| The current page will introduce those modules, but the details and all the arguments can be retrieved by using the ``--help`` option for each module. + +Prerequisites +^^^^^^^^^^^^^ + +To use mesh-doctor, you need to have installed the mandatory packages using the following commands (in this order): + +.. code-block:: bash + + python -m pip install --upgrade ./geos-utils + python -m pip install --upgrade ./geos-mesh + python -m pip install --upgrade ./mesh-doctor + +Once done, you can call ``mesh-doctor`` in your command line as presented in the rest of this documentation. + +Modules +^^^^^^^ + +To list all the modules available through ``mesh-doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. + +.. command-output:: mesh-doctor --help + :shell: + +Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor -i random.vtu module_name --help`` pattern. +For example + +.. command-output:: mesh-doctor -i random.vtu collocatedNodes --help + :shell: + +``mesh-doctor`` loads its module dynamically. +If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. +If you see a message like + +.. code-block:: bash + + [1970-04-14 03:07:15,625][WARNING] Could not load module "collocatedNodes": No module named 'vtkmodules' + +then most likely ``mesh-doctor`` could not load the ``collocatedNodes`` module, because the ``vtk`` python package was not found. +Thereafter, the documentation for module ``collocatedNodes`` will not be displayed. +You can solve this issue by installing the dependencies of ``mesh-doctor`` defined in its ``requirements.txt`` file (``python -m pip install -r requirements.txt``). + +Here is a list and brief description of all the modules available. + +``allChecks`` and ``mainChecks`` +"""""""""""""""""""""""""""""""" + +``mesh-doctor`` modules are called ``actions`` and they can be split into 2 different categories: +``check actions`` that will give you a feedback on a .vtu mesh that you would like to use in GEOS. +``operate actions`` that will either create a new mesh or modify an existing mesh. + +``allChecks`` aims at applying every single ``check`` action in one single command. The available list is of check is: +``collocatedNodes``, ``elementVolumes``, ``nonConformal``, ``selfIntersectingElements``, ``supportedElements``. + +``mainChecks`` does only the fastest checks ``collocatedNodes``, ``elementVolumes`` and ``selfIntersectingElements`` +that can quickly highlight some issues to deal with before investigating the other checks. + +Both ``allChecks`` and ``mainChecks`` have the same keywords and can be operated in the same way. The example below shows +the case of ``allChecks``, but it can be swapped for ``mainChecks``. + +.. command-output:: mesh-doctor -i random.vtu allChecks --help + :shell: + +``collocatedNodes`` +""""""""""""""""""" + +Displays the neighboring nodes that are closer to each other than a prescribed threshold. +It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. + +.. command-output:: mesh-doctor -i random.vtu collocatedNodes --help + :shell: + +``elementVolumes`` +"""""""""""""""""" + +Computes the volumes of all the cells and displays the ones that are below a prescribed threshold. +Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. + +.. command-output:: mesh-doctor -i random.vtu elementVolumes --help + :shell: + +``fixElementsOrderings`` +"""""""""""""""""""""""" + +It sometimes happens that an exported mesh does not abide by the ``vtk`` orderings. +The ``fixElementsOrderings`` module can rearrange the nodes of given types of elements. +This can be convenient if you cannot regenerate the mesh. + +.. command-output:: mesh-doctor -i random.vtu fixElementsOrderings --help + :shell: + +``generateCube`` +"""""""""""""""" + +This module conveniently generates cubic meshes in ``vtk``. +It can also generate fields with simple values. +This tool can also be useful to generate a trial mesh that will later be refined or customized. + +.. command-output:: mesh-doctor -i random.vtu generateCube --help + :shell: + +``generateFractures`` +""""""""""""""""""""" + +For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to be split at the faces where the fracture gets across the mesh. +The ``generateFractures`` module will split the mesh and generate the multi-block ``vtk`` files. + +.. command-output:: mesh-doctor -i random.vtu generateFractures --help + :shell: + +``generateGlobalIds`` +""""""""""""""""""""" + +When running ``geos`` in parallel, `global ids` can be used to refer to data across multiple ranks. +The ``generateGlobalIds`` can generate `global ids` for the imported ``vtk`` mesh. + +.. command-output:: mesh-doctor -i random.vtu generateGlobalIds --help + :shell: + +``nonConformal`` +"""""""""""""""" + +This module will detect elements which are close enough (there's a user defined threshold) but which are not in front of each other (another threshold can be defined). +`Close enough` can be defined in terms or proximity of the nodes and faces of the elements. +The angle between two faces can also be precribed. +This module can be a bit time consuming. + +.. command-output:: mesh-doctor -i random.vtu nonConformal --help + :shell: + +``selfIntersectingElements`` +"""""""""""""""""""""""""""" + +Some meshes can have cells that auto-intersect. +This module will display the elements that have faces intersecting. + +.. command-output:: mesh-doctor -i random.vtu selfIntersectingElements --help + :shell: + +``supportedElements`` +""""""""""""""""""""" + +``geos`` supports a specific set of elements. +Let's cite the standard elements like `tetrahedra`, `wedges`, `pyramids` or `hexahedra`. +But also prismes up to 11 faces. +``geos`` also supports the generic ``VTK_POLYHEDRON``/``42`` elements, which are converted on the fly into one of the elements just described. + +The ``supportedElements`` check will validate that no unsupported element is included in the input mesh. +It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. + +.. command-output:: mesh-doctor -i random.vtu supportedElements --help + :shell: + + +Why only use vtkUnstructuredGrid? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +| The mesh doctor is designed specifically for unstructured meshes used in GEOS. +| All input files are expected to be ``.vtu`` (VTK Unstructured Grid) format. +| What about other formats? + +VTK Hierarchy +""""""""""""" + +Supposedly, other grid types that are part of the following VTK hierarchy could be used:: + + vtkDataObject + └── vtkDataSet + └── vtkCartesianGrid + └── vtkRectilinearGrid + └── vtkImageData + └── vtkStructuredPoints + └── vtkUniformGrid + └── vtkPointSet + └── vtkExplicitStructuredGrid + └── vtkPolyData + └── vtkStructuredGrid + └── vtkUnstructuredGrid + +And when looking at specific methods used in mesh-doctor, it could suggest that other formats could be used: + +* Points access: ``mesh.GetPoints()`` - Available in all vtkPointSet subclasses ✓ +* Cell iteration: ``mesh.GetNumberOfCells()``, ``mesh.GetCell()`` - Available in all vtkDataSet subclasses ✓ +* Cell types: ``mesh.GetCellType()`` - Available in all vtkDataSet subclasses ✓ +* Cell/Point data: ``mesh.GetCellData()``, ``mesh.GetPointData()`` - Available in all vtkDataSet subclasses ✓ + +VTK Filter Compatibility +"""""""""""""""""""""""" + +| ``vtkCellSizeFilter``, ``vtkMeshQuality``, and other VTK filters used in the actions expect ``vtkDataSet`` or its subclasses + ``vtkUnstructuredGrid`` is compatible with all VTK filters used. +| ``vtkPolyData`` has a different data structure, not suitable for 3D volumetric meshes. + +Specific Operations Require vtkUnstructuredGrid +""""""""""""""""""""""""""""""""""""""""""""""" + +* ``GetCellNeighbors()`` - Only available in vtkUnstructuredGrid +* ``GetFaceStream()`` - Only available in vtkUnstructuredGrid (for polyhedron support) +* ``GetDistinctCellTypesArray()`` - Only available in vtkUnstructuredGrid \ No newline at end of file diff --git a/geos-mesh/pyproject.toml b/geos-mesh/pyproject.toml index 2daf4301..7cf4ef76 100644 --- a/geos-mesh/pyproject.toml +++ b/geos-mesh/pyproject.toml @@ -39,8 +39,6 @@ dependencies = [ ] [project.scripts] - mesh-doctor = "geos.mesh.doctor.meshDoctor:main" - meshDoctor = "geos.mesh.doctor.meshDoctor:main" convert_abaqus = "geos.mesh.conversion.main:main" [project.urls] diff --git a/install_packages.sh b/install_packages.sh index b6726ef7..4a1a8b45 100755 --- a/install_packages.sh +++ b/install_packages.sh @@ -8,6 +8,7 @@ python -m pip install --upgrade ./geos-xml-tools python -m pip install --upgrade ./geos-xml-viewer python -m pip install --upgrade ./hdf5-wrapper python -m pip install --upgrade ./geos-timehistory +python -m pip install --upgrade ./mesh-doctor python -m pip install --upgrade ./pygeos-tools python -m pip install --upgrade ./geos-pv #! trame install requires npm From 24f8c293cac43288db8e1c06c7ab5623ba753f05 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 17:02:45 -0800 Subject: [PATCH 08/15] Improve mesh-doctor command line --- docs/mesh-doctor.rst | 37 +++++++++++++------ .../src/geos/mesh_doctor/meshDoctor.py | 9 ++++- .../parsing/_sharedChecksParsingLogic.py | 22 +++++++---- .../mesh_doctor/parsing/allChecksParsing.py | 3 +- .../geos/mesh_doctor/parsing/cliParsing.py | 23 ++++++++---- .../parsing/collocatedNodesParsing.py | 3 +- .../parsing/elementVolumesParsing.py | 3 +- .../parsing/fixElementsOrderingsParsing.py | 3 +- .../parsing/generateCubeParsing.py | 7 ++-- .../parsing/generateFracturesParsing.py | 2 + .../parsing/generateGlobalIdsParsing.py | 17 ++++++++- .../mesh_doctor/parsing/mainChecksParsing.py | 3 +- .../parsing/nonConformalParsing.py | 3 +- .../selfIntersectingElementsParsing.py | 3 +- .../parsing/supportedElementsParsing.py | 3 +- .../mesh_doctor/parsing/vtkOutputParsing.py | 4 -- 16 files changed, 100 insertions(+), 45 deletions(-) diff --git a/docs/mesh-doctor.rst b/docs/mesh-doctor.rst index 410d6cdd..2ff03f0e 100644 --- a/docs/mesh-doctor.rst +++ b/docs/mesh-doctor.rst @@ -26,12 +26,18 @@ To list all the modules available through ``mesh-doctor``, you can simply use th .. command-output:: mesh-doctor --help :shell: -Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor -i random.vtu module_name --help`` pattern. +Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor module_name --help`` pattern. For example -.. command-output:: mesh-doctor -i random.vtu collocatedNodes --help +.. command-output:: mesh-doctor collocatedNodes --help :shell: +Finally, to execute a specific module, you can use the following pattern: + +.. code-block:: bash + + mesh-doctor module_name -i /path/to/input.vtu --output /path/to/output.vtu [module_options] + ``mesh-doctor`` loads its module dynamically. If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. If you see a message like @@ -62,7 +68,7 @@ that can quickly highlight some issues to deal with before investigating the oth Both ``allChecks`` and ``mainChecks`` have the same keywords and can be operated in the same way. The example below shows the case of ``allChecks``, but it can be swapped for ``mainChecks``. -.. command-output:: mesh-doctor -i random.vtu allChecks --help +.. command-output:: mesh-doctor allChecks --help :shell: ``collocatedNodes`` @@ -71,7 +77,7 @@ the case of ``allChecks``, but it can be swapped for ``mainChecks``. Displays the neighboring nodes that are closer to each other than a prescribed threshold. It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. -.. command-output:: mesh-doctor -i random.vtu collocatedNodes --help +.. command-output:: mesh-doctor collocatedNodes --help :shell: ``elementVolumes`` @@ -80,7 +86,7 @@ It is not uncommon to define multiple nodes for the exact same position, which w Computes the volumes of all the cells and displays the ones that are below a prescribed threshold. Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. -.. command-output:: mesh-doctor -i random.vtu elementVolumes --help +.. command-output:: mesh-doctor elementVolumes --help :shell: ``fixElementsOrderings`` @@ -90,7 +96,7 @@ It sometimes happens that an exported mesh does not abide by the ``vtk`` orderin The ``fixElementsOrderings`` module can rearrange the nodes of given types of elements. This can be convenient if you cannot regenerate the mesh. -.. command-output:: mesh-doctor -i random.vtu fixElementsOrderings --help +.. command-output:: mesh-doctor fixElementsOrderings --help :shell: ``generateCube`` @@ -100,16 +106,23 @@ This module conveniently generates cubic meshes in ``vtk``. It can also generate fields with simple values. This tool can also be useful to generate a trial mesh that will later be refined or customized. -.. command-output:: mesh-doctor -i random.vtu generateCube --help +.. command-output:: mesh-doctor generateCube --help :shell: +Exceptionally, this module does not require an input ``vtk`` mesh because its purpose is to generate a new one. +The command to use would be something like: + +.. code-block:: bash + + mesh-doctor generateCube --output cube.vtu --x 0:10 --y 0:10 --z 0:10 --nx 10 --ny 15 --nz 20 --cells --nopoints + ``generateFractures`` """"""""""""""""""""" For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to be split at the faces where the fracture gets across the mesh. The ``generateFractures`` module will split the mesh and generate the multi-block ``vtk`` files. -.. command-output:: mesh-doctor -i random.vtu generateFractures --help +.. command-output:: mesh-doctor generateFractures --help :shell: ``generateGlobalIds`` @@ -118,7 +131,7 @@ The ``generateFractures`` module will split the mesh and generate the multi-bloc When running ``geos`` in parallel, `global ids` can be used to refer to data across multiple ranks. The ``generateGlobalIds`` can generate `global ids` for the imported ``vtk`` mesh. -.. command-output:: mesh-doctor -i random.vtu generateGlobalIds --help +.. command-output:: mesh-doctor generateGlobalIds --help :shell: ``nonConformal`` @@ -129,7 +142,7 @@ This module will detect elements which are close enough (there's a user defined The angle between two faces can also be precribed. This module can be a bit time consuming. -.. command-output:: mesh-doctor -i random.vtu nonConformal --help +.. command-output:: mesh-doctor nonConformal --help :shell: ``selfIntersectingElements`` @@ -138,7 +151,7 @@ This module can be a bit time consuming. Some meshes can have cells that auto-intersect. This module will display the elements that have faces intersecting. -.. command-output:: mesh-doctor -i random.vtu selfIntersectingElements --help +.. command-output:: mesh-doctor selfIntersectingElements --help :shell: ``supportedElements`` @@ -152,7 +165,7 @@ But also prismes up to 11 faces. The ``supportedElements`` check will validate that no unsupported element is included in the input mesh. It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. -.. command-output:: mesh-doctor -i random.vtu supportedElements --help +.. command-output:: mesh-doctor supportedElements --help :shell: diff --git a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py index 3d2bf3d3..c2d35065 100644 --- a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py +++ b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py @@ -9,7 +9,12 @@ def main() -> None: parseAndSetVerbosity( sys.argv ) mainParser, allActions, allActionsHelpers = registerParsingActions() args = mainParser.parse_args( sys.argv[ 1: ] ) - setupLogger.info( f"Working on mesh \"{args.vtkInputFile}\"." ) + + # Extract vtuInputFile from parsed arguments + vtuInputFile = getattr( args, 'vtuInputFile', None ) + if vtuInputFile: + setupLogger.info( f"Working on mesh \"{vtuInputFile}\"." ) + actionOptions = allActionsHelpers[ args.subparsers ].convert( vars( args ) ) try: action = allActions[ args.subparsers ] @@ -17,7 +22,7 @@ def main() -> None: setupLogger.error( f"Action {args.subparsers} is not a valid action." ) sys.exit( 1 ) helper: ActionHelper = allActionsHelpers[ args.subparsers ] - result = action( args.vtkInputFile, actionOptions ) + result = action( vtuInputFile, actionOptions ) helper.displayResults( actionOptions, result ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index 02c8e52b..181f22e9 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -2,10 +2,10 @@ import argparse from copy import deepcopy from dataclasses import dataclass -from typing import Type, Any +from typing import Any, Type, Union from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.actions.allChecks import Result as AllChecksResult -from geos.mesh_doctor.parsing.cliParsing import parseCommaSeparatedString, setupLogger +from geos.mesh_doctor.parsing.cliParsing import parseCommaSeparatedString, setupLogger, addVtuInputFileArgument @dataclass( frozen=True ) @@ -64,7 +64,7 @@ def fillSubparser( subparsers: argparse._SubParsersAction, subparserName: str, h parser = subparsers.add_parser( subparserName, help=helpMessage, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) - + addVtuInputFileArgument( parser ) parametersHelp: str = _generateParametersHelp( orderedCheckNames, checkFeaturesConfig ) parser.add_argument( f"--{CHECKS_TO_DO_ARG}", @@ -84,12 +84,12 @@ def fillSubparser( subparsers: argparse._SubParsersAction, subparserName: str, h f"Example: --{PARAMETERS_ARG} parameter_name:10.5,other_param:25" ) ) -def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], +def convert( parsedArgs: Union[ dict[ str, Any ], argparse.Namespace ], orderedCheckNames: list[ str ], checkFeaturesConfig: dict[ str, CheckFeature ] ) -> AllChecksOptions: """Converts parsed command-line arguments into an AllChecksOptions object based on the provided configuration. Args: - parsedArgs (argparse.Namespace): Parsed command-line arguments. + parsedArgs: Parsed command-line arguments (dict or Namespace). orderedCheckNames (list[ str ]): Ordered list of check names. checkFeaturesConfig (dict[ str, CheckFeature ]): Configuration dictionary for check features. @@ -100,7 +100,11 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], AllChecksOptions: The options for all checks to be performed. """ # 1. Determine which checks to perform - checksToDo = getattr( parsedArgs, CHECKS_TO_DO_ARG ) + # Support both dict and Namespace + if isinstance( parsedArgs, dict ): + checksToDo = parsedArgs.get( CHECKS_TO_DO_ARG, "" ) + else: + checksToDo = getattr( parsedArgs, CHECKS_TO_DO_ARG ) if not checksToDo: finalSelectedCheckNames: list[ str ] = deepcopy( orderedCheckNames ) setupLogger.info( "All configured checks will be performed by default." ) @@ -119,7 +123,11 @@ def convert( parsedArgs: argparse.Namespace, orderedCheckNames: list[ str ], defaultParams = { name: feature.defaultParams.copy() for name, feature in checkFeaturesConfig.items() } finalCheckParams = { name: defaultParams[ name ] for name in finalSelectedCheckNames } - parametersArg = getattr( parsedArgs, PARAMETERS_ARG ) + # Support both dict and Namespace + if isinstance( parsedArgs, dict ): + parametersArg = parsedArgs.get( PARAMETERS_ARG, "" ) + else: + parametersArg = getattr( parsedArgs, PARAMETERS_ARG ) if not parametersArg: setupLogger.info( "Default configuration of parameters adopted for every check to perform." ) else: diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py index c24dbaa1..99a9ab14 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse from copy import deepcopy +from typing import Any from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser as sharedFillSubparser, displayResults ) # noqa: F401 @@ -71,7 +72,7 @@ def fillSubparser( subparsers: argparse._SubParsersAction ) -> None: checkFeaturesConfig=CHECK_FEATURES_CONFIG ) -def convert( parsedArgs: argparse.Namespace ) -> AllChecksOptions: +def convert( parsedArgs: dict[ str, Any ] ) -> AllChecksOptions: """Converts arguments by calling the shared logic with the 'allChecks' configuration.""" return sharedConvert( parsedArgs=parsedArgs, orderedCheckNames=ORDERED_CHECK_NAMES, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py index 4e96ef38..6fea1102 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py @@ -20,6 +20,22 @@ def parseCommaSeparatedString( value: str ) -> list[ str ]: return [ item.strip() for item in value.split( ',' ) if item.strip() ] +def addVtuInputFileArgument( parser: argparse.ArgumentParser, required: bool = True ) -> None: + """Add the VTU input file argument to a parser. + + Args: + parser: The argument parser to add the input file argument to. + required: Whether the input file argument is required. Defaults to True. + """ + parser.add_argument( '-i', + '--vtu-input-file', + metavar='VTU_MESH_FILE', + type=str, + required=required, + dest='vtuInputFile', + help="[string]: The VTU mesh file to process." + ( "" if required else " (optional)" ) ) + + def parseAndSetVerbosity( cliArgs: list[ str ] ) -> None: """Parse the verbosity flag only and set the root logger's level accordingly. @@ -80,7 +96,6 @@ def parseAndSetVerbosity( cliArgs: list[ str ] ) -> None: def initParser() -> argparse.ArgumentParser: """Initialize the main argument parser for mesh-doctor.""" - vtkInputFileKey = "vtkInputFile" epilogMsg = f"""\ Note that checks are dynamically loaded. An option may be missing because of an unloaded module. @@ -107,10 +122,4 @@ def formatter( prog: str ) -> argparse.RawTextHelpFormatter: default=0, dest=__QUIET_KEY, help=f"Use -{__QUIET_FLAG} to reduce the verbosity of the output." ) - parser.add_argument( '-i', - '--vtk-input-file', - metavar='VTK_MESH_FILE', - type=str, - required=True, - dest=vtkInputFileKey ) return parser diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index ceffe978..786bb958 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -4,7 +4,7 @@ from geos.mesh_doctor.actions.collocatedNodes import Options, Result from geos.mesh_doctor.parsing import COLLOCATES_NODES from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __TOLERANCE = "tolerance" __TOLERANCE_DEFAULT = 0. @@ -31,6 +31,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( COLLOCATES_NODES, help="Checks if nodes are collocated." ) + addVtuInputFileArgument( p ) p.add_argument( '--' + __TOLERANCE, type=float, metavar=__TOLERANCE_DEFAULT, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py index 319e464a..30ee2e1a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py @@ -4,7 +4,7 @@ from geos.mesh_doctor.actions.elementVolumes import Options, Result from geos.mesh_doctor.parsing import ELEMENT_VOLUMES from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __MIN_VOLUME = "minVolume" __MIN_VOLUME_DEFAULT = 0. @@ -20,6 +20,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: """ p = subparsers.add_parser( ELEMENT_VOLUMES, help=f"Checks if the volumes of the elements are greater than \"{__MIN_VOLUME}\"." ) + addVtuInputFileArgument( p ) p.add_argument( '--' + __MIN_VOLUME, type=float, metavar=__MIN_VOLUME_DEFAULT, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py index ad9b65e5..50c5d088 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py @@ -13,7 +13,7 @@ from typing import Any from geos.mesh_doctor.actions.fixElementsOrderings import Options, Result from geos.mesh_doctor.parsing import vtkOutputParsing, FIX_ELEMENTS_ORDERINGS -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __CELL_TYPE_MAPPING = { "Hexahedron": VTK_HEXAHEDRON, @@ -43,6 +43,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( FIX_ELEMENTS_ORDERINGS, help="Reorders the support nodes for the given cell types." ) + addVtuInputFileArgument( p ) for key, vtkKey in __CELL_TYPE_MAPPING.items(): tmp = list( range( __CELL_TYPE_SUPPORT_SIZE[ vtkKey ] ) ) random.Random( 4 ).shuffle( tmp ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index 5bbbfd87..b2c60814 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -3,8 +3,8 @@ from typing import Any from geos.mesh_doctor.actions.generateCube import Options, Result, FieldInfo from geos.mesh_doctor.parsing import vtkOutputParsing, generateGlobalIdsParsing, GENERATE_CUBE -from geos.mesh_doctor.parsing.cliParsing import setupLogger -from geos.mesh_doctor.parsing.generateGlobalIdsParsing import GlobalIdsInfo +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument +from geos.mesh_doctor.parsing.generateGlobalIdsParsing import GlobalIdsInfo, convertToGlobalIdsInfo __X, __Y, __Z, __NX, __NY, __NZ = "x", "y", "z", "nx", "ny", "nz" __FIELDS = "fields" @@ -41,7 +41,7 @@ def parseFields( s: str ) -> FieldInfo: raise ValueError( f"Dimension {dimension} must be a positive integer" ) from e return FieldInfo( name=name, support=support, dimension=dimension ) - gids: GlobalIdsInfo = generateGlobalIdsParsing.convert( parsedOptions ) + gids: GlobalIdsInfo = convertToGlobalIdsInfo( parsedOptions ) return Options( vtkOutput=vtkOutputParsing.convert( parsedOptions ), generateCellsGlobalIds=gids.cells, @@ -62,6 +62,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( GENERATE_CUBE, help="Generate a cube and its fields." ) + addVtuInputFileArgument( p, required=False ) p.add_argument( '--' + __X, type=lambda s: tuple( map( float, s.split( ":" ) ) ), metavar="0:1.5:3", diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py index 15b90d0f..cc9dd33b 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py @@ -4,6 +4,7 @@ from typing import Any from geos.mesh_doctor.actions.generateFractures import Options, Result, FracturePolicy from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_FRACTURES +from geos.mesh_doctor.parsing.cliParsing import addVtuInputFileArgument from geos.mesh.io.vtkIO import VtkOutput __POLICY = "policy" @@ -48,6 +49,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( GENERATE_FRACTURES, help="Splits the mesh to generate the faults and fractures." ) + addVtuInputFileArgument( p ) p.add_argument( '--' + __POLICY, type=convertToFracturePolicy, metavar=", ".join( __POLICIES ), diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py index a4966d28..118d3589 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py @@ -4,7 +4,7 @@ from typing import Any from geos.mesh_doctor.actions.generateGlobalIds import Options, Result from geos.mesh_doctor.parsing import vtkOutputParsing, GENERATE_GLOBAL_IDS -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __CELLS, __POINTS = "cells", "points" @@ -15,6 +15,18 @@ class GlobalIdsInfo: points: bool +def convertToGlobalIdsInfo( parsedOptions: dict[ str, Any ] ) -> GlobalIdsInfo: + """Extract GlobalIdsInfo from parsed options. + + Args: + parsedOptions: Dictionary of parsed command-line options. + + Returns: + GlobalIdsInfo: The global IDs configuration. + """ + return GlobalIdsInfo( cells=parsedOptions[ __CELLS ], points=parsedOptions[ __POINTS ] ) + + def convert( parsedOptions: dict[ str, Any ] ) -> Options: """Convert parsed command-line options to Options object. @@ -24,7 +36,7 @@ def convert( parsedOptions: dict[ str, Any ] ) -> Options: Returns: Options: Configuration options for supported elements check. """ - gids: GlobalIdsInfo = GlobalIdsInfo( cells=parsedOptions[ __CELLS ], points=parsedOptions[ __POINTS ] ) + gids: GlobalIdsInfo = convertToGlobalIdsInfo( parsedOptions ) return Options( vtkOutput=vtkOutputParsing.convert( parsedOptions ), generateCellsGlobalIds=gids.cells, generatePointsGlobalIds=gids.points ) @@ -61,6 +73,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( GENERATE_GLOBAL_IDS, help="Adds globals ids for points and cells." ) + addVtuInputFileArgument( p ) addArguments( p ) vtkOutputParsing.fillVtkOutputSubparser( p ) diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py index 6a90c7a4..e4e0f2b4 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py @@ -1,6 +1,7 @@ from __future__ import annotations import argparse from copy import deepcopy +from typing import Any from geos.mesh_doctor.actions.allChecks import Options as AllChecksOptions from geos.mesh_doctor.parsing._sharedChecksParsingLogic import ( CheckFeature, convert as sharedConvert, fillSubparser as sharedFillSubparser, displayResults ) # noqa: F401 @@ -53,7 +54,7 @@ def fillSubparser( subparsers: argparse._SubParsersAction ) -> None: checkFeaturesConfig=CHECK_FEATURES_CONFIG ) -def convert( parsedArgs: argparse.Namespace ) -> AllChecksOptions: +def convert( parsedArgs: dict[ str, Any ] ) -> AllChecksOptions: """Converts arguments by calling the shared logic with the 'main_checks' configuration.""" return sharedConvert( parsedArgs=parsedArgs, orderedCheckNames=ORDERED_CHECK_NAMES, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index 790f9daf..cd325f25 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -4,7 +4,7 @@ from geos.mesh_doctor.actions.nonConformal import Options, Result from geos.mesh_doctor.parsing import NON_CONFORMAL from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __ANGLE_TOLERANCE = "angleTolerance" __POINT_TOLERANCE = "pointTolerance" @@ -42,6 +42,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: subparsers: The subparsers action to add the parser to. """ p = subparsers.add_parser( NON_CONFORMAL, help="Detects non conformal elements. [EXPERIMENTAL]" ) + addVtuInputFileArgument( p ) p.add_argument( '--' + __ANGLE_TOLERANCE, type=float, metavar=__ANGLE_TOLERANCE_DEFAULT, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index 0d987701..6e1a6262 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -5,7 +5,7 @@ from geos.mesh_doctor.actions.selfIntersectingElements import Options, Result from geos.mesh_doctor.parsing import SELF_INTERSECTING_ELEMENTS from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __MIN_DISTANCE = "minDistance" __MIN_DISTANCE_DEFAULT = numpy.finfo( float ).eps @@ -43,6 +43,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: """ p = subparsers.add_parser( SELF_INTERSECTING_ELEMENTS, help="Checks if the faces of the elements are self intersecting." ) + addVtuInputFileArgument( p ) help_text = ( "[float]: The minimum distance in the computation. " f"Defaults to your machine precision {__MIN_DISTANCE_DEFAULT}." ) p.add_argument( '--' + __MIN_DISTANCE, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py index 848ed7d4..0128debe 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py @@ -5,7 +5,7 @@ from geos.mesh_doctor.actions.supportedElements import Options, Result from geos.mesh_doctor.parsing import SUPPORTED_ELEMENTS from geos.mesh_doctor.parsing._sharedChecksParsingLogic import getOptionsUsedMessage -from geos.mesh_doctor.parsing.cliParsing import setupLogger +from geos.mesh_doctor.parsing.cliParsing import setupLogger, addVtuInputFileArgument __CHUNK_SIZE = "chunkSize" __NUM_PROC = "nproc" @@ -36,6 +36,7 @@ def fillSubparser( subparsers: _SubParsersAction[ Any ] ) -> None: """ p = subparsers.add_parser( SUPPORTED_ELEMENTS, help="Check that all the elements of the mesh are supported by GEOSX." ) + addVtuInputFileArgument( p ) p.add_argument( '--' + __CHUNK_SIZE, type=int, required=False, diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index c9882c5d..0d5e396a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -1,9 +1,7 @@ from __future__ import annotations -import os.path import textwrap from argparse import ArgumentParser, _ArgumentGroup from typing import Any -from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh.io.vtkIO import VtkOutput __OUTPUT_FILE = "output" @@ -71,7 +69,5 @@ def convert( parsedOptions: dict[ str, Any ], prefix: str = "" ) -> VtkOutput: outputKey = __buildArg( prefix, __OUTPUT_FILE ).replace( "-", "_" ) binaryModeKey = __buildArg( prefix, __OUTPUT_BINARY_MODE ).replace( "-", "_" ) output = parsedOptions[ outputKey ] - if parsedOptions[ binaryModeKey ] and os.path.splitext( output )[ -1 ] == ".vtk": - setupLogger.info( "VTK data mode will be ignored for legacy file format \"vtk\"." ) isDataModeBinary: bool = parsedOptions[ binaryModeKey ] == __OUTPUT_BINARY_MODE_DEFAULT return VtkOutput( output=output, isDataModeBinary=isDataModeBinary ) From c6d3e8468c940ca63b0221353ffff9a82b28ade7 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 17:13:00 -0800 Subject: [PATCH 09/15] Use meshAction instead of action in allChecks and mainChecks to avoid redundant reading of input file --- .../src/geos/mesh_doctor/actions/allChecks.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py index 3eca1b97..2b0523fd 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py @@ -1,7 +1,9 @@ from dataclasses import dataclass from typing import Any +from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid from geos.mesh_doctor.parsing.cliParsing import setupLogger from geos.mesh_doctor.register import __loadModuleAction +from geos.mesh.io.vtkIO import readUnstructuredGrid @dataclass( frozen=True ) @@ -16,11 +18,11 @@ class Result: checkResults: dict[ str, Any ] -def action( vtuInputFile: str, options: Options ) -> Result: - """Reads a vtu file and performs all checks available on it. +def meshAction( mesh: vtkUnstructuredGrid, options: Options ) -> Result: + """Performs all checks available on a loaded mesh. Args: - vtuInputFile (str): The path to the input VTU file. + mesh (vtkUnstructuredGrid): The loaded mesh to analyze. options (Options): The options for processing. Returns: @@ -28,9 +30,23 @@ def action( vtuInputFile: str, options: Options ) -> Result: """ checkResults: dict[ str, Any ] = {} for checkName in options.checksToPerform: - checkAction = __loadModuleAction( checkName ) + checkMeshAction = __loadModuleAction( checkName, "meshAction" ) setupLogger.info( f"Performing check '{checkName}'." ) option = options.checksOptions[ checkName ] - checkResult = checkAction( vtuInputFile, option ) + checkResult = checkMeshAction( mesh, option ) checkResults[ checkName ] = checkResult return Result( checkResults=checkResults ) + + +def action( vtuInputFile: str, options: Options ) -> Result: + """Reads a vtu file and performs all checks available on it. + + Args: + vtuInputFile (str): The path to the input VTU file. + options (Options): The options for processing. + + Returns: + Result: The result of all checks performed. + """ + mesh: vtkUnstructuredGrid = readUnstructuredGrid( vtuInputFile ) + return meshAction( mesh, options ) From 757fed6d66415a358f464ce6d84275796c7503a7 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 19 Nov 2025 17:21:10 -0800 Subject: [PATCH 10/15] Reupdate all tests --- .../tests/test_sharedChecksParsingLogic.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mesh-doctor/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py index 871a6979..20490530 100644 --- a/mesh-doctor/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -81,22 +81,24 @@ def test_fillSubparser( checkFeaturesConfig: dict[ str, CheckFeature ], orderedC parser = argparse.ArgumentParser() subparsers = parser.add_subparsers( dest="command" ) fillSubparser( subparsers, "test-command", "Test help message", orderedCheckNames, checkFeaturesConfig ) - # Parse with no args should use defaults - args = parser.parse_args( [ "test-command" ] ) + # Parse with no args (except required vtu-input-file) should use defaults + args = parser.parse_args( [ "test-command", "-i", "test.vtu" ] ) assert getattr( args, CHECKS_TO_DO_ARG ) == "" assert getattr( args, PARAMETERS_ARG ) == "" + assert args.vtuInputFile == "test.vtu" # Parse with specified args args = parser.parse_args( - [ "test-command", f"--{CHECKS_TO_DO_ARG}", "check1", f"--{PARAMETERS_ARG}", "param1:10.5" ] ) + [ "test-command", "-i", "test.vtu", f"--{CHECKS_TO_DO_ARG}", "check1", f"--{PARAMETERS_ARG}", "param1:10.5" ] ) assert getattr( args, CHECKS_TO_DO_ARG ) == "check1" assert getattr( args, PARAMETERS_ARG ) == "param1:10.5" + assert args.vtuInputFile == "test.vtu" @patch( 'geos.mesh_doctor.parsing._sharedChecksParsingLogic.setupLogger' ) def test_convertDefaultChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when no specific checks or parameters are specified.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames assert len( options.checksOptions ) == 2 @@ -108,7 +110,7 @@ def test_convertDefaultChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ def test_convertSpecificChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when specific checks are specified.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "check1", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == [ "check1" ] assert len( options.checksOptions ) == 1 @@ -120,7 +122,7 @@ def test_convertSpecificChecks( mockLogger: MagicMock, checkFeaturesConfig: dict def test_convertWithParameters( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when parameters are specified.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:10.5,param2:20.5" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) assert options.checksToPerform == orderedCheckNames assert options.checksOptions[ "check1" ].param1 == 10.5 @@ -133,7 +135,7 @@ def test_convertWithParameters( mockLogger: MagicMock, checkFeaturesConfig: dict def test_convertWithInvalidParameters( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when some invalid parameters are specified.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "", PARAMETERS_ARG: "param1:invalid,param2:20.5" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid parameter should be skipped, but the valid one applied assert options.checksOptions[ "check1" ].param1 == 1.0 # Default maintained @@ -144,7 +146,7 @@ def test_convertWithInvalidParameters( mockLogger: MagicMock, checkFeaturesConfi def test_convertWithInvalidCheck( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when an invalid check is specified.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check,check1", PARAMETERS_ARG: "" } options = convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) # The invalid check should be skipped assert options.checksToPerform == [ "check1" ] @@ -156,7 +158,7 @@ def test_convertWithInvalidCheck( mockLogger: MagicMock, checkFeaturesConfig: di def test_convertWithAllInvalidChecks( mockLogger: MagicMock, checkFeaturesConfig: dict[ str, CheckFeature ], orderedCheckNames: list[ str ] ) -> None: """Tests convert when all checks are invalid.""" - parsedArgs = argparse.Namespace( **{ CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } ) + parsedArgs = { CHECKS_TO_DO_ARG: "invalid_check1,invalid_check2", PARAMETERS_ARG: "" } # Should raise ValueError since no valid checks were selected with pytest.raises( ValueError, match="No valid checks were selected" ): convert( parsedArgs, orderedCheckNames, checkFeaturesConfig ) From 0babbb72f5c9894cb01d69d56f00b150c8485cce Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 20 Nov 2025 13:48:15 -0800 Subject: [PATCH 11/15] Add module to CI testing --- .github/workflows/README.md | 9 ++++++--- .github/workflows/python-package.yml | 4 ++++ .github/workflows/typing-check.yml | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3f823457..9ad0a906 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -29,6 +29,7 @@ Tests each Python package independently to ensure: - `geos-xml-tools` - XML preprocessing and formatting - `geos-xml-viewer` - XML viewing tools - `hdf5-wrapper` - HDF5 file handling wrapper +- `mesh-doctor` - Tools to perform checks on vtkUnstructuredGrids - `pygeos-tools` - GEOS Python tools ### Jobs @@ -240,9 +241,10 @@ GEOS integration tests are **automatically triggered** when changes affect: #### GEOS-Integrated Packages - `geos-utils/` - Core utilities used of goesPythonPackages -- `geos-mesh/` - Mesh conversion (`convert_abaqus`, `mesh-doctor`) +- `geos-mesh/` - Mesh conversion (`convert_abaqus`) - `geos-xml-tools/` - XML preprocessing (`preprocess_xml`, `format_xml`) - `hdf5-wrapper/` - HDF5 I/O wrapper +- `mesh-doctor/` - Checks of vtkUnstructuredGrid before using in GEOS - `pygeos-tools/` - Python tools for GEOS - `geos-ats/` - Automated testing framework @@ -305,7 +307,6 @@ The CI uses the following decision matrix: ✅ **Tests Will Run (Required + Label)** ``` Changes: - - geos-mesh/src/mesh_converter.py - geos-xml-tools/src/preprocessor.py Labels: test-geos-integration Result: GEOS integration will run (changes affect integrated packages) @@ -374,6 +375,7 @@ Result: GEOS integration forced (tests will run regardless of changes) │ pip install geos-mesh │ │ pip install geos-xml-tools │ │ pip install hdf5-wrapper │ +│ pip install mesh-doctor │ │ pip install pygeos-tools │ │ pip install geos-ats │ └─────────────────────────────────────────────┘ @@ -402,9 +404,10 @@ Result: GEOS integration forced (tests will run regardless of changes) | Package | Tools | Purpose | |--------------------|-----------------------------------------------------|---------------------------------------------------------------------| | **geos-xml-tools** | `preprocess_xml`;`format_xml` | Preprocess XML input files;Format XML for readability | -| **geos-mesh** | `convert_abaqus`;`mesh-doctor` | Convert Abaqus meshes to VTU/GMSH;Validate and fix mesh quality | +| **geos-mesh** | `convert_abaqus`;`mesh-doctor` | Convert Abaqus meshes to VTU/GMSH | | **geos-ats** | `run_geos_ats`;`setup_ats_environment`;`geos_ats_*` | Run automated test suite;Setup test environment;Various test checks | | **hdf5-wrapper** | Python API | HDF5 file I/O operations | +| **mesh-doctor** | `mesh-doctor` | Validate and fix mesh quality | | **pygeos-tools** | Python API | GEOS workflow utilities | | **geos-utils** | Python API | Common utility functions | diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9c668d1b..48f10cce 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,6 +68,7 @@ jobs: - geos-trame - geos-xml-tools - geos-xml-viewer + - mesh-doctor - hdf5-wrapper - pygeos-tools include: @@ -77,6 +78,8 @@ jobs: dependencies: "geos-utils geos-geomechanics" - package-name: geos-processing dependencies: "geos-utils geos-mesh geos-geomechanics" + - package-name: mesh-doctor + dependencies: "geos-utils geos-mesh" - package-name: pygeos-tools dependencies: "geos-utils geos-mesh" - package-name: geos-timehistory @@ -201,6 +204,7 @@ jobs: "geos-mesh" "geos-xml-tools" "hdf5-wrapper" + "mesh-doctor" "pygeos-tools" "geos-ats" ) diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 2cd6a7c9..46a0f869 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -16,7 +16,7 @@ jobs: max-parallel: 3 matrix: # add packages to check typing - package-name: ["geos-geomechanics", "geos-processing", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper"] + package-name: ["geos-geomechanics", "geos-processing", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper", "mesh-doctor", "pygeos-tools"] steps: - uses: actions/checkout@v4 From 9644dcd4410ced750b03bf172e78811441112142 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 20 Nov 2025 14:10:16 -0800 Subject: [PATCH 12/15] Remove pygeos from type checking --- .github/workflows/typing-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 46a0f869..6d22bf3b 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -16,7 +16,7 @@ jobs: max-parallel: 3 matrix: # add packages to check typing - package-name: ["geos-geomechanics", "geos-processing", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper", "mesh-doctor", "pygeos-tools"] + package-name: ["geos-geomechanics", "geos-processing", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper", "mesh-doctor"] steps: - uses: actions/checkout@v4 From 7ebbf863a089fa6b59133a19d3a5c0c8db381961 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 21 Nov 2025 10:39:39 -0800 Subject: [PATCH 13/15] Add headers and update licence --- mesh-doctor/pyproject.toml | 2 +- mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py | 3 +++ .../src/geos/mesh_doctor/actions/fixElementsOrderings.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py | 3 +++ .../src/geos/mesh_doctor/actions/selfIntersectingElements.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/meshDoctor.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py | 3 +++ .../src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/checkFracturesParsing.py | 2 +- mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/collocatedNodesParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/elementVolumesParsing.py | 3 +++ .../geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/generateCubeParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/generateFracturesParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/nonConformalParsing.py | 3 +++ .../mesh_doctor/parsing/selfIntersectingElementsParsing.py | 3 +++ .../src/geos/mesh_doctor/parsing/supportedElementsParsing.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py | 3 +++ mesh-doctor/src/geos/mesh_doctor/register.py | 3 +++ mesh-doctor/tests/test_collocatedNodes.py | 3 +++ mesh-doctor/tests/test_elementVolumes.py | 3 +++ mesh-doctor/tests/test_generateCube.py | 3 +++ mesh-doctor/tests/test_generateGlobalIds.py | 3 +++ mesh-doctor/tests/test_nonConformal.py | 3 +++ mesh-doctor/tests/test_reorientMesh.py | 3 +++ mesh-doctor/tests/test_selfIntersectingElements.py | 3 +++ mesh-doctor/tests/test_sharedChecksParsingLogic.py | 3 +++ mesh-doctor/tests/test_triangleDistance.py | 3 +++ 43 files changed, 125 insertions(+), 2 deletions(-) diff --git a/mesh-doctor/pyproject.toml b/mesh-doctor/pyproject.toml index 603a6702..06b94e7c 100644 --- a/mesh-doctor/pyproject.toml +++ b/mesh-doctor/pyproject.toml @@ -18,7 +18,7 @@ authors = [{name = "GEOS Contributors" }] maintainers = [{name = "Alexandre Benedicto", email = "alexandre.benedicto@external.totalenergies.com" }, {name = "Romain Baville", email = "romain.baville@external.totalenergies.com" }, {name = "Paloma Martinez", email = "paloma.martinez@external.totalenergies.com" }] -license = {text = "LGPL-2.1"} +license = {text = "Apache-2.0"} classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python" diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py index 2b0523fd..c193f63e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/allChecks.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass from typing import Any from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py index 9b2404a9..50799f4d 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/checkFractures.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import numpy as np import numpy.typing as npt diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py index e6932e61..e25df370 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/collocatedNodes.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from collections import defaultdict from dataclasses import dataclass import numpy diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py index a27c3f3a..87ea9f6d 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/elementVolumes.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import uuid import numpy as np diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py index 8a9b7829..e29ea0fb 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/fixElementsOrderings.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass from vtkmodules.vtkCommonCore import vtkIdList from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py index 14ed28c4..9af53ddb 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateCube.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import numpy as np import numpy.typing as npt diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py index 8d4ebb3d..8e38f8fe 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateFractures.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from collections import defaultdict from dataclasses import dataclass from enum import Enum diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py index 797a120e..70ce5a56 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/generateGlobalIds.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass from vtkmodules.vtkCommonCore import vtkIdTypeArray from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py index f0e8d167..7d20c225 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/mainChecks.py @@ -1 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from geos.mesh_doctor.actions.allChecks import action # noqa: F401 diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py index ed77fbfc..7d2ce18a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/nonConformal.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import math import numpy as np diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py index 69e263ff..af341a43 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/reorientMesh.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import networkx import numpy from tqdm import tqdm diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py index bb8986de..1329005f 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/selfIntersectingElements.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass from vtkmodules.util.numpy_support import vtk_to_numpy from vtkmodules.vtkFiltersGeneral import vtkCellValidator diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py index d3d600d2..23c6a28c 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/supportedElements.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import multiprocessing import networkx diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py index 8b67197f..03c7fc1a 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/triangleDistance.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import itertools from math import sqrt import numpy diff --git a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py index c695be1b..40cb5e6e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py +++ b/mesh-doctor/src/geos/mesh_doctor/actions/vtkPolyhedron.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from collections import defaultdict from dataclasses import dataclass import networkx diff --git a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py index c2d35065..417d3cf0 100644 --- a/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py +++ b/mesh-doctor/src/geos/mesh_doctor/meshDoctor.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import sys from geos.mesh_doctor.parsing import ActionHelper from geos.mesh_doctor.parsing.cliParsing import parseAndSetVerbosity, setupLogger diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py b/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py index 9c747332..7169fad3 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/__init__.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import argparse from dataclasses import dataclass from typing import Callable, Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py index 181f22e9..21a1d090 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/_sharedChecksParsingLogic.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations import argparse from copy import deepcopy diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py index 99a9ab14..a4d200da 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/allChecksParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations import argparse from copy import deepcopy diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py index f1f20bf6..f0f2303d 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/checkFracturesParsing.py @@ -1 +1 @@ -# empty: the check is not available yet! +# TODO create the parsing for checkFractures action diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py index 6fea1102..cef2a2bd 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/cliParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import argparse import logging import textwrap diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py index 786bb958..7abe6eaf 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/collocatedNodesParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py index 30ee2e1a..1ac55c1c 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/elementVolumesParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py index 50c5d088..f40ce53b 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/fixElementsOrderingsParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction import random diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py index b2c60814..f4b1d8ad 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateCubeParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py index cc9dd33b..15d05db7 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateFracturesParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction import os diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py index 118d3589..0bd8063c 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/generateGlobalIdsParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction, ArgumentParser, _ArgumentGroup from dataclasses import dataclass diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py index e4e0f2b4..f45dac16 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/mainChecksParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations import argparse from copy import deepcopy diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py index cd325f25..45f1facd 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/nonConformalParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction from typing import Any diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py index 6e1a6262..5838394c 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/selfIntersectingElementsParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations from argparse import _SubParsersAction import numpy diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py index 0128debe..89f57e93 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/supportedElementsParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations import multiprocessing from argparse import _SubParsersAction diff --git a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py index 0d5e396a..b382e8e8 100644 --- a/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py +++ b/mesh-doctor/src/geos/mesh_doctor/parsing/vtkOutputParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from __future__ import annotations import textwrap from argparse import ArgumentParser, _ArgumentGroup diff --git a/mesh-doctor/src/geos/mesh_doctor/register.py b/mesh-doctor/src/geos/mesh_doctor/register.py index c0a39885..bb677f4e 100644 --- a/mesh-doctor/src/geos/mesh_doctor/register.py +++ b/mesh-doctor/src/geos/mesh_doctor/register.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import argparse import importlib from typing import Callable, Any diff --git a/mesh-doctor/tests/test_collocatedNodes.py b/mesh-doctor/tests/test_collocatedNodes.py index 49c466b0..281f7df2 100644 --- a/mesh-doctor/tests/test_collocatedNodes.py +++ b/mesh-doctor/tests/test_collocatedNodes.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import pytest from typing import Iterator, Tuple from vtkmodules.vtkCommonCore import vtkPoints diff --git a/mesh-doctor/tests/test_elementVolumes.py b/mesh-doctor/tests/test_elementVolumes.py index b74de789..669a5e92 100644 --- a/mesh-doctor/tests/test_elementVolumes.py +++ b/mesh-doctor/tests/test_elementVolumes.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import numpy from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import VTK_TETRA, vtkCellArray, vtkTetra, vtkUnstructuredGrid diff --git a/mesh-doctor/tests/test_generateCube.py b/mesh-doctor/tests/test_generateCube.py index 2f13fbc5..c087bc46 100644 --- a/mesh-doctor/tests/test_generateCube.py +++ b/mesh-doctor/tests/test_generateCube.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from geos.mesh_doctor.actions.generateCube import Options, FieldInfo, buildCube diff --git a/mesh-doctor/tests/test_generateGlobalIds.py b/mesh-doctor/tests/test_generateGlobalIds.py index d16778a8..a5549369 100644 --- a/mesh-doctor/tests/test_generateGlobalIds.py +++ b/mesh-doctor/tests/test_generateGlobalIds.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkUnstructuredGrid, vtkVertex, VTK_VERTEX from geos.mesh_doctor.actions.generateGlobalIds import buildGlobalIds diff --git a/mesh-doctor/tests/test_nonConformal.py b/mesh-doctor/tests/test_nonConformal.py index 2b455c83..3d8df190 100644 --- a/mesh-doctor/tests/test_nonConformal.py +++ b/mesh-doctor/tests/test_nonConformal.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import numpy from geos.mesh_doctor.actions.generateCube import buildRectilinearBlocksMesh, XYZ from geos.mesh_doctor.actions.nonConformal import Options, meshAction diff --git a/mesh-doctor/tests/test_reorientMesh.py b/mesh-doctor/tests/test_reorientMesh.py index df6d25d5..dd3ffa8f 100644 --- a/mesh-doctor/tests/test_reorientMesh.py +++ b/mesh-doctor/tests/test_reorientMesh.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import numpy import pytest diff --git a/mesh-doctor/tests/test_selfIntersectingElements.py b/mesh-doctor/tests/test_selfIntersectingElements.py index c1b94852..a477e912 100644 --- a/mesh-doctor/tests/test_selfIntersectingElements.py +++ b/mesh-doctor/tests/test_selfIntersectingElements.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from vtkmodules.vtkCommonCore import vtkPoints from vtkmodules.vtkCommonDataModel import vtkCellArray, vtkHexahedron, vtkUnstructuredGrid, VTK_HEXAHEDRON from geos.mesh_doctor.actions.selfIntersectingElements import Options, meshAction diff --git a/mesh-doctor/tests/test_sharedChecksParsingLogic.py b/mesh-doctor/tests/test_sharedChecksParsingLogic.py index 20490530..91ca47d5 100644 --- a/mesh-doctor/tests/test_sharedChecksParsingLogic.py +++ b/mesh-doctor/tests/test_sharedChecksParsingLogic.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import argparse from dataclasses import dataclass import pytest diff --git a/mesh-doctor/tests/test_triangleDistance.py b/mesh-doctor/tests/test_triangleDistance.py index 9eabadbf..0938f04f 100644 --- a/mesh-doctor/tests/test_triangleDistance.py +++ b/mesh-doctor/tests/test_triangleDistance.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import numpy import numpy.typing as npt From b53a5c1d4bbc7ccc3e31b03e1a81e807913d287e Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 21 Nov 2025 13:47:57 -0800 Subject: [PATCH 14/15] Correct skipped tests --- mesh-doctor/tests/data/supportedElements.vtu | 47 ++++++++++++++++++ mesh-doctor/tests/test_cliParsing.py | 14 ++++-- mesh-doctor/tests/test_generateFractures.py | 19 ++----- mesh-doctor/tests/test_supportedElements.py | 52 +++++++++++--------- 4 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 mesh-doctor/tests/data/supportedElements.vtu diff --git a/mesh-doctor/tests/data/supportedElements.vtu b/mesh-doctor/tests/data/supportedElements.vtu new file mode 100644 index 00000000..830e0c81 --- /dev/null +++ b/mesh-doctor/tests/data/supportedElements.vtu @@ -0,0 +1,47 @@ + + + + + + + + + + + AQAAAACAAADAAwAAHwEAAA==eJxlk7FuwjAURT2xlS9g6NCFL4AB6lepW8f+AFs3ZtgshjYDQ8REhwp/CEMkFsbsXSLxBZUqZmI79/kqWIp04tx3/JzExvBwlm6EkNgRLyTXeJqveswZXeOlvSIf//5lelxHfh1eZDt7U64f3p+Rua7Os/v+og/rWeq15Ueh50J7IdZ8j01XKz0WcoIdOT2xI6cnRj7UemLkMQ+uyNkQ1+SsyNMQ1+TRtQ31bagPQ3sIo50vae9lnF9dxvI1OUQuRgPZfXwrL/efmlmf4Uets7nW2VybONWmTKpFX2Xn2cv896fjgthRppDT00bzmeEJTngCF8SOMvCkfOb4P3fOhji8Z08MTxg4D/otDX0nk10YODO6ns15OBPfAMsrmQg= + + + 0 + + + 8.2006097334 + + + + + + + AQAAAACAAACAAgAAjgAAAA==eJwtxddCAQAAAECbStJQGdGm0paRkS0R//83Hty9XCCwFXTIYUccdcxxJ7zjXe856X2nfOC0D33kY58441Of+dxZ55x3wRcuuuRLX/naN771ne9ddsUPfvSTq372i1/95nd/+NM1f7nuhptu+dttd9x1zz/ue+ChRx574qln/vXcf1546X+vvPYGbVYMWQ== + + + AQAAAACAAABwAAAALQAAAA==eJxjZIAAZijNBqW5oDQ/lBaG0tJQWhlKa0JpPShtAaVdoLQHlA6A0gBDeAHg + + + AQAAAACAAAAOAAAAFgAAAA==eJxjZGblZOfi4ebl4xfQ0gIAA60AyQ== + + + AQAAAACAAAAgAQAARwAAAA==eJx1jjkOwDAIBPM4ErCd4/+/SeHZZiVoRhwDxLHjhBcMq2czl5aH1QsOOOGCN3zgC7/G15w83ydPe6rpy/e/5OnOD9yzClc= + + + AQAAAACAAABQAAAAIwAAAA==eJxjZoAANijNCaV5oLQAlBaB0hJQWgZKK0BpFSgNABawALs= + + + AQAAAACAAABQAAAAIgAAAA==eJwtxbcBACAIADAsiP7/sAPJkog2PL28nT4uXz9/BXgALg== + + + AQAAAACAAABwAAAAEQAAAA==eJxjYKAtYIHSXFAaAAEAAA8= + + + + + diff --git a/mesh-doctor/tests/test_cliParsing.py b/mesh-doctor/tests/test_cliParsing.py index de50db57..5fb0b3a9 100644 --- a/mesh-doctor/tests/test_cliParsing.py +++ b/mesh-doctor/tests/test_cliParsing.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto import argparse from dataclasses import dataclass import pytest @@ -17,10 +20,13 @@ class TestCase: def __generateGenerateFracturesParsingTestData() -> Iterator[ TestCase ]: field: str = "attribute" + inputMesh: str = "input.vtu" mainMesh: str = "output.vtu" fractureMesh: str = "fracture.vtu" - cliGen: str = f"generateFractures --policy {{}} --name {field} --values 0,1 --output {mainMesh} --fracturesOutputDir ." + cliBase: str = f"generateFractures -i {inputMesh} --policy {{}} --name {field} --values 0,1" + cliEnd: str = f"--output {mainMesh} --fracturesOutputDir ." + cliGen: str = f"{cliBase} {cliEnd}" allCliArgs = cliGen.format( "field" ).split(), cliGen.format( "internalSurfaces" ).split(), cliGen.format( "dummy" ).split() policies = FracturePolicy.FIELD, FracturePolicy.INTERNAL_SURFACES, FracturePolicy.FIELD @@ -71,8 +77,6 @@ def test( testCase: TestCase ) -> None: """Test CLI parsing for generateFractures action.""" if testCase.exception: with pytest.raises( SystemExit ): - pytest.skip( "Test to be fixed" ) - # __parseAndValidateOptions( testCase ) + __parseAndValidateOptions( testCase ) else: - pytest.skip( "Test to be fixed" ) - # __parseAndValidateOptions( testCase ) + __parseAndValidateOptions( testCase ) diff --git a/mesh-doctor/tests/test_generateFractures.py b/mesh-doctor/tests/test_generateFractures.py index abbbc067..9848b9c3 100644 --- a/mesh-doctor/tests/test_generateFractures.py +++ b/mesh-doctor/tests/test_generateFractures.py @@ -1,3 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto from dataclasses import dataclass import numpy as np import numpy.typing as npt @@ -300,7 +303,6 @@ def addQuad( mesh: vtkUnstructuredGrid, face: FaceNodesCoords ) -> None: mesh.InsertNextCell( quad.GetCellType(), quad.GetPointIds() ) -@pytest.mark.skip( "Test to be fixed" ) def test_copyFieldsWhenSplittingMesh() -> None: """This test is designed to check the __copyFields method from generateFractures, that will be called when using __splitMeshOnFractures method from generateFractures.""" # Generating the rectilinear grid and its quads on all borders @@ -343,18 +345,3 @@ def test_copyFieldsWhenSplittingMesh() -> None: mainMeshValues: list[ int ] = vtk_to_numpy( mainMesh.GetCellData().GetArray( 0 ) ).tolist() assert fractureMeshValues == [ 9 ] # The value for the fracture surface assert mainMeshValues == [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] - # Test for invalid point field name - addSimplifiedFieldForCells( mesh0, "GLOBAL_IDS_POINTS", 1 ) - with pytest.raises( ValueError ): - mainMesh, fractureMeshes = __splitMeshOnFractures( mesh0, options ) - # Test for invalid cell field name - mesh1: vtkUnstructuredGrid = buildRectilinearBlocksMesh( [ xyzs ] ) - borderFaces1: BorderFacesNodesCoords = findBordersFacesRectilinearGrid( mesh1 ) - for face in borderFaces1: - addQuad( mesh1, face ) - addQuad( mesh1, fracture ) - addSimplifiedFieldForCells( mesh1, "TestField", 1 ) - addSimplifiedFieldForCells( mesh1, "GLOBAL_IDS_CELLS", 1 ) - assert mesh1.GetCellData().GetNumberOfArrays() == 2 - with pytest.raises( ValueError ): - mainMesh, fractureMeshes = __splitMeshOnFractures( mesh1, options ) diff --git a/mesh-doctor/tests/test_supportedElements.py b/mesh-doctor/tests/test_supportedElements.py index cde51f16..2e38ffa6 100644 --- a/mesh-doctor/tests/test_supportedElements.py +++ b/mesh-doctor/tests/test_supportedElements.py @@ -1,27 +1,28 @@ -# import os -import pytest +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Thomas Gazolla, Alexandre Benedicto +import os from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid, VTK_POLYHEDRON -# from geos.mesh_doctor.actions.supportedElements import Options, action, meshAction -from geos.mesh_doctor.actions.vtkPolyhedron import parseFaceStream, FaceStream from geos.mesh.utils.genericHelpers import toVtkIdList +from geos.mesh_doctor.actions.supportedElements import Options, action, meshAction +from geos.mesh_doctor.actions.vtkPolyhedron import FaceStream, parseFaceStream -# TODO Update this test to have access to another meshTests file -@pytest.mark.parametrize( "baseName", ( "supportedElements.vtk", "supportedElementsAsVTKPolyhedra.vtk" ) ) -def test_supportedElements( baseName: str ) -> None: +dataRoot: str = os.path.join( os.path.dirname( os.path.abspath( __file__ ) ), "data" ) +supportElementsFile: str = os.path.join( dataRoot, "supportedElements.vtu" ) + + +def test_supportedElements() -> None: """Testing that the supported elements are properly detected as supported! Args: baseName (str): Supported elements are provided as standard elements or polyhedron elements. """ - ... - # directory = os.path.dirname( os.path.realpath( __file__ ) ) - # supportedElementsFileName = os.path.join( directory, "../../../../unitTests/meshTests", baseName ) - # options = Options( chunkSize=1, numProc=4 ) - # result = check( supportedElementsFileName, options ) - # assert not result.unsupportedStdElementsTypes - # assert not result.unsupportedPolyhedronElements + options = Options( chunkSize=1, nproc=4 ) + result = action( supportElementsFile, options ) + assert not result.unsupportedStdElementsTypes + assert not result.unsupportedPolyhedronElements def makeDodecahedron() -> tuple[ vtkPoints, vtkIdList ]: @@ -81,19 +82,24 @@ def makeDodecahedron() -> tuple[ vtkPoints, vtkIdList ]: return p, f -# TODO make this test work def test_dodecahedron() -> None: - """Tests whether a dodecahedron is supported by GEOS or not.""" + """Tests whether a dodecahedron is supported by GEOS or not. + + A dodecahedron has 12 pentagonal faces and is not supported by GEOS, + which only supports hexahedra, tetrahedra, pyramids, wedges, and polygons. + """ points, faces = makeDodecahedron() mesh = vtkUnstructuredGrid() mesh.Allocate( 1 ) mesh.SetPoints( points ) mesh.InsertNextCell( VTK_POLYHEDRON, faces ) - # TODO Why does __check triggers an assertion error with 'assert MESH is not None' ? - # result = __check( mesh, Options( num_proc=1, chunk_size=1 ) ) - # assert set( result.unsupported_polyhedron_elements ) == { 0 } - # assert not result.unsupported_std_elements_types + # Test using meshAction directly instead of the internal __check function + options = Options( nproc=1, chunkSize=1 ) + result = meshAction( mesh, options ) + # Dodecahedron (12 pentagonal faces) is not supported by GEOS + assert set( result.unsupportedPolyhedronElements ) == { 0 } + assert not result.unsupportedStdElementsTypes def test_parseFaceStream() -> None: @@ -117,6 +123,6 @@ def test_parseFaceStream() -> None: ) # yapf: enable assert result == expected - face_stream = FaceStream.buildFromVtkIdList( faces ) - assert face_stream.numFaces == 12 - assert face_stream.numSupportPoints == 20 + faceStream = FaceStream.buildFromVtkIdList( faces ) + assert faceStream.numFaces == 12 + assert faceStream.numSupportPoints == 20 From a87f70cb816aa7b2e7017f07419d37d00bf20a1f Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 21 Nov 2025 14:06:45 -0800 Subject: [PATCH 15/15] yapf --- mesh-doctor/tests/test_supportedElements.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mesh-doctor/tests/test_supportedElements.py b/mesh-doctor/tests/test_supportedElements.py index 2e38ffa6..f55f9e80 100644 --- a/mesh-doctor/tests/test_supportedElements.py +++ b/mesh-doctor/tests/test_supportedElements.py @@ -8,7 +8,6 @@ from geos.mesh_doctor.actions.supportedElements import Options, action, meshAction from geos.mesh_doctor.actions.vtkPolyhedron import FaceStream, parseFaceStream - dataRoot: str = os.path.join( os.path.dirname( os.path.abspath( __file__ ) ), "data" ) supportElementsFile: str = os.path.join( dataRoot, "supportedElements.vtu" )