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.
- solidipes_solid_mech_plugin-0.0.1/PKG-INFO +39 -0
- solidipes_solid_mech_plugin-0.0.1/README.md +13 -0
- solidipes_solid_mech_plugin-0.0.1/pyproject.toml +60 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/__init__.py +3 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/__init__.py +0 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/abaqus.py +182 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/geof_mesh.py +14 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/meshio.py +17 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/parse_inp.py +221 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/pyvista_mesh.py +163 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/loaders/xdmf.py +170 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/viewers/__init__.py +0 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/viewers/pyvista_plotter.py +150 -0
- solidipes_solid_mech_plugin-0.0.1/solidipes_solid_mech_plugin/viewers/xdmf.py +27 -0
|
@@ -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'
|
|
File without changes
|
|
@@ -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
|
|
File without changes
|
|
@@ -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)
|