solidipes-solid-mech-plugin 0.0.14__tar.gz → 0.0.16__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.
Files changed (55) hide show
  1. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/PKG-INFO +6 -3
  2. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/pyproject.toml +5 -2
  3. solidipes_solid_mech_plugin-0.0.16/solidipes_solid_mech_plugin/config.py +8 -0
  4. solidipes_solid_mech_plugin-0.0.16/solidipes_solid_mech_plugin/loaders/exodus.py +184 -0
  5. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/viewers/pyvista_plotter.py +3 -3
  6. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/PKG-INFO +6 -3
  7. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/SOURCES.txt +2 -0
  8. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/requires.txt +5 -2
  9. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/scm_file_list.json +27 -25
  10. solidipes_solid_mech_plugin-0.0.16/solidipes_solid_mech_plugin.egg-info/scm_version.json +8 -0
  11. solidipes_solid_mech_plugin-0.0.16/tests/test_exodus_mesh.py +88 -0
  12. solidipes_solid_mech_plugin-0.0.16/tests/test_pyvista_plotter.py +59 -0
  13. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/test_streamlit_pyvista.py +109 -7
  14. solidipes_solid_mech_plugin-0.0.16/uv.lock +4464 -0
  15. solidipes_solid_mech_plugin-0.0.14/solidipes_solid_mech_plugin/config.py +0 -5
  16. solidipes_solid_mech_plugin-0.0.14/solidipes_solid_mech_plugin.egg-info/scm_version.json +0 -8
  17. solidipes_solid_mech_plugin-0.0.14/tests/test_pyvista_plotter.py +0 -29
  18. solidipes_solid_mech_plugin-0.0.14/uv.lock +0 -3519
  19. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/.flake8 +0 -0
  20. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/.gitignore +0 -0
  21. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/.gitlab-ci.yml +0 -0
  22. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/.pre-commit-config.yaml +0 -0
  23. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/CHANGELOG.md +0 -0
  24. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/LICENSE +0 -0
  25. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/README.md +0 -0
  26. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/setup.cfg +0 -0
  27. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/__init__.py +0 -0
  28. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/__init__.py +0 -0
  29. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/abaqus.py +0 -0
  30. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/abaqus_python_code.py +0 -0
  31. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/geo_msh.py +0 -0
  32. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/geof_mesh.py +0 -0
  33. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/meshio.py +0 -0
  34. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/parse_inp.py +0 -0
  35. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/pyvista_mesh.py +0 -0
  36. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/loaders/xdmf.py +0 -0
  37. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/ontologies/solidipes_solid_mech.py +0 -0
  38. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/viewers/__init__.py +0 -0
  39. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin/viewers/xdmf.py +0 -0
  40. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/dependency_links.txt +0 -0
  41. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/entry_points.txt +0 -0
  42. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/solidipes_solid_mech_plugin.egg-info/top_level.txt +0 -0
  43. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/generate_assets.py +0 -0
  44. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/pyvista_mesh.vtu +0 -0
  45. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/pyvista_mesh_no_fields.vtu +0 -0
  46. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/pyvista_plot.png +0 -0
  47. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/test_mesh_loaded.png +0 -0
  48. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/xdmf_mesh.h5 +0 -0
  49. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/assets/xdmf_mesh.xdmf +0 -0
  50. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/conftest.py +0 -0
  51. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/helpers/utils.py +0 -0
  52. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/test_load_modules.py +0 -0
  53. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/test_ontologies.py +0 -0
  54. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/test_pyvista_mesh.py +0 -0
  55. {solidipes_solid_mech_plugin-0.0.14 → solidipes_solid_mech_plugin-0.0.16}/tests/test_xdmf_mesh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solidipes-solid-mech-plugin
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: Plugin for Solidipes with solid mechanics components
5
5
  Author-email: Son Pham-Ba <son.phamba@epfl.ch>, Guillaume Anciaux <guillaume.anciaux@epfl.ch>
6
6
  License: GPL-3.0-or-later
