A3. Visualisation

Objectives

You will learn how to visualise the reaction, residual and edge forces and external loads in Rhino with separate functions. These functions will not only be used within this script but also imported for all other scripts throughout the three weeks on cable nets.

Visualisation Functions

The visualisation functions all follow a similar pattern: we build a list of dictionaries for each vertex/edge that we then hand over to a compas_rhino function that does the rest for us.

Check the documentation e.g. for draw_lines to find out what dictionary it expects.

Reaction Forces

def draw_reactions(mesh, baselayer, color=(0, 255, 0), scale=1):
    """Visualise the reaction forces in rhino as green arrows.
    """
    lines = []
    for key in mesh.vertices_where({'is_anchor': True}):
        start = mesh.vertex_attributes(key, 'xyz')
        residual = mesh.vertex_attributes(key, ['rx', 'ry', 'rz'])
        end = add_vectors(start, scale_vector(residual, -scale))
        lines.append({
            'start': start,
            'end': end,
            'name': "{}.reaction.{}".format(key, round(length_vector(residual), 3)),
            'arrow': 'end',
            'color': color
        })

    layer = baselayer+"::Reactions"
    compas_rhino.draw_lines(lines, layer=layer, clear=True)

Residual Forces

def draw_residuals(mesh, baselayer, color=(0, 255, 255), tol=0.001, scale=1):
    """Visualise the residual forces in rhino as cyan arrows.
    """
    lines = []
    for key in mesh.vertices_where({'is_anchor': False}):
        start = mesh.vertex_attributes(key, 'xyz')
        residual = mesh.vertex_attributes(key, ['rx', 'ry', 'rz'])
        end = add_vectors(start, scale_vector(residual, scale))
        if length_vector(residual) < tol:
            continue
        lines.append({
            'start': start,
            'end': end,
            'name': "{}.residual.{}".format(key, round(length_vector(residual), 3)),
            'arrow': 'end',
            'color': color})

    layer = baselayer+"::Residuals"
    compas_rhino.draw_lines(lines, layer=layer, clear=True)

Edge Forces

def draw_forces(mesh, baselayer, color=(255, 0, 0), scale=0.1, tol=0.001):
    """Visualise the edge forces in rhino as red-gradient pipes.
    """
    f_max = []
    for edge in mesh.edges():
        f = mesh.edge_attribute(edge, 'f')
        f_max.append(abs(f))
    f_max = max(f_max)

    cylinders = []
    for edge in mesh.edges():
        f = mesh.edge_attribute(edge, 'f')
        radius = scale * f
        if radius < tol:
            continue
        start_end = mesh.edge_coordinates(*edge)

        cylinders.append({
            'start': start_end[0],
            'end': start_end[1],
            'name': "{}.force.{}".format(edge, round(f, 3)),
            'radius': radius,
            'color': i_to_red(abs(f)/f_max)})

    layer = baselayer+"::Forces"
    compas_rhino.draw_cylinders(cylinders, layer=layer, clear=True)

External Loads

def draw_loads(mesh, baselayer, color=(0, 255, 0), scale=1):
    """Visualise the external loads in rhino as green arrows.
    """
    lines = []
    for key in mesh.vertices_where({'is_anchor': False}):
        start = mesh.vertex_attributes(key, 'xyz')
        load = mesh.vertex_attributes(key, ['px', 'py', 'pz'])
        end = add_vectors(start, scale_vector(load, scale))
        lines.append({
            'start': start,
            'end': end,
            'name': "{}.load.{}".format(key, round(length_vector(load), 3)),
            'arrow': 'end',
            'color': color})

    layer = baselayer+"::Loads"
    compas_rhino.draw_lines(lines, layer=layer, clear=True)

All these visualisation functionalities and more are already available in compas_fofin's CablenetArtist.

Main

The if __name__ == '__main__': checks if the file itself is run or not. So everything inside will only be executed if we run the A3_visualization.py file itself but to if we import its functions from another script. Everything inside serves for testing the functions.

So inside the main body, we create some artificial load and reaction force that we then visualise together with the form found mesh that we import from the data folder.

# ==============================================================================
# Main
# ==============================================================================

if __name__ == '__main__':

    def test_load(mesh):
        # placeholder only to have something to visualise

        # for all vertices that are free
        for key, attr in mesh.vertices_where({'is_anchor': False}, True):

            attr['pz'] = -0.05

    def test_residual(mesh):
        # placeholder only to have something to visualise

        # for all vertices that are free
        for key, attr in mesh.vertices_where({'is_anchor': False}, True):
            attr['rz'] = 0.1

    # ==============================================================================
    # Initialise
    # ==============================================================================

    HERE = os.path.dirname(__file__)
    DATA = os.path.abspath(os.path.join(HERE, '..', 'data'))
    FILE_I = os.path.join(DATA, 'cablenet_fofin.json')

    # create the mesh from imported geometry
    mesh = Mesh.from_json(FILE_I)

    test_load(mesh)
    test_residual(mesh)
    
    # ==============================================================================
    # Visualize
    # ==============================================================================

    baselayer = "CSD2::A3::Visualization"

    artist = MeshArtist(mesh, layer=baselayer+"::Mesh")
    artist.clear_layer()

    artist.draw_vertices(color={vertex: (255, 0, 0) for vertex in mesh.vertices_where({'is_anchor': True})})
    artist.draw_edges()
    artist.draw_faces()

    draw_reactions(mesh, baselayer=baselayer)
    draw_residuals(mesh, baselayer=baselayer)
    # draw_forces(mesh, baselayer=baselayer)
    draw_loads(mesh, baselayer=baselayer, scale=10)

Access from Other Scripts

As mentioned, the visualisation functions can also be accessed from other Python scripts. This can be done with the import keyword, same as we for example import a Mesh or MeshArtist from the COMPAS framework:

from compas.datastructures import Mesh

from compas_rhino.artists import MeshArtist
from A3_visualization import draw_reactions  
from A3_visualization import draw_residuals  
from A3_visualization import draw_forces  
from A3_visualization import draw_loads  

For importing the function in another file, the A3_visualisation.py file must be in the same subfolder! So just copy the file over into the next week's session folder.

This can be avoided when working with your own package and modules, however, this is outside the scope of this course.

Last updated