Revert "feat(rebar): B1–B4와 B5–B8 방향 교환 (B1–B4는 W 방향, B5–B8은 L 방향)"
이 교환은 9,350 방향이 L이라는 가정에서 적용됐으나, 단면 삽도(B5–B8 선, 교각축 직각 단면)와 재료표가 원래 배치(B1–B4 L 방향, B5–B8 W 방향)와 맞는 것으로 확인되어 되돌림. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a026d52799
commit
99db6a1f52
12 files changed
+167
-160
No files matched your search
@@ -3,8 +3,8 @@
|
||||
* 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: along L (sL) or along W (sW). */
|
||||
export type FootingGridKey = 'sL' | 'sW';
|
||||
/** Which grid spacing a helper uses: the 교각축 (sAxis) or the 교각축 직각 (sPerp). */
|
||||
export type FootingGridKey = 'sAxis' | 'sPerp';
|
||||
|
||||
export interface FootingRebarSettings {
|
||||
/** 상면 피복 (최외측 철근 중심까지, mm). */
|
||||
@@ -15,10 +15,10 @@ export interface FootingRebarSettings {
|
||||
cBottom: number;
|
||||
/** 첫 철근 최소 여유 (측면 피복 다음, mm). */
|
||||
eMin: number;
|
||||
/** 격자 실간격 · L 방향 (mm): B1–B4 spacing, the B9 grid and B11 (승인 2026-10-26, 방향 교환). */
|
||||
sL: number;
|
||||
/** 격자 실간격 · W 방향 (mm): B5–B8 spacing and B12 (승인 2026-10-26, 방향 교환). */
|
||||
sW: 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). */
|
||||
@@ -36,8 +36,8 @@ export const DEFAULT_FOOTING_REBAR: FootingRebarSettings = {
|
||||
cSide: 100,
|
||||
cBottom: 150,
|
||||
eMin: 150,
|
||||
sL: 125,
|
||||
sW: 125,
|
||||
sAxis: 125,
|
||||
sPerp: 125,
|
||||
sV: 200,
|
||||
eTop: 150,
|
||||
n: 2,
|
||||
@@ -51,8 +51,8 @@ export const FOOTING_REBAR_RANGES: Record<keyof FootingRebarSettings, [number, n
|
||||
cSide: [80, 150],
|
||||
cBottom: [100, 200],
|
||||
eMin: [100, 200],
|
||||
sL: [100, 150],
|
||||
sW: [100, 150],
|
||||
sAxis: [100, 150],
|
||||
sPerp: [100, 150],
|
||||
sV: [150, 250],
|
||||
eTop: [100, 200],
|
||||
n: [1, 3],
|
||||
|
||||
+21
-22
@@ -23,7 +23,7 @@ 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, key: FootingGridKey = 'sL'): 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) / s);
|
||||
@@ -70,13 +70,13 @@ export interface FootingBar {
|
||||
length: number;
|
||||
}
|
||||
|
||||
/** B1–B4 (승인 2026-10-26, 방향 교환): run along W, spaced along L (grid L, sL), with lengths from W. */
|
||||
/** B1–B4 (§5): the grid along W, with lengths from L. */
|
||||
export function bottomBars(L: number, W: number, H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
|
||||
const g = grid(L, f, 'sL');
|
||||
const g = grid(W, f);
|
||||
const n = g.positions;
|
||||
const N4 = Math.ceil(n / 2); // B4 at even i
|
||||
const N2 = Math.floor(n / 2); // B2 at odd i
|
||||
const l0 = W - 2 * f.cSide;
|
||||
const l0 = L - 2 * f.cSide;
|
||||
const leg2 = legB2(H, f);
|
||||
return [
|
||||
{ name: 'B1', size: 'S25', count: N2, length: l0 + 2 * legLength('S25') },
|
||||
@@ -86,13 +86,13 @@ export function bottomBars(L: number, W: number, H: number, f: FootingRebarSetti
|
||||
];
|
||||
}
|
||||
|
||||
/** B5–B8 (승인 2026-10-26, 방향 교환): run along L, spaced along W (grid W, sW), with lengths from L. */
|
||||
/** 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(W, f, 'sW');
|
||||
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
|
||||
const l0 = L - 2 * f.cSide;
|
||||
const l0 = W - 2 * f.cSide;
|
||||
const z = layers(H, f);
|
||||
const leg6 = z.B5 - z.B68;
|
||||
return [
|
||||
@@ -172,9 +172,9 @@ export function assemblyGridIndices(n: number, k: number, idx: number): number[]
|
||||
|
||||
export function assemblyBars(L: number, W: number, H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): FootingBar[] {
|
||||
const levelsJ = assemblyJs(H, f);
|
||||
// B11 is spread along L (grid L, sL) and B12 along W (grid W, sW), per 확대기초철근 (1).md §8 (after the 방향 교환).
|
||||
const nW = grid(W, f, 'sW').positions - 1;
|
||||
const nL = grid(L, f, 'sL').positions - 1;
|
||||
// B11 is spread along L (교각축 직각 grid) and B12 along W (교각축 grid), per 확대기초철근 (1).md §8.
|
||||
const nW = grid(W, f, 'sAxis').positions - 1;
|
||||
const nL = grid(L, f, 'sPerp').positions - 1;
|
||||
const b11 = levelsJ.reduce((a, _, idx) => a + assemblyGridIndices(nL, f.kB11, idx).length, 0);
|
||||
const b12 = levelsJ.reduce((a, _, idx) => a + assemblyGridIndices(nW, f.kB12, idx).length, 0);
|
||||
const hook = Math.round(hookLength('main90', d('S16'))); // 246 mm, rounded as in the document (§8.3)
|
||||
@@ -187,7 +187,7 @@ 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, key: FootingGridKey = 'sL'): number[] {
|
||||
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]);
|
||||
}
|
||||
@@ -204,9 +204,8 @@ export interface B9Bar {
|
||||
* than the column radius plus half a bar to a column axis (Q-B20, applied: the 기둥 구역 is removed).
|
||||
*/
|
||||
export function b9Bars(L: number, W: number, columnX: number[], columnD: number = 2000, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): { rows: number; perRow: number[]; bars: B9Bar[] } {
|
||||
// B9: columns along L use the L grid (sL); rows along W use the W grid (sW) (방향 교환 후 물리적 격자, 승인 2026-10-26).
|
||||
const xs = gridPositions(L, f, 'sL');
|
||||
const ns = gridPositions(W, f, 'sW');
|
||||
const xs = gridPositions(L, f);
|
||||
const ns = gridPositions(W, f);
|
||||
const rowIndices: number[] = [];
|
||||
for (let i = 1; i < ns.length; i += 4) rowIndices.push(i);
|
||||
const halfBar = NOMINAL_DIAMETER.H16 / 2;
|
||||
@@ -266,8 +265,8 @@ export function assemblyHeights(H: number, f: FootingRebarSettings = DEFAULT_FOO
|
||||
* rebar table and the 3D view use, so the panel cannot drift from the calculation. H is the footing height (mm).
|
||||
*/
|
||||
export function footingRuleValues(H: number, f: FootingRebarSettings = DEFAULT_FOOTING_REBAR): {
|
||||
upper2L: number;
|
||||
upper2W: number;
|
||||
upper2sAxis: number;
|
||||
upper2sPerp: number;
|
||||
delta2: number;
|
||||
b2Leg: number;
|
||||
b6Leg: number;
|
||||
@@ -280,15 +279,15 @@ export function footingRuleValues(H: number, f: FootingRebarSettings = DEFAULT_F
|
||||
} {
|
||||
const z = layers(H, f);
|
||||
return {
|
||||
upper2L: 2 * f.sL,
|
||||
upper2W: 2 * f.sW,
|
||||
upper2sAxis: 2 * f.sAxis,
|
||||
upper2sPerp: 2 * f.sPerp,
|
||||
delta2: DELTA2,
|
||||
b2Leg: legB2(H, f),
|
||||
b6Leg: z.B5 - z.B68,
|
||||
b9Pitch: 4 * f.sL,
|
||||
b9Stagger: 2 * f.sL,
|
||||
b11Pitch: f.kB11 * f.sL,
|
||||
b12Pitch: f.kB12 * f.sW,
|
||||
b9Pitch: 4 * f.sAxis,
|
||||
b9Stagger: 2 * f.sAxis,
|
||||
b11Pitch: f.kB11 * f.sPerp,
|
||||
b12Pitch: f.kB12 * f.sAxis,
|
||||
assemblyVertical: f.n * f.sV,
|
||||
assemblyLevels: assemblyJs(H, f).length,
|
||||
};
|
||||
|
||||
@@ -166,16 +166,16 @@ export function linkedSpacing(name: string, model: PierModel): Pick<RebarRow, 's
|
||||
const fd = DEFAULT_FOOTING_REBAR;
|
||||
if (/^B[1-4]$/.test(name)) {
|
||||
const cycle = name === 'B2' || name === 'B4';
|
||||
return { spacing: String(2 * fs.sL), actual: String(cycle ? fs.sL : 2 * fs.sL), basis: basis(fs.sL !== fd.sL) };
|
||||
return { spacing: String(2 * fs.sAxis), actual: String(cycle ? fs.sAxis : 2 * fs.sAxis), basis: basis(fs.sAxis !== fd.sAxis) };
|
||||
}
|
||||
if (/^B[5-8]$/.test(name)) {
|
||||
const cycle = name === 'B6' || name === 'B8';
|
||||
return { spacing: String(2 * fs.sW), actual: String(cycle ? fs.sW : 2 * fs.sW), basis: basis(fs.sW !== fd.sW) };
|
||||
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.sL} (4s)`, actual: `${2 * fs.sL} (2s 엇갈림)`, basis: basis(fs.sL !== fd.sL) };
|
||||
if (name === 'B9') return { spacing: `${4 * fs.sAxis} (4s)`, actual: `${2 * fs.sAxis} (2s 엇갈림)`, basis: basis(fs.sAxis !== fd.sAxis) };
|
||||
// B11 along L uses the 교각축 직각 grid, B12 along W uses the 교각축 grid; each is k · s (승인 2026-10-26).
|
||||
if (name === 'B11') return { spacing: `${fs.kB11 * fs.sL} (${fs.kB11}s)`, actual: String(fs.kB11 * fs.sL), basis: basis(fs.sL !== fd.sL || fs.kB11 !== fd.kB11) };
|
||||
if (name === 'B12') return { spacing: `${fs.kB12 * fs.sW} (${fs.kB12}s)`, actual: String(fs.kB12 * fs.sW), basis: basis(fs.sW !== fd.sW || fs.kB12 !== fd.kB12) };
|
||||
if (name === 'B11') return { spacing: `${fs.kB11 * fs.sPerp} (${fs.kB11}s)`, actual: String(fs.kB11 * fs.sPerp), basis: basis(fs.sPerp !== fd.sPerp || fs.kB11 !== fd.kB11) };
|
||||
if (name === 'B12') return { spacing: `${fs.kB12 * fs.sAxis} (${fs.kB12}s)`, actual: String(fs.kB12 * fs.sAxis), basis: basis(fs.sAxis !== fd.sAxis || fs.kB12 !== fd.kB12) };
|
||||
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) };
|
||||
|
||||
+7
-7
@@ -1299,13 +1299,13 @@ const FOOTING_REBAR_FIELDS: { key: keyof FootingRebarSettings; label: string }[]
|
||||
{ key: 'cSide', label: '측면 피복 (mm)' },
|
||||
{ key: 'cBottom', label: '하면 피복 (mm)' },
|
||||
{ key: 'eMin', label: '첫 철근 최소 여유 (mm)' },
|
||||
{ key: 'sL', label: '격자 실간격 · L 방향 B1–B4 · B9 · B11 (mm)' },
|
||||
{ key: 'sW', label: '격자 실간격 · W 방향 B5–B8 · B12 (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: 'B11·B12 연직간격 배수 n (B10 간격의 n배)' },
|
||||
{ key: 'kB11', label: 'B11 수평간격 배수 k (L 방향, k·sL)' },
|
||||
{ key: 'kB12', label: 'B12 수평간격 배수 k (W 방향, k·sW)' },
|
||||
{ key: 'kB11', label: 'B11 수평간격 배수 k (L 방향, k·s교각축직각)' },
|
||||
{ key: 'kB12', label: 'B12 수평간격 배수 k (W 방향, k·s교각축)' },
|
||||
];
|
||||
|
||||
/** 적용: the values go to the draft and the model is rebuilt only when every check passes (FT01·FT02). */
|
||||
@@ -1349,11 +1349,11 @@ function renderFootingSettings(box: HTMLDivElement, pier: Pier): void {
|
||||
} else {
|
||||
const r = footingRuleValues(H, fs);
|
||||
fixed.textContent = [
|
||||
`상면·2단 간격 B1·B3 = 2·sL ${r.upper2L} mm · B5·B7 = 2·sW ${r.upper2W} mm (규칙)`,
|
||||
`상면·2단 간격 B1·B3 = 2·s교각축 ${r.upper2sAxis} mm · B5·B7 = 2·s교각축직각 ${r.upper2sPerp} mm (규칙)`,
|
||||
`하면 1·2단 간격 Δ₂ = ${r.delta2} mm (고정)`,
|
||||
`B2 디리 ${r.b2Leg} mm · B6 디리 ${r.b6Leg} mm (자동, B1·B5 높이까지)`,
|
||||
`B9 간격 4·sL ${r.b9Pitch} · 행 엇갈림 2·sL ${r.b9Stagger} mm (규칙)`,
|
||||
`B11 평면 간격 k·sL ${r.b11Pitch} · B12 평면 간격 k·sW ${r.b12Pitch} mm · 연직 n·s_v ${r.assemblyVertical} mm · 조립 단 ${r.assemblyLevels}단 (자동)`,
|
||||
`B9 간격 4·s교각축 ${r.b9Pitch} · 행 엇갈림 2·s교각축 ${r.b9Stagger} mm (규칙)`,
|
||||
`B11 평면 간격 k·s교각축직각 ${r.b11Pitch} · B12 평면 간격 k·s교각축 ${r.b12Pitch} mm · 연직 n·s_v ${r.assemblyVertical} mm · 조립 단 ${r.assemblyLevels}단 (자동)`,
|
||||
'cycle 배치: 교대 배치 (고정)',
|
||||
].join(' · ');
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DEFAULT_FOOTING_REBAR } from '../core/rebar/footing-settings';
|
||||
/**
|
||||
* 확대기초 · 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추천안 1·2단계). A cut across the footing width: horizontal = W, vertical = height
|
||||
* from the 하면 at true scale. Every mark comes from the same functions as the rebar table and the 3D view:
|
||||
* - B1–B4 orange lines (bars along W, cover to cover), B5–B8 teal points on the grid along W (방향 교환), B9 purple lines per row,
|
||||
* - B5–B8 teal lines (bars along W), B1–B4 orange points on the grid along W, B9 purple lines per row,
|
||||
* - B10-1 green points at both W faces per level, B11 grey dashed lines per assembly level, B12 grey points on its W grid.
|
||||
* Legs are not drawn; the B6 leg length is noted in the figure. The column and the piles are shown as context.
|
||||
* Each mark carries data-bar="<철근 번호>" so a bubble can highlight its bars (철근표 강조와 같은 방식).
|
||||
@@ -66,41 +66,44 @@ export function footingSectionSvg(model: PierModel): string {
|
||||
parts.push(`<rect x="${x(-W / 2).toFixed(1)}" y="${y(H).toFixed(1)}" width="${(W * kX).toFixed(1)}" height="${(H * kY).toFixed(1)}" fill="none" stroke="${COLOR.outline}" stroke-width="1.2"/>`);
|
||||
parts.push(line('base', '', x(-W / 2), x(W / 2), y(0), COLOR.outline, 1));
|
||||
|
||||
// Cover lines on the W faces (측면 피복 c_s).
|
||||
// B5–B8: teal lines along W between the covers. B5 at the top, B6 and B8 at the 하면 1단, B7 at the 하면 2단.
|
||||
const xa = x(-W / 2 + fs.cSide);
|
||||
const xb = x(W / 2 - fs.cSide);
|
||||
// B1–B4 (방향 교환, 승인 2026-10-26): run along W, so in this section they are lines from cover to cover.
|
||||
// The B2 legs turn up to the B1 height at both W ends (측면 피복선), drawn heavier.
|
||||
parts.push(line('b1', 'B1', xa, xb, y(z.B1), COLOR.b14, 1.4));
|
||||
parts.push(line('b2', 'B2', xa, xb, y(z.B24), COLOR.b14, 1.6));
|
||||
parts.push(line('b3', 'B3', xa, xb, y(z.B3), COLOR.b14, 1.2));
|
||||
parts.push(line('b4', 'B4', xa, xb, y(z.B24), COLOR.b14, 1.2));
|
||||
for (const xx of [xa, xb]) {
|
||||
parts.push(`<line class="leg" data-bar="B2" x1="${xx.toFixed(1)}" x2="${xx.toFixed(1)}" y1="${y(z.B1).toFixed(1)}" y2="${y(z.B24).toFixed(1)}" stroke="${COLOR.b14}" stroke-width="1.8"/>`);
|
||||
}
|
||||
// B6 leg note: the B6 leg runs from the 하면 1단 up to the B5 height (B6 다리 → B5 높이), shown in the right margin below.
|
||||
parts.push(line('b5', 'B5', xa, xb, y(z.B5), COLOR.b5, 1.6));
|
||||
parts.push(line('b5', 'B6', xa, xb, y(z.B68), COLOR.b5, 1.2));
|
||||
parts.push(line('b5', 'B7', xa, xb, y(z.B7), COLOR.b5, 1.2));
|
||||
parts.push(line('b5', 'B8', xa, xb, y(z.B68), COLOR.b5, 1.2));
|
||||
|
||||
// B5–B8 (방향 교환): run along L and are spaced along W (sW). In this section they are points on the W grid:
|
||||
// odd positions carry B5, B6, B7 and even positions carry B8 (same parity rule as crossBars).
|
||||
const xsW = gridPositions(W, fs, 'sW');
|
||||
const last = xsW.length - 1;
|
||||
xsW.forEach((mm, i) => {
|
||||
// B1–B4 on the grid along W (교각축, orange points): odd grid positions carry B1, B2 and B3; even carry B4 (as bottomBars).
|
||||
const xs = gridPositions(W, fs, 'sAxis');
|
||||
xs.forEach((mm, i) => {
|
||||
const xx = x(mm);
|
||||
if (i % 2 === 1) {
|
||||
parts.push(dot('b5', 'B5', xx, y(z.B5), COLOR.b5));
|
||||
parts.push(dot('b6', 'B6', xx, y(z.B68), COLOR.b5));
|
||||
parts.push(dot('b7', 'B7', xx, y(z.B7), COLOR.b5));
|
||||
parts.push(dot('b1', 'B1', xx, y(z.B1), COLOR.b14));
|
||||
parts.push(dot('b2', 'B2', xx, y(z.B24), COLOR.b14));
|
||||
parts.push(dot('b3', 'B3', xx, y(z.B3), COLOR.b14));
|
||||
} else {
|
||||
parts.push(dot('b8', 'B8', xx, y(z.B68), COLOR.b5));
|
||||
parts.push(dot('b4', 'B4', xx, y(z.B24), COLOR.b14));
|
||||
}
|
||||
});
|
||||
// Outermost columns (승인 2026-10-26 추천, 방향 교환 반영): the missing B5–B8 points at the first and last W position are drawn as 'ext' points.
|
||||
|
||||
// Outermost columns (승인 2026-10-26, 추천): B1·B2·B3·B4 all sit in the first and last grid column (확대기초철근 (1).md §3, §5).
|
||||
// The missing ones are drawn as 'ext' points (they do not change the table counts). Their top (B1) and bottom (B2·B4) runs extend
|
||||
// to the side cover c_s, and a leg line at the cover runs from the 하면 1단 (B2) up to the B1 height.
|
||||
const xSideL = x(-W / 2 + fs.cSide);
|
||||
const xSideR = x(W / 2 - fs.cSide);
|
||||
const last = xs.length - 1;
|
||||
for (const i of [0, last]) {
|
||||
const xx = x(xsW[i]);
|
||||
const drawnHere = i % 2 === 1 ? ['B5', 'B6', 'B7'] : ['B8'];
|
||||
for (const [name, zz] of [['B5', z.B5], ['B6', z.B68], ['B7', z.B7], ['B8', z.B68]] as const) {
|
||||
if (!drawnHere.includes(name)) parts.push(dot('ext', name, xx, y(zz), COLOR.b5));
|
||||
const drawnHere = i % 2 === 1 ? ['B1', 'B2', 'B3'] : ['B4'];
|
||||
const xx = x(xs[i]);
|
||||
for (const [name, zz] of [['B1', z.B1], ['B2', z.B24], ['B3', z.B3], ['B4', z.B24]] as const) {
|
||||
if (!drawnHere.includes(name)) parts.push(dot('ext', name, xx, y(zz), COLOR.b14));
|
||||
}
|
||||
const xSide = i === 0 ? xSideL : xSideR;
|
||||
parts.push(line('ext', 'B1', xx, xSide, y(z.B1), COLOR.b14, 1));
|
||||
parts.push(line('ext', 'B2', xx, xSide, y(z.B24), COLOR.b14, 1));
|
||||
// The B2 side leg (up to the B1 height at the side cover) is drawn heavier, so the corner reads as one run (승인 2026-10-26).
|
||||
parts.push(`<line class="leg" data-bar="B2" x1="${xSide.toFixed(1)}" x2="${xSide.toFixed(1)}" y1="${y(z.B1).toFixed(1)}" y2="${y(z.B24).toFixed(1)}" stroke="${COLOR.b14}" stroke-width="1.8"/>`);
|
||||
}
|
||||
|
||||
// B9: one purple vertical line per row along W (the rows of b9Bars).
|
||||
@@ -123,12 +126,12 @@ export function footingSectionSvg(model: PierModel): string {
|
||||
js.forEach((j, idx) => {
|
||||
const zA = fs.cBottom + j * fs.sV;
|
||||
parts.push(line('b11', 'B11', xa, xb, y(zA), COLOR.b11, 1.2, '6 3'));
|
||||
const xsB12 = gridPositions(W, fs, 'sW');
|
||||
const xsB12 = gridPositions(W, fs, 'sAxis');
|
||||
for (const i of assemblyGridIndices(xsB12.length - 1, fs.kB12, idx)) parts.push(dot('b12', 'B12', x(xsB12[i]), y(zA + dB10), COLOR.b12, 2.2));
|
||||
});
|
||||
|
||||
// Dimensions: the width line with ticks and the grid string, and the right-hand heights.
|
||||
const g = grid(W, fs, 'sW');
|
||||
const g = grid(W, fs, 'sAxis');
|
||||
const yDim = Y0 + 40;
|
||||
parts.push(`<line x1="${x(-W / 2).toFixed(1)}" x2="${x(W / 2).toFixed(1)}" y1="${yDim}" y2="${yDim}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
|
||||
parts.push(tick(x(-W / 2), yDim) + tick(x(W / 2), yDim));
|
||||
@@ -138,7 +141,7 @@ export function footingSectionSvg(model: PierModel): string {
|
||||
parts.push(text(x(-W / 2 + fs.cSide) + 2, yDim + 14, `${g.e.toFixed(1)}`, 'start', COLOR.dim));
|
||||
parts.push(text(x(W / 2 - fs.cSide) - 2, yDim + 14, `${g.e.toFixed(1)}`, 'end', COLOR.dim));
|
||||
// The grid string sits right of centre, so the bottom leaders (at the centre and the two sides) do not cross it.
|
||||
parts.push(text(cx + 150, yDim - 6, `${g.n}@${fs.sW}=${g.G}`, 'middle', COLOR.dim));
|
||||
parts.push(text(cx + 150, yDim - 6, `${g.n}@${fs.sAxis}=${g.G}`, 'middle', COLOR.dim));
|
||||
parts.push(text(cx, 20, `교각축 직각 단면 · 폭 ${W.toFixed(0)}`, 'middle', '#1F2937'));
|
||||
const xr = X0 + W_PX + 18;
|
||||
parts.push(`<line x1="${xr}" x2="${xr}" y1="${y(H).toFixed(1)}" y2="${y(0).toFixed(1)}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
|
||||
@@ -157,22 +160,32 @@ export function footingSectionSvg(model: PierModel): string {
|
||||
const edgeY = by < ty ? by + 11 : by - 11;
|
||||
return `<g class="bubble" data-bubble="${name}"><line class="leader" data-bar="${name}" x1="${bx.toFixed(1)}" x2="${tx.toFixed(1)}" y1="${edgeY.toFixed(1)}" y2="${ty.toFixed(1)}" stroke="${COLOR.outline}" stroke-width="0.7"/><circle cx="${bx.toFixed(1)}" cy="${by.toFixed(1)}" r="11" fill="#fff" stroke="${COLOR.outline}"/><text x="${bx.toFixed(1)}" y="${(by + 3).toFixed(1)}" font-size="9" text-anchor="middle" fill="${COLOR.outline}">${name}</text></g>`;
|
||||
};
|
||||
const zB11 = js.length > 0 ? fs.cBottom + js[0] * fs.sV : fs.cBottom;
|
||||
const yTop = 40;
|
||||
const yBot = Y0 + 100;
|
||||
// Each bottom bubble sits straight below its own bar (B2 left of centre, B4 at centre, B3 right of centre) on a bar of the right parity.
|
||||
parts.push(bubble('B5', xa + 12, yTop, x(xsW[1]), y(z.B5)));
|
||||
parts.push(bubble('B1', cx - 60, yTop, cx - 60, y(z.B1)));
|
||||
const nearest = (target: number, parity: number) => {
|
||||
let best = -1;
|
||||
xs.forEach((mm, i) => {
|
||||
if (i % 2 === parity && (best < 0 || Math.abs(mm - target) < Math.abs(xs[best] - target))) best = i;
|
||||
});
|
||||
return best;
|
||||
};
|
||||
const iB2 = nearest(-1930, 1);
|
||||
const iB4 = nearest(0, 0);
|
||||
const iB3 = nearest(1930, 1);
|
||||
const zB11 = js.length > 0 ? fs.cBottom + js[0] * fs.sV : fs.cBottom;
|
||||
parts.push(bubble('B5', xa + 12, yTop, xa + 12, y(z.B5)));
|
||||
parts.push(bubble('B1', x(xs[1]) + 60, yTop, x(xs[1]), y(z.B1)));
|
||||
parts.push(bubble('B11', xb - 20, yTop, xb, y(zB11)));
|
||||
parts.push(bubble('B10-1', X0 - 18, y(fs.cBottom + (levels - 1) * fs.sV / 2), X0 + 2, y(fs.cBottom + (levels - 1) * fs.sV / 2)));
|
||||
parts.push(bubble('B2', cx - 70, yBot, cx - 70, y(z.B24)));
|
||||
parts.push(bubble('B4', cx, yBot, cx, y(z.B24)));
|
||||
parts.push(bubble('B3', cx + 70, yBot, cx + 70, y(z.B3)));
|
||||
parts.push(bubble('B2', x(xs[iB2]), yBot, x(xs[iB2]), y(z.B24)));
|
||||
parts.push(bubble('B4', x(xs[iB4]), yBot, x(xs[iB4]), y(z.B24)));
|
||||
parts.push(bubble('B3', x(xs[iB3]), yBot, x(xs[iB3]), y(z.B3)));
|
||||
|
||||
// Legend (선 모양별, 삽도 기준) and the units note.
|
||||
const yLeg = 352;
|
||||
parts.push(`<circle cx="20" cy="${yLeg}" r="2.6" fill="${COLOR.b5}"/>${text(28, yLeg + 3, '주철근 (B5–B8)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<line x1="180" x2="204" y1="${yLeg}" y2="${yLeg}" stroke="${COLOR.b14}" stroke-width="1.6"/>${text(212, yLeg + 3, '직각·보조 (B1–B4)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<line x1="20" x2="44" y1="${yLeg}" y2="${yLeg}" stroke="${COLOR.b5}" stroke-width="1.6"/>${text(50, yLeg + 3, '주철근 (B5–B8)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<circle cx="180" cy="${yLeg}" r="2.6" fill="${COLOR.b14}"/>${text(188, yLeg + 3, '직각·보조 (B1–B4)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<line x1="330" x2="330" y1="${yLeg - 8}" y2="${yLeg + 8}" stroke="${COLOR.b9}" stroke-width="1"/>${text(338, yLeg + 3, '전단·띠 (B9)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<circle cx="20" cy="${yLeg + 22}" r="2.2" fill="${COLOR.b10}"/>${text(28, yLeg + 25, '측면 수평 (B10-1 · B10-2)', 'start', COLOR.outline)}`);
|
||||
parts.push(`<line x1="250" x2="274" y1="${yLeg + 22}" y2="${yLeg + 22}" stroke="${COLOR.b11}" stroke-width="1.2" stroke-dasharray="6 3"/>${text(280, yLeg + 25, '조립 (B11·B12)', 'start', COLOR.outline)}`);
|
||||
|
||||
+20
-23
@@ -384,11 +384,10 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
|
||||
// 확대기초 입력 (승인 2026-10-26): the grid, covers and layer heights come from the model's footing settings.
|
||||
const fs = m.footingRebar;
|
||||
const z = layers(Hf, fs);
|
||||
// 방향 교환 (승인 2026-10-26): B1–B4 run along W and are spaced along L (sL); B5–B8 run along L and are spaced along W (sW).
|
||||
const gW = grid(Wf * 1000, fs, 'sW');
|
||||
const gL = grid(Lf * 1000, fs, 'sL');
|
||||
const posW = (j: number) => -Wf / 2 + mm(fs.cSide + gW.e + j * fs.sW);
|
||||
const posL = (i: number) => -Lf / 2 + mm(fs.cSide + gL.e + i * fs.sL);
|
||||
const gW = grid(Wf * 1000, fs);
|
||||
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;
|
||||
@@ -409,28 +408,26 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
|
||||
const at = (t: number, h: number): [number, number, number] => [f.s + s, h, t];
|
||||
path(name, legTo === undefined ? [at(nA, y), at(nB, y)] : [at(nA, legTo), at(nA, y), at(nB, y), at(nB, legTo)], dMm);
|
||||
};
|
||||
// B1–B4 run along W (at each L position s): odd positions carry B1, B2, B3, even carry B4 (same parity rule as bottomBars).
|
||||
for (let i = 0; i <= gL.n; i++) {
|
||||
const sPos = posL(i);
|
||||
if (i % 2 === 1) {
|
||||
for (let j = 0; j <= gW.n; j++) {
|
||||
const n = posW(j);
|
||||
if (j % 2 === 1) {
|
||||
// B1 ⊓ down, B2 ⊔ up to the B1 height, B3 ⊔ up. B1·B3 sit beside the B2 leg (+25.4 / +22.25, §5.3).
|
||||
across('B1', sPos + mm(off.B1), bottom + mm(z.B1), bottom + mm(z.B1) - mm(legLength('S25')), NOMINAL_DIAMETER.S25);
|
||||
across('B2', sPos, bottom + mm(z.B24), bottom + mm(z.B1), NOMINAL_DIAMETER.S25);
|
||||
across('B3', sPos + mm(off.B3), bottom + mm(z.B3), bottom + mm(z.B3) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
|
||||
along('B1', n + mm(off.B1), bottom + mm(z.B1), bottom + mm(z.B1) - mm(legLength('S25')), NOMINAL_DIAMETER.S25);
|
||||
along('B2', n, bottom + mm(z.B24), bottom + mm(z.B1), NOMINAL_DIAMETER.S25);
|
||||
along('B3', n + mm(off.B3), bottom + mm(z.B3), bottom + mm(z.B3) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
|
||||
} else {
|
||||
across('B4', sPos, bottom + mm(z.B24), undefined, NOMINAL_DIAMETER.S25);
|
||||
along('B4', n, bottom + mm(z.B24), undefined, NOMINAL_DIAMETER.S25);
|
||||
}
|
||||
}
|
||||
// B5–B8 run along L (at each W position n): odd positions carry B5, B6, B7, even carry B8.
|
||||
for (let j = 0; j <= gW.n; j++) {
|
||||
const nPos = posW(j);
|
||||
if (j % 2 === 1) {
|
||||
for (let i = 0; i <= gL.n; i++) {
|
||||
const s = posL(i);
|
||||
if (i % 2 === 1) {
|
||||
// B5 ⊓ down, B6 ⊔ up to the B5 height, B7 ⊔ up. B5·B7 sit beside the B6 leg (+28.6 / +23.85, §6.3).
|
||||
along('B5', nPos + mm(off.B5), bottom + mm(z.B5), bottom + mm(z.B5) - mm(legLength('S29')), NOMINAL_DIAMETER.S29);
|
||||
along('B6', nPos, bottom + mm(z.B68), bottom + mm(z.B5), NOMINAL_DIAMETER.S29);
|
||||
along('B7', nPos + mm(off.B7), bottom + mm(z.B7), bottom + mm(z.B7) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
|
||||
across('B5', s + mm(off.B5), bottom + mm(z.B5), bottom + mm(z.B5) - mm(legLength('S29')), NOMINAL_DIAMETER.S29);
|
||||
across('B6', s, bottom + mm(z.B68), bottom + mm(z.B5), NOMINAL_DIAMETER.S29);
|
||||
across('B7', s + mm(off.B7), bottom + mm(z.B7), bottom + mm(z.B7) + mm(legLength('S19')), NOMINAL_DIAMETER.S19);
|
||||
} else {
|
||||
along('B8', nPos, bottom + mm(z.B68), undefined, NOMINAL_DIAMETER.S29);
|
||||
across('B8', s, bottom + mm(z.B68), undefined, NOMINAL_DIAMETER.S29);
|
||||
}
|
||||
}
|
||||
// B9 (확대기초철근 (1).md §9, rule applied): vertical H16 bars on the grid, outside the column zones, with 135° hooks.
|
||||
@@ -475,8 +472,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, 'sL');
|
||||
const ysW = gridPositions(Wf * 1000, fs, 'sW');
|
||||
const xsL = gridPositions(Lf * 1000, fs, 'sPerp');
|
||||
const ysW = gridPositions(Wf * 1000, fs, 'sAxis');
|
||||
const dAsm = assemblyEndOffset();
|
||||
const nAsm = Wf / 2 - mm(d1 + dAsm);
|
||||
const sAsm = Lf / 2 - mm(d2 + dAsm);
|
||||
|
||||
@@ -9,17 +9,17 @@ const footingRows = (input: Parameters<typeof buildPier>[0]) =>
|
||||
const count = (rows: ReturnType<typeof footingRows>, name: string) => rows.find((r) => r.name === name)!.count;
|
||||
|
||||
describe('확대기초 철근은 입력 치수를 따른다 (3단계, 승인 2026-10-23)', () => {
|
||||
it('P1 footing rebar is unchanged with the default inputs (B1 40, B5 31, B9 306; 방향 교환 후)', () => {
|
||||
it('P1 footing rebar is unchanged with the default inputs (B1 31, B5 40, B9 306)', () => {
|
||||
const rows = footingRows(loadP1());
|
||||
expect(count(rows, 'B1')).toBe(40);
|
||||
expect(count(rows, 'B5')).toBe(31);
|
||||
expect(count(rows, 'B1')).toBe(31);
|
||||
expect(count(rows, 'B5')).toBe(40);
|
||||
expect(count(rows, 'B9')).toBe(306);
|
||||
});
|
||||
|
||||
it('a wider footing (W 9000) and a longer one (L 11000) add bars to B1–B4 and B5–B8', () => {
|
||||
const rows = footingRows({ ...loadP1(), footingWidthMm: 9000, footingLengthMm: 11000 });
|
||||
expect(count(rows, 'B1')).toBeGreaterThan(40);
|
||||
expect(count(rows, 'B5')).toBeGreaterThan(31);
|
||||
expect(count(rows, 'B1')).toBeGreaterThan(31);
|
||||
expect(count(rows, 'B5')).toBeGreaterThan(40);
|
||||
});
|
||||
|
||||
it('a different footing height (2200) is computed without error, and the 3D rebar still builds', () => {
|
||||
|
||||
@@ -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: { sL: 150 } });
|
||||
expect(m.footingRebar.sL).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: { sL: 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,20 +44,18 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초
|
||||
expect(rows.find((r) => r.name === 'B10-1')!.basis).toBe('default');
|
||||
});
|
||||
|
||||
// 방향 교환 (승인 2026-10-26): sL = spacing along L (B1–B4, B9 columns, B11); sW = spacing along W (B5–B8, B9 rows, B12).
|
||||
it('방향 (교환 후): sW는 B5–B8·B12, sL은 B1–B4·B9·B11', () => {
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sW: 150 } })).rows;
|
||||
it('방향 (문서 기준, 승인 2026-10-26): B1–B4·B9·B12는 교각축(sAxis), B5–B8·B11은 교각축 직각(sPerp)', () => {
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sPerp: 150 } })).rows;
|
||||
expect(rows.find((r) => r.name === 'B5')!.spacing).toBe('300');
|
||||
expect(rows.find((r) => r.name === 'B5')!.basis).toBe('input');
|
||||
expect(rows.find((r) => r.name === 'B12')!.spacing).toBe('1200 (8s)');
|
||||
expect(rows.find((r) => r.name === 'B11')!.basis).toBe('default');
|
||||
expect(rows.find((r) => r.name === 'B11')!.spacing).toBe('1200 (8s)');
|
||||
expect(rows.find((r) => r.name === 'B12')!.basis).toBe('default');
|
||||
expect(rows.find((r) => r.name === 'B1')!.basis).toBe('default');
|
||||
|
||||
const rowsL = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sL: 150 } })).rows;
|
||||
expect(rowsL.find((r) => r.name === 'B1')!.spacing).toBe('300');
|
||||
expect(rowsL.find((r) => r.name === 'B9')!.spacing).toBe('600 (4s)');
|
||||
expect(rowsL.find((r) => r.name === 'B11')!.spacing).toBe('1200 (8s)');
|
||||
expect(rowsL.find((r) => r.name === 'B5')!.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 === 'B12')!.spacing).toBe('1200 (8s)');
|
||||
expect(rowsAxis.find((r) => r.name === 'B5')!.basis).toBe('default');
|
||||
});
|
||||
|
||||
it('k 배수: k 4 → 간격 500, 개수 60 / 47 (문서 4s 값)', () => {
|
||||
|
||||
@@ -17,15 +17,16 @@ describe('확대기초 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추
|
||||
const svg = footingSectionSvg(m);
|
||||
|
||||
it('point and line counts match the rebar table families', () => {
|
||||
const cross = crossBars(L, W, H, fs);
|
||||
// B1–B4 run along W: one line per family in this section; B5–B8 run along L: points on the W grid (방향 교환).
|
||||
for (const name of ['B1', 'B2', 'B3', 'B4']) expect(countMark(svg, name.toLowerCase(), name)).toBe(1);
|
||||
for (const name of ['B5', 'B6', 'B7', 'B8']) expect(countMark(svg, name.toLowerCase(), name)).toBe(cross.find((b) => b.name === name)!.count);
|
||||
const bottom = bottomBars(L, W, H, fs);
|
||||
expect(countMark(svg, 'b1', 'B1')).toBe(bottom.find((b) => b.name === 'B1')!.count);
|
||||
expect(countMark(svg, 'b2', 'B2')).toBe(bottom.find((b) => b.name === 'B2')!.count);
|
||||
expect(countMark(svg, 'b4', 'B4')).toBe(bottom.find((b) => b.name === 'B4')!.count);
|
||||
expect(countMark(svg, 'b9', 'B9')).toBe(b9Bars(L, W, [], 2000, fs).rows);
|
||||
expect(countMark(svg, 'b10', 'B10-1')).toBe(sideBars(L, W, H, fs).find((b) => b.name === 'B10-1')!.count);
|
||||
expect(countMark(svg, 'b11', 'B11')).toBe(assemblyJs(H, fs).length);
|
||||
expect(countMark(svg, 'b12', 'B12')).toBe(assemblyBars(L, W, H, fs).find((b) => b.name === 'B12')!.count);
|
||||
expect(cross.length).toBe(4);
|
||||
for (const name of ['B5', 'B6', 'B7', 'B8']) expect(countMark(svg, 'b5', name)).toBe(1);
|
||||
expect(crossBars(L, W, H, fs).length).toBe(4);
|
||||
});
|
||||
|
||||
it('shows the width, the grid string, and the B10 levels as dimensions', () => {
|
||||
|
||||
+23
-24
@@ -31,25 +31,24 @@ describe('확대기초 철근 (확대기초철근 (1).md, plan C5)', () => {
|
||||
expect(z.B7).toBeCloseTo(277, 6);
|
||||
});
|
||||
|
||||
// 방향 교환 (승인 2026-10-26): B1–B4 run along W (grid L, 79 steps → 80 positions); B5–B8 run along L (grid W).
|
||||
it('B2 leg 1723, B2 length 11396 (W − 2c + 2 × 1723), count 40', () => {
|
||||
it('B2 leg 1723, B2 length 13646 (over the 12000 bar, splice needed)', () => {
|
||||
expect(legB2(H)).toBeCloseTo(1723, 6);
|
||||
const b2 = bottomBars(L, W, H).find((b) => b.name === 'B2')!;
|
||||
expect(b2.length).toBeCloseTo(11396, 0);
|
||||
expect(b2.count).toBe(40);
|
||||
expect(b2.length).toBeCloseTo(13646, 0);
|
||||
expect(b2.count).toBe(31);
|
||||
});
|
||||
|
||||
it('B1–B4 counts 40 / 40 / 40 / 40 (grid along L) and B4 length 7950 (W − 2c)', () => {
|
||||
it('B1–B4 counts 31 / 31 / 31 / 31 and B4 length 10200', () => {
|
||||
const bars = bottomBars(L, W, H);
|
||||
expect(bars.map((b) => b.count)).toEqual([40, 40, 40, 40]);
|
||||
expect(bars.find((b) => b.name === 'B4')!.length).toBeCloseTo(7950, 6);
|
||||
expect(bars.map((b) => b.count)).toEqual([31, 31, 31, 31]);
|
||||
expect(bars.find((b) => b.name === 'B4')!.length).toBeCloseTo(10200, 6);
|
||||
});
|
||||
|
||||
it('B5–B8 counts 31 each (grid along W: n 61), B8 length 10200 (L − 2c), B6 length 13646', () => {
|
||||
it('B5–B8 counts 40 each (grid along L: n 79), B8 length 7950, B6 length 11396', () => {
|
||||
const bars = crossBars(L, W, H);
|
||||
expect(bars.map((b) => b.count)).toEqual([31, 31, 31, 31]);
|
||||
expect(bars.find((b) => b.name === 'B8')!.length).toBeCloseTo(10200, 6);
|
||||
expect(bars.find((b) => b.name === 'B6')!.length).toBeCloseTo(13646, 0);
|
||||
expect(bars.map((b) => b.count)).toEqual([40, 40, 40, 40]);
|
||||
expect(bars.find((b) => b.name === 'B8')!.length).toBeCloseTo(7950, 6);
|
||||
expect(bars.find((b) => b.name === 'B6')!.length).toBeCloseTo(11396, 0);
|
||||
});
|
||||
|
||||
it('B10-1 16 bars ≈ 10748 (lowest level removed), B10-2 16 bars ≈ 8494', () => {
|
||||
@@ -109,29 +108,29 @@ describe('확대기초 배근도 재현 (확대기초철근 (1).md §13)', () =>
|
||||
expect(h.B12.map((z) => Math.round(z * 10) / 10)).toEqual([569.1, 969.1, 1369.1]);
|
||||
});
|
||||
|
||||
it('배근도 재현 W 7800 → B8 30, B6 29 (B5–B8 follow W, odd count split)', () => {
|
||||
const bars = crossBars(10400, 7800, 2000);
|
||||
expect(bars.find((b) => b.name === 'B8')!.count).toBe(30);
|
||||
expect(bars.find((b) => b.name === 'B6')!.count).toBe(29);
|
||||
it('배근도 재현 W 7800 → B4 30, B2 29 (odd count split)', () => {
|
||||
const bars = bottomBars(10400, 7800, 2000);
|
||||
expect(bars.find((b) => b.name === 'B4')!.count).toBe(30);
|
||||
expect(bars.find((b) => b.name === 'B2')!.count).toBe(29);
|
||||
});
|
||||
|
||||
it('배근도 재현 L 9350 → B4 36, B2 35 (B1–B4 follow L)', () => {
|
||||
const bars = bottomBars(9350, 8150, 2000);
|
||||
expect(bars.find((b) => b.name === 'B4')!.count).toBe(36);
|
||||
expect(bars.find((b) => b.name === 'B2')!.count).toBe(35);
|
||||
it('배근도 재현 L 9350 → B8 36, B6 35', () => {
|
||||
const bars = crossBars(9350, 8150, 2000);
|
||||
expect(bars.find((b) => b.name === 'B8')!.count).toBe(36);
|
||||
expect(bars.find((b) => b.name === 'B6')!.count).toBe(35);
|
||||
});
|
||||
|
||||
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, sL: 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);
|
||||
expect(grid(12000).e).toBeCloseTo(150, 6);
|
||||
});
|
||||
|
||||
it('splice cases: W 8700 → B2 11946 (no splice); W 12400 → B4 12200; L 8800 → B6 12046', () => {
|
||||
expect(bottomBars(10400, 8700, 2000).find((b) => b.name === 'B2')!.length).toBeCloseTo(11946, 0);
|
||||
expect(bottomBars(10400, 12400, 2000).find((b) => b.name === 'B4')!.length).toBeCloseTo(12200, 0);
|
||||
expect(crossBars(8800, 8150, 2000).find((b) => b.name === 'B6')!.length).toBeCloseTo(12046, 0);
|
||||
it('splice cases: L 8700 → B2 11946 (no splice); L 12400 → B4 12200; W 8800 → B6 12046', () => {
|
||||
expect(bottomBars(8700, 8150, 2000).find((b) => b.name === 'B2')!.length).toBeCloseTo(11946, 0);
|
||||
expect(bottomBars(12400, 8150, 2000).find((b) => b.name === 'B4')!.length).toBeCloseTo(12200, 0);
|
||||
expect(crossBars(10400, 8800, 2000).find((b) => b.name === 'B6')!.length).toBeCloseTo(12046, 0);
|
||||
});
|
||||
});
|
||||
@@ -41,11 +41,11 @@ describe('② 부재별 fck (spec §6.2 · ②)', () => {
|
||||
expect(changedIn(rows, 'column').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('a footing fck changes only the footing rows (B6 splice, 방향 교환)', () => {
|
||||
it('a footing fck changes only the footing rows (B2 splice)', () => {
|
||||
const rows = rebarSummary(model, DEFAULT_MATERIAL, { footing: 35 }).rows;
|
||||
expect(changedIn(rows, 'coping')).toEqual([]);
|
||||
expect(changedIn(rows, 'column')).toEqual([]);
|
||||
expect(changedIn(rows, 'footing')).toContain('B6');
|
||||
expect(changedIn(rows, 'footing')).toContain('B2');
|
||||
});
|
||||
|
||||
it('a per-member fck equal to the 공통 value changes nothing', () => {
|
||||
|
||||
@@ -35,21 +35,21 @@ describe('③ 철근표 요약 (P1)', () => {
|
||||
expect(count('P6')).toBe(32);
|
||||
});
|
||||
|
||||
it('footing (방향 교환): B1–B4 40 each, B5–B8 31 each, B10-1 16, B10-2 16, B11 30, B12 24 (k = 8)', () => {
|
||||
for (const n of ['B1', 'B2', 'B3', 'B4']) expect(count(n)).toBe(40);
|
||||
for (const n of ['B5', 'B6', 'B7', 'B8']) expect(count(n)).toBe(31);
|
||||
it('footing: B1–B4 31 each, B5–B8 40 each, B10-1 16, B10-2 16, B11 30, B12 24 (k = 8)', () => {
|
||||
for (const n of ['B1', 'B2', 'B3', 'B4']) expect(count(n)).toBe(31);
|
||||
for (const n of ['B5', 'B6', 'B7', 'B8']) expect(count(n)).toBe(40);
|
||||
expect(count('B10-1')).toBe(16);
|
||||
expect(count('B10-2')).toBe(16);
|
||||
expect(count('B11')).toBe(30);
|
||||
expect(count('B12')).toBe(24);
|
||||
});
|
||||
|
||||
it('B9: 306 bars (320 on the grid less 14 in the column zone); warnings: B6 needs a splice (13646 > 12000)', () => {
|
||||
it('B9: 306 bars (320 on the grid less 14 in the column zone); warnings: B2 needs a splice (13646 > 12000)', () => {
|
||||
expect(count('B9')).toBe(306);
|
||||
expect(warnings.map((w) => w.code)).toEqual(['I02', 'RB-S04', 'RB-F08']); // defaults are assumed values (I02); bearing embedment 619–669 < 882 (RB-S04)
|
||||
expect(warnings.find((w) => w.code === 'RB-F08')!.bar).toBe('B6');
|
||||
// B6 is split into two pieces (추천안): each piece is about half the 13646 bar plus half the lap.
|
||||
expect(rows.find((r) => r.name === 'B6')!.length!).toBeCloseTo(7776, 0);
|
||||
expect(warnings.find((w) => w.code === 'RB-F08')!.bar).toBe('B2');
|
||||
// B2 is split into two pieces (추천안): each piece is about half the 13646 bar plus half the lap.
|
||||
expect(rows.find((r) => r.name === 'B2')!.length!).toBeCloseTo(7776, 0);
|
||||
});
|
||||
|
||||
it('lengths from the documents: C13 11746, C14 3447.5, C15 3852, C16 4070, P3 ≈ C_t + 1.3·l_d, P5 ≈ C_t', () => {
|
||||
|
||||
Reference in new issue
Block a user