/** * 교대 받침 카드 (2026-11-03, 시안 ② 교량받침·받침블럭·솔플레이트, 교각 카드와 같은 형식). 순수 함수. * * - 솔플레이트는 교각과 같은 규칙(C안, src/core/solplate-rule.ts)을 쓴다: 받침높이 H(솔플레이트 포함)는 교대 받침 시트 값 * (운곡교 A1 275 mm)으로 고정하고, 교직 L · 교축 W · 종단 기울기 S를 입력한다. 중심 두께 T = ⌈22 + L·|S|/200⌉, * T1·T2 = T ± L·|S|/200, 받침 본체 h3 = H − T. 기본값은 교각과 같은 1000 × 1100 · −0.5 % (가정, QA-15, 결정 2026-11-03). * - 높이: E = h1 (블럭 콘크리트) + h2 (몰탈패드 50) + h3 + h4 (= T) = h1 + h2 + H. z = 패드 상면 = EL.X. */ import { solplatePlan, solplateRuleIssues } from '../solplate-rule'; import type { SolplatePlan, SolplateRule } from '../types'; import { ABUT_PAD_MM, type AbutModel } from './geometry'; import type { AbutMembers } from './members'; export interface AbutBearingRule { /** 솔플레이트 교직 L (mm). */ lengthMm: number; /** 솔플레이트 교축 W (mm). */ widthMm: number; /** 종단 기울기 S (%). */ slopePct: number; } /** 기본값: 교각 솔플레이트 규칙과 같은 1000 × 1100 · −0.5 % (QA-15, 사용자 결정 2026-11-03). */ export const ABUT_BEARING_RULE_DEFAULT: AbutBearingRule = { lengthMm: 1000, widthMm: 1100, slopePct: -0.5 }; /** 받침블럭 평면 (mm, 정사각) and 몰탈패드 평면 (mm), spec_abut §4.2. */ export const ABUT_BLOCK_MM = 1200; export const ABUT_PAD_PLAN_MM = 1000; /** I01: 블럭 콘크리트 높이가 피복 50 + 철근 지름 13보다 작으면 블럭 철근을 교좌면 아래로 묻는다 (spec_abut §6.6). */ const I01_MIN_MM = 50 + 13; const CIRCLED = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']; export function resolveBearingRule(changes?: Partial): AbutBearingRule { return { ...ABUT_BEARING_RULE_DEFAULT, ...(changes ?? {}) }; } /** The pier rule with the 교대 받침높이 H (from the 받침 sheet). */ export function abutSolplateRule(rule: AbutBearingRule, heightMm: number): SolplateRule { return { heightMm, lengthMm: rule.lengthMm, widthMm: rule.widthMm, slopePct: rule.slopePct }; } export function abutSolplatePlan(rule: AbutBearingRule, heightMm: number): SolplatePlan { return solplatePlan(abutSolplateRule(rule, heightMm)); } export interface AbutElevationRow { no: number; /** IP3(① 쪽)에서 잰 위치 (m). */ sFromIp3M: number; seg: string; seatEl: number; h1: number; h2: number; /** 패드 상면 = EL.X (m). */ z: number; h3: number; h4: number; E: number; /** 솔플레이트 상면 EL (m) = 교좌면 + E. */ solplateTopEl: number; /** 블럭 끝에서 가장 가까운 계단 경계까지 (m). 경계가 없으면 null. */ stepGapM: number | null; /** 받침 방위각 (deg): CAD (엑셀 값, 동 = 0° 반시계) / 진북 (북에서 시계). 입력이 없으면 null. */ azimuthCadDeg: number | null; azimuthNorthDeg: number | null; /** 블럭 평면 회전 (deg), 교대축 직각 기준. */ rotDeg: number; } export function abutElevationRows(model: AbutModel, rule: AbutBearingRule, heightMm: number): AbutElevationRow[] { const half = ABUT_BLOCK_MM / 2000; const plan = abutSolplatePlan(rule, heightMm); return model.bearings.map((b) => { const h1 = b.blockHeightMm - ABUT_PAD_MM; const E = h1 + ABUT_PAD_MM + plan.h3 + plan.T; const gaps = model.stepBounds.map((x) => Math.abs(x - b.s) - half); return { no: b.no, sFromIp3M: model.length - b.s, seg: CIRCLED[b.no - 1] ?? String(b.no), seatEl: b.seatEl, h1, h2: ABUT_PAD_MM, z: b.z, h3: plan.h3, h4: plan.T, E, solplateTopEl: b.seatEl + E / 1000, stepGapM: gaps.length ? Math.min(...gaps) : null, azimuthCadDeg: b.azimuthDeg === undefined ? null : (((90 - b.azimuthDeg) % 360) + 360) % 360, azimuthNorthDeg: b.azimuthDeg === undefined ? null : ((b.azimuthDeg % 360) + 360) % 360, rotDeg: b.rotDeg, }; }); } export interface AbutSolplateRow { no: number; /** 기울기 (%). */ S: number; L: number; W: number; T: number; T1: number; T2: number; /** 검산 문구 "T1 − T2 = L·|S|/100". */ check: string; } export function abutSolplateRows(model: AbutModel, rule: AbutBearingRule, heightMm: number): AbutSolplateRow[] { const p = abutSolplatePlan(rule, heightMm); return model.bearings.map((b) => ({ no: b.no, S: p.slopePct, L: p.L, W: p.W, T: p.T, T1: p.T1, T2: p.T2, check: `${(p.T1 - p.T2).toFixed(1)} = ${((p.L * Math.abs(p.slopePct)) / 100).toFixed(1)}`, })); } /** Input problems of the 교대 받침 규칙 (the pier rule's checks with the 교대 H). Empty when it can build a solplate. */ export function abutBearingRuleIssues(rule: AbutBearingRule, heightMm: number): string[] { return solplateRuleIssues(abutSolplateRule(rule, heightMm)); } /** 검사 줄 (시안): W02 블럭 콘크리트, W04 블럭이 교좌면 안, W03 블럭 끝 ~ 계단 경계, I01 블럭 철근. 문제 없으면 "없음". */ export function abutBearingChecks(model: AbutModel, members: AbutMembers, rule: AbutBearingRule, heightMm: number): string[] { const rows = abutElevationRows(model, rule, heightMm); const minH1 = Math.min(...rows.map((r) => r.h1)); const half = ABUT_BLOCK_MM / 2000; // Block plan across the wall: centre t = frontSide × lateral, between the 흉벽 front (t 0) and the 벽체 front. const tCentres = model.bearings.map((b) => model.frontSide * b.lateral); const backGap = Math.min(...tCentres.map((t) => t - half)); const frontGap = Math.min(...tCentres.map((t) => members.shape.wallFrontM - (t + half))); const stepGaps = rows.map((r) => r.stepGapM).filter((g): g is number => g !== null); const minStep = stepGaps.length ? Math.min(...stepGaps) : null; return [ `블럭 콘크리트 ${minH1} > 0 (W02 ${minH1 > 0 ? '없음' : '있음'})`, `블럭이 교좌면 안 (흉벽 ${Math.round(backGap * 1000)} · 전면 ${Math.round(frontGap * 1000)}, W04 ${backGap >= 0 && frontGap >= 0 ? '없음' : '있음'})`, minStep === null ? '블럭 끝 ~ 계단 경계: 경계 없음 (W03 없음)' : `블럭 끝 ~ 계단 경계 최소 ${minStep.toFixed(3)} m (W03 ${minStep >= 0 ? '없음' : '있음'})`, `${minH1} ≥ ${I01_MIN_MM} → I01 ${minH1 >= I01_MIN_MM ? '없음' : '있음 (블럭 철근을 교좌면 아래로)'}`, ]; }