Files
Pier_v1/src/core/rebar/footing.ts
T
ypshimandClaude Haiku 5.5 bad220d791 fix(rebar): 조립 철근 B11·B12를 짝수 격자에 배치 (B9 홀수 격자와 겹치지 않음)
- assemblyEvenStart: 짝수 시작칸 중 양끝 여유 차이가 가장 작은 값 선택
- B12(W 방향, n 61)는 시작칸이 1(홀수)이던 것을 2(짝수)로 바꿔 B9 행선과 겹치지 않게 함
- 개수(B11 30, B12 24)와 가운데 정렬(차이 ≤ 1칸)은 그대로, 엇갈림 2칸 유지
- 테스트: 조립 철근 인덱스가 모두 짝수

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 17:51:10 +09:00

293 lines
12 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';
import { DEFAULT_FOOTING_REBAR, type FootingGridKey, type FootingRebarSettings } from './footing-settings';
/**
* 확대기초 철근 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 확대기초 하면.
*/
/** 확대기초 고정 규칙 (사양): Δ₂ 하면 1·2단 간격 = 100 (고정). The other values are the editable defaults. */
export const DELTA2 = 100;
/** Kept for reference; the calculations read the settings passed in (승인 2026-10-26). */
export const FOOTING_DEFAULTS = { ...DEFAULT_FOOTING_REBAR, delta2: DELTA2 } 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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR, key: FootingGridKey = 'sAxis'): GridResult {
const s = f[key];
const A = side - 2 * f.cSide;
const n = Math.floor((A - 2 * f.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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): Layers {
const zB5 = H - f.cTop;
const zB1 = zB5 - (d('S29') + d('S25')) / 2;
const zB24 = f.cBottom;
const zB68 = f.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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): number {
const z = layers(H, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
const g = grid(W, f);
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 * f.cSide;
const leg2 = legB2(H, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
const g = grid(L, f, 'sPerp');
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 * f.cSide;
const z = layers(H, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): number {
const m = Math.floor((layers(H, f).B5 - f.eTop - f.cBottom) / f.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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR) => f.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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
const depth1 = sideDepth(NOMINAL_DIAMETER.S29, NOMINAL_DIAMETER.S19, f); // B10-1: 123.85
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19, f); // B10-2: 122.25
const levels = b10Levels(H, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): number[] {
const m = b10Levels(H, f) - 1;
const js: number[] = [];
for (let j = f.n; j < m; j += f.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). */
/**
* First grid index of the even group of the assembly bars (B11 or B12), chosen on EVEN indices (확대기초철근 (1).md §9.1: the
* B5·B1 and B9 positions take the odd grid, so B11·B12 sit on the even grid and never overlap them). Among the even choices,
* the one that centres the union of both 2-step-staggered groups best (left and right gaps as equal as possible) is used.
*/
export function assemblyEvenStart(n: number, k: number): number {
let best = 0;
let bestGap = Infinity;
for (let e0 = 0; e0 + 2 <= n; e0 += 2) {
const idx: number[] = [];
for (const s of [e0, e0 + 2]) for (let i = s; i <= n; i += k) idx.push(i);
const gap = Math.abs(Math.min(...idx) - (n - Math.max(...idx)));
if (gap < bestGap) {
best = e0;
bestGap = gap;
}
}
return best;
}
/**
* Grid indices of the assembly bars (B11 or B12) on one level (승인 2026-10-26): every k grid steps, on even indices, with the
* 2-step stagger between levels (even levels at e0, odd levels at e0 + 2). n = positions − 1; idx = level index (0, 1, …).
*/
export function assemblyGridIndices(n: number, k: number, idx: number): number[] {
const start = idx % 2 === 0 ? assemblyEvenStart(n, k) : assemblyEvenStart(n, k) + 2;
const count = start <= n ? Math.floor((n - start) / k) + 1 : 0;
return Array.from({ length: count }, (_, m) => start + m * k);
}
export function assemblyBars(L: number, W: number, H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
const levelsJ = assemblyJs(H, f);
// B11 is spread along L (교각축 직각 grid) and B12 along W (교각축 grid), per 확대기초철근 (1).md §8.
const nW = grid(W, f, 'sAxis').positions - 1;
const nL = grid(L, f, 'sPerp').positions - 1;
const b11 = levelsJ.reduce((a, _, idx) => a + assemblyGridIndices(nL, f.kB11, idx).length, 0);
const b12 = levelsJ.reduce((a, _, idx) => a + assemblyGridIndices(nW, f.kB12, idx).length, 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, f);
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR, key: FootingGridKey = 'sAxis'): number[] {
const g = grid(side, f, key);
return Array.from({ length: g.positions }, (_, i) => -side / 2 + f.cSide + g.e + i * f[key]);
}
/** 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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): { rows: number; perRow: number[]; bars: B9Bar[] } {
const xs = gridPositions(L, f);
const ns = gridPositions(W, f);
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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): { rows: number; perRow: number[]; count: number; length: number; bars: B9Bar[] } {
const { rows, perRow, bars } = b9Bars(L, W, columnX, columnD, f);
const lv = layers(H, f).B5 - layers(H, f).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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): { B11: number[]; B12: number[] } {
const js = assemblyJs(H, f);
const B11 = js.map((j) => f.cBottom + j * f.sV);
const B12 = B11.map((z) => z + NOMINAL_DIAMETER.S19);
return { B11, B12 };
}
/**
* Values shown on the 확대기초 panel as rules and derived values (승인 2026-10-26). Each is read from the same functions the
* rebar table and the 3D view use, so the panel cannot drift from the calculation. H is the footing height (mm).
*/
export function footingRuleValues(H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): {
upper2sAxis: number;
upper2sPerp: number;
delta2: number;
b2Leg: number;
b6Leg: number;
b9Pitch: number;
b9Stagger: number;
b11Pitch: number;
b12Pitch: number;
assemblyVertical: number;
assemblyLevels: number;
} {
const z = layers(H, f);
return {
upper2sAxis: 2 * f.sAxis,
upper2sPerp: 2 * f.sPerp,
delta2: DELTA2,
b2Leg: legB2(H, f),
b6Leg: z.B5 - z.B68,
b9Pitch: 4 * f.sAxis,
b9Stagger: 2 * f.sAxis,
b11Pitch: f.kB11 * f.sPerp,
b12Pitch: f.kB12 * f.sAxis,
assemblyVertical: f.n * f.sV,
assemblyLevels: assemblyJs(H, f).length,
};
}