Skip to content

fix(gmsh,med): restore group metadata when converting MED to MSH#3

Open
fznoussi wants to merge 3 commits into
mainfrom
fix/med-restore-group-metadata
Open

fix(gmsh,med): restore group metadata when converting MED to MSH#3
fznoussi wants to merge 3 commits into
mainfrom
fix/med-restore-group-metadata

Conversation

@fznoussi
Copy link
Copy Markdown
Collaborator

@fznoussi fznoussi commented May 7, 2026

When converting a native GMSH-generated MED file to MSH format:

$ meshio convert mesh.med output.msh

the output contained a single undifferentiated block with no metadata:

$ meshio info output.msh

-> 1 block of 264 triangles, no groups, no physical names

The root cause was that the MED format stores element families as integer IDs mapped to group names:

cell_tags = [-6, -5, -4, -3, -2, -1, ...] # one per cell
cell_tags_mapping = {-1: ['subgroup'], -2: ['subgroup'], ...}

while the GMSH MSH writer requires a completely different metadata structure to produce a valid .msh file:

gmsh:physical # physical tag per cell
gmsh:geometrical # entity tag per cell
gmsh:dim_tags # (dim, entity_tag) per node
field_data # {"subgroup": [physical_tag, dimension]}

_convert_med_tags_to_gmsh() was introduced to bridge the two formats but its initial implementation only renamed cell_tags to gmsh:physical without splitting blocks, reconstructing entity tags, or rebuilding field_data. As a result the MSH writer had no information to populate $PhysicalNames, $Entities or $Nodes sections correctly.

Fix - _convert_med_tags_to_gmsh(): full metadata reconstruction

The function was completed in five steps.

Step 1 - Build physical tag mapping from MED family groups

family_groups = {-1: ['subgroup'], -2: ['subgroup'], ...}

Unique group names are extracted and assigned sequential physical
tags starting from 1:

group_to_phys = {'subgroup': 1}

Each family ID is then mapped to its physical tag:

fam_to_phys = {-1: 1, -2: 1, -3: 1, -4: 1, -5: 1, -6: 1}

Step 2 - Choose split criterion

Two cases are handled:

  • Round-trip (GMSH -> MED -> MSH): gmsh:geometrical is already present in cell_data -> split by gmsh:geometrical
  • Native MED (GMSH .med export -> MSH): only cell_tags exist -> split by cell_tags (MED family IDs)

Step 3 - Split blocks and generate per-cell tags

For each unique tag value within each cell block, a boolean mask
selects the matching cells. A new CellBlock is created for each
subset and assigned a sequential entity tag (entity_counter).
The family ID is translated to a physical tag via fam_to_phys.

Result for the test mesh:
before: 1 block x 264 triangles, tags mixed after: 6 blocks x 44 triangles, one per geometric entity

Step 4 - Generate gmsh:dim_tags for nodes

For each node, the topological dimension (cb.dim) and the entity
tag of the first block that references it are recorded in a
(n_points, 2) array. Nodes shared between entities are assigned
to the first entity encountered, which is consistent with the
GMSH convention.

Step 5 - Reconstruct field_data

field_data is rebuilt in the format expected by the GMSH writer:

{'subgroup': [1, 2]}   # name -> [physical_tag, dimension]

Addition - _cleanup_med_fields(): fallback for single-tag blocks

When no split is needed (only one unique tag value per block), a lightweight cleanup function handles the conversion instead:

  • Renames cell_tags to gmsh:physical if not already present
  • Removes point_tags from point_data
  • Strips MED-specific keys (med:*) and malformed field_data entries that the GMSH writer does not understand

Imports added to gmsh/main.py

import numpy as np
from .._mesh import CellBlock, Mesh

Confirmed against mesh.med generated by GMSH 4.13:

Geometry : Box with BooleanDifference sphere cutout
Groups : Physical Surface("subgroup")
Cell types: triangles (6 families) + tetra (1 family)

Before: output.msh -> 1 block, no $PhysicalNames, no $Entities
After : output.msh -> 6 blocks, $PhysicalNames contains "subgroup",
$Entities and $Nodes sections correctly populated

Fixes nschloe#1541

fznoussi added 3 commits May 7, 2026 16:28
When converting a native GMSH-generated MED file to MSH format:

  $ meshio convert mesh.med output.msh

