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>
This commit is contained in:
1 parent
9f336e2be4
commit
7340f8c1a1
8 files changed
+263
-105
No files matched your search
@@ -0,0 +1,86 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user