fix(med,gmsh): convert multi-block GMSH meshes to MED without errors#2
Open
fix(med,gmsh): convert multi-block GMSH meshes to MED without errors#2
Conversation
GMSH and other meshers naturally split elements of the same type across
multiple blocks, one per geometric entity, and do not always assign a
Physical Entity to every geometric entity (e.g. surfaces created by a
BooleanDifference operation). These two facts combined made it impossible
to convert a GMSH .msh file to .med using meshio:
$ meshio convert mesh.msh mesh.med
The conversion pipeline goes through two stages, each of which had its
own bug:
mesh.msh
│
▼
_gmsh41.py (read) ← Fix A: entities without physical tags
│
▼
mesh object in memory
│
▼
_med.py (write) ← Fix B + C: multi-block same-type cells
Fix A — _gmsh41.py: missing physical tag on some entities
GMSH .msh files generated with BooleanDifference + Delete contain
geometric entities that have no associated Physical Entity. The newly
created surfaces/volumes do not inherit the Physical tags of the
original entities that were destroyed.
The reader was accessing entity_tags[dim][entity_tag] unconditionally,
causing a KeyError when an entity had no physical tag:
KeyError: <entity_tag>
Fixed by replacing direct dict access with .get() and recording None
when no physical tag is found, instead of crashing:
# BEFORE
physical_tag = entity_tags[dim][entity_tag]
# AFTER
physical_tag = entity_tags[dim].get(entity_tag, None)
Without this fix the .msh read fails before the MED writer is even
reached, making the rest of the fixes irrelevant.
Fix B — _med.py: remove pre-flight guard that rejected valid meshes
A pre-check was unconditionally rejecting any mesh where the
same cell type appeared in more than one block:
WriteError: MED files cannot have two sections of the same cell type.
This made every standard GMSH mesh impossible to export to MED since
GMSH always produces one block per geometric entity:
mesh.cells = [
CellBlock(triangle, [...]), # surface 1
CellBlock(triangle, [...]), # surface 2
CellBlock(triangle, [...]), # surface 3
CellBlock(tetra, [...]), # volume 1
]
The check was removed entirely. The correct approach is to merge
duplicate blocks rather than reject them.
Fix C : _med.py: merge cell blocks by type before writing (MAI)
After removing the pre-check guard, the underlying HDF5 write was
crashing when trying to create MAI/TR3/ a second time:
ValueError: Unable to synchronously create group (name already exists)
The MED format only allows one HDF5 group per element type under MAI/.
The writer was creating one group per block:
MAI/
├── TR3/ ← block 1 ok!
├── TR3/ ← block 2 crashes! name already exists
└── TE4/ ← block 1 ok!
Fixed by building two dicts in a single pass over mesh.cells:
cells_by_type[cell_type] = [block.data, ...]
cell_tags_by_type[cell_type] = [tags, ...]
Both dicts are populated in the same loop and in the same order to keep
cell connectivity and their associated family tags aligned. Then for each
unique type, np.concatenate merges the blocks into a single array and
only one HDF5 group is created:
MAI/
├── TR3/ ← np.concatenate([block1, block2, block3])
└── TE4/ ← np.concatenate([block1])
Fix D : _med.py: merge cell_data fields by type before writing (CHA)
The same duplication occurred in _write_data() when writing
cell-centered fields. Each call was trying to create MAI.TR3 inside
the CHA group once per block. Applied the same pattern: group by type,
concatenate, write once. Also added gmsh:physical to the skip list
alongside cell_tags since it is a GMSH-specific tag that has no MED
equivalent and would otherwise cause a spurious field to be written.
Fixes nschloe#1541
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.
GMSH and other meshers naturally split elements of the same type across multiple blocks, one per geometric entity, and do not always assign a Physical Entity to every geometric entity (e.g. surfaces created by a BooleanDifference operation). These two facts combined made it impossible to convert a GMSH .msh file to .med using meshio:
$ meshio convert mesh.msh mesh.med
The conversion pipeline goes through two stages, each of which had its own bug:
mesh.msh
│
▼
_gmsh41.py (read) ← Fix A: entities without physical tags
│
▼
mesh object in memory
│
▼
_med.py (write) ← Fix B + C: multi-block same-type cells
Fix A : _gmsh41.py: missing physical tag on some entities
GMSH .msh files generated with BooleanDifference + Delete contain geometric entities that have no associated Physical Entity. The newly created surfaces/volumes do not inherit the Physical tags of the original entities that were destroyed.
The reader was accessing entity_tags[dim][entity_tag] unconditionally, causing a KeyError when an entity had no physical tag:
KeyError: <entity_tag>
Fixed by replacing direct dict access with .get() and recording None when no physical tag is found, instead of crashing:
BEFORE
physical_tag = entity_tags[dim][entity_tag]
AFTER
physical_tag = entity_tags[dim].get(entity_tag, None)
Without this fix the .msh read fails before the MED writer is even reached, making the rest of the fixes irrelevant.
Fix B : _med.py: remove pre-flight guard that rejected valid meshes
A pre-check was unconditionally rejecting any mesh where the same cell type appeared in more than one block:
WriteError: MED files cannot have two sections of the same cell type.
This made every standard GMSH mesh impossible to export to MED since GMSH always produces one block per geometric entity:
mesh.cells = [
CellBlock(triangle, [...]), # surface 1
CellBlock(triangle, [...]), # surface 2
CellBlock(triangle, [...]), # surface 3
CellBlock(tetra, [...]), # volume 1
]
The check was removed entirely. The correct approach is to merge duplicate blocks rather than reject them.
Fix C : _med.py: merge cell blocks by type before writing (MAI)
After removing the pre-check guard, the underlying HDF5 write was crashing when trying to create MAI/TR3/ a second time:
ValueError: Unable to synchronously create group (name already exists)
The MED format only allows one HDF5 group per element type under MAI/. The writer was creating one group per block:
MAI/
├── TR3/ ← block 1 ok!
├── TR3/ ← block 2 crashes! name already exists
└── TE4/ ← block 1 ok!
Fixed by building two dicts in a single pass over mesh.cells:
cells_by_type[cell_type] = [block.data, ...]
cell_tags_by_type[cell_type] = [tags, ...]
Both dicts are populated in the same loop and in the same order to keep cell connectivity and their associated family tags aligned. Then for each unique type, np.concatenate merges the blocks into a single array and only one HDF5 group is created:
MAI/
├── TR3/ ← np.concatenate([block1, block2, block3])
└── TE4/ ← np.concatenate([block1])
Fix D : _med.py: merge cell_data fields by type before writing (CHA)
The same duplication occurred in _write_data() when writing cell-centered fields. Each call was trying to create MAI.TR3 inside the CHA group once per block. Applied the same pattern: group by type, concatenate, write once. Also added gmsh:physical to the skip list alongside cell_tags since it is a GMSH-specific tag that has no MED equivalent and would otherwise cause a spurious field to be written.
Fixes nschloe#1541