Structural

Overview

Structural analysis evaluates how beams, frames, plates, and trusses respond to loads, constraints, and material behavior, making it a core discipline in civil, mechanical, and infrastructure engineering. In data-driven workflows, these methods convert geometric and material inputs into actionable response quantities such as displacements, reactions, and internal forces. The category sits within the broader context of the finite element method, where complex continua are discretized into elements and solved numerically. This matters in engineering analytics because it enables repeatable scenario studies, design checks, and early-stage optimization directly from tabular inputs.

The unifying ideas across these tools are stiffness assembly, boundary conditions, and result recovery. In linear static form, each model solves the global equilibrium system

\mathbf{K}\mathbf{u} = \mathbf{F}

where \mathbf{K} is the assembled structural stiffness matrix, \mathbf{u} is the nodal unknown vector (translations/rotations), and \mathbf{F} is the external load vector. Once nodal states are obtained, post-processing maps those states back to engineering outputs such as shear and moment diagrams, member axial force, plate force components, and support reactions.

Implementation is based on PyNite, a Python finite-element library for practical structural modeling and linear analysis. PyNite provides the model objects, element formulations, load handling, and solvers that these spreadsheet-facing tools expose in a consistent, Excel-friendly interface.

BEAM_2D and FRAME_2D address line-member systems where bending behavior is central. BEAM_2D focuses on continuous planar beams defined by spans, support types, and distributed or point loading, returning reactions, nodal displacements, shear, moment, and deflection summaries. FRAME_2D generalizes this to rigid-jointed planar frames with user-defined nodes, members, materials, and sections, so axial-bending interaction and frame action can be captured in one model. Together they support common use cases such as preliminary building-frame checks, portal frame sizing, and beam line load verification.

TRUSS_2D targets pin-jointed systems where members primarily carry axial force, making it suitable for triangulated roof trusses, bracing layouts, and lightweight lattice concepts. By enforcing truss-like end releases and solving for nodal displacements and reactions, it yields direct tension/compression force results for each member. This specialization makes interpretation straightforward for capacity checks and member selection workflows. In practice, it complements frame analysis by covering the axial-dominant end of structural behavior.

PLATE extends the category to surface elements, enabling plate/shell-style modeling with generated meshes or manually defined nodes and elements. It is used when two-dimensional structural surfaces (slabs, panels, deck regions, and thin wall segments) require distributed pressure loading and spatial response assessment. Typical outputs include nodal displacement fields, support reactions, and plate force quantities derived from the solved deformation state. This bridges the gap between 1D member models and continuum-like behavior needed in slab and panel design studies.

Collectively, these four tools form a coherent structural workflow: choose the element family that matches the physical system, apply supports and loads, solve a linear FE model, and extract decision-ready response metrics. That workflow is especially effective for spreadsheet-centric engineering teams that need transparent assumptions, rapid iterations, and reproducible calculations.

BEAM_2D

This function performs two-dimensional beam analysis by constructing a finite element model from span geometry, support conditions, material/section properties, and applied loads. It supports common beam loading types such as concentrated forces and distributed loads, then solves for nodal reactions, displacements, internal shear/moment response, and deflection summaries.

The underlying beam equilibrium is governed by force and moment balance, with stiffness-based solution of nodal unknowns. For a linear static case, the assembled system is solved as:

\mathbf{K}\mathbf{u} = \mathbf{F}

where \mathbf{K} is the global stiffness matrix, \mathbf{u} is the nodal displacement vector, and \mathbf{F} is the applied load vector. Member result quantities are then recovered from the solved displacement state.

Excel Usage

=BEAM_2D(spans, supports, loads, material_section, pynite_beam_output)
  • spans (list[list], required): List of span lengths [length].
  • supports (list[list], required): Support locations and types [x_location, type]. Type=‘pinned’, ‘fixed’, ‘roller’, ‘free’.
  • loads (list[list], required): Loads [type, magnitude, x_start, x_end]. Type=‘point’, ‘distributed’, ‘moment’.
  • material_section (list[list], required): Material and Section properties [E, G, nu, rho, A, Iy].
  • pynite_beam_output (str, optional, default: “Reactions”): Output type.

Returns (list[list]): Analysis results based on pynite_output.

Example 1: Reactions Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 pinned dist -1 0 10 29000 11200 0.3 0 10 100 Reactions
10 roller

Excel formula:

=BEAM_2D({10}, {0,"pinned";10,"roller"}, {"dist",-1,0,10}, {29000,11200,0.3,0,10,100}, "Reactions")

