OpenSees Cloud
OpenSees AMI
On Another Plane
12 Jul 2026 - Michael H. Scott
Rigid diaphragm constraints are among the most misunderstood and misapplied modeling concepts in OpenSees, right up there with numerical integration and the vector in the x-z plane.
Even our original implementation of rigid diaphragm constraints was based on a misunderstanding of where the primary node could be located in a model.
Looking back through OpenSees GitHub history, which begins in 2011, and
another decade back to 2001 via the
source code on my Zip disk,
the implementation of rigid diaphragm constraints in
SRC/domain/constraints/RigidDiaphragm.cpp required the primary
node to be in the same plane as each secondary node. However, this
restriction is mathematically unnecessary as the secondary nodes can
rotate about an axis that passes through the primary node, in planes
normal to the user-specified perpendicular direction.
If the nodes were not in the same plane, a warning was output and the analysis continued. But the worst part was that the rigid diaphragm constraint would not be set up if the nodes were not coplanar…only a warning that could reasonably be ignored.
The unnecessarily restrictive, silently deadly implementation was resolved via a simple GitHub commit in October 2025. Now the primary and secondary nodes of a rigid diaphragm constraint do not have to be coplanar. If the nodes are not coplanar, a warning is still output, but the constraint is created and the analysis proceeds as intended.
Consider the 3D frame model from this verification post where node 10 is the primary node in a rigid diaphragm constraint for secondary nodes 11, 12, 13, and 14.

Moving node 10 out of the diaphragm plane does not affect the eigenvalues computed for this model.
import openseespy.opensees as ops
from numpy import isclose
w2 = dict()
for coplanar in [True,False]:
ops.wipe()
#
# Define model
#
if coplanar:
Z = H
else:
Z = 2*H # Define node out of diaphragm plane
ops.node(10,L/2,L/2,Z)
ops.fix(10,0,0,1,1,1,0)
ops.rigidDiaphragm(3,10,11,12,13,14)
w2[coplanar] = ops.eigen('fullGenLapack',3) # Get all three modes
assert isclose(w2[True],w2[False]).all()
Note that H and L are variables that define the column height and beam span length, respectively, for the frame model.
Try this analysis yourself and you’ll see the rigid diaphragm constraint equations do not require the primary node to be coplanar with the secondary nodes.