Files
Pier_v1/tests/rebar-stairs.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

81 lines
3.8 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 { c14Bars, c14Length, copingSideBars, tStepBars, undersideAt } from '../src/core/rebar/coping';
import { loadP1 } from './helpers';
const m = buildPier(loadP1());
const L = m.copingL * 1000;
const B = m.copingB * 1000;
const side = copingSideBars(L, 41.288, 42.538);
const rows = [...side.c13, ...side.c12];
const under = (x: number) => undersideAt(L, x, m.endBottom, m.levelBottom);
describe('C14 / C15 / C16 (코핑철근.md §7–8)', () => {
it('C14: 2 upper lines of 11 (C2, C3) and lower rows 5 / 6 / 3 / 2 = 38', () => {
const bars = c14Bars(L, m.copingRefTop, rows, under);
const count = (line: number) => bars.filter((b) => b.line === line).length;
expect(count(1)).toBe(11);
expect(count(2)).toBe(11);
expect([3, 4, 5, 6].map(count)).toEqual([5, 6, 3, 2]);
expect(bars.length).toBe(38);
});
it('C14 heights: upper 43.208 / 43.108, lower 42.810 / 42.310 / 41.810 / 41.310 (상면 단 간격 100)', () => {
const bars = c14Bars(L, m.copingRefTop, rows, under);
const z = (line: number) => bars.find((b) => b.line === line)!.z;
expect(z(1)).toBeCloseTo(43.208, 3);
expect(z(2)).toBeCloseTo(43.108, 3);
expect([3, 4, 5, 6].map(z)).toEqual([42.81, 42.31, 41.81, 41.31].map((v) => expect.closeTo(v, 2)));
});
it('C14 length 3447.5 (2855.5 run + 2 × 296 legs)', () => {
expect(c14Length(B)).toBeCloseTo(3447.5, 0);
});
// P1 문서값 (§7) = 일반 규칙의 기준 (결정 2026-11-10): 모델의 계단 72/144/215/287 → C15 5구간, C16 3·4구간.
const steps = tStepBars(m).bars;
const c15 = steps.filter((b) => b.name === 'C15');
const c16 = steps.filter((b) => b.name === 'C16');
it('C15: 11 bars, z 43.598, length 3852 (2088 + 2 × 882), from 9.812 to 11.900', () => {
expect(c15.length).toBe(11);
expect(c15[0].z).toBeCloseTo(43.598, 3);
expect(c15[0].length).toBe(3852);
expect([c15[0].x0, c15[0].x1]).toEqual([9812, 11900]);
});
it('C16: 22 bars (3·4구간), z 43.436 / 43.507, lengths 4070 / 4069, leg at the low side', () => {
expect(c16.length).toBe(22);
expect(c16[0].z).toBeCloseTo(43.436, 3);
expect(c16[11].z).toBeCloseTo(43.507, 3);
expect(c16[0].length).toBe(4070);
expect(c16[11].length).toBe(4069);
expect([c16[0].x0, c16[0].x1, c16[11].x0, c16[11].x1]).toEqual([4801, 7989, 7307, 10494]);
expect(c16.every((b) => b.legs === 'start')).toBe(true);
});
it('C15 / C16 across positions: 137.5 from the cover line (문서 137), then @250, 11 bars', () => {
expect(c15[0].n * 1000).toBeCloseTo(-B / 2 + 237.5, 6);
expect((c15[10].n - c15[0].n) * 1000).toBeCloseTo(2500, 6);
});
});
describe('C14 연직 발 (코핑철근.md §8)', () => {
it('upper lines keep their legs; a lower bar keeps its legs only if they end above the underside', () => {
const bars = c14Bars(L, m.copingRefTop, rows, under);
expect(bars.filter((b) => b.line <= 2).every((b) => b.legs)).toBe(true);
// The leg of the C12 5단 bar (line 4) at the ends (x 1 m, 11 m) would end below the sloped underside, so it is left out.
const omitted = bars.filter((b) => !b.legs).map((b) => [b.line, Math.round(b.x / 1000)]);
expect(omitted).toEqual([[4, 1], [4, 11]]);
});
});
describe('C15 간격 = 2 × 주철근 기본간격 (승인 2026-10-26)', () => {
it('the default pitch is 250, and a 140 기본간격 moves the C15 bars to 280', () => {
const base = tStepBars(m).bars.filter((b) => b.name === 'C15');
expect(Math.round((base[1].n - base[0].n) * 1000)).toBe(250);
const wider = tStepBars(buildPier({ ...loadP1(), copingRebar: { mainSpacing: 140 } })).bars.filter((b) => b.name === 'C15');
expect(Math.round((wider[1].n - wider[0].n) * 1000)).toBe(280);
});
});