diff --git a/src/core/rebar/footing.ts b/src/core/rebar/footing.ts index 8528c9e..698ed08 100644 --- a/src/core/rebar/footing.ts +++ b/src/core/rebar/footing.ts @@ -139,18 +139,33 @@ export function assemblyEndOffset(): number { /** B11 (along W, between the B10-2 levels) and B12 (along L), 4s apart with a 2s stagger per level (§8). */ /** - * Grid indices of the assembly bars (B11 or B12) on one level, centred in the grid (승인 2026-10-26, 추천안). - * Bars every k steps; the level's 2-step stagger alternates (even levels start at e0, odd levels at e0 + 2), and e0 centres - * the even group so the left and right gaps differ by at most one step. n = positions − 1; idx = level index (0, 1, …). + * 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 cEven = Math.floor(n / k) + 1; - const cOdd = Math.floor((n - 2) / k) + 1; - const e0 = Math.max(0, Math.floor((n - (cEven - 1) * k - 2) / 2)); - const even = idx % 2 === 0; - const start = even ? e0 : e0 + 2; - const count = even ? cEven : cOdd; - return Array.from({ length: count }, (_, m) => start + m * k).filter((i) => i <= n); + 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[] { diff --git a/tests/footing-section-svg.test.ts b/tests/footing-section-svg.test.ts index 05315e3..8e5dd11 100644 --- a/tests/footing-section-svg.test.ts +++ b/tests/footing-section-svg.test.ts @@ -49,4 +49,13 @@ describe('확대기초 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추 } expect(assemblyGridIndices(61, 8, 1)[0] - assemblyGridIndices(61, 8, 0)[0]).toBe(2); }); + + it('조립 철근(B11·B12)은 짝수 격자에 놓여 B9(홀수 격자)와 겹치지 않음 (확대기초철근 (1).md §9.1)', () => { + const levels = assemblyJs(H, fs).length; + for (const [n, k] of [[79, 8], [61, 8]] as const) { + for (let idx = 0; idx < levels; idx++) { + for (const i of assemblyGridIndices(n, k, idx)) expect(i % 2).toBe(0); + } + } + }); });