Files
Pier_v1/tests/step-bars.test.ts
T
ypshimandClaude Haiku 5.5 33aa2d9a61 feat(rebar): 전·후열 단차 300 mm 이상 블록 보강 C17·C18
전열·후열 상면 차가 300 mm 이상인 구간의 높은 쪽 블록에 (결정 2026-11-10, step-bars.ts):
- C17 ㄷ형 띠 H16 @전단간격: 단차면 + c ~ 측면 − c, 위 = 높은 상면 − c, 다리 낮은 상면 아래 l_d까지
- C18 수평 피부철근 H16 @측면간격: 단차면 안쪽, 낮은 상면 + 측면간격마다
T형(철근표·3D)과 다주형(편 좌표 → 꺾임 접기) 모두에 넣는다. 구간마다 따로 두고 이웃 구간과 잇지 않는다 (가정).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-10 17:17:23 +09:00

130 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { tStepBars } from '../src/core/rebar/coping';
import { multicolCopingInfo } from '../src/core/rebar/multicol-coping';
import { stepBlockBars } from '../src/core/rebar/step-bars';
import { rebarSummary } from '../src/core/rebar/summary';
import type { PierInput } from '../src/core/types';
import { loadP1 } from './helpers';
// 계단부 C15 · C16 일반 규칙 (결정 2026-11-10, src/core/rebar/step-bars.ts). P1 문서값은 rebar-stairs.test.ts.
/** P1 with the 교좌면 EL (and z with it) of bearings 1–5 / 6–10 set to `seats` (m), both rows alike. */
function withSeats(seats: number[]): PierInput {
const p1 = loadP1();
return {
...p1,
bearings: p1.bearings.map((b) => {
const s = seats[(b.no - 1) % 5];
return { ...b, z: b.z - b.seatEl! + s, seatEl: s };
}),
};
}
const SEAT = [43.411, 43.483, 43.555, 43.626, 43.698];
describe('계단부 철근 일반 규칙', () => {
it('steps that fall towards the end: C15 on the first step (leg at the coping start + c), C16 legs at the low (end) side', () => {
const m = buildPier(withSeats([...SEAT].reverse()));
const bars = tStepBars(m).bars;
const c15 = bars.filter((b) => b.name === 'C15');
const c16 = bars.filter((b) => b.name === 'C16');
expect(c15.length).toBe(11);
expect(c15[0].x0).toBe(100);
expect(c16.length).toBe(22);
expect(c16.every((b) => b.legs === 'end')).toBe(true);
// Mirror of P1: the run reaches l_d into the higher (start-side) step.
expect(c16.map((b) => b.length).sort()).toEqual([...Array(11).fill(4069), ...Array(11).fill(4070)].sort());
});
it('every bar stays inside the coping length (the P1 positions no longer leak past a shorter coping)', () => {
const m = buildPier({ ...withSeats([...SEAT].reverse()), copingLengthMm: 12000 });
const L = m.copingL * 1000;
for (const b of tStepBars(m).bars) {
expect(b.x0).toBeGreaterThanOrEqual(100);
expect(b.x1).toBeLessThanOrEqual(L - 100);
}
});
it('no step bar when no step is h_16 (100 mm) above the lowest top', () => {
const m = buildPier(withSeats([43.411, 43.45, 43.48, 43.5, 43.51]));
expect(tStepBars(m).bars).toEqual([]);
});
it('a step with both neighbours higher gets no bar and a warning (RB-S01 규칙 밖)', () => {
const m = buildPier(withSeats([43.698, 43.561, 43.711, 43.411, 43.411]));
const { bars, warnings } = tStepBars(m);
expect(warnings).toHaveLength(1);
expect(warnings[0]).toContain('양쪽이 더 높아');
// The two peaks each get C15; the valley step none.
expect(bars.filter((b) => b.name === 'C15').length).toBe(22);
expect(bars.filter((b) => b.name === 'C16')).toEqual([]);
});
it('with a lateral step, front and back rows are separate and stay in their own part of the width', () => {
const p1 = loadP1();
const bearings = p1.bearings.map((b) => (b.row === '후열' && b.no === 7 ? { ...b, z: b.z + 0.3, seatEl: b.seatEl! + 0.3 } : b));
const m = buildPier({ ...p1, bearings });
const [front, back] = m.copingParts.filter((p) => p.k === 0);
const bars = tStepBars(m).bars;
expect(new Set(bars.map((b) => b.row))).toEqual(new Set(['전열', '후열']));
for (const b of bars) {
const part = b.row === '전열' ? front : back;
expect(b.n).toBeGreaterThanOrEqual(part.nFrom - 1e-9);
expect(b.n).toBeLessThanOrEqual(part.nTo + 1e-9);
}
// The raised back segment 2 (⑦) is a peak: C15 there, on the back row only.
const backC15 = bars.filter((b) => b.row === '후열' && b.name === 'C15');
expect(new Set(backC15.map((b) => b.x0))).toEqual(new Set([2296, 9812]));
});
it('multi-column copings use the same rule: a peak gets C15, a lower step C16 (no more RB-M04)', () => {
const m = buildPier({ ...withSeats(SEAT), type: '2주형', columnCount: 2, foundationType: '말뚝기초', groundEl: 30, pileLengthMm: 24000 });
const info = multicolCopingInfo(m);
const names = info.bars.map((b) => b.name);
expect(names).toContain('C15');
expect(names).toContain('C16');
expect(info.warnings.some((w) => w.code === 'RB-M04')).toBe(false);
});
});
describe('전·후열 단차 블록 보강 C17 · C18 (결정 2026-11-10)', () => {
const raised = (dz: number, extra: Partial<PierInput> = {}) => {
const p1 = loadP1();
return buildPier({ ...p1, ...extra, bearings: p1.bearings.map((b) => (b.row === '후열' && b.no === 7 ? { ...b, z: b.z + dz, seatEl: b.seatEl! + dz } : b)) });
};
const opts = (m: ReturnType<typeof buildPier>) => ({ cover: 100, d: 15.9, ld: 600, tieSpacing: m.copingRebar.spacingS, skinSpacing: m.copingRebar.sideSpacing });
it('a 400 mm step: C17 ㄷ ties across the higher (back) block @전단간격, legs l_d below the lower top; one C18 row', () => {
const m = raised(0.4);
const bars = stepBlockBars(m, opts(m));
const c17 = bars.filter((b) => b.name === 'C17'), c18 = bars.filter((b) => b.name === 'C18');
expect(c17.length).toBe(10);
const [, back] = m.copingParts.filter((p) => p.k === 0);
const hi = m.backSegments![1].top, lo = m.segments[1].top;
for (const b of c17) {
for (const p of b.pts) { expect(p[2]).toBeGreaterThanOrEqual(back.nFrom - 1e-9); expect(p[2]).toBeLessThanOrEqual(back.nTo + 1e-9); }
expect(b.pts[1][1]).toBeCloseTo(hi - 0.1, 9);
expect(b.pts[0][1]).toBeCloseTo(lo - 0.6, 9);
expect(b.pts[0][0]).toBeGreaterThan(2.196 + 0.1 - 1e-6);
expect(b.pts[0][0]).toBeLessThan(4.701 - 0.1 + 1e-6);
}
expect(Math.abs(c17[1].pts[0][0] - c17[0].pts[0][0])).toBeCloseTo(0.25, 9);
expect(c18.length).toBe(1);
expect(c18[0].pts[0][1]).toBeCloseTo(lo + 0.25, 9);
});
it('below 300 mm no block bars', () => {
const m = raised(0.25);
expect(stepBlockBars(m, opts(m))).toEqual([]);
});
it('they reach the T형 철근표 and the multi-column bars', () => {
const rows = rebarSummary(raised(0.4)).rows;
expect(rows.find((r) => r.name === 'C17')!.count).toBe(10);
expect(rows.find((r) => r.name === 'C18')!.count).toBe(1);
const multi = multicolCopingInfo(raised(0.4, { type: '2주형', columnCount: 2, foundationType: '말뚝기초', groundEl: 30, pileLengthMm: 24000 }));
expect(multi.bars.some((b) => b.name === 'C17')).toBe(true);
expect(multi.bars.some((b) => b.name === 'C18')).toBe(true);
});
});