fix(gmsh,med): restore group metadata when converting MED to MSH#3
Open
fznoussi wants to merge 3 commits into
Open
fix(gmsh,med): restore group metadata when converting MED to MSH#3fznoussi wants to merge 3 commits into
fznoussi wants to merge 3 commits into
Conversation
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Each family ID is then mapped to its physical tag:
Step 2 - Choose split criterion
Two cases are handled:
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:
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:
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