Skip to content

Commit 4d54e0b

Browse files
committed
optimize actor handling for performance
1 parent 2b618a5 commit 4d54e0b

File tree

2 files changed

+146
-131
lines changed

2 files changed

+146
-131
lines changed

src/fourc_webviewer/fourc_webserver.py

Lines changed: 145 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ def __init__(
117117
self.state.read_in_status = self.state.all_read_in_statuses[
118118
"vtu_conversion_error"
119119
]
120-
121-
self.update_pyvista_render_objects(init_rendering=True)
120+
self.init_pyvista_render_objects()
122121

123122
# create ui
124123
create_gui(self.server, self._server_vars["render_window"])
@@ -140,7 +139,7 @@ def init_state_and_server_vars(self):
140139
"""Initialize state variables (reactive shared state) and server-side
141140
only variables, particularly the ones related to the fourc yaml
142141
content."""
143-
142+
self._actors = {}
144143
### --- self.state VARIABLES FOR INPUT FILE CONTENT --- ###
145144
# name of the 4C yaml file
146145
self.state.fourc_yaml_name = self._server_vars["fourc_yaml_name"]
@@ -228,61 +227,149 @@ def sync_server_vars_from_state(self):
228227
self.sync_result_description_section_from_state()
229228
self.sync_funct_section_from_state()
230229

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.
230+
def init_pyvista_render_objects(self):
231+
"""Initialize pyvista view objects (reader, thresholds, global COS,
232+
...) for the rendered window.
235233
236-
Args:
237-
init_rendering (bool): perform initialization tasks? (True:
238-
yes | False: no -> only updating)
234+
The saved vtu file path is hereby utilized.
239235
"""
240-
241-
# initialization tasks
242-
if init_rendering:
243-
# initialization: declare render window as a pyvista plotter
236+
if "render_window" not in self._actors:
244237
self._server_vars["render_window"] = pv.Plotter()
245238

239+
self._server_vars["render_window"].clear_actors()
240+
241+
problem_mesh = pv.read(self.state.vtu_path)
246242
# get problem mesh
247-
self._server_vars["pv_mesh"] = pv.read(self.state.vtu_path)
243+
self._actors["problem_mesh"] = self._server_vars["render_window"].add_mesh(
244+
problem_mesh, color="bisque", opacity=0.2, render=False
245+
)
248246

249247
# 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-
)
248+
self._actors["material_meshes"] = {}
249+
for material in self.state.materials_section.keys():
250+
# get meshes of materials
251+
master_mat_ind = self.determine_master_mat_ind_for_material(material)
252+
self._actors["material_meshes"][material] = self._server_vars[
253+
"render_window"
254+
].add_mesh(
255+
problem_mesh.threshold(
256+
value=(master_mat_ind - 0.05, master_mat_ind + 0.05),
257+
scalars="element-material",
258+
),
259+
color="darkorange",
260+
opacity=0.7,
261+
render=False,
262+
)
257263

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-
)
264+
all_dc_entities = [
265+
{"entity": k, "geometry_type": sec_name}
266+
for sec_name, sec in self.state.dc_sections.items()
267+
for k in sec # == sec.keys()
268+
]
269+
self._actors["dc_geometry_entities"] = {}
270+
# get nodes of the selected condition geometries + entities
271+
for dc_entity in all_dc_entities:
272+
points = problem_mesh.threshold(
273+
value=1.0,
274+
scalars=f"d{dc_entity['geometry_type'].lower()}{dc_entity['entity'].replace('E', '')}",
275+
preference="point",
276+
).points
277+
278+
if points.size:
279+
pts = pv.PolyData(points)
280+
r = (
281+
pv_render.get_problem_length_scale(self._actors["problem_mesh"])
282+
* pv_render.PV_SPHERE_FRAC_SCALE
283+
)
284+
285+
# niedrige Auflösung = viel schneller
286+
sphere = pv.Sphere(radius=r, theta_resolution=5, phi_resolution=5)
287+
288+
glyphs = pts.glyph(
289+
geom=sphere, scale=False, orient=False
290+
) # in PolyData
291+
self._actors["dc_geometry_entities"][
292+
(dc_entity["entity"], dc_entity["geometry_type"])
293+
] = self._server_vars["render_window"].add_mesh(
294+
glyphs,
295+
color="navy",
296+
opacity=1.0,
297+
render=False,
298+
)
266299

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,
300+
self._actors["result_description_nodes"] = {}
301+
all_result_descriptions = self.state.result_description_section.keys()
302+
303+
for dc in all_result_descriptions:
304+
node_coords = problem_mesh.points[
305+
self.state.result_description_section[dc]["PARAMETERS"]["NODE"] - 1,
274306
:,
275307
]
276-
)
308+
self._actors["result_description_nodes"][dc] = self._server_vars[
309+
"render_window"
310+
].add_mesh(
311+
pv.Sphere(
312+
center=node_coords,
313+
radius=pv_render.get_problem_length_scale(problem_mesh)
314+
* pv_render.PV_SPHERE_FRAC_SCALE,
315+
),
316+
color="deepskyblue",
317+
render=False,
318+
)
319+
self.update_pyvista_render_objects()
277320

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-
)
321+
def update_pyvista_render_objects(self):
322+
"""Update/ initialize pyvista view objects (reader, thresholds, global
323+
COS, ...) for the rendered window. The saved vtu file path is hereby
324+
utilized.
325+
326+
Args:
327+
init_rendering (bool): perform initialization tasks? (True:
328+
yes(also when loading a new file) | False: no -> only updating)
329+
"""
330+
# 70ms for tutorial_solid.4C.yaml DESIGN CONDITIONS/SURF/E2 on LasseLaptop
331+
# 0ms for tutorial_solid.4C.yaml PROBLEM TYPE on LasseLaptop
332+
legend_items = []
333+
334+
for dc in self._actors["result_description_nodes"].values():
335+
dc.SetVisibility(False)
336+
if (
337+
self.state.selected_result_description_id
338+
in self._actors["result_description_nodes"].keys()
339+
and self.state.selected_result_description_id
340+
and self.state.selected_main_section_name == "RESULT DESCRIPTION"
341+
):
342+
self._actors["result_description_nodes"][
343+
self.state.selected_result_description_id
344+
].SetVisibility(True)
345+
legend_items.append(("Selected result description", "deepskyblue"))
346+
347+
for rd in self._actors["dc_geometry_entities"].values():
348+
rd.SetVisibility(False)
349+
if (
350+
self.state.selected_dc_entity
351+
and self.state.selected_dc_geometry_type
352+
and self.state.selected_main_section_name == "DESIGN CONDITIONS"
353+
):
354+
self._actors["dc_geometry_entities"][
355+
(self.state.selected_dc_entity, self.state.selected_dc_geometry_type)
356+
].SetVisibility(True)
357+
legend_items.append(("Selected design condition", "navy"))
358+
359+
for mat in self._actors["material_meshes"].values():
360+
mat.SetVisibility(False)
361+
if (
362+
self.state.selected_material
363+
and self.state.selected_main_section_name == "MATERIALS"
364+
):
365+
self._actors["material_meshes"][self.state.selected_material].SetVisibility(
366+
True
367+
)
368+
legend_items.append(("Selected material", "orange"))
369+
370+
self._server_vars["render_window"].remove_legend()
371+
if legend_items:
372+
self._server_vars["render_window"].add_legend(labels=legend_items)
286373

