med2limit 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- med2limit-0.0.1/LICENSE +21 -0
- med2limit-0.0.1/PKG-INFO +134 -0
- med2limit-0.0.1/README.md +119 -0
- med2limit-0.0.1/med2limit/__init__.py +7 -0
- med2limit-0.0.1/med2limit/cli.py +106 -0
- med2limit-0.0.1/med2limit/converter.py +127 -0
- med2limit-0.0.1/med2limit/element_types.py +80 -0
- med2limit-0.0.1/med2limit/fields.py +108 -0
- med2limit-0.0.1/med2limit/filter.py +144 -0
- med2limit-0.0.1/med2limit/mesh.py +202 -0
- med2limit-0.0.1/med2limit/orientation.py +122 -0
- med2limit-0.0.1/med2limit/reader.py +52 -0
- med2limit-0.0.1/med2limit/result_mapper.py +132 -0
- med2limit-0.0.1/med2limit/writer.py +246 -0
- med2limit-0.0.1/med2limit.egg-info/PKG-INFO +134 -0
- med2limit-0.0.1/med2limit.egg-info/SOURCES.txt +21 -0
- med2limit-0.0.1/med2limit.egg-info/dependency_links.txt +1 -0
- med2limit-0.0.1/med2limit.egg-info/entry_points.txt +2 -0
- med2limit-0.0.1/med2limit.egg-info/requires.txt +4 -0
- med2limit-0.0.1/med2limit.egg-info/top_level.txt +1 -0
- med2limit-0.0.1/pyproject.toml +36 -0
- med2limit-0.0.1/setup.cfg +4 -0
- med2limit-0.0.1/tests/test_element_types.py +79 -0
med2limit-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 simvia-tech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
med2limit-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: med2limit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Convert Code_Aster MED/RMED simulation files to LIMIT .linp / .lui input format
|
|
5
|
+
Author-email: Dorian Nezzar <dorian.nezzar@simvia.tech>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: med,rmed,code_aster,limit,fea,fatigue
|
|
8
|
+
Requires-Python: <=3.13,>=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: medcoupling>=9.15
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
<p align="center"><img src="logo/logo_med_coupling.png" alt="logo" width="50%"></p>
|
|
17
|
+
|
|
18
|
+
[](https://opensource.org/licenses/MIT)
|
|
19
|
+
|
|
20
|
+
# med2limit
|
|
21
|
+
Convert Code_Aster MED/RMED simulation results into LIMIT `.linp` / `.lui` input files for fatigue analysis.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- Shell workflows (DKT elements: S3, S4) with REPLO/CARCOQUE handling
|
|
26
|
+
- Linear solid workflows (C3D8 / HEXA8, C3D6 / PENTA6) with validated LIMIT node ordering
|
|
27
|
+
- Multi-step / multi-increment displacement and stress transfer
|
|
28
|
+
- Optional shell orientation file, or read directly from `IMPR_CONCEPT`-embedded result file
|
|
29
|
+
- Automatic detection of shell support level (works for shell-only and mixed hexa+shell models)
|
|
30
|
+
|
|
31
|
+
## Code_aster Requirement
|
|
32
|
+
- Identify weld groups as Group_NO (not Group_MA), 1 node set per weld
|
|
33
|
+
- For shell element, extract top/bottom stresses as:
|
|
34
|
+
```bash
|
|
35
|
+
SIEF_SUP=POST_CHAMP(RESULTAT=RESU,
|
|
36
|
+
EXTR_COQUE=_F(NOM_CHAM='SIEF_ELNO',
|
|
37
|
+
NUME_COUCHE=1,
|
|
38
|
+
NIVE_COUCHE='SUP',),);
|
|
39
|
+
|
|
40
|
+
SIEF_INF=POST_CHAMP(RESULTAT=RESU,
|
|
41
|
+
EXTR_COQUE=_F(NOM_CHAM='SIEF_ELNO',
|
|
42
|
+
NUME_COUCHE=1,
|
|
43
|
+
NIVE_COUCHE='INF',),);
|
|
44
|
+
```
|
|
45
|
+
- For shell element, extract orientation/tichkness as:
|
|
46
|
+
```bash
|
|
47
|
+
IMPR_CONCEPT(FORMAT='MED',
|
|
48
|
+
UNITE=80, --> Same unit as your results or in a dedicated file
|
|
49
|
+
CONCEPT=(_F(CARA_ELEM=Elem,
|
|
50
|
+
REPERE_LOCAL='ELEM',
|
|
51
|
+
MODELE=Modell,),),)
|
|
52
|
+
```
|
|
53
|
+
## Installation
|
|
54
|
+
Install med2limit with pip into a virtual python environnement (venv):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python3 -m venv .venv
|
|
58
|
+
source .venv/bin/activate
|
|
59
|
+
pip install git+https://github.com/simvia-tech/med2limit.git@dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Command line
|
|
66
|
+
From 01_exemple in folder:
|
|
67
|
+
```bash
|
|
68
|
+
med2limit/exemples/data
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
med2limit 01_exemple.rmed output.linp output.lui --groups "Shell1,Shell2" --nsets "WeldNo"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
With separate orientation file:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
med2limit 01_exemple.rmed output.linp output.lui 01_carcoc.rmed --groups "Shell1,Shell2" --nsets "WeldNo"
|
|
79
|
+
```
|
|
80
|
+
# 01_exemple
|
|
81
|
+
<img src="./examples/images/01_exemple_LIMIT.png" width="50%">
|
|
82
|
+
Code_aster Shell-Shell geometry successfully imported in LIMIT Software
|
|
83
|
+
|
|
84
|
+
# 02_exemple
|
|
85
|
+
<img src="./examples/images/02_exemple_LIMIT.png" width="50%">
|
|
86
|
+
Code_aster Solid-Shell geometry successfully imported in LIMIT Software
|
|
87
|
+
|
|
88
|
+
### Python API
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from med2limit import MEDToLimitConverter
|
|
92
|
+
|
|
93
|
+
conv = MEDToLimitConverter(
|
|
94
|
+
med_filename="LIMIT1.rmed",
|
|
95
|
+
linp_filename="out.linp",
|
|
96
|
+
lui_filename="out.lui",
|
|
97
|
+
active_groups=["Shell1", "Shell2"],
|
|
98
|
+
active_nsets=["Weld"],
|
|
99
|
+
)
|
|
100
|
+
conv.convert()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Package layout
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
med2limit/
|
|
107
|
+
├── element_types.py # MED↔LIMIT type mapping + helpers (pure)
|
|
108
|
+
├── reader.py # MED file open + field lookup
|
|
109
|
+
├── mesh.py # nodes, elements, GROUP_MA, GROUP_NO
|
|
110
|
+
├── fields.py # DEPL + SIEF over all timesteps
|
|
111
|
+
├── orientation.py # REPLO + CARCOQUE (embedded or separate)
|
|
112
|
+
├── filter.py # active group selection + shell metadata mapping
|
|
113
|
+
├── result_mapper.py # per-timestep stress/displacement mapping
|
|
114
|
+
├── writer.py # .linp + .lui output
|
|
115
|
+
├── converter.py # orchestrator (step_1 .. step_6 + convert)
|
|
116
|
+
└── cli.py # CLI + in-script config
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Testing
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pytest # all tests
|
|
123
|
+
pytest tests/test_element_types.py # one module
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Known limitations
|
|
127
|
+
|
|
128
|
+
- Quadratic solids (C3D10, C3D15, C3D20) — node ordering not yet validated in LIMIT
|
|
129
|
+
- Shell elsets with mixed thicknesses use the most-frequent value (with warning)
|
|
130
|
+
|
|
131
|
+
## Acknowledgments
|
|
132
|
+
|
|
133
|
+
Special thanks to Tobias and Nikolaus for their feedback as early adopters
|
|
134
|
+
and their patience during the iterative development of the converter.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<p align="center"><img src="logo/logo_med_coupling.png" alt="logo" width="50%"></p>
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
|
|
5
|
+
# med2limit
|
|
6
|
+
Convert Code_Aster MED/RMED simulation results into LIMIT `.linp` / `.lui` input files for fatigue analysis.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Shell workflows (DKT elements: S3, S4) with REPLO/CARCOQUE handling
|
|
11
|
+
- Linear solid workflows (C3D8 / HEXA8, C3D6 / PENTA6) with validated LIMIT node ordering
|
|
12
|
+
- Multi-step / multi-increment displacement and stress transfer
|
|
13
|
+
- Optional shell orientation file, or read directly from `IMPR_CONCEPT`-embedded result file
|
|
14
|
+
- Automatic detection of shell support level (works for shell-only and mixed hexa+shell models)
|
|
15
|
+
|
|
16
|
+
## Code_aster Requirement
|
|
17
|
+
- Identify weld groups as Group_NO (not Group_MA), 1 node set per weld
|
|
18
|
+
- For shell element, extract top/bottom stresses as:
|
|
19
|
+
```bash
|
|
20
|
+
SIEF_SUP=POST_CHAMP(RESULTAT=RESU,
|
|
21
|
+
EXTR_COQUE=_F(NOM_CHAM='SIEF_ELNO',
|
|
22
|
+
NUME_COUCHE=1,
|
|
23
|
+
NIVE_COUCHE='SUP',),);
|
|
24
|
+
|
|
25
|
+
SIEF_INF=POST_CHAMP(RESULTAT=RESU,
|
|
26
|
+
EXTR_COQUE=_F(NOM_CHAM='SIEF_ELNO',
|
|
27
|
+
NUME_COUCHE=1,
|
|
28
|
+
NIVE_COUCHE='INF',),);
|
|
29
|
+
```
|
|
30
|
+
- For shell element, extract orientation/tichkness as:
|
|
31
|
+
```bash
|
|
32
|
+
IMPR_CONCEPT(FORMAT='MED',
|
|
33
|
+
UNITE=80, --> Same unit as your results or in a dedicated file
|
|
34
|
+
CONCEPT=(_F(CARA_ELEM=Elem,
|
|
35
|
+
REPERE_LOCAL='ELEM',
|
|
36
|
+
MODELE=Modell,),),)
|
|
37
|
+
```
|
|
38
|
+
## Installation
|
|
39
|
+
Install med2limit with pip into a virtual python environnement (venv):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python3 -m venv .venv
|
|
43
|
+
source .venv/bin/activate
|
|
44
|
+
pip install git+https://github.com/simvia-tech/med2limit.git@dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Command line
|
|
51
|
+
From 01_exemple in folder:
|
|
52
|
+
```bash
|
|
53
|
+
med2limit/exemples/data
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
med2limit 01_exemple.rmed output.linp output.lui --groups "Shell1,Shell2" --nsets "WeldNo"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With separate orientation file:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
med2limit 01_exemple.rmed output.linp output.lui 01_carcoc.rmed --groups "Shell1,Shell2" --nsets "WeldNo"
|
|
64
|
+
```
|
|
65
|
+
# 01_exemple
|
|
66
|
+
<img src="./examples/images/01_exemple_LIMIT.png" width="50%">
|
|
67
|
+
Code_aster Shell-Shell geometry successfully imported in LIMIT Software
|
|
68
|
+
|
|
69
|
+
# 02_exemple
|
|
70
|
+
<img src="./examples/images/02_exemple_LIMIT.png" width="50%">
|
|
71
|
+
Code_aster Solid-Shell geometry successfully imported in LIMIT Software
|
|
72
|
+
|
|
73
|
+
### Python API
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from med2limit import MEDToLimitConverter
|
|
77
|
+
|
|
78
|
+
conv = MEDToLimitConverter(
|
|
79
|
+
med_filename="LIMIT1.rmed",
|
|
80
|
+
linp_filename="out.linp",
|
|
81
|
+
lui_filename="out.lui",
|
|
82
|
+
active_groups=["Shell1", "Shell2"],
|
|
83
|
+
active_nsets=["Weld"],
|
|
84
|
+
)
|
|
85
|
+
conv.convert()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Package layout
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
med2limit/
|
|
92
|
+
├── element_types.py # MED↔LIMIT type mapping + helpers (pure)
|
|
93
|
+
├── reader.py # MED file open + field lookup
|
|
94
|
+
├── mesh.py # nodes, elements, GROUP_MA, GROUP_NO
|
|
95
|
+
├── fields.py # DEPL + SIEF over all timesteps
|
|
96
|
+
├── orientation.py # REPLO + CARCOQUE (embedded or separate)
|
|
97
|
+
├── filter.py # active group selection + shell metadata mapping
|
|
98
|
+
├── result_mapper.py # per-timestep stress/displacement mapping
|
|
99
|
+
├── writer.py # .linp + .lui output
|
|
100
|
+
├── converter.py # orchestrator (step_1 .. step_6 + convert)
|
|
101
|
+
└── cli.py # CLI + in-script config
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Testing
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pytest # all tests
|
|
108
|
+
pytest tests/test_element_types.py # one module
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Known limitations
|
|
112
|
+
|
|
113
|
+
- Quadratic solids (C3D10, C3D15, C3D20) — node ordering not yet validated in LIMIT
|
|
114
|
+
- Shell elsets with mixed thicknesses use the most-frequent value (with warning)
|
|
115
|
+
|
|
116
|
+
## Acknowledgments
|
|
117
|
+
|
|
118
|
+
Special thanks to Tobias and Nikolaus for their feedback as early adopters
|
|
119
|
+
and their patience during the iterative development of the converter.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line entry point.
|
|
3
|
+
|
|
4
|
+
Two modes:
|
|
5
|
+
- in-script configuration: edit the constants below for Salome / IDE direct runs
|
|
6
|
+
- CLI: standard argparse mode
|
|
7
|
+
|
|
8
|
+
This module does NOT call sys.exit() so it stays Salome-safe.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
from .converter import MEDToLimitConverter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
VERSION = "0.1.0"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# In-script configuration (edit for Salome / direct IDE execution)
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
INPUT_MED = ""
|
|
25
|
+
OUTPUT_LINP = ""
|
|
26
|
+
OUTPUT_LUI = ""
|
|
27
|
+
ORIENTATION_MED = None
|
|
28
|
+
ACTIVE_GROUPS = []
|
|
29
|
+
ACTIVE_NSETS = []
|
|
30
|
+
|
|
31
|
+
USE_IN_SCRIPT_CONFIGURATION = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_name_list(text_value: str):
|
|
35
|
+
if not text_value:
|
|
36
|
+
return []
|
|
37
|
+
return [item.strip() for item in text_value.split(",") if item.strip()]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _from_in_script_config():
|
|
41
|
+
return (
|
|
42
|
+
INPUT_MED,
|
|
43
|
+
OUTPUT_LINP or os.path.splitext(INPUT_MED)[0] + ".linp",
|
|
44
|
+
OUTPUT_LUI or os.path.splitext(INPUT_MED)[0] + ".lui",
|
|
45
|
+
ORIENTATION_MED,
|
|
46
|
+
list(ACTIVE_GROUPS),
|
|
47
|
+
list(ACTIVE_NSETS),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _from_cli():
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
description=f"MED/RMED to LIMIT converter ({VERSION})"
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument("input_med")
|
|
56
|
+
parser.add_argument("output_linp", nargs="?")
|
|
57
|
+
parser.add_argument("output_lui", nargs="?")
|
|
58
|
+
parser.add_argument("orientation_med", nargs="?", default=None)
|
|
59
|
+
parser.add_argument("--groups", default="")
|
|
60
|
+
parser.add_argument("--nsets", default="")
|
|
61
|
+
args, _unknown = parser.parse_known_args()
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
args.input_med,
|
|
65
|
+
args.output_linp or os.path.splitext(args.input_med)[0] + ".linp",
|
|
66
|
+
args.output_lui or os.path.splitext(args.input_med)[0] + ".lui",
|
|
67
|
+
args.orientation_med,
|
|
68
|
+
_parse_name_list(args.groups),
|
|
69
|
+
_parse_name_list(args.nsets),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
if USE_IN_SCRIPT_CONFIGURATION and INPUT_MED:
|
|
75
|
+
(input_file, out_linp, out_lui, orient, groups, nsets) = _from_in_script_config()
|
|
76
|
+
print("Running in in-script configuration mode")
|
|
77
|
+
else:
|
|
78
|
+
(input_file, out_linp, out_lui, orient, groups, nsets) = _from_cli()
|
|
79
|
+
|
|
80
|
+
print(f" input : {input_file}")
|
|
81
|
+
print(f" output_linp : {out_linp}")
|
|
82
|
+
print(f" output_lui : {out_lui}")
|
|
83
|
+
print(f" orientation_med: {orient}")
|
|
84
|
+
print(f" groups : {groups}")
|
|
85
|
+
print(f" nsets : {nsets}")
|
|
86
|
+
|
|
87
|
+
if not os.path.exists(input_file):
|
|
88
|
+
print(f"ERROR: Input file not found: {input_file}")
|
|
89
|
+
return 1
|
|
90
|
+
if orient and not os.path.exists(orient):
|
|
91
|
+
print(f"ERROR: Orientation file not found: {orient}")
|
|
92
|
+
return 1
|
|
93
|
+
|
|
94
|
+
converter = MEDToLimitConverter(
|
|
95
|
+
med_filename=input_file,
|
|
96
|
+
linp_filename=out_linp,
|
|
97
|
+
lui_filename=out_lui,
|
|
98
|
+
orientation_med_filename=orient,
|
|
99
|
+
active_groups=groups,
|
|
100
|
+
active_nsets=nsets,
|
|
101
|
+
)
|
|
102
|
+
return 0 if converter.convert() else 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
main()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Top-level orchestrator. Public step methods allow debugging in a notebook.
|
|
3
|
+
|
|
4
|
+
Typical use:
|
|
5
|
+
converter = MEDToLimitConverter("model.rmed", "out.linp", "out.lui",
|
|
6
|
+
active_groups=["Shell1", "Shell2"])
|
|
7
|
+
converter.convert()
|
|
8
|
+
|
|
9
|
+
Step-by-step debug:
|
|
10
|
+
converter.step_1_load()
|
|
11
|
+
converter.step_2_extract_mesh()
|
|
12
|
+
print(converter.mesh.all_nodes) # inspect
|
|
13
|
+
converter.step_3_extract_fields()
|
|
14
|
+
...
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .reader import MedFileReader
|
|
18
|
+
from .mesh import MeshExtractor
|
|
19
|
+
from .fields import FieldExtractor
|
|
20
|
+
from .orientation import ShellMetadata
|
|
21
|
+
from .filter import ActiveFilter
|
|
22
|
+
from .writer import LinpWriter, LuiWriter
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MEDToLimitConverter:
|
|
26
|
+
"""Orchestrate the MED → LIMIT conversion pipeline."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, med_filename, linp_filename, lui_filename,
|
|
29
|
+
orientation_med_filename=None,
|
|
30
|
+
active_groups=None, active_nsets=None):
|
|
31
|
+
self.med_filename = med_filename
|
|
32
|
+
self.linp_filename = linp_filename
|
|
33
|
+
self.lui_filename = lui_filename
|
|
34
|
+
self.orientation_med_filename = orientation_med_filename
|
|
35
|
+
self.active_groups = list(active_groups or [])
|
|
36
|
+
self.active_nsets = list(active_nsets or [])
|
|
37
|
+
|
|
38
|
+
# Filled at runtime
|
|
39
|
+
self.reader = None
|
|
40
|
+
self.mesh = None
|
|
41
|
+
self.fields = None
|
|
42
|
+
self.shell_meta = None
|
|
43
|
+
self.filter = None
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------------------- steps
|
|
46
|
+
|
|
47
|
+
def step_1_load(self):
|
|
48
|
+
"""Open the main MED/RMED file."""
|
|
49
|
+
print(f"Loading MED file: {self.med_filename}")
|
|
50
|
+
self.reader = MedFileReader(self.med_filename)
|
|
51
|
+
print(f" Found {len(self.reader.meshes)} mesh(es)")
|
|
52
|
+
print(f" Found {len(self.reader.fields)} field(s)")
|
|
53
|
+
for f in self.reader.fields:
|
|
54
|
+
print(f" - {f.getName()}")
|
|
55
|
+
|
|
56
|
+
def step_2_extract_mesh(self):
|
|
57
|
+
"""Extract nodes, elements, element groups and node groups."""
|
|
58
|
+
print("\nExtracting mesh...")
|
|
59
|
+
self.mesh = MeshExtractor(
|
|
60
|
+
self.reader.meshes, self.active_groups, self.active_nsets
|
|
61
|
+
)
|
|
62
|
+
self.mesh.extract_all()
|
|
63
|
+
print(f" Total nodes: {len(self.mesh.all_nodes)}")
|
|
64
|
+
print(f" Total elements: {len(self.mesh.all_elements)}")
|
|
65
|
+
print(f" Element sets: {list(self.mesh.element_sets.keys())}")
|
|
66
|
+
print(f" Node sets: {list(self.mesh.node_sets.keys())}")
|
|
67
|
+
|
|
68
|
+
def step_3_extract_fields(self):
|
|
69
|
+
"""Extract DEPL and SIEF fields for all time steps."""
|
|
70
|
+
print("\nExtracting fields...")
|
|
71
|
+
self.fields = FieldExtractor(self.reader)
|
|
72
|
+
self.fields.extract()
|
|
73
|
+
print(f" Stress mode: {self.fields.stress_mode}")
|
|
74
|
+
print(f" Time steps: {self.fields.n_timesteps}")
|
|
75
|
+
|
|
76
|
+
def step_4_load_shell_metadata(self):
|
|
77
|
+
"""Load REPLO/CARCOQUE: embedded first, then separate file as fallback."""
|
|
78
|
+
print("\nLoading shell metadata (REPLO, CARCOQUE)...")
|
|
79
|
+
self.shell_meta = ShellMetadata()
|
|
80
|
+
loaded = self.shell_meta.load(self.reader, self.orientation_med_filename)
|
|
81
|
+
if loaded:
|
|
82
|
+
print(f" Loaded from: {self.shell_meta.source}")
|
|
83
|
+
if self.shell_meta.replo1 is not None:
|
|
84
|
+
print(f" REPLO_1: {len(self.shell_meta.replo1)} entries")
|
|
85
|
+
if self.shell_meta.carcoque_ep is not None:
|
|
86
|
+
uniq = sorted({round(float(v), 3) for v in self.shell_meta.carcoque_ep})
|
|
87
|
+
print(f" CARCOQUE EP unique values: {uniq}")
|
|
88
|
+
else:
|
|
89
|
+
print(" No shell metadata available (using defaults)")
|
|
90
|
+
|
|
91
|
+
def step_5_filter(self):
|
|
92
|
+
"""Reduce model to active groups and map shell metadata."""
|
|
93
|
+
print("\nFiltering active data...")
|
|
94
|
+
self.filter = ActiveFilter(
|
|
95
|
+
self.mesh, self.shell_meta,
|
|
96
|
+
requested_groups=self.active_groups,
|
|
97
|
+
requested_nsets=self.active_nsets,
|
|
98
|
+
)
|
|
99
|
+
self.filter.apply()
|
|
100
|
+
print(f" Active elements: {len(self.filter.active_elem_ids)}")
|
|
101
|
+
print(f" Active nodes: {len(self.filter.active_node_ids)}")
|
|
102
|
+
|
|
103
|
+
def step_6_write(self):
|
|
104
|
+
"""Write the .linp and .lui output files."""
|
|
105
|
+
LinpWriter(self.mesh, self.filter, self.med_filename).write(self.linp_filename)
|
|
106
|
+
LuiWriter(self.mesh, self.filter, self.fields, self.med_filename).write(self.lui_filename)
|
|
107
|
+
|
|
108
|
+
# --------------------------------------------------------------- full
|
|
109
|
+
|
|
110
|
+
def convert(self):
|
|
111
|
+
"""Run the complete pipeline."""
|
|
112
|
+
try:
|
|
113
|
+
self.step_1_load()
|
|
114
|
+
self.step_2_extract_mesh()
|
|
115
|
+
self.step_3_extract_fields()
|
|
116
|
+
self.step_4_load_shell_metadata()
|
|
117
|
+
self.step_5_filter()
|
|
118
|
+
self.step_6_write()
|
|
119
|
+
print("\n" + "=" * 60)
|
|
120
|
+
print("Translation complete")
|
|
121
|
+
print("=" * 60)
|
|
122
|
+
return True
|
|
123
|
+
except Exception as e:
|
|
124
|
+
import traceback
|
|
125
|
+
print(f"\nERROR: Conversion failed: {e}")
|
|
126
|
+
print(traceback.format_exc())
|
|
127
|
+
return False
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MED ↔ LIMIT element type mapping and classification helpers.
|
|
3
|
+
|
|
4
|
+
This module is pure: no I/O, no MEDCoupling reads. Only mapping tables and
|
|
5
|
+
small functions. Easy to test in isolation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import medcoupling as mc
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Mapping from MEDCoupling geometric type codes to LIMIT/Abaqus element type names.
|
|
12
|
+
MED_TO_LIMIT = {
|
|
13
|
+
# Solids
|
|
14
|
+
mc.NORM_HEXA8: "C3D8",
|
|
15
|
+
mc.NORM_HEXA20: "C3D20",
|
|
16
|
+
mc.NORM_TETRA4: "C3D4",
|
|
17
|
+
mc.NORM_TETRA10: "C3D10",
|
|
18
|
+
mc.NORM_PENTA6: "C3D6",
|
|
19
|
+
mc.NORM_PENTA15: "C3D15",
|
|
20
|
+
# Shells / 2D
|
|
21
|
+
mc.NORM_TRI3: "S3",
|
|
22
|
+
mc.NORM_QUAD4: "S4",
|
|
23
|
+
mc.NORM_TRI6: "STRI65",
|
|
24
|
+
mc.NORM_QUAD8: "S8R",
|
|
25
|
+
# Beams / 1D
|
|
26
|
+
mc.NORM_SEG2: "T3D2",
|
|
27
|
+
mc.NORM_SEG3: "B32",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Validated MED → LIMIT local-node permutations for linear 3D solids.
|
|
32
|
+
# Identified by direct LIMIT_CAE geometry tests.
|
|
33
|
+
NODE_REORDER = {
|
|
34
|
+
"C3D8": [0, 3, 2, 1, 4, 7, 6, 5],
|
|
35
|
+
"C3D6": [0, 2, 1, 3, 5, 4],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def med_to_limit(geo_type):
|
|
40
|
+
"""Return the LIMIT name for a MEDCoupling geometric type, or a placeholder."""
|
|
41
|
+
return MED_TO_LIMIT.get(geo_type, f"UNKNOWN_{geo_type}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_shell(elem_type: str) -> bool:
|
|
45
|
+
"""True for shell-like element types handled by this tool."""
|
|
46
|
+
return elem_type.startswith("S") or elem_type.startswith("M")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_solid(elem_type: str) -> bool:
|
|
50
|
+
"""True for 3D solid element types."""
|
|
51
|
+
return elem_type.startswith("C3D")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_beam_or_truss(elem_type: str) -> bool:
|
|
55
|
+
"""True for 1D beam/truss element types."""
|
|
56
|
+
return elem_type.startswith("T3D") or elem_type.startswith("B")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def is_result_carrying(elem_type: str) -> bool:
|
|
60
|
+
"""True for element types that carry stress/displacement results in LIMIT."""
|
|
61
|
+
return is_shell(elem_type) or is_solid(elem_type)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_reorder_indices(elem_type: str, n_nodes: int):
|
|
65
|
+
"""Return the validated MED→LIMIT local-node permutation, or identity."""
|
|
66
|
+
perm = NODE_REORDER.get(elem_type)
|
|
67
|
+
if perm is None or len(perm) != n_nodes:
|
|
68
|
+
return list(range(n_nodes))
|
|
69
|
+
return perm
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def reorder_connectivity(elem_type: str, connectivity):
|
|
73
|
+
"""Reorder a connectivity list with the validated MED→LIMIT permutation."""
|
|
74
|
+
order = get_reorder_indices(elem_type, len(connectivity))
|
|
75
|
+
return [connectivity[i] for i in order]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def clean_name(name) -> str:
|
|
79
|
+
"""Normalize a group name by removing underscores (LIMIT naming convention)."""
|
|
80
|
+
return str(name).replace("_", "")
|