from __future__ import annotations
import os
import tempfile
from pathlib import Path
from typing import TYPE_CHECKING, Any, Tuple
from ._doc import doc
from .region_markers import RegionMarkerList
from .utils import _to_opts_string
if TYPE_CHECKING:
from .mesh import TriangleMesh
from .mesh_container import MeshContainer
def write_smesh(filename: os.PathLike,
mesh: TriangleMesh,
region_markers: RegionMarkerList = None):
"""Save a mesh to a `.smesh` format (Tetgen). http://wias-
berlin.de/software/tetgen/1.5/doc/manual/manual006.html#ff_smesh.
Parameters
----------
filename : os.PathLike
Filename to save the data to.
mesh : TriangleMesh
Mesh data to be saved.
region_markers : RegionMarkerList, optional
Override region markers from input mesh.
"""
if region_markers is None:
region_markers = mesh.region_markers
path = Path(filename)
with path.open('w') as f:
n_nodes, n_dim = mesh.points.shape
n_attrs = 0
node_markers = 0
print(f'{n_nodes} {n_dim} {n_attrs} {node_markers}', file=f)
node_fmt = '{:4d}' + ' {:8.2f}' * n_dim
for i, node in enumerate(mesh.points):
print(node_fmt.format(i + 1, *node), file=f)
n_facets, n_corners = mesh.cells.shape
facet_markers = 0
print(f'{n_facets} {facet_markers}', file=f)
facet_fmt = '{:4d}' + ' {:8d}' * n_corners
for facet in mesh.cells + 1: # tetgen uses 1-indexing
print(facet_fmt.format(n_corners, *facet), file=f)
# TODO, store holes in TriangleMesh?
n_holes = 0
hole_dim = 3
print(f'{n_holes}', file=f)
holes: Tuple[Any, ...] = ()
hole_fmt = '{:4d}' + ' {:8.2f}' * hole_dim
for i, hole in enumerate(holes):
print(hole_fmt.format(i + 1, *hole), file=f)
n_regions = len(region_markers)
region_dim = 3
print(f'{n_regions}', file=f)
region_fmt = ('{:4d}' + ' {:8.2f}' * region_dim +
' {label:8} {constraint:8}')
for i, marker in enumerate(region_markers):
constraint = marker.constraint if marker.constraint else ''
print(region_fmt.format(i + 1,
*marker.point,
label=marker.label,
constraint=constraint),
file=f)
def call_tetgen(fname: os.PathLike, opts: str = '-pAq'):
"""Call tetgen via subprocess.
Parameters
----------
fname : os.PathLike
Location of tetgen input file ('.smesh')
opts : str
Command-line options passed to `tetgen`.
More info:
http://wias-berlin.de/software/tetgen/1.5/doc/manual/manual005.html
Some useful flags:
- `-A`: Assigns attributes to tetrahedra in different regions.
- `-p`: Tetrahedralizes a piecewise linear complex (PLC).
- `-q`: Refines mesh (to improve mesh quality).
- `-a`: Applies a maximum tetrahedron volume constraint.
"""
import subprocess as sp
sp.run(['tetgen', opts, fname])
[docs]@doc(prefix='Tetrahedralize a surface mesh')
def tetrahedralize(mesh: TriangleMesh,
opts: str | dict = '-pAq',
default_opts: dict = None) -> MeshContainer:
"""{prefix}.
Parameters
----------
mesh : TriangleMesh
Input contour mesh
opts : str, optional
Command-line options passed to `tetgen`.
More info:
http://wias-berlin.de/software/tetgen/1.5/doc/manual/manual005.html
Some useful flags:
- `-A`: Assigns attributes to tetrahedra in different regions.
- `-p`: Tetrahedralizes a piecewise linear complex (PLC).
- `-q`: Refines mesh (to improve mesh quality).
- `-a`: Applies a maximum tetrahedron volume constraint.
Can be passed as a raw string, `opts='-pAq1.2', or dict,
`opts=dict('p'= True, 'A'= True, 'q'=1.2)`.
default_opts : dict, optional
Dictionary with default options. These will be merged with `opts`.
Returns
-------
MeshContainer
Tetrahedralized mesh.
"""
from .mesh_container import MeshContainer
opts = _to_opts_string(opts, defaults=default_opts, prefix='-', sep=' ')
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp, 'nanomesh.smesh')
write_smesh(path, mesh)
call_tetgen(path, opts)
tetras = MeshContainer.read(path.with_suffix('.1.ele'))
return MeshContainer(tetras.points,
tetras.cells,
cell_data=tetras.cell_data)