Finite Elements

Overview

Introduction

Finite element analysis (FEA) is a numerical method for solving boundary-value problems in structures, continua, and multi-physics systems by dividing a complex domain into smaller, connected elements. Instead of attempting a closed-form solution for an entire structure, FEA approximates displacement, rotation, stress, strain, and internal force fields over each element, then enforces compatibility and equilibrium at shared nodes. The method is now foundational in structural engineering, mechanical design, geotechnics, and many reliability workflows because real systems rarely match textbook geometries or loading conditions. For a broad conceptual reference, see Finite element method (Wikipedia).

In practical terms, finite elements convert “hard geometry + hard loading + hard supports” into a matrix problem that computers solve efficiently. Boardflare’s finite-elements category packages this workflow into spreadsheet-friendly tools backed by the PyNite structural analysis library. The category includes BEAM_2D, FRAME_2D, PLATE, and TRUSS_2D, each aimed at a different idealization of structural behavior. Together, these functions cover most first-pass linear static analysis tasks: one-dimensional flexural members, planar frame action, in-plane axial truss behavior, and two-dimensional plate/shell response.

For business and technical users, this matters because engineering decisions are usually constrained by time, not by theory. Teams often need to evaluate many alternatives—different spans, section properties, support layouts, or load patterns—before selecting one design direction for deeper code-compliant checks. Traditional spreadsheet methods can become brittle as models scale. Finite-element workflows provide a more systematic representation of stiffness interactions, boundary conditions, and load paths while still being automatable inside familiar analyst tooling.

The core economic value is faster and more defensible iteration. A project team can quickly answer questions such as: “How sensitive is midspan deflection to stiffness upgrades?”, “Which support reaction governs foundation design?”, or “How does axial force redistribute if one truss diagonal is resized?” Boardflare’s function set makes these scenarios repeatable by exposing solver inputs and outputs directly in tabular form. That structure supports scenario analysis, dashboarding, and design option comparisons without forcing every stakeholder into a standalone FEA desktop application.

It is important to frame scope clearly: these tools are designed for linear elastic, small-deformation structural response in typical conceptual-to-preliminary design workflows. They are excellent for member sizing studies, load path understanding, and rapid sensitivity checks. They are not substitutes for full nonlinear analyses, detailed code checks, connection design, or jurisdiction-specific stamped calculations where those are required.

When to Use It

Finite-element calculators are most valuable when the structure is too interconnected for hand methods but not so specialized that a full custom simulation stack is needed. The right use case is usually a “decision under uncertainty” problem: many design choices are possible, and the team needs robust, comparable response metrics quickly.

A first common scenario is building framing concept selection. Suppose a team is comparing a long-span floor beam with several support and section alternatives. They need reaction envelopes for support sizing, shear/moment shapes for reinforcement or connection planning, and displacement trends for serviceability. BEAM_2D fits this job: it captures support types, span definitions, and mixed load formats, then returns reactions, displacements, shear, moment, and deflection-oriented views. The business outcome is faster option screening and clearer communication to architects or owners about what drives structural depth and cost.

A second scenario is portal frame and low-rise lateral/gravity interaction studies. Early in a project, engineers often test several frame topologies and stiffness distributions before final member schedules are set. FRAME_2D is appropriate here because frame behavior couples axial, shear, and bending response through rigid joints. By modeling nodes, members, materials, sections, supports, and both nodal/member loads, a team can compare drift trends, base reactions, and member force patterns under different assumptions. This is useful for deciding where stiffness should be added (columns, beams, braces) and for identifying likely governing members before detailed code checks.

A third scenario is slab and panel behavior estimation under distributed pressure. Floor plates, equipment pads, and panelized systems often require more than line-member approximations, especially when support conditions are uneven or load spread is two-dimensional. PLATE addresses this by supporting mesh-based or manually defined plate models with pressure loading and six-DOF support constraints. It is especially helpful when teams need nodal displacement patterns and plate force components to determine whether a region warrants thickening, additional support points, or a more refined model downstream.

A fourth scenario is axial-force-dominated systems such as light trusses, roof bracing, and simplified bridge triangulations. When joints are intended to be pin-connected and bending is secondary, TRUSS_2D provides an efficient model that emphasizes member axial forces, support reactions, and nodal movements. This is ideal for rapid member force ranking, sign checking (tension vs compression), and preliminary sizing loops where dozens of geometry variants must be tested.

These functions are also useful in cross-functional contexts:

  • In procurement planning, reactions and force envelopes support early sizing assumptions that influence cost forecasts.
  • In risk reviews, displacement outputs highlight serviceability or compatibility concerns before late-stage redesign.
  • In value engineering, side-by-side model outputs make it easier to quantify trade-offs between material usage and structural performance.

A practical heuristic is to use these tools when three conditions hold: (1) load paths are coupled enough that statics alone is cumbersome, (2) the team needs repeatable scenario runs, and (3) linear elastic assumptions are acceptable for the decision stage.

How It Works

