Skip to content

Commit a272ec1

Browse files
committed
Enable Exodus geometry support
- Added Exodus support for the webviewer: hereby, the webviewer accounts for multiple sections ending with "GEOMETRY" for different fields - all element blocks are also taken into account when enhancing the discretization. As restricted by 4C, we only allow for a single file in all geometry sections. - Removed these geometry sections from the general sections within the webviewer, since these are not meant to be visualized. - Absolute paths for the exodus files are necessary due to VFileInput not knowing the local path of the file.
1 parent 49065a4 commit a272ec1

File tree

4 files changed

+433
-178
lines changed

4 files changed

+433
-178
lines changed

src/fourc_webviewer/fourc_webserver.py

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
import numpy as np
1111
import pyvista as pv
1212
from fourcipp import CONFIG
13+
from fourcipp.fourc_input import FourCInput
1314
from trame.app import get_server
1415
from trame.decorators import TrameApp, change, controller
1516

1617
import fourc_webviewer.pyvista_render as pv_render
1718
from fourc_webviewer.gui_utils import create_gui
1819
from fourc_webviewer.input_file_utils.fourc_yaml_file_visualization import (
19-
convert_to_vtu,
2020
function_plot_figure,
2121
)
2222
from fourc_webviewer.input_file_utils.io_utils import (
@@ -26,6 +26,10 @@
2626
write_fourc_yaml_file,
2727
)
2828
from fourc_webviewer.python_utils import convert_string2number, find_value_recursively
29+
from fourc_webviewer.read_geometry_from_file import (
30+
FourCGeometry,
31+
)
32+
2933

