Files
Pier_v1/tests/step-bars.test.ts
T
ypshimandClaude Haiku 5.5 7340f8c1a1 feat(rebar): 계단부 C15·C16을 모델의 실제 계단으로 만든다 (T형·다주형 공통)
T형은 P1 문서의 고정 위치(2.196~12.000 m)·높이·11개를 써서 코핑 길이가 다르면 철근이 코핑 밖으로 나갔고,
다주형은 높은 계단 전체에 C15 하나만 두었다. 공통 규칙 (step-bars.ts):
- 열마다(전·후열 단차가 있으면 각 쪽) 최저 상면 + h_16(100) 이상 구간이 계단, 경계는 mm 반올림
- 이웃보다 모두 높은 구간 C15(⊓), 나머지 계단 C16(L형, 낮은 쪽 다리, 높은 쪽으로 l_d 연장)
- 폭 방향: T형은 주철근 내부 격자 하나 건너, 다주형은 상면 주철근 하나 건너
- 양쪽이 높은 계단은 RB-S01 알림, RB-M04 폐지. 꺾임은 편 좌표의 한 개(꺾은 철근)
P1 문서값(C15 11 × 3852, C16 22 × 4070/4069, 137.5 · 10@250)을 그대로 재현한다.

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

87 lines
4.0 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 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);
});
});