1414from trame .app import get_server
1515from trame .decorators import TrameApp , change , controller
1616
17- import fourc_webviewer .pyvista_render as pv_render
1817from fourc_webviewer .gui_utils import create_gui
1918from fourc_webviewer .input_file_utils .fourc_yaml_file_visualization import (
2019 function_plot_figure ,
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
3639pv .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
0 commit comments