3034
# always set pyvista to plot off screen with Trame
3135
pv.OFF_SCREEN = True
@@ -102,10 +106,12 @@ def __init__(
102106
self.init_state_and_server_vars()
103107

104108
# convert file to vtu and create dedicated render objects
105-
self.state.vtu_path = convert_to_vtu(
106-
fourc_yaml_file,
107-
Path(self._server_vars["temp_dir_object"].name),
109+
fourc_geometry = FourCGeometry(
110+
fourc_yaml_file=fourc_yaml_file,
111+
temp_dir=Path(self._server_vars["temp_dir_object"].name),
108112
)
113+
self.state.vtu_path = fourc_geometry.vtu_file_path
114+
109115
if self.state.vtu_path == "":
110116
self.state.read_in_status = self.state.all_read_in_statuses[
111117
"vtu_conversion_error"
@@ -258,15 +264,15 @@ def update_pyvista_render_objects(self, init_rendering=False):
258264
)
259265

260266
# get coords of node with prescribed result description
261-
self._server_vars["pv_selected_result_description_node_coords"] = (
262-
self._server_vars["pv_mesh"].points[
263-
self.state.result_description_section[
264-
self.state.selected_result_description_id
265-
]["PARAMETERS"]["NODE"]
266-
- 1,
267-
:,
268-
]
269-
)
267+
self._server_vars[
268+
"pv_selected_result_description_node_coords"
269+
] = self._server_vars["pv_mesh"].points[
270+
self.state.result_description_section[
271+
self.state.selected_result_description_id
272+
]["PARAMETERS"]["NODE"]
273+
- 1,
274+
:,
275+
]
270276

271277
# update plotter / rendering
272278
pv_render.update_pv_plotter(
@@ -299,7 +305,14 @@ def init_general_sections_state_and_server_vars(self):
299305
self.state.json_schema = CONFIG.fourc_json_schema
300306

301307
# define substrings of section names to exclude
302-
substr_to_exclude = ["DESIGN", "TOPOLOGY", "ELEMENTS", "NODE", "FUNCT"]
308+
substr_to_exclude = [
309+
"DESIGN",
310+
"TOPOLOGY",
311+
"ELEMENTS",
312+
"NODE",
313+
"FUNCT",
314+
"GEOMETRY",
315+
]
303316
# define full section names to exclude
304317
sect_to_exclude = [
305318
"MATERIALS",
@@ -337,9 +350,9 @@ def init_general_sections_state_and_server_vars(self):
337350
self.state.general_sections[main_section_name] = {}
338351

339352
# add subsection
340-
self.state.general_sections[main_section_name][section_name] = (
341-
section_data
342-
)
353+
self.state.general_sections[main_section_name][
354+
section_name
355+
] = section_data
343356

344357
def sync_general_sections_from_state(self):
345358
"""Syncs the server-side general sections based on the current values
@@ -421,9 +434,9 @@ def init_materials_state_and_server_vars(self):
421434
):
422435
if mat_id in linked_material_indices_item:
423436
# add linked material indices
424-
mat_item_val["RELATIONSHIPS"]["LINKED MATERIALS"] = (
425-
linked_material_indices_item
426-
)
437+
mat_item_val["RELATIONSHIPS"][
438+
"LINKED MATERIALS"
439+
] = linked_material_indices_item
427440

428441
# add master material index
429442
mat_item_val["RELATIONSHIPS"]["MASTER MATERIAL"] = material_indices[
@@ -483,9 +496,9 @@ def sync_materials_sections_from_state(self):
483496
# write to server-side content
484497
self._server_vars["fourc_yaml_content"]["MATERIALS"] = new_materials_section
485498
if new_cloning_material_map_section:
486-
self._server_vars["fourc_yaml_content"]["CLONING MATERIAL MAP"] = (
487-
new_cloning_material_map_section
488-
)
499+
self._server_vars["fourc_yaml_content"][
500+
"CLONING MATERIAL MAP"
501+
] = new_cloning_material_map_section
489502

490503
def init_design_conditions_state_and_server_vars(self):
491504
"""Initialize the state and server variables for the design condition
@@ -677,9 +690,9 @@ def sync_result_description_section_from_state(self):
677690
new_result_description_section.append({field: params})
678691

679692
# set result description section on the server
680-
self._server_vars["fourc_yaml_content"]["RESULT DESCRIPTION"] = (
681-
new_result_description_section
682-
)
693+
self._server_vars["fourc_yaml_content"][
694+
"RESULT DESCRIPTION"
695+
] = new_result_description_section
683696

684697
def init_funct_state_and_server_vars(self):
685698
"""Initialize the state and server variables for the function
@@ -1058,6 +1071,7 @@ def click_convert_button(self, **kwargs):
10581071

10591072
with open(temp_fourc_yaml_file, "w") as f:
10601073
f.write(self.state.fourc_yaml_file["content"].decode("utf-8"))
1074+
# copy eventual exodus file as well
10611075

10621076
if self._server_vars["fourc_yaml_read_in_status"]:
10631077
self.state.read_in_status = self.state.all_read_in_statuses["success"]
@@ -1066,10 +1080,11 @@ def click_convert_button(self, **kwargs):
10661080
self.init_state_and_server_vars()
10671081

10681082
# convert to vtu
1069-
self.state.vtu_path = convert_to_vtu(
1070-
temp_fourc_yaml_file,
1071-
Path(self._server_vars["temp_dir_object"].name),
1083+
fourc_geometry = FourCGeometry(
1084+
fourc_yaml_file=temp_fourc_yaml_file,
1085+
temp_dir=Path(self._server_vars["temp_dir_object"].name),
10721086
)
1087+
self.state.vtu_path = fourc_geometry.vtu_file_path
10731088

10741089
# catch eventual conversion error
10751090
if self.state.vtu_path == "":

src/fourc_webviewer/input_file_utils/fourc_yaml_file_visualization.py

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,6 @@
99
import numpy as np
1010
import plotly.express as px
1111

12-
from fourc_webviewer.input_file_utils.io_utils import (
13-
add_fourc_yaml_file_data_to_dis,
14-
)
15-
16-
17-
def convert_to_vtu(fourc_yaml_file_path, temp_dir):
18-
"""Convert fourc yaml file to vtu.
19-
20-
Args:
21-
fourc_yaml_file_path (str, Path): Path to input file
22-
temp_dir (str, Path): Temp directory
23-
24-
Returns:
25-
str: Path to vtu file
26-
"""
27-
# define the vtu_file_path to have the same name as the input file, but the its directory is in './temp_files'
28-
vtu_file_path = str(Path(temp_dir) / f"{Path(fourc_yaml_file_path).stem}.vtu")
29-
30-
# convert yaml file to vtu file and return the path to the vtu file
31-
try:
32-
dis = lnmmeshio.read(str(fourc_yaml_file_path))
33-
34-
to_vtu(dis, vtu_file_path)
35-
except Exception as exc: # if file conversion not successful
36-
print(
37-
exc
38-
) # currently, we throw the lnmmeshio conversion error as terminal output
39-
vtu_file_path = ""
40-
41-
return vtu_file_path
42-
4312

4413
def function_plot_figure(state_data):
4514
"""Get function plot figure.
@@ -149,7 +118,7 @@ def funct_using_eval(x, y, z, t):
149118
# funct_string copy
150119
funct_string_copy = funct_string
151120

152-
# replace the defined functions in the funct_string with "np.<def_funct>"
121+
# replace the defined functions in the funct_string with "<def_funct>"
153122
for i in range(len(def_funct)):
154123
funct_string_copy = funct_string_copy.replace(
155124
def_funct[i], f"np.{def_funct[i]}"
@@ -174,25 +143,8 @@ def funct_using_eval(x, y, z, t):
174143
r"heaviside\((.*?)\)", r"heaviside(\1,0)", funct_string_copy
175144
) # usage of raw strings, (.*?) is a non greedy capturing, and \1 replaces the captured value
176145

177-
return ne.evaluate(funct_string_copy) # this parses string in as a function
146+
return eval(
147+
funct_string_copy, {"np": np}, {}
148+
) # this parses string in as a function
178149

179150
return np.frompyfunc(funct_using_eval, 4, 1)
180-
181-
182-
def to_vtu(dis, vtu_file: str, override=True):
183-
"""Discretization to vtu.
184-
185-
Args:
186-
dis (lnmmeshio.Discretization): Discretization object
187-
vtu_file (str): Path to vtu file
188-
override (bool, optional): Overwrite existing file. Defaults to True
189-
"""
190-
add_fourc_yaml_file_data_to_dis(dis)
191-
192-
# write case file
193-
lnmmeshio.write(
194-
vtu_file,
195-
dis,
196-
file_format="vtu",
197-
override=override,
198-
)

src/fourc_webviewer/input_file_utils/io_utils.py

Lines changed: 7 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -91,51 +91,6 @@ def write_fourc_yaml_file(fourc_yaml_content, new_fourc_yaml_file):
9191
return True
9292

9393

94-
def add_fourc_yaml_file_data_to_dis(dis):
95-
"""Adds further data contained within the yaml file (e.g. material id) to
96-
the discretization from lnmmeshio."""
97-
dis.compute_ids(zero_based=False)
98-
99-
# write node data
100-
for n in dis.nodes:
101-
# write node id
102-
n.data["node-id"] = n.id
103-
n.data["node-coords"] = n.coords
104-
105-
# write fibers
106-
for name, f in n.fibers.items():
107-
n.data["node-" + name] = f.fiber
108-
109-
# write dpoints
110-
for dp in n.pointnodesets:
111-
n.data["dpoint{0}".format(dp.id)] = 1.0
112-
113-
# write dlines
114-
for dl in n.linenodesets:
115-
n.data["dline{0}".format(dl.id)] = 1.0
116-
117-
# write dsurfs
118-
for ds in n.surfacenodesets:
119-
n.data["dsurf{0}".format(ds.id)] = 1.0
120-
121-
# write dvols
122-
for dv in n.volumenodesets:
123-
n.data["dvol{0}".format(dv.id)] = 1.0
124-
125-
# write element data
126-
for elements in dis.elements.values():
127-
for ele in elements:
128-
ele.data["element-id"] = ele.id
129-
130-
# write mat
131-
if "MAT" in ele.options:
132-
ele.data["element-material"] = int(ele.options["MAT"])
133-
134-
# write fibers
135-
for name, f in ele.fibers.items():
136-
ele.data["element-" + name] = f.fiber
137-
138-
13994
def find_linked_materials(material_id, material_items):
14095
"""Find materials linked with a material number / id recursively based on
14196
given material specifiers (parameters which refer to other materials). This
@@ -237,7 +192,9 @@ def get_main_and_clustered_section_names(sections_list):
237192
# append the main section "FUNCTIONS"
238193
main_sections.append("FUNCTIONS")
239194

240-
clustered_sections_to_be_added = [] # list of clustered sections to be added
195+
clustered_sections_to_be_added = (
196+
[]
197+
) # list of clustered sections to be added
241198
# add current element to clustered sections and remove it from sections
242199
clustered_sections_to_be_added.append(sections.pop(0))
243200

@@ -252,7 +209,6 @@ def get_main_and_clustered_section_names(sections_list):
252209

253210
# add the clustered sections
254211
clustered_sections.append(clustered_sections_to_be_added)
255-
256212
else: # no
257213
# check if element already in main sections -> SHOULD NEVER HAPPEN
258214
if sections[0].split("/")[0] in main_sections:
@@ -359,7 +315,9 @@ def get_master_and_linked_material_indices(materials_section):
359315
master_mat_indices = [mat_item["MAT"] for mat_item in materials_section]
360316

361317
# check whether some of the master materials are actually related to others, and eliminate them from the master material index list
362-
linked_mat_indices = [] # list of linked material indices for each of the "master" materials: [<list of linked materials for "MASTER" 1>, <list of linked materials for "MASTER" 2>,... ]
318+
linked_mat_indices = (
319+
[]
320+
) # list of linked material indices for each of the "master" materials: [<list of linked materials for "MASTER" 1>, <list of linked materials for "MASTER" 2>,... ]
363321
for master_mat_index in master_mat_indices:
364322
linked_mat_indices.append(
365323
find_linked_materials(
@@ -381,9 +339,7 @@ def get_master_and_linked_material_indices(materials_section):
381339
del master_mat_indices[next_master_list_ind]
382340
del linked_mat_indices[next_master_list_ind]
383341
# next_master_ind stays the same
384-
elif set(
385-
linked_mat_indices[curr_master_list_ind]
386-
).issubset(
342+
elif set(linked_mat_indices[curr_master_list_ind]).issubset(
387343
set(linked_mat_indices[next_master_list_ind])
388344
): # this means that the current element is not truly a master -> has to be eliminated and we also break out of the for-loop
389345
del master_mat_indices[curr_master_list_ind]

0 commit comments

Comments
 (0)