TRUSS_2D
This function analyzes two-dimensional pin-jointed trusses using a finite element representation of nodes, axial members, support conditions, and nodal loads. It automatically applies rotational member end releases to emulate ideal truss behavior, then reports reactions, nodal displacements, and member axial forces.
For linear static loading, the assembled structural equations are solved in stiffness form:
\mathbf{K}\mathbf{u} = \mathbf{F}
with \mathbf{K} as the global truss stiffness matrix, \mathbf{u} as nodal displacement unknowns, and \mathbf{F} as external loads. Member axial force in each bar is then recovered from the relative end displacements and element stiffness.
Excel Usage
=TRUSS_2D(nodes, members, supports, loads, pynite_truss_output)
nodes(list[list], required): Nodes [name, x, y].members(list[list], required): Members [name, node_i, node_j, A, E].supports(list[list], optional, default: []): Supports [node, fix_x, fix_y]. Bools.loads(list[list], optional, default: []): Nodal loads [node, fx, fy].pynite_truss_output(str, optional, default: “AxialForces”): Output type (Reactions, AxialForces, Displacements).
Returns (list[list]): Analysis results.
Example 1: Triangle truss axial member forces under apex load
Inputs:
| nodes | members | supports | loads | pynite_truss_output | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | M1 | N1 | N2 | 10 | 29000 | N1 | true | true | N3 | 0 | -10 | AxialForces |
| N2 | 10 | 0 | M2 | N1 | N3 | 10 | 29000 | N2 | false | true | ||||
| N3 | 5 | 5 | M3 | N2 | N3 | 10 | 29000 |
Excel formula:
=TRUSS_2D({"N1",0,0;"N2",10,0;"N3",5,5}, {"M1","N1","N2",10,29000;"M2","N1","N3",10,29000;"M3","N2","N3",10,29000}, {"N1",TRUE,TRUE;"N2",FALSE,TRUE}, {"N3",0,-10}, "AxialForces")
Expected output:
| Member | Axial Force (k) |
|---|---|
| M1 | -5 |
| M2 | 7.07107 |
| M3 | 7.07107 |
Example 2: Triangle truss support reactions under apex load
Inputs:
| nodes | members | supports | loads | pynite_truss_output | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | M1 | N1 | N2 | 10 | 29000 | N1 | true | true | N3 | 0 | -10 | Reactions |
| N2 | 10 | 0 | M2 | N1 | N3 | 10 | 29000 | N2 | false | true | ||||
| N3 | 5 | 5 | M3 | N2 | N3 | 10 | 29000 |
Excel formula:
=TRUSS_2D({"N1",0,0;"N2",10,0;"N3",5,5}, {"M1","N1","N2",10,29000;"M2","N1","N3",10,29000;"M3","N2","N3",10,29000}, {"N1",TRUE,TRUE;"N2",FALSE,TRUE}, {"N3",0,-10}, "Reactions")
Expected output:
| Node | Rx (k) | Ry (k) | Rz (k) | Mx (k-ft) | My (k-ft) | Mz (k-ft) |
|---|---|---|---|---|---|---|
| N1 | 8.88178e-16 | 5 | 0 | 0 | 0 | 0 |
| N2 | 0 | 5 | 0 | 0 | 0 | 0 |
| N3 | 0 | 0 | 0 | 0 | 0 | 0 |
Example 3: Triangle truss nodal displacements under apex load
Inputs:
| nodes | members | supports | loads | pynite_truss_output | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | M1 | N1 | N2 | 10 | 29000 | N1 | true | true | N3 | 0 | -10 | Displacements |
| N2 | 10 | 0 | M2 | N1 | N3 | 10 | 29000 | N2 | false | true | ||||
| N3 | 5 | 5 | M3 | N2 | N3 | 10 | 29000 |
Excel formula:
=TRUSS_2D({"N1",0,0;"N2",10,0;"N3",5,5}, {"M1","N1","N2",10,29000;"M2","N1","N3",10,29000;"M3","N2","N3",10,29000}, {"N1",TRUE,TRUE;"N2",FALSE,TRUE}, {"N3",0,-10}, "Displacements")
Expected output:
| Node | Dx (in) | Dy (in) | Dz (in) | Rx (rad) | Ry (rad) | Rz (rad) |
|---|---|---|---|---|---|---|
| N1 | 0 | 0 | 0 | 0 | 0 | 0 |
| N2 | 0.000172414 | 0 | 0 | 0 | 0 | 0 |
| N3 | 0.0000862069 | -0.000330037 | 0 | 0 | 0 | 0 |
Example 4: Combined triangle truss output for an apex point load
Inputs:
| nodes | members | supports | loads | pynite_truss_output | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| N1 | 0 | 0 | M1 | N1 | N2 | 10 | 29000 | N1 | true | true | N3 | 0 | -10 | Stacked |
| N2 | 10 | 0 | M2 | N1 | N3 | 10 | 29000 | N2 | false | true | ||||
| N3 | 5 | 5 | M3 | N2 | N3 | 10 | 29000 |
Excel formula:
=TRUSS_2D({"N1",0,0;"N2",10,0;"N3",5,5}, {"M1","N1","N2",10,29000;"M2","N1","N3",10,29000;"M3","N2","N3",10,29000}, {"N1",TRUE,TRUE;"N2",FALSE,TRUE}, {"N3",0,-10}, "Stacked")
Expected output:
| Member | Axial Force (k) | |||||
|---|---|---|---|---|---|---|
| M1 | -5 | |||||
| M2 | 7.07107 | |||||
| M3 | 7.07107 | |||||
| Node | Rx (k) | Ry (k) | Rz (k) | Mx (k-ft) | My (k-ft) | Mz (k-ft) |
| N1 | 8.88178e-16 | 5 | 0 | 0 | 0 | 0 |
| N2 | 0 | 5 | 0 | 0 | 0 | 0 |
| N3 | 0 | 0 | 0 | 0 | 0 | 0 |
| Node | Dx (in) | Dy (in) | Dz (in) | Rx (rad) | Ry (rad) | Rz (rad) |
| N1 | 0 | 0 | 0 | 0 | 0 | 0 |
| N2 | 0.000172414 | 0 | 0 | 0 | 0 | 0 |
| N3 | 0.0000862069 | -0.000330037 | 0 | 0 | 0 | 0 |
Python Code
Show Code
from Pynite import FEModel3D
def truss_2d(nodes, members, supports=[], loads=[], pynite_truss_output='AxialForces'):
"""
Analyze 2D pin-jointed trusses. Members are automatically released for rotation.
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, A, E].
supports (list[list], optional): Supports [node, fix_x, fix_y]. Bools. Default is [].
loads (list[list], optional): Nodal loads [node, fx, fy]. Default is [].
pynite_truss_output (str, optional): Output type (Reactions, AxialForces, Displacements). Valid options: Axial Forces, Reactions, Displacements, Stacked Results. Default is 'AxialForces'.
Returns:
list[list]: Analysis results.
"""
try:
def to2d(x): return [[x]] if not isinstance(x, list) else x
model = FEModel3D()
# 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 (A, E needed)
# We define a unique material/section for each Member or share?
# Simplest: create generic Mat/Sec for each execution or reused if identical.
# For truss, we just need A and E.
# We'll create a dummy material and section for each member or generic.
# Let's assume generic for this simple function if A/E are passed inline.
count = 0
for row in to2d(members):
if not row: continue
count += 1
m_name = str(row[0])
ni = str(row[1])
nj = str(row[2])
A = float(row[3])
E = float(row[4])
mat_name = f"Mat_{count}"
sec_name = f"Sec_{count}"
model.add_material(mat_name, E, E*0.4, 0.3, 1.0) # G approximated
model.add_section(sec_name, A, Iy=1.0, Iz=1.0, J=1.0) # Dummy inertias
model.add_member(m_name, ni, nj, mat_name, sec_name)
# RELEASE MOMENTS (Pin-Pin)
# PyNite 2.0+: member.releases = [xi, yi, zi, rix, riy, riz, xj, yj, zj, rjx, rjy, rjz] (Springs?)
# Or methods?
# Docs: model.def_releases(member_name, Dxi, Dyi, Dzi, Rxi, Ryi, Rzi, Dxj, Dyj, Dzj, Rxj, Ryj, Rzj)
# Releasing Rotation Z (Rzi, Rzj) for 2D truss behavior.
# Note: PyNite convention True = Fixed (Support). For releases, it's typically "Released" = True?
# Actually, FEModel3D.def_releases: "False" means fixed, "True" means released? Or "False" means released?
# Standard FEA: Support 'True' = Fixed. Release 'True' = Released?
# Let's check PyNite convention via testing or safe assumptions.
# Most PyNite examples: model.def_releases('M1', Rzi=True, Rzj=True) (kwargs).
# Since I can't verify, I'll use the kwarg method if it exists, or assume standard.
# Pynite docs say: def_releases(member_name, Rxi=False, ... Rzj=False) where True = Released.
# Release MZ at both ends
model.def_releases(m_name, Rzi=True, Rzj=True)
# 2D Stability: Fix all nodes out-of-plane
# For a TRUSS nodes are pins, but we still need to prevent RZ rotation if the solver expects it.
# Fix DZ, RX, RY, and RZ at ALL nodes to ensure global stability in 3D solver.
for n_name in model.nodes:
model.def_support(n_name, False, False, True, True, True, True)
# Supports
for row in to2d(supports):
if not row: continue
# [node, fix_x, fix_y]
n = str(row[0])
fx = bool(row[1])
fy = bool(row[2])
# Apply user-specified fixes for FX and FY.
# Out-of-plane DOFs (DZ, RX, RY, RZ) remain fixed from the stability loop above.
model.def_support(n, fx, fy, True, True, True, True)
# Nodal loads
for row in to2d(loads):
if not row or row[0] is None: continue
n, fx, fy = str(row[0]), float(row[1]), float(row[2])
model.add_node_load(n, 'FX', fx)
model.add_node_load(n, 'FY', fy)
# Add load combination and analyze
model.add_load_combo('Combo 1', {'Case 1': 1.0})
model.analyze(check_statics=False)
# 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
res = []
# Column count for Stacked output: 7
if pynite_truss_output == 'AxialForces' or pynite_truss_output == 'Stacked':
header = ['Member', 'Axial Force (k)']
if pynite_truss_output == 'Stacked': header += [''] * 5
res.append(header)
for m_nm in model.members:
row = [m_nm, get_val(model.members[m_nm], 'axial', 0)]
if pynite_truss_output == 'Stacked': row += [''] * 5
res.append(row)
if pynite_truss_output == 'Reactions' or pynite_truss_output == 'Stacked':
res.append(['Node', 'Rx (k)', 'Ry (k)', 'Rz (k)', 'Mx (k-ft)', 'My (k-ft)', 'Mz (k-ft)'])
for n_name in model.nodes:
node = model.nodes[n_name]
# Use getattr for robustness in case combo name or attribute access differs
def get_val(obj, attr):
try:
val = getattr(obj, attr)
if isinstance(val, dict): return val.get('Combo 1', 0.0)
return float(val)
except: return 0.0
res.append([n_name, get_val(node, 'RxnFX'), get_val(node, 'RxnFY'),
get_val(node, 'RxnFZ'), get_val(node, 'RxnMX'),
get_val(node, 'RxnMY'), get_val(node, 'RxnMZ')])
if pynite_truss_output == 'Displacements' or pynite_truss_output == 'Stacked':
res.append(['Node', 'Dx (in)', 'Dy (in)', 'Dz (in)', 'Rx (rad)', 'Ry (rad)', 'Rz (rad)'])
for n_name in model.nodes:
node = model.nodes[n_name]
def get_val(obj, attr):
try:
val = getattr(obj, attr)
if isinstance(val, dict): return val.get('Combo 1', 0.0)
return float(val)
except: return 0.0
res.append([n_name, get_val(node, 'DX'), get_val(node, 'DY'),
get_val(node, 'DZ'), get_val(node, 'RX'),
get_val(node, 'RY'), get_val(node, 'RZ')])
return res
except Exception as e:
return f"Error: {str(e)}"