FRAME_2D
This function analyzes planar rigid frames by assembling a finite element model from user-defined nodes, members, materials, and sections. It applies support constraints plus nodal/member loads, then computes structural response including reactions, nodal displacements, and representative member force quantities.
Frame analysis is performed using linear elastic stiffness methods, solving the global equilibrium equations:
\mathbf{K}\mathbf{u} = \mathbf{F}
where \mathbf{K} is the assembled frame stiffness matrix, \mathbf{u} are nodal degrees of freedom, and \mathbf{F} are applied loads. Post-processing recovers internal member actions from the solved displacement field.
Excel Usage
=FRAME_2D(nodes, members, materials, sections, supports, nodal_loads, member_loads, pynite_frame_output)
nodes(list[list], required): Nodes [name, x, y].members(list[list], required): Members [name, node_i, node_j, material_name, section_name].materials(list[list], required): Materials [name, E, G, nu, rho].sections(list[list], required): Sections [name, A, Iy].supports(list[list], required): Supports [node, fix_x, fix_y, fix_mz]. Bools.nodal_loads(list[list], optional, default: []): Nodal loads [node, direction, magnitude]. Direction=FX, FY, MZ.member_loads(list[list], optional, default: []): Member loads [member, type, magnitude, x1, x2]. Type=point, dist.pynite_frame_output(str, optional, default: “Reactions”): Output type.
Returns (list[list]): Analysis results.
Example 1: Portal frame support reactions under beam distributed load
Inputs:
| nodes | members | materials | sections | supports | member_loads | pynite_frame_output | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | C1 | N1 | N2 | Mat1 | Sec1 | Mat1 | 29000 | 11200 | 0.3 | 0 | Sec1 | 10 | 100 | N1 | true | true | true | B1 | dist | -1 | 0 | 10 | Reactions |
| N2 | 0 | 10 | B1 | N2 | N3 | Mat1 | Sec1 | N4 | true | true | true | ||||||||||||||
| N3 | 10 | 10 | C2 | N3 | N4 | Mat1 | Sec1 | ||||||||||||||||||
| N4 | 10 | 0 |
Excel formula:
=FRAME_2D({"N1",0,0;"N2",0,10;"N3",10,10;"N4",10,0}, {"C1","N1","N2","Mat1","Sec1";"B1","N2","N3","Mat1","Sec1";"C2","N3","N4","Mat1","Sec1"}, {"Mat1",29000,11200,0.3,0}, {"Sec1",10,100}, {"N1",TRUE,TRUE,TRUE;"N4",TRUE,TRUE,TRUE}, {"B1","dist",-1,0,10}, "Reactions")
Expected output:
| Node | Rx | Ry | Mz |
|---|---|---|---|
| N1 | 0.830841 | 5 | -2.76116 |
| N2 | 0 | 0 | 0 |
| N3 | 0 | 0 | 0 |
| N4 | -0.830841 | 5 | 2.76116 |
Example 2: Cantilever column displacement from horizontal nodal load
Inputs:
| nodes | members | materials | sections | supports | nodal_loads | pynite_frame_output | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | C1 | N1 | N2 | Mat1 | Sec1 | Mat1 | 29000 | 11200 | 0.3 | 0 | Sec1 | 10 | 100 | N1 | true | true | true | N2 | FX | 10 | Displacements |
| N2 | 0 | 10 |
Excel formula:
=FRAME_2D({"N1",0,0;"N2",0,10}, {"C1","N1","N2","Mat1","Sec1"}, {"Mat1",29000,11200,0.3,0}, {"Sec1",10,100}, {"N1",TRUE,TRUE,TRUE}, {"N2","FX",10}, "Displacements")
Expected output:
| Node | Dx | Dy | Rz |
|---|---|---|---|
| N1 | 0 | 0 | 0 |
| N2 | 0.114943 | 0 | -0.0172414 |
Example 3: Fixed-fixed beam end forces from uniform member load
Inputs:
| nodes | members | materials | sections | supports | member_loads | pynite_frame_output | |||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | M1 | N1 | N2 | Mat1 | Sec1 | Mat1 | 29000 | 11200 | 0.3 | 0 | Sec1 | 10 | 100 | N1 | true | true | true | M1 | dist | -1 | 0 | 10 | MemberForces |
| N2 | 10 | 0 | N2 | true | true | true |
Excel formula:
=FRAME_2D({"N1",0,0;"N2",10,0}, {"M1","N1","N2","Mat1","Sec1"}, {"Mat1",29000,11200,0.3,0}, {"Sec1",10,100}, {"N1",TRUE,TRUE,TRUE;"N2",TRUE,TRUE,TRUE}, {"M1","dist",-1,0,10}, "MemberForces")
Expected output:
| Member | Axial (Fx) | Shear (Fy) | Moment (Mz) Start | Moment (Mz) End |
|---|---|---|---|---|
| M1 | 0 | 5 | 8.33333 | 8.33333 |
Example 4: Combined frame output for a cantilever column load case
Inputs:
| nodes | members | materials | sections | supports | nodal_loads | pynite_frame_output | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | C1 | N1 | N2 | Mat1 | Sec1 | Mat1 | 29000 | 11200 | 0.3 | 0 | Sec1 | 10 | 100 | N1 | true | true | true | N2 | FX | 10 | Stacked |
| N2 | 0 | 10 |
Excel formula:
=FRAME_2D({"N1",0,0;"N2",0,10}, {"C1","N1","N2","Mat1","Sec1"}, {"Mat1",29000,11200,0.3,0}, {"Sec1",10,100}, {"N1",TRUE,TRUE,TRUE}, {"N2","FX",10}, "Stacked")
Expected output:
| Node | Rx | Ry | Mz | |
|---|---|---|---|---|
| N1 | -10 | 0 | 100 | |
| N2 | 0 | 0 | 0 | |
| Node | Dx | Dy | Rz | |
| N1 | 0 | 0 | 0 | |
| N2 | 0.114943 | 0 | -0.0172414 | |
| Member | Axial (Fx) | Shear (Fy) | Moment (Mz) Start | Moment (Mz) End |
| C1 | 0 | 10 | 100 | 1.42109e-14 |
Python Code
Show Code
from Pynite import FEModel3D
def frame_2d(nodes, members, materials, sections, supports, nodal_loads=[], member_loads=[], pynite_frame_output='Reactions'):
"""
Analyze 2D rigid frames. Supports nodal and member loads.
See: https://pynite.readthedocs.io/en/latest/member.html
This example function is provided as-is without any representation of accuracy.
Args:
nodes (list[list]): Nodes [name, x, y].
members (list[list]): Members [name, node_i, node_j, material_name, section_name].
materials (list[list]): Materials [name, E, G, nu, rho].
sections (list[list]): Sections [name, A, Iy].
supports (list[list]): Supports [node, fix_x, fix_y, fix_mz]. Bools.
nodal_loads (list[list], optional): Nodal loads [node, direction, magnitude]. Direction=FX, FY, MZ. Default is [].
member_loads (list[list], optional): Member loads [member, type, magnitude, x1, x2]. Type=point, dist. Default is [].
pynite_frame_output (str, optional): Output type. Valid options: Reactions, Displacements, Member Forces, Stacked Results. Default is 'Reactions'.
Returns:
list[list]: Analysis results.
"""
try:
def to2d(x): return [[x]] if not isinstance(x, list) else x
model = FEModel3D()
# Materials
for row in to2d(materials):
if not row: continue
model.add_material(str(row[0]), float(row[1]), float(row[2]), float(row[3]), float(row[4]))
# Sections
for row in to2d(sections):
if not row: continue
# 2D frame: Area and Iy are critical. Iz, J dummy.
model.add_section(str(row[0]), float(row[1]), float(row[2]), 1.0, 1.0)
# Nodes
for row in to2d(nodes):
if not row: continue
model.add_node(str(row[0]), float(row[1]), float(row[2]), 0.0)
# Members
for row in to2d(members):
if not row: continue
model.add_member(str(row[0]), str(row[1]), str(row[2]), str(row[3]), str(row[4]))
# 2D Stability: Fix all nodes out-of-plane
for n_name in model.nodes:
model.def_support(n_name, False, False, True, True, True, False)
# Supports
for row in to2d(supports):
if not row: continue
n = str(row[0])
fx, fy, fmz = bool(row[1]), bool(row[2]), bool(row[3])
# Fix out-of-plane for 2D stability
model.def_support(n, fx, fy, True, True, True, fmz)
# Nodal Loads
for row in to2d(nodal_loads):
if not row: continue
model.add_node_load(str(row[0]), str(row[1]), float(row[2]))
# Member Loads
for row in to2d(member_loads):
if not row: continue
m_name = str(row[0])
l_type = str(row[1]).lower()
mag = float(row[2])
x1 = float(row[3]) if len(row) > 3 else 0
if 'point' in l_type:
model.add_member_pt_load(m_name, 'Fy', mag, x1) # Assume vertical load in local y
elif 'dist' in l_type:
x2 = float(row[4]) if len(row) > 4 else x1
w2 = mag # Uniform by default
model.add_member_dist_load(m_name, 'Fy', mag, w2, x1, x2)
# Safe result helper
def get_val(obj, attr, *args):
try:
val = getattr(obj, attr)
if callable(val): val = val(*args)
if isinstance(val, dict): return float(val.get('Combo 1', 0.0))
return float(val)
except: return 0.0
# Add load combination and analyze
model.add_load_combo('Combo 1', {'Case 1': 1.0})
model.analyze(check_statics=False)
res = []
# Column count for Stacked output: 5
if pynite_frame_output == 'Reactions' or pynite_frame_output == 'Stacked':
header = ['Node', 'Rx', 'Ry', 'Mz']
if pynite_frame_output == 'Stacked': header += [''] * 1
res.append(header)
for n_nm in model.nodes:
n = model.nodes[n_nm]
row = [n_nm, get_val(n, 'RxnFX'), get_val(n, 'RxnFY'), get_val(n, 'RxnMZ')]
if pynite_frame_output == 'Stacked': row += [''] * 1
res.append(row)
if pynite_frame_output == 'Displacements' or pynite_frame_output == 'Stacked':
header = ['Node', 'Dx', 'Dy', 'Rz']
if pynite_frame_output == 'Stacked': header += [''] * 1
res.append(header)
for n_nm in model.nodes:
n = model.nodes[n_nm]
row = [n_nm, get_val(n, 'DX'), get_val(n, 'DY'), get_val(n, 'RZ')]
if pynite_frame_output == 'Stacked': row += [''] * 1
res.append(row)
if pynite_frame_output == 'MemberForces' or pynite_frame_output == 'Stacked':
res.append(['Member', 'Axial (Fx)', 'Shear (Fy)', 'Moment (Mz) Start', 'Moment (Mz) End'])
for m_nm in model.members:
m = model.members[m_nm]
res.append([m_nm, get_val(m, 'axial', 0), get_val(m, 'shear', 'Fy', 0),
get_val(m, 'moment', 'Mz', 0), get_val(m, 'moment', 'Mz', m.L())])
return res
except Exception as e:
return f"Error: {str(e)}"