Note
Go to the end to download the full example code. or to run this example in your browser via Binder
Meshes from YAML#
Section author: Norbert Grunwald (Helmholtz Centre for Environmental Research GmbH - UFZ)
This example shows how to generate OGS-ready VTU meshes directly from a YAML
geometry description using from_yaml()
.
The YAML schema defines the mesh in terms of five top-level keys:
parameters - scalar values and expressions that can be reused in the geometry
points - coordinates and local mesh sizes
lines - connections between points (straight or circular arcs)
surfaces - closed loops of lines/arcs, optionally with holes
groups - physical groups (domains, boundaries, subregions)
Together these building blocks allow you to describe arbitrary 2D geometries, assign regions and boundaries, and directly obtain a conforming triangular mesh.
Internally, Gmsh is still used to build the geometry and generate a .msh file,
but from_yaml()
converts the result
immediately into a Meshes
object with VTU
submeshes (domain, boundaries, and groups) that can be used directly in OGS.
import ogstools as ot
from ogstools.definitions import EXAMPLES_DIR
Example YAML geometry#
An example geometry is provided in ogstools/examples/meshlib/meshes_from_yaml/example_hlw.yml.
yaml_file = EXAMPLES_DIR / "meshlib/meshes_from_yaml/example_hlw.yml"
yaml_content = yaml_file.read_text(encoding="utf-8")
print(yaml_content)
# -----------------------------------------------------------------------------
# Example geometry definition for gmsh-wrapper YAML
# -----------------------------------------------------------------------------
# This YAML file demonstrates how to define a 2D tunnel cross-section
# with several subdomains (host rock, excavation damage zone, support,
# floor, canister, filling). The syntax is essentially a wrapper around
# the gmsh Python API, with identical keywords (e.g. 'start', 'end',
# 'center' for arcs).
#
# All identifiers (names of points, lines, surfaces, groups) are written
# in quotation marks ("..."). This is recommended to clearly separate
# them from keywords like 'start' or 'end'.
# -----------------------------------------------------------------------------
parameters:
# --- User-defined parameters (geometry input) -----------------------------
"domain_width": 40.0 # width of the rectangular outer domain
"domain_height": 60.0 # height of the rectangular outer domain
"tunnel_radius": 6.0 # tunnel radius
"support_thickness": 1.8 # thickness of support layer
"edz_thickness": 10 # thickness of excavation damaged zone
"floor_width": 10.0 # tunnel floor width
"canister_height": 8.0 # canister height
"canister_width": 6.5 # canister width
# --- Derived parameters ---------------------------------------------------
# Mathematical expressions can be used here. They are evaluated with
# 'eval' (actually via the Python package 'simpleeval'), so functions
# like sqrt() are supported.
"center_x": "domain_width/2" # domain center (x)
"center_y": "domain_height/2" # domain center (y)
# floor_level is computed from tunnel radius and floor width
"floor_level": "center_y - sqrt(tunnel_radius**2 - (floor_width/2)**2)"
# characteristic lengths control mesh element sizes in different regions
"domain_size": "(domain_width + domain_height)/2"
"char_length_domain": "domain_size/10"
"char_length_edz": "edz_thickness/5"
"char_length_support": "support_thickness/5"
"canister_size": "(canister_height+canister_width)/2"
"char_length_canister": "canister_size/20"
# -----------------------------------------------------------------------------
# Point definitions
# Each point has coordinates (x,y) and a target mesh element size
# ('char_length'). Coordinates can be numeric or expressions.
# -----------------------------------------------------------------------------
points:
# Outer domain corners
"d_bottom_left": { coords: [0.0, 0.0], char_length: "char_length_domain" }
"d_bottom_right": { coords: ["domain_width", 0.0], char_length: "char_length_domain" }
"d_top_right": { coords: ["domain_width", "domain_height"], char_length: "char_length_domain" }
"d_top_left": { coords: [0.0, "domain_height"], char_length: "char_length_domain" }
# Reference circle center (used for arcs)
"r0": { coords: ["center_x", "center_y"], char_length: 0.3 }
# Tunnel boundaries (top and bottom points of tunnel arc)
"t_top": { coords: ["center_x", "center_y + tunnel_radius"], char_length: "char_length_support" }
"t_bottom": { coords: ["center_x", "center_y - tunnel_radius"], char_length: "char_length_support" }
# Support boundaries
"s_top": { coords: ["center_x", "center_y + tunnel_radius + support_thickness"], char_length: "char_length_support" }
"s_bottom": { coords: ["center_x", "center_y - tunnel_radius - support_thickness"], char_length: "char_length_support" }
# EDZ boundaries
"e_top": { coords: ["center_x", "center_y + tunnel_radius + support_thickness + edz_thickness"], char_length: "char_length_edz" }
"e_bottom": { coords: ["center_x", "center_y - tunnel_radius - support_thickness - edz_thickness"], char_length: "char_length_edz" }
# Tunnel floor edges
"f_left": { coords: ["center_x - floor_width/2", "floor_level"], char_length: 0.3 }
"f_right": { coords: ["center_x + floor_width/2", "floor_level"], char_length: 0.3 }
# Canister corners
"c_bottom_left": { coords: ["center_x - canister_width/2", "floor_level"], char_length: "char_length_canister" }
"c_bottom_right": { coords: ["center_x + canister_width/2", "floor_level"], char_length: "char_length_canister" }
"c_top_right": { coords: ["center_x + canister_width/2", "floor_level + canister_height"], char_length: "char_length_canister" }
"c_top_left": { coords: ["center_x - canister_width/2", "floor_level + canister_height"], char_length: "char_length_canister" }
# -----------------------------------------------------------------------------
# Line and arc definitions
# - A straight line requires 'start' and 'end'
# - A circular arc requires 'start', 'end', and 'center'
# - Elliptical arcs are not supported yet, they will follow in a future release
# -----------------------------------------------------------------------------
lines:
# Outer domain rectangle
"domain_bottom": { start: "d_bottom_left", end: "d_bottom_right" }
"domain_left": { start: "d_bottom_left", end: "d_top_left" }
"domain_top": { start: "d_bottom_right", end: "d_top_right" }
"domain_right": { start: "d_top_right", end: "d_top_left" }
# Tunnel (half-circle, split into left, bottom, right arcs)
"tunnel_arc_left": { start: "t_top", end: "f_left", center: "r0" }
"tunnel_arc_bottom": { start: "f_left", end: "f_right", center: "r0" }
"tunnel_arc_right": { start: "f_right", end: "t_top", center: "r0" }
# EDZ arcs
"edz_arc_left": { start: "e_top", end: "e_bottom", center: "r0" }
"edz_arc_right": { start: "e_bottom", end: "e_top", center: "r0" }
# Support arcs
"support_arc_left": { start: "s_top", end: "s_bottom", center: "r0" }
"support_arc_right": { start: "s_bottom", end: "s_top", center: "r0" }
# Floor and canister
"floor_line_1": { start: "f_left", end: "c_bottom_left" }
"floor_line_2": { start: "c_bottom_left", end: "c_bottom_right" }
"floor_line_3": { start: "c_bottom_right", end: "f_right" }
"canister_bottom": { start: "c_bottom_left", end: "c_bottom_right" }
"canister_right": { start: "c_bottom_right", end: "c_top_right" }
"canister_top": { start: "c_top_right", end: "c_top_left" }
"canister_left": { start: "c_top_left", end: "c_bottom_left" }
# -----------------------------------------------------------------------------
# Surface definitions
# Each surface is defined by one or more "loops".
#
# - A loop is a closed contour made of line/arc segments.
# - Segments must connect without gaps; their orientations must be consistent.
# - A leading minus ("-segment") reverses the orientation of that segment.
# - All segments in a loop must share the same orientation (either CCW or CW).
# - CCW loops define positive surfaces.
# - CW loops define holes (subtracted regions from the parent surface).
# -----------------------------------------------------------------------------
surfaces:
"host_rock":
loops:
- [ "domain_bottom", "domain_right", "domain_top", "-domain_left" ]
# Outer domain loop (counter-clockwise). 'domain_left' is reversed so
# that all lines follow a consistent CCW orientation.
- [ "-edz_arc_right", "-edz_arc_left" ]
# Inner loop (clockwise). Because this loop is CW, gmsh interprets it
# as a hole → the EDZ region is subtracted from the host rock.
"edz":
loops:
- [ "edz_arc_left", "edz_arc_right" ]
# Outer EDZ loop (CCW, positive surface).
- [ "-support_arc_right", "-support_arc_left" ]
# Inner support loop (CW, hole). Support is cut out from EDZ.
"support":
loops:
- [ "support_arc_left", "support_arc_right" ]
# Outer support loop (CCW).
- [ "-tunnel_arc_right", "-tunnel_arc_left", "-tunnel_arc_bottom", ]
# Inner tunnel loop (CW). Tunnel is cut out from support.
"floor":
loops:
- [ "floor_line_1", "floor_line_2", "floor_line_3", "-tunnel_arc_bottom" ]
# Rectangular floor slab. The bottom arc is reversed to ensure CCW.
"canister":
loops:
- [ "canister_bottom", "canister_right", "canister_top", "canister_left" ]
# Simple rectangular canister (CCW).
"filling":
loops:
- [ "floor_line_1", "-canister_left", "-canister_top", "-canister_right",
"floor_line_3", "tunnel_arc_right", "tunnel_arc_left" ]
# Complex filling region: outer floor + tunnel arcs, with the canister
# subtracted (CW inner loop).
# -----------------------------------------------------------------------------
# Groups
# Groups are collections of geometric entities (points, lines, or surfaces)
# grouped under a common name. They define physical groups in gmsh.
# The 'dim' field specifies the geometric dimension:
# dim=0: points, dim=1: lines, dim=2: surfaces.
# -----------------------------------------------------------------------------
groups:
"Hostrock": { dim: 2, entities: ["host_rock"] }
"EDZ": { dim: 2, entities: ["edz"] }
"Support": { dim: 2, entities: ["support"] }
"Floor": { dim: 2, entities: ["floor"] }
"Canister": { dim: 2, entities: ["canister"] }
"Filling": { dim: 2, entities: ["filling"] }
# Boundary groups, e.g. for BCs etc.
"Domain_Left": { dim: 1, entities: ["domain_left"] }
"Domain_Top": { dim: 1, entities: ["domain_top"] }
"Domain_Right": { dim: 1, entities: ["domain_right"] }
"Domain_Bottom": { dim: 1, entities: ["domain_bottom"] }
# 0-dimensional entities, for e.g. observation points
"Observation_Point": { dim: 0, entities: ["t_top"] }
Mesh generation#
# Using :meth:`~ogstools.meshlib.meshes.Meshes.from_yaml` we create
# a :class:`~ogstools.meshlib.meshes.Meshes` container directly from
# the YAML file.
# Internally, a Gmsh `.msh` file is generated, but it is automatically
# converted into VTU meshes. The returned object already provides
# access to all named meshes (domain, boundaries, groups) and can be
# queried or plotted without further conversion.
meshes = ot.Meshes.from_yaml(yaml_file)
print(*[f"{name}: {mesh.n_cells=}" for name, mesh in meshes.items()], sep="\n")
Info: Mesh written to /tmp/tmp9ogweipc/mesh.msh
domain: mesh.n_cells=6500
Observation_Point: mesh.n_cells=1
Domain_Left: mesh.n_cells=12
Domain_Top: mesh.n_cells=12
Domain_Right: mesh.n_cells=8
Domain_Bottom: mesh.n_cells=8
Hostrock: mesh.n_cells=352
EDZ: mesh.n_cells=2110
Support: mesh.n_cells=1694
Floor: mesh.n_cells=450
Canister: mesh.n_cells=976
Filling: mesh.n_cells=918
Plot the domain mesh#
Here we plot the domain mesh with material IDs shown.
fig = ot.plot.contourf(
meshes.domain(), ot.variables.material_id, show_edges=True
)

Saving meshes
meshes.save()
[PosixPath('/tmp/tmpj6_j5tf9meshes/domain.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Observation_Point.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Domain_Left.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Domain_Top.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Domain_Right.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Domain_Bottom.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Hostrock.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/EDZ.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Support.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Floor.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Canister.vtu'), PosixPath('/tmp/tmpj6_j5tf9meshes/Filling.vtu')]
Command line usage#
# The same functionality is also available via the command line.
# You can run the tool directly, passing a YAML geometry file and optionally
# an output directory. The tool will generate the intermediate `.msh` file,
# convert it to VTU meshes, and save everything to the output directory.
# .. code-block:: bash
# yaml2vtu -i example_simple.yml
# yaml2vtu -i example_simple.yml -o output_dir
# This produces:
# * `output_dir/mesh.msh` - the raw Gmsh mesh
# * `output_dir/mesh_*.vtu` - domain and submeshes in VTU format, ready for OGS
Total running time of the script: (0 minutes 5.119 seconds)