feat(rebar): 격자 실간격을 교각축(B1–B4·B9)과 교각축 직각(B5–B8·B11·B12)으로 나눠 입력
- 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
c40c58d27f
commit
ddd6d55a72
7 files changed
+59
-32
No files matched your search
@@ -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<keyof FootingRebarSettings, [number, n
|
||||
cSide: [80, 150],
|
||||
cBottom: [100, 200],
|
||||
eMin: [100, 200],
|
||||
s: [100, 150],
|
||||
sAxis: [100, 150],
|
||||
sPerp: [100, 150],
|
||||
sV: [150, 250],
|
||||
eTop: [100, 200],
|
||||
n: [1, 3],
|
||||
|
||||
+11
-10
@@ -1,5 +1,5 @@
|
||||
import { hookLength, NOMINAL_DIAMETER, type BarSize } from './common';
|
||||
import { DEFAULT_FOOTING_REBAR, type FootingRebarSettings } from './footing-settings';
|
||||
import { DEFAULT_FOOTING_REBAR, type FootingGridKey, type FootingRebarSettings } from './footing-settings';
|
||||
|
||||
/**
|
||||
* 확대기초 철근 B1–B8, B10–B12 (plan C5). Source: RAG/확대기초철근 (1).md, 2026-10-22 최종 정리.
|
||||
@@ -23,10 +23,11 @@ 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, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): GridResult {
|
||||
export function grid(side: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR, key: FootingGridKey = 'sAxis'): GridResult {
|
||||
const s = f[key];
|
||||
const A = side - 2 * f.cSide;
|
||||
const n = Math.floor((A - 2 * f.eMin) / f.s);
|
||||
const G = n * f.s;
|
||||
const n = Math.floor((A - 2 * f.eMin) / s);
|
||||
const G = n * s;
|
||||
const e = (A - G) / 2;
|
||||
return { n, G, e, positions: n + 1 };
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export function bottomBars(L: number, W: number, H: number, f: FootingRebarSetti
|
||||
|
||||
/** B5–B8 (§6): the grid along L, with lengths from W. */
|
||||
export function crossBars(L: number, W: number, H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
|
||||
const g = grid(L, f);
|
||||
const g = grid(L, f, 'sPerp');
|
||||
const n = g.positions;
|
||||
const N8 = Math.ceil(n / 2); // B8 at even i
|
||||
const N6 = Math.floor(n / 2); // B6, B5, B7 at odd i
|
||||
@@ -144,8 +145,8 @@ export function assemblyBars(L: number, W: number, H: number, f: FootingRebarSet
|
||||
const i0 = idx % 2 === 0 ? startOdd : startEven;
|
||||
return Math.floor((n - i0) / 4) + 1;
|
||||
});
|
||||
const nW = grid(W, f).positions - 1; // 61
|
||||
const nL = grid(L, f).positions - 1; // 79
|
||||
const nW = grid(W, f, 'sPerp').positions - 1; // 61 (B12 uses the 교각축 직각 value, 승인 2026-10-26)
|
||||
const nL = grid(L, f, 'sPerp').positions - 1; // 79 (B11 along L)
|
||||
// B11 is spread along L (n = 79) and B12 along W (n = 61); each level has its own count.
|
||||
const b11 = perLevel(nL, 0, 2).reduce((a, b) => 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. */
|
||||
|
||||
@@ -164,12 +164,16 @@ export function linkedSpacing(name: string, model: PierModel): Pick<RebarRow, 's
|
||||
// B10 s_v; B11·B12 4s. The basis is 기본값 unless the grid spacing s or s_v differs from its default.
|
||||
const fs = model.footingRebar;
|
||||
const fd = DEFAULT_FOOTING_REBAR;
|
||||
if (/^B[1-8]$/.test(name)) {
|
||||
const cycle = name === 'B2' || name === 'B4' || name === 'B6' || name === 'B8';
|
||||
return { spacing: String(2 * fs.s), actual: String(cycle ? fs.s : 2 * fs.s), basis: basis(fs.s !== fd.s) };
|
||||
if (/^B[1-4]$/.test(name)) {
|
||||
const cycle = name === 'B2' || name === 'B4';
|
||||
return { spacing: String(2 * fs.sAxis), actual: String(cycle ? fs.sAxis : 2 * fs.sAxis), basis: basis(fs.sAxis !== fd.sAxis) };
|
||||
}
|
||||
if (name === 'B9') return { spacing: `${4 * fs.s} (4s)`, actual: `${2 * fs.s} (2s 엇갈림)`, basis: basis(fs.s !== fd.s) };
|
||||
if (name === 'B11' || name === 'B12') return { spacing: `${4 * fs.s} (4s)`, actual: String(4 * fs.s), basis: basis(fs.s !== fd.s) };
|
||||
if (/^B[5-8]$/.test(name)) {
|
||||
const cycle = name === 'B6' || name === 'B8';
|
||||
return { spacing: String(2 * fs.sPerp), actual: String(cycle ? fs.sPerp : 2 * fs.sPerp), basis: basis(fs.sPerp !== fd.sPerp) };
|
||||
}
|
||||
if (name === 'B9') return { spacing: `${4 * fs.sAxis} (4s)`, actual: `${2 * fs.sAxis} (2s 엇갈림)`, basis: basis(fs.sAxis !== fd.sAxis) };
|
||||
if (name === 'B11' || name === 'B12') return { spacing: `${4 * fs.sPerp} (4s)`, actual: String(4 * fs.sPerp), basis: basis(fs.sPerp !== fd.sPerp) };
|
||||
if (name === 'B10-1' || name === 'B10-2') return { spacing: String(fs.sV), actual: String(fs.sV), basis: basis(fs.sV !== fd.sV) };
|
||||
if (name === 'S1') return { spacing: String(b.spacingS1), actual: String(b.spacingS1), basis: basis(b.spacingS1 !== bd.spacingS1) };
|
||||
if (name === 'S2') return { spacing: String(b.spacingS2), actual: String(b.spacingS2), basis: basis(b.spacingS2 !== bd.spacingS2) };
|
||||
|
||||
+5
-4
@@ -1298,7 +1298,8 @@ const FOOTING_REBAR_FIELDS: { key: keyof FootingRebarSettings; label: string }[]
|
||||
{ key: 'cSide', label: '측면 피복 (mm)' },
|
||||
{ key: 'cBottom', label: '하면 피복 (mm)' },
|
||||
{ key: 'eMin', label: '첫 철근 최소 여유 (mm)' },
|
||||
{ key: 's', label: '격자 실간격 s (mm)' },
|
||||
{ key: 'sAxis', label: '격자 실간격 · 교각축 B1–B4 · B9 (mm)' },
|
||||
{ key: 'sPerp', label: '격자 실간격 · 교각축 직각 B5–B8 · B11·B12 (mm)' },
|
||||
{ key: 'sV', label: 'B10 높이 간격 s_v (mm)' },
|
||||
{ key: 'eTop', label: 'B5 아래 최소 여유 e_top (mm)' },
|
||||
{ key: 'n', label: '조립 단 배수 n (B10 간격의 n배)' },
|
||||
@@ -1340,10 +1341,10 @@ function renderFootingSettings(box: HTMLDivElement, pier: Pier): void {
|
||||
const fixed = el('div', undefined, 'muted small');
|
||||
const H = pier.model.footings[0] ? (pier.model.footings[0].top - pier.model.footings[0].bottom) * 1000 : undefined;
|
||||
fixed.textContent = [
|
||||
`상면·2단 간격 = 2s (고정) ${2 * fs.s} mm`,
|
||||
`B1–B4 간격 2·s (고정) ${2 * fs.sAxis} mm · B5–B8 간격 2·s (고정) ${2 * fs.sPerp} mm`,
|
||||
`하면 1·2단 간격 Δ₂ (고정) 100 mm`,
|
||||
`전단 B9 간격 4s · 행 엇갈림 2s (고정) ${4 * fs.s} · ${2 * fs.s} mm`,
|
||||
`조립 평면 간격 4s · 연직 n·s_v (자동) ${4 * fs.s} · ${fs.n * fs.sV} mm`,
|
||||
`전단 B9 간격 4s · 행 엇갈림 2s (고정) ${4 * fs.sAxis} · ${2 * fs.sAxis} mm`,
|
||||
`조립 평면 간격 4s · 연직 n·s_v (자동) ${4 * fs.sPerp} · ${fs.n * fs.sV} mm`,
|
||||
`cycle 배치: 교대 배치 (고정)`,
|
||||
H === undefined ? 'B2·B6 디리 (자동): 확대기초 없음' : `B2·B6 디리 (자동) z_B1 − z_B24 = ${legB2(H, fs)} mm`,
|
||||
].join(' · ');
|
||||
|
||||
+5
-5
@@ -385,9 +385,9 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
|
||||
const fs = m.footingRebar;
|
||||
const z = layers(Hf, fs);
|
||||
const gW = grid(Wf * 1000, fs);
|
||||
const gL = grid(Lf * 1000, fs);
|
||||
const posW = (j: number) => -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user