From ddd6d55a72c49b9b1f685661d1be92fba5809a1e Mon Sep 17 00:00:00 2001 From: ypshim Date: Mon, 26 Oct 2026 17:10:31 +0900 Subject: [PATCH] =?UTF-8?q?feat(rebar):=20=EA=B2=A9=EC=9E=90=20=EC=8B=A4?= =?UTF-8?q?=EA=B0=84=EA=B2=A9=EC=9D=84=20=EA=B5=90=EA=B0=81=EC=B6=95(B1?= =?UTF-8?q?=E2=80=93B4=C2=B7B9)=EA=B3=BC=20=EA=B5=90=EA=B0=81=EC=B6=95=20?= =?UTF-8?q?=EC=A7=81=EA=B0=81(B5=E2=80=93B8=C2=B7B11=C2=B7B12)=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=82=98=EB=88=A0=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - footing-settings: s → sAxis(교각축) · sPerp(교각축 직각), 범위는 동일 (100–150) - grid·gridPositions가 방향 키를 받음. B1–B4 grid는 sAxis, B5–B8 grid는 sPerp, B9 행·열은 sAxis, B11·B12는 sPerp - 철근표 간격·실간격과 근거가 방향별 값을 씀 (B12는 교각축 직각 값, 승인 2026-10-26) - 3D 확대기초 그림의 위치도 방향별 값을 씀 - 패널 입력 두 칸으로 분리 Co-Authored-By: Claude Haiku 5.5 --- src/core/rebar/footing-settings.ts | 15 +++++++++++---- src/core/rebar/footing.ts | 21 +++++++++++---------- src/core/rebar/summary.ts | 14 +++++++++----- src/main.ts | 9 +++++---- src/view/rebar3d.ts | 10 +++++----- tests/footing-rebar-settings.test.ts | 20 +++++++++++++++++--- tests/rebar-footing.test.ts | 2 +- 7 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/core/rebar/footing-settings.ts b/src/core/rebar/footing-settings.ts index 8033277..d6bfe52 100644 --- a/src/core/rebar/footing-settings.ts +++ b/src/core/rebar/footing-settings.ts @@ -3,6 +3,9 @@ * Defaults are the 확대기초철근 (1).md values; the ranges are proposed and pending the design-standard check. * Fixed values (k = 2, Δ₂ = 100, 전단 4s · 엇갈림 2s, cycle 교대 배치) are not inputs. */ +/** Which grid spacing a helper uses: the 교각축 (sAxis) or the 교각축 직각 (sPerp). */ +export type FootingGridKey = 'sAxis' | 'sPerp'; + export interface FootingRebarSettings { /** 상면 피복 (최외측 철근 중심까지, mm). */ cTop: number; @@ -12,8 +15,10 @@ export interface FootingRebarSettings { cBottom: number; /** 첫 철근 최소 여유 (측면 피복 다음, mm). */ eMin: number; - /** 격자 실간격 s (mm). */ - s: number; + /** 격자 실간격 · 교각축 (mm): B1–B4 along W, and the B9 grid (승인 2026-10-26). */ + sAxis: number; + /** 격자 실간격 · 교각축 직각 (mm): B5–B8 along L, and B11·B12 (승인 2026-10-26). */ + sPerp: number; /** B10 높이 간격 s_v (mm). */ sV: number; /** B5 아래 최소 여유 e_top (mm). */ @@ -27,7 +32,8 @@ export const DEFAULT_FOOTING_REBAR: FootingRebarSettings = { cSide: 100, cBottom: 150, eMin: 150, - s: 125, + sAxis: 125, + sPerp: 125, sV: 200, eTop: 150, n: 2, @@ -39,7 +45,8 @@ export const FOOTING_REBAR_RANGES: Record a + b, 0); const b12 = perLevel(nW, 0, 2).reduce((a, b) => a + b, 0); @@ -159,9 +160,9 @@ export function assemblyBars(L: number, W: number, H: number, f: FootingRebarSet } /** Plane positions of the grid along one side, mm from the footing centre: x_i = −side/2 + c_s + e + i·s (§3.1). */ -export function gridPositions(side: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): number[] { - const g = grid(side, f); - return Array.from({ length: g.positions }, (_, i) => -side / 2 + f.cSide + g.e + i * f.s); +export function gridPositions(side: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR, key: FootingGridKey = 'sAxis'): number[] { + const g = grid(side, f, key); + return Array.from({ length: g.positions }, (_, i) => -side / 2 + f.cSide + g.e + i * f[key]); } /** B9 bar of the rule (§9.1, Q-B18): a vertical bar at (x, n), mm from the footing centre. */ diff --git a/src/core/rebar/summary.ts b/src/core/rebar/summary.ts index 89f802a..91b0f94 100644 --- a/src/core/rebar/summary.ts +++ b/src/core/rebar/summary.ts @@ -164,12 +164,16 @@ export function linkedSpacing(name: string, model: PierModel): Pick -Wf / 2 + mm(fs.cSide + gW.e + j * fs.s); - const posL = (i: number) => -Lf / 2 + mm(fs.cSide + gL.e + i * fs.s); + const gL = grid(Lf * 1000, fs, 'sPerp'); + const posW = (j: number) => -Wf / 2 + mm(fs.cSide + gW.e + j * fs.sAxis); + const posL = (i: number) => -Lf / 2 + mm(fs.cSide + gL.e + i * fs.sPerp); const sA = -Lf / 2 + 0.1; const sB = Lf / 2 - 0.1; const nA = -Wf / 2 + 0.1; @@ -470,8 +470,8 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL, } } // B11 (across W, spread along L) and B12 (along L, spread across W) at the assembly levels, 4s apart, 2s stagger per level. - const xsL = gridPositions(Lf * 1000, fs); - const ysW = gridPositions(Wf * 1000, fs); + const xsL = gridPositions(Lf * 1000, fs, 'sPerp'); + const ysW = gridPositions(Wf * 1000, fs, 'sPerp'); const dAsm = assemblyEndOffset(); const nAsm = Wf / 2 - mm(d1 + dAsm); const sAsm = Lf / 2 - mm(d2 + dAsm); diff --git a/tests/footing-rebar-settings.test.ts b/tests/footing-rebar-settings.test.ts index 0063df9..9335b30 100644 --- a/tests/footing-rebar-settings.test.ts +++ b/tests/footing-rebar-settings.test.ts @@ -20,8 +20,8 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초 }); it('the grid spacing s reaches the grid positions and the rebar table', () => { - const m = buildPier({ ...loadP1(), footingRebar: { s: 150 } }); - expect(m.footingRebar.s).toBe(150); + const m = buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } }); + expect(m.footingRebar.sAxis).toBe(150); const xs = gridPositions(8000, m.footingRebar); expect(xs[1] - xs[0]).toBeCloseTo(150, 6); const b4 = rebarSummary(m).rows.find((r) => r.name === 'B4')!; @@ -36,7 +36,7 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초 const b10 = base.find((r) => r.name === 'B10-1')!; expect([b10.spacing, b10.basis]).toEqual(['200', 'default']); - const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { s: 150 } })).rows; + const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } })).rows; const b2s = rows.find((r) => r.name === 'B2')!; expect([b2s.spacing, b2s.actual, b2s.basis]).toEqual(['300', '150', 'input']); const b1s = rows.find((r) => r.name === 'B1')!; @@ -44,6 +44,20 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초 expect(rows.find((r) => r.name === 'B10-1')!.basis).toBe('default'); }); + it('교각축 직각 값은 B5–B8·B11·B12에만, 교각축 값은 B1–B4·B9에 적용 (승인 2026-10-26)', () => { + const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sPerp: 150 } })).rows; + const b5 = rows.find((r) => r.name === 'B5')!; + expect([b5.spacing, b5.basis]).toEqual(['300', 'input']); + const b12 = rows.find((r) => r.name === 'B12')!; + expect([b12.spacing, b12.basis]).toEqual(['600 (4s)', 'input']); + expect(rows.find((r) => r.name === 'B1')!.basis).toBe('default'); + expect(rows.find((r) => r.name === 'B9')!.basis).toBe('default'); + + const rowsAxis = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } })).rows; + expect(rowsAxis.find((r) => r.name === 'B9')!.spacing).toBe('600 (4s)'); + expect(rowsAxis.find((r) => r.name === 'B5')!.basis).toBe('default'); + }); + it('the 조립 단 배수 n reaches the assembly levels', () => { const H = 2000; const base = assemblyJs(H, DEFAULT_FOOTING_REBAR); diff --git a/tests/rebar-footing.test.ts b/tests/rebar-footing.test.ts index 4e49af1..049d9c2 100644 --- a/tests/rebar-footing.test.ts +++ b/tests/rebar-footing.test.ts @@ -120,7 +120,7 @@ describe('확대기초 배근도 재현 (확대기초철근 (1).md §13)', () => }); 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, { ...DEFAULT_FOOTING_REBAR, s: 150 }); + const g = grid(8000, { ...DEFAULT_FOOTING_REBAR, sAxis: 150 }); expect(g.n).toBe(50); expect(g.e).toBeCloseTo(150, 6); expect(grid(12000).n).toBe(92);