@@ -12,9 +12,12 @@ Requires-Dist: ipython~=8.18.1
12
12
  Requires-Dist: mergedeep~=1.3.4
13
13
  Requires-Dist: meshio[all]~=5.3.5
14
14
  Requires-Dist: streamlit~=1.45.0
15
- Requires-Dist: trame~=3.5.2
16
- Requires-Dist: streamlit-pyvista==0.0.20rc0
15
+ Requires-Dist: trame<4,>=3.13.2
16
+ Requires-Dist: trame-pyvista>=0.2.1
17
+ Requires-Dist: trame-vtk>=2.11.15
18
+ Requires-Dist: streamlit-pyvista~=0.0.24
17
19
  Requires-Dist: h5py~=3.13.0
20
+ Requires-Dist: netCDF4~=1.7.0
18
21
  Provides-Extra: dev
19
22
  Requires-Dist: pillow~=10.4.0; extra == "dev"
20
23
  Requires-Dist: pixelmatch~=0.3.0; extra == "dev"
@@ -16,9 +16,12 @@ dependencies = [
16
16
  "mergedeep~=1.3.4",
17
17
  "meshio[all]~=5.3.5",
18
18
  "streamlit~=1.45.0",
19
- "trame~=3.5.2",
20
- "streamlit-pyvista==0.0.20rc0",
19
+ "trame>=3.13.2,<4",
20
+ "trame-pyvista>=0.2.1",
21
+ "trame-vtk>=2.11.15",
22
+ "streamlit-pyvista~=0.0.24",
21
23
  "h5py~=3.13.0",
24
+ "netCDF4~=1.7.0",
22
25
  ]
23
26
 
24
27
  [project.optional-dependencies]
