Your first ETABS app
After you have downloaded the 'ETABS getting started' template, this step-by-step guide will explain you how to complete it. We will explain how you can process and visualize outputs from ETABS. You'll see how to export these outputs from ETABS into a file, upload them into VIKTOR and post-process reaction loads for various load combinations. Along the way, you will work with Plotly and Pandas, using the flexible tools and building blocks that VIKTOR offers.
Here is what we'll cover:
- Add the required input fields
- Processing the uploaded
.xlsx
file exported from ETABS - Adding processed data into a field so that the user can select from the results
- Visualizing results based on those selections
By the end of this tutorial, you will be able to generate a heatmap of the reaction loads for your structure by selecting specific load cases. This will allow you to gain valuable insights from your results and easily share them with your clients and team. Additionally, you will learn how to automate this process using VIKTOR. The final result will look like the figure below:
1. Add input fields
Let's add some text and input fields for the user to upload their file into. We will include a FileField
to upload our .xlsx
file
and an OptionField
to select a load combination from our Excel file.
Replace the placeholder Text
that explained you where to find these instructions with the highlighted lines of code in app.py
,
save the file and refresh the app in the browser. You should now see the input fields you just added to the code.
import viktor as vkt
import pandas as pd
import plotly.graph_objects as go
class Parametrization(vkt.Parametrization):
intro = vkt.Text("""
# ETABS reaction heatmap
This app allows you to inspect results from an uploaded ETABS output file. Export your ETABS model results to an .xlsx file and upload it below. After uploading your Excel file, select the load combination you want to visualize.
Ensure the file includes the tables:
- **Joint Reactions**
- **Objects and Elements - Joints**
""")
xlsx_file = vkt.FileField("Upload ETABS exported .xlsx")
lb = vkt.LineBreak()
selected_load_combo = vkt.OptionField("Select available load combos", options=[])
class Controller(vkt.Controller):
parametrization = Parametrization
The result should look like the image below. Now that the inputs are sorted, we will continue with processing the ETABS data.
2. Process the XLSX file
Sample ETABS exported .xlsx
file
For this tutorial, you can use this sample data from etabs_tutorial.xlsx
located in your app folder, but don't worry; at the end of this tutorial, we will guide you on how to export this data from ETABS and create your own file.
Processing the uploaded xlsx file
The next step is to process the data from the uploaded Excel file. It's a good idea to do this processing in a separate function, that we'll name process_etabs_file
. This function will extract the unique load combinations (which will be used later in our OptionField
) and the joint reactions. The goal is to combine this data with the joint coordinates to create a DataFrame that can be used to plot the heatmap in the Plotly view. Below is the logic in the code, along with a breakdown. Add the highlighted lines of code to app.py
and save the file.
import viktor as vkt
import pandas as pd
import plotly.graph_objects as go
def process_etabs_file(uploaded_file):
# Read the file into a dataframe
sheet_names = ["Joint Reactions", "Objects and Elements - Joints"]
with uploaded_file.file.open_binary() as fr:
dataframes = pd.read_excel(fr, sheet_name=sheet_names, skiprows=1)
# Process the 'Joint Reactions' dataframe
loads_df = dataframes["Joint Reactions"].dropna(subset=["Unique Name", "Output Case"]).copy()
# Process the 'Objects and Elements - Joints' dataframe
cords = dataframes["Objects and Elements - Joints"].dropna(
subset=["Element Name", "Object Name", "Global X", "Global Y", "Global Z"]
).copy()
cords = cords.rename(columns={"Object Name": "Unique Name"})
# Get unique load case names as a list
unique_output_cases = loads_df["Output Case"].unique().tolist()
# Merge loads and cords dataframe
merged_df = pd.merge(loads_df, cords, on="Unique Name", how="inner")
return unique_output_cases, merged_df.reset_index(drop=True)
class Parametrization(vkt.Parametrization):
intro = vkt.Text("""
# ETABS reaction heatmap
This app allows you to inspect results from an uploaded ETABS output file. Export your ETABS model results to an .xlsx file and upload it below. After uploading your Excel file, select the load combination you want to visualize.
Ensure the file includes the tables:
- **Joint Reactions**
- **Objects and Elements - Joints**
""")
xlsx_file = vkt.FileField("Upload ETABS exported .xlsx")
lb = vkt.LineBreak()
selected_load_combo = vkt.OptionField("Select available load combos", options=[])
class Controller(vkt.Controller):
parametrization = Parametrization
How this works
- In the top of the file we import Pandas, which we'll use to load the file content into dataframes
- The function reads the excel file and extract the necessary sheets: Joint Reactions and Objects and Elements - Joints.
- Then we filter for and remove the rows that we want based on the required data, keeping columns
Global X
,Global Y
, andGlobal Z
. - Finally, merge the processed Joint Reactions data with the joint coordinates using the
Unique Name
column as the key. This merged DataFrame includes all the necessary information to plot a heatmap. - This functions returns two things at once, the unique load combinations and the merged dataframe.
3. Allow user selection of load combinations
With the function to process the uploaded file done, we can now use that processed data in the Parametrization
. We will allow users to select a load combination from the unique load combinations. To get the unique load combinations into the OptionField
, we can use a callback function. Let's call it get_load_combos
. It will get the unique load combinations from read_file
and update our OptionField
to display a dropdown with the correct options. Add the highlighted lines of code to app.py
and don't forget to reference the get_load_combos
in the selected_load_combo
field.
import viktor as vkt
import pandas as pd
import plotly.graph_objects as go
def process_etabs_file(uploaded_file):
# Read the file into a dataframe
sheet_names = ["Joint Reactions", "Objects and Elements - Joints"]
with uploaded_file.file.open_binary() as fr:
dataframes = pd.read_excel(fr, sheet_name=sheet_names, skiprows=1)
# Process the 'Joint Reactions' dataframe
loads_df = dataframes["Joint Reactions"].dropna(subset=["Unique Name", "Output Case"]).copy()
# Process the 'Objects and Elements - Joints' dataframe
cords = dataframes["Objects and Elements - Joints"].dropna(
subset=["Element Name", "Object Name", "Global X", "Global Y", "Global Z"]
).copy()
cords = cords.rename(columns={"Object Name": "Unique Name"})
# Get unique load case names as a list
unique_output_cases = loads_df["Output Case"].unique().tolist()
# Merge loads and cords dataframe
merged_df = pd.merge(loads_df, cords, on="Unique Name", how="inner")
return unique_output_cases, merged_df.reset_index(drop=True)
def get_load_combos(params, **kwargs):
if params.xlsx_file:
load_combos, _ = process_etabs_file(params.xlsx_file) # Only use the first returned item, the load_combos
return load_combos
return []
class Parametrization(vkt.Parametrization):
intro = vkt.Text("""
# ETABS reaction heatmap
This app allows you to inspect results from an uploaded ETABS output file. Export your ETABS model results to an .xlsx file and upload it below. After uploading your Excel file, select the load combination you want to visualize.
Ensure the file includes the tables:
- **Joint Reactions**
- **Objects and Elements - Joints**
""")
xlsx_file = vkt.FileField("Upload ETABS exported .xlsx")
lb = vkt.LineBreak()
selected_load_combo = vkt.OptionField("Select available load combos", options=get_load_combos)
class Controller(vkt.Controller):
parametrization = Parametrization
By replacing the options=[]
with a reference to the callback function (options=get_load_combos
) we fill the field with the processed load combos from the file (if there is a file uploaded).
4. Visualize reaction loads with Plotly
Once the user selected a load combination, we filter the data to focus on the selected load combination. Then we use Plotly to generate an interactive heat map. To visualize a Plotly figure in a VIKTOR app we can add a PloltyView
method to the Controller
.
We will add a scatter plot to represent each joint from the ETABS data, with its color indicating the magnitude of the reaction load (FZ) in kN. This visualization allows you to clearly observe how loads are distributed across the base floor, making it easier to analyze and interpret the results.
Copy the highlighted piece of code and add it to app.py
.
import viktor as vkt
import pandas as pd
import plotly.graph_objects as go
def process_etabs_file(uploaded_file):
# Read the file into a dataframe
sheet_names = ["Joint Reactions", "Objects and Elements - Joints"]
with uploaded_file.file.open_binary() as fr:
dataframes = pd.read_excel(fr, sheet_name=sheet_names, skiprows=1)
# Process the 'Joint Reactions' dataframe
loads_df = dataframes["Joint Reactions"].dropna(subset=["Unique Name", "Output Case"]).copy()
# Process the 'Objects and Elements - Joints' dataframe
cords = dataframes["Objects and Elements - Joints"].dropna(
subset=["Element Name", "Object Name", "Global X", "Global Y", "Global Z"]
).copy()
cords = cords.rename(columns={"Object Name": "Unique Name"})
# Get unique load case names as a list
unique_output_cases = loads_df["Output Case"].unique().tolist()
# Merge loads and cords dataframe
merged_df = pd.merge(loads_df, cords, on="Unique Name", how="inner")
return unique_output_cases, merged_df.reset_index(drop=True)
def get_load_combos(params, **kwargs):
if params.xlsx_file:
load_combos, _ = process_etabs_file(params.xlsx_file) # Only use the first returned item, the load_combos
return load_combos
return []
class Parametrization(vkt.Parametrization):
intro = vkt.Text("""
# ETABS reaction heatmap
This app allows you to inspect results from an uploaded ETABS output file. Export your ETABS model results to an .xlsx file and upload it below. After uploading your Excel file, select the load combination you want to visualize.
Ensure the file includes the tables:
- **Joint Reactions**
- **Objects and Elements - Joints**
""")
xlsx_file = vkt.FileField("Upload ETABS exported .xlsx")
lb = vkt.LineBreak()
selected_load_combo = vkt.OptionField("Select available load combos", options=get_load_combos)
class Controller(vkt.Controller):
parametrization = Parametrization
@vkt.PlotlyView("Heatmap")
def plot_heat_map(params,**kwargs):
if params.selected_load_combo:
_, merged_df = process_etabs_file(params.xlsx_file)
# Filter the dataframe based on the selected load combination
filtered_df = merged_df[merged_df["Output Case"] == params.selected_load_combo]
FZ_min, FZ_max = filtered_df["FZ"].min(), filtered_df["FZ"].max()
# Create plotly scatter plot
fig = go.Figure(
data=go.Scatter(
x=filtered_df["Global X"],
y=filtered_df["Global Y"],
mode='markers+text',
marker=dict(
size=16,
color=filtered_df["FZ"],
colorscale=[
[0, "green"],
[0.5, "yellow"],
[1, "red"]
],
colorbar=dict(title="FZ (kN)"),
cmin=FZ_min,
cmax=FZ_max
),
text=[f"{fz:.1f}" for fz in filtered_df["FZ"]],
textposition="top right"
)
)
# Style the plot
fig.update_layout(
title=f"Heatmap for Output Case: {params.selected_load_combo}",
xaxis_title="X (m)",
yaxis_title="Y (m)",
plot_bgcolor='rgba(0,0,0,0)',
)
fig.update_xaxes(
linecolor='LightGrey',
tickvals=filtered_df["Global X"],
ticktext=[f"{x / 1000:.3f}" for x in filtered_df["Global X"]],
)
fig.update_yaxes(
linecolor='LightGrey',
tickvals=filtered_df["Global Y"],
ticktext=[f"{y / 1000:.3f}" for y in filtered_df["Global Y"]],
)
return vkt.PlotlyResult(fig.to_json())
else:
return vkt.PlotlyResult({})
This code explained
- At the top of the file, we import Plotly, which we'll use to create the plot
- At the top of the function, we use
process_etabs_file
again, but this time we only use the second returned item, the merged dataframe. - The dataframe is filtered, based on the selected option in
params.selected_load_combo
. We also find the lowest and highest values for FZ. - Using Plotly, we create a scatter plot based on the filtered dataframe. Using the lowest and highest values for FZ the colors are determined for each point.
- Finally, we apply some styling to the figure, like adding a title, setting linecolors and text for the tickmarks
The resulting app
Refresh your app in the browser. You will now see the empty graph appear on the right side of the screen. You can now upload the Excel sheet with ETABS loads in the upload field. After that select one of the load combinations, and a heatmap will display the magnitudes, color-coded, along with the joint locations. You can try any combination and test it with any ETABS model. Our final app will look like this:
Complete code
Were you able to do everything in this tutorial without error? If not, you can always look at the full code:
import viktor as vkt
import pandas as pd
import plotly.graph_objects as go
def process_etabs_file(uploaded_file):
# Read the file into a dataframe
sheet_names = ["Joint Reactions", "Objects and Elements - Joints"]
with uploaded_file.file.open_binary() as fr:
dataframes = pd.read_excel(fr, sheet_name=sheet_names, skiprows=1)
# Process the 'Joint Reactions' dataframe
loads_df = dataframes["Joint Reactions"].dropna(subset=["Unique Name", "Output Case"]).copy()
# Process the 'Objects and Elements - Joints' dataframe
cords = dataframes["Objects and Elements - Joints"].dropna(
subset=["Element Name", "Object Name", "Global X", "Global Y", "Global Z"]
).copy()
cords = cords.rename(columns={"Object Name": "Unique Name"})
# Get unique load case names as a list
unique_output_cases = loads_df["Output Case"].unique().tolist()
# Merge loads and cords dataframe
merged_df = pd.merge(loads_df, cords, on="Unique Name", how="inner")
return unique_output_cases, merged_df.reset_index(drop=True)
def get_load_combos(params, **kwargs):
if params.xlsx_file:
load_combos, _ = process_etabs_file(params.xlsx_file)
return load_combos
return []
class Parametrization(vkt.Parametrization):
intro = vkt.Text("""
# ETABS reaction heatmap
This app allows you to inspect results from an uploaded ETABS output file. Export your ETABS model results to an .xlsx file and upload it below. After uploading your Excel file, select the load combination you want to visualize.
Ensure the file includes the tables:
- **Joint Reactions**
- **Objects and Elements - Joints**
""")
xlsx_file = vkt.FileField("Upload ETABS exported .xlsx")
lb = vkt.LineBreak()
selected_load_combo = vkt.OptionField("Select available load combos", options=get_load_combos)
class Controller(vkt.Controller):
parametrization = Parametrization
@vkt.PlotlyView("Heatmap")
def plot_heat_map(params,**kwargs):
if params.selected_load_combo:
_, merged_df = process_etabs_file(params.xlsx_file)
# Filter the dataframe based on the selected load combination
filtered_df = merged_df[merged_df["Output Case"] == params.selected_load_combo]
FZ_min, FZ_max = filtered_df["FZ"].min(), filtered_df["FZ"].max()
# Create plotly scatter plot
fig = go.Figure(
data=go.Scatter(
x=filtered_df["Global X"],
y=filtered_df["Global Y"],
mode='markers+text',
marker=dict(
size=16,
color=filtered_df["FZ"],
colorscale=[
[0, "green"],
[0.5, "yellow"],
[1, "red"]
],
colorbar=dict(title="FZ (kN)"),
cmin=FZ_min,
cmax=FZ_max
),
text=[f"{fz:.1f}" for fz in filtered_df["FZ"]],
textposition="top right"
)
)
# Style the plot
fig.update_layout(
title=f"Heatmap for Output Case: {params.selected_load_combo}",
xaxis_title="X (m)",
yaxis_title="Y (m)",
plot_bgcolor='rgba(0,0,0,0)',
)
fig.update_xaxes(
linecolor='LightGrey',
tickvals=filtered_df["Global X"],
ticktext=[f"{x / 1000:.3f}" for x in filtered_df["Global X"]],
)
fig.update_yaxes(
linecolor='LightGrey',
tickvals=filtered_df["Global Y"],
ticktext=[f"{y / 1000:.3f}" for y in filtered_df["Global Y"]],
)
return vkt.PlotlyResult(fig.to_json())
else:
return vkt.PlotlyResult({})
Export your own results from ETABS
If you want to try this app with your own data, first run your analysis and then export the reaction loads from your ETABS model. Navigate to File menu > Export > ETABS Tables to Excel
within ETABS, and select the Joint Reactions and Objects and Elements - Joints tables, as shown in the figure below:
Ensure that your exported Excel file contains these specific sheets. Without them, the app won’t be able to read and process the data correctly. Set the model units to international system units. The model expects the loads in kN and in millimeters (mm).
What's next?
In this tutorial, you learned how to post-process results exported from ETABS. Your next goal could be to implement automation using the CSI API from your VIKTOR app. To achieve this, the best place to start is the following tutorial.
If you want to explore more use cases where VIKTOR shines, feel free to check out our other tutorials!