diff --git a/src/core/rebar/footing.ts b/src/core/rebar/footing.ts index d8140ee..eb3066e 100644 --- a/src/core/rebar/footing.ts +++ b/src/core/rebar/footing.ts @@ -37,7 +37,7 @@ export interface GridResult { } /** Grid along one side of the footing (§3.1). side = W for B1–B4, L for B5–B8. */ -export function grid(side: number, s = FOOTING_DEFAULTS.s, cSide = FOOTING_DEFAULTS.cSide, eMin = FOOTING_DEFAULTS.eMin): GridResult { +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; @@ -165,3 +165,30 @@ export function shearB9(L: number, W: number, H: number): { rows: number; perRow const lh = hookLength('stirrup135', d('H16')); return { rows: rowIndices.length, perRow, count, length: lv + 2 * lh }; } + +/** 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(): { B11: number[]; B12: number[] } { + const { cBottom } = FOOTING_DEFAULTS; + const sV = FOOTING_DEFAULTS.sV; + const n = FOOTING_DEFAULTS.n; + const js = [n, 2 * n, 3 * n]; + const B11 = js.map((j) => cBottom + j * sV); + const B12 = B11.map((z) => z + NOMINAL_DIAMETER.S19); + return { B11, B12 }; +} diff --git a/tests/rebar-footing.test.ts b/tests/rebar-footing.test.ts index 984e8a5..3f99c26 100644 --- a/tests/rebar-footing.test.ts +++ b/tests/rebar-footing.test.ts @@ -76,3 +76,47 @@ describe('확대기초 철근 (확대기초철근 (1).md, plan C5)', () => { expect(b9.length).toBeCloseTo(2128, 0); }); }); + +import { assemblyHeights, legOffsets } from '../src/core/rebar/footing'; + +describe('확대기초 배근도 재현 (확대기초철근 (1).md §13)', () => { + it('leg offsets: B1 +25.4, B3 +22.25, B5 +28.6, B7 +23.85', () => { + const o = legOffsets(); + expect(o.B1).toBeCloseTo(25.4, 6); + expect(o.B3).toBeCloseTo(22.25, 6); + expect(o.B5).toBeCloseTo(28.6, 6); + expect(o.B7).toBeCloseTo(23.85, 6); + }); + + it('B11 at 550 / 950 / 1350 and B12 at 569.1 / 969.1 / 1369.1', () => { + const h = assemblyHeights(); + expect(h.B11).toEqual([550, 950, 1350]); + expect(h.B12.map((z) => Math.round(z * 10) / 10)).toEqual([569.1, 969.1, 1369.1]); + }); + + it('배근도 재현 W 7800 → B4 30, B2 29 (odd count split)', () => { + const bars = bottomBars(10400, 7800, 2000); + expect(bars.find((b) => b.name === 'B4')!.count).toBe(30); + expect(bars.find((b) => b.name === 'B2')!.count).toBe(29); + }); + + it('배근도 재현 L 9350 → B8 36, B6 35', () => { + const bars = crossBars(9350, 8150, 2000); + expect(bars.find((b) => b.name === 'B8')!.count).toBe(36); + expect(bars.find((b) => b.name === 'B6')!.count).toBe(35); + }); + + it('variables: W 8000 with s 150 → 50 grid steps, e 150, B4 26 / B2 25; W 12000 → 92 steps, e 150', () => { + const g = grid(8000, 150); + expect(g.n).toBe(50); + expect(g.e).toBeCloseTo(150, 6); + expect(grid(12000).n).toBe(92); + expect(grid(12000).e).toBeCloseTo(150, 6); + }); + + it('splice cases: L 8700 → B2 11946 (no splice); L 12400 → B4 12200; W 8800 → B6 12046', () => { + expect(bottomBars(8700, 8150, 2000).find((b) => b.name === 'B2')!.length).toBeCloseTo(11946, 0); + expect(bottomBars(12400, 8150, 2000).find((b) => b.name === 'B4')!.length).toBeCloseTo(12200, 0); + expect(crossBars(10400, 8800, 2000).find((b) => b.name === 'B6')!.length).toBeCloseTo(12046, 0); + }); +});