feat(rebar): B1–B4와 B5–B8 방향 교환 (B1–B4는 W 방향, B5–B8은 L 방향)
- 재료표(B5–B8 9150 = 9350 − 2c, B1–B4 7600 = 7800 − 2c)와 같은 방향 배정으로 맞춤 - 격자 키를 물리 방향으로 정리: sL = L 방향 간격, sW = W 방향 간격 (기존 sAxis → sL, sPerp → sW) - B1–B4: L 격자로 개수, 길이 W − 2c. B5–B8: W 격자로 개수, 길이 L − 2c - B9: 열(L)은 sL, 행(W)은 sW 격자 (물리 격자 일치), 개수 불변 - B11은 L 방향 간격(sL), B12는 W 방향 간격(sW) - 3D 확대기초 철근과 단면 그림의 역할을 교환 (B1–B4 선, B5–B8 점) - 철근표 간격과 규칙 값, 설정 라벨을 방향 기준으로 수정 - 테스트 기대값 갱신 (B1–B4 40, B5–B8 31, B6 13646 분할 경고) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f2fa48913b
commit
752de2fd9d
12 files changed
+160
-167
No files matched your search
@@ -9,17 +9,17 @@ const footingRows = (input: Parameters<typeof buildPier>[0]) =>
|
||||
const count = (rows: ReturnType<typeof footingRows>, name: string) => rows.find((r) => r.name === name)!.count;
|
||||
|
||||
describe('확대기초 철근은 입력 치수를 따른다 (3단계, 승인 2026-10-23)', () => {
|
||||
it('P1 footing rebar is unchanged with the default inputs (B1 31, B5 40, B9 306)', () => {
|
||||
it('P1 footing rebar is unchanged with the default inputs (B1 40, B5 31, B9 306; 방향 교환 후)', () => {
|
||||
const rows = footingRows(loadP1());
|
||||
expect(count(rows, 'B1')).toBe(31);
|
||||
expect(count(rows, 'B5')).toBe(40);
|
||||
expect(count(rows, 'B1')).toBe(40);
|
||||
expect(count(rows, 'B5')).toBe(31);
|
||||
expect(count(rows, 'B9')).toBe(306);
|
||||
});
|
||||
|
||||
it('a wider footing (W 9000) and a longer one (L 11000) add bars to B1–B4 and B5–B8', () => {
|
||||
const rows = footingRows({ ...loadP1(), footingWidthMm: 9000, footingLengthMm: 11000 });
|
||||
expect(count(rows, 'B1')).toBeGreaterThan(31);
|
||||
expect(count(rows, 'B5')).toBeGreaterThan(40);
|
||||
expect(count(rows, 'B1')).toBeGreaterThan(40);
|
||||
expect(count(rows, 'B5')).toBeGreaterThan(31);
|
||||
});
|
||||
|
||||
it('a different footing height (2200) is computed without error, and the 3D rebar still builds', () => {
|
||||
|
||||
@@ -20,8 +20,8 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초
|
||||
});
|
||||
|
||||
it('the grid spacing s reaches the grid positions and the rebar table', () => {
|
||||
const m = buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } });
|
||||
expect(m.footingRebar.sAxis).toBe(150);
|
||||
const m = buildPier({ ...loadP1(), footingRebar: { sL: 150 } });
|
||||
expect(m.footingRebar.sL).toBe(150);
|
||||
const xs = gridPositions(8000, m.footingRebar);
|
||||
expect(xs[1] - xs[0]).toBeCloseTo(150, 6);
|
||||
const b4 = rebarSummary(m).rows.find((r) => r.name === 'B4')!;
|
||||
@@ -36,7 +36,7 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초
|
||||
const b10 = base.find((r) => r.name === 'B10-1')!;
|
||||
expect([b10.spacing, b10.basis]).toEqual(['200', 'default']);
|
||||
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } })).rows;
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sL: 150 } })).rows;
|
||||
const b2s = rows.find((r) => r.name === 'B2')!;
|
||||
expect([b2s.spacing, b2s.actual, b2s.basis]).toEqual(['300', '150', 'input']);
|
||||
const b1s = rows.find((r) => r.name === 'B1')!;
|
||||
@@ -44,18 +44,20 @@ describe('확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초
|
||||
expect(rows.find((r) => r.name === 'B10-1')!.basis).toBe('default');
|
||||
});
|
||||
|
||||
it('방향 (문서 기준, 승인 2026-10-26): B1–B4·B9·B12는 교각축(sAxis), B5–B8·B11은 교각축 직각(sPerp)', () => {
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sPerp: 150 } })).rows;
|
||||
// 방향 교환 (승인 2026-10-26): sL = spacing along L (B1–B4, B9 columns, B11); sW = spacing along W (B5–B8, B9 rows, B12).
|
||||
it('방향 (교환 후): sW는 B5–B8·B12, sL은 B1–B4·B9·B11', () => {
|
||||
const rows = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sW: 150 } })).rows;
|
||||
expect(rows.find((r) => r.name === 'B5')!.spacing).toBe('300');
|
||||
expect(rows.find((r) => r.name === 'B5')!.basis).toBe('input');
|
||||
expect(rows.find((r) => r.name === 'B11')!.spacing).toBe('1200 (8s)');
|
||||
expect(rows.find((r) => r.name === 'B12')!.basis).toBe('default');
|
||||
expect(rows.find((r) => r.name === 'B12')!.spacing).toBe('1200 (8s)');
|
||||
expect(rows.find((r) => r.name === 'B11')!.basis).toBe('default');
|
||||
expect(rows.find((r) => r.name === 'B1')!.basis).toBe('default');
|
||||
|
||||
const rowsAxis = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sAxis: 150 } })).rows;
|
||||
expect(rowsAxis.find((r) => r.name === 'B9')!.spacing).toBe('600 (4s)');
|
||||
expect(rowsAxis.find((r) => r.name === 'B12')!.spacing).toBe('1200 (8s)');
|
||||
expect(rowsAxis.find((r) => r.name === 'B5')!.basis).toBe('default');
|
||||
const rowsL = rebarSummary(buildPier({ ...loadP1(), footingRebar: { sL: 150 } })).rows;
|
||||
expect(rowsL.find((r) => r.name === 'B1')!.spacing).toBe('300');
|
||||
expect(rowsL.find((r) => r.name === 'B9')!.spacing).toBe('600 (4s)');
|
||||
expect(rowsL.find((r) => r.name === 'B11')!.spacing).toBe('1200 (8s)');
|
||||
expect(rowsL.find((r) => r.name === 'B5')!.basis).toBe('default');
|
||||
});
|
||||
|
||||
it('k 배수: k 4 → 간격 500, 개수 60 / 47 (문서 4s 값)', () => {
|
||||
|
||||
@@ -17,16 +17,15 @@ describe('확대기초 교각축 직각 단면 (폭 W) (승인 2026-10-26, 추
|
||||
const svg = footingSectionSvg(m);
|
||||
|
||||
it('point and line counts match the rebar table families', () => {
|
||||
const bottom = bottomBars(L, W, H, fs);
|
||||
expect(countMark(svg, 'b1', 'B1')).toBe(bottom.find((b) => b.name === 'B1')!.count);
|
||||
expect(countMark(svg, 'b2', 'B2')).toBe(bottom.find((b) => b.name === 'B2')!.count);
|
||||
expect(countMark(svg, 'b4', 'B4')).toBe(bottom.find((b) => b.name === 'B4')!.count);
|
||||
const cross = crossBars(L, W, H, fs);
|
||||
// B1–B4 run along W: one line per family in this section; B5–B8 run along L: points on the W grid (방향 교환).
|
||||
for (const name of ['B1', 'B2', 'B3', 'B4']) expect(countMark(svg, name.toLowerCase(), name)).toBe(1);
|
||||
for (const name of ['B5', 'B6', 'B7', 'B8']) expect(countMark(svg, name.toLowerCase(), name)).toBe(cross.find((b) => b.name === name)!.count);
|
||||
expect(countMark(svg, 'b9', 'B9')).toBe(b9Bars(L, W, [], 2000, fs).rows);
|
||||
expect(countMark(svg, 'b10', 'B10-1')).toBe(sideBars(L, W, H, fs).find((b) => b.name === 'B10-1')!.count);
|
||||
expect(countMark(svg, 'b11', 'B11')).toBe(assemblyJs(H, fs).length);
|
||||
expect(countMark(svg, 'b12', 'B12')).toBe(assemblyBars(L, W, H, fs).find((b) => b.name === 'B12')!.count);
|
||||
for (const name of ['B5', 'B6', 'B7', 'B8']) expect(countMark(svg, 'b5', name)).toBe(1);
|
||||
expect(crossBars(L, W, H, fs).length).toBe(4);
|
||||
expect(cross.length).toBe(4);
|
||||
});
|
||||
|
||||
it('shows the width, the grid string, and the B10 levels as dimensions', () => {
|
||||
|
||||
+24
-23
@@ -31,24 +31,25 @@ describe('확대기초 철근 (확대기초철근 (1).md, plan C5)', () => {
|
||||
expect(z.B7).toBeCloseTo(277, 6);
|
||||
});
|
||||
|
||||
it('B2 leg 1723, B2 length 13646 (over the 12000 bar, splice needed)', () => {
|
||||
// 방향 교환 (승인 2026-10-26): B1–B4 run along W (grid L, 79 steps → 80 positions); B5–B8 run along L (grid W).
|
||||
it('B2 leg 1723, B2 length 11396 (W − 2c + 2 × 1723), count 40', () => {
|
||||
expect(legB2(H)).toBeCloseTo(1723, 6);
|
||||
const b2 = bottomBars(L, W, H).find((b) => b.name === 'B2')!;
|
||||
expect(b2.length).toBeCloseTo(13646, 0);
|
||||
expect(b2.count).toBe(31);
|
||||
expect(b2.length).toBeCloseTo(11396, 0);
|
||||
expect(b2.count).toBe(40);
|
||||
});
|
||||
|
||||
it('B1–B4 counts 31 / 31 / 31 / 31 and B4 length 10200', () => {
|
||||
it('B1–B4 counts 40 / 40 / 40 / 40 (grid along L) and B4 length 7950 (W − 2c)', () => {
|
||||
const bars = bottomBars(L, W, H);
|
||||
expect(bars.map((b) => b.count)).toEqual([31, 31, 31, 31]);
|
||||
expect(bars.find((b) => b.name === 'B4')!.length).toBeCloseTo(10200, 6);
|
||||
expect(bars.map((b) => b.count)).toEqual([40, 40, 40, 40]);
|
||||
expect(bars.find((b) => b.name === 'B4')!.length).toBeCloseTo(7950, 6);
|
||||
});
|
||||
|
||||
it('B5–B8 counts 40 each (grid along L: n 79), B8 length 7950, B6 length 11396', () => {
|
||||
it('B5–B8 counts 31 each (grid along W: n 61), B8 length 10200 (L − 2c), B6 length 13646', () => {
|
||||
const bars = crossBars(L, W, H);
|
||||
expect(bars.map((b) => b.count)).toEqual([40, 40, 40, 40]);
|
||||
expect(bars.find((b) => b.name === 'B8')!.length).toBeCloseTo(7950, 6);
|
||||
expect(bars.find((b) => b.name === 'B6')!.length).toBeCloseTo(11396, 0);
|
||||
expect(bars.map((b) => b.count)).toEqual([31, 31, 31, 31]);
|
||||
expect(bars.find((b) => b.name === 'B8')!.length).toBeCloseTo(10200, 6);
|
||||
expect(bars.find((b) => b.name === 'B6')!.length).toBeCloseTo(13646, 0);
|
||||
});
|
||||
|
||||
it('B10-1 16 bars ≈ 10748 (lowest level removed), B10-2 16 bars ≈ 8494', () => {
|
||||
@@ -108,29 +109,29 @@ describe('확대기초 배근도 재현 (확대기초철근 (1).md §13)', () =>
|
||||
expect(h.B12.map((z) => Math.round(z * 10) / 10)).toEqual([569.1, 969.1, 1369.1]);
|
||||
});
|
||||
|
||||
it('배근도 재현 W 7800 → B4 30, B2 29 (odd count split)', () => {
|
||||
const bars = bottomBars(10400, 7800, 2000);
|
||||
expect(bars.find((b) => b.name === 'B4')!.count).toBe(30);
|
||||
expect(bars.find((b) => b.name === 'B2')!.count).toBe(29);
|
||||
it('배근도 재현 W 7800 → B8 30, B6 29 (B5–B8 follow W, odd count split)', () => {
|
||||
const bars = crossBars(10400, 7800, 2000);
|
||||
expect(bars.find((b) => b.name === 'B8')!.count).toBe(30);
|
||||
expect(bars.find((b) => b.name === 'B6')!.count).toBe(29);
|
||||
});
|
||||
|
||||
it('배근도 재현 L 9350 → B8 36, B6 35', () => {
|
||||
const bars = crossBars(9350, 8150, 2000);
|
||||
expect(bars.find((b) => b.name === 'B8')!.count).toBe(36);
|
||||
expect(bars.find((b) => b.name === 'B6')!.count).toBe(35);
|
||||
it('배근도 재현 L 9350 → B4 36, B2 35 (B1–B4 follow L)', () => {
|
||||
const bars = bottomBars(9350, 8150, 2000);
|
||||
expect(bars.find((b) => b.name === 'B4')!.count).toBe(36);
|
||||
expect(bars.find((b) => b.name === 'B2')!.count).toBe(35);
|
||||
});
|
||||
|
||||
it('variables: W 8000 with s 150 → 50 grid steps, e 150, B4 26 / B2 25; W 12000 → 92 steps, e 150', () => {
|
||||
const g = grid(8000, { ...DEFAULT_FOOTING_REBAR, sAxis: 150 });
|
||||
const g = grid(8000, { ...DEFAULT_FOOTING_REBAR, sL: 150 });
|
||||
expect(g.n).toBe(50);
|
||||
expect(g.e).toBeCloseTo(150, 6);
|
||||
expect(grid(12000).n).toBe(92);
|
||||
expect(grid(12000).e).toBeCloseTo(150, 6);
|
||||
});
|
||||
|
||||
it('splice cases: L 8700 → B2 11946 (no splice); L 12400 → B4 12200; W 8800 → B6 12046', () => {
|
||||
expect(bottomBars(8700, 8150, 2000).find((b) => b.name === 'B2')!.length).toBeCloseTo(11946, 0);
|
||||
expect(bottomBars(12400, 8150, 2000).find((b) => b.name === 'B4')!.length).toBeCloseTo(12200, 0);
|
||||
expect(crossBars(10400, 8800, 2000).find((b) => b.name === 'B6')!.length).toBeCloseTo(12046, 0);
|
||||
it('splice cases: W 8700 → B2 11946 (no splice); W 12400 → B4 12200; L 8800 → B6 12046', () => {
|
||||
expect(bottomBars(10400, 8700, 2000).find((b) => b.name === 'B2')!.length).toBeCloseTo(11946, 0);
|
||||
expect(bottomBars(10400, 12400, 2000).find((b) => b.name === 'B4')!.length).toBeCloseTo(12200, 0);
|
||||
expect(crossBars(8800, 8150, 2000).find((b) => b.name === 'B6')!.length).toBeCloseTo(12046, 0);
|
||||
});
|
||||
});
|
||||
@@ -41,11 +41,11 @@ describe('② 부재별 fck (spec §6.2 · ②)', () => {
|
||||
expect(changedIn(rows, 'column').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('a footing fck changes only the footing rows (B2 splice)', () => {
|
||||
it('a footing fck changes only the footing rows (B6 splice, 방향 교환)', () => {
|
||||
const rows = rebarSummary(model, DEFAULT_MATERIAL, { footing: 35 }).rows;
|
||||
expect(changedIn(rows, 'coping')).toEqual([]);
|
||||
expect(changedIn(rows, 'column')).toEqual([]);
|
||||
expect(changedIn(rows, 'footing')).toContain('B2');
|
||||
expect(changedIn(rows, 'footing')).toContain('B6');
|
||||
});
|
||||
|
||||
it('a per-member fck equal to the 공통 value changes nothing', () => {
|
||||
|
||||
@@ -35,21 +35,21 @@ describe('③ 철근표 요약 (P1)', () => {
|
||||
expect(count('P6')).toBe(32);
|
||||
});
|
||||
|
||||
it('footing: B1–B4 31 each, B5–B8 40 each, B10-1 16, B10-2 16, B11 30, B12 24 (k = 8)', () => {
|
||||
for (const n of ['B1', 'B2', 'B3', 'B4']) expect(count(n)).toBe(31);
|
||||
for (const n of ['B5', 'B6', 'B7', 'B8']) expect(count(n)).toBe(40);
|
||||
it('footing (방향 교환): B1–B4 40 each, B5–B8 31 each, B10-1 16, B10-2 16, B11 30, B12 24 (k = 8)', () => {
|
||||
for (const n of ['B1', 'B2', 'B3', 'B4']) expect(count(n)).toBe(40);
|
||||
for (const n of ['B5', 'B6', 'B7', 'B8']) expect(count(n)).toBe(31);
|
||||
expect(count('B10-1')).toBe(16);
|
||||
expect(count('B10-2')).toBe(16);
|
||||
expect(count('B11')).toBe(30);
|
||||
expect(count('B12')).toBe(24);
|
||||
});
|
||||
|
||||
it('B9: 306 bars (320 on the grid less 14 in the column zone); warnings: B2 needs a splice (13646 > 12000)', () => {
|
||||
it('B9: 306 bars (320 on the grid less 14 in the column zone); warnings: B6 needs a splice (13646 > 12000)', () => {
|
||||
expect(count('B9')).toBe(306);
|
||||
expect(warnings.map((w) => w.code)).toEqual(['I02', 'RB-S04', 'RB-F08']); // defaults are assumed values (I02); bearing embedment 619–669 < 882 (RB-S04)
|
||||
expect(warnings.find((w) => w.code === 'RB-F08')!.bar).toBe('B2');
|
||||
// B2 is split into two pieces (추천안): each piece is about half the 13646 bar plus half the lap.
|
||||
expect(rows.find((r) => r.name === 'B2')!.length!).toBeCloseTo(7776, 0);
|
||||
expect(warnings.find((w) => w.code === 'RB-F08')!.bar).toBe('B6');
|
||||
// B6 is split into two pieces (추천안): each piece is about half the 13646 bar plus half the lap.
|
||||
expect(rows.find((r) => r.name === 'B6')!.length!).toBeCloseTo(7776, 0);
|
||||
});
|
||||
|
||||
it('lengths from the documents: C13 11746, C14 3447.5, C15 3852, C16 4070, P3 ≈ C_t + 1.3·l_d, P5 ≈ C_t', () => {
|
||||
|
||||
Reference in new issue
Block a user