All four calculators follow the same finite-element pipeline: discretize, assign properties, apply boundary conditions, load the model, solve for nodal degrees of freedom, and post-process element-level response quantities. The mathematical backbone is the global stiffness formulation implemented by PyNite.

At a high level, each element contributes a local stiffness matrix \mathbf{k}^{(e)} in its local coordinate system. Through coordinate transformation and assembly, these contributions populate a global matrix \mathbf{K} such that:

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

where:

  • \mathbf{K} is the assembled global stiffness matrix,
  • \mathbf{u} is the vector of unknown nodal displacements and rotations,
  • \mathbf{F} is the global load vector including nodal loads and equivalent nodal loads from distributed actions.

After partitioning known and unknown DOFs (from supports/constraints), the reduced linear system is solved, then reactions are recovered from constrained DOFs:

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

Element internal forces are obtained by mapping solved nodal DOFs back into element formulations. For beam/frame members, this yields shear and bending moments along the member coordinate; for truss elements, axial force is dominant; for plate elements, bending/twisting moments and transverse shears are derived from plate kinematics and constitutive relations.

For thin isotropic plate behavior, a common constitutive bending stiffness term is:

D = \frac{Et^3}{12(1-\nu^2)}

with elastic modulus E, plate thickness t, and Poisson ratio \nu. While users do not manually derive these matrices in Boardflare, understanding this dependency is essential: small increases in thickness can dramatically increase plate bending stiffness due to the t^3 relationship.

The calculators differ primarily in element type and DOF interpretation:

  • BEAM_2D models 2D beam-line behavior and returns classic line-member outputs (reactions, nodal displacements, shear/moment, deflection-oriented summaries).
  • FRAME_2D extends line-member modeling to rigid-joint frame behavior in a plane, capturing coupled axial-flexural effects across connected members.
  • TRUSS_2D idealizes members as pin-jointed axial bars; rotational releases are applied to mimic truss assumptions.
  • PLATE models 2D surface behavior through meshes or manually defined plate elements, appropriate when load redistribution is inherently area-based.

Underlying assumptions should be explicit in any engineering workflow:

  1. Linearity: Material behavior is elastic and load-displacement response is linear.
  2. Small deformations: Geometry changes under load do not significantly alter stiffness.
  3. Idealized supports/joints: Real boundary behavior is approximated with discrete constraints.
  4. Model fidelity dependence: Output quality depends on geometry, property, and load definitions.

These assumptions are often acceptable in preliminary design, but analysts should escalate to more advanced models when large displacement, nonlinear material behavior, instability, contact, cracking, or staged construction effects are expected.

For implementation details and solver behavior, refer to the PyNite documentation: PyNite Docs. Boardflare wraps these capabilities into spreadsheet-compatible interfaces that expose model inputs and selected output views.

Practical Example

Consider a design team evaluating a small single-story industrial structure with three analysis questions due in one review cycle:

  1. Can the roof beam layout meet serviceability under gravity load?
  2. How do frame support reactions and member actions change with alternate column stiffness?
  3. Does a local equipment slab region need thickening under pressure load?

The team can run this as a staged workflow using the category functions.

Step 1: Screen roof beam behavior with BEAM_2D.

The analyst defines span lengths, support conditions (for example pinned–roller or fixed), load cases (point and distributed), and material/section properties. The first pass requests reactions and moment output to identify peak support and internal demand locations. A second pass requests displacement/deflection output to evaluate serviceability trends. At this stage, the team is not trying to finalize member design; they are narrowing feasible options and identifying which combinations merit deeper review.

Step 2: Test frame system interactions with FRAME_2D.

Next, nodes and members are assembled for a portal frame abstraction. Material and section sets represent candidate stiffness options, while supports and loads capture boundary assumptions. The analyst runs at least two alternatives (baseline and stiffer columns) and compares reactions, nodal displacements, and member force outputs. If drift-sensitive behavior improves significantly with modest stiffness changes, the team gains a data-backed rationale for the preferred scheme before detailed design resources are committed.

Step 3: Evaluate local slab response with PLATE.

For the equipment zone, the analyst creates either a generated rectangular mesh or manually defined plate elements. Material, thickness, support constraints, and pressure loads are specified. Displacement outputs reveal deflection patterns and high-response regions; plate force outputs provide additional insight into bending demand directionality. If sensitivity runs show that a slight thickness increase greatly reduces displacement, this can become a targeted design intervention instead of globally over-thickening the slab.

Step 4: Validate bracing concept with TRUSS_2D.

A triangulated bracing alternative is assessed as a pin-jointed truss model. The analyst checks axial force distribution and identifies likely compression-critical members for preliminary sizing. Reactions and displacements are reviewed to ensure compatibility with adjacent framing assumptions. This isolates whether the bracing concept behaves as intended before adding connection and out-of-plane complexity.

Step 5: Consolidate decisions in a single comparison table.

Because outputs are tabular, the team can align key metrics across options: maximum displacement, controlling reaction, peak bending/axial indicators, and qualitative constructability notes. This creates a transparent audit trail from assumptions to recommendations, which is useful in design reviews and stakeholder communication.

