1515from trame .app import get_server
1616from trame .decorators import TrameApp , change , controller
1717
18- import fourc_webviewer .pyvista_render as pv_render
1918from fourc_webviewer .gui_utils import create_gui
2019from fourc_webviewer .input_file_utils .fourc_yaml_file_visualization import (
2120 function_plot_figure ,
4039 FourCGeometry ,
4140)
4241
42+ # Global variable
43+ # factor which scales the spheres used to represent nodal design conditions and result descriptions with respect to the problem length scale
44+ PV_SPHERE_FRAC_SCALE = 1.0 / 45.0
45+
4346# always set pyvista to plot off screen with Trame
4447pv .OFF_SCREEN = True
4548
@@ -128,8 +131,7 @@ def __init__(
128131 self .state .read_in_status = self .state .all_read_in_statuses [
129132 "vtu_conversion_error"
130133 ]
131-
132- self .update_pyvista_render_objects (init_rendering = True )
134+ self .init_pyvista_render_objects ()
133135
134136 # create ui
135137 create_gui (self .server , self ._server_vars ["render_window" ])
@@ -151,7 +153,7 @@ def init_state_and_server_vars(self):
151153 """Initialize state variables (reactive shared state) and server-side
152154 only variables, particularly the ones related to the fourc yaml
153155 content."""
154-
156+ self . _actors = {}
155157 ### --- self.state VARIABLES FOR INPUT FILE CONTENT --- ###
156158 # name of the 4C yaml file
157159 self .state .fourc_yaml_name = self ._server_vars ["fourc_yaml_name" ]
@@ -242,61 +244,160 @@ def sync_server_vars_from_state(self):
242244 self .sync_result_description_section_from_state ()
243245 self .sync_funct_section_from_state ()
244246
245- def update_pyvista_render_objects (self , init_rendering = False ):
246- """Update/ initialize pyvista view objects (reader, thresholds, global
247- COS, ...) for the rendered window. The saved vtu file path is hereby
248- utilized.
247+ def get_problem_length_scale (self , pv_mesh ):
248+ """Compute problem length scale from the bounds of the considered
249+ pyvista mesh.
249250
250251 Args:
251- init_rendering (bool): perform initialization tasks? (True:
252- yes | False: no -> only updating)
252+ pv_mesh (pyvista.UnstructuredGrid): geometry mesh
253+ Returns:
254+ float: maximum coordinate bound difference in 3-dimensions
253255 """
254256
255- # initialization tasks
256- if init_rendering :
257- # initialization: declare render window as a pyvista plotter
257+ # get maximum bound difference as the problem length scale
258+ return max (
259+ pv_mesh .bounds [1 ] - pv_mesh .bounds [0 ],
260+ pv_mesh .bounds [3 ] - pv_mesh .bounds [2 ],
261+ pv_mesh .bounds [5 ] - pv_mesh .bounds [4 ],
262+ )
263+
264+ def init_pyvista_render_objects (self ):
265+ """Initialize pyvista view objects (reader, thresholds, global COS,
266+ ...) for the rendered window.
267+
268+ The saved vtu file path is hereby utilized.
269+ """
270+ if "render_window" not in self ._actors :
258271 self ._server_vars ["render_window" ] = pv .Plotter ()
259272
273+ self ._server_vars ["render_window" ].clear_actors ()
274+
275+ problem_mesh = pv .read (self .state .vtu_path )
260276 # get problem mesh
261- self ._server_vars ["pv_mesh" ] = pv .read (self .state .vtu_path )
277+ self ._actors ["problem_mesh" ] = self ._server_vars ["render_window" ].add_mesh (
278+ problem_mesh , color = "bisque" , opacity = 0.2 , render = False
279+ )
262280
263281 # get mesh of the selected material
264- master_mat_ind = self .determine_master_mat_ind_for_current_selection ()
265- self ._server_vars ["pv_selected_material_mesh" ] = self ._server_vars [
266- "pv_mesh"
267- ].threshold (
268- value = (master_mat_ind - 0.05 , master_mat_ind + 0.05 ),
269- scalars = "element-material" ,
270- )
282+ self ._actors ["material_meshes" ] = {}
283+ for material in self .state .materials_section .keys ():
284+ # get meshes of materials
285+ master_mat_ind = self .determine_master_mat_ind_for_material (material )
286+ self ._actors ["material_meshes" ][material ] = self ._server_vars [
287+ "render_window"
288+ ].add_mesh (
289+ problem_mesh .threshold (
290+ value = (master_mat_ind - 0.05 , master_mat_ind + 0.05 ),
291+ scalars = "element-material" ,
292+ ),
293+ color = "darkorange" ,
294+ opacity = 0.7 ,
295+ render = False ,
296+ )
271297
272- # get nodes of the selected condition geometry + entity
273- self ._server_vars ["pv_selected_dc_geometry_entity" ] = pv .PointSet (
274- self ._server_vars ["pv_mesh" ]
275- ).threshold (
276- value = 1.0 ,
277- scalars = f"d{ self .state .selected_dc_geometry_type .lower ()} { self .state .selected_dc_entity .replace ('E' , '' )} " ,
278- preference = "point" ,
279- )
298+ all_dc_entities = [
299+ {"entity" : k , "geometry_type" : sec_name }
300+ for sec_name , sec in self .state .dc_sections .items ()
301+ for k in sec # == sec.keys()
302+ ]
303+ self ._actors ["dc_geometry_entities" ] = {}
304+ # get nodes of the selected condition geometries + entities
305+ for dc_entity in all_dc_entities :
306+ points = problem_mesh .threshold (
307+ value = 1.0 ,
308+ scalars = f"d{ dc_entity ['geometry_type' ].lower ()} { dc_entity ['entity' ].replace ('E' , '' )} " ,
309+ preference = "point" ,
310+ ).points
311+
312+ if points .size :
313+ pts = pv .PolyData (points )
314+ r = (
315+ self .get_problem_length_scale (self ._actors ["problem_mesh" ])
316+ * PV_SPHERE_FRAC_SCALE
317+ )
280318
281- # get coords of node with prescribed result description
282- self ._server_vars ["pv_selected_result_description_node_coords" ] = (
283- self ._server_vars ["pv_mesh" ].points [
284- self .state .result_description_section [
285- self .state .selected_result_description_id
286- ]["PARAMETERS" ]["NODE" ]
287- - 1 ,
319+ sphere = pv .Sphere (radius = r , theta_resolution = 5 , phi_resolution = 5 )
320+
321+ glyphs = pts .glyph (
322+ geom = sphere , scale = False , orient = False
323+ ) # in PolyData
324+ self ._actors ["dc_geometry_entities" ][
325+ (dc_entity ["entity" ], dc_entity ["geometry_type" ])
326+ ] = self ._server_vars ["render_window" ].add_mesh (
327+ glyphs ,
328+ color = "navy" ,
329+ opacity = 1.0 ,
330+ render = False ,
331+ )
332+
333+ self ._actors ["result_description_nodes" ] = {}
334+ all_result_descriptions = self .state .result_description_section .keys ()
335+
336+ for dc in all_result_descriptions :
337+ node_coords = problem_mesh .points [
338+ self .state .result_description_section [dc ]["PARAMETERS" ]["NODE" ] - 1 ,
288339 :,
289340 ]
290- )
341+ self ._actors ["result_description_nodes" ][dc ] = self ._server_vars [
342+ "render_window"
343+ ].add_mesh (
344+ pv .Sphere (
345+ center = node_coords ,
346+ radius = self .get_problem_length_scale (problem_mesh )
347+ * PV_SPHERE_FRAC_SCALE ,
348+ ),
349+ color = "deepskyblue" ,
350+ render = False ,
351+ )
352+ self .update_pyvista_render_objects ()
291353
292- # update plotter / rendering
293- pv_render .update_pv_plotter (
294- self ._server_vars ["render_window" ],
295- self ._server_vars ["pv_mesh" ],
296- self ._server_vars ["pv_selected_material_mesh" ],
297- self ._server_vars ["pv_selected_dc_geometry_entity" ],
298- self ._server_vars ["pv_selected_result_description_node_coords" ],
299- )
354+ def update_pyvista_render_objects (self ):
355+ """Update/ initialize pyvista view objects (reader, thresholds, global
356+ COS, ...) for the rendered window.
357+
358+ The saved vtu file path is hereby utilized.
359+ """
360+ legend_items = []
361+
362+ for dc in self ._actors ["result_description_nodes" ].values ():
363+ dc .SetVisibility (False )
364+ if (
365+ self .state .selected_main_section_name == "RESULT DESCRIPTION"
366+ and self .state .selected_result_description_id
367+ and self .state .selected_result_description_id
368+ in self ._actors ["result_description_nodes" ].keys ()
369+ ):
370+ self ._actors ["result_description_nodes" ][
371+ self .state .selected_result_description_id
372+ ].SetVisibility (True )
373+ legend_items .append (("Selected result description" , "deepskyblue" ))
374+
375+ for rd in self ._actors ["dc_geometry_entities" ].values ():
376+ rd .SetVisibility (False )
377+ if (
378+ self .state .selected_main_section_name == "DESIGN CONDITIONS"
379+ and self .state .selected_dc_entity
380+ and self .state .selected_dc_geometry_type
381+ ):
382+ self ._actors ["dc_geometry_entities" ][
383+ (self .state .selected_dc_entity , self .state .selected_dc_geometry_type )
384+ ].SetVisibility (True )
385+ legend_items .append (("Selected design condition" , "navy" ))
386+
387+ for mat in self ._actors ["material_meshes" ].values ():
388+ mat .SetVisibility (False )
389+ if (
390+ self .state .selected_material
391+ and self .state .selected_main_section_name == "MATERIALS"
392+ ):
393+ self ._actors ["material_meshes" ][self .state .selected_material ].SetVisibility (
394+ True
395+ )
396+ legend_items .append (("Selected material" , "orange" ))
397+
398+ self ._server_vars ["render_window" ].remove_legend ()
399+ if legend_items :
400+ self ._server_vars ["render_window" ].add_legend (labels = legend_items )
300401
301402 def init_general_sections_state_and_server_vars (self ):
302403 """Get the general sections and cluster them into subsections. For
@@ -966,6 +1067,9 @@ def change_selected_main_section_name(self, selected_main_section_name, **kwargs
9661067 selected_main_section_name
9671068 ]["subsections" ][0 ]
9681069
1070+ # update plotter / render objects
1071+ self .update_pyvista_render_objects ()
1072+
9691073 @change ("selected_section_name" )
9701074 def change_selected_section_name (self , selected_section_name , ** kwargs ):
9711075 """Reaction to change of state.selected_section_name."""
@@ -1191,7 +1295,7 @@ def click_convert_button(self, **kwargs):
11911295 ]
11921296 else :
11931297 # reset view
1194- self .update_pyvista_render_objects ()
1298+ self .init_pyvista_render_objects ()
11951299 self ._server_vars ["render_window" ].reset_camera ()
11961300 self .ctrl .view_reset_camera ()
11971301 self .ctrl .view_update ()
@@ -1259,21 +1363,20 @@ def convert_string2num_all_sections(self):
12591363 self .state .result_description_section
12601364 )
12611365
1262- def determine_master_mat_ind_for_current_selection (self ):
1263- """Determines the real master/source material of the currently selected
1264- material. Accounts for CLONING MATERIAL MAP by going one step further
1265- and checking for the real source material recursively (important in
1266- multi-field problem settings, e.g., in SSTI, the procedure finds the
1267- structural material).
1366+ def determine_master_mat_ind_for_material (self , material ):
1367+ """Determines the real master/source material of a material. Accounts
1368+ for CLONING MATERIAL MAP by going one step further and checking for the
1369+ real source material recursively (important in multi-field problem
1370+ settings, e.g., in SSTI, the procedure finds the structural material).
12681371
12691372 Returns:
12701373 int: id of the real master material of the currently
12711374 selected material.
12721375 """
12731376 # get id of the master material
1274- master_mat_id = self .state .materials_section [self . state . selected_material ][
1275- "RELATIONSHIPS "
1276- ][ "MASTER MATERIAL" ]
1377+ master_mat_id = self .state .materials_section [material ][ "RELATIONSHIPS" ][
1378+ "MASTER MATERIAL "
1379+ ]
12771380
12781381 # it could now be that the master material is a TARGET material
12791382 # during cloning material map (and its master might be also a
0 commit comments