Skip to content

Parser Internals

This page documents the parser classes used inside gmshparser. They are exposed for compatibility and development, but application code should normally use gmshparser.read() or gmshparser.parse().

Main dispatch

gmshparser.main_parser.MainParser

Route MSH sections to version-specific parsers with source context.

Source code in gmshparser/main_parser.py
class MainParser:
    """Route MSH sections to version-specific parsers with source context."""

    def __init__(self, parsers: list[ParserClass] | None = None) -> None:
        self.parsers = parsers
        self.version_detected = False

    def parse(self, mesh: ParserTarget, io: TextIO) -> None:
        """Parse an MSH stream and populate *mesh*.

        All section failures are exposed as structured ``ParseError`` subclasses
        carrying the source name, current section, line number, and line text.
        """
        self.version_detected = False
        filename = mesh.get_name() or str(getattr(io, "name", "<stream>"))
        source = io if isinstance(io, SourceTextIO) else SourceTextIO(io, filename)
        context = source.context

        for raw_line in source:
            line = raw_line.strip()
            if not line:
                continue

            if line == "$NOD" and not self.version_detected:
                mesh.set_version(1.0)
                self.version_detected = True
                if self.parsers is None:
                    self.parsers = DEFAULT_PARSERS_V1
                self._parse_section(NodesParserV1, mesh, source, line)
                continue

            if line == "$MeshFormat" and not self.version_detected:
                self._parse_section(MeshFormatParser, mesh, source, line)
                self.version_detected = True
                if self.parsers is None:
                    self.parsers = self._get_parsers_for_version(mesh)
                continue

            if self.parsers:
                for parser in self.parsers:
                    if parser.get_section_name() == line:
                        self._parse_section(parser, mesh, source, line)
                        break

        context.section = None
        if not self.version_detected:
            raise InvalidSectionError(
                "Could not detect a supported MSH format",
                filename=context.filename,
                line_number=context.line_number or None,
                line=context.line,
            )

    @staticmethod
    def _parse_section(
        parser: ParserClass,
        mesh: ParserTarget,
        source: SourceTextIO,
        section: str,
    ) -> None:
        context = source.context
        context.section = section
        try:
            parser.parse(cast(Mesh, mesh), cast(TextIO, source))
        except Exception as error:
            contextual = contextualize_error(error, context)
            if contextual is error:
                raise
            raise contextual from error
        finally:
            context.section = None

    def _get_parsers_for_version(self, mesh: ParserTarget) -> list[ParserClass]:
        """Return the parser set for the detected major MSH version."""
        version = mesh.get_version()
        if version is None:
            raise InvalidSectionError(
                "Cannot determine parsers because the version was not detected"
            )

        major = mesh.get_version_major()
        if major == 1:
            return DEFAULT_PARSERS_V1
        if major == 2:
            return DEFAULT_PARSERS_V2
        if major == 4:
            return DEFAULT_PARSERS_V4
        raise InvalidSectionError(f"Unsupported MSH format version: {version}")

parsers = parsers instance-attribute

version_detected = False instance-attribute

__init__(parsers=None)

Source code in gmshparser/main_parser.py
def __init__(self, parsers: list[ParserClass] | None = None) -> None:
    self.parsers = parsers
    self.version_detected = False

_get_parsers_for_version(mesh)

Return the parser set for the detected major MSH version.

Source code in gmshparser/main_parser.py
def _get_parsers_for_version(self, mesh: ParserTarget) -> list[ParserClass]:
    """Return the parser set for the detected major MSH version."""
    version = mesh.get_version()
    if version is None:
        raise InvalidSectionError(
            "Cannot determine parsers because the version was not detected"
        )

    major = mesh.get_version_major()
    if major == 1:
        return DEFAULT_PARSERS_V1
    if major == 2:
        return DEFAULT_PARSERS_V2
    if major == 4:
        return DEFAULT_PARSERS_V4
    raise InvalidSectionError(f"Unsupported MSH format version: {version}")

_parse_section(parser, mesh, source, section) staticmethod

Source code in gmshparser/main_parser.py
@staticmethod
def _parse_section(
    parser: ParserClass,
    mesh: ParserTarget,
    source: SourceTextIO,
    section: str,
) -> None:
    context = source.context
    context.section = section
    try:
        parser.parse(cast(Mesh, mesh), cast(TextIO, source))
    except Exception as error:
        contextual = contextualize_error(error, context)
        if contextual is error:
            raise
        raise contextual from error
    finally:
        context.section = None

parse(mesh, io)

Parse an MSH stream and populate mesh.

All section failures are exposed as structured ParseError subclasses carrying the source name, current section, line number, and line text.

Source code in gmshparser/main_parser.py
def parse(self, mesh: ParserTarget, io: TextIO) -> None:
    """Parse an MSH stream and populate *mesh*.

    All section failures are exposed as structured ``ParseError`` subclasses
    carrying the source name, current section, line number, and line text.
    """
    self.version_detected = False
    filename = mesh.get_name() or str(getattr(io, "name", "<stream>"))
    source = io if isinstance(io, SourceTextIO) else SourceTextIO(io, filename)
    context = source.context

    for raw_line in source:
        line = raw_line.strip()
        if not line:
            continue

        if line == "$NOD" and not self.version_detected:
            mesh.set_version(1.0)
            self.version_detected = True
            if self.parsers is None:
                self.parsers = DEFAULT_PARSERS_V1
            self._parse_section(NodesParserV1, mesh, source, line)
            continue

        if line == "$MeshFormat" and not self.version_detected:
            self._parse_section(MeshFormatParser, mesh, source, line)
            self.version_detected = True
            if self.parsers is None:
                self.parsers = self._get_parsers_for_version(mesh)
            continue

        if self.parsers:
            for parser in self.parsers:
                if parser.get_section_name() == line:
                    self._parse_section(parser, mesh, source, line)
                    break

    context.section = None
    if not self.version_detected:
        raise InvalidSectionError(
            "Could not detect a supported MSH format",
            filename=context.filename,
            line_number=context.line_number or None,
            line=context.line,
        )

MainParser detects the MSH version, selects a version-specific parser registry, and dispatches recognized section headers. Unknown sections are skipped.

Parser interface

gmshparser.abstract_parser.AbstractParser

AbstractParser is a superclass of section parsers.

Section parsers inherit AbstractParser and implement the static methods parse and get_section_name.

The first argument of parse is a mutable mesh object that the parser modifies in place. The second argument is the text stream. A section parser consumes input through its matching end marker, such as $EndNodes.

Source code in gmshparser/abstract_parser.py
class AbstractParser:
    """AbstractParser is a superclass of section parsers.

    Section parsers inherit ``AbstractParser`` and implement the static methods
    ``parse`` and ``get_section_name``.

    The first argument of ``parse`` is a mutable mesh object that the parser
    modifies in place. The second argument is the text stream. A section parser
    consumes input through its matching end marker, such as ``$EndNodes``.
    """

    @staticmethod
    def get_section_name() -> str:
        """Return the MSH section header handled by this parser."""
        raise NotImplementedError("Not implemented.")

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        """Parse one section into the mutable target mesh."""
        raise NotImplementedError("Not implemented.")

get_section_name() staticmethod

Return the MSH section header handled by this parser.

Source code in gmshparser/abstract_parser.py
@staticmethod
def get_section_name() -> str:
    """Return the MSH section header handled by this parser."""
    raise NotImplementedError("Not implemented.")

parse(mesh, io) staticmethod

Parse one section into the mutable target mesh.