287374
def init_general_sections_state_and_server_vars(self):
288375
"""Get the general sections and cluster them into subsections. For
@@ -951,6 +1038,9 @@ def change_selected_main_section_name(self, selected_main_section_name, **kwargs
9511038
selected_main_section_name
9521039
]["subsections"][0]
9531040

1041+
# update plotter / render objects
1042+
self.update_pyvista_render_objects()
1043+
9541044
@change("selected_material")
9551045
def change_selected_material(self, selected_material, **kwargs):
9561046
"""Reaction to change of state.selected_material."""
@@ -1148,7 +1238,7 @@ def click_convert_button(self, **kwargs):
11481238
]
11491239
else:
11501240
# reset view
1151-
self.update_pyvista_render_objects()
1241+
self.init_pyvista_render_objects()
11521242
self._server_vars["render_window"].reset_camera()
11531243
self.ctrl.view_reset_camera()
11541244
self.ctrl.view_update()
@@ -1189,21 +1279,20 @@ def convert_string2num_all_sections(self):
11891279
self.state.result_description_section
11901280
)
11911281

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).
1282+
def determine_master_mat_ind_for_material(self, material):
1283+
"""Determines the real master/source material of a material. Accounts
1284+
for CLONING MATERIAL MAP by going one step further and checking for the
1285+
real source material recursively (important in multi-field problem
1286+
settings, e.g., in SSTI, the procedure finds the structural material).
11981287
11991288
Returns:
12001289
int: id of the real master material of the currently
12011290
selected material.
12021291
"""
12031292
# get id of the master material
1204-
master_mat_id = self.state.materials_section[self.state.selected_material][
1205-
"RELATIONSHIPS"
1206-
]["MASTER MATERIAL"]
1293+
master_mat_id = self.state.materials_section[material]["RELATIONSHIPS"][
1294+
"MASTER MATERIAL"
1295+
]
12071296

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

src/fourc_webviewer/pyvista_render.py

Lines changed: 1 addition & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,8 @@
11
"""Import modules."""
22

3-
import pyvista as pv
4-
from pyvista.trame.ui import plotter_ui
5-
63
# Global variable
74
# factor which scales the spheres used to represent nodal design conditions and result descriptions with respect to the problem length scale
8-
PV_SPHERE_FRAC_SCALE = 1.0 / 50.0
9-
10-
11-
def update_pv_plotter(
12-
pv_plotter,
13-
mesh,
14-
selected_material_mesh,
15-
selected_dc_geometry_entity,
16-
selected_result_description_node_coords,
17-
):
18-
"""Updates the pyvista plotter for the GUI.
19-
20-
Args:
21-
mesh (pyvista.UnstructuredGrid): problem mesh
22-
selected_material_mesh (pyvista.UnstructuredGrid): mesh
23-
component with
24-
the selected
25-
material.
26-
selected_dc_geometry_entity (pyvista.PointSet): set of points of
27-
the geometric
28-
entity for the
29-
current design
30-
condition selection.
31-
selected_result_description_node_coords (pyvista.pyvista_ndarray): array of
32-
points (nodes) where the selected result description is prescribed.
33-
Returns:
34-
pyvista.Plotter(): plotter object to be integrated in the GUI
35-
"""
36-
37-
# clear plotter actors
38-
pv_plotter.clear_actors()
39-
40-
# add mesh to plotter
41-
pv_plotter.add_mesh(mesh, color="bisque", opacity=0.2)
42-
43-
# add selected material mesh to plotter
44-
pv_plotter.add_mesh(
45-
selected_material_mesh,
46-
color="darkorange",
47-
opacity=0.7,
48-
label="Selected material",
49-
)
50-
51-
# add selected design condition mesh to plotter
52-
dc_spheres = pv.MultiBlock()
53-
for i, point in enumerate(selected_dc_geometry_entity.points):
54-
sphere = pv.Sphere(
55-
center=point, radius=get_problem_length_scale(mesh) * PV_SPHERE_FRAC_SCALE
56-
)
57-
dc_spheres.append(sphere)
58-
pv_plotter.add_mesh(
59-
dc_spheres,
60-
color="navy",
61-
opacity=1.0,
62-
render_points_as_spheres=True,
63-
label="Selected design condition",
64-
)
65-
66-
# add selected result description node to plotter
67-
pv_plotter.add_mesh(
68-
pv.Sphere(
69-
center=selected_result_description_node_coords,
70-
radius=get_problem_length_scale(mesh) * PV_SPHERE_FRAC_SCALE,
71-
),
72-
color="deepskyblue",
73-
label="Selected result description",
74-
)
75-
76-
# add plotter legend
77-
pv_plotter.add_legend()
78-
79-
return pv_plotter
5+
PV_SPHERE_FRAC_SCALE = 1.0 / 45.0
806

817

828
def get_problem_length_scale(pv_mesh):

0 commit comments

Comments
 (0)