Expected output:

Node Rx (k) Ry (k) Mz (k-ft)
N0 0 5 0
N1 0 5 0
Example 2: Displacements Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 fixed point -1 10 29000 11200 0.3 0 10 100 Displacements

Excel formula:

=BEAM_2D({10}, {0,"fixed"}, {"point",-1,10}, {29000,11200,0.3,0,10,100}, "Displacements")

Expected output:

Node Dx (in) Dy (in) Rz (rad)
N0 0 0 0
N1 0 -0.0114943 -0.00172414
Example 3: Shear Diagram Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 pinned dist -1 0 10 29000 11200 0.3 0 10 100 Shear
10 roller

Excel formula:

=BEAM_2D({10}, {0,"pinned";10,"roller"}, {"dist",-1,0,10}, {29000,11200,0.3,0,10,100}, "Shear")

Expected output:

Member Loc (ft) Shear (k)
M1 0 5
M1 5 0
M1 10 -5
Example 4: Moment Diagram Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 pinned dist -1 0 10 29000 11200 0.3 0 10 100 Moment
10 roller

Excel formula:

=BEAM_2D({10}, {0,"pinned";10,"roller"}, {"dist",-1,0,10}, {29000,11200,0.3,0,10,100}, "Moment")

Expected output:

Member Loc (ft) Moment (k-ft)
M1 0 0
M1 5 -12.5
M1 10 0
Example 5: Deflection Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 pinned point -1 5 29000 11200 0.3 0 10 100 Deflection
10 roller

Excel formula:

=BEAM_2D({10}, {0,"pinned";10,"roller"}, {"point",-1,5}, {29000,11200,0.3,0,10,100}, "Deflection")

Expected output:

Node Dy (in)
N0 0
N1 0
Example 6: Stacked Results Output

Inputs:

spans supports loads material_section pynite_beam_output
10 0 pinned dist -1 0 10 29000 11200 0.3 0 10 100 Stacked
10 roller

Excel formula:

=BEAM_2D({10}, {0,"pinned";10,"roller"}, {"dist",-1,0,10}, {29000,11200,0.3,0,10,100}, "Stacked")

Expected output:

Node Rx (k) Ry (k) Mz (k-ft)
N0 0 5 0
N1 0 5 0
Node Dx (in) Dy (in) Rz (rad)
N0 0 0 -0.00143678
N1 0 0 0.00143678
Member Loc (ft) Shear (k)
M1 0 5
M1 5 0
M1 10 -5
Member Loc (ft) Moment (k-ft)
M1 0 0
M1 5 -12.5
M1 10 0
Node Dy (in)
N0 0
N1 0

Python Code

Show Code
import sys
import types

# PATCH: PyNiteFEA 2.0.2 pkg_resources fix
if "pip._vendor.pkg_resources" not in sys.modules:
    mock_pkg = types.ModuleType("pip._vendor.pkg_resources")
    mock_pkg.get_distribution = lambda _: types.SimpleNamespace(version="2.0.2")
    mock_pkg.working_set = []
    sys.modules["pip._vendor.pkg_resources"] = mock_pkg
    pip = sys.modules.setdefault("pip", types.ModuleType("pip"))
    vendor = sys.modules.setdefault("pip._vendor", types.ModuleType("pip._vendor"))
    pip._vendor = vendor
    vendor.pkg_resources = mock_pkg

from Pynite import FEModel3D