Source code in gmshparser/abstract_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    """Parse one section into the mutable target mesh."""
    raise NotImplementedError("Not implemented.")

Section parsers receive a mutable parser target and a text stream positioned immediately after the section header. The compatibility Mesh and ModernMeshBuilder implement the shared target operations.

Common sections

gmshparser.mesh_format_parser.MeshFormatParser

Bases: AbstractParser

Parse and validate the $MeshFormat section.

Source code in gmshparser/mesh_format_parser.py
class MeshFormatParser(AbstractParser):
    """Parse and validate the ``$MeshFormat`` section."""

    @staticmethod
    def get_section_name() -> str:
        return "$MeshFormat"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$MeshFormat header")
        fields = line.strip().split()
        if len(fields) != 3:
            raise InvalidSectionError(
                "$MeshFormat header must contain version, file type, and data size"
            )

        try:
            version_enum = VersionManager.validate_version(fields[0])
        except ValueError as error:
            raise UnsupportedVersionError(str(error)) from error

        try:
            file_type = int(fields[1])
            data_size = int(fields[2])
        except ValueError as error:
            raise InvalidSectionError(
                "$MeshFormat file type and data size must be integers"
            ) from error

        if file_type not in {0, 1}:
            raise InvalidSectionError(
                f"Unsupported $MeshFormat file type {file_type}; expected 0 or 1"
            )
        if file_type == 1:
            raise UnsupportedBinaryFormatError(
                "Binary MSH files are not supported; provide an ASCII MSH file"
            )
        if data_size <= 0:
            raise InvalidSectionError("$MeshFormat data size must be positive")

        mesh.set_version(version_enum.version_number)
        mesh.set_ascii(True)
        mesh.set_precision(data_size)
        expect_end_marker(io, "$EndMeshFormat")

get_section_name() staticmethod

Source code in gmshparser/mesh_format_parser.py
@staticmethod
def get_section_name() -> str:
    return "$MeshFormat"

parse(mesh, io) staticmethod

Source code in gmshparser/mesh_format_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$MeshFormat header")
    fields = line.strip().split()
    if len(fields) != 3:
        raise InvalidSectionError(
            "$MeshFormat header must contain version, file type, and data size"
        )

    try:
        version_enum = VersionManager.validate_version(fields[0])
    except ValueError as error:
        raise UnsupportedVersionError(str(error)) from error

    try:
        file_type = int(fields[1])
        data_size = int(fields[2])
    except ValueError as error:
        raise InvalidSectionError(
            "$MeshFormat file type and data size must be integers"
        ) from error

    if file_type not in {0, 1}:
        raise InvalidSectionError(
            f"Unsupported $MeshFormat file type {file_type}; expected 0 or 1"
        )
    if file_type == 1:
        raise UnsupportedBinaryFormatError(
            "Binary MSH files are not supported; provide an ASCII MSH file"
        )
    if data_size <= 0:
        raise InvalidSectionError("$MeshFormat data size must be positive")

    mesh.set_version(version_enum.version_number)
    mesh.set_ascii(True)
    mesh.set_precision(data_size)
    expect_end_marker(io, "$EndMeshFormat")

gmshparser.physical_names_parser.PhysicalNamesParser

Bases: AbstractParser

Parse optional $PhysicalNames declarations.

Source code in gmshparser/physical_names_parser.py
class PhysicalNamesParser(AbstractParser):
    """Parse optional ``$PhysicalNames`` declarations."""

    @staticmethod
    def get_section_name() -> str:
        return "$PhysicalNames"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$PhysicalNames count")
        if line.startswith("$PhysicalNames"):
            line = read_required_line(io, "$PhysicalNames count")

        try:
            count = int(line.strip())
        except ValueError as error:
            raise InvalidSectionError(
                "$PhysicalNames count must be an integer"
            ) from error
        if count < 0:
            raise InvalidSectionError("$PhysicalNames count cannot be negative")

        for _ in range(count):
            record = read_required_line(io, "a physical-name record")
            try:
                fields = shlex.split(record.strip(), posix=True)
            except ValueError as error:
                raise InvalidSectionError(
                    "A physical-name record contains invalid quoting"
                ) from error
            if len(fields) != 3:
                raise InvalidSectionError(
                    "A physical-name record must contain dimension, tag, and name"
                )

            dimension, tag, name = fields
            try:
                dimension_value = int(dimension)
                tag_value = int(tag)
            except ValueError as error:
                raise InvalidSectionError(
                    "Physical-name dimensions and tags must be integers"
                ) from error
            if dimension_value not in {0, 1, 2, 3}:
                raise InvalidSectionError(
                    f"Physical group {tag_value} has invalid dimension "
                    f"{dimension_value}"
                )
            mesh.set_physical_name(dimension_value, tag_value, name)

        expect_end_marker(io, "$EndPhysicalNames")

get_section_name() staticmethod

Source code in gmshparser/physical_names_parser.py
@staticmethod
def get_section_name() -> str:
    return "$PhysicalNames"

parse(mesh, io) staticmethod

Source code in gmshparser/physical_names_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$PhysicalNames count")
    if line.startswith("$PhysicalNames"):
        line = read_required_line(io, "$PhysicalNames count")

    try:
        count = int(line.strip())
    except ValueError as error:
        raise InvalidSectionError(
            "$PhysicalNames count must be an integer"
        ) from error
    if count < 0:
        raise InvalidSectionError("$PhysicalNames count cannot be negative")

    for _ in range(count):
        record = read_required_line(io, "a physical-name record")
        try:
            fields = shlex.split(record.strip(), posix=True)
        except ValueError as error:
            raise InvalidSectionError(
                "A physical-name record contains invalid quoting"
            ) from error
        if len(fields) != 3:
            raise InvalidSectionError(
                "A physical-name record must contain dimension, tag, and name"
            )

        dimension, tag, name = fields
        try:
            dimension_value = int(dimension)
            tag_value = int(tag)
        except ValueError as error:
            raise InvalidSectionError(
                "Physical-name dimensions and tags must be integers"
            ) from error
        if dimension_value not in {0, 1, 2, 3}:
            raise InvalidSectionError(
                f"Physical group {tag_value} has invalid dimension "
                f"{dimension_value}"
            )
        mesh.set_physical_name(dimension_value, tag_value, name)

    expect_end_marker(io, "$EndPhysicalNames")

MSH 1.0

gmshparser.nodes_parser_v1.NodesParserV1

Bases: AbstractParser

Parse the legacy MSH 1.0 $NOD section.

Source code in gmshparser/nodes_parser_v1.py
class NodesParserV1(AbstractParser):
    """Parse the legacy MSH 1.0 ``$NOD`` section."""

    @staticmethod
    def get_section_name() -> str:
        return "$NOD"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        count_line = read_required_line(io, "$NOD node count")
        try:
            number_of_nodes = int(count_line.strip())
        except ValueError as error:
            raise InvalidNodeError("$NOD node count must be an integer") from error
        if number_of_nodes < 0:
            raise InvalidNodeError("$NOD node count cannot be negative")

        mesh.set_number_of_nodes(number_of_nodes)
        records: list[tuple[int, tuple[float, ...]]] = []
        min_tag: int | None = None
        max_tag: int | None = None

        for _ in range(number_of_nodes):
            line = read_required_line(io, "an MSH 1 node record")
            fields = line.strip().split()
            if len(fields) != 4:
                raise InvalidNodeError(
                    "An MSH 1 node record must contain tag, x, y, and z"
                )
            try:
                node_tag = int(fields[0])
                coordinates = tuple(float(value) for value in fields[1:])
            except ValueError as error:
                raise InvalidNodeError(
                    "MSH 1 node tags and coordinates must be numeric"
                ) from error

            min_tag = node_tag if min_tag is None else min(min_tag, node_tag)
            max_tag = node_tag if max_tag is None else max(max_tag, node_tag)
            records.append((node_tag, coordinates))

        if min_tag is not None and max_tag is not None:
            mesh.set_min_node_tag(min_tag)
            mesh.set_max_node_tag(max_tag)
        mesh.set_number_of_node_entities(1)
        mesh.add_node_block(2, 1, 0, records)
        expect_end_marker(io, "$ENDNOD")

