From 5cc166c2e1bd4b61f2f454d823356fc3af47a35f Mon Sep 17 00:00:00 2001 From: ypshim Date: Thu, 22 Oct 2026 11:49:16 +0900 Subject: [PATCH] =?UTF-8?q?feat(core):=20RB-C07=20=E2=80=94=20delete=20P4?= =?UTF-8?q?=20ties=20that=20interfere=20with=20the=20footing=20top=20bars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - A P4 is deleted when |z_P4 − z_bar| < (d_P4 + d_bar)/2 against B5 and B1 (the 확대기초 top layer, from the 상면). B11/B12 are not checked (Q-C9 무시). - The P6 crossties start from the first kept P4. - Tests: the document's example keeps all 8; B5 moved to −70 deletes the P4 at −60; a P4 just outside the limit is kept. Co-Authored-By: Claude Haiku 5.5 --- src/core/rebar/column.ts | 40 ++++++++++++++++++++++++++++++++++++-- tests/rebar-column.test.ts | 23 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/core/rebar/column.ts b/src/core/rebar/column.ts index e503dfd..b7b3d25 100644 --- a/src/core/rebar/column.ts +++ b/src/core/rebar/column.ts @@ -1,4 +1,5 @@ import { developmentLength, hookLength, NOMINAL_DIAMETER, spliceLength } from './common'; +import { layers } from './footing'; /** * 기둥 철근 C4 (plan C4). Source: RAG/기둥철근 (1).md, 2026-10-22 최종 정리. Type-1 원형 중실 기둥. @@ -52,6 +53,38 @@ export const COLUMN_P1: ColumnInput = { 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; @@ -83,9 +116,11 @@ export function columnRebar(x: ColumnInput = COLUMN_P1) { 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 p4 = Array.from({ length: m4 }, (_, j) => z5last - (j + 1) * 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 P4 + // 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); @@ -127,5 +162,6 @@ export function columnRebar(x: ColumnInput = COLUMN_P1) { p3Count: p3.length, p5Count: p5.length, p4Count: p4.length, + p4Deleted: p4Check.deleted, }; } diff --git a/tests/rebar-column.test.ts b/tests/rebar-column.test.ts index cd1a553..cd607cf 100644 --- a/tests/rebar-column.test.ts +++ b/tests/rebar-column.test.ts @@ -61,3 +61,26 @@ describe('기둥 철근 C4 (기둥철근.md, plan C4)', () => { expect(c.p6Length).toBeCloseTo(2071, 0); }); }); + +import { deleteInterferingP4, footingTopBars } from '../src/core/rebar/column'; + +describe('RB-C07 P4 삭제 규칙 (기둥철근.md §6.2)', () => { + it('the example: no P4 interferes with the top bars (B5 −100, B1 −127): 8 kept, 0 deleted', () => { + expect(c.p4Count).toBe(8); + expect(c.p4Deleted).toEqual([]); + const r = deleteInterferingP4([-60, -180], footingTopBars(2000)); + expect(r.deleted).toEqual([]); + }); + + it('hypothetical: B5 moved to −70 deletes the P4 at −60 (|−60 + 70| = 10 < 23.85)', () => { + const bars = [{ name: 'B5', z: -70, d: 28.6 }]; + const r = deleteInterferingP4([-60, -180, -300], bars); + expect(r.deleted).toEqual([{ z: -60, bar: 'B5' }]); + expect(r.kept).toEqual([-180, -300]); + }); + + it('a P4 just outside the limit is kept: |−60 + 84| = 24 ≥ 23.85', () => { + const r = deleteInterferingP4([-60], [{ name: 'B5', z: -84, d: 28.6 }]); + expect(r.kept).toEqual([-60]); + }); +});