Files
Pier_v1/src/core/rebar/footing.ts
T

248 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { hookLength, NOMINAL_DIAMETER, type BarSize } from './common';
/**
* 확대기초 철근 B1–B8, B10–B12 (plan C5). Source: RAG/확대기초철근 (1).md, 2026-10-22 최종 정리.
* B9 (전단철근) is not computed yet. Lengths in mm, heights in mm from the 확대기초 하면.
*/
export const FOOTING_DEFAULTS = {
/** 피복 상면 / 측면 / 하면 (최외측 철근 중심까지). */
cTop: 100,
cSide: 100,
cBottom: 150,
/** 첫 철근 최소 여유. */
eMin: 150,
/** 격자 실간격 s. */
s: 125,
/** 하면 1단과 2단의 간격 Δ_2. */
delta2: 100,
/** B10 높이 간격 s_v and 조립 단 배수 n. */
sV: 200,
n: 2,
/** 상단 최소 여유 e_top (B10 top level, §7.1). */
eTop: 150,
} as const;
export interface GridResult {
/** 격자 칸 수 n = floor((A − 2·e_min)/s); 위치 수 = n + 1. */
n: number;
/** 격자 길이 G = n·s. */
G: number;
/** 양끝 여유 e = (A − G)/2. */
e: number;
/** 위치 수 n + 1. */
positions: number;
}
/** Grid along one side of the footing (§3.1). side = W for B1–B4, L for B5–B8. */
export function grid(side: number, s: number = FOOTING_DEFAULTS.s, cSide: number = FOOTING_DEFAULTS.cSide, eMin: number = FOOTING_DEFAULTS.eMin): GridResult {
const A = side - 2 * cSide;
const n = Math.floor((A - 2 * eMin) / s);
const G = n * s;
const e = (A - G) / 2;
return { n, G, e, positions: n + 1 };
}
const d = (size: BarSize) => NOMINAL_DIAMETER[size];
/** Leg of B1·B3·B5·B7: 12·d_b on each end (proposed rule, spec §1.2 · Q-B5). */
export const legLength = (size: BarSize) => 12 * d(size);
/** Layer heights (§4.2), mm from the 하면. H = footing height. */
export interface Layers {
B5: number;
B1: number;
B24: number;
B68: number;
B3: number;
B7: number;
}
export function layers(H: number): Layers {
const { cTop, cBottom, delta2 } = FOOTING_DEFAULTS;
const zB5 = H - cTop;
const zB1 = zB5 - (d('S29') + d('S25')) / 2;
const zB24 = cBottom;
const zB68 = cBottom + (d('S25') + d('S29')) / 2;
return { B5: zB5, B1: zB1, B24: zB24, B68: zB68, B3: zB24 + delta2, B7: zB68 + delta2 };
}
/** Lengths of the B1–B4 family along the length L (§5.2). */
export function legB2(H: number): number {
const z = layers(H);
return z.B1 - z.B24; // 1723 at H 2000
}
export interface FootingBar {
name: 'B1' | 'B2' | 'B3' | 'B4' | 'B5' | 'B6' | 'B7' | 'B8' | 'B10-1' | 'B10-2' | 'B11' | 'B12';
size: BarSize;
/** Bars in all (over the footing). */
count: number;
/** Length of one bar, mm (straight part + legs). */
length: number;
}
/** B1–B4 (§5): the grid along W, with lengths from L. */
export function bottomBars(L: number, W: number, H: number): FootingBar[] {
const g = grid(W);
const n = g.positions;
const N4 = Math.ceil(n / 2); // B4 at even i
const N2 = Math.floor(n / 2); // B2 at odd i
const l0 = L - 2 * FOOTING_DEFAULTS.cSide;
const leg2 = legB2(H);
return [
{ name: 'B1', size: 'S25', count: N2, length: l0 + 2 * legLength('S25') },
{ name: 'B2', size: 'S25', count: N2, length: l0 + 2 * leg2 },
{ name: 'B3', size: 'S19', count: N2, length: l0 + 2 * legLength('S19') },
{ name: 'B4', size: 'S25', count: N4, length: l0 },
];
}
/** B5–B8 (§6): the grid along L, with lengths from W. */
export function crossBars(L: number, W: number, H: number): FootingBar[] {
const g = grid(L);
const n = g.positions;
const N8 = Math.ceil(n / 2); // B8 at even i
const N6 = Math.floor(n / 2); // B6, B5, B7 at odd i
const l0 = W - 2 * FOOTING_DEFAULTS.cSide;
const z = layers(H);
const leg6 = z.B5 - z.B68;
return [
{ name: 'B5', size: 'S29', count: N6, length: l0 + 2 * legLength('S29') },
{ name: 'B6', size: 'S29', count: N6, length: l0 + 2 * leg6 },
{ name: 'B7', size: 'S19', count: N6, length: l0 + 2 * legLength('S19') },
{ name: 'B8', size: 'S29', count: N8, length: l0 },
];
}
/** B10 levels: m = floor((z_B5 − e_top − c_b)/s_v) (§7.1), so the count is m + 1 (9 for P1, H 2000). */
export function b10Levels(H: number): number {
const m = Math.floor((layers(H).B5 - FOOTING_DEFAULTS.eTop - FOOTING_DEFAULTS.cBottom) / FOOTING_DEFAULTS.sV);
return m + 1;
}
/** Depth of a side bar from the side face: c_s + (d_a + d_b)/2 (§7, §8.3). B10-1 hangs on B6, B10-2 on B2. */
export const sideDepth = (dOuter: number, dB10: number) => FOOTING_DEFAULTS.cSide + (dOuter + dB10) / 2;
/** B10-1 (on W sides) and B10-2 (on L sides): m + 1 levels at s_v, two faces each (§7). */
export function sideBars(L: number, W: number, H: number): FootingBar[] {
const depth1 = sideDepth(NOMINAL_DIAMETER.S29, NOMINAL_DIAMETER.S19); // B10-1: 123.85
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19); // B10-2: 122.25
const levels = b10Levels(H);
const hook = hookLength('main90', d('S19'));
return [
{ name: 'B10-1', size: 'S19', count: levels * 2, length: L - 2 * depth2 + 2 * hook },
{ name: 'B10-2', size: 'S19', count: levels * 2, length: W - 2 * depth1 + 2 * hook },
];
}
/** Assembly levels j = n, 2n, … strictly inside the B10 levels, 0 < j < m (§8.1). */
export function assemblyJs(H: number): number[] {
const m = b10Levels(H) - 1;
const js: number[] = [];
for (let j = FOOTING_DEFAULTS.n; j < m; j += FOOTING_DEFAULTS.n) js.push(j);
return js;
}
/** Ends of B11 / B12 lie (d_B10 + d_assembly)/2 further inside than the B10 depth, so the two bars just touch (§8.3). */
export function assemblyEndOffset(): number {
return (d('S19') + d('S16')) / 2; // 17.5
}
/** B11 (along W, between the B10-2 levels) and B12 (along L), 4s apart with a 2s stagger per level (§8). */
export function assemblyBars(L: number, W: number, H: number): FootingBar[] {
const levelsJ = assemblyJs(H);
const perLevel = (n: number, startOdd: number, startEven: number) =>
levelsJ.map((_, idx) => {
const i0 = idx % 2 === 0 ? startOdd : startEven;
return Math.floor((n - i0) / 4) + 1;
});
const nW = grid(W).positions - 1; // 61
const nL = grid(L).positions - 1; // 79
// B11 is spread along L (n = 79) and B12 along W (n = 61); each level has its own count.
const b11 = perLevel(nL, 0, 2).reduce((a, b) => a + b, 0);
const b12 = perLevel(nW, 0, 2).reduce((a, b) => a + b, 0);
const hook = Math.round(hookLength('main90', d('S16'))); // 246 mm, rounded as in the document (§8.3)
const depth1 = sideDepth(NOMINAL_DIAMETER.S29, NOMINAL_DIAMETER.S19);
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19);
return [
{ name: 'B11', size: 'S16', count: b11, length: W - 2 * (depth1 + assemblyEndOffset()) + 2 * hook },
{ name: 'B12', size: 'S16', count: b12, length: L - 2 * (depth2 + assemblyEndOffset()) + 2 * hook },
];
}
/** Plane positions of the grid along one side, mm from the footing centre: x_i = −side/2 + c_s + e + i·s (§3.1). */
export function gridPositions(side: number): number[] {
const g = grid(side);
return Array.from({ length: g.positions }, (_, i) => -side / 2 + FOOTING_DEFAULTS.cSide + g.e + i * FOOTING_DEFAULTS.s);
}
/** B9 bar of the rule (§9.1, Q-B18): a vertical bar at (x, n), mm from the footing centre. */
export interface B9Bar {
x: number;
n: number;
}
/**
* B9 positions (확대기초철근 (1).md §9.1, rule applied): rows at W index 1, 5, 9 … (step 4 on the grid); in row r the L index
* starts at 1 (even r) or 3 (odd r) and steps by 4. A bar is left out when it falls in a column zone: its centre is closer
* than the column radius plus half a bar to a column axis (Q-B20, applied: the 기둥 구역 is removed).
*/
export function b9Bars(L: number, W: number, columnX: number[], columnD: number = 2000): { rows: number; perRow: number[]; bars: B9Bar[] } {
const xs = gridPositions(L);
const ns = gridPositions(W);
const rowIndices: number[] = [];
for (let i = 1; i < ns.length; i += 4) rowIndices.push(i);
const halfBar = NOMINAL_DIAMETER.H16 / 2;
const bars: B9Bar[] = [];
const perRow = rowIndices.map((iW, r) => {
const start = r % 2 === 0 ? 1 : 3;
let kept = 0;
for (let iL = start; iL < xs.length; iL += 4) {
const x = xs[iL];
const n = ns[iW];
if (columnX.some((cx) => Math.hypot(x - cx, n) < columnD / 2 + halfBar)) continue;
bars.push({ x, n });
kept++;
}
return kept;
});
return { rows: rowIndices.length, perRow, bars };
}
/**
* B9 전단철근 (확대기초철근 (1).md §9). Vertical bars from the B2·B4 level to the B5 level, H16, with 135° hooks at both ends.
* Length = l_v + 2·l_h (l_v = z_B5 − z_B24). The hook tail (6·d) turns toward the footing centre (Q-B19, applied).
*/
export function shearB9(L: number, W: number, H: number, columnX: number[] = [], columnD: number = 2000): { rows: number; perRow: number[]; count: number; length: number; bars: B9Bar[] } {
const { rows, perRow, bars } = b9Bars(L, W, columnX, columnD);
const lv = layers(H).B5 - layers(H).B24; // 1750
const lh = hookLength('stirrup135', d('H16'));
return { rows, perRow, count: bars.length, length: lv + 2 * lh, bars };
}
/** Leg offsets from the grid line (§5.3, §6.3): the B1/B3 and B5/B7 legs sit beside the outer leg. */
export function legOffsets(): { B1: number; B3: number; B5: number; B7: number } {
const dB2 = NOMINAL_DIAMETER.S25;
const dB1 = NOMINAL_DIAMETER.S25;
const dB3 = NOMINAL_DIAMETER.S19;
const dB6 = NOMINAL_DIAMETER.S29;
const dB5 = NOMINAL_DIAMETER.S29;
const dB7 = NOMINAL_DIAMETER.S19;
return {
B1: (dB2 + dB1) / 2,
B3: (dB2 + dB3) / 2,
B5: (dB6 + dB5) / 2,
B7: (dB6 + dB7) / 2,
};
}
/** B11/B12 heights (§8.1): B10 levels at 150 + k·s_v; the assembly levels are j = n, 2n, 3n (n = 2), and B12 sits d_B10 above B11. */
export function assemblyHeights(H: number): { B11: number[]; B12: number[] } {
const { cBottom } = FOOTING_DEFAULTS;
const sV = FOOTING_DEFAULTS.sV;
const js = assemblyJs(H);
const B11 = js.map((j) => cBottom + j * sV);
const B12 = B11.map((z) => z + NOMINAL_DIAMETER.S19);
return { B11, B12 };
}