get_section_name() staticmethod

Source code in gmshparser/nodes_parser_v1.py
@staticmethod
def get_section_name() -> str:
    return "$NOD"

parse(mesh, io) staticmethod

Source code in gmshparser/nodes_parser_v1.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    count_line = read_required_line(io, "$NOD node count")
    try:
        number_of_nodes = int(count_line.strip())
    except ValueError as error:
        raise InvalidNodeError("$NOD node count must be an integer") from error
    if number_of_nodes < 0:
        raise InvalidNodeError("$NOD node count cannot be negative")

    mesh.set_number_of_nodes(number_of_nodes)
    records: list[tuple[int, tuple[float, ...]]] = []
    min_tag: int | None = None
    max_tag: int | None = None

    for _ in range(number_of_nodes):
        line = read_required_line(io, "an MSH 1 node record")
        fields = line.strip().split()
        if len(fields) != 4:
            raise InvalidNodeError(
                "An MSH 1 node record must contain tag, x, y, and z"
            )
        try:
            node_tag = int(fields[0])
            coordinates = tuple(float(value) for value in fields[1:])
        except ValueError as error:
            raise InvalidNodeError(
                "MSH 1 node tags and coordinates must be numeric"
            ) from error

        min_tag = node_tag if min_tag is None else min(min_tag, node_tag)
        max_tag = node_tag if max_tag is None else max(max_tag, node_tag)
        records.append((node_tag, coordinates))

    if min_tag is not None and max_tag is not None:
        mesh.set_min_node_tag(min_tag)
        mesh.set_max_node_tag(max_tag)
    mesh.set_number_of_node_entities(1)
    mesh.add_node_block(2, 1, 0, records)
    expect_end_marker(io, "$ENDNOD")

gmshparser.elements_parser_v1.ElementsParserV1

Bases: AbstractParser

Parse the legacy MSH 1.0 $ELM section.

Source code in gmshparser/elements_parser_v1.py
class ElementsParserV1(AbstractParser):
    """Parse the legacy MSH 1.0 ``$ELM`` section."""

    @staticmethod
    def get_section_name() -> str:
        return "$ELM"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        count_line = read_required_line(io, "$ELM element count")
        try:
            number_of_elements = int(count_line.strip())
        except ValueError as error:
            raise InvalidElementError(
                "$ELM element count must be an integer"
            ) from error
        if number_of_elements < 0:
            raise InvalidElementError("$ELM element count cannot be negative")

        mesh.set_number_of_elements(number_of_elements)
        element_groups: dict[
            tuple[int, int, int],
            list[tuple[int, list[int], tuple[int, ...]]],
        ] = {}
        physical_tags_by_entity: dict[tuple[int, int], list[int]] = {}
        min_tag: int | None = None
        max_tag: int | None = None

        for _ in range(number_of_elements):
            line = read_required_line(io, "an MSH 1 element record")
            fields = line.strip().split()
            if len(fields) < 5:
                raise InvalidElementError(
                    "An MSH 1 element record must contain at least five fields"
                )

            try:
                element_tag = int(fields[0])
                element_type_id = int(fields[1])
                physical_tag = int(fields[2])
                entity_tag = int(fields[3])
                declared_node_count = int(fields[4])
                node_tags = [int(value) for value in fields[5:]]
            except ValueError as error:
                raise InvalidElementError(
                    "MSH 1 element records must contain integers"
                ) from error

            if declared_node_count < 0:
                raise InvalidElementError("Declared node counts cannot be negative")
            if len(node_tags) != declared_node_count:
                raise InvalidElementConnectivityError(
                    f"Element {element_tag} declares {declared_node_count} nodes, "
                    f"but the record contains {len(node_tags)}"
                )

            element_type = validate_element_connectivity(
                element_type_id,
                node_tags,
                element_tag=element_tag,
            )
            dimension = element_type.dimension
            assert dimension is not None

            physical_tags = (physical_tag,) if physical_tag > 0 else ()
            entity_key = dimension, entity_tag
            if physical_tag > 0:
                entity_tags = physical_tags_by_entity.setdefault(entity_key, [])
                if physical_tag not in entity_tags:
                    entity_tags.append(physical_tag)

            min_tag = element_tag if min_tag is None else min(min_tag, element_tag)
            max_tag = element_tag if max_tag is None else max(max_tag, element_tag)
            block_key = (dimension, entity_tag, int(element_type))
            element_groups.setdefault(block_key, []).append(
                (element_tag, node_tags, physical_tags)
            )

        if min_tag is not None and max_tag is not None:
            mesh.set_min_element_tag(min_tag)
            mesh.set_max_element_tag(max_tag)
        mesh.set_number_of_element_entities(len(element_groups))

        for (
            dimension,
            entity_tag,
        ), entity_physical_tags in physical_tags_by_entity.items():
            mesh.add_entity_physical_tags(
                dimension,
                entity_tag,
                entity_physical_tags,
            )
        for (dimension, entity_tag, type_id), block_elements in element_groups.items():
            mesh.add_element_block(dimension, entity_tag, type_id, block_elements)

        expect_end_marker(io, "$ENDELM")

get_section_name() staticmethod

Source code in gmshparser/elements_parser_v1.py
@staticmethod
def get_section_name() -> str:
    return "$ELM"

parse(mesh, io) staticmethod

Source code in gmshparser/elements_parser_v1.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    count_line = read_required_line(io, "$ELM element count")
    try:
        number_of_elements = int(count_line.strip())
    except ValueError as error:
        raise InvalidElementError(
            "$ELM element count must be an integer"
        ) from error
    if number_of_elements < 0:
        raise InvalidElementError("$ELM element count cannot be negative")

    mesh.set_number_of_elements(number_of_elements)
    element_groups: dict[
        tuple[int, int, int],
        list[tuple[int, list[int], tuple[int, ...]]],
    ] = {}
    physical_tags_by_entity: dict[tuple[int, int], list[int]] = {}
    min_tag: int | None = None
    max_tag: int | None = None

    for _ in range(number_of_elements):
        line = read_required_line(io, "an MSH 1 element record")
        fields = line.strip().split()
        if len(fields) < 5:
            raise InvalidElementError(
                "An MSH 1 element record must contain at least five fields"
            )

        try:
            element_tag = int(fields[0])
            element_type_id = int(fields[1])
            physical_tag = int(fields[2])
            entity_tag = int(fields[3])
            declared_node_count = int(fields[4])
            node_tags = [int(value) for value in fields[5:]]
        except ValueError as error:
            raise InvalidElementError(
                "MSH 1 element records must contain integers"
            ) from error

        if declared_node_count < 0:
            raise InvalidElementError("Declared node counts cannot be negative")
        if len(node_tags) != declared_node_count:
            raise InvalidElementConnectivityError(
                f"Element {element_tag} declares {declared_node_count} nodes, "
                f"but the record contains {len(node_tags)}"
            )

        element_type = validate_element_connectivity(
            element_type_id,
            node_tags,
            element_tag=element_tag,
        )
        dimension = element_type.dimension
        assert dimension is not None

        physical_tags = (physical_tag,) if physical_tag > 0 else ()
        entity_key = dimension, entity_tag
        if physical_tag > 0:
            entity_tags = physical_tags_by_entity.setdefault(entity_key, [])
            if physical_tag not in entity_tags:
                entity_tags.append(physical_tag)

        min_tag = element_tag if min_tag is None else min(min_tag, element_tag)
        max_tag = element_tag if max_tag is None else max(max_tag, element_tag)
        block_key = (dimension, entity_tag, int(element_type))
        element_groups.setdefault(block_key, []).append(
            (element_tag, node_tags, physical_tags)
        )

    if min_tag is not None and max_tag is not None:
        mesh.set_min_element_tag(min_tag)
        mesh.set_max_element_tag(max_tag)
    mesh.set_number_of_element_entities(len(element_groups))

    for (
        dimension,
        entity_tag,
    ), entity_physical_tags in physical_tags_by_entity.items():
        mesh.add_entity_physical_tags(
            dimension,
            entity_tag,
            entity_physical_tags,
        )
    for (dimension, entity_tag, type_id), block_elements in element_groups.items():
        mesh.add_element_block(dimension, entity_tag, type_id, block_elements)

    expect_end_marker(io, "$ENDELM")

