Repository navigation
Frame analysis
extensions/structure/ analyzes linear-elastic frames and flat shells in three dimensions. It gives static displacements, support reactions, member end forces and natural modes. extensions/building/views/structural.mojo turns a building model into a frame with gravity loads. This page covers frames and the building view. Shell analysis covers the shell element.

| Module | What it gives |
|---|---|
ids |
NodeId, MemberId, ShellId and LoadCaseId
|
kinds |
Dof and the six degrees of freedom UX, UY, UZ, RX, RY, RZ
|
frame |
LocalAxes, member_axes, the local stiffness and mass, the transformation and the consistent loads of a uniform load |
shell |
ShellElement and ShellResultants
|
model |
StructuralModel, with nodes, supports, members, shells, load cases, loads and added masses |
static |
StaticSolver, solve_static, StaticResult, MemberForces and the assembly functions |
modal |
solve_modes, ModalResult and assemble_mass
|
Coordinates are meters, with z up. Gravity acts along -z. The loads and the results at the API carry unit types: Force64, Moment64, LineLoad64, Pressure64, Length64, Angle64, Mass64 and Frequency64.
Each node has six degrees of freedom. A Dof names one. Its value is also its offset in the node's block of a global vector, so entry 6 n + d is degree of freedom d of node n. The raw vectors of a result hold meters, radians, newtons and newton meters.
Add nodes, then members between them. A member takes a Section and a BuildingMaterial from extensions/building/. Fix degrees of freedom with add_support or fix. Add a load case, then loads in it.
var model = StructuralModel()
var base = model.add_node(Vec3d(0, 0, 0))
var tip = model.add_node(Vec3d(3, 0, 0))
_ = model.add_member(base, tip, rectangle(Length64(0.1, METER), Length64(0.2, METER)), steel(), Vec3d(0, 0, 1))
model.fix(base)
var wind = model.add_load_case("wind")
model.add_force(wind, tip, UY, Force64(10, KILONEWTON))
model.add_line_load(wind, MemberId(0), UZ, LineLoad64(-2, KILONEWTON_PER_METER))
model.add_self_weight(wind, Acceleration64(9.80665, METER_PER_SECOND_SQUARED))| Method | Adds |
|---|---|
add_node(at) |
A free node |
add_support(node, dofs), fix(node)
|
Fixed degrees of freedom, at zero displacement |
add_member(start, end, section, material, reference) |
A frame member |
add_shell(a, b, c, thickness, material) |
A flat three-node shell |
add_load_case(name) |
An empty load case |
add_force(case, node, dof, force) |
A force along UX, UY or UZ
|
add_moment(case, node, dof, moment) |
A moment about RX, RY or RZ
|
add_line_load(case, member, dof, load) |
A uniform load per member length, along a global axis |
add_pressure(case, shell, pressure) |
A uniform pressure on a shell |
add_self_weight(case, acceleration) |
The weight of every member and shell |
add_mass(node, mass) |
A point mass on the three translations, for modal analysis only |
StaticSolver takes the model, assembles the stiffness, removes the fixed degrees of freedom and factors the rest once. solve(case) then gives one load case. solve_static(model, case) does both for one case.
var solver = StaticSolver(model^)
var result = solver.solve(wind)
var sway = result.translation(tip, UY)
var base_moment = result.reaction_moment(base, RZ)
var axial = result.member_forces[0].end.axial| Result | Holds |
|---|---|
translation(node, dof), rotation(node, dof)
|
One displacement or rotation |
reaction_force(node, dof), reaction_moment(node, dof)
|
The force or moment the support applies, or zero at a free degree of freedom |
member_forces[m] |
The member's local axes and the end forces at its start and end |
shell_resultants[s] |
The shell's membrane stresses and moments |
displacements, reactions, loads
|
The raw global vectors, six entries per node |
An EndForces value holds the force and moment that a node applies to a member end, along the member's local axes. Its fields are axial, shear_y, shear_z, torque, moment_y and moment_z. At the end node a positive axial force is tension. At the start node a positive axial force is compression. The member's own loads are taken out, so the end forces include the fixed-end forces.
A structure that can move without strain is a mechanism. Its stiffness is singular or not positive definite. StaticSolver raises an error that names the mechanism. Add supports or members to fix it.
solve_modes(model, count) gives the lowest natural frequencies, in hertz, and their mode shapes. The supports apply. Each shape has six entries per node and is normalized so that φᵀ M φ = 1.
model.add_mass(roof, Mass64(20000, KILOGRAM))
var modes = solve_modes(model, 3)
var first = modes.frequencies[0]The mass matrix adds the consistent mass of each member, the lumped mass of each shell and the added masses. The eigensolver is lowest_modes of Numerics: subspace iteration with a Sturm check.
The member is the two-node Euler-Bernoulli element of Przemieniecki, with twelve degrees of freedom. It has no shear deformation and no end releases.
The local x axis runs from the start node to the end node. The local z axis is the part of the reference vector normal to x. It is the direction of the section depth. The local y axis is z × x, the direction of the section width.
If the reference vector is parallel to the member, the global z axis is used instead. If the member is vertical too, the global x axis is used. So a column always has its depth along x, the same as Building.add_column.
| Term | Value |
|---|---|
| Axial | E A / L |
| Torsion | G J / L, with Saint-Venant's constant J from Section.torsion_constant
|
| Bending in the x-z plane | 12 E Iy / L³, 6 E Iy / L², 4 E Iy / L and 2 E Iy / L, with Iy the strong second moment |
| Bending in the x-y plane | The same terms with Iz, the weak second moment |
| Consistent mass | ρ A L / 420 times the Hermite matrix, plus rotary inertia ρ I / (30 L) times its matrix in each plane, plus the polar inertia ρ (Iy + Iz) L in torsion |
| Uniform load w | w L / 2 at each node and moments of w L² / 12 |
| Transformation | Four copies of the 3 by 3 rotation, with the local axes as rows. A global matrix is Tᵀ K T. |
A line load and a member's weight become consistent nodal loads. The rotation about the local y axis is -dw/dx and the rotation about the local z axis is dv/dx. Both are right-handed.
structural_view(building, options) returns a StructuralView. It holds a StructuralModel, its dead and live load cases, the building element of each member and shell, and notes on what it dropped. The view validates the canonical building before it reads model references or creates structural nodes. Invalid public-field edits raise Error.
var view = structural_view(building, default_options())
var solver = StaticSolver(view.model.copy())
var dead = solver.solve(view.dead)
var live = solver.solve(view.live)The view builds the model in these steps:
- Each column and beam becomes frame members. Ends closer than the tolerance share one node.
- A member is split at each node that lies on it, so a beam that passes a column is connected to it.
- The columns with the lowest base are fixed in all six degrees of freedom.
- The dead case holds the weight of the members and of each floor and roof construction. The construction weight is g times the sum of ρ t over its layers.
- The live case holds the live load of the space above each floor, and the roof live load on each roof.
The walls, the openings and the ground slabs are dropped. The notes give the count of each. A ground slab bears on the ground, so it carries no load to the frame.
With shell_divisions at zero, each floor and roof loads the beams at its level by tributary area. The method is this:
- A 32 by 32 grid of points covers the slab's bounding box.
- Each point inside the slab, and the face centroid, goes to the nearest beam in plan.
- A beam takes the share of the slab area that its points have, as a uniform load along its length.
The sum of the loads equals the slab load exactly. The load along each beam is uniform, not triangular or trapezoidal. A slab with no beam at its level loses its load, and a note says so.
With shell_divisions from 1 to 16, each floor and roof becomes a mesh of flat shells instead. The view cuts the slab polygon into triangles with earcut and splits each triangle into that many divisions per side. The shell takes the material of the thickest layer and the total thickness of the construction. Its density is set so that its mass per area equals the construction's. The beams are split at the shell corners on them.
The live loads are typical values from ASCE/SEI 7-16, table 4.3-1. They are not design values. A project must use the loads of its own code and occupancy.
| Use | Live load |
|---|---|
OFFICE |
2.40 kPa |
CORRIDOR, CORE, LOBBY, RETAIL, MEETING
|
4.79 kPa |
LIVING, BEDROOM, KITCHEN, BATHROOM
|
1.92 kPa |
STORAGE |
6.00 kPa, light storage |
MECHANICAL |
7.18 kPa. The table has no row for it, so the view uses the library stack-room value. |
| A roof | 0.96 kPa, an ordinary flat roof |
live_load(use) and roof_live_load() give these values. StructuralViewOptions holds the tolerance, the shell divisions and the gravity. default_options() gives a 1 mm tolerance, tributary loads and standard gravity.
Each row is a test in tests/test_structure_frame.mojo, tests/test_structure_modal.mojo or tests/test_structural_view.mojo. The error is the measured relative difference from the reference.
| Case | Reference | Error |
|---|---|---|
| Cantilever, tip load, both bending planes | P L³ / (3 E I) and P L² / (2 E I) | Below 1e-9 |
| Cantilever, axial load and torque | P L / (E A) and T L / (G J) | Below 1e-9 |
| Cantilever under its own weight | q L⁴ / (8 E I) | Below 1e-9 |
| Fixed-fixed beam, uniform load | w L⁴ / (384 E I) at midspan and w L² / 12 at the ends | Below 1e-9 |
| Simply supported beam, uniform load | 5 w L⁴ / (384 E I) and w L² / 8 | Below 1e-9 |
| Inclined cantilever along (2, 3, 6) | Axial and transverse parts of the tip load | Below 1e-9 |
| Portal frame, 4 m by 6 m, lateral load | Slope-deflection method, which ignores axial strain | 1.47% |
| The same frame, ten times larger | Slope-deflection method | 0.015% |
| Cantilever, first frequency, 2, 4 and 8 elements | 1.8751² sqrt(E I / (m L⁴)) / (2π) | +0.047%, +0.0018%, -0.0013% |
| Simply supported beam, first three frequencies, 10 elements | (n π)² sqrt(E I / (m L⁴)) / (2π) | -0.011%, -0.035%, -0.049% |
| Cantilever with a tip mass ten times the beam mass | Rayleigh's method with 0.2357 m L | -0.002% |
| Two-storey frame view, tributary and shell slabs | Sum of reactions equals the dead and live loads | Below 1e-9 |
| L-shaped roof view | Sum of reactions equals the dead and live loads | Below 1e-9 |
The portal frame difference falls by a factor of 100 when the frame is ten times larger. That is the share of axial strain, which the hand method ignores. The small negative frequency errors come from rotary inertia, which the Euler-Bernoulli formulas ignore.
Every function raises Error for input it cannot use.
| Case | Functions |
|---|---|
| An id is out of range | All methods that take an id |
| A position, load or mass is not finite |
add_node, the add_ load methods, add_mass
|
| A force is not along a translation, or a moment is not about a rotation |
add_force, add_moment, add_line_load, the result accessors |
| A member's ends are at one point, or its reference is zero |
add_member, member_axes
|
| A section or material is not valid |
add_member, add_shell
|
| The structure is a mechanism |
StaticSolver, solve_static, solve_modes
|
| The mode count is out of range | solve_modes |
| The building or view options are not valid | structural_view |
- The analysis is linear and elastic. It has no second-order effects, no buckling and no material nonlinearity.
- The members have no shear deformation, no end releases, no offsets and no warping torsion.
- A support fixes a degree of freedom at zero. The model has no springs and no settlements.
- A member load is uniform along the member and acts along a global axis.
- The view uses uniform tributary loads on beams. It does not model walls as shear walls or as loads.
- Przemieniecki, "Theory of Matrix Structural Analysis", McGraw-Hill, 1968: sections 5.6 and 11.3.
- Bathe, "Finite Element Procedures", 2nd edition, 2014: sections 4.2 and 10.3.
- Hibbeler, "Structural Analysis", chapter 11, the slope-deflection method.
- Blevins, "Formulas for Natural Frequency and Mode Shape", 1979: table 8-1.
- ASCE/SEI 7-16, "Minimum Design Loads and Associated Criteria for Buildings and Other Structures", table 4.3-1.
Tutorials
How-to
Reference
- Scene graph
- Rotations
- Cameras
- Geometry
- Geometry addons
- Lofts and clipping groups
- Sculptor
- Procedural generators
- Meshes and assets
- Lines
- Points and sprites
- Helpers
- Scene objects
- Animated objects
- Raycasting
- Curves and paths
- Animation
- Skinning
- Materials
- Node materials
- TSL functions
- Lights
- Lighting addons
- Fog
- Textures
- Renderer
- Renderer hooks and material flags
- Rasterization
- GPU backend
- Render target
- Post-processing
- GPU computation
- Compute nodes
- Progressive light map
- Gaussian splats
- Voxel global illumination
- SVG renderer
- Image files
- Image decode queue
- Model files
- More model files
- Exporters
- Scene JSON
- Windowing and controls
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Animals
- Buildings
- CARLA
- Femur
- Tibia
- Fibula
- Patella
- Knee
- Muscles
- Vessels
- Lymph
- Nerves
- Integument
- Leg
- Foot
- Pelvis
- Torso
- Arm
- Hand
- Head
- Genome
- Game humanoid
- Humanoid fidelity
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Segment inertia
Explanation
- Software rasterizer
- Scene graph as an array
- Linear color
- Normals are not colors
- Perspective correction
- Fixed-point coverage
- Per-fragment shading
- Mipmaps
- Sorted transparency
- Types and checks
- Render hooks as a trait
- Shared CPU and GPU code
- Bands and threads
- PNG structure
- Extensions beside the port
- CUDA teardown hang
- Mojo compiler hang