Analyzing cross-sections can be one of the more challenging aspects of a structural engineer's work. Detailed calculations of area, moments of inertia, centroids, radii of gyration, and section modulus require time and effort, and traditional tools are often not easy to use or accessible. These properties are crucial in:
Structural Design: Properties like area and moments of inertia help determine the section's ability to resist bending and compression.
Stability Analysis: Radii of gyration and centroids help assess column stability and stress distribution.
Finite Element Analysis (FEA): Moments of inertia and centroids are needed to create accurate models for analysis.
Fortunately, SectionProperties, an open-source Python package developed to help engineers perform these calcluations quickly, is here to change that.
In this blog, you will learn how to get started with this tool and how it could transform your workflow.
Installing SectionProperties is as simple as running the following command in your terminal:
1pip install sectionproperties
Below, we will outline the basic workflow for using SectionProperties to calculate properties and analyze a cross-section.
First, we import the specific modules we will need.
Material allows us to define mechanical properties of materials, Geometry lets us import or define the geometry of a section, and Section is responsible for analyzing the section, such as calculating its geometric properties and generating stresses.
1from sectionproperties.pre import Material 2from sectionproperties.pre.geometry import Geometry 3from sectionproperties.analysis.section import Section 4import matplotlib.pyplot as plt
Next, we define a material for our section. In this case, we will use concrete:
1concrete = Material(
2 name="Concrete",
3 elastic_modulus=30.1e3, # MPa
4 poissons_ratio=0.2,
5 density=2.4e-6, # kg/mm³
6 yield_strength=32, # MPa
7 color="lightgrey")
The SectionProperties library provides built-in functions to create typical cross-sections such as:
Steel Sections: channel section, circular hollow section, angle_section, etc.
Concrete Sections: concrete tee section, concrete circular section, etc.
Bridge Sections
These functions are very useful for quickly defining typical structural cross-sections.
However, for more complex sections, it can be challenging to define them directly through code.
It is often easier to draw the section in AutoCAD or similar software and export it as a .dxf file, which can then be imported for analysis.
We import the geometry of our section, which represents a box girder bridge, from a .dxf file and associate it with the defined material:
1geom = Geometry.from_dxf(dxf_filepath="seccion1.dxf")
2geom.material = concrete
3geom.plot_geometry()
Meshing is a crucial step in finite element analysis. It directly impacts the accuracy and reliability of the results.
Balancing mesh density is key: a fine mesh can improve accuracy, especially in critical areas, but also increases computational effort. We create the mesh for the section with the following code:
1geom.create_mesh(mesh_sizes=30) 2sec = Section(geom) 3# sec.display_mesh_info() 4sec.plot_mesh() 5plt.show()
It is possible to control the mesh size using the mesh_sizes parameter, allowing us to achieve better accuracy, control computational cost, and analyze complex sections.
Visualizing the mesh allows us to verify that the discretization is adequate before proceeding with the calculations.
Once we have generated the mesh, we can calculate the geometric- and warping properties of the section:
1sec.calculate_geometric_properties()
2sec.calculate_warping_properties()
3sec.calculate_plastic_properties()
4sec.display_results(fmt=".2f")
These calculations allow us to obtain important information, such as area, moments of inertia, and centroid coordinates. Below are some of the results obtained:
To calculate the axial, flexural, and torsional stiffness of the section and understand how it will respond to different types of loads, we use the following code:
1ea = sec.get_ea()
2eixx, _, _ = sec.get_eic()
3ej = sec.get_ej()
4gj = sec.get_g_eff() / sec.get_e_eff() * ej
5
6print(f"Axial rigidity (E.A): {ea:.3e} N")
7print(f"Flexural rigidity (E.I): {eixx:.3e} N.mm2")
8print(f"Torsional rigidity (G.J): {gj:.3e} N.mm2")
Additionally, we can visualize the elastic centroid, shear center, global plastic centroid, and principal axes to better understand the geometric distribution and structural behavior:
1sec.plot_centroids()
Finally, we can calculate and visualize the stresses. The cross-section being analyzed belongs to the central span of a simply supported beam. This beam is subjected to a combination of external loads, resulting in various types of stresses in the section:
n = 10e3 N, Axial load
mxx = 10e6 N-mm, Bending moment about the x-axis
vx = 25e3 N, Shear force in the x direction
vy = 50e3 N, Shear force in the y direction
We use this code to plot the Von Mises stresses in the section:
1stress = sec.calculate_stress(n=10e3, mxx=10e6, vx=25e3, vy=50e3) 2stress.plot_stress(stress="vm", normalize=False, fmt="{x:.2f}")
We can also use the following code to plot the shear stress vectors in the y direction:
1stress.plot_stress_vector(stress="vy_zxy", fmt="{x:.2f}")
This way, we can visualize how shear stresses are distributed, where they act, and their values, allowing us to identify critical points and potential failure areas due to shear.
Lastly, we can plot Mohr's Circle to visualize the stress state at a specific point in the section located at coordinates x = 45, y = 40.
1stress.plot_mohrs_circles(x=45, y=40) 2plt.show()
Mohr's Circle helps us visualize how principal and shear stresses relate to each other at a specific point in the section, also finding the maximum and minimum stress values and the orientations of the planes where they occur, helping to evaluate whether the section can withstand the applied loads without failing.
But what happens when you want to share your section analysis with other engineers if they don't know any Python? This is where you need a platform to turn your script into a user-friendly and accessible tool, like VIKTOR!
To show you how, we will now go through the process of turning a Python script that uses sectionproperties
into an interactive web application that anyone can use.
Let's get started!
If you would like to give this application a go, feel free to try it here:
It is important to define the parameters that the user can adjust. We will use NumberField, FileField, and OptionField, allowing users to input numerical values, upload .DXF files containing the section geometry, and select the type of analysis they wish to perform.
1class Parametrization(vkt.Parametrization): 2 title1 = vkt.Text("## Material property") 3 E = vkt.NumberField('Elastic modulus', default=30.1e3, flex=35, suffix="MPa") 4 fc = vkt.NumberField('Yield strength', default=32, flex=35, suffix="MPa") 5 6 title2 = vkt.Text("## Define Section") 7 dxf = vkt.FileField('Upload DXF file', file_types=['.dxf'], flex=40) 8 mesh = vkt.NumberField('Mesh size', default=70, flex=30) 9 10 title3 = vkt.Text("## Loads") 11 axial_force = vkt.NumberField('Axial force', default=10e3, flex=30, suffix="N") 12 momentxx = vkt.NumberField('Moment XX', default=1e6, flex=30, suffix="N-mm") 13 momentyy = vkt.NumberField('Moment YY', default=1e4, flex=30, suffix="N-mm") 14 momentzz = vkt.NumberField('Moment ZZ', default=2e5, flex=30, suffix="N-mm") 15 shear_x = vkt.NumberField('Shear force x', default=25e3, flex=30, suffix="N") 16 shear_y = vkt.NumberField('Shear force y', default=50e3, flex=30, suffix="N") 17 stress_option = vkt.OptionField('Stress visualization', options=['mzz_zxy', "vx_zxy",'zxy',"vy_zxy","v_zxy"], default='vy_zxy')
These fields allow the user to define the material properties, upload a .DXF file representing the section, and adjust the loads acting on the section.
With VIKTOR, we can take a DXF file uploaded by the user and create the geometry we will use for the calculations. The following create_geometry
method reads the DXF file and assigns material properties:
1def create_geometry(self, params):
2 geom = Geometry.from_dxf(dxf_filepath=filepath(params.dxf))
3 geom.material = Material(
4 name="Concrete",
5 elastic_modulus=params.E,
6 poissons_ratio=0.2,
7 density=2.4e-6, # kg/mm³
8 yield_strength=params.fc,
9 color="lightgrey")
10 return geom
After defining the geometry, we need to calculate its properties. This includes geometric, warping, and plastic properties. First, we must define the mesh size to be used. Choosing the right mesh size is an act of balancing the accuracy of our calculation and computation time which is why we will let the user adjust this in our tool.
1def calculate_section_properties(self, geom, mesh_size):
2 geom.create_mesh(mesh_sizes=mesh_size)
3 section = Section(geom, time_info=True)
4 section.calculate_geometric_properties()
5 section.calculate_warping_properties()
6 section.calculate_plastic_properties()
7 return section
Once we have created the geometry and mesh, we can visualize them using VIKTOR's ImageView
. For example, to visualize the geometry:
1@ImageView("Geometry")
2def visualize_geo(self, params, **kwargs):
3 geom = self.create_geometry(params)
4 fig, ax = plt.subplots()
5 geom.plot_geometry(ax=ax)
6 format_plots(ax)
7 ax.set_title(f"Cross-Section Geometry\n{params.dxf.filename[:-4]}", fontsize=10)
8 return vkt.ImageResult(self.fig_to_svg(fig))
To calculate stresses, we use the calculate_stress
method from the Section
class and visualize the results in a graph, as you can see this is a very similar setup to how we display the geometry and mesh:
1@ImageView("Stress")
2def visualize_stress(self, params, **kwargs):
3 geom = self.create_geometry(params)
4 section = self.calculate_section_properties(geom, params.mesh)
5 stress = section.calculate_stress(n=params.axial_force, mxx=params.momentxx, vx=params.shear_x, vy=params.shear_y)
6 fig, ax = plt.subplots()
7 stress.plot_stress(stress=params.stress_option, normalize=False, fmt="{x:.2f}", ax=ax, zorder=1)
8 format_plots(ax)
9 return vkt.ImageResult(self.fig_to_svg(fig))
To add more detail to the stress analysis, we can also visualize Mohr's Circle for a specific point within the section:
1@ImageView("Mohr's Circles")
2def visualize_Mohrs_Circles(self, params, **kwargs):
3 geom = self.create_geometry(params)
4 section = self.calculate_section_properties(geom, params.mesh)
5 stress = section.calculate_stress(n=params.axial_force, mxx=params.momentxx, myy=params.momentyy, mzz=params.momentzz, vx=params.shear_x, vy=params.shear_y)
6 fig, ax = plt.subplots()
7 stress.plot_mohrs_circles(x=params.x_coord, y=params.y_coord, ax=ax)
8 format_plots(ax)
9 return vkt.ImageResult(self.fig_to_svg(fig))
That's it! You've turned your SectionProperties script into an interactive web application using VIKTOR. With this application, anyone can now visualize the section geometry, analyze stresses, and see Mohr's Circle without needing to write a single line of code. This not only makes sharing results easier but also protects your intellectual property at the same time!
For your convenience, find the complete code below:
1from sectionproperties.pre import Material 2from sectionproperties.pre.geometry import Geometry 3from sectionproperties.analysis.section import Section 4import matplotlib.pyplot as plt 5import viktor as vkt 6from viktor.views import ImageView 7from io import StringIO 8import tempfile 9 10class Parametrization(vkt.Parametrization): 11 12 title1 = vkt.Text("## Material property") 13 E = vkt.NumberField('Elastic modulus', default=30.1e3, flex=35, suffix="MPa") 14 fc = vkt.NumberField('Yield strength', default=32, flex=35, suffix="MPa") 15 16 title2 = vkt.Text("## Define Section") 17 dxf = vkt.FileField('Upload DXF file', file_types=['.dxf'], flex=40) 18 mesh = vkt.NumberField('Mesh size', default=70, flex=30) 19 20 title3 = vkt.Text("## Loads") 21 axial_force = vkt.NumberField('Axial force', default=10e3, flex=30, suffix="N") 22 momentxx = vkt.NumberField('Moment XX', default=1e6, flex=30, suffix="N-mm") 23 momentyy = vkt.NumberField('Moment YY', default=1e4, flex=30, suffix="N-mm") 24 momentzz = vkt.NumberField('Moment ZZ', default=2e5, flex=30, suffix="N-mm") 25 shear_x = vkt.NumberField('Shear force x', default=25e3, flex=30, suffix="N") 26 shear_y = vkt.NumberField('Shear force y', default=50e3, flex=30, suffix="N") 27 stress_option = vkt.OptionField('Stress visualization', options=['mzz_zxy', "vx_zxy",'zxy',"vy_zxy","v_zxy"], default='vy_zxy') 28 29 title4 = vkt.Text("## Morh Analysis") 30 note = vkt.Text("Note: The Mohr's circle is plotted at the point (x,y) on the stress space of the section") 31 x_coord = vkt.NumberField('X', default=45, flex=30) 32 y_coord = vkt.NumberField('Y', default=40, flex=30) 33 34def format_plots(ax): 35 ax.set_xlabel("X", fontsize=8) 36 ax.set_ylabel("Y", fontsize=8) 37 ax.minorticks_on() 38 ax.grid(which='major', linestyle='-', linewidth=0.5, color='silver', zorder =0) 39 ax.grid(which='minor', linestyle='-', linewidth=0.2, color='silver', zorder =0) 40 41def filepath(file): 42 43 """ 44 Saves the uploaded DXF file to a temporary location and returns the file path. 45 """ 46 with tempfile.NamedTemporaryFile(delete=False, suffix=".dxf") as temp_file: 47 temp_file.write(file.file.getvalue_binary()) 48 return temp_file.name 49 50class Controller(vkt.ViktorController): 51 label = "Viktor-SectionProperties" 52 parametrization = Parametrization(width=35) 53 def create_geometry(self, params): 54 55 """ 56 Creates the geometry from the uploaded DXF file and assigns material properties. 57 """ 58 geom = Geometry.from_dxf(dxf_filepath=filepath(params.dxf)) 59 60 # Define and Assign material properties for the geometry 61 geom.material = Material(name="Concrete", 62 elastic_modulus=params.E, # MPa 63 poissons_ratio=0.2, 64 density=2.4e-6, # kg/mm³ 65 yield_strength=params.fc, # MPa 66 color="lightgrey") 67 68 return geom 69 70 def calculate_section_properties(self, geom, mesh_size): 71 72 """ 73 Calculates the geometric, warping, and plastic properties of the section. 74 """ 75 geom.create_mesh(mesh_sizes=mesh_size) 76 section = Section(geom, time_info=True) 77 section.calculate_geometric_properties() 78 section.calculate_warping_properties() 79 section.calculate_plastic_properties() 80 return section 81 82 @ImageView("Geometry") 83 84 def visualize_geo(self, params, **kwargs): 85 geom = self.create_geometry(params) 86 fig, ax = plt.subplots() 87 geom.plot_geometry(ax=ax) 88 format_plots(ax) 89 ax.set_title(f"Cross-Section Geometry\n{params.dxf.filename[:-4]}", fontsize=10) 90 return vkt.ImageResult(self.fig_to_svg(fig)) 91 92 @ImageView("Mesh") 93 def visualize_mesh(self, params, **kwargs): 94 geom = self.create_geometry(params) 95 geom.create_mesh(mesh_sizes=params.mesh) 96 section = Section(geom, time_info=True) 97 fig, ax = plt.subplots() 98 section.plot_mesh(materials=False, ax=ax) 99 format_plots(ax) 100 return vkt.ImageResult(self.fig_to_svg(fig)) 101 102 @ImageView("Centroids") 103 104 def visualize_centroids(self, params, **kwargs): 105 geom = self.create_geometry(params) 106 section = self.calculate_section_properties(geom , params.mesh) 107 fig, ax = plt.subplots() 108 section.plot_centroids(ax=ax) 109 format_plots(ax) 110 return vkt.ImageResult(self.fig_to_svg(fig)) 111 112 @ImageView("Stress") 113 def visualize_stress(self, params, **kwargs): 114 115 geom = self.create_geometry(params) 116 section = self.calculate_section_properties(geom, params.mesh) 117 stress = section.calculate_stress(n=params.Axial, mxx=params.Moment, vx=params.Shear_x, vy=params.Shear_y) 118 fig, ax = plt.subplots() 119 stress.plot_stress(stress=params.option, normalize=False, fmt="{x:.2f}", ax=ax, zorder = 1) 120 format_plots(ax) 121 return vkt.ImageResult(self.fig_to_svg(fig)) 122 123 @ImageView("Stress Vector") 124 125 def visualize_stress_vector(self, params, **kwargs): 126 geom = self.create_geometry(params) 127 section = self.calculate_section_properties(geom, params.mesh) 128 stress = section.calculate_stress(n=params.axial_force, 129 mxx=params.momentxx,myy=params.momentyy, mzz=params.momentzz, 130 vx=params.shear_x, vy=params.shear_y) 131 132 fig, ax = plt.subplots() 133 stress.plot_stress_vector(stress=params.stress_option ,cmap="viridis",normalize=False,fmt="{x:.2f}", ax=ax) 134 format_plots(ax) 135 return vkt.ImageResult(self.fig_to_svg(fig)) 136 137 @ImageView("Mohr's Circles") 138 139 def visualize_Mohrs_Circles(self, params, **kwargs): 140 141 """ 142 Visualizes the Mohr's circles for the specified point in the stress space. 143 """ 144 145 geom = self.create_geometry(params) 146 section = self.calculate_section_properties(geom, params.mesh) 147 stress = section.calculate_stress(n=params.axial_force, 148 mxx=params.momentxx,myy=params.momentyy, mzz=params.momentzz, 149 vx=params.shear_x, vy=params.shear_y) 150 151 fig, ax = plt.subplots() 152 stress.plot_mohrs_circles(x=params.x_coord, y=params.y_coord, ax=ax) 153 format_plots(ax) 154 return vkt.ImageResult(self.fig_to_svg(fig)) 155 156 @staticmethod 157 def fig_to_svg(fig): 158 svg_data = StringIO() 159 fig.savefig(svg_data, format="svg", bbox_inches='tight') 160 return svg_data 161
SectionProperties is a library that allows you to calculate properties of complex sections that would otherwise require tedious and error-prone manual calculation. This facilitates the design process, optimizing the workflow of the structural engineer.
However, SectionProperties has many more capabilities, such as allowing you to work with different types of materials for composite sections, create your own geometries, import other file types, and generate dense meshes in specific areas.
Excited to explore and discover how Python can be an ally in your daily work as a structural engineer? VIKTOR offers many tutorials and templates to help you get started building tools to automate your engineering processes!