Source code for pyvista_cad._bridges.gmsh

"""Bridges to and from gmsh.

gmsh is a global-state mesh generator, so these helpers consult the
currently-initialized gmsh model. The caller is responsible for
``gmsh.initialize()`` / ``gmsh.finalize()`` bracketing.
"""

from typing import Any

import numpy as np
import pyvista as pv

from pyvista_cad._errors import OptionalDependencyError

# gmsh element-type ID -> (vtk cell type, n nodes)
_GMSH_TO_VTK: dict[int, tuple[int, int]] = {
    1: (3, 2),  # 2-node line
    2: (5, 3),  # 3-node triangle
    3: (9, 4),  # 4-node quad
    4: (10, 4),  # 4-node tet
    5: (12, 8),  # 8-node hex
    6: (13, 6),  # 6-node wedge
    7: (14, 5),  # 5-node pyramid
    15: (1, 1),  # 1-node point
}


[docs] def from_gmsh(model_name: str | None = None) -> pv.UnstructuredGrid: """Pull the current gmsh model into a :class:`pyvista.UnstructuredGrid`. Parameters ---------- model_name : str, optional Name of the gmsh model to read. When omitted, the current model is used. Returns ------- pyvista.UnstructuredGrid Mesh from gmsh, with one cell per gmsh element. Element types outside :data:`_GMSH_TO_VTK` are skipped with a warning. Raises ------ pyvista_cad.OptionalDependencyError If :mod:`gmsh` is not installed. Examples -------- Mesh a unit square in gmsh and pull it into PyVista (requires the ``[gmsh]`` extra): >>> import gmsh >>> from pyvista_cad import from_gmsh >>> gmsh.initialize() >>> gmsh.option.setNumber('General.Terminal', 0) >>> _ = gmsh.model.add('demo') >>> _ = gmsh.model.occ.addRectangle(0, 0, 0, 1, 1) >>> gmsh.model.occ.synchronize() >>> gmsh.model.mesh.generate(2) >>> grid = from_gmsh() >>> type(grid).__name__ 'UnstructuredGrid' >>> grid.n_cells > 0 True >>> gmsh.finalize() """ try: import gmsh except ImportError as exc: msg = 'gmsh not installed; install pyvista-cad[gmsh]' raise OptionalDependencyError(msg) from exc if model_name is not None: gmsh.model.setCurrent(model_name) node_tags, coords, _ = gmsh.model.mesh.getNodes() coords = np.asarray(coords, dtype=float).reshape(-1, 3) tag_to_idx = {int(t): i for i, t in enumerate(node_tags)} cells: list[int] = [] cell_types: list[int] = [] elem_types, _elem_tags, elem_nodes = gmsh.model.mesh.getElements() for etype, nodes in zip(elem_types, elem_nodes, strict=True): if int(etype) not in _GMSH_TO_VTK: continue vtk_type, n_nodes = _GMSH_TO_VTK[int(etype)] flat = np.asarray(nodes, dtype=np.int64).reshape(-1, n_nodes) for row in flat: cells.append(n_nodes) cells.extend(tag_to_idx[int(t)] for t in row) cell_types.append(vtk_type) if not cells: grid = pv.UnstructuredGrid() if coords.size: grid.points = coords return grid cells_arr = np.asarray(cells, dtype=np.int64) types_arr = np.asarray(cell_types, dtype=np.uint8) grid = pv.UnstructuredGrid(cells_arr, types_arr, coords) grid.field_data['cad.source_format'] = np.array(['gmsh']) return grid
[docs] def to_gmsh(mesh: pv.UnstructuredGrid | pv.PolyData, *, model_name: str = 'pyvista_cad') -> None: """Install a PyVista mesh as the current gmsh model (in place). Parameters ---------- mesh : pyvista.UnstructuredGrid or pyvista.PolyData Mesh to register. model_name : str, default: ``'pyvista_cad'`` Name of the gmsh model to create. Raises ------ pyvista_cad.OptionalDependencyError If :mod:`gmsh` is not installed. Examples -------- Register a triangulated PyVista mesh as a gmsh model (requires the ``[gmsh]`` extra): >>> import gmsh >>> import pyvista as pv >>> from pyvista_cad import to_gmsh >>> gmsh.initialize() >>> gmsh.option.setNumber('General.Terminal', 0) >>> to_gmsh(pv.Sphere()) >>> gmsh.model.getCurrent() 'pyvista_cad' >>> gmsh.finalize() """ try: import gmsh except ImportError as exc: msg = 'gmsh not installed; install pyvista-cad[gmsh]' raise OptionalDependencyError(msg) from exc gmsh.model.add(model_name) gmsh.model.setCurrent(model_name) surface_tag = gmsh.model.addDiscreteEntity(2) points = np.asarray(mesh.points, dtype=float).reshape(-1) node_tags = list(range(1, len(mesh.points) + 1)) gmsh.model.mesh.addNodes(2, surface_tag, node_tags, points.tolist()) triangles: list[int] = [] cells = _iter_triangles(mesh) for tri in cells: triangles.extend(int(i) + 1 for i in tri) if triangles: elem_tags = list(range(1, 1 + len(triangles) // 3)) gmsh.model.mesh.addElementsByType(surface_tag, 2, elem_tags, triangles)
def _iter_triangles(mesh: pv.UnstructuredGrid | pv.PolyData) -> Any: """Yield triangle index triplets from a surface mesh.""" if isinstance(mesh, pv.PolyData): tri = mesh.triangulate() faces = tri.faces.reshape(-1, 4) for row in faces: if int(row[0]) == 3: yield row[1:4] return surf = mesh.extract_surface(algorithm='dataset_surface').triangulate() faces = surf.faces.reshape(-1, 4) for row in faces: if int(row[0]) == 3: yield row[1:4]