feat(abut-rebar): 교대 날개벽 배근 규칙 WR1–WR20 (AD7)
- 연직 WR1 · 2 · 11 · 12 (기준선 뒷굽 끝 − c, 등단면 올림 δ, 변화단면 끝 여유), 수평 WR3–5 · 13–15 (띠 3개, 맨 위 2단 경사와 나란히 Q-WR1, 교대 쪽 갈고리) - WR6 경사 하단, WR7-1 · 7-2 · 7-3 헌치 꺾임, WR9 헌치 대각 @2·s_h 자동 개수 (Q-WR3), WR10 모서리 꺾임, WR20 간격재. 간섭은 보지 않음 (Q-WR2 · Q-WR4) - 단축형: WR1 · WR11 · WR6 없음. 좌우는 ② 형상 그대로 (skew로 배면 길이 ± 두께 · cot φ) - 테스트: 교대날개벽철근.md §11 (fck 27, 벽체 높이 2500), 삽도 = 우측 날개벽 치수 재현 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3800a75b78
commit
06220fa312
2 files changed
+565
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildAbut } from '../src/core/abut/geometry';
|
||||
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
|
||||
import { buildMembers } from '../src/core/abut/members';
|
||||
import { buildWings } from '../src/core/abut/wings';
|
||||
import { abutFootingGeom, abutFootingRebar } from '../src/core/abut/rebar/footing';
|
||||
import { skewAngle } from '../src/core/abut/rebar/skew-layout';
|
||||
import { abutWingGeom, abutWingRebar, wingLevelCount } from '../src/core/abut/rebar/wing';
|
||||
import { DEFAULT_MATERIAL } from '../src/core/rebar/common';
|
||||
|
||||
// 예제 A1 좌측 날개벽 (교대날개벽철근.md §11). 기획서 값은 fck 27 · 벽체 높이 2500 기준 (흉벽 상단 46.835 / 47.180).
|
||||
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
||||
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
|
||||
const model = buildAbut(toAbutInput(rec));
|
||||
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, { parapetHeightMm: 2500 });
|
||||
const wings = buildWings(model, members);
|
||||
const footing = abutFootingRebar(abutFootingGeom(model.length, members.shape, skewAngle(rec.skewDeg)));
|
||||
const fck27 = { ...DEFAULT_MATERIAL, fck: 27 };
|
||||
const geomOf = (side: 'left' | 'right') => abutWingGeom(model, members, wings.find((w) => w.side === side)!, footing.z.B68);
|
||||
const left = geomOf('left');
|
||||
const L1 = abutWingRebar(left, undefined, fck27, 'A1');
|
||||
const bar = (r: typeof L1, mark: string) => r.bars.find((b) => b.mark === mark);
|
||||
|
||||
describe('교대 날개벽 배근 A1 좌 (AD7, 교대날개벽철근.md §11)', () => {
|
||||
it('geometry: 길이 6500, 뒷굽 끝 1807, 상단 시작 46.835 (저판 상면 위 4840), 일반형', () => {
|
||||
expect(left.form).toBe('general');
|
||||
expect(left.L).toBeCloseTo(6500, 3);
|
||||
expect(left.heelD).toBeCloseTo(1807, 0);
|
||||
expect(left.topStart - left.footTop).toBeCloseTo(4840, 0);
|
||||
});
|
||||
|
||||
it('삽도 재현 (삽도 = 우측 날개벽): 전면 100 · 193 · 18@250 · 4@250 · 707, 배면 100 · 193 · 36@125 · 9@125 · 622; 연직 WR12 5 + WR11 18, WR2 10 + WR1 36', () => {
|
||||
const R = abutWingRebar(geomOf('right'), undefined, fck27);
|
||||
expect(R.vertical.front.text).toBe('100 · 193 · 18@250=4.500 · 4@250=1.000 · 707');
|
||||
expect(R.vertical.back.text).toBe('100 · 193 · 36@125=4.500 · 9@125=1.125 · 621.7'); // 삽도 622 (두께 · cot φ 39.7로 반올림)
|
||||
expect(['WR12', 'WR11', 'WR2', 'WR1'].map((m) => bar(R, m)?.count)).toEqual([5, 18, 10, 36]);
|
||||
});
|
||||
|
||||
it('좌측은 skew로 배면이 두께 · cot φ = 39.6 짧음: 배면 기준선 1667.4 → 8@125 · 667.4, WR2 9 (전면은 같음)', () => {
|
||||
expect(L1.vertical.front.text).toBe('100 · 193 · 18@250=4.500 · 4@250=1.000 · 707');
|
||||
expect(L1.vertical.back.text).toBe('100 · 193 · 36@125=4.500 · 8@125=1.000 · 667.4');
|
||||
expect(['WR12', 'WR11', 'WR2', 'WR1'].map((m) => bar(L1, m)?.count)).toEqual([5, 18, 9, 36]);
|
||||
});
|
||||
|
||||
it('수평 단 (맨 위 2단 경사, 사용자 결정 2026-11-04): 전면 19 (교대 쪽) / 20 (끝), 배면 38 / 39', () => {
|
||||
expect([L1.levels.front.start, L1.levels.front.end, L1.levels.back.start, L1.levels.back.end]).toEqual([19, 20, 38, 39]);
|
||||
expect(wingLevelCount(4840, 100, 250)).toBe(19);
|
||||
});
|
||||
|
||||
it('WR6 2개, WR7-1 · 7-2 · 7-3 3 / 6 / 10단, WR10 38단', () => {
|
||||
expect(bar(L1, 'WR6')?.count).toBe(2);
|
||||
expect(L1.haunchLevels).toEqual({ 'WR7-1': 3, 'WR7-2': 6, 'WR7-3': 10 });
|
||||
expect(bar(L1, 'WR10')?.count).toBe(38);
|
||||
});
|
||||
|
||||
it('WR9: 아래 헌치 대각 (300 · √2 = 424) 위 @250 → 2개 (자동, Q-WR3)', () => {
|
||||
expect(bar(L1, 'WR9')?.count).toBe(Math.floor((300 * Math.SQRT2) / 250) + 1);
|
||||
});
|
||||
|
||||
it('우측: 상단은 우측 흉벽 상단 47.180 기준 (저판 상면 위 5185)', () => {
|
||||
const right = geomOf('right');
|
||||
expect(right.topStart - right.footTop).toBeCloseTo(5185, 0);
|
||||
});
|
||||
|
||||
it('단축형: WR1 · WR11 · WR6 없음 (QA-14)', () => {
|
||||
const shortW = buildWings(model, members, { left: 'short', right: 'general' }).find((w) => w.side === 'left')!;
|
||||
const S = abutWingRebar(abutWingGeom(model, members, shortW, footing.z.B68), undefined, fck27);
|
||||
expect(['WR1', 'WR11', 'WR6'].map((m) => bar(S, m))).toEqual([undefined, undefined, undefined]);
|
||||
expect(bar(S, 'WR2')!.count).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('모든 철근이 날개벽 · 교대 범위 안 (t: 벽체 전면 ~ 날개벽 끝, z: 저판 하면망 위 ~ 상단)', () => {
|
||||
const tMin = left.p0[1] + left.L * left.w[1] - 50;
|
||||
for (const p of L1.paths) {
|
||||
for (const [, t, z] of p.pts) {
|
||||
expect(t, p.mark).toBeGreaterThan(tMin);
|
||||
expect(t, p.mark).toBeLessThan(left.wallFrontT + 1);
|
||||
expect(z, p.mark).toBeLessThan(left.topStart + left.slope * left.L);
|
||||
expect(z, p.mark).toBeGreaterThan(footing.z.B68 - (p.mark === 'WR6' ? 800 : 400));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('간격 수정 → RB-AW03', () => {
|
||||
const r = abutWingRebar(left, { sHoriz: 150 }, fck27);
|
||||
expect(r.issues.map((i) => i.code)).toContain('RB-AW03');
|
||||
expect(bar(r, 'WR3')!.modified).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user