fix(abut-rebar): 벽체 W7 skew 길이 · W11 ㄷ형 · RB-AB06 피복 경고
- W7 길이에 1/sin θ 적용 (2594 → 2603) - W11 위 갈고리 추가, 아래 · 위 모두 교대축 안쪽 ㄷ형 (3433/3717 → 3679/3963), 계단 단면 삽도에 반영 - W9 · W10 연장부 피복 < c이면 RB-AB06 경고만 (A1 44.9) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a4c7bcabcf
commit
34959f5b4e
3 files changed
+32
-14
No files matched your search
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* - W1 전면 연직 (cycle-1만) / W3 · W4 배면 연직 (cycle-1 / 2): 저판 격자 − (d_B13 + d_W)/2 (§4.3).
|
||||
* - W2 교좌면 상면 (교대축 직각, skew) / W9 · W10 계단 상면 (교대축 방향, 끝 계단 / 중간 계단).
|
||||
* - W5 · W6 전면 · 배면 수평 (계단에서 끊음) / W7 간격재 / W8 끝면 수평 (ㄷ형) / W11 끝면 연직.
|
||||
* - W5 · W6 전면 · 배면 수평 (계단에서 끊음) / W7 간격재 / W8 끝면 수평 (ㄷ형) / W11 끝면 연직 (ㄷ형).
|
||||
* - 간격은 벽체 기본간격(s_L, s_W, s_v)에서 계산한다. 묶음별로 고치면 그 묶음만 연동이 끊긴다(RB-AB04).
|
||||
* - 좌표: u = 교대축을 따라 IP1 쪽 끝면에서 잰 skew 격자 위치 (끝면과 나란한 선이 u 일정), t = 교대축에서 전면 +,
|
||||
* z = 저판 하면에서. 저판 배근(footing.ts)과 같은 u를 쓴다.
|
||||
@@ -243,14 +243,19 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
const endIn = (size: BarSize) => (c + (d(SZ.W11) + d(size)) / 2) / sin;
|
||||
|
||||
// W9 · W10 (§5.2): W2 바로 아래, 낮은 이웃 구간으로 l_d 연장, 높은 이웃 쪽은 경계에서 끝.
|
||||
// 연장 부분이 그 이웃 교좌면 아래 피복 c를 지키지 못하면 경고만 한다 (RB-AB06, 사용자 결정 2026-11-04).
|
||||
let thinCover = Infinity;
|
||||
segs.forEach((sg, k) => {
|
||||
const mark: WallMark = k === 0 || k === last ? 'W9' : 'W10';
|
||||
const z = g.HF + sg.H - c - (d(SZ.W2) + d(SZ.W9)) / 2;
|
||||
for (const nb of [segs[k - 1], segs[k + 1]]) if (nb && nb.H < sg.H) thinCover = Math.min(thinCover, g.HF + nb.H - z);
|
||||
const a = k === 0 ? endIn(SZ.W9) : segs[k - 1].H < sg.H ? sg.from - ld.H16 : sg.from;
|
||||
const b = k === last ? g.L - endIn(SZ.W9) : segs[k + 1].H < sg.H ? sg.to + ld.H16 : sg.to;
|
||||
for (const x of xW9) put(mark, [[a, x, z], [b, x, z]], b - a, sg === one || mark === 'W10');
|
||||
});
|
||||
|
||||
if (thinCover < c - 1e-6) add('RB-AB06', 'W', `W9 · W10이 낮은 이웃 구간으로 l_d 연장되는 부분의 피복이 ${lenText(thinCover)}로 ${lenText(c)}보다 작습니다 (계단 높이 차 확인).`);
|
||||
|
||||
// W5 · W6 (§5.3): 단 z_k = H_F + c + k·s_v, 들어가는 구간이 이어진 범위마다 한 줄. 계단 쪽 끝 = 경계 − c.
|
||||
const levelsOf = (H: number) => Math.floor((H - 2 * c) / sHoriz + 1e-9) + 1;
|
||||
const zMax = g.HF + Math.max(...segs.map((sg) => sg.H)) - c;
|
||||
@@ -277,12 +282,12 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
});
|
||||
if (segs.length > 1 && levels.every((lv) => lv.count === levels[0].count)) add('RB-AB05', 'I', '계단 높이 차가 작아 수평철근 단 수가 구간마다 같습니다 (W5 · W6 끊김 없음).');
|
||||
|
||||
// W7 (§6): 교대축 @k_7L·s_L, 연직 @k_7v·s_v, W3 선 ~ W5 선 + 양끝 90° 갈고리.
|
||||
// W7 (§6): 교대축 @k_7L·s_L, 연직 @k_7v·s_v, W3 선 ~ W5 선 (skew, 1/sin θ, 사용자 결정 2026-11-04) + 양끝 90° 갈고리.
|
||||
const h13 = hook(SZ.W7);
|
||||
const g7 = gridLayout(g.L, c / sin, s.eMin, s.k7L * sVert);
|
||||
for (const u of g7.positions) {
|
||||
for (let z = g.HF + c; z <= zTop(u) - c + 1e-9; z += s.k7V * sHoriz) {
|
||||
put('W7', [[u, xW34, z - h13], [u, xW34, z], [u, xW5, z], [u, xW5, z - h13]], xW5 - xW34 + 2 * h13, inOne(u));
|
||||
put('W7', [[u, xW34, z - h13], [u, xW34, z], [u, xW5, z], [u, xW5, z - h13]], (xW5 - xW34) / sin + 2 * h13, inOne(u));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,8 +307,8 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
const zh16 = zHook.H16;
|
||||
for (const [u, dir, sg] of [[c / sin, 1, segs[0]], [g.L - c / sin, -1, segs[last]]] as const) {
|
||||
const top = g.HF + sg.H - c;
|
||||
// The hook turns inward along the axis (the end face has no concrete outside).
|
||||
for (const x of [...xs11, ...seat11]) put('W11', [[u + dir * h16, x, zh16], [u, x, zh16], [u, x, top]], top - zh16 + h16, sg === one);
|
||||
// ㄷ형 (사용자 결정 2026-11-04): 아래 · 위 90° 갈고리 모두 교대축 안쪽으로 (끝면 밖에는 콘크리트가 없음).
|
||||
for (const x of [...xs11, ...seat11]) put('W11', [[u + dir * h16, x, zh16], [u, x, zh16], [u, x, top], [u + dir * h16, x, top]], top - zh16 + 2 * h16, sg === one);
|
||||
}
|
||||
|
||||
// 표 (시안 순서).
|
||||
@@ -330,12 +335,12 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
|
||||
const lvText = levels.map((lv) => `구간 ${lv.no} ${lv.count}단`).join(' · ');
|
||||
push({ mark: 'W5', size: SZ.W5, role: '수평', spacing: hSp, actual: hSp, shape: '직선 · 계단에서 끊음', position: `전면 최외측 x ${lenText(xW5)} · 교대축`, note: `${lvText} · 끝은 W11 안쪽`, basis: '규칙', modified: hMod });
|
||||
push({ mark: 'W6', size: SZ.W6, role: '수평', spacing: hSp, actual: hSp, shape: '직선 · 계단에서 끊음', position: `배면 W3 · W4 안쪽 x ${lenText(xW6)} · 교대축`, note: 'W5와 같은 단', basis: '규칙', modified: hMod });
|
||||
push({ mark: 'W7', size: SZ.W7, role: '간격재', spacing: `${lenText(s.k7L * sVert)} × ${lenText(s.k7V * sHoriz)}`, actual: '—', shape: `양끝 90° 갈고리 ${h13}`, position: `내측 간격재 · W3 선 ~ W5 선`, note: `${s.k7L}·s × ${s.k7V}·s · 철근표 H13 · 길이는 기획서 §6 값 (skew 1/sin θ 미반영)`, basis: '규칙', modified: s.k7L !== 8 || s.k7V !== 4 });
|
||||
push({ mark: 'W7', size: SZ.W7, role: '간격재', spacing: `${lenText(s.k7L * sVert)} × ${lenText(s.k7V * sHoriz)}`, actual: '—', shape: `양끝 90° 갈고리 ${h13}`, position: `내측 간격재 · W3 선 ~ W5 선`, note: `${s.k7L}·s × ${s.k7V}·s · 철근표 H13 · skew 방향 (1/sin θ)`, basis: '규칙', modified: s.k7L !== 8 || s.k7V !== 4 });
|
||||
push({ mark: 'W8', size: SZ.W8, role: '수평', spacing: hSp, actual: hSp, shape: `ㄷ형 · 다리 l_d ${ld.H13}`, position: `양 끝면 (skew) · W11 안쪽 ${lenText(in8 * sin)}`, note: `낮은 끝 · 높은 끝 구간 높이로 단 수`, basis: '규칙', modified: hMod });
|
||||
const tSp = lenText(sTop);
|
||||
push({ mark: 'W9', size: SZ.W9, role: '계단 상면', spacing: tSp, actual: tSp, shape: `직선 · 낮은 쪽 이웃 구간으로 l_d ${ld.H16}`, position: `끝 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: `W2 바로 아래 · 첫 줄 ${lenText(xW9[0] ?? 0)} (W1 선 피함)`, basis: '규칙', modified: tMod });
|
||||
push({ mark: 'W10', size: SZ.W10, role: '계단 상면', spacing: tSp, actual: tSp, shape: `직선 · 낮은 쪽 이웃 구간으로 l_d ${ld.H16}`, position: `중간 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: '결정: W9 끝 계단 / W10 중간 계단', basis: '규칙', modified: tMod });
|
||||
push({ mark: 'W11', size: SZ.W11, role: '연직', spacing: `${lenText(s.sW)} / 300`, actual: '—', shape: `아래 90° 갈고리 ${h16}`, position: `끝면 최외측 연직 · x 0 … ${lenText(xW5)} @${lenText(s.sW)} + ${xs11.map(lenText).join(' · ')}`, note: '규격 H16 제안 (삽도 표기 없음) · 흉벽 P11로 이어짐', basis: '가정', modified: false });
|
||||
push({ mark: 'W11', size: SZ.W11, role: '연직', spacing: `${lenText(s.sW)} / 300`, actual: '—', shape: `ㄷ형 · 아래 · 위 90° 갈고리 ${h16} (안쪽)`, position: `끝면 최외측 연직 · x 0 … ${lenText(xW5)} @${lenText(s.sW)} + ${xs11.map(lenText).join(' · ')}`, note: '규격 H16 제안 (삽도 표기 없음) · 흉벽 P11로 이어짐', basis: '가정', modified: false });
|
||||
|
||||
return {
|
||||
settings: s,
|
||||
|
||||
@@ -97,7 +97,10 @@ export function abutWallElevationSvg(g: AbutWallGeom, r: AbutWallRebar, pick: Ab
|
||||
if (p.mark === 'W1') parts.push(seg('W1', [p.pts[1][0], p.pts[1][2]], [u1, z1], 0.7));
|
||||
else if (p.mark === 'W5') parts.push(seg('W5', [u0, z0], [u1, z1], 0.8));
|
||||
else if ((p.mark === 'W9' || p.mark === 'W10') && Math.abs(t0 - r.x.W9[0]) < 1e-6) parts.push(seg(p.mark, [u0, z0], [u1, z1], 2));
|
||||
else if (p.mark === 'W11' && Math.abs(t0 - w11Front) < 1e-6) parts.push(seg('W11', [p.pts[1][0], p.pts[1][2]], [u1, z1], 2));
|
||||
else if (p.mark === 'W11' && Math.abs(t0 - w11Front) < 1e-6) {
|
||||
// ㄷ형: 연직 + 위 갈고리 (아래 갈고리는 저판 안).
|
||||
parts.push(seg('W11', [p.pts[1][0], p.pts[1][2]], [p.pts[2][0], p.pts[2][2]], 2), seg('W11', [p.pts[2][0], p.pts[2][2]], [u1, z1], 2));
|
||||
}
|
||||
else if (p.mark === 'W8') parts.push(`<circle cx="${X(p.pts[1][0]).toFixed(1)}" cy="${Y(z0).toFixed(1)}" r="1.6" fill="${COLOR.W8}" opacity="${opacity(pick, ['W8'])}"/>`);
|
||||
}
|
||||
// 단 치수 (양끝).
|
||||
|
||||
@@ -92,8 +92,8 @@ describe('교대 벽체 배근 A1 (AD4, 교대벽체철근.md §11)', () => {
|
||||
expect(p.pts[1][0]).toBeCloseTo(lowRight ? sg.to + 735 : sg.to, 6);
|
||||
});
|
||||
|
||||
it('W7: 12 × 5 = 60, 2594', () => {
|
||||
expect([bar(A1, 'W7').count, bar(A1, 'W7').length]).toEqual([60, 2594]);
|
||||
it('W7: 12 × 5 = 60, 2200 / sin θ + 2 × 197 = 2603 (skew, 사용자 결정 2026-11-04)', () => {
|
||||
expect([bar(A1, 'W7').count, bar(A1, 'W7').length]).toEqual([60, 2603]);
|
||||
});
|
||||
|
||||
it('W8: 3383, 낮은 끝 18 + 높은 끝 20 = 38, 끝면에서 114.3', () => {
|
||||
@@ -102,15 +102,25 @@ describe('교대 벽체 배근 A1 (AD4, 교대벽체철근.md §11)', () => {
|
||||
expect(w8.pts[1][0] * angle.sin).toBeCloseTo(114.3, 6);
|
||||
});
|
||||
|
||||
it('W11: 끝면당 13 + 3 = 16, 양끝 32, 길이 3433 / 3717', () => {
|
||||
it('W11: 끝면당 13 + 3 = 16, 양끝 32, ㄷ형 (위 갈고리 246 추가) 길이 3679 / 3963', () => {
|
||||
const w = bar(A1, 'W11');
|
||||
expect([w.count, w.length, w.lengthMax]).toEqual([32, 3433, 3717]);
|
||||
expect([w.count, w.length, w.lengthMax]).toEqual([32, 3433 + 246, 3717 + 246]);
|
||||
const p = A1.paths.find((q) => q.mark === 'W11')!;
|
||||
expect(p.pts.length).toBe(4);
|
||||
expect(p.pts[0][0]).toBeCloseTo(p.pts[3][0], 6); // both hooks turn the same way (inward)
|
||||
expect(p.pts[3][0]).toBeGreaterThan(p.pts[1][0]);
|
||||
const xs = A1.paths.filter((p) => p.mark === 'W11').slice(0, 16).map((p) => Math.round(p.pts[1][1]));
|
||||
expect(xs).toEqual([-700, -400, -100, ...Array.from({ length: 13 }, (_, i) => i * 125)]);
|
||||
});
|
||||
|
||||
it('A1 has no warning (RB-AB02 · RB-AB03 없음)', () => {
|
||||
expect(A1.issues.filter((i) => i.level !== 'I').map((i) => i.code)).toEqual([]);
|
||||
it('A1 warns only RB-AB06: W9 · W10 연장부 피복 44.9 < 100 (계단 71.1)', () => {
|
||||
expect(A1.issues.filter((i) => i.level !== 'I').map((i) => i.code)).toEqual(['RB-AB06']);
|
||||
expect(A1.issues.find((i) => i.code === 'RB-AB06')!.message).toContain('44.9');
|
||||
});
|
||||
|
||||
it('RB-AB06 is gone when the steps are flat enough (높이 차 0)', () => {
|
||||
const flat = { ...geom, segments: geom.segments.map((s) => ({ ...s, H: 2372 })) };
|
||||
expect(abutWallRebar(flat, footing, undefined, fck27).issues.map((i) => i.code)).not.toContain('RB-AB06');
|
||||
});
|
||||
|
||||
it('l_d 연동: fck 30 → W2 앞 다리 · W3 위 끝이 짧아짐', () => {
|
||||
|
||||
Reference in new issue
Block a user