- 사용자 도면(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>
98 lines
4.6 KiB
TypeScript
98 lines
4.6 KiB
TypeScript
/**
|
||
* 교대 부재 · 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,
|
||
};
|
||
}
|