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)}"

Online Calculator

Nodes [name, x, y].
Members [name, node_i, node_j, material_name, section_name].
Materials [name, E, G, nu, rho].
Sections [name, A, Iy].
Supports [node, fix_x, fix_y, fix_mz]. Bools.
Nodal loads [node, direction, magnitude]. Direction=FX, FY, MZ.
Member loads [member, type, magnitude, x1, x2]. Type=point, dist.
Output type.