Source code for nekmeshpy.tetmesh.tetmesh
"""Linear tetrahedral volume-mesh container (``points`` + ``tets``, both 0-based)."""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
from .._typing import IntArray, PointArray
if TYPE_CHECKING:
from ..trimesh import TriMesh
[docs]
class TetMesh:
"""A linear tetrahedral volume mesh: ``points`` ``(P,3)`` and ``tets`` ``(T,4)``
connectivity.
Deliberately linear only -- unlike the point/line/quad/hex ladder, a ``TetMesh``
carries no order, no interior nodes and no tags. It exists to solve a field or
walk a volume (:mod:`tetmesh.ops <nekmeshpy.tetmesh.ops>`), not to be exported: a
mesher's actual output is the hex mesh built *around* what the solve decided, never
the tets themselves."""
# local tet faces (P,Q,R) as femoral_vol's own boundary-face scan enumerated them;
# winding is not load-bearing here since every consumer sorts before comparing.
FACE_POINTS = np.array([[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]], dtype=np.int64)
[docs]
def __init__(self, points: PointArray, tets: IntArray) -> None:
self.points: PointArray = np.asarray(points, dtype=float).reshape(-1, 3)
self.tets: IntArray = np.asarray(tets, dtype=np.int64).reshape(-1, 4)
def __repr__(self) -> str:
"""One-line REPL summary: point and tet counts."""
try:
return "<TetMesh %d points, %d tets>" % (
self.points.shape[0], self.tets.shape[0])
except Exception: # a repr must never break a debug session
return "<TetMesh (unprintable)>"
# -- sizes -------------------------------------------------------------
@property
def n_points(self) -> int:
"""Number of points."""
return self.points.shape[0]
@property
def n_tets(self) -> int:
"""Number of tetrahedra."""
return self.tets.shape[0]
# -- boundary ------------------------------------------------------------
[docs]
def boundary_faces(self) -> IntArray:
"""``(F,3)`` unique triangle faces borne by exactly one tet -- the open outer
boundary."""
faces: IntArray = self.tets[:, self.FACE_POINTS].reshape(-1, 3)
keys = np.sort(faces, axis=1)
_, inverse, counts = np.unique(
keys, axis=0, return_inverse=True, return_counts=True)
return faces[counts[inverse.ravel()] == 1]
[docs]
def boundary_mesh(self) -> TriMesh:
"""The outer boundary as a :class:`TriMesh <nekmeshpy.trimesh.TriMesh>`, over
this mesh's own points (not renumbered -- a face id here is a face id there)."""
from ..trimesh import TriMesh
return TriMesh(self.points, self.boundary_faces())