MSH 2.x

gmshparser.nodes_parser_v2.NodesParserV2

Bases: AbstractParser

Parse flat MSH 2.x node records.

Source code in gmshparser/nodes_parser_v2.py
class NodesParserV2(AbstractParser):
    """Parse flat MSH 2.x node records."""

    @staticmethod
    def get_section_name() -> str:
        return "$Nodes"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$Nodes node count")
        if line.startswith("$Nodes"):
            line = read_required_line(io, "$Nodes node count")

        try:
            number_of_nodes = int(line.strip())
        except ValueError as error:
            raise InvalidNodeError("$Nodes node count must be an integer") from error
        if number_of_nodes < 0:
            raise InvalidNodeError("$Nodes node count cannot be negative")

        mesh.set_number_of_nodes(number_of_nodes)
        records: list[tuple[int, tuple[float, ...]]] = []
        min_tag: int | None = None
        max_tag: int | None = None

        for _ in range(number_of_nodes):
            node_line = read_required_line(io, "an MSH 2 node record")
            fields = node_line.strip().split()
            if len(fields) != 4:
                raise InvalidNodeError(
                    "An MSH 2 node record must contain tag, x, y, and z"
                )
            try:
                node_tag = int(fields[0])
                coordinates = tuple(float(value) for value in fields[1:])
            except ValueError as error:
                raise InvalidNodeError(
                    "MSH 2 node tags and coordinates must be numeric"
                ) from error

            min_tag = node_tag if min_tag is None else min(min_tag, node_tag)
            max_tag = node_tag if max_tag is None else max(max_tag, node_tag)
            records.append((node_tag, coordinates))

        if min_tag is not None and max_tag is not None:
            mesh.set_min_node_tag(min_tag)
            mesh.set_max_node_tag(max_tag)
        mesh.set_number_of_node_entities(1)
        mesh.add_node_block(3, 1, 0, records)
        expect_end_marker(io, "$EndNodes")

get_section_name() staticmethod

Source code in gmshparser/nodes_parser_v2.py
@staticmethod
def get_section_name() -> str:
    return "$Nodes"

parse(mesh, io) staticmethod

Source code in gmshparser/nodes_parser_v2.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$Nodes node count")
    if line.startswith("$Nodes"):
        line = read_required_line(io, "$Nodes node count")

    try:
        number_of_nodes = int(line.strip())
    except ValueError as error:
        raise InvalidNodeError("$Nodes node count must be an integer") from error
    if number_of_nodes < 0:
        raise InvalidNodeError("$Nodes node count cannot be negative")

    mesh.set_number_of_nodes(number_of_nodes)
    records: list[tuple[int, tuple[float, ...]]] = []
    min_tag: int | None = None
    max_tag: int | None = None

    for _ in range(number_of_nodes):
        node_line = read_required_line(io, "an MSH 2 node record")
        fields = node_line.strip().split()
        if len(fields) != 4:
            raise InvalidNodeError(
                "An MSH 2 node record must contain tag, x, y, and z"
            )
        try:
            node_tag = int(fields[0])
            coordinates = tuple(float(value) for value in fields[1:])
        except ValueError as error:
            raise InvalidNodeError(
                "MSH 2 node tags and coordinates must be numeric"
            ) from error

        min_tag = node_tag if min_tag is None else min(min_tag, node_tag)
        max_tag = node_tag if max_tag is None else max(max_tag, node_tag)
        records.append((node_tag, coordinates))

    if min_tag is not None and max_tag is not None:
        mesh.set_min_node_tag(min_tag)
        mesh.set_max_node_tag(max_tag)
    mesh.set_number_of_node_entities(1)
    mesh.add_node_block(3, 1, 0, records)
    expect_end_marker(io, "$EndNodes")

gmshparser.elements_parser_v2.ElementsParserV2

Bases: AbstractParser

Parse flat MSH 2.x element records.

Source code in gmshparser/elements_parser_v2.py
class ElementsParserV2(AbstractParser):
    """Parse flat MSH 2.x element records."""

    @staticmethod
    def get_section_name() -> str:
        return "$Elements"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$Elements element count")
        if line.startswith("$Elements"):
            line = read_required_line(io, "$Elements element count")

        try:
            number_of_elements = int(line.strip())
        except ValueError as error:
            raise InvalidElementError(
                "$Elements element count must be an integer"
            ) from error
        if number_of_elements < 0:
            raise InvalidElementError("$Elements element count cannot be negative")

        mesh.set_number_of_elements(number_of_elements)
        element_groups: dict[
            tuple[int, int, int],
            list[tuple[int, list[int], tuple[int, ...]]],
        ] = {}
        physical_tags_by_entity: dict[tuple[int, int], list[int]] = {}
        min_tag: int | None = None
        max_tag: int | None = None

        for _ in range(number_of_elements):
            record = read_required_line(io, "an MSH 2 element record")
            try:
                fields = [int(value) for value in record.strip().split()]
            except ValueError as error:
                raise InvalidElementError(
                    "MSH 2 element records must contain integers"
                ) from error
            if len(fields) < 3:
                raise InvalidElementError(
                    "An MSH 2 element record must contain at least three fields"
                )

            element_tag = fields[0]
            element_type_id = fields[1]
            number_of_tags = fields[2]
            if number_of_tags < 0:
                raise InvalidElementError("Element tag counts cannot be negative")

            tags_start = 3
            tags_end = tags_start + number_of_tags
            if len(fields) < tags_end:
                raise InvalidElementError(
                    f"Element {element_tag} declares {number_of_tags} tags, "
                    f"but the record contains {max(0, len(fields) - tags_start)}"
                )

            tags = fields[tags_start:tags_end]
            node_tags = fields[tags_end:]
            element_type = validate_element_connectivity(
                element_type_id,
                node_tags,
                element_tag=element_tag,
            )
            dimension = element_type.dimension
            assert dimension is not None

            physical_tag = tags[0] if tags and tags[0] > 0 else 0
            physical_tags = (physical_tag,) if physical_tag else ()
            entity_tag = tags[1] if len(tags) > 1 else 1
            entity_key = dimension, entity_tag
            if physical_tag:
                entity_tags = physical_tags_by_entity.setdefault(entity_key, [])
                if physical_tag not in entity_tags:
                    entity_tags.append(physical_tag)

            min_tag = element_tag if min_tag is None else min(min_tag, element_tag)
            max_tag = element_tag if max_tag is None else max(max_tag, element_tag)
            block_key = (dimension, entity_tag, int(element_type))
            element_groups.setdefault(block_key, []).append(
                (element_tag, node_tags, physical_tags)
            )

        if min_tag is not None and max_tag is not None:
            mesh.set_min_element_tag(min_tag)
            mesh.set_max_element_tag(max_tag)
        mesh.set_number_of_element_entities(len(element_groups))

        for (
            dimension,
            entity_tag,
        ), entity_physical_tags in physical_tags_by_entity.items():
            mesh.add_entity_physical_tags(
                dimension,
                entity_tag,
                entity_physical_tags,
            )
        for (dimension, entity_tag, type_id), block_elements in element_groups.items():
            mesh.add_element_block(dimension, entity_tag, type_id, block_elements)

        expect_end_marker(io, "$EndElements")

