@theerapat-s28/ts-structural-eng-tools/fea
Frame Analysis
Linear-elastic 3D frame analysis by the direct stiffness method, run for user-defined load cases and factored load combinations, with member loads, self-weight, end releases, spring supports, force diagrams, envelopes and design demands that feed the RC capacity functions.
analyzeFrame takes nodes, members, supports, the load cases (dead,
superimposed dead, live, wind…) and the load combinations built from them. It analyses
every load case, forms every combination by superposition, and returns displacements, reactions,
member end forces and force diagrams for each, plus an envelope over the combinations. Section
properties come from
rectBeamToSectionProperties
or are supplied directly, so the module itself does not depend on the RC code.
rectBeamToSectionProperties come from ACI 318-19. Load factors are entered by the user; the module does not generate code combinations.
| Quantity | Basis | Code or adaptation |
|---|---|---|
| Element stiffness | Euler–Bernoulli prismatic beam, 12 DOF; no shear deformation | Adaptation (standard matrix analysis) |
| Member loads | Exact fixed-end forces from the beam shape functions | Adaptation |
| Solution | Linear, small displacements; no P-Δ, no cracking | Adaptation |
| Load combinations | Linear superposition of load-case results, factors entered by the user | Adaptation; valid because the analysis is linear |
| Stiffness modifiers (0.35 Ig beams, 0.70 Ig columns) | ACI 318-19, 6.6.3.1.1 | Code, applied in rectBeamToSectionProperties |
| Concrete unit weight (24 kN/m³ default) | Common design practice | Not a code value; override with unitWeight |
| Topic | Convention |
|---|---|
| Global axes | Right-handed X, Y, Z with Z up. DOFs: UX, UY, UZ, RX, RY, RZ. |
| Local axes | ETABS-style 1, 2, 3. Local 1 runs from the start node to the end node. DOFs: U1, U2, U3, R1, R2, R3. |
| Non-vertical member | Local 3 = (1 × Z) normalised, horizontal; local 2 = 3 × 1, in the vertical plane pointing up. A member along +X has local 2 = +Z and local 3 = −Y. |
| Vertical member | Local 2 = +X, local 3 = 1 × 2. A column along +Z has local 3 = +Y. |
| Member angle | Rotates local 2 and 3 about local 1 by the right-hand rule, in rad. |
| Section axes | I33 is about local 3 and governs bending in the 1–2 plane (U2–R3, the major axis); I22 is about local 2 and governs the 1–3 plane (U3–R2). A rectangle with depth h along local 2: I33 = b·h³/12. |
| Rotations | All results use the right-hand rule. The reference stiffness matrix measures R2 and R3 the other way; analyzeFrame converts internally. |
| Units | Coordinates, lengths and positions in mm; E, G in MPa; A in mm²; I, J in mm⁴; loads in kN, kN·m and kN/m; springs in kN/mm and kN·m/rad; unit weight in kN/m³. Results in kN, kN·m, mm and rad. |
| Self-weight | Acts along global −Z (gravity, with Z up), as selfWeight · unitWeight · A per unit length of member. |
| Internal forces | Forces and moments acting on the positive (+1) face of a cut, along the positive local axes. N is positive in tension; M3 is positive for sagging when local 2 points up; V2 = −dM3/dx. |
| Member displacements | u1, u2, u3 along the local axes and ux, uy, uz along the global axes, in mm, including the member's rigid-body motion. Bending follows u2″ = M3/EI33 and u3″ = −M2/EI22. |
"X", "Y", "Z") are per unit length of member, not per unit projected length.
Every load sits in a load case. A case is analysed on its own, so its results show what
that load alone does. A load combination is a factored sum of cases (and of other
combinations), for example 1.2D + 1.6L. Load factors are always entered by the user.
id such as "D", "SD", "L". The optional type ("dead", "superDead", "live", "roofLive", "snow", "wind", "seismic", "other") is a label only and does not change the analysis.nodalLoads and memberLoads, each tagged with the loadCase it belongs to. Keeping them out of loadCases keeps the case list short in a large model, and the loads stay flat tables that can come straight from a spreadsheet or database. Their order does not matter.selfWeight: 1. Every member then carries selfWeight · unitWeight · A per unit length along global −Z, so each member's section needs a unitWeight (rectBeamToSectionProperties sets 24 kN/m³). Use unitWeight: 0 for a weightless member.{ case, factor } or { combination, factor }. A combination may reference combinations listed after it; loops are rejected (310). Factors may be negative, e.g. 0.9D − 1.0W for reversed wind.result.cases.D, result.combinations["1.2D+1.6L"], and result.envelope for the max/min over all combinations. Case and combination ids share one namespace and must be unique across both lists.
Three kinds of load can be applied. Nodal and member loads are listed in the model's
nodalLoads and memberLoads, each tagged with its loadCase;
self-weight is switched on per load case. Several loads on the same node or member in the same
case simply add up.
| Load | Where | Unit | Applied as |
|---|---|---|---|
| Nodal force / moment | nodalLoads | kN, kN·m | fx, fy, fz along and mx, my, mz about the global axes, at any node (supported nodes included). Omitted components are 0 |
| Uniform member load | memberLoads, type: "udl" | kN/m | w per unit length of member, over the whole member or from start to end (mm from the member start) for a partial load |
| Point member load | memberLoads, type: "point" | kN | P at distance a (mm) from the member start |
| Self-weight | loadCases[].selfWeight | — | A uniform load selfWeight · unitWeight · A along global −Z on every member; see Load cases |
| direction | Meaning |
|---|---|
| "1" | Along the member axis (local 1, from start to end node) — an axial load |
| "2", "3" | Along local 2 or local 3 — a transverse load in that member's own axes. For a beam along +X, local 2 is +Z, so direction: "2", w: -10 acts downward |
| "X", "Y", "Z" | Along a global axis. The load is resolved onto the member's local axes, so on an inclined member a vertical load produces both axial and transverse components |
A positive w or P acts in the positive direction of the chosen axis. With Z up, gravity loads are negative "Z" (e.g. w: -10).
w · cos θ.
nodalLoads: [ // 25 kN horizontal and 3 kN·m about Z at node B, wind case { loadCase: "W", nodeId: "B", fx: 25, mz: 3 }, // kN, kN·m ], memberLoads: [ // full-length gravity load { loadCase: "SD", type: "udl", memberId: "AB", direction: "Z", w: -10 }, // kN/m // partial load from 1.0 m to 3.0 m along the member { loadCase: "L", type: "udl", memberId: "AB", direction: "Z", w: -15, start: 1000, end: 3000 }, // mm // 40 kN point load 2.2 m from the start, in the member's local 2 direction { loadCase: "L", type: "point", memberId: "BC", direction: "2", P: -40, a: 2200 }, // kN, mm ],
Each member load is turned into the exact fixed-end forces of a prismatic member (from the beam shape functions), adjusted for any end releases, and applied to the end nodes as equivalent nodal loads. The member's force diagrams are then built from its end forces plus the loads themselves, so a point load shows a shear jump (both sides are reported) and a partial load changes slope exactly where it starts and ends.
0 ≤ a ≤ L, or a uniform load does not satisfy 0 ≤ start < end ≤ L; 302 if a load names a node or member that is not in the model; 309 if its loadCase is not in loadCases.
| Load | Workaround |
|---|---|
| Concentrated moment inside a member | Split the member with a node at that point and apply a nodal moment |
| Triangular / trapezoidal (varying) load | Approximate with several partial uniform loads (start / end) |
| Load per projected length | Enter w · cos θ per member length (see above) |
| Temperature load | — |
| Imposed support settlement | — |
member-diagram.ts
Every load case and combination result has a memberDiagrams entry per member, with values
along the member, not only at its ends, ready for plotting axial force, shear, torsion and
bending-moment diagrams and the deflected shape.
| Station | Why it is there |
|---|---|
| Evenly spaced points | options.stations of them (default 21), ends included. Raise it for smoother curves |
| Load breakpoints | Every point load and every start and end of a partial uniform load, where a diagram changes slope or steps |
| Zero-shear points | Where V2 or V3 crosses zero, so the peak bending moment is always a station |
| Both sides of a point load | Two stations share the same x: the value just before and just after the load, so a shear step plots vertically |
| Field | Type | Unit | Description |
|---|---|---|---|
| x | number | mm | Distance from the member's start node |
| N, V2, V3 | number | kN | Axial force (tension positive) and shears along local 2 and 3 |
| T, M2, M3 | number | kN·m | Torsion and bending moments about local 2 and 3 |
| u1, u2, u3 | number | mm | Displacement along local 1, 2, 3 |
| ux, uy, uz | number | mm | The same displacement in global axes, for drawing the deflected shape on the model |
extremes gives the max and min of N, V2, V3, T, M2, M3, u1, u2 and u3, each as { value, x }, for example the largest deflection between the nodes and where it occurs.
A single 6 m simply supported member along X, 10 kN/m downward, E = 200 000 MPa, I33 = 8·10⁷ mm⁴ (no node at midspan):
memberDiagrams[0].stations // 21 stations, x = 0, 300, … 6000 mm { x: 0, V2: -30, M3: 0, u2: 0, uz: 0, … } { x: 3000, V2: 0, M3: 45, u2: -10.54687, uz: -10.54687, … } // kN, kN·m, mm memberDiagrams[0].extremes.u2 // { max: { value: 0, x: 0 }, min: { value: -10.54687, x: 3000 } }
That is wL²/8 = 45 kN·m and 5wL⁴/384EI = 10.5469 mm at midspan, while both nodal displacements are 0.
start + (x / length) · (end − start) along the member.M3 and V2 along local 2, M2 and V3 along local 3. memberAxes returns those directions.M3 is positive for sagging when local 2 points up, so it plots upward on a beam along X; to draw moments on the tension side, flip the sign. V2 is the force on the positive face, negative at the left support of a gravity-loaded beam; flip it for the textbook shear sign.(ux, uy, uz) times a magnification factor to each station's position.The module is plain TypeScript with no runtime dependencies or Node.js APIs, and both the model and the result are plain JSON data, so it runs in a browser, in a Web Worker, or on a server.
@theerapat-s28/ts-structural-eng-tools/fea. The package ships an ES-module build (import) for bundlers such as Vite, with "sideEffects": false so unused code is dropped, and a CommonJS build (require) for Node.js.details (node, member, load case, combination, field) in the UI.diagrams: "extremes", and keep the createFrameAnalysis object in the worker to answer "show this member's diagram" requests.FrameAnalysisError has code, details and issues; a 304 (unstable) names a node and DOF of the mechanism.import { createFrameAnalysis, type FrameAnalysis } from "@theerapat-s28/ts-structural-eng-tools/fea"; let analysis: FrameAnalysis | undefined; self.onmessage = ({ data }) => { try { if (data.type === "analyse") { analysis = createFrameAnalysis(data.model, { diagrams: "extremes" }); self.postMessage({ type: "result", result: analysis.result }); } else if (data.type === "diagram" && analysis) { self.postMessage({ type: "diagram", diagram: analysis.memberDiagram(data.resultId, data.memberId) }); } } catch (e) { self.postMessage({ type: "error", code: e.code, message: e.message, issues: e.issues }); } };
Measured on regular building grids (columns and two-way beams, 3 load cases including self-weight, 3 combinations) with Node.js on a development machine; browsers run the same code at a similar speed. Time includes validation, all cases, combinations, diagrams and the envelope.
| Nodes | Members | DOF | Factorisation | Full run | Result ("full") | Run / result ("extremes") |
|---|---|---|---|---|---|---|
| 60 | 111 | 360 | 8 ms | 0.1 s | 2.9 MB | 0.06 s / 0.8 MB |
| 486 | 1 176 | 2 916 | 0.1 s | 0.8 s | 31 MB | 0.6 s / 8 MB |
| 847 | 2 130 | 5 082 | 0.26 s | 1.5 s | 56 MB | 1.2 s / 15 MB |
| 1 872 | 4 935 | 11 232 | 1.5 s | 4.3 s | 131 MB | 4.0 s / 34 MB |
| 3 927 | 10 660 | 23 562 | 8.0 s | 15 s | 285 MB | 14 s / 73 MB |
validateFrameModel (errors 301–303 and 305–314); every problem is collected, and the first is thrown with all of them in issues.T; number the free DOFs with the nodes in reverse Cuthill–McKee order (which narrows the band), assemble the stiffness in skyline (variable-band) storage, add spring stiffnesses and factorise by Cholesky once. A singular matrix throws 304, naming a node and DOF of the mechanism.loadCase, resolve member loads and self-weight into local fixed-end forces, form F = nodal loads − fixed-end forces, and recover displacements, reactions R = K·U − F and member end forces.analyze-frame.tsAnalyses a 3D frame for every load case, forms every load combination, and returns the results of each with an envelope over the combinations.
| Param | Type | Unit | Required? | Description |
|---|---|---|---|---|
| model | FrameModel | — | Required | Nodes, members, restraints, at least one load case, and optional combinations; see FrameModel |
| options.stations | number | — | Optional | Evenly spaced stations per member diagram (default 21); load breakpoints and zero-shear points are added |
| options.diagrams | "full" | "extremes" | — | Optional | "full" (default) returns every member diagram with its stations. "extremes" keeps each diagram's extremes — so the envelope and memberDemandEnvelope still work — but leaves stations empty, about 4× smaller for large models. Get full diagrams on demand from createFrameAnalysis |
const { sectionProperties } = rectBeamToSectionProperties({ fc_: 28, b: 300, h: 600 }); // unitWeight defaults to 24 kN/m³ → self-weight 24 · 0.18 m² = 4.32 kN/m const udl = (loadCase, memberId, w) => ({ loadCase, type: "udl", memberId, direction: "Z", w }); // w in kN/m const result = analyzeFrame({ nodes: [ { id: "A", x: 0, y: 0, z: 0 }, // mm { id: "B", x: 5000, y: 0, z: 0 }, { id: "C", x: 10000, y: 0, z: 0 }, ], members: [ { id: "AB", startNodeId: "A", endNodeId: "B", section: sectionProperties }, { id: "BC", startNodeId: "B", endNodeId: "C", section: sectionProperties }, ], restraints: [ { nodeId: "A", ux: true, rx: true, uy: true, uz: true }, { nodeId: "B", uy: true, uz: true }, { nodeId: "C", uy: true, uz: true }, ], loadCases: [ { id: "D", type: "dead", selfWeight: 1 }, { id: "SD", type: "superDead" }, { id: "L", type: "live" }, ], memberLoads: [ // one flat list for every load case; each load names its case udl("SD", "AB", -10), udl("SD", "BC", -10), udl("L", "AB", -15), udl("L", "BC", -15), ], combinations: [ { id: "DEAD", terms: [{ case: "D", factor: 1 }, { case: "SD", factor: 1 }] }, { id: "1.4D", terms: [{ combination: "DEAD", factor: 1.4 }] }, { id: "1.2D+1.6L", terms: [{ combination: "DEAD", factor: 1.2 }, { case: "L", factor: 1.6 }] }, ], });
{
cases: {
D: { reactions: [{ nodeId: "A", fz: 8.1 }, { nodeId: "B", fz: 27 }, { nodeId: "C", fz: 8.1 }], … }, // kN
SD: { … },
L: { … },
},
combinations: {
"1.2D+1.6L": {
id: "1.2D+1.6L", kind: "combination",
reactions: [{ nodeId: "A", fz: 77.22 }, { nodeId: "B", fz: 257.4 }, { nodeId: "C", fz: 77.22 }],
nodalDisplacements: [{ nodeId: "A", ry: 0.0022817, /* rad */ … }, …],
memberEndForces: [{ memberId: "AB",
start: { f2: 77.22, m3: 0 }, end: { f2: 128.7, m3: -128.7 } }, …], // kN, kN·m
memberDiagrams: [{ memberId: "AB",
extremes: { M3: { max: { value: 72.3938, x: 1875 }, min: { value: -128.7, x: 5000 } }, … },
stations: […] }, …],
equilibriumResidual: { force: 0, moment: 0 },
warnings: []
},
"DEAD": { … }, "1.4D": { … }
},
envelope: {
combinationIds: ["DEAD", "1.4D", "1.2D+1.6L"],
reactions: […, { nodeId: "B", fz: { max: { value: 257.4, combinationId: "1.2D+1.6L" },
min: { value: 89.5, combinationId: "DEAD" } }, … }, …],
members: [{ memberId: "AB", extremes: { M3: {
max: { value: 72.3938, x: 1875, combinationId: "1.2D+1.6L" },
min: { value: -128.7, x: 5000, combinationId: "1.2D+1.6L" } }, … } }, …],
nodalDisplacements: […]
},
calculationDetails: { nodeCount: 3, memberCount: 2, dofCount: 18, freeDofCount: 10, profileSize: 47,
loadCaseCount: 3, combinationCount: 3 },
units: { force: "kN", moment: "kN·m", length: "mm", rotation: "rad" },
warnings: []
}
The factored load is 1.2·(4.32 + 10) + 1.6·15 = 41.184 kN/m, and the results are the textbook two-span values: reactions 3wL/8, 10wL/8, 3wL/8, support moment wL²/8 = 128.7 kN·m and span moment 9wL²/128 = 72.3938 kN·m.
| Field | Type | Unit | Description |
|---|---|---|---|
| cases | Record<string, FrameCaseResult> | — | Result of each load case, keyed by its id; see FrameCaseResult |
| combinations | Record<string, FrameCaseResult> | — | Result of each load combination, keyed by its id; empty when none are defined |
| envelope | FrameEnvelope | undefined | — | Max/min over all combinations with the governing combination; absent when there are no combinations. See FrameEnvelope |
| calculationDetails | object | — | Node, member, DOF, load case and combination counts, and profileSize — the stored entries of the banded stiffness factor, a measure of memory and solve work |
| units | object | — | Units of every result field |
| warnings | Warnings | — | Every case and combination warning, each message prefixed with its load case or combination id |
details say where it is, and issues lists every problem. See the error table.
Runs the same analysis as analyzeFrame and keeps the solution, so a member's full diagram can be computed later on demand without re-analysing. Analyse with diagrams: "extremes" for a small result, then draw a member's diagram when the user selects it. Keep the returned object inside the Web Worker that ran the analysis and post plain results to the UI.
Same as analyzeFrame.
const analysis = createFrameAnalysis(model, { diagrams: "extremes" }); analysis.result.cases.D.memberDiagrams[0].stations; // [] — extremes only // the user clicks member "m" while viewing load case "D" const diagram = analysis.memberDiagram("D", "m"); // 21 stations, same as "full" const fine = analysis.memberDiagram("D", "m", 51); // more stations
| Field | Type | Unit | Description |
|---|---|---|---|
| result | FrameResult | — | What analyzeFrame returns for the same model and options |
| memberDiagram(resultId, memberId, stations?) | MemberDiagram | kN, kN·m, mm | The full diagram (stations and extremes) of one member for one load case or combination, identical to the one a "full" analysis returns |
analyzeFrame (FrameAnalysisError 301–314). memberDiagram throws a plain Error for an unknown load case, combination or member id.
Checks a model without analysing it and returns every problem, each with the node, member, load case, combination and field it concerns — for marking all invalid inputs in a form at once. An empty array means the model can be analysed; the only problem it cannot see is an unstable structure (304), which the solve detects. analyzeFrame runs it first.
| Param | Type | Unit | Required? | Description |
|---|---|---|---|---|
| model | FrameModel | — | Required | The model to check, including untyped input from a UI |
const issues = validateFrameModel({ … loadCases: [{ id: "D" }], nodalLoads: [{ loadCase: "D", nodeId: 2, fz: NaN }], // empty input box memberLoads: [{ loadCase: "L", type: "udl", memberId: "m", direction: "z", w: -5 }], // no case "L"; lower-case "z" });
[
{ code: 314, details: { loadCaseId: "D", nodeId: 2, field: "fz" },
message: "An input value is invalid: … [node \"2\", load case \"D\", field \"fz\"]" },
{ code: 309, details: { loadCaseId: "L", memberId: "m", field: "loadCase" }, message: "…" },
{ code: 314, details: { loadCaseId: "L", memberId: "m", field: "direction" }, message: "…" }
]
| Field | Type | Unit | Description |
|---|---|---|---|
| code | number | — | The error code |
| message | string | — | The code's message followed by its location in brackets |
| details | FrameErrorDetails | — | { nodeId?, memberId?, loadCaseId?, combinationId?, field?, dof? } — only the fields that apply |
NaN, Infinity and numeric strings such as "10" are rejected (314), so convert form input with Number(...) and check it before analysing.
Reduces a member's force diagrams to the design demands Mu⁺, Mu⁻, Vu and Nu, enveloped over load combinations (and optionally load cases), and names the combination that governs each. The demands are ready for rectBeamMomentCapacity and the shear functions; the function takes plain numbers and does not import the RC module.
| Param | Type | Unit | Required? | Description |
|---|---|---|---|---|
| result | FrameResult | — | Required | Output of analyzeFrame |
| memberId | string | number | — | Required | Member to reduce |
| options.combinations | string[] | — | Optional | Combination ids to envelope. Default: every combination, unless cases is given |
| options.cases | string[] | — | Optional | Load case ids to envelope as well (or instead). Default: none |
| options.shearCriticalDistance | number | mm | Optional | Take Vu only between this distance from each end, e.g. the effective depth d (default 0) |
const demand = memberDemandEnvelope(result, "AB", { shearCriticalDistance: 540, // mm, d });
{
memberId: "AB",
MuPositive: 72.3938, // kN·m
MuNegative: 128.7, // kN·m, magnitude
Vu: 106.4606, // kN, at d from the supports
Nu: 0, // kN, tension positive
governing: { MuPositive: "1.2D+1.6L", MuNegative: "1.2D+1.6L", Vu: "1.2D+1.6L", Nu: null },
VuRange: { from: 540, to: 4460 } // mm
}
governing names the combination (or case) producing each demand, and is null when that demand is zero in every selected result. The sign of M3 depends on the member's local 2 axis: for a beam along X, local 2 is +Z, so sagging is positive and MuPositive is the span moment.
Error if memberId or a requested combination or case id is not in the result, if nothing is selected (a result without combinations needs cases), or if shearCriticalDistance is used on a result analysed with diagrams: "extremes" (which has no stations to read Vu between). Without a critical distance it works on such a result, taking Vu from the shear extremes.
beam-3d-stiffness.ts · coordinate-transform.tsThe 12×12 local stiffness matrix of a prismatic beam, exactly as in the reference "Complete Beam Stiffness Matrix" (DOF order [U1, U2, U3, R1, R2, R3] at node i, then j). Returned in N and mm.
| Param | Type | Unit | Required? | Description |
|---|---|---|---|---|
| E | number | MPa | Required | Young's modulus |
| G | number | MPa | Required | Shear modulus |
| A | number | mm² | Required | Cross-section area |
| I22 | number | mm⁴ | Required | Used by the U3–R2 terms |
| I33 | number | mm⁴ | Required | Used by the U2–R3 terms |
| J | number | mm⁴ | Required | Torsion constant |
| L | number | mm | Required | Member length |
A symmetric number[][]. Its nonzero terms are ±EA/L, ±GJ/L, 12EI/L³, ±6EI/L², 4EI/L and 2EI/L as laid out in the reference; with I22 = I33 it equals the reference's single-EI matrix.
analyzeFrame does this internally.
The same element as beam3dLocalStiffness with every rotation following the right-hand rule: D·k·D, where D flips the sign of R2 and R3 at both nodes. This is the standard textbook matrix (for example F2,i = +6EI33/L²·R3,i).
Parameters and units are identical to beam3dLocalStiffness.
Returns the member length and the ETABS-style local axes (see Axes & conventions) as the rows of a 3×3 direction-cosine matrix.
| Param | Type | Unit | Required? | Description |
|---|---|---|---|---|
| start | [number, number, number] | mm | Required | Start node X, Y, Z |
| end | [number, number, number] | mm | Required | End node X, Y, Z |
| angle | number | rad | Optional | Rotation of local 2 and 3 about local 1 (default 0) |
const { length, lambda } = memberAxes([0, 0, 0], [5000, 0, 0]); // mm // length = 5000 // lambda = [[1, 0, 0], local 1 = +X // [0, 0, 1], local 2 = +Z // [0, -1, 0]] local 3 = −Y
Input of analyzeFrame. Node and member ids may be strings or numbers and must be unique within nodes, members and restraints.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| nodes | FrameNode[] | mm | Required | { id, x, y, z } |
| members | Member[] | — | Required | { id, startNodeId, endNodeId, section, angle?, releases? }. angle in rad. releases: { start?, end? } hold flags u1, u2, u3, r1, r2, r3 for local end DOFs that carry no force (hinges, rollers) |
| restraints | Restraint[] | kN/mm, kN·m/rad | Required | { nodeId, ux, uy, uz, rx, ry, rz } in global axes. true fixes the DOF, a positive number is a spring stiffness, omitted is free. At least one DOF must be restrained |
| loadCases | LoadCase[] | — | Required | At least one; see LoadCase |
| nodalLoads | NodalLoad[] | kN, kN·m | Optional | { loadCase, nodeId, fx, fy, fz, mx, my, mz } along and about global axes; see NodalLoad |
| memberLoads | MemberLoad[] | kN/m, kN, mm | Optional | Uniform: { loadCase, type: "udl", memberId, direction, w, start?, end? }. Point: { loadCase, type: "point", memberId, direction, P, a }. direction is "1" | "2" | "3" (local) or "X" | "Y" | "Z" (global); positions are measured from the member start in mm; see MemberLoad |
| combinations | LoadCombination[] | — | Optional | See LoadCombination |
One independently analysed set of loads. The loads themselves are listed in the model's nodalLoads and memberLoads, each with loadCase set to this case's id.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| id | string | — | Required | Unique across load cases and combinations, e.g. "D" |
| name | string | — | Optional | Description, copied to the result |
| type | LoadCaseType | — | Optional | "dead" | "superDead" | "live" | "roofLive" | "snow" | "wind" | "seismic" | "other"; a label only |
| selfWeight | number | — | Optional | Self-weight multiplier (1 = full self-weight) along global −Z; default 0. Needs unitWeight on every member section |
A linear combination: the sum of factored load cases and other combinations.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| id | string | — | Required | Unique across load cases and combinations, e.g. "1.2D+1.6L" |
| name | string | — | Optional | Description, copied to the result |
| terms | CombinationTerm[] | — | Required | At least one. Each term is { case: id, factor } or { combination: id, factor }; factors may be negative |
A force and/or moment applied at a node, in global axes. See Loads.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| loadCase | string | — | Required | Id of the load case this load belongs to |
| nodeId | string | number | — | Required | Loaded node |
| fx, fy, fz | number | kN | Optional | Force along global X, Y, Z (default 0) |
| mx, my, mz | number | kN·m | Optional | Moment about global X, Y, Z, right-hand rule (default 0) |
A load on a member, chosen by type. Positions are measured from the member's start node. See Loads.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| loadCase | string | — | Required | Id of the load case this load belongs to |
| type | "udl" | "point" | — | Required | Uniform or point load |
| memberId | string | number | — | Required | Loaded member |
| direction | LoadDirection | — | Required | "1" | "2" | "3" (local) or "X" | "Y" | "Z" (global) |
| w | number | kN/m | Required (udl) | Load per unit member length, positive along the direction axis |
| start, end | number | mm | Optional (udl) | Loaded length for a partial load (default 0 and the member length) |
| P | number | kN | Required (point) | Load, positive along the direction axis |
| a | number | mm | Required (point) | Distance of the load from the member start |
Output of analyzeFrame: cases, combinations, envelope, calculationDetails, units and warnings. The fields are described under analyzeFrame.
Results of one load case or one load combination; both have the same shape.
| Field | Type | Unit | Description |
|---|---|---|---|
| id, kind | string, "case" | "combination" | — | Which load case or combination this is; name and (cases only) type are copied from the input |
| nodalDisplacements | NodalDisplacement[] | mm, rad | ux, uy, uz, rx, ry, rz per node (global axes) |
| reactions | NodalReaction[] | kN, kN·m | fx…mz per node; zero at unrestrained DOFs. A spring DOF reports the force the spring exerts on the node |
| memberEndForces | MemberEndForces[] | kN, kN·m | f1, f2, f3, m1, m2, m3 that the node exerts on each member end, local axes, right-hand |
| memberDiagrams | MemberDiagram[] | kN, kN·m, mm | { memberId, length, stations, extremes }: forces N, V2, V3, T, M2, M3 and displacements u1, u2, u3 (local) and ux, uy, uz (global) at stations along the member, and each component's { max, min } as { value, x }; see Member diagrams |
| equilibriumResidual | { force, moment } | kN, kN·m | Global equilibrium residual of applied loads plus reactions |
| warnings | Warnings | — | Warnings of this case or combination |
Max and min of every presented result over all combinations, each as { value, combinationId } (member extremes also carry x). When two combinations tie, the one listed first governs.
| Field | Type | Unit | Description |
|---|---|---|---|
| combinationIds | string[] | — | The enveloped combinations, in model order |
| nodalDisplacements | array | mm, rad | Per node: ux … rz, each { max, min } |
| reactions | array | kN, kN·m | Per node: fx … mz, each { max, min } |
| members | array | kN, kN·m, mm | Per member: extremes of N, V2, V3, T, M2, M3, u1, u2, u3, each { max, min } with position x |
Returned by createFrameAnalysis: { result: FrameResult; memberDiagram(resultId, memberId, stations?): MemberDiagram }. Unlike the result it holds a function, so it stays in the thread (or worker) that created it.
Prismatic member properties. Produced for rectangular RC beams by rectBeamToSectionProperties, or supplied directly for any other section.
| Field | Type | Unit | Required? | Description |
|---|---|---|---|---|
| E | number | MPa | Required | Young's modulus |
| G | number | MPa | Required | Shear modulus |
| A | number | mm² | Required | Area |
| I22 | number | mm⁴ | Required | About local 2 (bending in the 1–3 plane) |
| I33 | number | mm⁴ | Required | About local 3 (bending in the 1–2 plane, major axis) |
| J | number | mm⁴ | Required | Torsion constant |
| unitWeight | number | kN/m³ | Optional | Material unit weight for load-case self-weight; required only when a case sets selfWeight. Must not be negative |
Thrown errors are instances of FrameAnalysisError (exported from
@theerapat-s28/ts-structural-eng-tools/core, with the coded entries in
FrameErrors). It mirrors RCDesignError and uses the 3XX range.
| Field | Type | Unit | Description |
|---|---|---|---|
| code | number | — | The code below |
| message | string | — | Description, followed by the location in brackets when known, e.g. [member "m", load case "L", field "w"] |
| details | FrameErrorDetails | — | { nodeId?, memberId?, loadCaseId?, combinationId?, field?, dof? } — where the problem is, for highlighting it |
| issues | FrameModelIssue[] | — | Every problem validateFrameModel found; the error itself is the first |
| Code | Name | Meaning |
|---|---|---|
| 301 | ZERO_LENGTH_MEMBER | A member's start and end nodes coincide. |
| 302 | UNKNOWN_NODE_REFERENCE | A member, restraint or load refers to a node or member id that is not in the model. |
| 303 | DUPLICATE_ID | Node ids, member ids or restrained node ids are not unique, or an id is used by more than one load case or combination. |
| 304 | STRUCTURE_UNSTABLE | The restrained structure is a mechanism (singular stiffness matrix): add supports or members. details names a node and DOF involved in the mechanism. |
| 305 | INVALID_SECTION_PROPERTIES | E, G, A, I22, I33 or J is not a positive finite number, or unitWeight is negative. |
| 306 | NO_RESTRAINTS | No DOF is fixed or sprung. |
| 307 | INVALID_RELEASE | The end releases let the member move as a rigid body, for example axial or torsion released at both ends. |
| 308 | LOAD_OUTSIDE_MEMBER | A point load or a uniform load lies outside the member, or a uniform load's start is not before its end. |
| 309 | UNKNOWN_LOAD_CASE | A load's loadCase, or a combination term, refers to a load case or combination that is not in the model, or a term names neither. |
| 310 | CIRCULAR_COMBINATION | Load combinations reference each other in a loop. |
| 311 | INVALID_LOAD_FACTOR | A combination factor or self-weight multiplier is not a finite number, or a combination has no terms. |
| 312 | NO_LOAD_CASES | The model has no load cases. |
| 313 | MISSING_UNIT_WEIGHT | A load case requests self-weight but a member section has no unitWeight. |
| 314 | INVALID_INPUT_VALUE | A number is not finite (NaN, Infinity, or not a number at all, e.g. the string "10"), a spring stiffness is negative, or a value is not one of its allowed options (load direction, member load type, load case type). |
| Kind | Export | Returns | Description |
|---|---|---|---|
| FN | analyzeFrame | FrameResult | 3D frame analysis for every load case and combination |
| FN | createFrameAnalysis | FrameAnalysis | Same analysis, keeping the solution for on-demand member diagrams |
| FN | validateFrameModel | FrameModelIssue[] | Every problem in a model, with its location, without analysing |
| FN | memberDemandEnvelope | { MuPositive, MuNegative, Vu, Nu, governing, … } | Design demands of a member over combinations, with the governing one |
| FN | beam3dLocalStiffness | number[][] | Reference 12×12 local stiffness |
| FN | beam3dLocalStiffnessRightHanded | number[][] | Same matrix with right-hand rotations |
| FN | memberAxes | { length, lambda } | ETABS-style local axes of a member |
| TYPE | FrameModel | — | Analysis input (nodes, members, restraints, load cases, combinations) |
| TYPE | LoadCase | — | One set of loads, with optional self-weight |
| TYPE | LoadCombination | — | Factored sum of load cases and combinations |
| TYPE | NodalLoad | — | Force/moment at a node, global axes |
| TYPE | MemberLoad | — | Uniform (full or partial) or point load on a member |
| TYPE | FrameResult | — | Analysis output |
| TYPE | FrameCaseResult | — | Results of one load case or combination |
| TYPE | FrameEnvelope | — | Max/min over combinations with the governing combination |
| TYPE | FrameAnalysis | — | Result plus on-demand memberDiagram |
| TYPE | SectionProperties | — | E, G, A, I22, I33, J, unitWeight (core) |
| ERR | FrameAnalysisError, FrameErrors | — | Error class (code, message, details, issues) and coded entries 301–314 (core) |