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.