get_section_name() staticmethod

Source code in gmshparser/elements_parser_v2.py
@staticmethod
def get_section_name() -> str:
    return "$Elements"

parse(mesh, io) staticmethod

Source code in gmshparser/elements_parser_v2.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$Elements element count")
    if line.startswith("$Elements"):
        line = read_required_line(io, "$Elements element count")

    try:
        number_of_elements = int(line.strip())
    except ValueError as error:
        raise InvalidElementError(
            "$Elements element count must be an integer"
        ) from error
    if number_of_elements < 0:
        raise InvalidElementError("$Elements element count cannot be negative")

    mesh.set_number_of_elements(number_of_elements)
    element_groups: dict[
        tuple[int, int, int],
        list[tuple[int, list[int], tuple[int, ...]]],
    ] = {}
    physical_tags_by_entity: dict[tuple[int, int], list[int]] = {}
    min_tag: int | None = None
    max_tag: int | None = None

    for _ in range(number_of_elements):
        record = read_required_line(io, "an MSH 2 element record")
        try:
            fields = [int(value) for value in record.strip().split()]
        except ValueError as error:
            raise InvalidElementError(
                "MSH 2 element records must contain integers"
            ) from error
        if len(fields) < 3:
            raise InvalidElementError(
                "An MSH 2 element record must contain at least three fields"
            )

        element_tag = fields[0]
        element_type_id = fields[1]
        number_of_tags = fields[2]
        if number_of_tags < 0:
            raise InvalidElementError("Element tag counts cannot be negative")

        tags_start = 3
        tags_end = tags_start + number_of_tags
        if len(fields) < tags_end:
            raise InvalidElementError(
                f"Element {element_tag} declares {number_of_tags} tags, "
                f"but the record contains {max(0, len(fields) - tags_start)}"
            )

        tags = fields[tags_start:tags_end]
        node_tags = fields[tags_end:]
        element_type = validate_element_connectivity(
            element_type_id,
            node_tags,
            element_tag=element_tag,
        )
        dimension = element_type.dimension
        assert dimension is not None

        physical_tag = tags[0] if tags and tags[0] > 0 else 0
        physical_tags = (physical_tag,) if physical_tag else ()
        entity_tag = tags[1] if len(tags) > 1 else 1
        entity_key = dimension, entity_tag
        if physical_tag:
            entity_tags = physical_tags_by_entity.setdefault(entity_key, [])
            if physical_tag not in entity_tags:
                entity_tags.append(physical_tag)

        min_tag = element_tag if min_tag is None else min(min_tag, element_tag)
        max_tag = element_tag if max_tag is None else max(max_tag, element_tag)
        block_key = (dimension, entity_tag, int(element_type))
        element_groups.setdefault(block_key, []).append(
            (element_tag, node_tags, physical_tags)
        )

    if min_tag is not None and max_tag is not None:
        mesh.set_min_element_tag(min_tag)
        mesh.set_max_element_tag(max_tag)
    mesh.set_number_of_element_entities(len(element_groups))

    for (
        dimension,
        entity_tag,
    ), entity_physical_tags in physical_tags_by_entity.items():
        mesh.add_entity_physical_tags(
            dimension,
            entity_tag,
            entity_physical_tags,
        )
    for (dimension, entity_tag, type_id), block_elements in element_groups.items():
        mesh.add_element_block(dimension, entity_tag, type_id, block_elements)

    expect_end_marker(io, "$EndElements")

MSH 4.x

gmshparser.entities_parser.EntitiesParser

Bases: AbstractParser

Parse MSH 4.0 and 4.1 entity records and physical assignments.

Source code in gmshparser/entities_parser.py
class EntitiesParser(AbstractParser):
    """Parse MSH 4.0 and 4.1 entity records and physical assignments."""

    @staticmethod
    def get_section_name() -> str:
        return "$Entities"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$Entities header")
        if line.startswith("$Entities"):
            line = read_required_line(io, "$Entities header")

        try:
            counts = [int(value) for value in line.split()]
        except ValueError as error:
            raise InvalidSectionError(
                "$Entities header must contain integers"
            ) from error
        if len(counts) != 4:
            raise InvalidSectionError(
                "$Entities header must contain four entity counts"
            )
        if any(count < 0 for count in counts):
            raise InvalidSectionError("$Entities counts cannot be negative")

        is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
        seen_entity_tags: list[set[int]] = [set() for _ in counts]

        for dimension, count in enumerate(counts):
            geometry_count = 6 if is_v40 or dimension > 0 else 3
            physical_count_index = 1 + geometry_count

            for _ in range(count):
                record = read_required_line(io, "an entity record")
                parts = record.split()
                minimum_fields = physical_count_index + 1
                if len(parts) < minimum_fields:
                    raise InvalidSectionError(
                        f"A dimension-{dimension} entity record requires at least "
                        f"{minimum_fields} fields"
                    )

                try:
                    tag = int(parts[0])
                    geometry = tuple(
                        float(value) for value in parts[1:physical_count_index]
                    )
                    number_of_physical_tags = int(parts[physical_count_index])
                except ValueError as error:
                    raise InvalidSectionError(
                        "Entity tags and counts must be integers and entity geometry "
                        "values must be numbers"
                    ) from error

                if tag <= 0:
                    raise InvalidSectionError("Entity tags must be positive")
                if tag in seen_entity_tags[dimension]:
                    raise InvalidSectionError(
                        f"Duplicate dimension-{dimension} entity tag {tag}"
                    )
                if any(not isfinite(value) for value in geometry):
                    raise InvalidSectionError(
                        f"Entity {tag} contains non-finite geometry values"
                    )
                if geometry_count == 6:
                    minimum = geometry[:3]
                    maximum = geometry[3:]
                    if any(
                        lower > upper
                        for lower, upper in zip(minimum, maximum, strict=True)
                    ):
                        raise InvalidSectionError(
                            f"Entity {tag} has an inverted bounding box"
                        )
                if number_of_physical_tags < 0:
                    raise InvalidSectionError(
                        "Entity physical-group counts cannot be negative"
                    )

                physical_start = physical_count_index + 1
                physical_stop = physical_start + number_of_physical_tags
                if len(parts) < physical_stop:
                    raise InvalidSectionError(
                        f"Entity {tag} declares {number_of_physical_tags} physical "
                        f"tags, but the record contains "
                        f"{max(0, len(parts) - physical_start)}"
                    )

                try:
                    physical_tags = tuple(
                        int(value) for value in parts[physical_start:physical_stop]
                    )
                except ValueError as error:
                    raise InvalidSectionError(
                        "Entity physical-group tags must be integers"
                    ) from error
                if any(physical_tag <= 0 for physical_tag in physical_tags):
                    raise InvalidSectionError(
                        "Entity physical-group tags must be positive integers"
                    )

                if dimension == 0:
                    if len(parts) != physical_stop:
                        raise InvalidSectionError(
                            f"Point entity {tag} contains unexpected trailing fields"
                        )
                else:
                    if len(parts) < physical_stop + 1:
                        raise InvalidSectionError(
                            f"Entity {tag} is missing its boundary-entity count"
                        )
                    try:
                        boundary_count = int(parts[physical_stop])
                    except ValueError as error:
                        raise InvalidSectionError(
                            "Entity boundary counts must be integers"
                        ) from error
                    if boundary_count < 0:
                        raise InvalidSectionError(
                            "Entity boundary counts cannot be negative"
                        )

                    boundary_start = physical_stop + 1
                    boundary_stop = boundary_start + boundary_count
                    if len(parts) != boundary_stop:
                        available = max(0, len(parts) - boundary_start)
                        raise InvalidSectionError(
                            f"Entity {tag} declares {boundary_count} boundary tags, "
                            f"but the record contains {available}"
                        )
                    try:
                        boundary_tags = tuple(
                            int(value) for value in parts[boundary_start:boundary_stop]
                        )
                    except ValueError as error:
                        raise InvalidSectionError(
                            "Entity boundary tags must be integers"
                        ) from error
                    if any(boundary_tag == 0 for boundary_tag in boundary_tags):
                        raise InvalidSectionError(
                            "Entity boundary tags must be non-zero signed integers"
                        )

                seen_entity_tags[dimension].add(tag)
                mesh.set_entity_physical_tags(dimension, tag, physical_tags)

        expect_end_marker(io, "$EndEntities")

