feat(rebar): C15·C16 폭 간격을 2 × 주철근 기본간격에 연동

- stairBars가 pitch를 받는다 (기본 2 × 기본간격 = 250, P1 그대로)
- 3D와 철근표가 같은 pitch를 쓴다
- 철근표 간격·실간격: 2 × 기본간격, 근거는 기본값/입력

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-26 16:04:31 +09:00
1 parent 3cfbdf62f6
commit 72053be420
5 files changed
+23 -5

No files matched your search

+4 -3
View File
@@ -229,12 +229,13 @@ export interface StepBar {
/**
* C15 (거꾸로 된 U, on the highest segment) and C16 (L형, on segments 3 and 4), 11 bars each across the width (코핑철근.md §7).
* Across: 137 from the cover line, then @250 (피복선 기준). Along: from each segment start + c to the next end − c.
* Across: 137 from the cover line, then @pitch (피복선 기준). pitch = 2 × 주철근 기본간격 (승인 2026-10-26; 250 at P1).
* Along: from each segment start + c to the next end − c.
*/
export function stairBars(L: number, B: number, topEl: number, m: Material = DEFAULT_MATERIAL): { c15: StepBar[]; c16: StepBar[] } {
export function stairBars(L: number, B: number, topEl: number, m: Material = DEFAULT_MATERIAL, pitch = 2 * DEFAULT_COPING_REBAR.mainSpacing): { c15: StepBar[]; c16: StepBar[] } {
const ld = stepDevLength(m);
const c = COPING_REBAR.cover;
const ns = Array.from({ length: 11 }, (_, k) => -B / 2 + c + 137 + 250 * k);
const ns = Array.from({ length: 11 }, (_, k) => -B / 2 + c + 137 + pitch * k);
const seg = STAIR_SEGMENTS;
const top = (i: number) => topEl + seg[i].rise / 1000;
const c15Z = top(3) - c / 1000;
+5 -1
View File
@@ -134,6 +134,10 @@ export function linkedSpacing(name: string, model: PierModel): Pick<RebarRow, 's
if (MAIN_BARS.includes(name)) {
return { spacing: String(2 * c.mainSpacing), actual: String(c.mainSpacing), basis: basis(c.mainSpacing !== d.mainSpacing) };
}
if (name === 'C15' || name === 'C16') {
// C15·C16 are on the same width pitch as the main bars (2 × 기본간격); 250 at P1.
return { spacing: String(2 * c.mainSpacing), actual: String(2 * c.mainSpacing), basis: basis(c.mainSpacing !== d.mainSpacing) };
}
if (name === 'C9' || name === 'C11') return { spacing: String(c.spacingS), actual: String(c.spacingS), basis: basis(c.spacingS !== d.spacingS) };
if (name === 'C10') return { spacing: String(c.spacingL), actual: String(c.spacingL), basis: basis(c.spacingL !== d.spacingL) };
if (name === 'C12' || name === 'C13') return { spacing: String(c.sideSpacing), actual: String(c.sideSpacing), basis: basis(c.sideSpacing !== d.sideSpacing) };
@@ -240,7 +244,7 @@ export function rebarSummary(
const lineCount = (line: number) => c14.filter((b) => b.line === line).length;
const omitted = c14.filter((b) => !b.legs).length;
rows.push({ member: 'coping', name: 'C14', size: 'H19', count: c14.length, length: c14Length(model.copingB * 1000), note: `위 2 줄 ${lineCount(1)} · ${lineCount(2)}본씩, 아래 ${[3, 4, 5, 6].map(lineCount).join(' · ')} (합 ${c14.length}) · 연직 발 생략 ${omitted}개 (하면 밖)` });
const stairs = stairBars(model.copingL * 1000, model.copingB * 1000, model.copingRefTop, mCoping);
const stairs = stairBars(model.copingL * 1000, model.copingB * 1000, model.copingRefTop, mCoping, 2 * model.copingRebar.mainSpacing);
rows.push({ member: 'coping', name: 'C15', size: 'H19', count: stairs.c15.length, length: stairs.c15[0].length, note: '계단부, 가장 높은 구간 (거꾸로 U)' });
rows.push({ member: 'coping', name: 'C16', size: 'H19', count: stairs.c16.length, length: stairs.c16[0].length, note: `계단부, 3·4구간 (${Math.round(stairs.c16[0].length)} / ${Math.round(stairs.c16[stairs.c16.length - 1].length)})` });
+1 -1
View File
@@ -249,7 +249,7 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
}
}
// C15 (거꾸로 된 U) and C16 (L형) on the stair (코핑철근.md §7): 11 bars each across the width.
const stairs = stairBars(L * 1000, B * 1000, topEl, mCoping);
const stairs = stairBars(L * 1000, B * 1000, topEl, mCoping, 2 * m.copingRebar.mainSpacing);
for (const s of stairs.c15) {
const n = mm(s.n);
const ld = mm(s.leg);
+4
View File
@@ -40,12 +40,16 @@ describe('C1–C8 상·하면 주철근 (코핑철근.md §5, plan C3)', () => {
expect([c1.spacing, c1.actual, c1.basis]).toEqual(['250', '125', 'default']);
const c12 = base.find((r) => r.name === 'C12')!;
expect(c12.basis).toBe('default');
const c15 = base.find((r) => r.name === 'C15')!;
expect([c15.spacing, c15.actual, c15.basis]).toEqual(['250', '250', 'default']);
const m = buildPier({ ...loadP1(), copingRebar: { mainSpacing: 140 }, blockRebar: { spacingS1: 130 } });
const rows = rebarSummary(m).rows;
const c8 = rows.find((r) => r.name === 'C8')!;
expect([c8.spacing, c8.actual, c8.basis]).toEqual(['280', '140', 'input']);
expect(rows.find((r) => r.name === 'C12')!.basis).toBe('default');
const c16 = rows.find((r) => r.name === 'C16')!;
expect([c16.spacing, c16.actual, c16.basis]).toEqual(['280', '280', 'input']);
const s1 = rows.find((r) => r.name === 'S1')!;
expect([s1.spacing, s1.actual, s1.basis]).toEqual(['130', '130', 'input']);
});
+9
View File
@@ -65,3 +65,12 @@ describe('C14 연직 발 (코핑철근.md §8)', () => {
expect(omitted).toEqual([[4, 1], [4, 11]]);
});
});
describe('C15 간격 = 2 × 주철근 기본간격 (승인 2026-10-26)', () => {
it('the default pitch is 250, and a larger pitch moves the 11 C15 bars apart', () => {
const { c15 } = stairBars(L, B, m.copingRefTop);
expect(c15[1].n - c15[0].n).toBe(250);
const wider = stairBars(L, B, m.copingRefTop, undefined, 280).c15;
expect(wider[1].n - wider[0].n).toBe(280);
});
});