"""Generic shared-point unstructured mesh -- the gmsh/meshio-style data model."""
from __future__ import annotations
from typing import Any
import numpy as np
from .._typing import IntArray, PointArray
# points-per-cell for the types we use
_POINTS_PER_CELL = {
"vertex": 1, "line": 2, "triangle": 3, "quad": 4,
"tetra": 4, "hexahedron": 8,
}
[docs]
class Mesh:
"""Shared-point mesh model: ``points`` ``(P,3)`` plus a
``{cell_type: connectivity}`` dict, named ``point_sets`` and ``cell_sets``, and
gmsh ``field_data``. Serves as the meshio bridge."""
[docs]
def __init__(
self,
points: PointArray,
cells: dict[str, IntArray] | None = None,
point_sets: dict[str, IntArray] | None = None,
cell_sets: dict[str, dict[str, IntArray]] | None = None,
field_data: dict[str, Any] | None = None,
) -> None:
self.points = np.asarray(points, dtype=float).reshape(-1, 3)
# {type: (M,k) int connectivity into points}
self.cells: dict[str, IntArray] = {}
for ctype, conn in (cells or {}).items():
self.cells[ctype] = np.asarray(conn, dtype=np.int64).reshape(
-1, _POINTS_PER_CELL[ctype])
# {name: (n,) point ids}
self.point_sets = {k: np.asarray(v, dtype=np.int64).ravel()
for k, v in (point_sets or {}).items()}
# {name: {type: (m,) local cell ids}}
self.cell_sets: dict[str, dict[str, IntArray]] = {}
for name, block in (cell_sets or {}).items():
self.cell_sets[name] = {t: np.asarray(ids, dtype=np.int64).ravel()
for t, ids in block.items()}
# {name: (tag, dim)} gmsh-style group metadata
self.field_data = dict(field_data or {})
# -- sizes -----------------------------------------------------------
@property
def n_points(self) -> int:
"""Number of points."""
return self.points.shape[0]
@property
def cell_types(self) -> list[str]:
"""The cell-type keys present."""
return list(self.cells.keys())
[docs]
def n_cells(self, ctype: str | None = None) -> int:
"""Number of cells of type ``ctype``, or the total when ``ctype`` is ``None``."""
if ctype is not None:
return self.cells[ctype].shape[0] if ctype in self.cells else 0
return sum(c.shape[0] for c in self.cells.values())
# -- meshio bridge ---------------------------------------------------
[docs]
def to_meshio(self) -> Any:
"""Return an equivalent :class:`meshio.Mesh` (requires ``meshio``)."""
import meshio
cells = [(t, conn) for t, conn in self.cells.items()]
return meshio.Mesh(
points=self.points, cells=cells,
point_sets={k: v for k, v in self.point_sets.items()},
cell_sets={n: [b.get(t, np.empty(0, np.int64)) for t, _ in cells]
for n, b in self.cell_sets.items()},
field_data={k: np.asarray(v) for k, v in self.field_data.items()},
)
[docs]
@classmethod
def from_meshio(cls, m: Any) -> Mesh:
"""Build a :class:`Mesh` from a :class:`meshio.Mesh`."""
cells = {cb.type: cb.data for cb in m.cells}
ordered_types = [cb.type for cb in m.cells]
cell_sets = {}
for name, blocks in getattr(m, "cell_sets", {}).items():
block = {}
for t, ids in zip(ordered_types, blocks):
if ids is not None and len(ids):
block[t] = ids
cell_sets[name] = block
return cls(points=m.points, cells=cells,
point_sets=getattr(m, "point_sets", None),
cell_sets=cell_sets,
field_data=getattr(m, "field_data", None))
[docs]
def write(self, path: str, file_format: str | None = None) -> str:
"""Write via :mod:`meshio` (``.vtu``, ``.msh``, ``.xdmf``, ...)."""
self.to_meshio().write(path, file_format=file_format)
return path
[docs]
@classmethod
def read(cls, path: str, file_format: str | None = None) -> Mesh:
"""Read any meshio-supported file into a :class:`Mesh`."""
import meshio
return cls.from_meshio(meshio.read(path, file_format=file_format))
def __repr__(self) -> str:
parts = ", ".join("%s=%d" % (t, c.shape[0]) for t, c in self.cells.items())
return "Mesh(points=%d, %s, groups=%d)" % (
self.n_points, parts or "empty", len(self.cell_sets))