get_section_name() staticmethod

Source code in gmshparser/entities_parser.py
@staticmethod
def get_section_name() -> str:
    return "$Entities"

parse(mesh, io) staticmethod

Source code in gmshparser/entities_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$Entities header")
    if line.startswith("$Entities"):
        line = read_required_line(io, "$Entities header")

    try:
        counts = [int(value) for value in line.split()]
    except ValueError as error:
        raise InvalidSectionError(
            "$Entities header must contain integers"
        ) from error
    if len(counts) != 4:
        raise InvalidSectionError(
            "$Entities header must contain four entity counts"
        )
    if any(count < 0 for count in counts):
        raise InvalidSectionError("$Entities counts cannot be negative")

    is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
    seen_entity_tags: list[set[int]] = [set() for _ in counts]

    for dimension, count in enumerate(counts):
        geometry_count = 6 if is_v40 or dimension > 0 else 3
        physical_count_index = 1 + geometry_count

        for _ in range(count):
            record = read_required_line(io, "an entity record")
            parts = record.split()
            minimum_fields = physical_count_index + 1
            if len(parts) < minimum_fields:
                raise InvalidSectionError(
                    f"A dimension-{dimension} entity record requires at least "
                    f"{minimum_fields} fields"
                )

            try:
                tag = int(parts[0])
                geometry = tuple(
                    float(value) for value in parts[1:physical_count_index]
                )
                number_of_physical_tags = int(parts[physical_count_index])
            except ValueError as error:
                raise InvalidSectionError(
                    "Entity tags and counts must be integers and entity geometry "
                    "values must be numbers"
                ) from error

            if tag <= 0:
                raise InvalidSectionError("Entity tags must be positive")
            if tag in seen_entity_tags[dimension]:
                raise InvalidSectionError(
                    f"Duplicate dimension-{dimension} entity tag {tag}"
                )
            if any(not isfinite(value) for value in geometry):
                raise InvalidSectionError(
                    f"Entity {tag} contains non-finite geometry values"
                )
            if geometry_count == 6:
                minimum = geometry[:3]
                maximum = geometry[3:]
                if any(
                    lower > upper
                    for lower, upper in zip(minimum, maximum, strict=True)
                ):
                    raise InvalidSectionError(
                        f"Entity {tag} has an inverted bounding box"
                    )
            if number_of_physical_tags < 0:
                raise InvalidSectionError(
                    "Entity physical-group counts cannot be negative"
                )

            physical_start = physical_count_index + 1
            physical_stop = physical_start + number_of_physical_tags
            if len(parts) < physical_stop:
                raise InvalidSectionError(
                    f"Entity {tag} declares {number_of_physical_tags} physical "
                    f"tags, but the record contains "
                    f"{max(0, len(parts) - physical_start)}"
                )

            try:
                physical_tags = tuple(
                    int(value) for value in parts[physical_start:physical_stop]
                )
            except ValueError as error:
                raise InvalidSectionError(
                    "Entity physical-group tags must be integers"
                ) from error
            if any(physical_tag <= 0 for physical_tag in physical_tags):
                raise InvalidSectionError(
                    "Entity physical-group tags must be positive integers"
                )

            if dimension == 0:
                if len(parts) != physical_stop:
                    raise InvalidSectionError(
                        f"Point entity {tag} contains unexpected trailing fields"
                    )
            else:
                if len(parts) < physical_stop + 1:
                    raise InvalidSectionError(
                        f"Entity {tag} is missing its boundary-entity count"
                    )
                try:
                    boundary_count = int(parts[physical_stop])
                except ValueError as error:
                    raise InvalidSectionError(
                        "Entity boundary counts must be integers"
                    ) from error
                if boundary_count < 0:
                    raise InvalidSectionError(
                        "Entity boundary counts cannot be negative"
                    )

                boundary_start = physical_stop + 1
                boundary_stop = boundary_start + boundary_count
                if len(parts) != boundary_stop:
                    available = max(0, len(parts) - boundary_start)
                    raise InvalidSectionError(
                        f"Entity {tag} declares {boundary_count} boundary tags, "
                        f"but the record contains {available}"
                    )
                try:
                    boundary_tags = tuple(
                        int(value) for value in parts[boundary_start:boundary_stop]
                    )
                except ValueError as error:
                    raise InvalidSectionError(
                        "Entity boundary tags must be integers"
                    ) from error
                if any(boundary_tag == 0 for boundary_tag in boundary_tags):
                    raise InvalidSectionError(
                        "Entity boundary tags must be non-zero signed integers"
                    )

            seen_entity_tags[dimension].add(tag)
            mesh.set_entity_physical_tags(dimension, tag, physical_tags)

    expect_end_marker(io, "$EndEntities")

gmshparser.nodes_parser.NodesParser

Bases: AbstractParser

Parse entity-block nodes from MSH 4.0 and 4.1 files.

