import { DEFAULT_MATERIAL, developmentLength, fyOf, hookLength, Material, NOMINAL_DIAMETER, spliceLength } from './common'; import { layers } from './footing'; /** * 기둥 철근 C4 (plan C4). Source: RAG/기둥철근 (1).md, 2026-10-22 최종 정리. Type-1 원형 중실 기둥. * Lengths in mm, heights in mm from the 확대기초 상면 (up +). */ export interface ColumnInput { /** 기둥 지름 D. */ D: number; /** 기둥 높이 H_c (확대기초 상면 ~ 코핑 Level 하면). */ Hc: number; /** 기둥 중심 위치의 코핑 높이 h_cop. */ hcop: number; /** 기둥 면 ~ P1 중심 피복 c. */ c: number; /** P1 위 끝의 코핑 상면 아래 깊이 e_P1. */ eP1: number; /** 띠철근 구간 위 끝의 코핑 상면 아래 깊이 e_top. */ eTop: number; /** 확대기초 깊이 H_f (for P4). */ Hf: number; /** 주철근 원주 등분 수 n_div. */ nDiv: number; /** 주철근 커플러 간격 (확대기초 상면부터). */ ctcCoupler: number; /** P2 커플러를 P1보다 올리는 높이. */ dzCp2: number; ctcP3: number; ctcP5: number; ctcP4: number; /** P4 구간 깊이 비율 r_P4. */ rP4: number; ctcP6: number; } /** 예제 운곡교 P1 (기둥철근.md §1.2 기본값). */ export const COLUMN_P1: ColumnInput = { D: 2000, Hc: 12703, hcop: 2644, c: 125, eP1: 200, eTop: 1623, Hf: 2000, nDiv: 46, ctcCoupler: 3000, dzCp2: 150, ctcP3: 300, ctcP5: 120, ctcP4: 120, rP4: 0.5, ctcP6: 1800, }; /** A bar in the 확대기초 top layer, height from the 확대기초 상면 (mm, negative = below). */ export interface TopBar { name: string; z: number; d: number; } /** * RB-C07 (기둥철근.md §6.1–6.2, 결정): a P4 that interferes with a 확대기초 top bar is deleted. * Interference: |z_P4 − z_bar| < (d_P4 + d_bar)/2. Only the top layer (B5, B1) is checked; the assembly bars * B11/B12 are not, by the 2026-10-22 decision (Q-C9 무시). */ export function deleteInterferingP4(p4: number[], topBars: TopBar[], dP4: number = NOMINAL_DIAMETER.H19): { kept: number[]; deleted: { z: number; bar: string }[] } { const kept: number[] = []; const deleted: { z: number; bar: string }[] = []; for (const z of p4) { const hit = topBars.find((b) => Math.abs(z - b.z) < (dP4 + b.d) / 2); if (hit) deleted.push({ z, bar: hit.name }); else kept.push(z); } return { kept, deleted }; } /** Top-layer bars of the footing as heights from its 상면 (B5 and B1, §4.2). */ export function footingTopBars(Hf: number): TopBar[] { const z = layers(Hf); return [ { name: 'B5', z: z.B5 - Hf, d: NOMINAL_DIAMETER.S29 }, { name: 'B1', z: z.B1 - Hf, d: NOMINAL_DIAMETER.S25 }, ]; } const dS32 = NOMINAL_DIAMETER.S32; const dH19 = NOMINAL_DIAMETER.H19; const dH16 = NOMINAL_DIAMETER.H16; const round = (v: number, d = 1) => Math.round(v * 10 ** d) / 10 ** d; export function columnRebar(x: ColumnInput = COLUMN_P1, m: Material = DEFAULT_MATERIAL) { // P1 and P2 main bars (기둥철근.md §3) const r1 = x.D / 2 - x.c; const r2 = r1 - dS32; const zTop1 = x.Hc + x.hcop - x.eP1; const lD2 = developmentLength(dS32, fyOf(m, 'S32'), m.fck); const zTop2 = x.Hc + lD2; // Bottom of P1 and P2: 확대기초 하면 2단 (B7) 위 + (d_B7 + d_P1)/2 (기둥철근.md §3.2, the same for both). const zBot = -x.Hf + layers(x.Hf).B7 + (NOMINAL_DIAMETER.S19 + dS32) / 2; const couplerLevels = Array.from({ length: Math.max(0, Math.floor(zTop1 / x.ctcCoupler) - 1) }, (_, i) => (i + 1) * x.ctcCoupler); // Pieces of each main bar between its couplers (기둥철근.md §3.2): the bottom piece carries the 90° hook (16.5·d = 525). const p1Hook = Math.round(hookLength('main90', dS32)); const p1Points = [zBot, ...couplerLevels, zTop1]; const p1Pieces = p1Points.slice(1).map((z, i) => round(z - p1Points[i] + (i === 0 ? p1Hook : 0))); const p2Points = [zBot, ...couplerLevels.map((z) => z + x.dzCp2), zTop2]; const p2Pieces = p2Points.slice(1).map((z, i) => round(z - p2Points[i])); const couplersP1 = couplerLevels.length; const mainLines = x.nDiv; const mainP1 = mainLines * (couplersP1 + 1); const mainP2 = mainLines * (couplersP1 + 1); const couplers = couplersP1 * x.nDiv * 2; // Ties (기둥철근.md §4–§6) // Tie radius r_t = r_1 + (d_P1 + d_P3)/2 (기둥철근.md §4.1) const tieRadius = r1 + (dS32 + dH19) / 2; const zTopT = x.Hc + x.hcop - x.eTop; const lEmin = Math.max(x.D, zTopT / 6, 450); const n5 = Math.floor(lEmin / x.ctcP5); const lE = Math.max(lEmin, n5 * x.ctcP5 + x.ctcP5 / 2); const p3 = Array.from({ length: Math.floor((zTopT - lE) / x.ctcP3) + 1 }, (_, k) => lE + k * x.ctcP3); const n5all = Math.floor(lE / x.ctcP5); const p5 = Array.from({ length: n5all }, (_, j) => lE - (j + 1) * x.ctcP5); const z5last = lE - n5all * x.ctcP5; const m4 = Math.floor((z5last + x.rP4 * x.Hf) / x.ctcP4); const p4All = Array.from({ length: m4 }, (_, j) => z5last - (j + 1) * x.ctcP4); const p4Check = deleteInterferingP4(p4All, footingTopBars(x.Hf)); const p4 = p4Check.kept; // P6 crossties (기둥철근.md §7): 4 bars per 단, from the first kept P4 const z4first = p4.length ? p4[0] : z5last; const p6Levels: number[] = []; for (let z = z4first; z <= x.Hc; z += x.ctcP6) p6Levels.push(z); const p6Straight = 2 * tieRadius; const p6Hook = hookLength('tie90', dH16); const p6Length = p6Straight + 2 * Math.round(p6Hook); // 135 mm per hook, as in the document // P3 lap: Q-C6 is open; recommended = 1.3·l_d of H19 with the material inputs. const lapP3 = spliceLength(dH19, fyOf(m, 'H19'), m.fck); const p3Length = 2 * Math.PI * tieRadius + lapP3; const p5Length = 2 * Math.PI * tieRadius; return { p3Length: round(p3Length, 1), p5Length: round(p5Length, 1), lapP3: round(lapP3, 1), r1: round(r1), r2: round(r2), angleStepDeg: 360 / x.nDiv, zTop1: round(zTop1), zTop2: round(zTop2), zBot: round(zBot, 2), p1Hook, p1Pieces, p2Pieces, couplerP1: couplerLevels, couplerP2: couplerLevels.map((z) => z + x.dzCp2), mainP1, mainP2, couplers, tieRadius: round(tieRadius, 2), tieCircumference: round(2 * Math.PI * tieRadius, 1), zTopT: round(zTopT), lEmin: round(lEmin), lE: round(lE), p3, p5, p4, p6Levels, p6Count: p6Levels.length * 4, p6Length: round(p6Length, 1), p4SemicircleLength: round(Math.PI * tieRadius, 1), p3Count: p3.length, p5Count: p5.length, p4Count: p4.length, p4Deleted: p4Check.deleted, }; }