def beam_2d(spans, supports, loads, material_section, pynite_beam_output='Reactions'):
    """
    Analyze continuous 2D beams with auto-meshing. Supports point and distributed loads.

    See: https://pynite.readthedocs.io/en/latest/index.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        spans (list[list]): List of span lengths [length].
        supports (list[list]): Support locations and types [x_location, type]. Type='pinned', 'fixed', 'roller', 'free'.
        loads (list[list]): Loads [type, magnitude, x_start, x_end]. Type='point', 'distributed', 'moment'.
        material_section (list[list]): Material and Section properties [E, G, nu, rho, A, Iy].
        pynite_beam_output (str, optional): Output type. Valid options: Reactions, Displacements, Shear Diagram, Moment Diagram, Deflection Plot, Stacked Results. Default is 'Reactions'.

    Returns:
        list[list]: Analysis results based on pynite_output.
    """
    try:
        # Helpers
        def to2d(x):
            return [[x]] if not isinstance(x, list) else x

        # Prepare inputs
        spans = [float(row[0]) for row in to2d(spans) if row[0] is not None]

        # Create Model
        model = FEModel3D()

        # Material/Section (Simplified: Uniform for now, or take first row)
        props = to2d(material_section)[0]
        E, G, nu, rho = float(props[0]), float(props[1]), float(props[2]), float(props[3])
        A, Iy = float(props[4]), float(props[5])

        model.add_material('Mat1', E, G, nu, rho)
        model.add_section('Sec1', A, Iy, 1.0, 1.0) # Dummy J, Iz for 2D beam

        # Generate Nodes and Members from Spans
        x_accum = 0.0
        node_names = ['N0']
        model.add_node('N0', 0, 0, 0)

        for i, length in enumerate(spans):
            x_end = x_accum + length
            n_name = f'N{i+1}'
            model.add_node(n_name, x_end, 0, 0)
            model.add_member(f'M{i+1}', node_names[-1], n_name, 'Mat1', 'Sec1')
            node_names.append(n_name)
            x_accum = x_end

        # 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 or row[0] is None: continue
            x_loc = float(row[0])
            sup_type = str(row[1]).lower()
            for n_name in model.nodes:
                n = model.nodes[n_name]
                if abs(n.X - x_loc) < 1e-4:
                    if 'fixed' in sup_type:
                        model.def_support(n_name, True, True, True, True, True, True)
                    elif 'pinned' in sup_type:
                        model.def_support(n_name, True, True, True, True, True, False)
                    elif 'roller' in sup_type:
                        model.def_support(n_name, False, True, True, True, True, False)
                    break

        # Loads
        for row in to2d(loads):
            if not row or row[0] is None: continue
            l_type, mag, x1 = str(row[0]).lower(), float(row[1]), float(row[2])
            if 'point' in l_type:
                for m_name in model.members:
                    m = model.members[m_name]
                    if m.i_node.X <= x1 <= m.j_node.X:
                        model.add_member_pt_load(m_name, 'Fy', mag, x1 - m.i_node.X)
                        break
            elif 'dist' in l_type:
                x2 = float(row[3])
                for m_name in model.members:
                    m = model.members[m_name]
                    s, e = max(x1, m.i_node.X), min(x2, m.j_node.X)
                    if s < e:
                        model.add_member_dist_load(m_name, 'Fy', mag, mag, s - m.i_node.X, e - m.i_node.X)

        # 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: 4

        if pynite_beam_output == 'Reactions' or pynite_beam_output == 'Stacked':
            res.append(['Node', 'Rx (k)', 'Ry (k)', 'Mz (k-ft)'])
            for n_nm in model.nodes:
                n = model.nodes[n_nm]
                res.append([n_nm, get_val(n, 'RxnFX'), get_val(n, 'RxnFY'), get_val(n, 'RxnMZ')])

        if pynite_beam_output == 'Displacements' or pynite_beam_output == 'Stacked':
            res.append(['Node', 'Dx (in)', 'Dy (in)', 'Rz (rad)'])
            for n_nm in model.nodes:
                n = model.nodes[n_nm]
                res.append([n_nm, get_val(n, 'DX'), get_val(n, 'DY'), get_val(n, 'RZ')])

        if pynite_beam_output == 'Shear' or pynite_beam_output == 'Stacked':
            res.append(['Member', 'Loc (ft)', 'Shear (k)', ''])
            for m_nm in model.members:
                 m = model.members[m_nm]
                 for x in [0.0, m.L()/2, m.L()]:
                      res.append([m_nm, x, get_val(m, 'shear', 'Fy', x), ''])

        if pynite_beam_output == 'Moment' or pynite_beam_output == 'Stacked':
            res.append(['Member', 'Loc (ft)', 'Moment (k-ft)', ''])
            for m_nm in model.members:
                 m = model.members[m_nm]
                 for x in [0.0, m.L()/2, m.L()]:
                      res.append([m_nm, x, get_val(m, 'moment', 'Mz', x), ''])

        if pynite_beam_output == 'Deflection' or pynite_beam_output == 'Stacked':
            res.append(['Node', 'Dy (in)', '', ''])
            for n_nm in model.nodes:
                 res.append([n_nm, get_val(model.nodes[n_nm], 'DY'), '', ''])

        return res
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

List of span lengths [length].
Support locations and types [x_location, type]. Type='pinned', 'fixed', 'roller', 'free'.
Loads [type, magnitude, x_start, x_end]. Type='point', 'distributed', 'moment'.
Material and Section properties [E, G, nu, rho, A, Iy].
Output type.

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: Reactions Output

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: Displacements Output

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: Member Forces Output

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: Stacked Results Output

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
import sys
import types

