Checks every concrete beam and column of a solved frame for every strength combination, magnifies the moments of slender columns, and sizes a slab's reinforcement from the plate moments.
How the precision is kept
Demands are station values of the member diagrams (25 stations) or plate center values, in 64-bit numbers, set against the strengths of the ACI module. The stations, the sampled strength diagram and the mesh set the accuracy. Thresholds on moments and shears are absolute values in N and N·mm.
Calculations under this policy
11 calculations in 3 source files. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
The bending strengths for both axes and signs, the two strength diagrams, the effective shear depths and the threshold torsion of a section.
shear depth d = centroid of the bars beyond mid-depth, or 0.8 × depth when there are none
The effective depth is a weighted mean of layer depths, which were rounded to 1e-6 mm when the layers were formed.
frame/rc-design.js
lines 63 to 83
CadCraft 0.8.2
/** Capacities a section needs once (both axes, both signs, the P–M diagrams). */
function sectionSetup(sec, rc) {
const mat = { fc: rc.fc, fy: rc.fy };
const flex = {};
for (const axis of ['y', 'z']) for (const sign of [1, -1]) {
const r = ACI.beamFlexure({ b: sec.b, h: sec.h, bars: sec.bars, axis, sign, ...mat });
flex[axis + (sign > 0 ? '+' : '-')] = r.ok ? r : null;
}
const D = { y: ACI.interaction({ b: sec.b, h: sec.h, bars: sec.bars, axis: 'y', spiral: sec.spiral, ...mat }), z: ACI.interaction({ b: sec.b, h: sec.h, bars: sec.bars, axis: 'z', spiral: sec.spiral, ...mat }) };
// shear along local z is carried over the depth h (the layers of bending about y), along y over b; d = the centroid of the
// bars in the far half (the tension side of a sagging beam), 0.8 × depth when that half has none
const shear = {};
for (const [dir, axis] of [['z', 'y'], ['y', 'z']]) {
const L = layersFor(sec, axis, 1), depth = L.depth;
const t = L.layers.filter((l) => l.d > depth / 2);
const As = t.reduce((s, l) => s + l.As, 0);
const d = As ? t.reduce((s, l) => s + l.As * l.d, 0) / As : 0.8 * depth;
shear[dir] = { bw: L.width, d, h: depth, As };
}
return { flex, D, shear, Ag: sec.b * sec.h, Tth: ACI.torsionThreshold({ b: sec.b, h: sec.h, fc: rc.fc, lambda: rc.lambda }) };
}
Design moment strength at an axial load
64-bit
The largest design moment the strength diagram allows at a given axial force.
linear interpolation between the two diagram points that bracket Pu
A straight line between sampled points. When two points have the same axial strength within 1e-12 N the first point's moment is used.
frame/rc-design.js
lines 85 to 96
CadCraft 0.8.2
/** φMn at a given Pu on a φ diagram (the outline's largest moment at that axial load). */
function momentAt(D, Pu) {
const P = D.points;
let best = 0;
for (let k = 0; k + 1 < P.length; k++) {
const a = P[k], b = P[k + 1];
if ((a.phiPn - Pu) * (b.phiPn - Pu) > 0) continue;
const t = Math.abs(b.phiPn - a.phiPn) < 1e-12 ? 0 : (Pu - a.phiPn) / (b.phiPn - a.phiPn);
best = Math.max(best, a.phiMn + t * (b.phiMn - a.phiMn));
}
return best;
}
Moment magnification of a slender column
64-bit
The magnified design moment of a non-sway column about one axis.
Pc = π² (0.4 Ec Ig ÷ (1 + βdns)) ÷ (k L)²; δ = Cm ÷ (1 − Pu ÷ 0.75 Pc) ≥ 1; Mc = δ max(M₂, Pu (15 + 0.03 h)); Ec = 4700 √f′c
The radius of gyration is taken as 0.3 × the section depth. As Pu approaches 0.75 Pc the divisor 1 − Pu ÷ 0.75 Pc becomes a small difference and δ grows without limit; at or above 0.75 Pc the column is reported as buckling. The diagram counts as loaded between its ends when its middle value leaves the straight line between the end moments by more than 5 % of the largest of them.
frame/rc-design.js
lines 100 to 119
CadCraft 0.8.2
/**
* Non-sway moment magnification of one axis of a column for one combination (ACI 318-19 6.6.4.5). Ms = the moments along the member (N·mm, sign as the diagram's:
* the same sign at both ends is single curvature), Pu the largest compression (N), dim the section depth in the plane of bending (mm), Ig that axis's gross inertia.
* → { slender: false } · { slender: true, unstable, delta, Mc, Pc, Cm, M2min, kLr, limit }
*/
export function magnifyNonSway({ Ms, Pu, dim, Ig, L, rc }) {
const M1 = Ms[0], M2 = Ms[Ms.length - 1];
const big = Math.abs(M1) > Math.abs(M2) ? M1 : M2, small = big === M1 ? M2 : M1;
const ratio = Math.abs(big) > 1e-9 ? small / big : 1;
const kLr = rc.k * L / (0.3 * dim), limit = Math.min(34 + 12 * ratio, 40);
if (kLr <= limit) return { slender: false, kLr, limit };
const Ec = 4700 * Math.sqrt(rc.fc), EI = 0.4 * Ec * Ig / (1 + rc.betaDns), Pc = Math.PI ** 2 * EI / (rc.k * L) ** 2;
if (Pu >= 0.75 * Pc) return { slender: true, unstable: true, Pc, kLr, limit };
const absBig = Math.abs(big), M2min = Math.max(Pu, 0) * (15 + 0.03 * dim);
const mid = Ms[Math.floor(Ms.length / 2)], span = Math.max(absBig, Math.abs(mid), 1e-9);
const transverse = Math.abs(mid - (M1 + M2) / 2) > 0.05 * span; // the diagram is not a straight line between the ends: loads between the supports (6.6.4.5.3b)
const Cm = transverse || absBig < M2min ? 1 : Math.max(0.4, 0.6 + 0.4 * ratio);
const delta = Math.max(1, Cm / (1 - Pu / (0.75 * Pc)));
return { slender: true, unstable: false, delta, Mc: delta * Math.max(absBig, M2min), M2: absBig, Pc, Cm, M2min, kLr, limit };
}
Drawn section against the concrete section
64-bit
Checks that the analyzed section's area and inertias agree with the b × h the bars were given for.
|A − b h| ≤ 0.05 b h; I within 5 % of b h³ ÷ 12 × 1, 0.70 or 0.35
Both tests are relative, 5 %. A mismatch marks the member as partly checked; it does not change a number.
frame/rc-design.js
lines 140 to 143
CadCraft 0.8.2
if (msec && Math.abs(msec.A - S.Ag) > 0.05 * S.Ag) partial.add('area-mismatch');
// the drawn section's inertias against the rectangle (b along local y, h along local z): gross, or the cracked 0.70 / 0.35 of ACI 6.6.3.1.1
const likeIg = (I, Ig) => [1, 0.7, 0.35].some((f) => Math.abs(I - f * Ig) <= 0.05 * f * Ig);
if (msec && !partial.has('area-mismatch') && !(likeIg(msec.Iy, sec.b * sec.h ** 3 / 12) && likeIg(msec.Iz, sec.h * sec.b ** 3 / 12))) partial.add('axes-mismatch');
Beam flexure ratio
64-bit
The utilization of a beam in bending about one or both axes.
|My| ÷ φMny + |Mz| ÷ φMnz
The two ratios are added linearly. The minor-axis term is added only when |Mz| exceeds 1e-9 N·mm, an absolute limit.
The utilization of a column under axial force with one or two moments.
one moment: the ray ratio on its diagram; two, Pu ≥ 0.1 f′c Ag: max(r_y + r_z − Pu ÷ (φ Po), Pu ÷ φPn,max); two, lower Pu: |My| ÷ φMny(Pu) + |Mz| ÷ φMnz(Pu)
The moments count as two when the smaller exceeds 1 % of the larger. The two ray ratios are taken on the diagram without its axial cap. Divisions by a moment strength are floored at 1e-9 N·mm.
frame/rc-design.js
lines 183 to 190
CadCraft 0.8.2
const uy = ACI.pmRatio(S.D.y, Pu, My), uz = ACI.pmRatio(S.D.z, Pu, Mz);
const both = Math.min(Math.abs(My), Math.abs(Mz)) > 0.01 * Math.max(Math.abs(My), Math.abs(Mz), 1e-9);
let r, clause;
if (!both) { r = Math.abs(My) >= Math.abs(Mz) ? uy : uz; clause = 'ACI 318-19 22.4 + Table 21.2.2'; }
else if (Pu >= 0.1 * rc.fc * S.Ag) { r = Math.max(rayUncapped(S.D.y, Pu, My) + rayUncapped(S.D.z, Pu, Mz) - Pu / (S.D.y.points[0].phi * S.D.y.Po), Pu / S.D.y.phiPnMax); clause = 'ACI 318-19 R22.4 (Bresler 1960)'; }
// i18n-ignore: clause names of the design code, the same in every language
else { r = Math.abs(My) / Math.max(momentAt(S.D.y, Pu), 1e-9) + Math.abs(Mz) / Math.max(momentAt(S.D.z, Pu), 1e-9); clause = 'ACI 318-19 R22.4 (linear, low axial load)'; }
keep('rc-pm', clause, r, 1, x);
The moment each of the four bar layers must carry so that bending and twisting are both covered.
M*x = Mx + |Mxy|, M*y = My + |Mxy|; if one is negative: 0, and the other raised by Mxy² ÷ |that moment|
The division by a moment happens only in the branch where that moment is below −|Mxy|, so it is not zero. Results are floored at 0.
frame/slab-design.js
lines 35 to 46
CadCraft 0.8.2
/** Bottom and top design moments of the two layers for the plate moments Mx, My, Mxy (all per unit width, any consistent units). */
export function woodArmer(Mx, My, Mxy) {
const t = Math.abs(Mxy);
const layer = (a, b) => { // a, b = the moments in the direction of each layer, tension on the face this layer is on
let xa = a + t, yb = b + t;
if (xa < 0) { yb = b + (t * t) / Math.abs(a); xa = 0; } // here a < −t ≤ 0, so a is not zero
else if (yb < 0) { xa = a + (t * t) / Math.abs(b); yb = 0; }
return [Math.max(0, xa), Math.max(0, yb)];
};
const [bx, by] = layer(Mx, My), [tx, ty] = layer(-Mx, -My);
return { bx, by, tx, ty };
}
Slab reinforcement and shear
64-bit
The steel required and provided per meter in each layer, and the one-way shear check of a slab.
d = h − cover − bar ÷ 2 (outer layer); provided = (π d_b² ÷ 4) × 1000 ÷ s; need = max(required, ρ_min × 1000 × h); ratio = need ÷ provided
The moments are the largest plate center values over all plates and strength combinations, so they depend on the mesh. Minimum steel is 0.0020 below 420 MPa, else max(0.0014, 0.0018 × 420 ÷ fy). The shear check uses the mean depth and mean steel of the four layers.
frame/slab-design.js
lines 50 to 97
CadCraft 0.8.2
export function slabDesign(model, sol, slabIn, { sets = null } = {}) {
const slab = cleanSlab(slabIn);
if (!slab) return { ok: false, error: 'no-slab' };
if (!sol?.ok || !model.plates?.length) return { ok: false, error: 'no-plates' };
const combos = (sets || model.combos.filter((c) => c.limit === 'strength').map((c) => c.id)).filter((id) => sol.results['combo:' + id]);
if (!combos.length) return { ok: false, error: 'no-combos' };
const prep = sol.prep;
const fc = slab.fc, fy = slab.fy;
const rhoMin = fy < 420 ? 0.0020 : Math.max(0.0014, 0.0018 * 420 / fy);
const out = { ok: true, slab, combos: combos.length, plates: model.plates.length, rows: [], checks: [], max: {} };
const layers = [['bx', 'bottomX', 0], ['by', 'bottomY', 1], ['tx', 'topX', 0], ['ty', 'topY', 1]];
const worst = Object.fromEntries(layers.map(([k]) => [k, { M: 0, plate: null, set: null }]));
let shear = { V: 0, plate: null, set: null, dir: '' };
const tOf = new Map(model.plates.map((p) => [p.id, p.t]));
for (const id of combos) {
const R = sol.results['combo:' + id];
model.plates.forEach((p, k) => {
const r = plateCentre(prep, R, k); // [Nx Ny Nxy Mx My Mxy Qx Qy] per mm of width
const wa = woodArmer(r[3], r[4], r[5]);
for (const [key] of layers) if (wa[key] > worst[key].M) worst[key] = { M: wa[key], plate: p.id, set: id };
for (const [dir, V] of [['x', Math.abs(r[6])], ['y', Math.abs(r[7])]]) if (V > shear.V) shear = { V, plate: p.id, set: id, dir };
});
}
const h = slab.h || Math.max(...model.plates.map((p) => p.t));
out.h = h;
for (const [key, field, which] of layers) {
const b = slab[field], top = key[0] === 't';
// x layer outermost; for the top layers too (cover to the face)
const d = which === 0 ? h - slab.cover - b.d / 2 : h - slab.cover - slab[layers[which === 0 ? 1 : 0][1]].d - b.d / 2;
const Mu = worst[key].M * 1000; // N·mm per m
const req = Mu > 0 ? requiredAs({ b: 1000, d, fc, fy, Mu, phi: PHI_FLEX }) : 0;
const min = rhoMin * 1000 * h;
const prov = area(b.d) * 1000 / b.s;
const row = { key, layer: field, bar: b.d, s: b.s, d, Mu: Mu / 1e6, req: req === null ? Infinity : req, min, need: req === null ? Infinity : Math.max(req, min), prov, ratio: req === null ? Infinity : Math.max(req, min) / prov, plate: worst[key].plate, set: worst[key].set,
clause: 'ACI 318-19 22.3 + 7.6.1.1', top };
out.rows.push(row);
out.max[key] = row;
}
// shear: per metre, at the plate centre of the worst plate (no stirrups in a slab): φVc
const dAvg = out.rows.reduce((a, r) => a + r.d, 0) / out.rows.length;
const AsAvg = out.rows.reduce((a, r) => a + r.prov, 0) / out.rows.length;
const bs = beamShear({ bw: 1000, d: dAvg, h, fc, fyt: 420, Av: 0, s: 0, Vu: shear.V * 1000, As: AsAvg * 1, lambda: slab.lambda });
out.checks.push({ id: 'slab-shear', clause: 'ACI 318-19 22.5.5.1', demand: shear.V * 1000, capacity: bs.phiVn, ratio: bs.phiVn > 0 ? shear.V * 1000 / bs.phiVn : Infinity, unit: 'N/m', plate: shear.plate, set: shear.set, dir: shear.dir, d: dAvg });
out.util = Math.max(...out.rows.map((r) => r.ratio), out.checks[0].ratio);
out.thin = model.plates.some((p) => tOf.get(p.id) < h - 1e-6 || tOf.get(p.id) > h + 1e-6);
void PHI_SHEAR;
return out;
}
The cross-section area of a reinforcing bar from its diameter.
A = π d² ÷ 4
One 64-bit expression with Math.PI; nominal bar areas from a table are not used.
frame/frame-concrete.js
line 51
CadCraft 0.8.2
const area = (d) => Math.PI * d * d / 4;
In detail
Method
ACI 318-19: beams by flexure and shear; columns on the axial force and moment diagrams, with Bresler's reciprocal load form for two moments; non-sway moment magnification 6.6.4.5; slabs by Wood and Armer design moments. The code's comments give the references.
Number types
64-bit numbers.
Tolerances
Two moments count as acting together when the smaller exceeds 1 % of the larger. Moments below 1e-9 or 1e-6 N·mm and shears below 1e-6 N are treated as none (absolute). The drawn section must match b × h within 5 % of area and inertia. A member counts as vertical when |x·Z| is above 0.999999.
Limits
First-order moments apart from the non-sway magnification. Sway magnification, torsion design, punching shear and slab deflection are not built. Slab moments are the plate center values of the worst plate.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.