the output contained a single undifferentiated block with no metadata:

  $ meshio info output.msh
  # -> 1 block of 264 triangles, no groups, no physical names

The root cause was that the MED format stores element families as
integer IDs mapped to group names:

  cell_tags         = [-6, -5, -4, -3, -2, -1, ...]   # one per cell
  cell_tags_mapping = {-1: ['subgroup'], -2: ['subgroup'], ...}

while the GMSH MSH writer requires a completely different metadata
structure to produce a valid .msh file:

  gmsh:physical     # physical tag per cell
  gmsh:geometrical  # entity tag per cell
  gmsh:dim_tags     # (dim, entity_tag) per node
  field_data        # {"subgroup": [physical_tag, dimension]}

_convert_med_tags_to_gmsh() was introduced to bridge the two formats
but its initial implementation only renamed cell_tags to gmsh:physical
without splitting blocks, reconstructing entity tags, or rebuilding
field_data. As a result the MSH writer had no information to populate
$PhysicalNames, $Entities or $Nodes sections correctly.

Fix - _convert_med_tags_to_gmsh(): full metadata reconstruction

The function was completed in five steps.

Step 1 - Build physical tag mapping from MED family groups

  family_groups = {-1: ['subgroup'], -2: ['subgroup'], ...}

  Unique group names are extracted and assigned sequential physical
  tags starting from 1:

    group_to_phys = {'subgroup': 1}

  Each family ID is then mapped to its physical tag:

    fam_to_phys = {-1: 1, -2: 1, -3: 1, -4: 1, -5: 1, -6: 1}

Step 2 - Choose split criterion

  Two cases are handled:

  - Round-trip (GMSH -> MED -> MSH): gmsh:geometrical is already
    present in cell_data -> split by gmsh:geometrical
  - Native MED (GMSH .med export -> MSH): only cell_tags exist
    -> split by cell_tags (MED family IDs)

Step 3 - Split blocks and generate per-cell tags

  For each unique tag value within each cell block, a boolean mask
  selects the matching cells. A new CellBlock is created for each
  subset and assigned a sequential entity tag (entity_counter).
  The family ID is translated to a physical tag via fam_to_phys.

  Result for the test mesh:
    before: 1 block x 264 triangles, tags mixed
    after:  6 blocks x 44 triangles, one per geometric entity

Step 4 - Generate gmsh:dim_tags for nodes

  For each node, the topological dimension (cb.dim) and the entity
  tag of the first block that references it are recorded in a
  (n_points, 2) array. Nodes shared between entities are assigned
  to the first entity encountered, which is consistent with the
  GMSH convention.

Step 5 - Reconstruct field_data

  field_data is rebuilt in the format expected by the GMSH writer:

    {'subgroup': [1, 2]}   # name -> [physical_tag, dimension]

Addition - _cleanup_med_fields(): fallback for single-tag blocks

When no split is needed (only one unique tag value per block), a
lightweight cleanup function handles the conversion instead:

  - Renames cell_tags to gmsh:physical if not already present
  - Removes point_tags from point_data
  - Strips MED-specific keys (med:*) and malformed field_data
    entries that the GMSH writer does not understand

Imports added to gmsh/main.py

  import numpy as np
  from .._mesh import CellBlock, Mesh

Confirmed against mesh.med generated by GMSH 4.13:

  Geometry  : Box with BooleanDifference sphere cutout
  Groups    : Physical Surface("subgroup")
  Cell types: triangles (6 families) + tetra (1 family)

  Before: output.msh -> 1 block, no $PhysicalNames, no $Entities
  After : output.msh -> 6 blocks, $PhysicalNames contains "subgroup",
          $Entities and $Nodes sections correctly populated

Fixes nschloe#1541
…lit into separate blocks per family group

- Verify gmsh:geometrical tags are distinct per geometric group
- Verify gmsh:physical tags are distinct per physical group
- Verify gmsh:dim_tags is present in point_data with correct shape (n_points, 2)
- Verify field_data is reconstructed with original group names (group_a, group_b)
- Fix missing points/cells args in Mesh instantiation (required positional args)
- Add import numpy as np missing in test_helpers.py
- Cas 1: mesh with cell_tags (MED) should select gmsh format
- Cas 2: mesh with gmsh:physical should select gmsh format
- Cas 3: bare mesh should fallback to default format (ansys)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Unable to convert between .msh and .med or read info from .med ("MED files cannot have two sections of the same cell type")

1 participant