Module beef.plot

Plotting functions.

Expand source code
'''
Plotting functions.
'''
import numpy as np
from copy import deepcopy
import pyvista as pv

def flat(l):
  return [y for x in l for y in x]
    

def frame_creator(frames=30, repeats=1, swing=False, full_cycle=False):
    '''
    Construct scaling of value.

    Arguments
    ------------
    frames : int, optional
        number of frames (30 is standard)
    repeats : int, optional
        number of repeats (1 is standard)
    swing : False, optional
        whether or not to swing back and forth
    full_cycle : False, optional
        whether or not to include the full cycle (resulting in initial scaling being -1 rather than 0)

    Returns
    ----------
    scaling : float
        numpy array describing scaling of value to animate
    '''
    
    if full_cycle:
        d = 2/frames
        start = -1
    else:
        d = 1/frames
        start = 0
    
    if swing:
        base_scaling = np.hstack([np.linspace(start,1-d,frames), np.linspace(1,start+d,frames)])
    else:
        base_scaling = np.linspace(start,1-d,frames) 

    return np.tile(base_scaling, repeats)



def plot_eldef(eldef, plot_elements=True, plot_states=['undeformed'], plot_nodes=False, vals=None, el_opts={}, def_el_opts={}, node_opts={}, canvas_opts={},
                  show=True, plot_tmat_ax=[1,2], tmat_opts={}, tmat_scaling=10, tmat_on=[], val_fun=None, show_maxmin=False, maxmin_fmt='.2f',
                  vals_on=[], colorbar_opts={}, clim=None, annotate_vals={}, pl=None, node_labels=False, 
                  element_labels=False, thickness_scaling=None, cmap='viridis', view=None, nodelabel_opts={}, elementlabel_opts={},
                  element_label_fun=None, constraints_on=['undeformed','deformed'], def_constraint_opts={}, constraint_opts={}, 
                  plot_constraints=[], node_label_fun=None, plot_node_states=None):
    
    if plot_node_states is None:
        plot_node_states = plot_states
        
    if element_label_fun is None:
        element_label_fun = lambda el: str(int(el.label))
    if node_label_fun is None:
        node_label_fun = lambda n: str(int(n.label))
        
    if plot_elements is not False:
        if plot_elements is True:
            elements = eldef.elements
        else:
            elements = [el for el in eldef.elements if el in plot_elements]

    nodes = eldef.nodes
    
    if elements[0].domain == '2d':
        def conv_fun(xyz):
            if len(xyz)==2:
                return np.hstack([xyz, 0])
            elif len(xyz)==3:
                return np.hstack([xyz[:2], 0, 0, xyz[2], 0])
            else:
                raise ValueError('Wrong size of xyz')
    else:
        conv_fun = lambda xyz: xyz


    def group_elements(els):
        sections = list(set([el.section for el in els]))
        grouped_els = {sec: [el for el in els if el.section==sec] for sec in sections}
        group_ixs = {sec: np.array([el in grouped_els[sec] for el in els]) for sec in grouped_els}
        
        return grouped_els, group_ixs
        
    def generate_constraint_mesh(constraints, field='x', pad_size=2):
        nodes = []
        for c in constraints:
            nodes.append([[eldef.get_node(nc.master_node), eldef.get_node(nc.slave_node)] for nc in c.node_constraints])
        
        nodes = [a for b in nodes for a in b]
        nodes_pairs = [n for n in nodes if n[1] is not None]
        
        nodes = [a for b in nodes_pairs for a in b]
        node_pos = np.vstack([conv_fun(getattr(node, field))[:3] for node in nodes])

        nodes = [n.label for n in nodes]
        edges = np.vstack([[ix*2, ix*2+1] for ix in range(len(nodes_pairs))])

        # # We must "pad" the edges to indicate to vtk how many points per edge
        padding = np.empty(edges.shape[0], int) * 2
        padding[:] = pad_size
        edges_w_padding = np.vstack((padding, edges.T)).T
        mesh = pv.PolyData(node_pos, edges_w_padding)
        return mesh
        
    def generate_mesh(els, field='x', pad_size=2):
        # Coordinates of nodes
        nodes = list(set([a for b in [el.nodes for el in els] for a in b])) #flat list of unique nodes
        node_pos = np.vstack([conv_fun(getattr(node, field))[:3] for node in nodes])

        nodes = [n.label for n in nodes]
        edges = np.vstack([[nodes.index(el.nodes[0]), nodes.index(el.nodes[1])] for el in els])
        
        # # We must "pad" the edges to indicate to vtk how many points per edge
        padding = np.empty(edges.shape[0], int) * 2
        padding[:] = pad_size
        edges_w_padding = np.vstack((padding, edges.T)).T
        mesh = pv.PolyData(node_pos, edges_w_padding)
        return mesh
    
    def generate_node_mesh(nodes, field='x', pad_size=2):
        # Coordinates of nodes
        node_pos = np.vstack([conv_fun(getattr(node, field))[:3] for node in nodes])
        return node_pos
    
    def get_maxmin_fun():
        ix_max = np.argmax(vals)
        ix_min = np.argmin(vals)

        def fun(el):
            if el == elements[ix_max]:
                return f'Max = {np.max(vals):{maxmin_fmt}}'
            elif el == elements[ix_min]:
                return f'Min = {np.min(vals):{maxmin_fmt}}'

        return fun

    
    tmat_colors = ['#ff0000', '#00ff00', '#0000ff']
    
    scalar_bar_settings = dict(
        title_font_size=20,
        label_font_size=16,
        n_labels=4,
        italic=False,
        color='black',
        fmt='.2e',
        font_family="arial"
    )
    
    scalar_bar_settings.update(colorbar_opts)
    
    if vals is None and val_fun is None and 'color' not in el_opts:
        el_opts['color'] = '#44ff88'

    if val_fun is not None:
        if type(val_fun) is str:
            vstr = val_fun + ''
            if 'title' not in colorbar_opts:
                colorbar_opts['title'] = vstr + ''

            val_fun = lambda el: getattr(el, vstr)

        vals = np.vstack([val_fun(el) for el in elements])

    scalars = dict(undeformed=None, deformed=None)
    
    show_scalarbar = dict(undeformed=False, deformed=False)
    show_scalarbar.update({key: True for key in vals_on})
    scalars.update({key: vals for key in vals_on})
    
    if vals is not None:
        if clim is None:
            clim = [np.min(vals[~np.isnan(vals)]), np.max(vals[~np.isnan(vals)])]
        
        if np.min(vals[~np.isnan(vals)])<0:     #adjust for negative values
            # print(vals[~np.isnan(vals)])
            shift = -np.min(vals[~np.isnan(vals)])
        else:
            shift = 0
            
        vals = vals + shift
        
        # TODO: SUUPER PATCHY - fix later
        if 'fmt' in scalar_bar_settings:
            fmt_annotate = scalar_bar_settings['fmt']
            scalar_bar_settings['fmt'] = '%' + fmt_annotate
        else:
            fmt_annotate = '.2f'
        
        annotate_vals = {(c+shift): (f'{annotate_vals[c]:{fmt_annotate}}' if (type(annotate_vals[c])!=str) else annotate_vals[c]) for c in annotate_vals}
        print(annotate_vals)
        clim = np.array(clim) + shift    
        cmap = pv.LookupTable(cmap=cmap, scalar_range=clim, annotations=annotate_vals,
                              nan_color='#dddddd') 
  

    else:
        cmap = None
        scalar_bar_settings = {}
        show_scalarbar = dict(undeformed=False, deformed=False)

    if show_maxmin:
        max_el = elements[np.argmax(vals)]
        min_el = elements[np.argmin(vals)]
        element_labels = [min_el, max_el]                
        element_label_fun = get_maxmin_fun()

    el_opts['clim'] = clim
    def_el_opts['clim'] = clim

    canvas_settings = dict(background_color='white')
    tmat_settings = dict(show_edges=False)    

    nodelabel_settings = dict(always_visible=True)
    elementlabel_settings = dict(always_visible=True, text_color='blue', shape_color='white', shape_opacity=0.4)
    
    def_el_settings = dict(
        scalars=scalars['deformed'],
        render_lines_as_tubes=True,
        style='wireframe',
        line_width=4,
        lighting=True,
        cmap=cmap,
        show_scalar_bar=show_scalarbar['deformed'],
        color='#ee8899'
        )
    
    el_settings = dict(
        scalars=scalars['undeformed'],
        render_lines_as_tubes=True,
        style='wireframe',
        lighting=True,
        line_width=4,
        cmap=cmap,
        show_scalar_bar=show_scalarbar['undeformed'],
        )

    
    node_settings = dict(
        render_points_as_spheres=True,
        color='magenta',
        lighting=True,
        point_size=5
    )  
    
    def_el_settings.update(def_el_opts)
    canvas_settings.update(canvas_opts)
    el_settings.update(el_opts)
    node_settings.update(node_opts)
    tmat_settings.update(tmat_opts)
    nodelabel_settings.update(nodelabel_opts)
    elementlabel_settings.update(elementlabel_opts)
    
    rel_constraint_settings = dict(el_settings) | {'render_lines_as_tubes': False, 'line_width':2}
    def_rel_constraint_settings = dict(def_el_settings) | {'render_lines_as_tubes': False, 'line_width': 2}
    
    rel_constraint_settings.update(constraint_opts)
    def_rel_constraint_settings.update(def_constraint_opts)
    
    
    if pl is None:
        pl = pv.Plotter()
        
    mesh = generate_mesh(elements,'x0')

    if 'undeformed' in plot_states:
        if thickness_scaling is not None:
            grouped_els, group_ixs = group_elements(elements)
            lw0 = el_settings['line_width']*1.0
            for sec in grouped_els:
                el_settings['line_width'] = lw0*thickness_scaling(sec)

                if 'undeformed' in vals_on:
                    el_settings['scalars'] = scalars['undeformed'][group_ixs[sec]]

                pl.add_mesh(generate_mesh(grouped_els[sec],'x0'), 
                             scalar_bar_args=scalar_bar_settings, annotations=annotate_vals, 
                            **el_settings)     
        else:
            pl.add_mesh(mesh, scalar_bar_args=scalar_bar_settings, 
                        annotations=annotate_vals, **el_settings)

    if 'deformed' in plot_states:
        if thickness_scaling is not None:
            grouped_els, group_ixs = group_elements(elements)
            lw0 = def_el_settings['line_width']*1.0
            for sec in grouped_els:
                def_el_settings['line_width'] = lw0*thickness_scaling(sec)
                
                if 'deformed' in vals_on:
                    el_settings['scalars'] = scalars['deformed'][group_ixs[sec]]

                pl.add_mesh(generate_mesh(grouped_els[sec], 'x'), annotations=annotate_vals,
                            scalar_bar_args=scalar_bar_settings, **def_el_settings)     
        else:
            pl.add_mesh(generate_mesh(elements, 'x'), annotations=annotate_vals,
                        scalar_bar_args=scalar_bar_settings, **def_el_settings)
    
    if 'relative' in plot_constraints:
        fields = {'undeformed':'x0', 'deformed':'x'}
        settings = {'undeformed': rel_constraint_settings,
                    'deformed': def_rel_constraint_settings}
        for state in constraints_on:
            mesh = generate_constraint_mesh(eldef.constraints, field=fields[state])
            pl.add_mesh(mesh, **settings[state])     

       
    
    if plot_nodes is not False:
        if plot_nodes is True:
            nodes_to_plot = nodes*1
        else:
            nodes_to_plot = [node for node in nodes if node in plot_nodes]
            
        if 'undeformed' in plot_node_states:
            pl.add_points(generate_node_mesh(nodes_to_plot, 'x0'), **node_settings)
        if 'deformed' in plot_node_states:
            pl.add_points(generate_node_mesh(nodes_to_plot, 'x'), **node_settings)

    if node_labels is not False:
        if node_labels is not True:
            nodes = [node for node in nodes if node in node_labels]
            
        lbl = [node_label_fun(node) for node in nodes]
        
        if 'deformed' in plot_states:
            pl.add_point_labels(np.vstack([node.x[:3] for node in nodes]), lbl, **nodelabel_settings)
        else:
            pl.add_point_labels(np.vstack([node.x0[:3] for node in nodes]), lbl, **nodelabel_settings)            
            
    if element_labels  is not False:
        if element_labels is not True:
            elements_labeled = [element for element in elements if element.label in element_labels]
        else:
            elements_labeled = elements
        
        lbl = [element_label_fun(el) for el in elements_labeled]

        if 'deformed' in plot_states:
            pl.add_point_labels(np.vstack([conv_fun(el.get_cog(deformed=True)) for el in elements_labeled]), lbl,  **elementlabel_settings)
        else:
            pl.add_point_labels(np.vstack([conv_fun(el.get_cog(deformed=False)) for el in elements_labeled]), lbl, **elementlabel_settings)            
            
        
    for key in canvas_settings:
        setattr(pl, key, canvas_settings[key])

    if plot_tmat_ax is not None:
        for state in tmat_on:
            if state == 'deformed':
                T_field = 'Tn'
                deformed = True
            else:
                T_field = 'T0'
                deformed = False
            
            for ax in plot_tmat_ax:
                vec = []
                pos = []

                for el in elements:
                    vec.append(conv_fun(getattr(el,T_field)[ax, :][:3])[:3]*tmat_scaling)
                    pos.append(conv_fun(el.get_cog(deformed=deformed)))

                pl.add_arrows(np.vstack(pos), np.vstack(vec), color=tmat_colors[ax], **tmat_settings)
    
    if elements[0].domain == '2d':
        pl.view_xy()
    else:
        pl.view_isometric()
    
    if view is not None:
        if view in ['xy', 'top']:
            pl.view_xy()
        if view in ['yz', 'front']:
            pl.view_yz()
        if view in ['xz', 'side']:
            pl.view_xz()
        
    
    pl.show_axes()
    if show:
        pl.show()

    return pl

Functions

def flat(l)
def frame_creator(frames=30, repeats=1, swing=False, full_cycle=False)

Construct scaling of value.

Arguments

frames : int, optional
number of frames (30 is standard)
repeats : int, optional
number of repeats (1 is standard)
swing : False, optional
whether or not to swing back and forth
full_cycle : False, optional
whether or not to include the full cycle (resulting in initial scaling being -1 rather than 0)

Returns

scaling : float
numpy array describing scaling of value to animate
def plot_eldef(eldef, plot_elements=True, plot_states=['undeformed'], plot_nodes=False, vals=None, el_opts={}, def_el_opts={}, node_opts={}, canvas_opts={}, show=True, plot_tmat_ax=[1, 2], tmat_opts={}, tmat_scaling=10, tmat_on=[], val_fun=None, show_maxmin=False, maxmin_fmt='.2f', vals_on=[], colorbar_opts={}, clim=None, annotate_vals={}, pl=None, node_labels=False, element_labels=False, thickness_scaling=None, cmap='viridis', view=None, nodelabel_opts={}, elementlabel_opts={}, element_label_fun=None, constraints_on=['undeformed', 'deformed'], def_constraint_opts={}, constraint_opts={}, plot_constraints=[], node_label_fun=None, plot_node_states=None)