solidipes-solid-mech-plugin 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.
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.1
2
+ Name: solidipes-solid-mech-plugin
3
+ Version: 0.0.1
4
+ Summary: Plugin for Solidipes with solid mechanics components
5
+ License: GPL-3.0-or-later
6
+ Author: Son Pham-Ba
7
+ Author-email: son.phamba@epfl.ch
8
+ Requires-Python: >=3.9.12,<3.13
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Provides-Extra: dev
15
+ Provides-Extra: test
16
+ Requires-Dist: build (>=1.0.3,<2.0.0) ; extra == "dev"
17
+ Requires-Dist: ipython (>=8.18.1,<9.0.0)
18
+ Requires-Dist: pre-commit (>=3.0.4,<4.0.0) ; extra == "dev"
19
+ Requires-Dist: pytest (>=8.1.1,<9.0.0) ; extra == "dev" or extra == "test"
20
+ Requires-Dist: pyvista (>=0.43.3,<0.44.0)
21
+ Requires-Dist: solidipes (>=1.0.1)
22
+ Requires-Dist: solidipes-core-plugin (>=0.0.2)
23
+ Requires-Dist: streamlit (>=1.37.0,<2.0.0)
24
+ Description-Content-Type: text/markdown
25
+
26
+ Plugin for Solidipes with solid mechanics components.
27
+
28
+ Meant to be used with [Solidipes](https://gitlab.com/solidipes/solidipes).
29
+
30
+
31
+ # Installation for development
32
+
33
+ ```bash
34
+ git clone https://gitlab.com/solidipes/solidipes-solid-mech-plugin.git
35
+ cd solidipes-solid-mech-plugin
36
+ pip install -e .[dev]
37
+ pre-commit install
38
+ ```
39
+
@@ -0,0 +1,13 @@
1
+ Plugin for Solidipes with solid mechanics components.
2
+
3
+ Meant to be used with [Solidipes](https://gitlab.com/solidipes/solidipes).
4
+
5
+
6
+ # Installation for development
7
+
8
+ ```bash
9
+ git clone https://gitlab.com/solidipes/solidipes-solid-mech-plugin.git
10
+ cd solidipes-solid-mech-plugin
11
+ pip install -e .[dev]
12
+ pre-commit install
13
+ ```
@@ -0,0 +1,60 @@
1
+ [tool.poetry]
2
+ name = "solidipes-solid-mech-plugin"
3
+ version = "0.0.1"
4
+ description = "Plugin for Solidipes with solid mechanics components"
5
+ authors = [
6
+ "Son Pham-Ba <son.phamba@epfl.ch>",
7
+ "Guillaume Anciaux <guillaume.anciaux@epfl.ch>",
8
+ ]
9
+ license = "GPL-3.0-or-later"
10
+ readme = "README.md"
11
+ packages = [{include = "solidipes_solid_mech_plugin"}]
12
+
13
+ [tool.poetry.dependencies]
14
+ python = "^3.9.12,<3.13"
15
+ solidipes = ">=1.0.1"
16
+ solidipes-core-plugin = ">=0.0.2"
17
+ streamlit = "^1.37.0"
18
+ ipython = "^8.18.1"
19
+ pyvista = "^0.43.3"
20
+ pre-commit = {version = "^3.0.4", optional = true}
21
+ build = {version = ">=1.0.3,<2.0.0", optional = true}
22
+ pytest = {version = "^8.1.1", optional = true}
23
+
24
+ [tool.poetry.extras]
25
+ dev = [
26
+ "pre-commit",
27
+ "build",
28
+ "pytest",
29
+ ]
30
+ test = [
31
+ "pytest",
32
+ ]
33
+
34
+ [tool.poetry.plugins."solidipes.plugins"]
35
+ solid-mech = "solidipes_solid_mech_plugin"
36
+
37
+ [build-system]
38
+ requires = ["poetry-core", "poetry-dynamic-versioning"]
39
+ build-backend = "poetry_dynamic_versioning.backend"
40
+
41
+ [tool.poetry-dynamic-versioning]
42
+ enable = false
43
+ vcs = "git"
44
+ style = "semver"
45
+
46
+ [tool.poetry-dynamic-versioning.substitution]
47
+ files = [
48
+ "solidipes_solid_mech_plugin/__init__.py",
49
+ ]
50
+
51
+ [tool.black]
52
+ line-length = 120
53
+ preview = true
54
+
55
+ [tool.isort]
56
+ line_length = 120
57
+ profile = "black"
58
+
59
+ [tool.codespell]
60
+ skip = 'poetry.lock,CHANGELOG.md'
@@ -0,0 +1,3 @@
1
+ """Plugin for Solidipes with solid mechanics components"""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,182 @@
1
+ from io import StringIO
2
+
3
+ import mergedeep
4
+ import meshio
5
+ import numpy as np
6
+ import pandas as pd
7
+ import pyparsing as pp
8
+ from solidipes_core_plugin.loaders.code_snippet import CodeSnippet
9
+
10
+
11
+ class ParseAbaqus:
12
+ _valid_characters = " " + pp.printables
13
+ _valid_characters = _valid_characters.replace("*", "")
14
+ ppText = pp.Word(_valid_characters + "\n")
15
+ ppHead = pp.Word(_valid_characters.replace(",", ""))
16
+
17
+ def parseComments(self, toks):
18
+ cs = [e.strip() for e in toks if e.strip() != ""]
19
+ if cs:
20
+ return {"Comment": cs}
21
+ return None
22
+
23
+ comments = pp.OneOrMore(pp.Suppress(pp.Literal("**")) + ppText).addParseAction(parseComments)
24
+
25
+ def parseData(self, toks):
26
+ name = toks[0]
27
+ data = toks[2].strip()
28
+ params = [e.strip() for e in toks[1]]
29
+ res = {}
30
+ if params:
31
+ res[":".join(params)] = {"data": data, "@params": params}
32
+ else:
33
+ res["data"] = data
34
+
35
+ return {name: res}
36
+
37
+ def _tag(self, name):
38
+ return (
39
+ pp.Suppress(pp.Literal("*"))
40
+ + name
41
+ + pp.Group(pp.ZeroOrMore(pp.Suppress(pp.Literal(",")) + self.ppHead))
42
+ + pp.Suppress(pp.Optional(pp.Literal("\n")))
43
+ )
44
+
45
+ def _data(self, name):
46
+ return (self._tag(name) + pp.Combine(pp.ZeroOrMore(self.ppText))).addParseAction(self.parseData)
47
+
48
+ def parseTag(self, toks):
49
+ name = toks[0]
50
+ params = [e.strip() for e in toks[1]]
51
+ res = {}
52
+ res[name] = {}
53
+ for e in toks[2:]:
54
+ if isinstance(e, str):
55
+ e = e.strip()
56
+ if e == "":
57
+ continue
58
+ if isinstance(e, dict):
59
+ res[name] = mergedeep.merge(res[name], e)
60
+ # print('aaa', res, conv)
61
+ if params:
62
+ res[name]["@params"] = params
63
+
64
+ return res
65
+
66
+ def _heading(self):
67
+ _heading = self._tag("Heading") + self.comments()
68
+ return _heading.addParseAction(self.parseTag)
69
+
70
+ def _paragraph(self, name):
71
+ return self._tag(name) + pp.ZeroOrMore(self.ppText | self.comments())
72
+
73
+ def _block(self, name):
74
+ stag = name
75
+ etag = "End " + stag
76
+
77
+ _block_start = self._tag(stag)
78
+ _block_content = pp.Forward()
79
+ _block_end = self._tag(etag)
80
+ _block = _block_start + _block_content + pp.Suppress(_block_end)
81
+ _block_content << pp.ZeroOrMore(
82
+ self._data("Node")
83
+ | self._data("Element")
84
+ | self._data("Nset")
85
+ | self._data("Elset")
86
+ | self._data("Equation")
87
+ | self._data("Surface")
88
+ | self._data("Solid Section")
89
+ | self.comments()
90
+ )
91
+ return _block.addParseAction(self.parseTag)
92
+
93
+ def inp_file(self):
94
+ _file = (
95
+ self._heading()
96
+ + pp.Optional(self._tag("Preprint").addParseAction(self.parseTag))
97
+ + pp.ZeroOrMore(self.comments | self._block("Part"))
98
+ )
99
+ _file = _file.leaveWhitespace().addParseAction(
100
+ lambda toks: {"main": [e for e in toks if (not isinstance(e, str) or e.strip() != "")]}
101
+ )
102
+ return _file
103
+
104
+ def parse(self, filename):
105
+ to_parse = open(filename).read()
106
+ ret = self.inp_file().parseString(to_parse)
107
+ return ret
108
+
109
+
110
+ ################################################################
111
+
112
+
113
+ class Abaqus(CodeSnippet):
114
+ supported_mime_types = {"application/fem/abaqus": "inp"}
115
+
116
+ def __init__(self, **kwargs):
117
+ from solidipes_core_plugin.viewers.xml import XML
118
+
119
+ from ..viewers.pyvista_plotter import PyvistaPlotter
120
+
121
+ super().__init__(**kwargs)
122
+ self.compatible_viewers[:0] = [PyvistaPlotter, XML]
123
+
124
+ @CodeSnippet.loadable
125
+ def structure(self):
126
+ try:
127
+ parser = ParseAbaqus()
128
+ ret = parser.parse(self.file_info.path)
129
+ return ret[0]
130
+ except Exception as e:
131
+ print(e)
132
+
133
+ @property
134
+ def xml(self):
135
+ return self.structure
136
+
137
+ @property
138
+ def parts(self):
139
+ return [e["Part"] for e in self.xml["main"] if "Part" in e]
140
+
141
+ def nodes(self, part):
142
+ p = self.parts[part]
143
+ if "Node" in p:
144
+ df = pd.read_csv(StringIO(p["Node"]["data"]), sep=",", header=None)
145
+ return df.to_numpy()[:, 1:]
146
+
147
+ def elements(self, part):
148
+ p = self.parts[part]
149
+ cells = []
150
+ if "Element" in p:
151
+ for _type, v in p["Element"].items():
152
+ if _type.startswith("type="):
153
+ _type = _type[5:]
154
+ if _type == "CPE4":
155
+ _type = "quad"
156
+ elif _type == "CPE3":
157
+ _type = "triangle"
158
+ else:
159
+ print(f"Do not know element type {_type}")
160
+ raise RuntimeError(f"Do not know element type {_type}")
161
+ conn = pd.read_csv(StringIO(v["data"]), sep=",", header=None).to_numpy()[:, 1:] - 1
162
+ cells.append((_type, np.array(conn)))
163
+ return cells
164
+
165
+ @CodeSnippet.loadable
166
+ def meshes(self):
167
+ import pyvista as pv
168
+
169
+ meshes = {}
170
+ for p, part in enumerate(self.parts):
171
+ mesh = meshio.Mesh(self.nodes(p), self.elements(p))
172
+ mesh = pv.from_meshio(mesh)
173
+ params = part["@params"]
174
+ for param in params:
175
+ if param.startswith("name="):
176
+ param = param[5:]
177
+ meshes[param] = mesh
178
+ return meshes
179
+
180
+ @property
181
+ def mesh(self):
182
+ return [v for k, v in self.meshes.items()][0]
@@ -0,0 +1,14 @@
1
+ from .parse_inp import read_geof
2
+ from .pyvista_mesh import PyvistaMesh
3
+
4
+
5
+ class GeofMesh(PyvistaMesh):
6
+ """Mesh file loaded with pyvista"""
7
+
8
+ supported_mime_types = {"meshing/z-set": "geof"}
9
+
10
+ @PyvistaMesh.loadable
11
+ def mesh(self):
12
+ from pyvista.core.utilities import from_meshio
13
+
14
+ return from_meshio(read_geof(self.file_info.path))
@@ -0,0 +1,17 @@
1
+ from solidipes.loaders.file import File
2
+
3
+
4
+ class Meshio(File):
5
+ """File loaded with meshio"""
6
+
7
+ def __init__(self, **kwargs):
8
+ from ..viewers.pyvista_plotter import PyvistaPlotter
9
+
10
+ super().__init__(**kwargs)
11
+ self.compatible_viewers[:0] = [PyvistaPlotter]
12
+
13
+ @File.loadable
14
+ def mesh(self):
15
+ import meshio
16
+
17
+ return meshio.read(self.file_info.path)
@@ -0,0 +1,221 @@
1
+ import meshio
2
+ import numpy as np
3
+ import pyparsing as pp
4
+ from solidipes.utils import solidipes_logging as logging
5
+
6
+ print = logging.invalidPrint
7
+ logger = logging.getLogger()
8
+
9
+ ################################################################
10
+ abaqus_to_meshio_permutation = {
11
+ "C3D10": {0: 0, 1: 1, 2: 2, 3: 9, 4: 3, 5: 4, 6: 5, 7: 6, 8: 7, 9: 8},
12
+ "C3D10_4": {0: 0, 1: 1, 2: 2, 3: 9, 4: 3, 5: 4, 6: 5, 7: 6, 8: 7, 9: 8},
13
+ "C3D10R": {0: 0, 1: 1, 2: 2, 3: 9, 4: 3, 5: 4, 6: 5, 7: 6, 8: 7, 9: 8},
14
+ }
15
+ ################################################################
16
+ abaqus_to_meshio_type = {
17
+ # trusses
18
+ "T2D2": "line",
19
+ "T2D2H": "line",
20
+ "T2D3": "line3",
21
+ "T2D3H": "line3",
22
+ "T3D2": "line",
23
+ "T3D2H": "line",
24
+ "T3D3": "line3",
25
+ "T3D3H": "line3",
26
+ # beams
27
+ "B21": "line",
28
+ "B21H": "line",
29
+ "B22": "line3",
30
+ "B22H": "line3",
31
+ "B31": "line",
32
+ "B31H": "line",
33
+ "B32": "line3",
34
+ "B32H": "line3",
35
+ "B33": "line3",
36
+ "B33H": "line3",
37
+ # surfaces
38
+ "CPS4": "quad",
39
+ "CPS4R": "quad",
40
+ "S4": "quad",
41
+ "S4R": "quad",
42
+ "S4RS": "quad",
43
+ "S4RSW": "quad",
44
+ "S4R5": "quad",
45
+ "S8R": "quad8",
46
+ "S8R5": "quad8",
47
+ "S9R5": "quad9",
48
+ # "QUAD": "quad",
49
+ # "QUAD4": "quad",
50
+ # "QUAD5": "quad5",
51
+ # "QUAD8": "quad8",
52
+ # "QUAD9": "quad9",
53
+ #
54
+ "CPS3": "triangle",
55
+ "STRI3": "triangle",
56
+ "S3": "triangle",
57
+ "S3R": "triangle",
58
+ "S3RS": "triangle",
59
+ "R3D3": "triangle",
60
+ # "TRI7": "triangle7",
61
+ # 'TRISHELL': 'triangle',
62
+ # 'TRISHELL3': 'triangle',
63
+ # 'TRISHELL7': 'triangle',
64
+ #
65
+ "STRI65": "triangle6",
66
+ # 'TRISHELL6': 'triangle6',
67
+ # volumes
68
+ "C3D8": "hexahedron",
69
+ "C3D8H": "hexahedron",
70
+ "C3D8I": "hexahedron",
71
+ "C3D8IH": "hexahedron",
72
+ "C3D8R": "hexahedron",
73
+ "C3D8RH": "hexahedron",
74
+ # "HEX9": "hexahedron9",
75
+ "C3D20": "hexahedron20",
76
+ "C3D20H": "hexahedron20",
77
+ "C3D20R": "hexahedron20",
78
+ "C3D20RH": "hexahedron20",
79
+ # "HEX27": "hexahedron27",
80
+ #
81
+ "C3D4": "tetra",
82
+ "C3D4H": "tetra4",
83
+ # "TETRA8": "tetra8",
84
+ "C3D10": "tetra10",
85
+ "C3D10_4": "tetra10",
86
+ "C3D10R": "tetra10",
87
+ "C3D10H": "tetra10",
88
+ "C3D10I": "tetra10",
89
+ "C3D10M": "tetra10",
90
+ "C3D10MH": "tetra10",
91
+ # "TETRA14": "tetra14",
92
+ #
93
+ # "PYRAMID": "pyramid",
94
+ "C3D6": "wedge",
95
+ "C3D15": "wedge15",
96
+ #
97
+ # 4-node bilinear displacement and pore pressure
98
+ "CAX4P": "quad",
99
+ # 6-node quadratic
100
+ "CPE6": "triangle6",
101
+ }
102
+
103
+ ################################################################
104
+
105
+
106
+ class ParseINP:
107
+ _valid_characters = "\n " + pp.printables
108
+ _valid_characters = _valid_characters.replace("*", "")
109
+ ppText = pp.Word(_valid_characters)
110
+
111
+ def parseBlock(self, token, level=None, content=None, tokens=None):
112
+ # print(f"detected: {token} ({level}) {type(content)} {len(content)}")
113
+ pass
114
+
115
+ def _parseBlock(self, tokens):
116
+ return self.parseBlock(
117
+ tokens[1],
118
+ level=len(tokens[0]),
119
+ content=tokens[2].strip(),
120
+ tokens=tokens,
121
+ )
122
+
123
+ def paragraph(self, n):
124
+ if n == 0:
125
+ return self.ppText
126
+ _block_start = pp.Literal("*" * n) + pp.Word(pp.alphas)
127
+ _block_content = pp.Combine(pp.ZeroOrMore(self.ppText | self.paragraph(n - 1)))
128
+ _block = _block_start + _block_content
129
+ _block.leaveWhitespace()
130
+ _block.addParseAction(lambda toks: self._parseBlock(toks))
131
+
132
+ return _block
133
+
134
+ def inp_file(self):
135
+ _p = self.paragraph(4) | self.paragraph(3) | self.paragraph(2) | self.paragraph(1)
136
+ _file = pp.Combine(pp.OneOrMore(_p).leaveWhitespace())
137
+ _file.addParseAction(lambda toks: self.final_parse(toks))
138
+ return _file
139
+
140
+ def final_parse(self, toks):
141
+ pass
142
+
143
+ def parse(self, filename):
144
+ to_parse = open(filename).read()
145
+ self.inp_file().parseString(to_parse)
146
+
147
+
148
+ ################################################################
149
+
150
+
151
+ class ParseGEOF(ParseINP):
152
+ def parseBlock(self, token, level=None, content=None, tokens=None):
153
+ if token == "node":
154
+ # print(f'{"*"*level}detected {token}')
155
+ content = content.split("\n")
156
+ n_nodes, dim = content[0].split()
157
+ n_nodes = int(n_nodes)
158
+ dim = int(dim)
159
+ values = [_l.split() for _l in content[1:]]
160
+ nodes = np.array(values, dtype=float)
161
+ assert nodes.shape[0] == n_nodes
162
+ assert nodes.shape[1] == dim + 1
163
+ logger.info(f"Loaded {n_nodes} nodes")
164
+ self.nodes = nodes
165
+ return "nodes:ok\n"
166
+ if token == "element":
167
+ # print(f'{"*"*level}detected {token}')
168
+ content = content.split("\n")
169
+ n_elements = int(content[0])
170
+ values = [_l.split() for _l in content[1:]]
171
+ values = np.array(values)
172
+ el_type = np.array(values[:, 1], dtype=str)
173
+ connectivity = np.array(values[:, 2:], dtype=int) - 1
174
+ # print(connectivity)
175
+ self.cells = {}
176
+ for e, _type in enumerate(el_type):
177
+ aba_type = _type.upper()
178
+ _type = abaqus_to_meshio_type[aba_type]
179
+ if _type not in self.cells:
180
+ self.cells[_type] = []
181
+
182
+ perm = abaqus_to_meshio_permutation[aba_type]
183
+ self.cells[_type].append(connectivity[e, :])
184
+
185
+ for _type, conn in self.cells.items():
186
+ permuted = np.zeros_like(conn)
187
+
188
+ for i, ii in perm.items():
189
+ permuted[:, i] = connectivity[:, ii]
190
+ self.cells[_type] = permuted
191
+ # print(self.cells)
192
+ logger.info(f"Loaded {n_elements} elements")
193
+ assert n_elements == connectivity.shape[0]
194
+ return "elements:ok\n"
195
+ if token == "geometry":
196
+ # print(f'{"*"*level}detected {token}')
197
+ # print(content)
198
+ return content
199
+ if token == "return":
200
+ return content
201
+ # print(f'{"*"*level}ignored {token}')
202
+ return f'{"*"*level}ignored {token}'
203
+
204
+ def final_parse(self, toks):
205
+ points = self.nodes[:, 1:]
206
+ cells = [(_type, c) for _type, c in self.cells.items()]
207
+ self.mesh = meshio.Mesh(points, cells)
208
+ return "mesh ok\n"
209
+
210
+
211
+ ################################################################
212
+
213
+
214
+ def read_geof(filename):
215
+ parser = ParseGEOF()
216
+ parser.parse(filename)
217
+ # print(parser.mesh)
218
+ # parser.mesh.write("foo.vtu", file_format='vtu')
219
+ # parser.mesh.write("foo.vtk", file_format='vtk')
220
+ # parser.mesh.write("foo.msh", file_format='gmsh')
221
+ return parser.mesh
@@ -0,0 +1,163 @@
1
+ from functools import wraps
2
+
3
+ import pyvista as pv
4
+ from solidipes.loaders.file import File
5
+
6
+ DEFAULT_DATA_ID = "_" #: data name given to implicitly added arrays
7
+ POINT_DATA_SUFFIX = " (point data)"
8
+ CELL_DATA_SUFFIX = " (cell data)"
9
+
10
+
11
+ def get_point_data_from_id_or_array(func):
12
+ """Decorator to give either data_id or array to method accepting data_id"""
13
+
14
+ @wraps(func)
15
+ def wrapper(self, data_id_or_array, *args, **kwargs):
16
+ if isinstance(data_id_or_array, str):
17
+ data_id = data_id_or_array
18
+ if data_id not in self.point_data_names:
19
+ raise ValueError(f'No point data entry with the name "{data_id}" exists.')
20
+
21
+ else:
22
+ data_id = DEFAULT_DATA_ID
23
+ self.add_point_data(data_id_or_array, data_id)
24
+
25
+ return func(self, data_id, *args, **kwargs)
26
+
27
+ return wrapper
28
+
29
+
30
+ def get_cell_data_from_id_or_array(func):
31
+ """Decorator to give either data_id or array to method accepting data_id"""
32
+
33
+ @wraps(func)
34
+ def wrapper(self, data_id_or_array, *args, **kwargs):
35
+ if isinstance(data_id_or_array, str):
36
+ data_id = data_id_or_array
37
+ if data_id not in self.cell_data_names:
38
+ raise ValueError(f'No cell data entry with the name "{data_id}" exists.')
39
+
40
+ else:
41
+ data_id = DEFAULT_DATA_ID
42
+ self.add_cell_data(data_id_or_array, data_id)
43
+
44
+ return func(self, data_id, *args, **kwargs)
45
+
46
+ return wrapper
47
+
48
+
49
+ class PyvistaMesh(File):
50
+ """Mesh file loaded with pyvista"""
51
+
52
+ supported_mime_types = {
53
+ "meshing/GMSH": "msh",
54
+ "meshing/StepFile": "stl",
55
+ "meshing/VTK": ["vtu", "pvtu", "vtk"],
56
+ "meshing/AVS": "avs",
57
+ }
58
+
59
+ def __init__(self, **kwargs):
60
+ from ..viewers.pyvista_plotter import PyvistaPlotter
61
+
62
+ super().__init__(**kwargs)
63
+ #: Fully loaded pyvista mesh
64
+ self.compatible_viewers[:0] = [PyvistaPlotter]
65
+
66
+ def copy_pyvista_data_to_collection(self):
67
+ """Add pyvista data to data collection"""
68
+ for name in self.point_data_names:
69
+ self.add(name + POINT_DATA_SUFFIX, self.get_point_data(name))
70
+
71
+ for name in self.cell_data_names:
72
+ self.add(name + CELL_DATA_SUFFIX, self.get_cell_data(name))
73
+
74
+ @property
75
+ def data_info(self):
76
+ """Trigger loading of Pyvista mesh and return info"""
77
+ self.load_all()
78
+ return super().data_info
79
+
80
+ @File.loadable
81
+ def pyvista_mesh(self):
82
+ return pv.read(self.file_info.path)
83
+
84
+ @File.loadable
85
+ def mesh(self):
86
+ self.copy_pyvista_data_to_collection()
87
+ return self.pyvista_mesh
88
+
89
+ @property
90
+ def point_data_names(self):
91
+ return self.pyvista_mesh.point_data.keys()
92
+
93
+ @property
94
+ def cell_data_names(self):
95
+ return self.pyvista_mesh.cell_data.keys()
96
+
97
+ def get_point_data(self, name):
98
+ return self.pyvista_mesh.point_data.get_array(name)
99
+
100
+ def add_point_data(self, array, name):
101
+ self.pyvista_mesh.point_data.set_array(array, name)
102
+ self.add(name + POINT_DATA_SUFFIX, array)
103
+
104
+ def remove_point_data(self, name):
105
+ self.pyvista_mesh.point_data.remove(name)
106
+ self.remove(name + POINT_DATA_SUFFIX)
107
+
108
+ def get_cell_data(self, name):
109
+ return self.pyvista_mesh.cell_data.get_array(name)
110
+
111
+ def add_cell_data(self, array, name):
112
+ self.pyvista_mesh.cell_data.set_array(array, name)
113
+ self.add(name + CELL_DATA_SUFFIX, array)
114
+
115
+ def remove_cell_data(self, name):
116
+ self.pyvista_mesh.cell_data.remove(name)
117
+ self.remove(name + CELL_DATA_SUFFIX)
118
+
119
+ @get_point_data_from_id_or_array
120
+ def get_warped(self, data_id, factor=1.0):
121
+ """
122
+ Returns another PyvistaMesh with the mesh points displaced by the
123
+ given data.
124
+
125
+ Args:
126
+ data (string): Name of point data. If data is 1D, the mesh is
127
+ warped along its normals. Otherwise, the data must have the
128
+ same number dimensionality as the mesh.
129
+ factor (float): Factor to multiply the displacements by. Defaults
130
+ to 1.0.
131
+ """
132
+ new_mesh = self.copy()
133
+ dim = self.get_point_data(data_id).ndim # 1 if scalar, 2 if vector
134
+ if dim == 1:
135
+ new_pyvista_mesh = new_mesh.pyvista_mesh.warp_by_scalar(data_id, factor=factor)
136
+
137
+ else:
138
+ new_pyvista_mesh = new_mesh.pyvista_mesh.warp_by_vector(data_id, factor=factor)
139
+
140
+ new_mesh.pyvista_mesh = new_pyvista_mesh
141
+ new_mesh.copy_pyvista_data_to_collection()
142
+
143
+ return new_mesh
144
+
145
+ @get_point_data_from_id_or_array
146
+ def set_point_values(self, data_id):
147
+ """
148
+ Sets the point values for plotting to the given data.
149
+
150
+ Args:
151
+ data (string): Name of point data.
152
+ """
153
+ self.pyvista_mesh.set_active_scalars(data_id, "point")
154
+
155
+ @get_cell_data_from_id_or_array
156
+ def set_cell_values(self, data_id):
157
+ """
158
+ Sets the cell values for plotting to the given data.
159
+
160
+ Args:
161
+ data (string): Name of cell data.
162
+ """
163
+ self.pyvista_mesh.set_active_scalars(data_id, "cell")
@@ -0,0 +1,170 @@
1
+ import os
2
+
3
+ import h5py
4
+ import numpy as np
5
+ from solidipes.loaders.sequence import Sequence
6
+ from solidipes_core_plugin.loaders.xml import XML
7
+
8
+
9
+ class XDMF(Sequence, XML):
10
+ supported_mime_types = {"mesh/XDMF": "xdmf"}
11
+
12
+ def __init__(self, **kwargs):
13
+ from ..viewers.pyvista_plotter import PyvistaPlotter
14
+
15
+ super().__init__(**kwargs)
16
+ self.compatible_viewers[:0] = [PyvistaPlotter]
17
+
18
+ def _load_hdf5_data_item(self, item):
19
+ if item["@Format"] == "HDF":
20
+ dimensions = item["@Dimensions"]
21
+ dimensions = [int(e) for e in dimensions.split(" ")]
22
+ path, hdf5_path = item["#text"].split(":")
23
+ path = os.path.join(os.path.dirname(self.path), path)
24
+ h5 = h5py.File(path)
25
+ h5 = h5[hdf5_path]
26
+ h5 = np.array(h5).reshape(dimensions)
27
+ return h5
28
+ if item["@Format"] == "XML":
29
+ return [float(e) for e in item["#text"].split(" ")]
30
+ return item
31
+
32
+ def _load_xdmf_data_item(self, item):
33
+ if isinstance(item, dict):
34
+ res = {}
35
+ for k, v in item.items():
36
+ if k == "DataItem":
37
+ # print(f'LoadasDataItem: {k} {v}\n')
38
+ res[k] = self._load_hdf5_data_item(v)
39
+ else:
40
+ res[k] = self._load_xdmf_data_item(v)
41
+ return res
42
+ if isinstance(item, list) and not isinstance(item, str):
43
+ return [self._load_xdmf_data_item(e) for e in item]
44
+ return item
45
+
46
+ def find_dataitem_from_ref(self, ref):
47
+ ref = [e for e in ref.split("/") if e != "" and e != "Xdmf"]
48
+ print(ref)
49
+
50
+ def extract_label(r):
51
+ try:
52
+ name, label = r.split("[")
53
+ label = label[:-1]
54
+ label = label.replace(r'"', "")
55
+ label = label.split("=")
56
+ return name, label
57
+ except ValueError:
58
+ return r, None
59
+
60
+ res = self.xdmf
61
+ for r in ref:
62
+ name, label = extract_label(r)
63
+ # print(name, label)
64
+ res = res[name]
65
+ if isinstance(res, list):
66
+ for r in res:
67
+ if r[label[0]] != label[1]:
68
+ continue
69
+ res = r
70
+ break
71
+ # print(res.keys())
72
+ if label is None:
73
+ # print(res)
74
+ continue
75
+
76
+ if label[0] not in res:
77
+ raise RuntimeError(f"Cannot find {name} {label} in {ref}")
78
+ if res[label[0]] != label[1]:
79
+ raise RuntimeError(f"Cannot find {name} {label} in {ref}")
80
+ return res
81
+
82
+ def _load_element(self, n):
83
+ """Load a single frame"""
84
+
85
+ grids = self.grids[n]
86
+ geometry = grids["Geometry"]
87
+ topology = grids["Topology"]
88
+
89
+ if "DataItem" in geometry:
90
+ geometry = geometry["DataItem"]
91
+ elif "@Reference" in geometry:
92
+ ref = geometry["#text"]
93
+ geometry = self.find_dataitem_from_ref(ref)["DataItem"]
94
+
95
+ points = np.array(geometry)
96
+
97
+ if "DataItem" in topology:
98
+ conn = topology["DataItem"]
99
+ elif "@Reference" in topology:
100
+ ref = topology["#text"]
101
+ conn = self.find_dataitem_from_ref(ref)["DataItem"]
102
+
103
+ cells = [(topology["@TopologyType"].lower(), np.array(conn))]
104
+
105
+ point_data = {}
106
+ cell_data = {}
107
+
108
+ if "Attribute" in grids:
109
+ for a in grids["Attribute"]:
110
+ # _type = a['@AttributeType']
111
+ _center = a["@Center"]
112
+ _name = a["@Name"]
113
+ _data = a["DataItem"]
114
+ if _center == "Node":
115
+ point_data[_name] = np.array(_data)
116
+ if _center == "Cell":
117
+ cell_data[_name] = np.array([_data])
118
+ import meshio
119
+
120
+ mesh = meshio.Mesh(points, cells, point_data=point_data, cell_data=cell_data)
121
+ import pyvista as pv
122
+
123
+ mesh = pv.from_meshio(mesh)
124
+ return mesh
125
+
126
+ def select_frame(self, frame):
127
+ self.select_element(frame)
128
+
129
+ # cannot be defined with pre_loaded because it changes on demand
130
+ @property
131
+ def mesh(self):
132
+ return self._current_element
133
+
134
+ @XML.loadable
135
+ def xdmf(self):
136
+ return self._load_xdmf_data_item(self.xml["Xdmf"])
137
+
138
+ @XML.loadable
139
+ def version(self):
140
+ return self.xdmf["@Version"]
141
+
142
+ @XML.loadable
143
+ def mesh_name(self):
144
+ return self.domain["@Name"]
145
+
146
+ @XML.loadable
147
+ def domain(self):
148
+ return self.xdmf["Domain"]
149
+
150
+ @XML.loadable
151
+ def grid(self):
152
+ _grid = self.domain["Grid"]
153
+ keys = [e for e in _grid.keys() if e != "Grid"]
154
+ grid = {}
155
+ for e in keys:
156
+ grid[e] = _grid[e]
157
+ return grid
158
+
159
+ @XML.loadable
160
+ def n_frames(self):
161
+ return len(self.grid["Time"]["DataItem"])
162
+
163
+ @property
164
+ def _element_count(self):
165
+ return self.n_frames
166
+
167
+ @XML.loadable
168
+ def grids(self):
169
+ _grid = self.domain["Grid"]["Grid"]
170
+ return _grid
@@ -0,0 +1,150 @@
1
+ import pyvista as pv
2
+ from solidipes.loaders.data_container import DataContainer
3
+ from solidipes.utils import viewer_backends
4
+ from solidipes.viewers.viewer import Viewer
5
+ from streamlit_pyvista.mesh_viewer_component import MeshViewerComponent
6
+ from streamlit_pyvista.server_managers import ServerManagerProxified
7
+ from streamlit_pyvista.trame_viewers import get_advanced_viewer_path
8
+
9
+
10
+ class PyvistaPlotter(Viewer):
11
+ """Viewer for pyvista meshes
12
+
13
+ Args:
14
+ **kwargs: keyword arguments passed to the pyvista.Plotter constructor
15
+ """
16
+
17
+ def __init__(self, data_container=None, add_kwargs={}, show_kwargs={}, **kwargs):
18
+ #: keeps track of whether the plotter has already been shown
19
+ self.shown = False
20
+
21
+ #: Pyvista plotter
22
+ self.plotter = None
23
+ if viewer_backends.current_backend == "streamlit":
24
+ self.plotter = pv.Plotter(off_screen=True, **kwargs)
25
+ else: # python or jupyter notebook
26
+ self.plotter = pv.Plotter(**kwargs)
27
+
28
+ self.plotter.background_color = "black"
29
+ self.meshes = []
30
+ self.points = []
31
+ self.path_list = []
32
+ super().__init__(data_container, add_kwargs=add_kwargs, show_kwargs=show_kwargs)
33
+
34
+ self._update_path_list(data_container)
35
+
36
+ def add(self, data_container, **kwargs):
37
+ """Add mesh to the viewer
38
+
39
+ Args:
40
+ **kwargs: keyword arguments passed to the pyvista.Plotter.add_mesh
41
+ method
42
+ """
43
+ from ..loaders.abaqus import Abaqus
44
+
45
+ self.check_data_compatibility(data_container)
46
+
47
+ if isinstance(data_container, Abaqus):
48
+ for name, m in data_container.meshes.items():
49
+ self.meshes.append((m, kwargs))
50
+
51
+ elif isinstance(data_container, DataContainer):
52
+ self.add_mesh(data_container, **kwargs)
53
+
54
+ def add_mesh(self, data_container: DataContainer, **kwargs):
55
+ """Add mesh to the viewer
56
+
57
+ Args:
58
+ **kwargs: keyword arguments passed to the pyvista.Plotter.add_mesh
59
+ method
60
+ """
61
+ data = data_container.mesh
62
+ self.meshes.append((data, kwargs))
63
+ self._update_path_list(data_container)
64
+
65
+ def _update_path_list(self, data_container):
66
+ from solidipes.loaders.file_sequence import FileSequence
67
+
68
+ if isinstance(data_container, FileSequence):
69
+ path = data_container.paths.copy()
70
+ self.path_list.extend(path)
71
+ else:
72
+ if data_container is not None:
73
+ path = data_container.file_info.path
74
+ self.path_list.append(path)
75
+
76
+ def add_points(self, data_container, **kwargs):
77
+ """Add mesh as points to the viewer
78
+
79
+ Args:
80
+ **kwargs: keyword arguments passed to the
81
+ pyvista.Plotter.add_points method
82
+ """
83
+ data = data_container.mesh
84
+ self.points.append((data, kwargs))
85
+
86
+ def show(self, auto_close=False, **kwargs):
87
+ """Show the viewer
88
+
89
+ Args:
90
+ auto_close: whether to close the viewer after showing it
91
+ **kwargs: keyword arguments passed to the pyvista.Plotter.show
92
+ method
93
+ """
94
+
95
+ for p, _kwargs in self.points:
96
+ self.plotter.add_points(p, **_kwargs)
97
+
98
+ for m, _kwargs in self.meshes:
99
+ self.plotter.add_mesh(m, **_kwargs)
100
+
101
+ if viewer_backends.current_backend == "streamlit":
102
+ key = f"pyvista_ploter_{self.path_list}"
103
+ import streamlit as st
104
+
105
+ for p, _ in self.points:
106
+ st.write(p)
107
+
108
+ # Display arrays of the raw data
109
+ for i, (m, kw) in enumerate(self.meshes):
110
+ options_key = key + f"mesh_{i}_options"
111
+ if options_key not in st.session_state:
112
+ st.session_state[options_key] = ["None"] + m.array_names
113
+
114
+ st.write(m)
115
+
116
+ if len(self.path_list) == 0:
117
+ st.error("No mesh passed to the PyvistaPlotter")
118
+ return
119
+
120
+ self.shown = True
121
+ # Instantiate the viewer
122
+ MeshViewerComponent(
123
+ self.path_list,
124
+ trame_viewer_class=get_advanced_viewer_path(),
125
+ server_manager_class=ServerManagerProxified,
126
+ ).show()
127
+ elif viewer_backends.current_backend == "python":
128
+ self.shown = True
129
+ self.plotter.show(kwargs)
130
+ else:
131
+ self.shown = True
132
+ MeshViewerComponent(
133
+ self.path_list,
134
+ trame_viewer_class=get_advanced_viewer_path(),
135
+ server_manager_class=ServerManagerProxified,
136
+ ).show()
137
+
138
+ def save(self, path, **kwargs):
139
+ """Save the view to a file
140
+
141
+ Args:
142
+ path: path to the file
143
+ **kwargs: keyword arguments passed to the
144
+ pyvista.Plotter.screenshot method
145
+ """
146
+ # Pyvista Plotter must be shown before saving
147
+ if not self.shown:
148
+ self.plotter.show(auto_close=False) # also for streamlit backend
149
+ self.shown = True
150
+ self.plotter.screenshot(path, **kwargs)
@@ -0,0 +1,27 @@
1
+ import streamlit as st
2
+ from IPython.display import display
3
+ from solidipes.utils import viewer_backends
4
+ from solidipes_core_plugin.viewers.xml import XML
5
+
6
+
7
+ class XDMF(XML):
8
+ """Viewer for xml text files"""
9
+
10
+ def __init__(self, data=None):
11
+ self.xdmf = None
12
+ super().__init__(data)
13
+
14
+ def add(self, data_container):
15
+ """Append text to the viewer"""
16
+ super().check_data_compatibility(data_container)
17
+ self.xdmf = data_container
18
+
19
+ def show(self):
20
+ content = self.xdmf.mesh
21
+ if viewer_backends.current_backend == "jupyter notebook":
22
+ display(content)
23
+
24
+ elif viewer_backends.current_backend == "streamlit":
25
+ st.write(content)
26
+ else: # python
27
+ print(content)