/** * 코핑 철근 (plan C3). Source: RAG/코핑철근.md, 2026-10-22 최종 정리 (spec §6.4 is older and is not used here). * Positions are along the coping length from its start, in mm. */ import { DEFAULT_MATERIAL, developmentLength, fyOf, hookLength, Material, NOMINAL_DIAMETER } from './common'; export const COPING_REBAR = { /** 피복 (최외측 철근 중심까지). */ cover: 100, /** 첫 전단철근 최소 여유 e_add. 끝 여유 e_min = cover + e_add = 300. */ eAdd: 200, /** Level 폭 W_L. */ levelWidth: 2100, /** C10 간격 s_L (기둥 중심부터). */ spacingL: 300, /** C9·C11 간격 s_s (C10에서 바깥으로). */ spacingS: 250, /** C12·C13 측면 수평 간격. */ spacingSide: 250, /** 테이퍼 높이 h2 (코핑철근.md §2). */ taperHeight: 1500, /** C14 위 2줄 간격 / 아래 줄 수평 간격 / 아래 줄 연직 간격 / 끝 여유. */ c14: { upperSpacing: 1000, lowerSpacing: 2000, verticalSpacing: 500, endGap: 500 }, } as const; export const COPING_END_GAP = COPING_REBAR.cover + COPING_REBAR.eAdd; export interface ShearBars { /** C10 positions (mm), from the first to the last. */ c10: number[]; /** C9 positions (mm), from the left end toward the column. */ c9: number[]; /** C11 positions (mm), from the column toward the right end. */ c11: number[]; /** Distance from the coping end to the outermost C9 / C11 bar (mm). */ endGap: number; } /** * Shear bars C9–C11 along the axis (코핑철근.md §3). A single column at the coping centre. * C10: @300 from the column centre, out to the first position beyond the Level edge (k_max). * C9 / C11: @250 outward from the outermost C10, as many as fit with the end gap of at least 300. */ export function shearBars(coping: number, xc = coping / 2): ShearBars { const { spacingL, spacingS, levelWidth } = COPING_REBAR; const kMax = Math.ceil(levelWidth / 2 / spacingL); // k_max = ceil((W_L/2)/s_L), spec §3.1 const c10 = Array.from({ length: 2 * kMax + 1 }, (_, i) => Math.round(xc + (i - kMax) * spacingL)); const left = c10[0]; const right = c10[c10.length - 1]; const n9 = Math.floor((left - COPING_END_GAP) / spacingS); if (n9 < 0) { throw new Error('RB-K01: C10 바깥 구간이 끝 여유보다 짧아 C9를 놓을 수 없습니다.'); } const c9 = Array.from({ length: n9 }, (_, j) => Math.round(left - (n9 - j) * spacingS)); const c11 = Array.from({ length: n9 }, (_, j) => Math.round(right + (j + 1) * spacingS)); const endGap = Math.min(left - c9[0], coping - c11[c11.length - 1]); return { c10, c9, c11, endGap: endGap }; } /** * Side horizontal bars (코핑철근.md §6): seven rows from the bottom at 250 mm. * Rows below the sloped-bottom break (z_7,end) are C12, the rows at and above it are C13. */ export function sideRows(zBottom: number, zBreak: number, rows = 7): { c12: number[]; c13: number[] } { const z = Array.from({ length: rows }, (_, i) => Math.round((zBottom + i * COPING_REBAR.spacingSide / 1000) * 1000) / 1000); return { c12: z.filter((v) => v < zBreak - 1e-9), c13: z.filter((v) => v >= zBreak - 1e-9) }; } /** C14 counts (코핑철근.md §8): 2 upper lines of 11 plus the lower rows 5 / 6 / 3 / 2 = 38. */ export const C14_COUNTS = { upperLines: 2, upperPerLine: 11, lower: [5, 6, 3, 2] as const }; export function c14Total(): number { return C14_COUNTS.upperLines * C14_COUNTS.upperPerLine + C14_COUNTS.lower.reduce((a, b) => a + b, 0); } /** C15 (거꾸로 된 U, 가장 높은 계단) and C16 (L형, 2구간) counts (코핑철근.md §7). */ export const STEP_BAR_COUNTS = { C15: 11, C16: 22 } as const; /** * C9·C10 lower U length (코핑철근.md §4.3): 1740 + 2h. h rises linearly along the slope between the document's * ends (x 300 → h 890.9, x 4550 → h 2178.8) and stays at the Level height 2300 from the Level edge (4950) on. * The bar's upper cap (삿갓) length depends on l_cap, which is still open (Q-K5), so only the U is given here. */ export const SHEAR_U = { x0: 300, h0: 890.9, x1: 4550, h1: 2178.8, levelH: 2300, levelEdgeFromCentre: 1050, flat: 1740 } as const; export function shearUHeight(x: number, coping: number): number { const xs = Math.min(x, coping - x); // mirror to the left half const slope = (SHEAR_U.h1 - SHEAR_U.h0) / (SHEAR_U.x1 - SHEAR_U.x0); const h = SHEAR_U.h0 + (xs - SHEAR_U.x0) * slope; return Math.min(h, SHEAR_U.levelH); } export function shearULength(x: number, coping: number): number { return SHEAR_U.flat + 2 * shearUHeight(x, coping); } /** C13 sits 127 in from each end face (코핑철근.md §6.2). */ export const SIDE_C13_END = 127; /** Leg of each ㄷ형 bar along the side, 1.3·l_d = 1525 (Q-K9). */ export const SIDE_HOOK_LEG = 1525; export interface SideBar { /** Row height (m, EL). */ z: number; /** Distance from each end face to where the 일자 bar starts (mm). */ xw: number; /** Length of the 일자 bar, L − 2·x_w (mm). */ straight: number; } /** * C12 and C13 rows for a coping of length L (mm), T형 (코핑철근.md §6.2). * C12 meets the sloped bottom at x_w = c + (z_break − z_k)·(x_L0 − c)/h2, with x_L0 = L/2 − W_L/2. * C13 starts at x_w = 127. Each row has two 일자 bars (side faces) and two ㄷ bars (one at each end). */ export function copingSideBars(L: number, zBottom: number, zBreak: number): { c12: SideBar[]; c13: SideBar[] } { const c = COPING_REBAR.cover; const xL0 = L / 2 - COPING_REBAR.levelWidth / 2; const bar = (z: number, xw: number): SideBar => ({ z, xw, straight: L - 2 * xw }); const rows = sideRows(zBottom, zBreak); return { c12: rows.c12.map((z) => bar(z, c + ((zBreak - z) * 1000 * (xL0 - c)) / COPING_REBAR.taperHeight)), c13: rows.c13.map((z) => bar(z, SIDE_C13_END)), }; } /** C15 (거꾸로 된 U, 3852) and C16 (L형, 4070 / 4069) lengths, mm (코핑철근.md §7). */ export const STEP_LENGTHS = { C15: 3852, C16: [4070, 4069] as const, C14: 3447.5 } as const; /** * Stair zone of the P1 coping (코핑철근.md §7.1): each segment from x to x (mm from the coping start), and its top rise * above the lowest top (mm). The stair positions are the document's; the model has no stair geometry yet. */ export const STAIR_SEGMENTS = [ { from: 2196, to: 4701, rise: 72 }, // 2구간 { from: 4701, to: 7207, rise: 144 }, // 3구간 { from: 7207, to: 9712, rise: 215 }, // 4구간 { from: 9712, to: 12000, rise: 287 }, // 5구간 (to = L in the document) ] as const; /** C14 end gap e_14 (코핑철근.md §8.1) and the inset of its ends from the side faces: c + (d_shear + d_14)/2 = 122.25. */ const C14_END_GAP = 500; export const C14_INSET = COPING_REBAR.cover + (NOMINAL_DIAMETER.H25 + NOMINAL_DIAMETER.H19) / 2; /** Development length l_d of H19 (882 for fy 400, fck 27), rounded to mm as the document does (§7.2). */ export function stepDevLength(m: Material = DEFAULT_MATERIAL): number { return Math.round(developmentLength(NOMINAL_DIAMETER.H19, fyOf(m, 'H19'), m.fck)); } export interface C14Bar { /** 1–2: upper lines (C2, C3). 3–6: lower rows from the 7th, 5th, 3rd and 1st side rows. */ line: number; /** Position along the coping (mm from its start). */ x: number; /** EL of the bar (m). */ z: number; /** false when the 연직 발 would end below the coping underside at x, so both legs are left out (§8). */ legs: boolean; } /** Underside of the coping (EL m) at x (mm from the start), T형: sloped from each end to the Level zone, flat in the middle. */ export function undersideAt(L: number, x: number, endBottom: number, levelBottom: number): number { const xL0 = L / 2 - COPING_REBAR.levelWidth / 2; return endBottom + (levelBottom - endBottom) * Math.min(Math.min(x, L - x) / xL0, 1); } /** * C14 ⊓ bars across the width (코핑철근.md §8.1). Upper lines hang from C2 and C3 at @1000 from the column centre. * Lower rows use the side rows 7, 5, 3, 1 from the bottom (k = m − 2(j − 1)): odd j at x_c ± 2000k, even j at x_c ± 1000 ± 2000k, * only where that side row exists (x_w ≤ x ≤ L − x_w). `sideRows` are the side rows (z m, x_w mm), ascending. */ export function c14Bars(L: number, topEl: number, sideRows: { z: number; xw: number }[], under: (x: number) => number): C14Bar[] { const leg = Math.round(hookLength('main90', NOMINAL_DIAMETER.H19)) / 1000; // 296 mm, EL m const xc = L / 2; const lo = COPING_REBAR.cover + C14_END_GAP; const hi = L - lo; const out: C14Bar[] = []; const dUpper = (NOMINAL_DIAMETER.H29 + NOMINAL_DIAMETER.H19) / 2000; // m, over C2 / C3 (H29) for (const k of [2, 3]) { const z = topEl - 0.127 - (k - 1) * 0.057 + dUpper; for (let q = -10; q <= 10; q++) { const x = xc + q * 1000; if (x >= lo && x <= hi) out.push({ line: k - 1, x, z, legs: z - leg >= under(x) }); } } const dLower = (NOMINAL_DIAMETER.H25 + NOMINAL_DIAMETER.H19) / 2000; // m, over a H25 side row for (let j = 1; 7 - 2 * (j - 1) >= 1; j++) { const row = sideRows[7 - 2 * (j - 1) - 1]; const a = Math.max(lo, row.xw); const b = Math.min(hi, L - row.xw); for (let q = -10; q <= 10; q++) { const x = xc + (j % 2 === 1 ? 0 : 1000) + 2000 * q; if (x >= a && x <= b) out.push({ line: 2 + j, x, z: row.z + dLower, legs: row.z + dLower - leg >= under(x) }); } } return out; } /** C14 length (mm): the straight run between the inset ends plus two 15.5·d_b legs (§8.2). 3447.5 in the document. */ export function c14Length(B: number): number { return B - 2 * C14_INSET + 2 * Math.round(hookLength('main90', NOMINAL_DIAMETER.H19)); // legs rounded to mm (296) } export interface StepBar { /** Position across the width, mm from the centre line. */ n: number; /** Start and end along the coping (mm). */ x0: number; x1: number; /** EL of the horizontal run (m). */ z: number; /** Legs: C15 hangs at both ends, C16 only at its start. */ legs: 'both' | 'start'; /** Leg length l_d (mm). */ leg: number; /** Developed length (mm): the horizontal run plus the leg(s) at its ends. */ length: number; } /** * C15 (거꾸로 된 U, on the highest segment) and C16 (L형, on segments 3 and 4), 11 bars each across the width (코핑철근.md §7). * Across: 137 from the cover line, then @250 (피복선 기준). Along: from each segment start + c to the next end − c. */ export function stairBars(L: number, B: number, topEl: number, m: Material = DEFAULT_MATERIAL): { c15: StepBar[]; c16: StepBar[] } { const ld = stepDevLength(m); const c = COPING_REBAR.cover; const ns = Array.from({ length: 11 }, (_, k) => -B / 2 + c + 137 + 250 * k); const seg = STAIR_SEGMENTS; const top = (i: number) => topEl + seg[i].rise / 1000; const c15Z = top(3) - c / 1000; const c16Z = (i: number) => top(i) - c / 1000 - NOMINAL_DIAMETER.H19 / 1000; // (d_C15 + d_C16)/2 = 19.1 const x0c15 = seg[3].from + c; const x1c15 = L - c; const c15: StepBar[] = ns.map((n) => ({ n, x0: x0c15, x1: x1c15, z: c15Z, legs: 'both', leg: ld, length: x1c15 - x0c15 + 2 * ld })); const c16: StepBar[] = [1, 2].flatMap((i) => { const x0 = seg[i].from + c; const x1 = seg[i].to - c + ld; // the horizontal run runs on past the segment end by l_d return ns.map((n): StepBar => ({ n, x0, x1, z: c16Z(i), legs: 'start', leg: ld, length: x1 - x0 + ld })); }); return { c15, c16 }; } /** * 삿갓 (upper cap) of C9–C11 (코핑철근.md §4.3). l_cap is open (Q-K5); recommended here: l_cap = h − c (top cover 100), * so that l_cap stays below h (RB-K04). Cap length = 1740 + 2·l_cap. */ export const CAP = { flat: 1740, cover: 100 } as const; export function capLength(x: number, coping: number): number { const h = shearUHeight(x, coping); const lCap = h - CAP.cover; return CAP.flat + 2 * lCap; } /** Pieces per position: 3 closed stirrups × (U + cap) = 6. Total 270 for C9 + C10 + C11 (document). */ export const PIECES_PER_POSITION = 6; /** * 전단철근 구간 표 (시안 ③ · 코핑): the segments of C9 / C10 / C11 along the coping, from the bar positions. * One column gives the three T형 segments; several columns add the segments between them (추천안, Q-K2). */ export function shearSegments(coping: number, columns: number[] = [coping / 2]): { segment: string; from: number; to: number; spacing: number; count: number; bar: 'C9' | 'C10' | 'C11' | 'C9·C11' }[] { const b = shearBarsMulti(coping, columns); const seg = (name: string, xs: number[], bar: 'C9' | 'C10' | 'C11' | 'C9·C11', spacing: number) => ({ segment: name, from: Math.min(...xs), to: Math.max(...xs), spacing, count: xs.length, bar, }); const out = [seg('좌 경사', b.c9, 'C9', COPING_REBAR.spacingS)]; const groups = columns.length > 1 ? columns.length : 1; for (let k = 0; k < groups; k++) { const xc = columns[k] ?? coping / 2; out.push(seg(`Level ${k + 1}`, b.c10.filter((x) => Math.abs(x - xc) <= (COPING_REBAR.levelWidth / 2 + COPING_REBAR.spacingL * 4)).slice(0, 2 * 4 + 1), 'C10', COPING_REBAR.spacingL)); } for (let k = 0; k + 1 < groups; k++) { const between = b.between.filter((x) => x > columns[k] && x < columns[k + 1]); if (between.length) out.push(seg(`기둥 사이 ${k + 1}`, between, 'C9·C11', COPING_REBAR.spacingS)); } out.push(seg('우 경사', b.c11, 'C11', COPING_REBAR.spacingS)); return out; } /** * Shear bars for a multi-column coping (코핑철근.md §9, 다주형 확장 원칙). * C10: each column's centre, @300 out to the first position beyond its Level edge. * C9 / C11: outward from the outermost C10 on each end, as for one column. * Between columns: @250 in from both C10 groups, the remaining length shared evenly (추천안, Q-K2). * With one column this is the same as shearBars. */ export function shearBarsMulti(coping: number, columns: number[]): ShearBars & { between: number[] } { if (columns.length <= 1) { const one = shearBars(coping, columns[0] ?? coping / 2); return { ...one, between: [] }; } const { spacingL, spacingS, levelWidth } = COPING_REBAR; const kMax = Math.ceil(levelWidth / 2 / spacingL); // k_max = ceil((W_L/2)/s_L), spec §3.1 const groups = columns.map((xc) => Array.from({ length: 2 * kMax + 1 }, (_, i) => Math.round(xc + (i - kMax) * spacingL)), ); for (let i = 1; i < groups.length; i++) { if (groups[i][0] <= groups[i - 1][groups[i - 1].length - 1]) { throw new Error('기둥 간격이 좁아 C10 구간이 겹칩니다. 다주형 코핑 철근을 만들 수 없습니다.'); } } const c10 = groups.flat(); const left = groups[0][0]; const right = groups[groups.length - 1][groups[groups.length - 1].length - 1]; const n9 = Math.floor((left - COPING_END_GAP) / spacingS); if (n9 < 0) throw new Error('RB-K01: C10 바깥 구간이 끝 여유보다 짧아 C9를 놓을 수 없습니다.'); const c9 = Array.from({ length: n9 }, (_, j) => Math.round(left - (n9 - j) * spacingS)); const c11 = Array.from({ length: n9 }, (_, j) => Math.round(right + (j + 1) * spacingS)); const between: number[] = []; for (let i = 1; i < groups.length; i++) { const a = groups[i - 1][groups[i - 1].length - 1]; const b = groups[i][0]; const gap = b - a; const pitches = Math.floor(gap / spacingS); for (let j = 1; j < pitches; j++) between.push(Math.round(a + (gap * j) / pitches)); } const endGap = Math.min(left - c9[0], coping - c11[c11.length - 1]); return { c10, c9, c11, between, endGap }; }