Source code for opstool.vis.ops_vis_pyvista

"""
Visualizing OpenSeesPy model based on pyvista
"""

import shelve
from typing import Union

import numpy as np
import pyvista as pv

from ..utils import check_file, shape_dict


[docs]class OpsVisPyvista: """A class to visualize OpenSeesPy model based on `pyvista <https://docs.pyvista.org/index.html>`_. Parameters ----------- point_size: float, default=1 The render size of node. line_width: float, default=3 The width of line element. colors_dict: dict, default: dict(point='#840000', line='#0165fc', face='#06c2ac', solid='#f48924', truss="#7552cc", link="#00c16e") The dict for ele color. theme: str, default='document' Plot theme for pyvista, optional 'default', 'paraview', 'document', 'dark'. color_map: str, default="jet" color map to display the cloud plot for pyvista. optional 'jet', 'rainbow', 'hot', 'afmhot', 'copper', 'winter', 'cool', 'coolwarm', 'gist_earth', 'bone', 'binary', 'gray', or any `Matplotlib colormap <https://matplotlib.org/stable/tutorials/colors/colormaps.html>`_ . on_notebook: bool, default=False Whether work in a notebook. results_dir: str, default="opstool_output" The dir that results saved. Returns -------- None """ def __init__( self, point_size: float = 1, line_width: float = 3, colors_dict: dict = None, theme: str = 'document', color_map: str = "jet", on_notebook: bool = False, results_dir: str = "opstool_output" ): # ------------------------------ self.point_size = point_size self.line_width = line_width self.title = "opstool" # Initialize the color dict colors = dict( point="#8f1402", line="#0504aa", face="#74a662", solid="#af884a", truss="#9a0eea", link="#c20078", ) if colors_dict is not None: colors.update(colors_dict) self.color_point = colors["point"] self.color_line = colors["line"] self.color_face = colors["face"] self.color_solid = colors["solid"] self.color_truss = colors["truss"] self.color_link = colors["link"] # ------------------------------------------------- self.theme = theme pv.set_plot_theme(theme) self.color_map = color_map self.notebook = on_notebook # ------------------------------------------------- self.out_dir = results_dir # ------------------------------------------------- self.bound_fact = 30
[docs] def model_vis( self, show_node_label: bool = False, show_ele_label: bool = False, show_local_crd: bool = False, label_size: float = 8, show_outline: bool = True, opacity: float = 1.0, save_fig: str = 'ModelVis.svg' ): """ Visualize the model in the current domain. Parameters ---------- show_node_label: bool, default=False Whether to display the node label. show_ele_label: bool, default=False Whether to display the ele label. show_local_crd: bool, default=False Whether to display the local coordinate system. label_size: float, default=8 The foontsize of node and ele label. show_outline: bool, default=True Whether to show the axis frame. opacity: float, default=1.0 Plane and solid element transparency. save_fig: str, default='ModelVis.svg' The file name to output. If False or None, the file will not be generated. The supported formats are: * '.svg' * '.eps' * '.ps' * '.pdf' * '.tex' Returns -------- None """ check_file(save_fig, ['.svg', '.eps', '.ps', 'pdf', '.tex']) filename = self.out_dir + '/ModelData' with shelve.open(filename) as db: model_info = db["ModelInfo"] cells = db["Cell"] plotter = pv.Plotter(notebook=self.notebook) point_plot = pv.PolyData(model_info["coord_no_deform"]) plotter.add_mesh( point_plot, color=self.color_point, point_size=self.point_size, render_points_as_spheres=True, ) if len(cells["truss"]) > 0: truss_plot = _generate_mesh( model_info["coord_no_deform"], cells["truss"], kind="line" ) plotter.add_mesh( truss_plot, color=self.color_truss, render_lines_as_tubes=True, line_width=self.line_width, ) if len(cells["link"]) > 0: link_plot = _generate_mesh( model_info["coord_no_deform"], cells["link"], kind="line" ) plotter.add_mesh( link_plot, color=self.color_link, render_lines_as_tubes=False, line_width=self.line_width / 5, ) if len(cells["beam"]) > 0: beam_plot = _generate_mesh( model_info["coord_no_deform"], cells["beam"], kind="line" ) plotter.add_mesh( beam_plot, color=self.color_line, render_lines_as_tubes=False, line_width=self.line_width, ) if len(cells["other_line"]) > 0: other_line_plot = _generate_mesh( model_info["coord_no_deform"], cells["other_line"], kind="line" ) plotter.add_mesh( other_line_plot, color=self.color_line, render_lines_as_tubes=True, line_width=self.line_width, ) if len(cells["plane"]) > 0: face_plot = _generate_mesh( model_info["coord_no_deform"], cells["plane"], kind="face" ) plotter.add_mesh( face_plot, color=self.color_face, show_edges=True, opacity=opacity ) if len(cells["tetrahedron"]) > 0: tet_plot = _generate_mesh( model_info["coord_no_deform"], cells["tetrahedron"], kind="face" ) plotter.add_mesh( tet_plot, color=self.color_solid, show_edges=True, opacity=opacity ) if len(cells["brick"]) > 0: bri_plot = _generate_mesh( model_info["coord_no_deform"], cells["brick"], kind="face" ) plotter.add_mesh( bri_plot, color=self.color_solid, show_edges=True, opacity=opacity ) plotter.add_text( "OpenSees 3D View", position="upper_left", font_size=15, # color="black", font="courier", viewport=True, ) plotter.add_text( "Num. of Node: {0} \n Num. of Ele:{1}".format( model_info["num_node"], model_info["num_ele"] ), position="upper_right", font_size=10, # color="black", font="courier", ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=model_info["bound"], show_zaxis=True, # color="black", ) if show_node_label: node_labels = [str(i) for i in model_info["NodeTags"]] plotter.add_point_labels( model_info["coord_no_deform"], node_labels, text_color="white", font_size=label_size, bold=False, always_visible=True, ) if show_ele_label: ele_labels = [str(i) for i in model_info["EleTags"]] plotter.add_point_labels( model_info["coord_ele_midpoints"], ele_labels, text_color="#ff796c", font_size=label_size, bold=False, always_visible=True, ) if show_local_crd: beam_midpoints = model_info["beam_midpoints"] beam_xlocal = model_info["beam_xlocal"] beam_ylocal = model_info["beam_ylocal"] beam_zlocal = model_info["beam_zlocal"] length = model_info["max_bound"] / 250 _ = plotter.add_arrows(beam_midpoints, beam_xlocal, mag=length, color="red") _ = plotter.add_arrows(beam_midpoints, beam_ylocal, mag=length, color="orange") _ = plotter.add_arrows(beam_midpoints, beam_zlocal, mag=length, color="green") plotter.add_axes() plotter.view_isometric() if np.max(model_info["model_dims"]) <= 2: plotter.view_xy(negative=False) if save_fig: plotter.save_graphic(save_fig) plotter.show(title=self.title) plotter.close()
[docs] def eigen_vis( self, mode_tags: list[int], subplots: bool = False, alpha: float = None, show_outline: bool = False, show_origin: bool = False, opacity: float = 1.0, show_face_line: bool = True, save_fig: str = "EigenVis.svg" ): """Eigenvalue Analysis Visualization. Parameters ---------- mode_tags: list[int], or tuple[int] Mode tags to be shown, if list or tuple [mode1, mode2], display the multiple modes from mode1 to mode2. subplots: bool, default=False If True, subplots in a figure. If False, plot in a slider style. alpha: float, default=None Model scaling factor, the default value is 1/5 of the model boundary according to the maximum deformation. show_outline: bool, default=True Whether to display the axes. show_origin: bool, default=False Whether to show undeformed shape. opacity: float, default=1.0 Plane and solid element transparency. show_face_line: bool, default=True If True, the edges of plate and solid elements will be displayed. save_fig: str, default='EigenVis.svg' The file name to output. If False or None, the file will not be generated. The supported formats are: * '.svg' * '.eps' * '.ps' * '.pdf' * '.tex' Returns -------- None """ check_file(save_fig, ['.svg', '.eps', '.ps', 'pdf', '.tex']) filename = self.out_dir + '/EigenData' with shelve.open(filename) as db: eigen_data = db["EigenInfo"] f = eigen_data["f"] eigenvector = eigen_data["eigenvector"] num_mode_tag = len(f) modei, modej = mode_tags modei, modej = int(modei), int(modej) if modej > num_mode_tag: raise ValueError( f"Insufficient number of modes in eigen file {filename}!") # !subplots if subplots: if modej - modei + 1 > 49: raise ValueError( "When subplots True, mode_tag range must < 49 for clarify" ) shape = shape_dict[modej - modei + 1] subplot_titles = [] for i, idx in enumerate(range(modei, modej + 1)): txt = "Mode {}: T = {:.3f} s".format(idx, 1 / f[idx - 1]) subplot_titles.append(txt) plotter = pv.Plotter(notebook=self.notebook, shape=shape) for i, idx in enumerate(range(modei, modej + 1)): eigen_vec = eigenvector[idx - 1] if alpha is None: alpha_ = ( eigen_data["max_bound"] / self.bound_fact / np.max(np.sqrt(np.sum(eigen_vec**2, axis=1))) ) else: alpha_ = alpha eigen_points = eigen_data["coord_no_deform"] + \ eigen_vec * alpha_ scalars = np.sqrt(np.sum(eigen_vec**2, axis=1)) idxi = int(np.ceil((i + 1) / shape[1]) - 1) idxj = int(i - idxi * shape[1]) # ------------------------------------------------------ plotter.subplot(idxi, idxj) _ = _generate_all_mesh( plotter, eigen_points, scalars, opacity, self.color_map, eigen_data["all_lines"], eigen_data["all_faces"], show_origin=show_origin, points_origin=eigen_data["coord_no_deform"], point_size=self.point_size, line_width=self.line_width, show_face_line=show_face_line ) # plotter.add_scalar_bar(color='#000000', n_labels=10, label_font_size=8) # txt = 'Mode {}\nf = {:.3f} Hz\nT = {:.3f} s'.format(i + 1, f[i], 1 / f[i]) txt = "Mode {} T = {:.3f} s".format(idx, 1 / f[idx - 1]) plotter.add_text( txt, position="upper_right", font_size=10, # color="black", font="courier", ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=eigen_data["bound"], show_zaxis=True, # color="black", font_size=10, ) plotter.add_axes(color="black") plotter.link_views() # !slide style else: plotter = pv.Plotter(notebook=self.notebook) def create_mesh(value): step = int(round(value)) - 1 eigen_vec = eigenvector[step] if alpha is None: alpha_ = ( eigen_data["max_bound"] / self.bound_fact / np.max(np.sqrt(np.sum(eigen_vec ** 2, axis=1))) ) else: alpha_ = alpha eigen_points = eigen_data["coord_no_deform"] + \ eigen_vec * alpha_ scalars = np.sqrt(np.sum(eigen_vec ** 2, axis=1)) cmin = np.min(scalars) cmax = np.max(scalars) plotter.clear_actors() _ = _generate_all_mesh( plotter, eigen_points, scalars, opacity, self.color_map, eigen_data["all_lines"], eigen_data["all_faces"], show_origin=show_origin, points_origin=eigen_data["coord_no_deform"], point_size=self.point_size, line_width=self.line_width, show_face_line=show_face_line ) plotter.add_scalar_bar( fmt="%.3e", n_labels=10, label_font_size=12 ) # txt = 'Mode {}\nf = {:.3f} Hz\nT = {:.3f} s'.format(mode_tag, f_, 1 / f_) txt = "Mode {}\nT = {:.3f} s".format(step + 1, 1 / f[step]) plotter.add_text( txt, position="upper_left", font_size=12, font="courier" ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=eigen_data["bound"], show_zaxis=True, # color="black", ) plotter.add_axes() slider = plotter.add_slider_widget( create_mesh, [modei, modej], value=modei, pointa=(0.4, 0.9), pointb=(0.9, 0.9), title="Mode", title_opacity=1, # title_color="black", fmt="%.0f", title_height=0.03, slider_width=0.03, tube_width=0.01, ) plotter.view_isometric() if np.max(eigen_data["model_dims"]) <= 2: plotter.view_xy(negative=False) if save_fig: plotter.save_graphic(save_fig) plotter.show(title=self.title) plotter.close()
[docs] def eigen_anim( self, mode_tag: int = 1, alpha: float = None, show_outline: bool = False, opacity: float = 1, framerate: int = 3, show_face_line: bool = True, save_fig: str = "EigenAnimation.gif" ): """Animation of Modal Analysis. Parameters ---------- mode_tag: int The mode tag. alpha: float, default=None Scaling factor, the default value is 1/5 of the model boundary according to the maximum deformation. show_outline: bool, default=False Whether to display the axes. opacity: float, default=1.0 Plane and solid element transparency. framerate: int The number of frames per second. show_face_line: bool, default=True If True, the edges of plate and solid elements will be displayed. save_fig: str, default='EigenAnimation.gif' The output file name, must end with `.gif` or `.mp4`. You can export to any folder, such as "C:mydir/myfile.gif", but the folder `mydir` must exist. Returns -------- None """ check_file(save_fig, ['.gif', '.mp4']) filename = self.out_dir + '/EigenData' with shelve.open(filename) as db: eigen_data = db["EigenInfo"] f = eigen_data["f"] eigenvector = eigen_data["eigenvector"] num_mode_tag = len(f) if mode_tag > num_mode_tag: raise ValueError("Insufficient number of modes in open file") eigen_vec = eigenvector[mode_tag - 1] f_ = f[mode_tag - 1] if alpha is None: alpha_ = ( eigen_data["max_bound"] / self.bound_fact / np.max(np.sqrt(np.sum(eigen_vec ** 2, axis=1))) ) else: alpha_ = alpha eigen_points = eigen_data["coord_no_deform"] + eigen_vec * alpha_ anti_eigen_points = eigen_data["coord_no_deform"] - eigen_vec * alpha_ scalars = np.sqrt(np.sum(eigen_vec ** 2, axis=1)) plt_points = [anti_eigen_points, eigen_data["coord_no_deform"], eigen_points] # ----------------------------------------------------------------------------- # start plot plotter = pv.Plotter(notebook=self.notebook) if alpha is None: alpha_ = ( eigen_data["max_bound"] / self.bound_fact / np.max(np.sqrt(np.sum(eigen_vec**2, axis=1))) ) else: alpha_ = alpha eigen_points = eigen_data["coord_no_deform"] + eigen_vec * alpha_ anti_eigen_points = eigen_data["coord_no_deform"] - eigen_vec * alpha_ scalars = np.sqrt(np.sum(eigen_vec**2, axis=1)) point_plot, line_plot, face_plot = _generate_all_mesh(plotter, eigen_data["coord_no_deform"], scalars * 0, opacity, self.color_map, eigen_data["all_lines"], eigen_data["all_faces"], show_origin=False, points_origin=eigen_data["coord_no_deform"], show_scalar_bar=True, point_size=self.point_size, line_width=self.line_width, show_face_line=show_face_line, ) plotter.add_scalar_bar( fmt="%.3E", n_labels=10, label_font_size=12 ) plotter.add_text( "Mode {}\nf = {:.3f} Hz\nT = {:.3f} s".format( mode_tag, f_, 1 / f_), position="upper_right", font_size=12, # color="black", font="courier", ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=eigen_data["bound"], show_zaxis=True, # color="black", ) plotter.add_axes() # plotter.add_text('OpenSees 3D View', position='upper_left', font_size=16, color='black', font='courier') plotter.view_isometric() if np.max(eigen_data["model_dims"]) <= 2: plotter.view_xy(negative=False) # animation # ---------------------------------------------------------------------------- if save_fig.endswith(".gif"): plotter.open_gif(save_fig, fps=framerate) else: plotter.open_movie(save_fig, framerate=framerate) plt_points = [anti_eigen_points, eigen_data["coord_no_deform"], eigen_points] render = False index = [2, 0] * 3 plotter.write_frame() # write initial data for idx in index: points = plt_points[idx] xyz = (eigen_data["coord_no_deform"] - points) / alpha_ xyz_eigen = np.sqrt(np.sum(xyz**2, axis=1)) plotter.update_coordinates(points, mesh=point_plot, render=render) plotter.update_scalars( scalars=xyz_eigen, mesh=point_plot, render=render) if line_plot: plotter.update_scalars( scalars=xyz_eigen, mesh=line_plot, render=render) plotter.update_coordinates( points, mesh=line_plot, render=render) if face_plot: plotter.update_scalars( scalars=xyz_eigen, mesh=face_plot, render=render) plotter.update_coordinates( points, mesh=face_plot, render=render) plotter.update_scalar_bar_range( clim=[np.min(xyz_eigen), np.max(xyz_eigen)], name=None ) plotter.write_frame() # ---------------------------------------------------------------------------------- plotter.show(title=self.title) plotter.close()
[docs] def deform_vis( self, analysis_tag: int, slider: bool = False, response: str = "disp", alpha: float = None, show_outline: bool = False, show_origin: bool = False, show_face_line: bool = True, opacity: float = 1, save_fig: str = "DefoVis.svg", model_update: bool = False ): """Visualize the deformation of the model at a certain analysis step. Parameters ---------- analysis_tag: int Analysis tag in get_node_resp_step() method. slider: bool, default=False If True, responses in all steps will display by slider style. If False, the step that max response will display. response: str, default='disp' Response type. Optional, "disp", "vel", "accel". alpha: float, default=None Scaling factor, the default value is 1/5 of the model boundary according to the maximum deformation. show_outline: bool, default=False Whether to display the axes. show_origin: bool, default=False Whether to show undeformed shape. show_face_line: bool, default=True If True, the edges of plate and solid elements will be displayed. opacity: float, default=1.0 Plane and solid element transparency. save_fig: str, default='DefoVis.svg' The file name to output. If False or None, the file will not be generated. The supported formats are: * '.svg' * '.eps' * '.ps' * '.pdf' * '.tex' Returns -------- None """ check_file(save_fig, ['.svg', '.eps', '.ps', 'pdf', '.tex']) resp_type = response.lower() if resp_type not in ['disp', 'vel', 'accel']: raise ValueError("response must be 'disp', 'vel', or 'accel'!") filename = self.out_dir + f'/NodeRespStepData-{analysis_tag}' with shelve.open(filename) as db: model_info_steps = db["ModelInfoSteps"] cell_steps = db["CellSteps"] node_resp_steps = db["NodeRespSteps"] num_steps = len(node_resp_steps["disp"]) # ! max response max_resps = [np.max(np.sqrt(np.sum(resp_ ** 2, axis=1))) for resp_ in node_resp_steps[resp_type]] max_step = np.argmax(max_resps) max_node_resp = node_resp_steps[resp_type][max_step] scalars = np.sqrt(np.sum(max_node_resp ** 2, axis=1)) cmin, cmax = np.min(scalars), np.max(scalars) if model_update: bounds = model_info_steps["bound"][0] model_dims = model_info_steps["model_dims"][0] else: bounds = model_info_steps["bound"] model_dims = model_info_steps["model_dims"] # scale factor if resp_type == "disp": if alpha is None: max_bound = np.max( [bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]]) value = np.max(np.sqrt(np.sum(max_node_resp ** 2, axis=1))) alpha_ = max_bound / self.bound_fact / value else: alpha_ = alpha else: alpha_ = 0 # ------------------------------------------------------------------------ # Start plot # ------------------------------------------------------------------------- plotter = pv.Plotter(notebook=self.notebook) def create_mesh(value): step = int(round(value)) - 1 if model_update: node_nodeform_coords = model_info_steps["coord_no_deform"][step] bounds = model_info_steps["bound"][step] lines_cells = cell_steps["all_lines"][step] faces_cells = cell_steps["all_faces"][step] else: node_nodeform_coords = model_info_steps["coord_no_deform"] bounds = model_info_steps["bound"] lines_cells = cell_steps["all_lines"] faces_cells = cell_steps["all_faces"] node_resp = node_resp_steps[resp_type][step] node_deform_coords = alpha_ * node_resp + node_nodeform_coords scalars = np.sqrt(np.sum(node_resp ** 2, axis=1)) plotter.clear_actors() # !!!!!! _ = _generate_all_mesh( plotter, node_deform_coords, scalars, opacity, self.color_map, lines_cells=lines_cells, face_cells=faces_cells, show_origin=show_origin, points_origin=node_nodeform_coords, point_size=self.point_size, line_width=self.line_width, show_face_line=show_face_line, clim=[cmin, cmax] ) plotter.add_scalar_bar( fmt="%.3e", n_labels=10, label_font_size=12 ) plotter.add_text( "OpenSees 3D View", position="upper_left", shadow=True, font_size=16, # color="black", font="courier", ) plotter.add_text( "peak of {}, step: {}\n" "min.x = {:.3E} max.x = {:.3E}\n" "min.y = {:.3E} max.y = {:.3E}\n" "min.z = {:.3E} max.z = {:.3E}\n".format( response, step + 1, np.min(node_resp[:, 0]), np.max(node_resp[:, 0]), np.min(node_resp[:, 1]), np.max(node_resp[:, 1]), np.min(node_resp[:, 2]), np.max(node_resp[:, 2]), ), position="upper_right", shadow=True, font_size=12, # color="black", font="courier", ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=bounds, show_zaxis=True, # color="black", ) plotter.add_axes() if slider: _ = plotter.add_slider_widget( create_mesh, [1, num_steps], value=num_steps, pointa=(0.0, 0.9), pointb=(0.5, 0.9), title="Step", title_opacity=1, # title_color="black", fmt="%.0f", title_height=0.03, slider_width=0.03, tube_width=0.01, ) # ------------------------------------------------------------------------- else: # plot a single step create_mesh(max_step + 1) plotter.view_isometric() if np.max(model_dims) <= 2: plotter.view_xy(negative=False) if save_fig: plotter.save_graphic(save_fig) plotter.show(title=self.title) plotter.close()
[docs] def deform_anim( self, analysis_tag: int, response: str = "disp", alpha: float = None, show_outline: bool = False, opacity: float = 1, framerate: int = 24, show_face_line: bool = True, save_fig: str = "DefoAnimation.gif", model_update: bool = False ): """Deformation animation of the model. Parameters ---------- analysis_tag: int Analysis tag in get_node_resp_step() method. response: str, default='disp' Response type. Optional, "disp", "vel", "accel". alpha: float, default=None Scaling factor, the default value is 1/5 of the model boundary according to the maximum deformation. show_outline: bool, default=False Whether to display the axes. show_face_line: bool, default=True If True, the edges of plate and solid elements will be displayed. framerate: int, default=24 The number of frames per second. opacity: float, default=1.0 Plane and solid element transparency. save_fig: str, default='DefoAnimation.gif' The output file name, must end with `.gif` or `.mp4`. You can export to any folder, such as "C:mydir/myfile.gif", but the folder `mydir` must exist. Returns -------- None """ check_file(save_fig, ['.gif', '.mp4']) resp_type = response.lower() if resp_type not in ['disp', 'vel', 'accel']: raise ValueError("response must be 'disp', 'vel', or 'accel'!") filename = self.out_dir + f'/NodeRespStepData-{analysis_tag}' with shelve.open(filename) as db: model_info_steps = db["ModelInfoSteps"] cell_steps = db["CellSteps"] node_resp_steps = db["NodeRespSteps"] num_steps = len(node_resp_steps["disp"]) # ! max response max_resps = [np.max(np.sqrt(np.sum(resp_ ** 2, axis=1))) for resp_ in node_resp_steps[resp_type]] max_step = np.argmax(max_resps) max_node_resp = node_resp_steps[resp_type][max_step] scalars = np.sqrt(np.sum(max_node_resp ** 2, axis=1)) cmin, cmax = np.min(scalars), np.max(scalars) if model_update: bounds = model_info_steps["bound"][0] model_dims = model_info_steps["model_dims"][0] else: bounds = model_info_steps["bound"] model_dims = model_info_steps["model_dims"] # scale factor if resp_type == "disp": if alpha is None: max_bound = np.max( [bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]]) value = np.max(np.sqrt(np.sum(max_node_resp ** 2, axis=1))) alpha_ = max_bound / self.bound_fact / value else: alpha_ = alpha else: alpha_ = 0 # ----------------------------------------------------------------------------- # start plot plotter = pv.Plotter(notebook=self.notebook) step = 0 if model_update: node_nodeform_coords = model_info_steps["coord_no_deform"][step] bounds = model_info_steps["bound"][step] lines_cells = cell_steps["all_lines"][step] faces_cells = cell_steps["all_faces"][step] else: node_nodeform_coords = model_info_steps["coord_no_deform"] bounds = model_info_steps["bound"] lines_cells = cell_steps["all_lines"] faces_cells = cell_steps["all_faces"] node_resp = node_resp_steps[resp_type][step] node_deform_coords = alpha_ * node_resp + node_nodeform_coords scalars = np.sqrt(np.sum(node_resp ** 2, axis=1)) point_plot, line_plot, face_plot = _generate_all_mesh( plotter, node_deform_coords, scalars, opacity, self.color_map, lines_cells=lines_cells, face_cells=faces_cells, point_size=self.point_size, line_width=self.line_width, show_face_line=show_face_line, clim=[cmin, cmax] ) plotter.add_scalar_bar( fmt="%.3e", n_labels=10, label_font_size=12 ) if show_outline: plotter.show_bounds( grid=False, location="outer", bounds=bounds, show_zaxis=True, # color="black", ) plotter.add_axes() # plotter.add_text('OpenSees 3D View', position='upper_left', font_size=16, color='black', font='courier') plotter.view_isometric() if np.max(model_dims) <= 2: plotter.view_xy(negative=False) # animation # ---------------------------------------------------------------------------- if save_fig.endswith(".gif"): plotter.open_gif(save_fig, fps=framerate) else: plotter.open_movie(save_fig, framerate=framerate) # plotter.write_frame() # write initial data for step in range(num_steps): if model_update: # lines_cells = cell_steps["all_lines"][step] # faces_cells = cell_steps["plane"][step] pass else: # lines_cells = cell_steps["all_lines"] # faces_cells = cell_steps["all_faces"] pass node_resp = node_resp_steps[resp_type][step] node_deform_coords = alpha_ * node_resp + node_nodeform_coords scalars = np.sqrt(np.sum(node_resp ** 2, axis=1)) if resp_type == "disp": plotter.update_coordinates( node_deform_coords, mesh=point_plot, render=False ) if line_plot is not None: if resp_type == "disp": plotter.update_coordinates( node_deform_coords, mesh=line_plot, render=False ) plotter.update_scalars(scalars, mesh=line_plot, render=False) if face_plot is not None: if resp_type == "disp": plotter.update_coordinates( node_deform_coords, mesh=face_plot, render=False ) plotter.update_scalars(scalars, mesh=face_plot, render=False) # plotter.update_scalar_bar_range(clim=[np.min(scalars), np.max(scalars)]) txt = plotter.add_text( "peak of {}, step: {}\n" "min.x = {:.3E} max.x = {:.3E}\n" "min.y = {:.3E} max.y = {:.3E}\n" "min.z = {:.3E} max.z = {:.3E}\n".format( resp_type, step + 1, np.min(node_resp[:, 0]), np.max(node_resp[:, 0]), np.min(node_resp[:, 1]), np.max(node_resp[:, 1]), np.min(node_resp[:, 2]), np.max(node_resp[:, 2]), ), position="upper_right", font_size=12, # color="black", font="courier", ) plotter.write_frame() if step < num_steps - 1: plotter.remove_actor(txt) # ---------------------------------------------------------------------------------- plotter.show(title=self.title) plotter.close()
[docs] def frame_resp_vis(self, analysis_tag: int, ele_tags: list[int] = None, slider: bool = False, response: str = "Mz", show_values=True, alpha: float = None, opacity: float = 1, save_fig: str = "FrameRespVis.svg" ): """ Display the force response of frame elements. Parameters ---------- analysis_tag: int Analysis tag in get_node_resp_step() method. ele_tags: int or list[int], default=None Element tags to display, if None, all frame elements will display. slider: bool, default=False If True, responses in all steps will display by slider style. If False, the step that max response will display. response: str, default='Mz' Response type. Optional, "Fx", "Fy", "Fz", "My", "Mz", "Mx". show_values: bool, default=True If True, will show the response values. alpha: float, default=None Scaling factor. opacity: float, default=1.0 Plane and solid element transparency. save_fig: str, default='FrameRespVis.svg' The file name to output. If False or None, the file will not be generated. The supported formats are: * '.svg' * '.eps' * '.ps' * '.pdf' * '.tex' Returns -------- None """ check_file(save_fig, ['.svg', '.eps', '.ps', 'pdf', '.tex']) filename = self.out_dir + f'/BeamRespStepData-{analysis_tag}' with shelve.open(filename) as db: beam_infos = db["BeamInfos"] beam_resp_step = db["BeamRespSteps"] beam_tags = beam_infos['beam_tags'] beam_cell_map = beam_infos['beam_cell_map'] ylocal_map = beam_infos['ylocal'] zlocal_map = beam_infos['zlocal'] local_forces_step = beam_resp_step['localForces'] num_steps = len(local_forces_step) if ele_tags is None: ele_tags = beam_tags beam_node_coords = beam_infos['beam_node_coords'] beam_cells = beam_infos['beam_cells'] idxs = range(len(beam_tags)) else: ele_tags = np.atleast_1d(ele_tags) beam_node_coords = [] beam_cells = [] idxs = [] beam_cell_map = {} for i, eletag in enumerate(ele_tags): idx = beam_infos['beam_cell_map'][eletag] idxs.append(idx) beam_cell_map[eletag] = i nodei, nodej = beam_infos['beam_cells'][idx, 1:] beam_node_coords.append(beam_infos['beam_node_coords'][nodei]) beam_node_coords.append(beam_infos['beam_node_coords'][nodej]) beam_cells.append([2, 2 * i, 2 * i + 1]) beam_node_coords = np.array(beam_node_coords) beam_cells = np.array(beam_cells) idx_plottype_map = dict(fx=[0, 6], fy=[1, 7], fz=[2, 8], my=[4, 10], mz=[5, 11], mx=[3, 9]) f_sign_map = dict(fx=[-1, 1], fy=[-1, 1], fz=[-1, 1], my=[1, -1], mz=[-1, 1], mx=[1, -1]) axis_sign_map = dict(fx=1, fy=1, fz=1, my=-1, mz=-1, mx=-1) axis_map_map = dict(fx=zlocal_map, fy=ylocal_map, fz=zlocal_map, my=zlocal_map, mz=ylocal_map, mx=zlocal_map) idx_plottype = idx_plottype_map[response.lower()] axis_map = axis_map_map[response.lower()] axis_sign = axis_sign_map[response.lower()] local_forces_step = [data[:, idx_plottype][idxs] * np.array(f_sign_map[response.lower()]) for data in local_forces_step] # new maxv = [np.max(np.abs(data)) for data in local_forces_step] maxstep = np.argmax(maxv) local_forces_max = local_forces_step[maxstep] cmin, cmax = np.min(local_forces_max), np.max(local_forces_max) if alpha is None: max_coord = np.max(beam_node_coords, axis=0) min_coord = np.min(beam_node_coords, axis=0) max_bound = np.max(max_coord - min_coord) maxv = np.amax(np.abs(local_forces_max)) alpha_ = max_bound / maxv / self.bound_fact else: alpha_ = alpha # ------------------------------------------------------------------------ # start plot # ------------------------------------------------------------------------- plotter = pv.Plotter(notebook=self.notebook) def create_mesh(value): step = int(round(value)) - 1 local_forces = local_forces_step[step] local_forces_scale = local_forces * alpha_ # add force face cells # TODO if values symbol versa, need trangle label_poins = [] labels = [] resp_points = [] resp_cells = [] scalars = [] for i, eletag in enumerate(ele_tags): axis = axis_map[eletag] node1, node2 = beam_cells[beam_cell_map[eletag], 1:] coord1, coord2 = beam_node_coords[node1], beam_node_coords[node2] f1, f2 = local_forces_scale[beam_cell_map[eletag]] f1_, f2_ = local_forces[beam_cell_map[eletag]] coord3 = coord2 + f2 * axis * axis_sign coord4 = coord1 + f1 * axis * axis_sign label_poins.extend([coord3, coord4]) labels.extend([f2_, f1_]) n = len(resp_points) if f1 * f2 >= 0: resp_points.extend([coord1, coord2, coord3, coord4]) resp_cells.append([4, n, n + 1, n + 2, n + 3]) scalars.extend([f1_, f2_, f2_, f1_]) else: ratio = np.abs(f2 / f1) ratio = 1 / (ratio + 1) coord0 = coord1 + (coord2 - coord1) * ratio resp_points.extend( [coord1, coord0, coord4, coord2, coord0, coord3]) resp_cells.append([3, n, n + 1, n + 2]) resp_cells.append([3, n + 3, n + 4, n + 5]) scalars.extend([f1_, 0, f1_, f2_, 0, f2_]) labels = [f"{label:.2E}" for label in labels] label_poins = np.array(label_poins) resp_points = np.array(resp_points) scalars = np.array(scalars) # --------------------------------- plotter.clear_actors() # !!!!!! point_plot = pv.PolyData(beam_node_coords) plotter.add_mesh( point_plot, color=self.color_point, point_size=self.point_size, render_points_as_spheres=True, show_scalar_bar=False, ) line_plot = _generate_mesh( beam_node_coords, beam_cells, kind="line") plotter.add_mesh( line_plot, color="black", render_lines_as_tubes=True, line_width=self.line_width / 3, show_scalar_bar=False, ) resp_plot = _generate_mesh(resp_points, resp_cells, kind="face") resp_plot.point_data["data0"] = scalars plotter.add_mesh( resp_plot, colormap=self.color_map, scalars=scalars, clim=[cmin, cmax], show_edges=False, opacity=opacity, interpolate_before_map=True, show_scalar_bar=False, ) plotter.add_scalar_bar( color="#000000", fmt="%.3e", n_labels=10, label_font_size=12, title=response, ) plotter.add_axes() plotter.add_text( "OpenSees 3D View", position="upper_left", font_size=15, # color="black", font="courier", viewport=True, ) plotter.add_text( "peak of {}, step: {}\n" "min = {:.3E}\nmax = {:.3E}\n".format( response, step + 1, np.min(scalars), np.max(scalars) ), position="upper_right", shadow=True, font_size=12, # color="black", font="courier", ) if show_values: plotter.add_point_labels( label_poins, labels, # text_color="white", font_size=10, bold=False, always_visible=True, ) if slider: _ = plotter.add_slider_widget( create_mesh, [1, num_steps], value=num_steps, pointa=(0.0, 0.9), pointb=(0.5, 0.9), title="Step", title_opacity=1, # title_color="black", fmt="%.0f", title_height=0.03, slider_width=0.03, tube_width=0.01, ) # ------------------------------------------------------------------------- else: # plot a single step create_mesh(maxstep + 1) plotter.view_isometric() if np.max(np.abs(beam_node_coords[:, -1])) < 1e-5: plotter.view_xy(negative=False) if save_fig: plotter.save_graphic(save_fig) plotter.show(title=self.title) plotter.close()
def _generate_mesh(points, cells, kind="line"): """ generate the mesh from the points and cells """ if kind == "line": pltr = pv.PolyData() pltr.points = points pltr.lines = np.array(cells) elif kind == "face": pltr = pv.PolyData() pltr.points = points pltr.faces = np.hstack(cells) else: raise ValueError("not supported kind!") return pltr def _generate_all_mesh( plotter, points, scalars, opacity, colormap, lines_cells, face_cells, show_origin=False, points_origin=None, show_scalar_bar=False, point_size=1, line_width=1, show_face_line=True, clim=None ): """ Auxiliary function for generating all meshes """ if clim is None: clim = [np.min(scalars), np.max(scalars)] sargs = dict( title_font_size=16, label_font_size=12, shadow=True, n_labels=10, italic=False, fmt="%.3E", font_family="arial", ) point_plot = pv.PolyData(points) point_plot.point_data["data0"] = scalars plotter.add_mesh( point_plot, colormap=colormap, scalars=scalars, clim=clim, interpolate_before_map=True, point_size=point_size, render_points_as_spheres=True, show_scalar_bar=show_scalar_bar, scalar_bar_args=sargs, ) if len(lines_cells) > 0: if show_origin: line_plot_origin = _generate_mesh( points_origin, lines_cells, kind="line" ) plotter.add_mesh( line_plot_origin, color="gray", line_width=line_width / 3, show_scalar_bar=False, ) line_plot = _generate_mesh(points, lines_cells, kind="line") line_plot.point_data["data0"] = scalars plotter.add_mesh( line_plot, colormap=colormap, scalars=scalars, interpolate_before_map=True, clim=clim, show_scalar_bar=show_scalar_bar, render_lines_as_tubes=True, line_width=line_width, ) else: line_plot = None if len(face_cells) > 0: if show_origin: face_plot_origin = _generate_mesh( points_origin, face_cells, kind="face" ) plotter.add_mesh( face_plot_origin, color="gray", style="wireframe", show_scalar_bar=False, show_edges=True, line_width=line_width / 3, ) face_plot = _generate_mesh(points, face_cells, kind="face") face_plot.point_data["data0"] = scalars plotter.add_mesh( face_plot, colormap=colormap, scalars=scalars, clim=clim, show_edges=show_face_line, opacity=opacity, interpolate_before_map=True, show_scalar_bar=show_scalar_bar, ) else: face_plot = None return point_plot, line_plot, face_plot