3. Reviewing our app's structure
Project Setup
📂 Please log in or enroll to access resources

In addition to the app.py file, we’re going to break our app up across 4 additional files. This will help keep our code relatively easy to navigate and manage but if you prefer you could keep all of your code in the app.py file. All of these additional .py files can be located in the root directory of our project, along with the app.py file. Let’s consider each new file briefly and generate a skeleton for each one.

3.1 analysis.py

This file will contain all of our OpenSeesPy code in a single function called analyseStructure(). This function will accept all of the necessary input data to define and solve the structural model. It will return:

  • displacements at each end of each member
  • nodal displacements
  • internal member forces
  • reactions

The code in this file will be derived directly from the Jupyter notebook from a previous OpenSeesPy tutorial.

At this stage, we can generate the analysis.py file and simply define the function and its arguments. We’ll return to fill in the function body later.

analysis.py

import openseespy.opensees as ops

def analyseStructure(E, A, nodes, members, restraints, forces):
	#Fill in function body later
	return

Note that we’re importing the openseespy module as ops at the top of the file. At the end of this lecture, when we try to import this function into app.py, so we can call it from our Controller class, this will cause an error because the openseespy module is not installed yet in our development environment. So, let's do that now.

In your Codespace terminal, make sure your VIKTOR app is not running (execute control+C in the terminal to stop the app). Then install the openseespy module by running the following command:

pip install openseespy

When the install is complete, you should see a message along the lines of Successfully installed openseespy-3.5.1.12 openseespylinux-3.5.1.12. Note that your version number may be different, depending on when you execute the install command. Now we just need to add this library along with the version number to our requirements.txt file.

App requirements

A requirements.txt file was generated for us during our app setup. This file is used to specify the Python libraries that our app depends on. When we deploy our app, VIKTOR will install these libraries for us in the app’s environment. For this app, our only third-party dependency not already included in the VIKTOR environment is openseespy.

Your requirements.txt file should look like this (note that your version numbers may be different):

requirements.txt

viktor==14.10.0
openseespy==3.5.1.12

3.2 visualisation.py

This file will contain all of the code needed to generate the data visualisations (plots) for the structure. Again this code will be based very closely on our Jupyter Notebook code. The file will contain 2 functions that will be called from our main app.py file and two helper functions that are called from within visualisation.py.

  1. plotStructure() will be used to plot the structure once it’s defined and before it has been solved.
  2. plotResponse() will be used to generate the plot of deflected shape and the axial force diagram. plotResponse() will call the helper functions subplotForces() and subplotDeflection() which will handle generation of these respective subplots.

visualisation.py

import matplotlib.pyplot as plt
from io import StringIO

def plotStructure(nodes, members):
	#Fill in function body later
	return

def plotResponse(nodes, members, nodal_disp, mbr_forces, xFac):
	#Fill in function body later
	return

def subplotForces(ax, nodes, members, mbr_forces):
	#Fill in function body later
	return

def subplotDeflection(ax, nodes, members, nodal_disp, xFac):
	#Fill in function body later
	return

3.3 dataIO.py

This file contains five functions that are associated with data input and output. These are all basically utility functions that perform some sort of conversion on incoming or outgoing data.

  1. processGeometryFile() is used to read the geometry csv file provided by the user and convert the data contained (node locations and member definitions) into NumPy ndarrays that we can work with in our analyseStructure() function.
  2. computeRestraintArray() takes the restraint information provided by the user in table format and converts it to an ndarray that our analyseStructure() function can use.
  3. computeForceArray() does the same thing for the user defined force data.
  4. exportResults() takes the information (forces, displacements etc.) generated by our analysis and converts them to a BytesIO object so they can be downloaded by the user as a csv file.
  5. exportDemo() takes the demo geometry csv file (we'll add this to the project directory later), reads its contents and converts it to a BytesIO object so it can also be downloaded by the user. This provides new users with some ‘getting started’ geometry to play around with.

So, we can create the dataIO.py file and generate its skeleton as shown below.

dataIO.py

import numpy as np
import io
import os

def processGeometryFile(input):
	#Fill in function body later
	return

def computeRestraintArray(inputData):
	#Fill in function body later
	return

def computeForceArray(inputData):
	#Fill in function body later
	return

def exportResults(mbr_forces, node_disp, reactions, members):
	#Fill in function body later
	return

def exportDemo():
	#Fill in function body later
	return

3.4 text.py

The final file is text.py - this is simply used as a place to store the strings used throughout our app. Storing all of our text in one location like this keeps the rest of our code clean and means we can find our text relatively easily if we need to make edits. We’ll add to this file as we go - it doesn’t contain any functions, only multi-line comments that are assigned variable names so we can access them from app.py.

text.py

welcomeText = """
This is a multi-line comment
We'll use somewhere in our app :)

## Markdown headers can also be used
We can also use markdown math formatting (eg. N/mm^2) and have it render correctly
"""

moreText="""
...
...
...
"""

Finally, we can return to app.py and import the contents of these four files so we can access their functions from within app.py.

app.py

# Other imports
# ...
# ...

# Utility functions and text blocks
from analysis import *
from visualisation import *
from dataIO import *
from text import *

class Parametrization(ViktorParametrization):
# ...
# ...
# ...

class Controller(ViktorController):
# ...
# ...
# ...

Now that we have initialised our app, reviewed the basic structure and setup the our function definitions, in the next section we’ll start building it out by filling in the various function bodies.

💡 Make sure to start your app again (viktor-cli start) following your openseespy install, to make sure everything is working. And again, now would be a great time to commit your code!

Next Lesson
4. Setting up input parameters