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:
ypshimandClaude Haiku 5.5 committed 2026-10-26 17:10:31 +09:00
1 parent c40c58d27f
commit ddd6d55a72
7 files changed
+59 -32

No files matched your search

+11 -4
View File
@@ -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
View File
@@ -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. */
+9 -5
View File
@@ -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) };