Source code in gmshparser/nodes_parser.py
class NodesParser(AbstractParser):
    """Parse entity-block nodes from MSH 4.0 and 4.1 files."""

    @staticmethod
    def get_section_name() -> str:
        return "$Nodes"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$Nodes header")
        if line.startswith("$Nodes"):
            line = read_required_line(io, "$Nodes header")

        try:
            metadata = [int(value) for value in line.strip().split()]
        except ValueError as error:
            raise InvalidNodeError("$Nodes header must contain integers") from error

        is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
        expected_header_fields = 2 if is_v40 else 4
        if len(metadata) != expected_header_fields:
            raise InvalidNodeError(
                f"$Nodes header for MSH {'4.0' if is_v40 else '4.1'} must contain "
                f"{expected_header_fields} integers"
            )

        if is_v40:
            number_of_entities, number_of_nodes = metadata
            min_tag = max_tag = 0
        else:
            number_of_entities, number_of_nodes, min_tag, max_tag = metadata

        if number_of_entities < 0 or number_of_nodes < 0:
            raise InvalidNodeError("$Nodes counts cannot be negative")

        mesh.set_number_of_node_entities(number_of_entities)
        mesh.set_number_of_nodes(number_of_nodes)
        mesh.set_min_node_tag(min_tag)
        mesh.set_max_node_tag(max_tag)

        parsed_nodes = 0
        parsed_tags: list[int] = []
        for _ in range(number_of_entities):
            entity_metadata = parse_ints(io)
            if len(entity_metadata) != 4:
                raise InvalidNodeError(
                    "A $Nodes entity block header must contain four integers"
                )

            if is_v40:
                entity_tag, dimension, parametric, entity_node_count = entity_metadata
            else:
                dimension, entity_tag, parametric, entity_node_count = entity_metadata

            if dimension not in {0, 1, 2, 3}:
                raise InvalidNodeError(
                    f"Node entity {entity_tag} has invalid dimension {dimension}"
                )
            if parametric not in {0, 1}:
                raise InvalidNodeError(
                    f"Node entity {entity_tag} has invalid parametric flag {parametric}"
                )
            if entity_node_count < 0:
                raise InvalidNodeError("Node entity counts cannot be negative")

            expected_coordinates = 3 + (dimension if parametric else 0)
            records: list[tuple[int, tuple[float, ...]]] = []

            if is_v40:
                for _ in range(entity_node_count):
                    record = read_required_line(io, "an MSH 4.0 node record")
                    fields = record.strip().split()
                    expected_fields = 1 + expected_coordinates
                    if len(fields) != expected_fields:
                        raise InvalidNodeError(
                            f"An MSH 4.0 node record requires {expected_fields} values, "
                            f"got {len(fields)}"
                        )
                    try:
                        tag = int(fields[0])
                        coordinates = tuple(float(value) for value in fields[1:])
                    except ValueError as error:
                        raise InvalidNodeError(
                            "MSH 4.0 node tags and coordinates must be numeric"
                        ) from error
                    records.append((tag, coordinates))
                    parsed_tags.append(tag)
            else:
                node_tags: list[int] = []
                for _ in range(entity_node_count):
                    tag_line = read_required_line(io, "a node tag")
                    fields = tag_line.strip().split()
                    if len(fields) != 1:
                        raise InvalidNodeError(
                            "Each MSH 4.1 node tag must be on its own line"
                        )
                    try:
                        tag = int(fields[0])
                    except ValueError as error:
                        raise InvalidNodeError("Node tags must be integers") from error
                    node_tags.append(tag)
                    parsed_tags.append(tag)

                for tag in node_tags:
                    coordinate_values = parse_floats(io)
                    if len(coordinate_values) != expected_coordinates:
                        raise InvalidNodeError(
                            f"Node {tag} requires {expected_coordinates} coordinate values, "
                            f"got {len(coordinate_values)}"
                        )
                    records.append((tag, tuple(coordinate_values)))

            parsed_nodes += entity_node_count
            mesh.add_node_block(
                dimension,
                entity_tag,
                dimension if parametric else 0,
                records,
            )

        if parsed_nodes != number_of_nodes:
            raise InvalidNodeError(
                f"$Nodes declares {number_of_nodes} nodes, parsed {parsed_nodes}"
            )

        if is_v40:
            mesh.set_min_node_tag(min(parsed_tags, default=0))
            mesh.set_max_node_tag(max(parsed_tags, default=0))

        expect_end_marker(io, "$EndNodes")

get_section_name() staticmethod

Source code in gmshparser/nodes_parser.py
@staticmethod
def get_section_name() -> str:
    return "$Nodes"

parse(mesh, io) staticmethod

Source code in gmshparser/nodes_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$Nodes header")
    if line.startswith("$Nodes"):
        line = read_required_line(io, "$Nodes header")

    try:
        metadata = [int(value) for value in line.strip().split()]
    except ValueError as error:
        raise InvalidNodeError("$Nodes header must contain integers") from error

    is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
    expected_header_fields = 2 if is_v40 else 4
    if len(metadata) != expected_header_fields:
        raise InvalidNodeError(
            f"$Nodes header for MSH {'4.0' if is_v40 else '4.1'} must contain "
            f"{expected_header_fields} integers"
        )

    if is_v40:
        number_of_entities, number_of_nodes = metadata
        min_tag = max_tag = 0
    else:
        number_of_entities, number_of_nodes, min_tag, max_tag = metadata

    if number_of_entities < 0 or number_of_nodes < 0:
        raise InvalidNodeError("$Nodes counts cannot be negative")

    mesh.set_number_of_node_entities(number_of_entities)
    mesh.set_number_of_nodes(number_of_nodes)
    mesh.set_min_node_tag(min_tag)
    mesh.set_max_node_tag(max_tag)

    parsed_nodes = 0
    parsed_tags: list[int] = []
    for _ in range(number_of_entities):
        entity_metadata = parse_ints(io)
        if len(entity_metadata) != 4:
            raise InvalidNodeError(
                "A $Nodes entity block header must contain four integers"
            )

        if is_v40:
            entity_tag, dimension, parametric, entity_node_count = entity_metadata
        else:
            dimension, entity_tag, parametric, entity_node_count = entity_metadata

        if dimension not in {0, 1, 2, 3}:
            raise InvalidNodeError(
                f"Node entity {entity_tag} has invalid dimension {dimension}"
            )
        if parametric not in {0, 1}:
            raise InvalidNodeError(
                f"Node entity {entity_tag} has invalid parametric flag {parametric}"
            )
        if entity_node_count < 0:
            raise InvalidNodeError("Node entity counts cannot be negative")

        expected_coordinates = 3 + (dimension if parametric else 0)
        records: list[tuple[int, tuple[float, ...]]] = []

        if is_v40:
            for _ in range(entity_node_count):
                record = read_required_line(io, "an MSH 4.0 node record")
                fields = record.strip().split()
                expected_fields = 1 + expected_coordinates
                if len(fields) != expected_fields:
                    raise InvalidNodeError(
                        f"An MSH 4.0 node record requires {expected_fields} values, "
                        f"got {len(fields)}"
                    )
                try:
                    tag = int(fields[0])
                    coordinates = tuple(float(value) for value in fields[1:])
                except ValueError as error:
                    raise InvalidNodeError(
                        "MSH 4.0 node tags and coordinates must be numeric"
                    ) from error
                records.append((tag, coordinates))
                parsed_tags.append(tag)
        else:
            node_tags: list[int] = []
            for _ in range(entity_node_count):
                tag_line = read_required_line(io, "a node tag")
                fields = tag_line.strip().split()
                if len(fields) != 1:
                    raise InvalidNodeError(
                        "Each MSH 4.1 node tag must be on its own line"
                    )
                try:
                    tag = int(fields[0])
                except ValueError as error:
                    raise InvalidNodeError("Node tags must be integers") from error
                node_tags.append(tag)
                parsed_tags.append(tag)

            for tag in node_tags:
                coordinate_values = parse_floats(io)
                if len(coordinate_values) != expected_coordinates:
                    raise InvalidNodeError(
                        f"Node {tag} requires {expected_coordinates} coordinate values, "
                        f"got {len(coordinate_values)}"
                    )
                records.append((tag, tuple(coordinate_values)))

        parsed_nodes += entity_node_count
        mesh.add_node_block(
            dimension,
            entity_tag,
            dimension if parametric else 0,
            records,
        )

    if parsed_nodes != number_of_nodes:
        raise InvalidNodeError(
            f"$Nodes declares {number_of_nodes} nodes, parsed {parsed_nodes}"
        )

    if is_v40:
        mesh.set_min_node_tag(min(parsed_tags, default=0))
        mesh.set_max_node_tag(max(parsed_tags, default=0))

    expect_end_marker(io, "$EndNodes")

