Source code for ogstools.meshlib.gmsh_meshing

# Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org)
#            Distributed under a Modified BSD License.
#            See accompanying file LICENSE.txt or
#            http://www.opengeosys.org/project/license
#

from collections.abc import Collection
from itertools import pairwise
from pathlib import Path

import gmsh
import numpy as np
import pyvista as pv


def _line(
    geo: gmsh.model.geo, length: float, n_edge_cells: int, structured: bool
) -> int:
    geo.addPoint(0, 0, 0, tag=1)
    geo.addPoint(length, 0, 0, tag=2)
    line_tag = geo.addLine(1, 2, tag=1)

    if structured:
        geo.mesh.setTransfiniteCurve(1, n_edge_cells + 1)
        geo.mesh.setTransfiniteSurface(1)
        geo.mesh.setRecombine(dim=1, tag=1)
    return line_tag


[docs] def rect( lengths: float | tuple[float, float] = 1.0, n_edge_cells: int | tuple[int, int] = 1, n_layers: int = 1, structured_grid: bool = True, order: int = 1, mixed_elements: bool = False, jiggle: float = 0.0, out_name: Path | str = Path("rect.msh"), msh_version: float | None = None, layer_ids: list | None = None, ) -> None: gmsh.initialize() gmsh.option.set_number("General.Verbosity", 0) if msh_version is not None: gmsh.option.setNumber("Mesh.MshFileVersion", msh_version) name = Path(out_name).stem gmsh.model.add(name) dx, dy = lengths if isinstance(lengths, Collection) else [lengths] * 2 nx, ny = ( n_edge_cells if isinstance(n_edge_cells, Collection) else [n_edge_cells] * 2 ) bottom_tag = _line(gmsh.model.geo, dx, nx, structured_grid) right_tags = [] left_tags = [] top_tag = bottom_tag if layer_ids is None: layer_ids = list(range(n_layers)) assert isinstance(layer_ids, list) for n in range(n_layers): recombine = n % 2 if mixed_elements else structured_grid newEntities = gmsh.model.geo.extrude( dimTags=[(1, top_tag)], dx=0, dy=dy / n_layers, dz=0, numElements=[ny] if structured_grid else [], recombine=recombine, # fmt: skip ) top_tag = abs(newEntities[0][1]) plane_tag = abs(newEntities[1][1]) layer_name = f"Layer {n}" if n_layers > 1 else name tag = layer_ids[n] gmsh.model.addPhysicalGroup( dim=2, tags=[plane_tag], name=layer_name, tag=tag ) right_tags += [abs(newEntities[2][1])] left_tags += [abs(newEntities[3][1])] gmsh.model.addPhysicalGroup(dim=1, tags=[bottom_tag], name="bottom") gmsh.model.addPhysicalGroup(dim=1, tags=[top_tag], name="top") gmsh.model.addPhysicalGroup(dim=1, tags=right_tags, name="right") gmsh.model.addPhysicalGroup(dim=1, tags=left_tags, name="left") gmsh.model.geo.synchronize() gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1) if not structured_grid: gmsh.option.setNumber("Mesh.MeshSizeMin", dy / ny) gmsh.option.setNumber("Mesh.MeshSizeMax", dy / ny) gmsh.model.mesh.generate(dim=2) if jiggle > 0.0: node_ids = gmsh.model.mesh.getNodes(dim=2)[0] for node_id in node_ids: offset = np.append(np.random.default_rng().random(2), 0.0) coord, parametric_coord, _, tag = gmsh.model.mesh.get_node(node_id) coord = np.asarray(coord) + offset * jiggle gmsh.model.mesh.set_node(node_id, coord, parametric_coord) gmsh.model.mesh.setOrder(order) gmsh.write(str(out_name)) gmsh.finalize()
def _square( geo: gmsh.model.geo, lengths: tuple[float, float], n_edge_cells: tuple[int, int], structured: bool, ) -> int: geo.addPoint(0, 0, 0, tag=1) geo.addPoint(lengths[0], 0, 0, tag=2) geo.addPoint(lengths[0], lengths[1], 0, tag=3) geo.addPoint(0, lengths[1], 0, tag=4) geo.addLine(1, 2, tag=1) geo.addLine(2, 3, tag=2) geo.addLine(3, 4, tag=3) geo.addLine(4, 1, tag=4) geo.addCurveLoop([1, 2, 3, 4], tag=1) plane_tag = geo.addPlaneSurface([1], tag=1) geo.mesh.setTransfiniteCurve(1, n_edge_cells[0] + 1) geo.mesh.setTransfiniteCurve(2, n_edge_cells[1] + 1) geo.mesh.setTransfiniteCurve(3, n_edge_cells[0] + 1) geo.mesh.setTransfiniteCurve(4, n_edge_cells[1] + 1) if structured: geo.mesh.setTransfiniteSurface(1) geo.mesh.setRecombine(dim=2, tag=1) return plane_tag
[docs] def cuboid( lengths: float | tuple[float, float, float] = 1.0, n_edge_cells: int | tuple[int, int, int] = 1, n_layers: int = 1, structured_grid: bool = True, order: int = 1, mixed_elements: bool = False, out_name: Path = Path("unit_cube.msh"), msh_version: float | None = None, ) -> None: gmsh.initialize() gmsh.option.set_number("General.Verbosity", 0) if msh_version is not None: gmsh.option.setNumber("Mesh.MshFileVersion", msh_version) name = Path(out_name).stem gmsh.model.add(name) _lengths = lengths if isinstance(lengths, Collection) else (lengths,) * 3 _n_edge_cells = ( n_edge_cells if isinstance(n_edge_cells, Collection) else (n_edge_cells,) * 3 ) bottom_tag = _square( gmsh.model.geo, _lengths[1:], _n_edge_cells[1:], structured_grid and not mixed_elements, ) dz = lengths if isinstance(lengths, float) else lengths[2] nz = n_edge_cells if isinstance(n_edge_cells, int) else n_edge_cells[2] front_tags = [] right_tags = [] back_tags = [] left_tags = [] top_tag = 1 for n in range(n_layers): recombine = n % 2 if mixed_elements else structured_grid newEntities = gmsh.model.geo.extrude( dimTags=[(2, top_tag)], dx=0, dy=0, dz=dz / n_layers, numElements=[nz] if structured_grid else [], recombine=recombine, # fmt: skip ) top_tag = abs(newEntities[0][1]) vol_tag = abs(newEntities[1][1]) layer_name = f"Layer {n}" if n_layers > 1 else name tag = -1 if n_layers > 1 else 0 gmsh.model.addPhysicalGroup( dim=3, tags=[vol_tag], name=layer_name, tag=tag ) front_tags += [abs(newEntities[2][1])] right_tags += [abs(newEntities[3][1])] back_tags += [abs(newEntities[4][1])] left_tags += [abs(newEntities[5][1])] gmsh.model.addPhysicalGroup(dim=2, tags=[bottom_tag], name="bottom") gmsh.model.addPhysicalGroup(dim=2, tags=[top_tag], name="top") gmsh.model.addPhysicalGroup(dim=2, tags=front_tags, name="front") gmsh.model.addPhysicalGroup(dim=2, tags=right_tags, name="right") gmsh.model.addPhysicalGroup(dim=2, tags=back_tags, name="back") gmsh.model.addPhysicalGroup(dim=2, tags=left_tags, name="left") gmsh.model.geo.synchronize() gmsh.model.mesh.generate(dim=3) gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1) if not structured_grid: gmsh.option.setNumber( "Mesh.MeshSizeMin", _lengths[0] / _n_edge_cells[0] ) gmsh.option.setNumber( "Mesh.MeshSizeMax", _lengths[0] / _n_edge_cells[0] ) gmsh.model.mesh.setOrder(order) gmsh.write(str(out_name)) gmsh.finalize()
def _ordered_edges(mesh: pv.UnstructuredGrid) -> np.ndarray: "Return edge elements ordered to form a contiguous array." edges = mesh.extract_feature_edges() n_cells = edges.n_cells # shape=(n_cells, 2, 3), the 2 is for pointA and pointB cell_pts = np.asarray([cell.points for cell in edges.cell]) ordered_cell_ids = [0] cell_id = 0 for _ in range(n_cells): next_id = np.argmax( np.equal(cell_pts[cell_id, 1], cell_pts[:, 0]).all(axis=1) ) ordered_cell_ids += [int(next_id)] cell_id = int(next_id) return cell_pts[ordered_cell_ids[:-1], 0]
[docs] def remesh_with_triangles( mesh: pv.UnstructuredGrid, output_file: Path | str = Path() / "tri_mesh.msh", size_factor: float = 1.0, order: int = 1, ) -> None: """Discretizes a given Mesh with triangles and saves as gmsh .msh. Requires the mesh to be 2D and to contain 'MaterialIDs in the cell data. :param mesh: The mesh which shall be discretized with triangles :param output_file: The full filepath to the resulting file :param size_factor: A factor to scale the element sizes. :param order: The element order (1=linear, 2=quadratic, ...) """ gmsh.initialize() gmsh.option.set_number("General.Verbosity", 0) gmsh.clear() gmsh.model.add("domain") mat_ids = np.unique(mesh["MaterialIDs"]) # gmsh requires the domain ids to start at 1 id_offset = 1 if 0 in mat_ids else 0 region_edge_points = [ _ordered_edges(mesh.threshold([m, m], "MaterialIDs")) for m in (mat_ids) ] for point in np.vstack(region_edge_points): gmsh.model.geo.addPoint(point[0], point[1], point[2]) region_lengths = [len(pts) for pts in region_edge_points] region_start_tag = np.cumsum([0] + region_lengths) + 1 for tag_0, tag_1 in pairwise(region_start_tag): for index in range(tag_0, tag_1 - 1): gmsh.model.geo.addLine(index, index + 1) gmsh.model.geo.addLine(tag_1 - 1, tag_0) field = gmsh.model.mesh.field for index, points in enumerate(region_edge_points): mat_id = mat_ids[index] + id_offset line_tags = range(region_start_tag[index], region_start_tag[index + 1]) gmsh.model.geo.addCurveLoop(line_tags, tag=mat_id) gmsh.model.geo.addPlaneSurface([mat_id], tag=mat_id) gmsh.model.addPhysicalGroup( dim=2, tags=[mat_id], name=f"Layer {mat_id}" ) f1, f2 = (2 * mat_id, 2 * mat_id + 1) distances = np.linalg.norm(np.diff(points, axis=0), axis=1) DEFAULT_SIZE = 6 # pylint: disable=C0103 min_elem_size = size_factor * np.min(distances) * DEFAULT_SIZE max_elem_size = size_factor * np.max(distances) * DEFAULT_SIZE field.add("Distance", f1) field.setNumbers(f1, "CurvesList", line_tags) field.add("Threshold", f2) field.setNumber(f2, "IField", f1) field.setNumber(f2, "SizeMin", min_elem_size) field.setNumber(f2, "SizeMax", max_elem_size) field.setNumber(f2, "DistMin", min_elem_size) field.setNumber(f2, "DistMax", 3 * max_elem_size) field.add("Min", tag=f2 + 1) field.setNumbers(f2 + 1, "FieldsList", (mat_ids * 2 + 1).tolist()) field.setAsBackgroundMesh(f2 + 1) gmsh.model.geo.removeAllDuplicates() gmsh.model.geo.synchronize() gmsh.option.setNumber("Mesh.SecondOrderIncomplete", 1) gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0) gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0) gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0) gmsh.model.mesh.generate(dim=2) gmsh.model.mesh.setOrder(order) gmsh.write(str(output_file)) gmsh.finalize() return