Why this workflow is effective in Boardflare rather than ad hoc spreadsheets:

  • It keeps the structural logic explicit through model inputs instead of hidden equation chains.
  • It supports repeatable scenario runs with consistent output schemas.
  • It enables phased fidelity: quick screening first, refinement second.
  • It reduces manual transcription risk when comparing alternatives.

The result is not “automatic final design.” The result is faster convergence on the right engineering questions and the most promising design directions.

How to Choose

Choosing the right calculator starts with structural idealization. The most common mistake is selecting a solver based on convenience rather than dominant behavior. A short decision tree helps:

graph TD
    A[Start: What is the dominant behavior?] --> B{Main load path in line members?}
    B -- Yes --> C{Are joints intended as rigid?}
    C -- Yes --> D[Use FRAME_2D]
    C -- No, pin-jointed axial system --> E[Use TRUSS_2D]
    B -- Yes, single/continuous beam focus --> F[Use BEAM_2D]
    B -- No, area/surface load redistribution --> G[Use PLATE]

The table below provides a more detailed selection guide.

Function Best for Key inputs Primary outputs Strengths Limitations
BEAM_2D Single or continuous planar beam lines with standard supports and load patterns Spans, supports, loads, material/section properties Reactions, nodal displacements, shear, moment, deflection-style summaries Fast setup, clear line-member diagrams, excellent for serviceability and support demand screening Not intended for full frame joint interaction or 2D area behavior
FRAME_2D Planar rigid-joint systems (portal frames, multi-member lateral/gravity interaction) Nodes, members, materials, sections, supports, nodal/member loads Reactions, displacements, member force quantities Captures coupled axial-shear-bending behavior in connected frames Still a planar linear model; 3D effects and advanced nonlinear behavior are outside scope
TRUSS_2D Pin-jointed axial systems (trusses, bracing layouts) Nodes, members (A, E), supports, nodal loads Axial forces, reactions, displacements Efficient for tension/compression force paths and rapid sizing loops Assumes truss idealization; bending and joint rigidity effects are not primary
PLATE Surface elements under pressure/distributed action (slabs, panels, local plate regions) Materials, meshes/plates/nodes, supports, pressures Nodal displacements, reactions, plate force tables Represents area load redistribution and surface response beyond line models Mesh quality/definition matters; interpretation requires plate behavior awareness

A practical selection workflow:

  1. Start with geometry and connection intent: line members vs surfaces; rigid vs pin joints.
  2. Match dominant response metric to solver output: moment/shear trends, axial forces, or plate effects.
  3. Run a coarse baseline model first, then refine only where sensitivity indicates high leverage.
  4. Validate that assumptions (linearity, support idealization) are acceptable for the decision stage.

If two tools seem plausible, use both at low fidelity as a cross-check. For example, a beam abstraction with BEAM_2D can quickly bracket expected trends before a fuller FRAME_2D model is built. Likewise, a frame can identify boundary force patterns that inform where PLATE detail is worth the additional setup.

The category is strongest when used as a hierarchy of insight:

  • TRUSS_2D for axial-system concept screening,
  • BEAM_2D for line-member flexural behavior,
  • FRAME_2D for connected planar system response,
  • PLATE for local or global surface behavior.

Selecting the smallest model that still captures the governing behavior generally yields the best balance of speed, interpretability, and decision quality.

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: Simply supported beam reactions under uniform load

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: Cantilever beam tip displacement under end point load

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 samples for uniformly loaded simple span

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 samples for uniformly loaded simple span

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: Support-node deflection summary for a centered point load

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: Combined beam output for a uniformly loaded simple span

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
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/member.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)
            elif 'moment' 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, 'Mz', mag, x1 - m.i_node.X)
                  break

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

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: Mesh nodal displacements under uniform surface pressure

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: Supported corner reaction summary for a single mesh panel

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 center force and shear result sample

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 4.34028 -83.3333 1.17832e-14 -9.32486e-15 -29.9603
Example 4: Combined plate displacement reaction and force 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 -4.16667 -4.16667 0.171327 -4.77865 -4.77865

Python Code

Show Code
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/plate.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

        def flatten_result(value):
            if hasattr(value, 'flatten'):
                value = value.flatten().tolist()
            elif isinstance(value, tuple):
                value = list(value)
            return value

        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]
                try:
                    center_x = ((p.j_node.X - p.i_node.X)**2 + (p.j_node.Y - p.i_node.Y)**2 + (p.j_node.Z - p.i_node.Z)**2)**0.5 / 2
                    center_y = ((p.n_node.X - p.i_node.X)**2 + (p.n_node.Y - p.i_node.Y)**2 + (p.n_node.Z - p.i_node.Z)**2)**0.5 / 2
                    moment = flatten_result(p.moment(center_x, center_y, combo_name='Combo 1'))
                    shear = flatten_result(p.shear(center_x, center_y, combo_name='Combo 1'))
                    res.append([
                        p_nm,
                        float(moment[0]),
                        float(moment[1]),
                        float(moment[2]),
                        float(shear[0]),
                        float(shear[1]),
                    ])
                except:
                    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: 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)}"

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).