# PATCH: PyNiteFEA 2.0.2 pkg_resources fix
if "pip._vendor.pkg_resources" not in sys.modules:
    mock_pkg = types.ModuleType("pip._vendor.pkg_resources")
    mock_pkg.get_distribution = lambda _: types.SimpleNamespace(version="2.0.2")
    mock_pkg.working_set = []
    sys.modules["pip._vendor.pkg_resources"] = mock_pkg
    pip = sys.modules.setdefault("pip", types.ModuleType("pip"))
    vendor = sys.modules.setdefault("pip._vendor", types.ModuleType("pip._vendor"))
    pip._vendor = vendor
    vendor.pkg_resources = mock_pkg

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/index.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.

PLATE

This function performs plate and shell analysis by building a finite element surface model from material properties, optional generated meshes, optional manually defined plate elements, supports, and pressure loads. It returns nodal displacement/reaction summaries and plate force result tables, depending on the selected output mode.

The solver applies linear elastic finite element equilibrium over plate degrees of freedom:

\mathbf{K}\mathbf{u} = \mathbf{F}

where \mathbf{K} is the assembled plate stiffness matrix, \mathbf{u} contains nodal displacement/rotation unknowns, and \mathbf{F} represents external nodal and equivalent surface load effects. Derived plate quantities (such as moment components) are then evaluated from the solved field.

Excel Usage

=PLATE(materials, meshes, plates, nodes, supports, pressures, pynite_plate_output)
  • materials (list[list], required): Materials [name, E, nu, rho].
  • meshes (list[list], optional, default: []): Rectangular meshes [name, width, height, div_x, div_y, material, thickness, origin_x, origin_y, origin_z].
  • plates (list[list], optional, default: []): Manual plates [name, n1, n2, n3, n4, material, thickness].
  • nodes (list[list], optional, default: []): Manual nodes [name, x, y, z].
  • supports (list[list], optional, default: []): Supports [node, fix_dx, fix_dy, fix_dz, fix_rx, fix_ry, fix_rz]. Bools.
  • pressures (list[list], optional, default: []): Uniform pressures [plate_or_mesh, pressure].
  • pynite_plate_output (str, optional, default: “Displacements”): Output type.

Returns (list[list]): Analysis results.

Example 1: Displacements Output

Inputs:

materials meshes supports pressures pynite_plate_output
Concrete 3000000 0.2 0.15 Mesh1 10 10 2 2 Concrete 0.5 0 0 0 Mesh1_N_0_0 true true true true true true Mesh1 -10 Displacements

Excel formula:

=PLATE({"Concrete",3000000,0.2,0.15}, {"Mesh1",10,10,2,2,"Concrete",0.5,0,0,0}, {"Mesh1_N_0_0",TRUE,TRUE,TRUE,TRUE,TRUE,TRUE}, {"Mesh1",-10}, "Displacements")

Expected output:

Node Dx Dy Dz
Mesh1_N_0_0 0 0 0
Mesh1_N_0_1 0 0 -0.552582
Mesh1_N_0_2 0 0 -1.45879
Mesh1_N_1_0 0 0 -0.552582
Mesh1_N_1_1 0 0 -1.47975
Mesh1_N_1_2 0 0 -2.43399
Mesh1_N_2_0 0 0 -1.45879
Mesh1_N_2_1 0 0 -2.43399
Mesh1_N_2_2 0 0 -3.39759
Example 2: Reactions Output

Inputs:

materials meshes supports pressures pynite_plate_output
Concrete 3000 0.2 0.15 M1 5 5 1 1 Concrete 0.5 0 0 0 M1_N_0_0 true true true true true true M1 -1 Reactions

Excel formula:

=PLATE({"Concrete",3000,0.2,0.15}, {"M1",5,5,1,1,"Concrete",0.5,0,0,0}, {"M1_N_0_0",TRUE,TRUE,TRUE,TRUE,TRUE,TRUE}, {"M1",-1}, "Reactions")

Expected output:

Node Rx Ry Rz
M1_N_0_0 0 0 25
M1_N_0_1 0 0 0
M1_N_1_0 0 0 0
M1_N_1_1 0 0 0
Example 3: Plate Forces Output

Inputs:

materials nodes plates supports pressures pynite_plate_output
Steel 29000 0.3 0.283 N1 0 0 0 P1 N1 N2 N3 N4 Steel 0.2 N1 true true true true true true P1 -5 PlateForces
N2 10 0 0 N2 true true true true true true
N3 10 10 0
N4 0 10 0

