-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add PyVista support for loading and converting meshes
- Added new PyVista module for loading and converting meshes - Updated import statements in the `__init__.py` file to include PyVista functions - Implemented functions to load and convert meshes using PyVista - Introduced a new PyVista file for mesh conversion operations
- Loading branch information
Showing
16 changed files
with
409 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
# shellcheck disable=SC2148 | ||
eval "$(pixi shell-hook --frozen)" | ||
eval "$(pixi shell-hook)" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import pyvista as pv | ||
import trimesh | ||
|
||
from mkit.io.types import AnyMesh | ||
from mkit.typing import StrPath | ||
|
||
|
||
def load_pyvista(filename: StrPath) -> pv.PolyData: | ||
raise NotImplementedError # TODO | ||
|
||
|
||
def as_pyvista(mesh: AnyMesh) -> pv.PolyData: | ||
match mesh: | ||
case pv.PolyData(): | ||
return mesh | ||
case trimesh.Trimesh(): | ||
return trimesh_to_pyvista(mesh) | ||
case _: | ||
raise NotImplementedError(f"unsupported mesh: {mesh}") | ||
|
||
|
||
def trimesh_to_pyvista(mesh: trimesh.Trimesh) -> pv.PolyData: | ||
mesh_pv: pv.PolyData = pv.wrap(mesh) | ||
return mesh_pv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import pymeshfix | ||
import pyvista as pv | ||
import trimesh | ||
|
||
from mkit import io as _io | ||
|
||
|
||
def mesh_fix( | ||
mesh: trimesh.Trimesh, | ||
*, | ||
verbose: bool = False, | ||
joincomp: bool = False, | ||
remove_smallest_components: bool = True, | ||
) -> trimesh.Trimesh: | ||
mesh_pv: pv.PolyData = _io.as_pyvista(mesh) | ||
fixer = pymeshfix.MeshFix(mesh_pv) | ||
fixer.repair( | ||
verbose=verbose, | ||
joincomp=joincomp, | ||
remove_smallest_components=remove_smallest_components, | ||
) | ||
mesh_pv = fixer.mesh | ||
return _io.as_trimesh(mesh_pv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import numpy as np | ||
import trimesh | ||
from numpy import typing as npt | ||
|
||
|
||
def find_inner_point( | ||
mesh: trimesh.Trimesh, *, max_retry: int = 8 | ||
) -> npt.NDArray[np.float64]: | ||
for _ in range(max_retry): | ||
origin: npt.NDArray[np.float64] = mesh.bounds[0] | ||
end_point: npt.NDArray[np.float64] = mesh.sample(1, return_index=False)[0] | ||
direction: npt.NDArray[np.float64] = end_point - origin | ||
locations: npt.NDArray[np.float64] | ||
index_ray: npt.NDArray[np.intp] | ||
index_tri: npt.NDArray[np.intp] | ||
locations, index_ray, index_tri = mesh.ray.intersects_location( | ||
[origin], [direction] | ||
) | ||
if len(locations) < 2: | ||
continue | ||
result: npt.NDArray = (locations[0] + locations[1]) / 2 | ||
if mesh.contains([result])[0]: | ||
return result | ||
raise ValueError("Cannot find inner point") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import functools | ||
import pathlib | ||
import subprocess | ||
import tempfile | ||
from collections.abc import Iterator | ||
from typing import Any | ||
|
||
import meshio | ||
import numpy as np | ||
from numpy import typing as npt | ||
|
||
from mkit.typing import StrPath | ||
|
||
|
||
def tetgen(mesh: meshio.Mesh) -> meshio.Mesh: | ||
""" | ||
Args: | ||
mesh: input mesh | ||
Returns: | ||
tetrahedral mesh | ||
""" | ||
with tempfile.TemporaryDirectory() as tmpdir: | ||
tmpdir = pathlib.Path(tmpdir) | ||
input_file: pathlib.Path = tmpdir / "mesh.smesh" | ||
save_smesh(input_file, mesh) | ||
subprocess.run( | ||
["tetgen", "-p", "-q", "-O", "-z", "-k", "-C", "-V", input_file], | ||
check=True, | ||
) | ||
tetra_mesh: meshio.Mesh = meshio.read(tmpdir / "mesh.1.vtk") | ||
points: npt.NDArray[np.floating] = tetra_mesh.points | ||
tetra: npt.NDArray[np.intp] = tetra_mesh.get_cells_type("tetra") | ||
faces: npt.NDArray[np.intp] | ||
boundary_marker: npt.NDArray[np.intp] | ||
faces, boundary_marker = load_face(tmpdir / "mesh.1.face") | ||
return meshio.Mesh( | ||
points=points, | ||
cells=[("tetra", tetra), ("triangle", faces)], | ||
cell_data={ | ||
"boundary_marker": [np.zeros(len(tetra), np.intp), boundary_marker] | ||
}, | ||
) | ||
|
||
|
||
def load_face( | ||
file: pathlib.Path, | ||
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: | ||
lines: list[str] = list(strip_comments(file)) | ||
# <# of faces> <boundary marker (0 or 1)> | ||
num_faces, has_boundary_marker = map(int, lines[0].split()) | ||
faces: npt.NDArray[np.intp] = np.zeros((num_faces, 3), np.intp) | ||
boundary_marker: npt.NDArray[np.intp] = np.zeros(num_faces, np.intp) | ||
if has_boundary_marker: | ||
for line in lines[1:]: | ||
# <face #> <node> <node> <node> ... [boundary marker] ... | ||
face_id, *face, marker = map(int, line.split()) | ||
faces[face_id] = face | ||
boundary_marker[face_id] = marker | ||
return faces, boundary_marker | ||
else: | ||
for line in lines[1:]: | ||
# <face #> <node> <node> <node> ... [boundary marker] ... | ||
face_id, *face = map(int, line.split()) | ||
faces[face_id] = face | ||
return faces, boundary_marker | ||
|
||
|
||
def save_smesh(file: StrPath, mesh: meshio.Mesh) -> None: | ||
file = pathlib.Path(file) | ||
with file.open("w") as f: | ||
fprint = functools.partial(print, file=f) | ||
fprint("# Part 1 - node list") | ||
fprint( | ||
"# <# of points> <dimension (3)> <# of attributes> <boundary markers (0 or 1)>" | ||
) | ||
if "boundary_marker" in mesh.point_data: | ||
fprint(f"{len(mesh.points)} 3 0 1") | ||
fprint("# <point #> <x> <y> <z> [attributes] [boundary marker]") | ||
point_boundary_marker: npt.NDArray[np.intp] = np.asarray( | ||
mesh.point_data["boundary_marker"] | ||
) | ||
for point_id, point in enumerate(mesh.points): | ||
fprint(point_id, *point, point_boundary_marker[point_id]) | ||
else: | ||
fprint(f"{len(mesh.points)} 3 0 0") | ||
fprint("# <point #> <x> <y> <z> [attributes] [boundary marker]") | ||
for point_id, point in enumerate(mesh.points): | ||
fprint(point_id, *point) | ||
|
||
fprint() | ||
fprint("# Part 2 - facet list") | ||
fprint("# <# of facets> <boundary markers (0 or 1)>") | ||
faces: npt.NDArray[np.intp] = mesh.get_cells_type("triangle") | ||
if "boundary_marker" in mesh.cell_data: | ||
face_boundary_marker: npt.NDArray[np.intp] = mesh.get_cell_data( | ||
"boundary_marker", "triangle" | ||
) | ||
fprint(len(faces), 1) | ||
fprint("# <# of corners> <corner 1> ... <corner #> [boundary marker]") | ||
for face_id, face in enumerate(faces): | ||
fprint(len(face), *face, face_boundary_marker[face_id]) | ||
else: | ||
fprint(len(faces), 0) | ||
fprint("# <# of corners> <corner 1> ... <corner #> [boundary marker]") | ||
for face in faces: | ||
fprint(len(face), *face) | ||
|
||
fprint() | ||
fprint("# Part 3 - hole list") | ||
fprint("# <# of holes>") | ||
holes: npt.NDArray[np.float64] = mesh.field_data.get("holes", []) | ||
fprint(len(holes)) | ||
fprint("# <hole #> <x> <y> <z>") | ||
for hole_id, hole in enumerate(holes): | ||
fprint(hole_id, *hole) | ||
|
||
fprint() | ||
fprint("# Part 4 - region attributes list") | ||
fprint("# <# of region>") | ||
fprint(0) | ||
|
||
|
||
def strip_comments(file: pathlib.Path) -> Iterator[str]: | ||
_: Any | ||
with file.open() as f: | ||
for line in f: | ||
line: str | ||
line, _, _ = line.partition("#") | ||
line = line.strip() | ||
if line: | ||
yield line |
Oops, something went wrong.