fix(abut-rebar): 벽체 W9 ㄷ형 · W10 ㄴ형, 정착장 입력 (기본 330)

- 사용자 도면(2026-11-04): 끝마다 이웃 높낮이로 정함 — 높은 이웃 쪽은 경계 너머 정착장만큼 직선 겹침, 낮은 이웃 · 끝면 쪽은 경계 − c에서 연직 갈고리 정착장
- 번호는 모양으로: 양끝 갈고리 W9 (ㄷ), 한쪽 갈고리 W10 (ㄴ). A1: W9 6 (IP1 끝 구간), W10 24
- 벽체 카드에 "W9 · W10 정착장 (mm)" 입력 (기본 330, fck 연동 없음), 철근표 · 삽도 · 안내 문구 반영

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-04 17:28:22 +09:00
1 parent 5e64a4f2ed
commit 3800a75b78
4 files changed
+69 -35

No files matched your search

+26 -13
View File
@@ -2,7 +2,7 @@
* 교대 벽체 배근 · AD4 (2026-11-04, 교대벽체철근.md, 결정 2026-11-02). 순수 함수, 길이는 mm.
*
* - W1 전면 연직 (cycle-1만) / W3 · W4 배면 연직 (cycle-1 / 2): 저판 격자 − (d_B13 + d_W)/2 (§4.3).
* - W2 교좌면 상면 (교대축 직각, skew) / W9 · W10 계단 상면 (교대축 방향, 끝 계단 / 중간 계단).
* - W2 교좌면 상면 (교대축 직각, skew) / W9 (ㄷ) · W10 (ㄴ) 계단 상면 (교대축 방향, 낮은 쪽 갈고리 · 높은 쪽 직선 겹침).
* - W5 · W6 전면 · 배면 수평 (계단에서 끊음) / W7 간격재 / W8 끝면 수평 (ㄷ형) / W11 끝면 연직 (ㄷ형).
* - 간격은 벽체 기본간격(s_L, s_W, s_v)에서 계산한다. 묶음별로 고치면 그 묶음만 연동이 끊긴다(RB-AB04).
* - 좌표: u = 교대축을 따라 IP1 쪽 끝면에서 잰 skew 격자 위치 (끝면과 나란한 선이 u 일정), t = 교대축에서 전면 +,
@@ -26,13 +26,15 @@ export interface AbutWallRebarSettings {
/** W7 간격 배수 (교대축 / 연직), 정수 ≥ 1. */
k7L: number;
k7V: number;
/** W9 · W10 정착장: 연직 갈고리 길이 = 높은 이웃 구간 철근과의 직선 겹침 길이 (사용자 도면 2026-11-04, 기본 330). */
la: number;
/** 묶음별 간격 수정 (없으면 기본간격에 연동): 연직 W1–W4 · W2 (교대축 실간격), 수평 W5 · W6 · W8 (연직), 계단 상면 W9 · W10 (폭). */
sVert?: number;
sHoriz?: number;
sTop?: number;
}
export const ABUT_WALL_REBAR_DEFAULT: AbutWallRebarSettings = { c: 100, eMin: 150, sL: 125, sW: 125, sV: 125, k7L: 8, k7V: 4 };
export const ABUT_WALL_REBAR_DEFAULT: AbutWallRebarSettings = { c: 100, eMin: 150, sL: 125, sW: 125, sV: 125, k7L: 8, k7V: 4, la: 330 };
/** 규격 (기획서 §1.2). W11 H16은 제안값 (삽도에 규격 표기 없음). */
export const ABUT_WALL_SIZES = {
@@ -109,7 +111,7 @@ export function resolveWallRebar(changes?: Partial<AbutWallRebarSettings>): Abut
/** Input problems in Korean (the edit refuses them). */
export function wallRebarIssues(s: AbutWallRebarSettings): string[] {
const out: string[] = [];
for (const key of ['c', 'eMin', 'sL', 'sW', 'sV'] as const) if (!(s[key] > 0)) out.push(`${key}은(는) 0보다 커야 합니다.`);
for (const key of ['c', 'eMin', 'sL', 'sW', 'sV', 'la'] as const) if (!(s[key] > 0)) out.push(`${key}은(는) 0보다 커야 합니다.`);
for (const key of ['sVert', 'sHoriz', 'sTop'] as const) if (s[key] !== undefined && !((s[key] as number) > 0)) out.push('철근 간격은 0보다 커야 합니다.');
if (!(Number.isInteger(s.k7L) && s.k7L >= 1 && Number.isInteger(s.k7V) && s.k7V >= 1)) out.push('W7 배수는 1 이상의 정수여야 합니다.');
return out;
@@ -157,8 +159,8 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
const sVert = s.sVert ?? s.sL;
const sHoriz = s.sHoriz ?? s.sV;
const sTop = s.sTop ?? 2 * s.sW;
if (s.sVert !== undefined || s.sHoriz !== undefined || s.sTop !== undefined || s.k7L !== 8 || s.k7V !== 4) {
add('RB-AB04', 'I', '철근 간격을 기본간격과 따로 고쳤거나 W7 배수가 기본값(8 × 4)이 아닙니다. [되돌리기]로 다시 연동합니다.');
if (s.sVert !== undefined || s.sHoriz !== undefined || s.sTop !== undefined || s.k7L !== 8 || s.k7V !== 4 || s.la !== 330) {
add('RB-AB04', 'I', '철근 간격을 기본간격과 따로 고쳤거나 W7 배수 · W9 · W10 정착장이 기본값(8 × 4 · 330)이 아닙니다. [되돌리기]로 다시 연동합니다.');
}
const fy = (size: BarSize) => fyOf(material, size);
const ldOf = (size: BarSize) => Math.ceil(developmentLength(d(size), fy(size), material.fck) - 1e-9);
@@ -242,14 +244,25 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
// 끝 여유 (§6.1): W5 · W6 · W9 · W10 끝은 W11 안쪽.
const endIn = (size: BarSize) => (c + (d(SZ.W11) + d(size)) / 2) / sin;
// W9 · W10 (§5.2, 사용자 도면 2026-11-04): W2 바로 아래, 구간마다 독립된 ⊓형. 양끝은 계단 경계 − c (끝면 쪽은 W11 안쪽)에서
// 아래로 l_d 꺾는다. 이웃 구간으로는 넘어가지 않는다.
// W9 · W10 (§5.2, 사용자 도면 2026-11-04): W2 바로 아래, 구간마다 한 줄. 끝마다 이웃 높낮이로 정한다:
// 높은 이웃 쪽 = 경계 너머 정착장 l_a만큼 직선 (그 구간 철근과 겹침), 낮은 이웃 · 끝면 쪽 = 경계 − c (끝면은 W11 안쪽)에서 연직 갈고리 l_a.
// 양끝 갈고리 = W9 (ㄷ), 한쪽 갈고리 = W10 (ㄴ). 번호는 모양으로 정한다 (사용자 결정 2026-11-04).
const la = s.la;
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;
const a = k === 0 ? endIn(SZ.W9) : sg.from + c;
const b = k === last ? g.L - endIn(SZ.W9) : sg.to - c;
for (const x of xW9) put(mark, [[a, x, z - ld.H16], [a, x, z], [b, x, z], [b, x, z - ld.H16]], b - a + 2 * ld.H16, sg === one || mark === 'W10');
const higher = (nb: (typeof segs)[number] | undefined) => nb !== undefined && nb.H >= sg.H;
const lapA = higher(segs[k - 1]);
const lapB = higher(segs[k + 1]);
const a = lapA ? sg.from - la : k === 0 ? endIn(SZ.W9) : sg.from + c;
const b = lapB ? sg.to + la : k === last ? g.L - endIn(SZ.W9) : sg.to - c;
const hooks = (lapA ? 0 : 1) + (lapB ? 0 : 1);
const mark: WallMark = hooks === 2 ? 'W9' : 'W10';
for (const x of xW9) {
const pts: [number, number, number][] = [[a, x, z], [b, x, z]];
if (!lapA) pts.unshift([a, x, z - la]);
if (!lapB) pts.push([b, x, z - la]);
put(mark, pts, b - a + hooks * la, true);
}
});
// W5 · W6 (§5.3): 단 z_k = H_F + c + k·s_v, 들어가는 구간이 이어진 범위마다 한 줄. 계단 쪽 끝 = 경계 − c.
@@ -334,8 +347,8 @@ export function abutWallRebar(g: AbutWallGeom, footing: AbutFootingRebar, change
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} (경계 − ${lenText(c)})`, 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} (경계 − ${lenText(c)})`, position: `중간 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: '결정: W9 끝 계단 / W10 중간 계단', basis: '규칙', modified: tMod });
push({ mark: 'W9', size: SZ.W9, role: '계단 상면', spacing: tSp, actual: tSp, shape: `ㄷ · 양끝 연직 갈고리 ${lenText(la)} (경계 − ${lenText(c)})`, position: `양쪽 이웃이 낮거나 끝면인 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: `W2 바로 아래 · 첫 줄 ${lenText(xW9[0] ?? 0)} (W1 선 피함) · 정착장 ${lenText(la)}`, basis: '규칙', modified: tMod || la !== 330 });
push({ mark: 'W10', size: SZ.W10, role: '계단 상면', spacing: tSp, actual: tSp, shape: `ㄴ · 한쪽 연직 갈고리 ${lenText(la)} · 반대쪽 높은 구간 철근과 직선 겹침 ${lenText(la)}`, position: `높은 이웃이 있는 계단 상면 · 교대축 · 폭 ${xW9.length}개`, note: '번호는 모양으로: 양끝 갈고리 W9 (ㄷ) / 한쪽 갈고리 W10 (ㄴ)', basis: '규칙', modified: tMod || la !== 330 });
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 {