gmshparser — parse Gmsh .msh files¶

gmshparser is a small, dependency-free Python package for reading ASCII Gmsh MSH files. It provides a modern immutable API for normal application code while retaining the original parser-oriented API for backward compatibility.
- Python: 3.12 or newer
- MSH formats: 1.0, 2.0, 2.1, 2.2, 4.0, and 4.1
- Core dependencies: none
- Typing: PEP 561 inline type information through
py.typed - Scope: reading meshes; writing and binary MSH files are not supported
Project links:
Installation¶
Install the stable release with uv:
or with pip:
Install the current development version directly from GitHub:
Entry points¶
The original top-level entry point is intentionally unchanged:
| Call | Result |
|---|---|
gmshparser.parse(path) |
original mutable compatibility Mesh |
gmshparser.read(path) |
modern immutable ModernMesh |
gmshparser.api.parse(path) |
modern immutable Mesh in the explicit modern namespace |
This means existing applications using gmshparser.parse() continue to behave
exactly as before.
Pythonic API¶
Use read() for ordinary new code:
import gmshparser
mesh = gmshparser.read("mesh.msh")
print(mesh.version, len(mesh.nodes), len(mesh.elements))
Applications that prefer the verb parse can opt into the modern namespace:
Collections iterate over value objects and use original Gmsh tags for lookup:
for node in mesh.nodes:
print(node.tag, node.x, node.y, node.z)
node = mesh.nodes[42]
element = mesh.elements[17]
Elements link directly to their nodes and expose a typed element kind:
from gmshparser import ElementType
triangles = mesh.elements.by_type(ElementType.TRIANGLE)
for triangle in triangles:
print(triangle.tag, triangle.element_type, triangle.node_tags)
for node in triangle:
print(node.coordinates)
Entity context is available without separate node and element block APIs:
surface = mesh.entity(dimension=2, tag=7)
print(len(surface.nodes), len(surface.elements), surface.element_types)
print(mesh.surfaces)
print(mesh.volumes)
Physical groups¶
Named and anonymous physical groups are available directly from the modern
model. Names from $PhysicalNames and assignments from MSH 1.x, 2.x, and 4.x
are retained:
walls = mesh.physical_groups["Walls"]
domain = mesh.physical_groups[(3, 2)]
for element in walls.elements:
print(element.tag, element.physical_tags)
print(walls.entities)
print(walls.nodes)
A convenience method also accepts a name or a numeric tag:
read() accepts filesystem paths, Path objects, and open text streams. See the
Pythonic API guide
for the complete model.
Periodic meshes¶
Periodic entity relations from MSH 2.x and 4.x are available through the modern model:
link = mesh.periodic_links[(2, 7)]
print(link.master_entity_tag, link.affine_transform)
for slave_node_tag, master_node_tag in link.node_pairs:
print(slave_node_tag, master_node_tag)
The compatibility model exposes the same records through
get_periodic_link() and get_periodic_links().
NumPy interoperability¶
NumPy support is optional:
Convert the modern mesh into detached array data:
import gmshparser.numpy as gnp
from gmshparser import ElementType
arrays = gnp.to_numpy(mesh)
triangles = arrays.cells[ElementType.TRIANGLE]
triangle_points = arrays.points[triangles.connectivity]
triangle_node_tags = arrays.cell_node_tags(ElementType.TRIANGLE)
Cell connectivity contains zero-based rows into arrays.points. Original Gmsh
node tags, element tags, and entity keys remain available alongside the arrays.
Mixed meshes are represented as rectangular cell blocks grouped by element type.
Structured parser errors¶
Both entry points expose the same error hierarchy and source context:
try:
mesh = gmshparser.read("broken.msh")
except gmshparser.ParseError as error:
print(error)
print(error.filename)
print(error.line_number)
print(error.section)
print(error.line)
A typical message is:
Specific errors include UnsupportedVersionError,
UnsupportedBinaryFormatError, UnexpectedEndOfFileError,
InvalidNodeError, InvalidElementError, and
InvalidElementConnectivityError. Every ParseError is also a ValueError, so
existing broad handlers continue to work. The parser does not print failures to
stdout or stderr.
See the error-handling guide for the full hierarchy and recovery examples.
Compatibility API¶
Existing applications using the original mutable get_* / set_* model can
continue unchanged:
legacy_mesh = gmshparser.parse("mesh.msh")
for entity in legacy_mesh.get_node_entities():
for node in entity.get_nodes():
print(node.get_tag(), node.get_coordinates())
The parser detects the MSH version automatically in both APIs.
Command-line interface¶
The installed gmshparser command can print a mesh summary, nodes, or elements:
gmshparser mesh.msh info
gmshparser mesh.msh nodes
gmshparser mesh.msh elements
gmshparser --version
nodes output begins with the node count, followed by
node_id x y z. elements output begins with the element count, followed by
element_id element_type connectivity....
Visualization helpers¶
Matplotlib is optional. Helpers accept both the modern and compatibility mesh models:
import gmshparser
import matplotlib.pyplot as plt
mesh = gmshparser.read("mesh.msh")
X, Y, triangles = gmshparser.helpers.get_triangles(mesh)
plt.triplot(X, Y, triangles)
plt.axis("equal")
plt.show()
Install the optional dependency separately:
Development¶
The repository uses uv and intentionally does not commit dependency lock files. Ruff provides formatting and linting, while mypy validates both the package and a strict downstream public-API example.
git clone https://github.com/ahojukka5/gmshparser.git
cd gmshparser
uv sync
uv run ruff format --check gmshparser tests examples benchmarks
uv run ruff check gmshparser tests examples benchmarks
uv run mypy gmshparser
uv run mypy --strict tests/typing/public_api.py
uv run pytest
Build the documentation with:
See the Contributing guide for dependency groups and the release workflow.
License¶
gmshparser is released under the MIT License.