Files
Pier_v1/src/core/abut/members.ts
T
ypshimandClaude Haiku 5.5 5e64a4f2ed feat(abut): 벽체 높이 H_w = 받침 ① 교좌면 ~ 흉벽 상단 연직 (기본 3326)
- 사용자 도면(2026-11-04): 받침 ① 위치에서 받침 ① 교좌면부터 흉벽 상단까지 연직 거리. 모든 교대 기본값 3326 (QA-4 가정 2500 대체)
- 입력 이름 "벽체 높이 · 받침 ① 기준 연직", ② 정면도에 받침 ① 기준선과 H_w 치수
- ③ 흉벽 철근표 대표 단면 = 받침 ① 위치 (P1–P3는 그 단면에서 정확히 계산)
- 테스트: 흉벽 상단 IP1 48.006 / IP3 47.661 등 + 0.826 반영, 교대흉벽철근.md §11 재현은 2500을 넣어 유지, 3326 기본 확인 (P1 3945)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-04 17:20:05 +09:00

98 lines
4.6 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.
/**
* 교대 부재 · AA3 (2026-11-03, spec_abut §4.3–4.6): 벽체, 흉벽 상단, 저판, 말뚝의 치수와 위치. Pure.
*
* - 교대축 기준 t: 전면 쪽이 +. 벽체 전면 +1.6 m (교좌면 끝), 배면 −0.8 m (브라켓 배면). 두께 2.4 m.
* - 흉벽: t −0.5 ~ 0 m. 상단 EL(s) = 받침 ① 교좌면 EL + 벽체 높이 H_w (받침 ① 기준 연직, 기본 3.326 m) + i_b × (s − s_1), i_b = 받침 z의 축 방향 경사.
* - 저판: 하면 = 기초 하단 EL, 두께 1.1 m. 폭은 t −1.8 ~ +2.8 m (앞굽 1.2, 뒷굽 1.0, 벽체 2.4). 길이 = 교좌면 길이 L.
* - 말뚝: 열 위치 t = −1.15 / +0.85 / +2.15 m. 열 안 본수 = int((L − 1.3)/2.0) + 1, 양끝 0.65 m 띄움, 균등 간격.
* 말뚝 선단 EL = 기초 하단 EL − 말뚝길이. (skew 평행사변형에서 열 끝이 밀리는 보정은 아직 없다 — QA-5 이후.)
* - 벽체 높이 = 구간 교좌면 EL − (기초 하단 EL + 저판 두께).
*/
import type { AbutModel } from './geometry';
import { resolveShape, type AbutShape, type ResolvedShape } from './shape';
export interface WallSegment {
/** 축 위 구간 범위 (IP1에서 m). */
sFrom: number;
sTo: number;
/** 구간의 교좌면 EL (구간 i). */
seatEl: number;
/** 벽체 높이 (m) = seat − (footing bottom + footing thickness). */
heightM: number;
}
export interface PileRow {
/** 횡방향 위치 t (m, 전면 +). */
t: number;
/** 축 위 말뚝 위치 (IP1에서 m). */
s: number[];
}
export interface AbutMembers {
/** The shape values in use (defaults merged with the user's changes), in m. */
shape: ResolvedShape;
wallSegments: WallSegment[];
/** 흉벽 상단 EL: s = 0 (IP1 끝)과 s = L (IP3 끝), 축 방향 경사 i_b (소수, + = IP3 쪽으로 올라감). */
parapetTopStartEl: number;
parapetTopEndEl: number;
parapetSlope: number;
footingTopEl: number;
footingBottomEl: number | undefined;
piles: PileRow[];
pileBottomEl: number | undefined;
}
/** 흉벽 상단 EL at axial position s (m). */
export function parapetTopEl(seatFirst: number, bearingSFirst: number, slope: number, s: number, parapetHeightM: number): number {
return seatFirst + parapetHeightM + slope * (s - bearingSFirst);
}
export function buildMembers(model: AbutModel, foundation: { bottomEl?: number; pileLengthMm?: number } = {}, shapeChanges?: Partial<AbutShape>): AbutMembers {
const L = model.length;
const shape = resolveShape(shapeChanges);
const sorted = [...model.bearings].sort((a, b) => a.s - b.s);
const bounds = [0, ...model.stepBounds, L];
const footingBottomEl = foundation.bottomEl;
const footingTopEl = footingBottomEl === undefined ? NaN : footingBottomEl + shape.footingThicknessM;
// 인접 무시 기준 (2026-11-03): a step smaller than shape.seatStepIgnoreM joins the segment before it (one 교좌면 EL).
const wallSegments: WallSegment[] = [];
sorted.forEach((b, k) => {
const prev = wallSegments[wallSegments.length - 1];
// Compared with the neighbouring bearing (인접), not with the first step of the joined run.
if (prev && k > 0 && Math.abs(b.seatEl - sorted[k - 1].seatEl) < shape.seatStepIgnoreM) {
prev.sTo = bounds[k + 1];
return;
}
wallSegments.push({ sFrom: bounds[k], sTo: bounds[k + 1], seatEl: b.seatEl, heightM: b.seatEl - footingTopEl });
});
// 받침 ①(첫 입력)과 마지막 입력 받침이 흉벽 상단선을 정한다 (spec §4.4).
const first = model.bearings[0];
const last = model.bearings[model.bearings.length - 1];
const slope = (last.z - first.z) / (last.s - first.s);
const parapetTopStartEl = parapetTopEl(first.seatEl, first.s, slope, 0, shape.parapetHeightM);
const parapetTopEndEl = parapetTopEl(first.seatEl, first.s, slope, L, shape.parapetHeightM);
// 본수 규칙 int((L − 2·연단)/간격) + 1; 열별 본수가 0이 아니면 그 값 (2026-11-04, 시안 열별 말뚝 본수).
const ruleCount = Math.floor((L - 2 * shape.pileEdgeM) / shape.pileSpacingM) + 1;
const piles: PileRow[] = shape.pileRowsT.map((t, r) => {
const count = shape.pileCounts[r] > 0 ? shape.pileCounts[r] : ruleCount;
const spacing = count > 1 ? (L - 2 * shape.pileEdgeM) / (count - 1) : 0;
return { t, s: Array.from({ length: count }, (_, k) => shape.pileEdgeM + k * spacing) };
});
const pileBottomEl = footingBottomEl === undefined || foundation.pileLengthMm === undefined ? undefined : footingBottomEl - foundation.pileLengthMm / 1000;
return {
shape,
wallSegments,
parapetTopStartEl,
parapetTopEndEl,
parapetSlope: slope,
footingTopEl,
footingBottomEl,
piles,
pileBottomEl,
};
}