Skip to content

Commit ac7cdcd

Browse files
committed
plot improvements and optimization
1 parent 2b618a5 commit ac7cdcd

File tree

2 files changed

+158
-152
lines changed

2 files changed

+158
-152
lines changed

src/fourc_webviewer/fourc_webserver.py

Lines changed: 158 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from trame.app import get_server
1515
from trame.decorators import TrameApp, change, controller
1616

17-
import fourc_webviewer.pyvista_render as pv_render
1817
from fourc_webviewer.gui_utils import create_gui
1918
from fourc_webviewer.input_file_utils.fourc_yaml_file_visualization import (
2019
function_plot_figure,
@@ -32,6 +31,10 @@
3231
FourCGeometry,
3332
)
3433

34+
# Global variable
35+
# factor which scales the spheres used to represent nodal design conditions and result descriptions with respect to the problem length scale
36+
PV_SPHERE_FRAC_SCALE = 1.0 / 45.0
37+
3538
# always set pyvista to plot off screen with Trame
3639
pv.OFF_SCREEN = True
3740

@@ -117,8 +120,7 @@ def __init__(
117120
self.state.read_in_status = self.state.all_read_in_statuses[
118121
"vtu_conversion_error"
119122
]
120-
121-
self.update_pyvista_render_objects(init_rendering=True)
123+
self.init_pyvista_render_objects()
122124

123125
# create ui
124126
create_gui(self.server, self._server_vars["render_window"])
@@ -140,7 +142,7 @@ def init_state_and_server_vars(self):
140142
"""Initialize state variables (reactive shared state) and server-side
141143
only variables, particularly the ones related to the fourc yaml
142144
content."""
143-
145+
self._actors = {}
144146
### --- self.state VARIABLES FOR INPUT FILE CONTENT --- ###
145147
# name of the 4C yaml file
146148
self.state.fourc_yaml_name = self._server_vars["fourc_yaml_name"]
@@ -228,61 +230,160 @@ def sync_server_vars_from_state(self):
228230
self.sync_result_description_section_from_state()
229231
self.sync_funct_section_from_state()
230232

231-
def update_pyvista_render_objects(self, init_rendering=False):
232-
"""Update/ initialize pyvista view objects (reader, thresholds, global
233-
COS, ...) for the rendered window. The saved vtu file path is hereby
234-
utilized.
233+
def get_problem_length_scale(self, pv_mesh):
234+
"""Compute problem length scale from the bounds of the considered
235+
pyvista mesh.
235236
236237
Args:
237-
init_rendering (bool): perform initialization tasks? (True:
238-
yes | False: no -> only updating)
238+
pv_mesh (pyvista.UnstructuredGrid): geometry mesh
239+
Returns:
240+
float: maximum coordinate bound difference in 3-dimensions
239241
"""
240242

241-
# initialization tasks
242-
if init_rendering:
243-
# initialization: declare render window as a pyvista plotter
243+
# get maximum bound difference as the problem length scale
244+
return max(
245+
pv_mesh.bounds[1] - pv_mesh.bounds[0],
246+
pv_mesh.bounds[3] - pv_mesh.bounds[2],
247+
pv_mesh.bounds[5] - pv_mesh.bounds[4],
248+
)
249+
250+
def init_pyvista_render_objects(self):
251+
"""Initialize pyvista view objects (reader, thresholds, global COS,
252+
...) for the rendered window.
253+
254+
The saved vtu file path is hereby utilized.
255+
"""
256+
if "render_window" not in self._actors:
244257
self._server_vars["render_window"] = pv.Plotter()
245258

259+
self._server_vars["render_window"].clear_actors()
260+
261+
problem_mesh = pv.read(self.state.vtu_path)
246262
# get problem mesh
247-
self._server_vars["pv_mesh"] = pv.read(self.state.vtu_path)
263+
self._actors["problem_mesh"] = self._server_vars["render_window"].add_mesh(
264+
problem_mesh, color="bisque", opacity=0.2, render=False
265+
)
248266

249267
# get mesh of the selected material
250-
master_mat_ind = self.determine_master_mat_ind_for_current_selection()
251-
self._server_vars["pv_selected_material_mesh"] = self._server_vars[
252-
"pv_mesh"
253-
].threshold(
254-
value=(master_mat_ind - 0.05, master_mat_ind + 0.05),
255-
scalars="element-material",
256-
)
268+
self._actors["material_meshes"] = {}
269+
for material in self.state.materials_section.keys():
270+
# get meshes of materials
271+
master_mat_ind = self.determine_master_mat_ind_for_material(material)
272+
self._actors["material_meshes"][material] = self._server_vars[
273+
"render_window"
274+
].add_mesh(
275+
problem_mesh.threshold(
276+
value=(master_mat_ind - 0.05, master_mat_ind + 0.05),
277+
scalars="element-material",
278+
),
279+
color="darkorange",
280+
opacity=0.7,
281+
render=False,
282+
)
257283

258-
# get nodes of the selected condition geometry + entity
259-
self._server_vars["pv_selected_dc_geometry_entity"] = pv.PointSet(
260-
self._server_vars["pv_mesh"]
261-
).threshold(
262-
value=1.0,
263-
scalars=f"d{self.state.selected_dc_geometry_type.lower()}{self.state.selected_dc_entity.replace('E', '')}",
264-
preference="point",
265-
)
284+
all_dc_entities = [
285+
{"entity": k, "geometry_type": sec_name}
286+
for sec_name, sec in self.state.dc_sections.items()
287+
for k in sec # == sec.keys()
288+
]
289+
self._actors["dc_geometry_entities"] = {}
290+
# get nodes of the selected condition geometries + entities
291+
for dc_entity in all_dc_entities:
292+
points = problem_mesh.threshold(
293+
value=1.0,
294+
scalars=f"d{dc_entity['geometry_type'].lower()}{dc_entity['entity'].replace('E', '')}",
295+
preference="point",
296+
).points
297+
298+
if points.size:
299+
pts = pv.PolyData(points)
300+
r = (
301+
self.get_problem_length_scale(self._actors["problem_mesh"])
302+
* PV_SPHERE_FRAC_SCALE
303+
)
266304

267-
# get coords of node with prescribed result description
268-
self._server_vars["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,
305+
sphere = pv.Sphere(radius=r, theta_resolution=5, phi_resolution=5)
306+
307+
glyphs = pts.glyph(
308+
geom=sphere, scale=False, orient=False
309+
) # in PolyData
310+
self._actors["dc_geometry_entities"][
311+
(dc_entity["entity"], dc_entity["geometry_type"])
312+
] = self._server_vars["render_window"].add_mesh(
313+
glyphs,
314+
color="navy",
315+
opacity=1.0,
316+
render=False,
317+
)
318+
319+
self._actors["result_description_nodes"] = {}
320+
all_result_descriptions = self.state.result_description_section.keys()
321+
322+
for dc in all_result_descriptions:
323+
node_coords = problem_mesh.points[
324+
self.state.result_description_section[dc]["PARAMETERS"]["NODE"] - 1,
274325
:,
275326
]
276-
)
327+
self._actors["result_description_nodes"][dc] = self._server_vars[
328+
"render_window"
329+
].add_mesh(
330+
pv.Sphere(
331+
center=node_coords,
332+
radius=self.get_problem_length_scale(problem_mesh)
333+
* PV_SPHERE_FRAC_SCALE,
334+
),
335+
color="deepskyblue",
336+
render=False,
337+
)
338+
self.update_pyvista_render_objects()
277339

278-
# update plotter / rendering
279-
pv_render.update_pv_plotter(
280-
self._server_vars["render_window"],
281-
self._server_vars["pv_mesh"],
282-
self._server_vars["pv_selected_material_mesh"],
283-
self._server_vars["pv_selected_dc_geometry_entity"],
284-
self._server_vars["pv_selected_result_description_node_coords"],
285-
)
340+
def update_pyvista_render_objects(self):
341+
"""Update/ initialize pyvista view objects (reader, thresholds, global
342+
COS, ...) for the rendered window.
343+
344+
The saved vtu file path is hereby utilized.
345+
"""
346+
legend_items = []
347+
348+
for dc in self._actors["result_description_nodes"].values():
349+
dc.SetVisibility(False)
350+
if (
351+
self.state.selected_main_section_name == "RESULT DESCRIPTION"
352+
and self.state.selected_result_description_id
353+
and self.state.selected_result_description_id
354+
in self._actors["result_description_nodes"].keys()
355+
):
356+
self._actors["result_description_nodes"][
357+
self.state.selected_result_description_id
358+
].SetVisibility(True)
359+
legend_items.append(("Selected result description", "deepskyblue"))
360+
361+
for rd in self._actors["dc_geometry_entities"].values():
362+
rd.SetVisibility(False)
363+
if (
364+
self.state.selected_main_section_name == "DESIGN CONDITIONS"
365+
and self.state.selected_dc_entity
366+
and self.state.selected_dc_geometry_type
367+
):
368+
self._actors["dc_geometry_entities"][
369+
(self.state.selected_dc_entity, self.state.selected_dc_geometry_type)
370+
].SetVisibility(True)
371+
legend_items.append(("Selected design condition", "navy"))
372+
373+
for mat in self._actors["material_meshes"].values():
374+
mat.SetVisibility(False)
375+
if (
376+
self.state.selected_material
377+
and self.state.selected_main_section_name == "MATERIALS"
378+
):
379+
self._actors["material_meshes"][self.state.selected_material].SetVisibility(
380+
True
381+
)
382+
legend_items.append(("Selected material", "orange"))
383+
384+
self._server_vars["render_window"].remove_legend()
385+
if legend_items:
386+
self._server_vars["render_window"].add_legend(labels=legend_items)
286387

287388
def init_general_sections_state_and_server_vars(self):
288389
"""Get the general sections and cluster them into subsections. For
@@ -951,6 +1052,9 @@ def change_selected_main_section_name(self, selected_main_section_name, **kwargs
9511052
selected_main_section_name
9521053
]["subsections"][0]
9531054

1055+
# update plotter / render objects
1056+
self.update_pyvista_render_objects()
1057+
9541058
@change("selected_material")
9551059
def change_selected_material(self, selected_material, **kwargs):
9561060
"""Reaction to change of state.selected_material."""
@@ -1148,7 +1252,7 @@ def click_convert_button(self, **kwargs):
11481252
]
11491253
else:
11501254
# reset view
1151-
self.update_pyvista_render_objects()
1255+
self.init_pyvista_render_objects()
11521256
self._server_vars["render_window"].reset_camera()
11531257
self.ctrl.view_reset_camera()
11541258
self.ctrl.view_update()
@@ -1189,21 +1293,20 @@ def convert_string2num_all_sections(self):
11891293
self.state.result_description_section
11901294
)
11911295