gmshparser.elements_parser.ElementsParser

Bases: AbstractParser

Parse entity-block elements from MSH 4.0 and 4.1 files.

Source code in gmshparser/elements_parser.py
class ElementsParser(AbstractParser):
    """Parse entity-block elements from MSH 4.0 and 4.1 files."""

    @staticmethod
    def get_section_name() -> str:
        return "$Elements"

    @staticmethod
    def parse(mesh: Mesh, io: TextIO) -> None:
        line = read_required_line(io, "$Elements header")
        if line.startswith("$Elements"):
            line = read_required_line(io, "$Elements header")

        try:
            metadata = [int(value) for value in line.strip().split()]
        except ValueError as error:
            raise InvalidElementError(
                "$Elements header must contain integers"
            ) from error

        is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
        expected_header_fields = 2 if is_v40 else 4
        if len(metadata) != expected_header_fields:
            raise InvalidElementError(
                f"$Elements header for MSH {'4.0' if is_v40 else '4.1'} must "
                f"contain {expected_header_fields} integers"
            )

        if is_v40:
            number_of_entities, number_of_elements = metadata
            min_tag = max_tag = 0
        else:
            number_of_entities, number_of_elements, min_tag, max_tag = metadata

        if number_of_entities < 0 or number_of_elements < 0:
            raise InvalidElementError("$Elements counts cannot be negative")

        mesh.set_number_of_element_entities(number_of_entities)
        mesh.set_number_of_elements(number_of_elements)
        mesh.set_min_element_tag(min_tag)
        mesh.set_max_element_tag(max_tag)

        parsed_elements = 0
        parsed_tags: list[int] = []
        for _ in range(number_of_entities):
            block_metadata = parse_ints(io)
            if len(block_metadata) != 4:
                raise InvalidElementError(
                    "An $Elements block header must contain four integers"
                )

            if is_v40:
                entity_tag, dimension, type_id, block_count = block_metadata
            else:
                dimension, entity_tag, type_id, block_count = block_metadata

            if dimension not in {0, 1, 2, 3}:
                raise InvalidElementError(
                    f"Element entity {entity_tag} has invalid dimension {dimension}"
                )
            if block_count < 0:
                raise InvalidElementError("Element block counts cannot be negative")

            element_type = ElementType(type_id)
            if element_type.is_known:
                validate_element_dimension(element_type, dimension)

            records: list[tuple[int, list[int], tuple[int, ...]]] = []
            for _ in range(block_count):
                element_info = parse_ints(io)
                if not element_info:
                    raise InvalidElementError("An element record cannot be empty")
                element_tag = element_info[0]
                node_tags = element_info[1:]
                if element_type.is_known:
                    validate_element_connectivity(
                        element_type,
                        node_tags,
                        element_tag=element_tag,
                    )
                records.append((element_tag, node_tags, ()))
                parsed_tags.append(element_tag)

            parsed_elements += block_count
            mesh.add_element_block(
                dimension,
                entity_tag,
                int(element_type),
                records,
            )

        if parsed_elements != number_of_elements:
            raise InvalidElementError(
                f"$Elements declares {number_of_elements} elements, "
                f"parsed {parsed_elements}"
            )

        if is_v40:
            mesh.set_min_element_tag(min(parsed_tags, default=0))
            mesh.set_max_element_tag(max(parsed_tags, default=0))

        expect_end_marker(io, "$EndElements")

get_section_name() staticmethod

Source code in gmshparser/elements_parser.py
@staticmethod
def get_section_name() -> str:
    return "$Elements"

parse(mesh, io) staticmethod

Source code in gmshparser/elements_parser.py
@staticmethod
def parse(mesh: Mesh, io: TextIO) -> None:
    line = read_required_line(io, "$Elements header")
    if line.startswith("$Elements"):
        line = read_required_line(io, "$Elements header")

    try:
        metadata = [int(value) for value in line.strip().split()]
    except ValueError as error:
        raise InvalidElementError(
            "$Elements header must contain integers"
        ) from error

    is_v40 = mesh.get_version_major() == 4 and mesh.get_version_minor() == 0
    expected_header_fields = 2 if is_v40 else 4
    if len(metadata) != expected_header_fields:
        raise InvalidElementError(
            f"$Elements header for MSH {'4.0' if is_v40 else '4.1'} must "
            f"contain {expected_header_fields} integers"
        )

    if is_v40:
        number_of_entities, number_of_elements = metadata
        min_tag = max_tag = 0
    else:
        number_of_entities, number_of_elements, min_tag, max_tag = metadata

    if number_of_entities < 0 or number_of_elements < 0:
        raise InvalidElementError("$Elements counts cannot be negative")

    mesh.set_number_of_element_entities(number_of_entities)
    mesh.set_number_of_elements(number_of_elements)
    mesh.set_min_element_tag(min_tag)
    mesh.set_max_element_tag(max_tag)

    parsed_elements = 0
    parsed_tags: list[int] = []
    for _ in range(number_of_entities):
        block_metadata = parse_ints(io)
        if len(block_metadata) != 4:
            raise InvalidElementError(
                "An $Elements block header must contain four integers"
            )

        if is_v40:
            entity_tag, dimension, type_id, block_count = block_metadata
        else:
            dimension, entity_tag, type_id, block_count = block_metadata

        if dimension not in {0, 1, 2, 3}:
            raise InvalidElementError(
                f"Element entity {entity_tag} has invalid dimension {dimension}"
            )
        if block_count < 0:
            raise InvalidElementError("Element block counts cannot be negative")

        element_type = ElementType(type_id)
        if element_type.is_known:
            validate_element_dimension(element_type, dimension)

        records: list[tuple[int, list[int], tuple[int, ...]]] = []
        for _ in range(block_count):
            element_info = parse_ints(io)
            if not element_info:
                raise InvalidElementError("An element record cannot be empty")
            element_tag = element_info[0]
            node_tags = element_info[1:]
            if element_type.is_known:
                validate_element_connectivity(
                    element_type,
                    node_tags,
                    element_tag=element_tag,
                )
            records.append((element_tag, node_tags, ()))
            parsed_tags.append(element_tag)

        parsed_elements += block_count
        mesh.add_element_block(
            dimension,
            entity_tag,
            int(element_type),
            records,
        )

    if parsed_elements != number_of_elements:
        raise InvalidElementError(
            f"$Elements declares {number_of_elements} elements, "
            f"parsed {parsed_elements}"
        )

    if is_v40:
        mesh.set_min_element_tag(min(parsed_tags, default=0))
        mesh.set_max_element_tag(max(parsed_tags, default=0))

    expect_end_marker(io, "$EndElements")

Version registries

The parser lists are defined in gmshparser.main_parser:

DEFAULT_PARSERS_V1
DEFAULT_PARSERS_V2
DEFAULT_PARSERS_V4

Add a section parser only to the format families whose record layout it understands. See Writing Parsers for the implementation and test checklist.