@@ -0,0 +1,8 @@
1
+ import os
2
+
3
+ from solidipes.utils import config
4
+ from solidipes.utils.utils import get_read_write_dir
5
+
6
+ streamlit_pyvista_cache_dir_name = get_read_write_dir(
7
+ os.path.join(config.solidipes_dirname, ".streamlit-pyvista-cache")
8
+ )
@@ -0,0 +1,184 @@
1
+ import re
2
+
3
+ import numpy as np
4
+ from solidipes.loaders.file import File
5
+ from solidipes.loaders.sequence import Sequence
6
+ from solidipes_core_plugin.loaders.netcdf import NetCDF
7
+
8
+
9
+ def _decode_exodus_string(raw) -> str:
10
+ """Decode a character array row from an Exodus II netCDF file."""
11
+ if isinstance(raw, np.ndarray):
12
+ raw = raw.tobytes()
13
+
14
+ if isinstance(raw, (bytes, np.bytes_)):
15
+ return raw.decode("utf-8", errors="ignore").strip("\x00").strip()
16
+
17
+ return str(raw)
18
+
19
+
20
+ class Exodus(Sequence, NetCDF):
21
+ """Exodus II finite element database file, read with meshio and netCDF4.
22
+
23
+ Every Exodus file is treated as a sequence of time steps (a mesh-only
24
+ database counts as a single frame). Nodal and elemental result variables
25
+ are exposed as point and cell data for each available time step, and the
26
+ mesh can be viewed with the PyvistaPlotter.
27
+
28
+ The `netcdf` loadable and the netCDF metadata (dimensions, variables,
29
+ global attributes) are inherited from the NetCDF loader.
30
+ """
31
+
32
+ from ..viewers.pyvista_plotter import PyvistaPlotter
33
+
34
+ supported_mime_types = {"application/x-netcdf": ["e", "exo", "gen"]}
35
+ _compatible_viewers = [PyvistaPlotter]
36
+
37
+ @File.loadable
38
+ def meshio_mesh(self):
39
+ """Static mesh (connectivity and coordinates) read with meshio"""
40
+ import meshio
41
+
42
+ return meshio.read(self.file_info.path)
43
+
44
+ @File.rocrate_metadata
45
+ def title(self):
46
+ return self.netcdf.title
47
+
48
+ @File.rocrate_metadata
49
+ def n_time_steps(self):
50
+ if "time_step" not in self.netcdf.dimensions:
51
+ return 1
52
+
53
+ # A mesh-only database has an empty (or missing) time_step dimension:
54
+ # the mesh itself counts as a single frame
55
+ return max(1, len(self.netcdf.dimensions["time_step"]))
56
+
57
+ @File.rocrate_metadata
58
+ def n_nodes(self):
59
+ return len(self.netcdf.dimensions["num_nodes"])
60
+
61
+ @File.rocrate_metadata
62
+ def n_elements(self):
63
+ return len(self.netcdf.dimensions["num_elem"])
64
+
65
+ @File.rocrate_metadata
66
+ def nodal_variable_names(self):
67
+ if "name_nod_var" not in self.netcdf.variables:
68
+ return []
69
+
70
+ return [_decode_exodus_string(raw) for raw in self.netcdf.variables["name_nod_var"][:]]
71
+
72
+ @File.rocrate_metadata
73
+ def element_block_names(self):
74
+ if "eb_names" not in self.netcdf.variables:
75
+ return {}
76
+
77
+ return {i + 1: _decode_exodus_string(raw) for i, raw in enumerate(self.netcdf.variables["eb_names"][:])}
78
+
79
+ @File.rocrate_metadata
80
+ def elemental_variable_names(self):
81
+ """List of (element_block_number, variable_number, name) triples"""
82
+ names = []
83
+
84
+ for key, var in self.netcdf.variables.items():
85
+ if not key.startswith("name_elem_var"):
86
+ continue
87
+
88
+ block = int(key[len("name_elem_var") :]) if len(key) > len("name_elem_var") else 1
89
+
90
+ for i, raw in enumerate(var[:]):
91
+ name = _decode_exodus_string(raw)
92
+
93
+ if name:
94
+ names.append((block, i + 1, name))
95
+
96
+ return names
97
+
98
+ @property
99
+ def _element_count(self):
100
+ return self.n_time_steps
101
+
102
+ def select_frame(self, frame):
103
+ self.select_element(frame)
104
+
105
+ @property
106
+ def mesh(self):
107
+ """Pyvista mesh of the currently selected time step"""
108
+ return self._current_element
109
+
110
+ def _load_element(self, n):
111
+ """Load the mesh with nodal and elemental data at time step n"""
112
+ import pyvista as pv
113
+ from meshio import Mesh
114
+
115
+ meshio_mesh = self.meshio_mesh
116
+ point_data = self._point_data_at_step(n)
117
+ cell_data = self._cell_data_at_step(n)
118
+ mesh = Mesh(meshio_mesh.points, meshio_mesh.cells, point_data=point_data, cell_data=cell_data)
119
+ return pv.from_meshio(mesh)
120
+
121
+ def _point_data_at_step(self, n):
122
+ """Nodal variable values at time step n"""
123
+ nc = self.netcdf
124
+ names = self.nodal_variable_names
125
+ data = {}
126
+
127
+ if "vals_nod_var" in nc.variables:
128
+ # Standard layout: single variable with dimensions
129
+ # (time_step, num_nod_var, num_nodes)
130
+ all_data = np.asarray(nc.variables["vals_nod_var"][n])
131
+
132
+ for i, name in enumerate(names):
133
+ data[name] = all_data[i]
134
+
135
+ else:
136
+ # Alternative layout: one variable per nodal variable
137
+ for i, name in enumerate(names):
138
+ var_name = f"vals_nod_var{i + 1}"
139
+
140
+ if var_name in nc.variables:
141
+ data[name] = np.asarray(nc.variables[var_name][n])
142
+
143
+ return data
144
+
145
+ def _cell_data_at_step(self, n):
146
+ """Elemental variable values at time step n, per meshio cell block"""
147
+ nc = self.netcdf
148
+ names = self.elemental_variable_names
149
+
150
+ if len(names) == 0:
151
+ return {}
152
+
153
+ # Collect data per (variable index, element block)
154
+ raw: dict[int, dict[int, np.ndarray]] = {}
155
+
156
+ for key, var in nc.variables.items():
157
+ match = re.fullmatch(r"vals_elem_var(\d+)?(?:eb(\d+))?", key)
158
+
159
+ if match is None:
160
+ continue
161
+
162
+ var_index = 0 if match.group(1) is None else int(match.group(1)) - 1
163
+ block = 0 if match.group(2) is None else int(match.group(2)) - 1
164
+ raw.setdefault(var_index, {})[block] = np.asarray(var[n])
165
+
166
+ cell_sizes = [len(c.data) for c in self.meshio_mesh.cells]
167
+ offsets = np.cumsum([0, *cell_sizes])
168
+
169
+ cell_data = {}
170
+
171
+ for block, var_index, name in names:
172
+ if var_index - 1 not in raw:
173
+ continue
174
+
175
+ by_block = raw[var_index - 1]
176
+ concatenated = np.concatenate([by_block[b] for b in sorted(by_block)])
177
+
178
+ if len(concatenated) != offsets[-1]:
179
+ # Element blocks without data for this variable: skip
180
+ continue
181
+
182
+ cell_data[name] = [concatenated[start:stop] for start, stop in zip(offsets[:-1], offsets[1:])]
183
+
184
+ return cell_data
@@ -95,7 +95,7 @@ class PyvistaPlotter(Viewer):
95
95
  method