Excel formula:

=PLATE({"Steel",29000,0.3,0.283}, {"N1",0,0,0;"N2",10,0,0;"N3",10,10,0;"N4",0,10,0}, {"P1","N1","N2","N3","N4","Steel",0.2}, {"N1",TRUE,TRUE,TRUE,TRUE,TRUE,TRUE;"N2",TRUE,TRUE,TRUE,TRUE,TRUE,TRUE}, {"P1",-5}, "PlateForces")

Expected output:

Plate Mx My Mxy Qx Qy
P1 0 0 0 0 0
Example 4: Stacked Results Output

Inputs:

materials meshes supports pressures pynite_plate_output
Concrete 3000 0.2 0.15 M1 5 5 1 1 Concrete 0.5 0 0 0 M1_N_0_0 true true true true true true M1 -1 Stacked

Excel formula:

=PLATE({"Concrete",3000,0.2,0.15}, {"M1",5,5,1,1,"Concrete",0.5,0,0,0}, {"M1_N_0_0",TRUE,TRUE,TRUE,TRUE,TRUE,TRUE}, {"M1",-1}, "Stacked")

Expected output:

Node Dx Dy Dz
M1_N_0_0 0 0 0
M1_N_0_1 0 0 -6.16228
M1_N_1_0 0 0 -6.16228
M1_N_1_1 0 0 -15.3246
Node Rx Ry Rz
M1_N_0_0 0 0 25
M1_N_0_1 0 0 0
M1_N_1_0 0 0 0
M1_N_1_1 0 0 0
Plate Mx My Mxy Qx Qy
M1_Q_0_0 0 0 0 0 0

Python Code

Show Code
import sys
import types

# PATCH: PyNiteFEA 2.0.2 pkg_resources fix
if "pip._vendor.pkg_resources" not in sys.modules:
    mock_pkg = types.ModuleType("pip._vendor.pkg_resources")
    mock_pkg.get_distribution = lambda _: types.SimpleNamespace(version="2.0.2")
    mock_pkg.working_set = []
    sys.modules["pip._vendor.pkg_resources"] = mock_pkg
    pip = sys.modules.setdefault("pip", types.ModuleType("pip"))
    vendor = sys.modules.setdefault("pip._vendor", types.ModuleType("pip._vendor"))
    pip._vendor = vendor
    vendor.pkg_resources = mock_pkg

from Pynite import FEModel3D

