OpenSees Cloud
OpenSees AMI
Rotated Local Axes
Original Post - 21 Sep 2025 - Michael H. Scott
Show your support at Buy Me a Coffee.
OpenSeesing through the SeismoStruct Verification Report (v2025), I expected smooth sailing across Chapter 2, Comparison with Independent Hand-Calcs, where “hand-calcs” means SAP2000 analysis results. But light storm clouds set in on Example 2, Rotated Local Axes.
The model is a W12x106 cantilever rotated 30 degrees about its longitudinal axis. A uniform distributed load (roughly the member self-weight) is applied in the global Y-direction.
Both the model definition and the distributed loading can get a little tricky in OpenSees.
Model Definition
Due to the rotated axes, the cantilever will deflect out-of-plane under in-plane loading. So we have to define a 3D model. No need to alter the section properties though, just rotate the vector in the x-z plane, vxz by 30 degrees from [0,0,1] to [0,sin 30,cos 30].
import openseespy.opensees as ops
import numpy as np
from math import isclose
kip = 1.0
inch = 1.0
ksi = kip/inch**2
E = 29000*ksi
nu = 0.3
G = 0.5*E/(1+nu)
# W12x106
A = 31.2*inch**2
Iz = 933*inch**4
Iy = 301*inch**4
J = 9.13*inch**4
theta = 30*np.pi/180
ops.wipe()
ops.model('basic','-ndm',3,'-ndf',6)
ops.node(1,0,0,0); ops.fix(1,1,1,1,1,1,1)
ops.node(2,144*inch,0,0)
ops.geomTransf('Linear',1, 0,np.sin(theta),np.cos(theta))
ops.element('elasticBeamColumn',1,1,2,A,E,G,J,Iy,Iz,1)
Note that the model does not include either shear or warping effects.
Distributed Load
Member loads in OpenSees are defined with respect to the element local axes. Some day you’ll be able to input member loads defined in the global axis directions. Until then, you can use dot products.
First, the global distributed load is defined in a vector, W
, then
this vector is projected on to each of the element local axes to get the
local distributed loads.
W = [0,-0.01*kip/inch,0] # Global X,Y,Z
# Element local axes
x = ops.eleResponse(1,'xaxis')
y = ops.eleResponse(1,'yaxis')
z = ops.eleResponse(1,'zaxis')
# Dot products
wx = np.dot(W,x)
wy = np.dot(W,y)
wz = np.dot(W,z)
ops.timeSeries('Constant',1)
ops.pattern('Plain',1,1)
ops.eleLoad('-ele',1,'-type','beamUniform',wy,wz,wx)
ops.analysis('Static','-noWarnings')
ops.analyze(1)
Dot products are a bit overkill for this model, but can get you out of a jam in less simplistic situations.
Assertions
The reported hand-calcs for the deflection at the free end of the cantilever are 0.03029 inch in Y and 0.01806 inch in Z.
assert isclose(-0.03029*inch,ops.nodeDisp(2,2),abs_tol=0.5e-5*inch)
assert isclose(-0.01806*inch,ops.nodeDisp(2,3),abs_tol=0.5e-5*inch)
The absolute tolerance for the assertions is 0.5e-5 inch because the hand-calcs are reported to five decimal places. For example, the first assertion checks that the Y deflection is between -0.030285 and -0.030295 inch, i.e., any value that rounds to -0.03029 inch.
If I run into any issues with the SeismoStruct verficiation examples, or find more OpenSees modeling issues that make me think “that was kinda weird, others should know about it”, I’ll let you know.