96
96
  """
97
97
  from streamlit_pyvista.mesh_viewer_component import MeshViewerComponent # noqa: E402
98
- from streamlit_pyvista.server_managers import ServerManagerProxified # noqa: E402
98
+ from streamlit_pyvista.server_managers import ServerManager # noqa: E402
99
99
  from streamlit_pyvista.trame_viewers import get_advanced_viewer_path # noqa: E402
100
100
 
101
101
  max_frames = 500
@@ -133,7 +133,7 @@ class PyvistaPlotter(Viewer):
133
133
  MeshViewerComponent(
134
134
  paths,
135
135
  trame_viewer_class=get_advanced_viewer_path(),
136
- server_manager_class=ServerManagerProxified,
136
+ server_manager_class=ServerManager,
137
137
  ).show()
138
138
  elif viewer_backends.current_backend == "python":
139
139
  self.shown = True
@@ -143,7 +143,7 @@ class PyvistaPlotter(Viewer):
143
143
  MeshViewerComponent(
144
144
  paths,
145
145
  trame_viewer_class=get_advanced_viewer_path(),
146
- server_manager_class=ServerManagerProxified,
146
+ server_manager_class=ServerManager,
147
147
  ).show()
148
148
 
149
149
  def save(self, path, **kwargs):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solidipes-solid-mech-plugin
3
- Version: 0.0.14
3
+ Version: 0.0.16
4
4
  Summary: Plugin for Solidipes with solid mechanics components
5
5
  Author-email: Son Pham-Ba <son.phamba@epfl.ch>, Guillaume Anciaux <guillaume.anciaux@epfl.ch>
6
6
  License: GPL-3.0-or-later
@@ -12,9 +12,12 @@ Requires-Dist: ipython~=8.18.1
12
12
  Requires-Dist: mergedeep~=1.3.4
13
13
  Requires-Dist: meshio[all]~=5.3.5
14
14
  Requires-Dist: streamlit~=1.45.0
15
- Requires-Dist: trame~=3.5.2
16
- Requires-Dist: streamlit-pyvista==0.0.20rc0
15
+ Requires-Dist: trame<4,>=3.13.2
16
+ Requires-Dist: trame-pyvista>=0.2.1
17
+ Requires-Dist: trame-vtk>=2.11.15
18
+ Requires-Dist: streamlit-pyvista~=0.0.24
17
19
  Requires-Dist: h5py~=3.13.0
20
+ Requires-Dist: netCDF4~=1.7.0
18
21
  Provides-Extra: dev
19
22
  Requires-Dist: pillow~=10.4.0; extra == "dev"
20
23
  Requires-Dist: pixelmatch~=0.3.0; extra == "dev"
@@ -20,6 +20,7 @@ solidipes_solid_mech_plugin.egg-info/top_level.txt
20
20
  solidipes_solid_mech_plugin/loaders/__init__.py
21
21
  solidipes_solid_mech_plugin/loaders/abaqus.py
22
22
  solidipes_solid_mech_plugin/loaders/abaqus_python_code.py
23
+ solidipes_solid_mech_plugin/loaders/exodus.py
23
24
  solidipes_solid_mech_plugin/loaders/geo_msh.py
24
25
  solidipes_solid_mech_plugin/loaders/geof_mesh.py
25
26
  solidipes_solid_mech_plugin/loaders/meshio.py
@@ -31,6 +32,7 @@ solidipes_solid_mech_plugin/viewers/__init__.py
31
32
  solidipes_solid_mech_plugin/viewers/pyvista_plotter.py
32
33
  solidipes_solid_mech_plugin/viewers/xdmf.py
33
34
  tests/conftest.py
35
+ tests/test_exodus_mesh.py
34
36
  tests/test_load_modules.py
35
37
  tests/test_ontologies.py
36
38
  tests/test_pyvista_mesh.py
@@ -3,9 +3,12 @@ ipython~=8.18.1
3
3
  mergedeep~=1.3.4
4
4
  meshio[all]~=5.3.5
5
5
  streamlit~=1.45.0
6
- trame~=3.5.2
7
- streamlit-pyvista==0.0.20rc0
6
+ trame<4,>=3.13.2
7
+ trame-pyvista>=0.2.1
8
+ trame-vtk>=2.11.15
9
+ streamlit-pyvista~=0.0.24
8
10
  h5py~=3.13.0
11
+ netCDF4~=1.7.0
9
12
 
10
13
  [dev]
11
14
  pillow~=10.4.0
@@ -1,43 +1,45 @@
1
1
  {
2
2
  "files": [
3
+ ".flake8",
3
4
  ".gitignore",
4
5
  ".gitlab-ci.yml",
5
6
  ".pre-commit-config.yaml",
6
7
  "CHANGELOG.md",
7
- ".flake8",
8
+ "LICENSE",
8
9
  "README.md",
9
- "uv.lock",
10
10
  "pyproject.toml",
11
- "LICENSE",
12
- "tests/test_pyvista_mesh.py",
13
- "tests/conftest.py",
14
- "tests/test_ontologies.py",
15
- "tests/test_load_modules.py",
16
- "tests/test_streamlit_pyvista.py",
17
- "tests/test_pyvista_plotter.py",
18
- "tests/test_xdmf_mesh.py",
19
- "tests/helpers/utils.py",
20
- "tests/assets/xdmf_mesh.h5",
21
- "tests/assets/test_mesh_loaded.png",
22
- "tests/assets/pyvista_mesh.vtu",
23
- "tests/assets/generate_assets.py",
24
- "tests/assets/pyvista_plot.png",
25
- "tests/assets/pyvista_mesh_no_fields.vtu",
26
- "tests/assets/xdmf_mesh.xdmf",
27
11
  "solidipes_solid_mech_plugin/__init__.py",
28
12
  "solidipes_solid_mech_plugin/config.py",
29
- "solidipes_solid_mech_plugin/ontologies/solidipes_solid_mech.py",
30
- "solidipes_solid_mech_plugin/loaders/parse_inp.py",
13
+ "solidipes_solid_mech_plugin/loaders/__init__.py",
31
14
  "solidipes_solid_mech_plugin/loaders/abaqus.py",
15
+ "solidipes_solid_mech_plugin/loaders/abaqus_python_code.py",
16
+ "solidipes_solid_mech_plugin/loaders/exodus.py",
32
17
  "solidipes_solid_mech_plugin/loaders/geo_msh.py",
33
- "solidipes_solid_mech_plugin/loaders/pyvista_mesh.py",
34
- "solidipes_solid_mech_plugin/loaders/__init__.py",
35
18
  "solidipes_solid_mech_plugin/loaders/geof_mesh.py",
36
- "solidipes_solid_mech_plugin/loaders/xdmf.py",
37
19
  "solidipes_solid_mech_plugin/loaders/meshio.py",
38
- "solidipes_solid_mech_plugin/loaders/abaqus_python_code.py",
20
+ "solidipes_solid_mech_plugin/loaders/parse_inp.py",
21
+ "solidipes_solid_mech_plugin/loaders/pyvista_mesh.py",
22
+ "solidipes_solid_mech_plugin/loaders/xdmf.py",
23
+ "solidipes_solid_mech_plugin/ontologies/solidipes_solid_mech.py",
39
24
  "solidipes_solid_mech_plugin/viewers/__init__.py",
25
+ "solidipes_solid_mech_plugin/viewers/pyvista_plotter.py",
40
26
  "solidipes_solid_mech_plugin/viewers/xdmf.py",
41
- "solidipes_solid_mech_plugin/viewers/pyvista_plotter.py"
27
+ "tests/assets/generate_assets.py",
28
+ "tests/assets/pyvista_mesh.vtu",
29
+ "tests/assets/pyvista_mesh_no_fields.vtu",
30
+ "tests/assets/pyvista_plot.png",
31
+ "tests/assets/test_mesh_loaded.png",
32
+ "tests/assets/xdmf_mesh.h5",
33
+ "tests/assets/xdmf_mesh.xdmf",
34
+ "tests/conftest.py",
35
+ "tests/helpers/utils.py",
36
+ "tests/test_exodus_mesh.py",
37
+ "tests/test_load_modules.py",
38
+ "tests/test_ontologies.py",
39
+ "tests/test_pyvista_mesh.py",
40
+ "tests/test_pyvista_plotter.py",
41
+ "tests/test_streamlit_pyvista.py",
42
+ "tests/test_xdmf_mesh.py",
43
+ "uv.lock"
42
44
  ]
43
45
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.0.16",
3
+ "distance": 0,
4
+ "node": "gcfd62de0a703eb7a2bab88cd4f3655c523025f43",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-21"
8
+ }
@@ -0,0 +1,88 @@
1
+ import os
2
+
3
+ import pytest
4
+ from solidipes.loaders import load_file
5
+
6
+ meshio = pytest.importorskip("meshio")
7
+ netCDF4 = pytest.importorskip("netCDF4")
8
+
9
+
10
+ @pytest.fixture
11
+ def exodus_file_path(tmp_path, study_dir):
12
+ """Create a small Exodus II file with a nodal variable and return its path."""
13
+ import numpy as np
14
+
15
+ points = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]]
16
+ cells = [("quad", np.array([[0, 1, 2, 3]]))]
17
+ mesh = meshio.Mesh(points, cells, point_data={"v": np.array([0.1, 0.2, 0.3, 0.4])})
18
+
19
+ path = os.path.join(tmp_path, "test.e")
20
+ meshio.write(path, mesh, file_format="exodus")
21
+ return path
22
+
23
+
24
+ @pytest.fixture
25
+ def exodus_file(exodus_file_path):
26
+ file = load_file(exodus_file_path)
27
+ return file
28
+
29
+
30
+ def test_file_type(exodus_file):
31
+ """The Exodus loader is selected for .e files"""
32
+ from solidipes.loaders.file import File
33
+
34
+ assert isinstance(exodus_file, File)
35
+ assert type(exodus_file).__name__ == "Exodus"
36
+
37
+
38
+ def test_metadata(exodus_file):
39
+ assert exodus_file.n_nodes == 4
40
+ assert exodus_file.n_elements == 1
41
+ assert exodus_file.n_time_steps == 1
42
+ assert exodus_file.nodal_variable_names == ["v"]
43
+
44
+
45
+ def test_mesh(exodus_file):
46
+ import numpy as np
47
+ import pyvista as pv
48
+
49
+ mesh = exodus_file.mesh
50
+ assert isinstance(mesh, pv.UnstructuredGrid)
51
+ assert mesh.n_points == 4
52
+ assert mesh.n_cells == 1
53
+ assert "v" in mesh.array_names
54
+ assert np.allclose(mesh.point_data["v"], [0.1, 0.2, 0.3, 0.4])
55
+
56
+
57
+ def test_time_steps(exodus_file_path):
58
+ """Add a second time step and check that it can be selected"""
59
+ import numpy as np
60
+
61
+ with netCDF4.Dataset(exodus_file_path, "a") as nc:
62
+ var = nc.variables["vals_nod_var1"]
63
+ var[1] = np.array([0.5, 0.6, 0.7, 0.8])
64
+ nc.variables["time_whole"][1] = 1.0
65
+
66
+ exodus_file = load_file(exodus_file_path)
67
+ assert exodus_file.n_time_steps == 2
68
+
69
+ mesh_0 = exodus_file.mesh
70
+ assert np.allclose(mesh_0.point_data["v"], [0.1, 0.2, 0.3, 0.4])
71
+
72
+ exodus_file.select_frame(1)
73
+ mesh_1 = exodus_file.mesh
74
+ assert np.allclose(mesh_1.point_data["v"], [0.5, 0.6, 0.7, 0.8])
75
+
76
+
77
+ def test_validation(exodus_file):
78
+ assert exodus_file.is_valid
79
+
80
+
81
+ def test_inherited_netcdf(exodus_file):
82
+ """The netCDF loadable and metadata are inherited from the NetCDF loader"""
83
+ import netCDF4
84
+
85
+ assert isinstance(exodus_file.netcdf, netCDF4.Dataset)
86
+ assert exodus_file.dimensions["num_nodes"] == 4
87
+ assert exodus_file.global_attributes["title"].startswith("Created by meshio")
88
+ assert "coord" in exodus_file.variables
@@ -0,0 +1,59 @@
1
+ import solidipes as sp
2
+ import utils
3
+ from PIL import Image
4
+ from pixelmatch.contrib.PIL import pixelmatch
5
+
6
+ image_name = "pyvista_plot.png"
7
+ image_size = (1024, 768)
8
+
9
+
10
+ def start_xvfb(wait=3, window_size=None):
11
+ """Start the virtual framebuffer Xvfb.
12
+
13
+ Parameters
14
+ ----------
15
+ wait : float, optional
16
+ Time to wait for the virtual framebuffer to start. Set to 0
17
+ to disable wait.
18
+
19
+ window_size : list, optional
20
+ Window size of the virtual frame buffer. Defaults to ``[1024, 768]``.
21
+ """
22
+ import os
23
+ import time
24
+
25
+ if os.name != "posix":
26
+ raise OSError("`start_xvfb` is only supported on Linux")
27
+
28
+ if os.system("which Xvfb > /dev/null"):
29
+ raise OSError("Xvfb is not installed. Install it with: apt-get install xvfb libgl1-mesa-glx")
30
+
31
+ if window_size is None:
32
+ window_size = [1024, 768]
33
+ window_size_parm = f"{window_size[0]:d}x{window_size[1]:d}x24"
34
+ display_num = ":99"
35
+ os.system(f"Xvfb {display_num} -screen 0 {window_size_parm} > /dev/null 2>&1 &")
36
+ os.environ["DISPLAY"] = display_num
37
+ if wait:
38
+ time.sleep(wait)
39
+
40
+
41
+ def test_plotter(study_dir):
42
+ pyvista_file = sp.load_file(utils.get_asset_path("pyvista_mesh.vtu"))
43
+ test_image_path = study_dir / image_name
44
+ reference_image_path = utils.get_asset_path(image_name)
45
+
46
+ # Start virtual frame buffer, otherwise segfault
47
+ start_xvfb()
48
+
49
+ # Create a plotter and save the image
50
+ plotter = sp.viewer.PyvistaPlotter(off_screen=True)
51
+ plotter.add_mesh(pyvista_file)
52
+ plotter.show() # Not shown because off_screen=True
53
+ plotter.save(test_image_path)
54
+
55
+ test_image = Image.open(test_image_path)
56
+ reference_image = Image.open(reference_image_path)
57
+ diff = Image.new("RGB", image_size)
58
+ mismatch = pixelmatch(test_image, reference_image, diff)
59
+ assert mismatch == 0