1192-
def determine_master_mat_ind_for_current_selection(self):
1193-
"""Determines the real master/source material of the currently selected
1194-
material. Accounts for CLONING MATERIAL MAP by going one step further
1195-
and checking for the real source material recursively (important in
1196-
multi-field problem settings, e.g., in SSTI, the procedure finds the
1197-
structural material).
1296+
def determine_master_mat_ind_for_material(self, material):
1297+
"""Determines the real master/source material of a material. Accounts
1298+
for CLONING MATERIAL MAP by going one step further and checking for the
1299+
real source material recursively (important in multi-field problem
1300+
settings, e.g., in SSTI, the procedure finds the structural material).
11981301
11991302
Returns:
12001303
int: id of the real master material of the currently
12011304
selected material.
12021305
"""
12031306
# get id of the master material
1204-
master_mat_id = self.state.materials_section[self.state.selected_material][
1205-
"RELATIONSHIPS"
1206-
]["MASTER MATERIAL"]
1307+
master_mat_id = self.state.materials_section[material]["RELATIONSHIPS"][
1308+
"MASTER MATERIAL"
1309+
]
12071310

12081311
# it could now be that the master material is a TARGET material
12091312
# during cloning material map (and its master might be also a

src/fourc_webviewer/pyvista_render.py

Lines changed: 0 additions & 97 deletions
This file was deleted.

0 commit comments

Comments
 (0)