def plate(materials, meshes=[], plates=[], nodes=[], supports=[], pressures=[], pynite_plate_output='Displacements'):
    """
    Analyze 2D plates and shells. Supports rectangular meshing or manual quad definition.

    See: https://pynite.readthedocs.io/en/latest/index.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        materials (list[list]): Materials [name, E, nu, rho].
        meshes (list[list], optional): Rectangular meshes [name, width, height, div_x, div_y, material, thickness, origin_x, origin_y, origin_z]. Default is [].
        plates (list[list], optional): Manual plates [name, n1, n2, n3, n4, material, thickness]. Default is [].
        nodes (list[list], optional): Manual nodes [name, x, y, z]. Default is [].
        supports (list[list], optional): Supports [node, fix_dx, fix_dy, fix_dz, fix_rx, fix_ry, fix_rz]. Bools. Default is [].
        pressures (list[list], optional): Uniform pressures [plate_or_mesh, pressure]. Default is [].
        pynite_plate_output (str, optional): Output type. Valid options: Displacements, Reactions, Plate Forces/Moments, Stacked Results. Default is 'Displacements'.

    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
            name = str(row[0])
            E = float(row[1])
            nu = float(row[2])
            rho = float(row[3])
            G = E / (2 * (1 + nu)) # Calculate G
            model.add_material(name, E, G, nu, rho)

        # Nodes (Manual)
        for row in to2d(nodes):
            if not row: continue
            model.add_node(str(row[0]), float(row[1]), float(row[2]), float(row[3]))

        # Meshes
        for row in to2d(meshes):
            if not row: continue
            name, w, h, div_x, div_y, mat, thick = str(row[0]), float(row[1]), float(row[2]), int(row[3]), int(row[4]), str(row[5]), float(row[6])
            ox = float(row[7]) if len(row) > 7 else 0
            oy = float(row[8]) if len(row) > 8 else 0
            oz = float(row[9]) if len(row) > 9 else 0

            dx_inc, dy_inc = w / div_x, h / div_y

            # Generate mesh manually for robustness
            node_map = {}
            for i in range(div_x + 1):
                for j in range(div_y + 1):
                     nx, ny, nz = ox + i * dx_inc, oy + j * dy_inc, oz
                     n_name = f"{name}_N_{i}_{j}"
                     model.add_node(n_name, nx, ny, nz)
                     node_map[(i,j)] = n_name

            for i in range(div_x):
                for j in range(div_y):
                     p_name = f"{name}_Q_{i}_{j}"
                     n1, n2, n3, n4 = node_map[(i,j)], node_map[(i+1,j)], node_map[(i+1,j+1)], node_map[(i,j+1)]
                     model.add_plate(p_name, n1, n2, n3, n4, thick, mat)

        # Manual Plates
        for row in to2d(plates):
            if not row: continue
            model.add_plate(str(row[0]), str(row[1]), str(row[2]), str(row[3]), str(row[4]), float(row[6]), str(row[5]))

        # Supports
        for row in to2d(supports):
            if not row: continue
            n = str(row[0])
            flags = [bool(x) for x in row[1:7]]
            model.def_support(n, *flags)

        # Pressures
        for row in to2d(pressures):
            if not row: continue
            target = str(row[0])
            press = float(row[1])
            # Target could be a single plate or a mesh prefix?
            # Simple check:
            if target in model.plates:
                 model.add_plate_surface_pressure(target, press)
            else:
                 # Try prefix match
                 for p_name in model.plates:
                     if p_name.startswith(target):
                         model.add_plate_surface_pressure(p_name, press)

        # 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 = []
        # max_cols = 6 for Stacked compatibility (PlateForces has 6)
        if pynite_plate_output == 'Displacements' or pynite_plate_output == 'Stacked':
            header = ['Node', 'Dx', 'Dy', 'Dz']
            if pynite_plate_output == 'Stacked': header += [''] * 2
            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, 'DZ')]
                if pynite_plate_output == 'Stacked': row += [''] * 2
                res.append(row)

        if pynite_plate_output == 'Reactions' or pynite_plate_output == 'Stacked':
            header = ['Node', 'Rx', 'Ry', 'Rz']
            if pynite_plate_output == 'Stacked': header += [''] * 2
            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, 'RxnFZ')]
                if pynite_plate_output == 'Stacked': row += [''] * 2
                res.append(row)

        if pynite_plate_output == 'PlateForces' or pynite_plate_output == 'Stacked':
            res.append(['Plate', 'Mx', 'My', 'Mxy', 'Qx', 'Qy'])
            for p_nm in model.plates:
                p = model.plates[p_nm]
                # moment returns [Mx, My, Mxy]
                m = get_val(p, 'moment', 0.5, 0.5) 
                # Since get_val might return a float if it fails, we handle it
                if isinstance(m, list):
                     res.append([p_nm, m[0], m[1], m[2], 0, 0])
                else:
                     res.append([p_nm, 0, 0, 0, 0, 0])


        return res
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Materials [name, E, nu, rho].
Rectangular meshes [name, width, height, div_x, div_y, material, thickness, origin_x, origin_y, origin_z].
Manual plates [name, n1, n2, n3, n4, material, thickness].
Manual nodes [name, x, y, z].
Supports [node, fix_dx, fix_dy, fix_dz, fix_rx, fix_ry, fix_rz]. Bools.
Uniform pressures [plate_or_mesh, pressure].
Output type.

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: Axial Forces Output

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: Reactions Output

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: Displacements Output

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: Stacked Results Output

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
import sys
import types

# PATCH: PyNiteFEA 2.0.2 pkg_resources fix
if "pip._vendor.pkg_resources" not in sys.modules:
    mock_pkg = types.ModuleType("pip._vendor.pkg_resources")
    mock_pkg.get_distribution = lambda _: types.SimpleNamespace(version="2.0.2")
    mock_pkg.working_set = []
    sys.modules["pip._vendor.pkg_resources"] = mock_pkg
    pip = sys.modules.setdefault("pip", types.ModuleType("pip"))
    vendor = sys.modules.setdefault("pip._vendor", types.ModuleType("pip._vendor"))
    pip._vendor = vendor
    vendor.pkg_resources = mock_pkg

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/index.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)}"

Online Calculator

Nodes [name, x, y].
Members [name, node_i, node_j, A, E].
Supports [node, fix_x, fix_y]. Bools.
Nodal loads [node, fx, fy].
Output type (Reactions, AxialForces, Displacements).