Files
Pier_v1/src/core/bearing-pair.ts
T
ypshimandClaude Opus 5.5 a0b0929e13 feat(bearing): 교각 솔플레이트를 2026-11-06 결정으로 (짝 받침 S · 열 T · 거더 하면 고정)
받침방위각_솔플레이트검토.md §3과 시안(Version 110–113)을 따른다 (사용자 결정 2026-11-06: 시안 적용).
11-03 C안(받침높이 H 고정, h3 = H − T, S 입력)을 대체한다.
- S: 교량 전체(A1, P1 … Pn, A2) 거더 짝 받침의 z 차 / 수평거리. 없으면 교각 종단경사 입력 →
  다른 열 같은 순번 값(가정, WS03) → 0 %(WS03). 받침별 직접 수정(수정됨)
- 평면 L = A + 100, W = B + 100 (받침별 A × B, 기본 700 × 640 가정), h3 181
- T = 열마다 max(22, ⌈22 + L·|S|/2⌉), 열 T 직접 수정 가능(T2 < T_min → WS02).
  T1은 오르막 쪽(3D 쐐기의 두꺼운 끝도 그쪽), T2 반대쪽
- 거더 하면 고정: 블럭 상면 = z − (T − T_ref), T_ref 30
- 짝 받침은 불러오기·프로젝트 열기 때 교량 전체로 계산해 교각 생성에 넘김
- ② 카드: h3·T_ref·T_min·여유·열 T·종단경사 입력, 표에 짝 받침·S 근거·받침별 S/A/B 수정·검산 W08·검사
- P1 검증(A1과 함께): S −1.908 … −1.902 %, L 800 · W 740 · T 30 · T1 38 · T2 22, 블럭 높이 변화 없음
- 블럭·솔플레이트 테스트는 A1–P1 짝 문맥으로 실행 (짝이 없으면 S 0 · T 22 · 블럭 +8 mm)
교대(QA-15 C안)와 Pier 시트 종단경사 열 읽기는 아직 남음.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:48:10 +09:00

115 lines
5.3 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.
/**
* 거더 짝 받침 · 종단경사 S · 솔플레이트 열 두께 (결정 2026-11-06, RAG/받침방위각_솔플레이트검토.md §3, Q-SH1·4·8·9).
* Pure. Used by src/core/sole-plate.ts (pier solplates, 2026-11-06) through src/core/bridge-pairs.ts.
*
* Frame: (E, N) in m as in the model (x = E, y = N). Azimuth: the model's 진북 방위각 (deg, clockwise from north), i.e. the
* workbook CAD angle θ_CAD converted by 90 − θ_CAD. A girder at azimuth θ runs along (sin θ, cos θ).
*/
export interface PairBearing {
no: number;
x: number;
y: number;
/** z (EL m). Q-SH5: both ends on the 받침블럭 상면 basis; the pier z definition is still spec Q16. */
z: number;
azimuthDeg: number;
}
export interface PairStructure {
name: string;
bearings: PairBearing[];
}
/** Q-SH1 tolerances and the Q-SH4 azimuth check. */
export const PAIR_AZIMUTH_TOL_DEG = 0.1;
export const PAIR_OFFSET_TOL_M = 0.1;
export const AZIMUTH_CHECK_SEC = 10;
export type PairWarning = 'WS01' | 'WS04';
export interface BearingPair {
/** The bearing on structure k and its partner on structure k + 1 (structure order A1, P1 … Pn, A2). */
from: { structure: string; no: number };
to: { structure: string; no: number };
/** Horizontal distance (m), distance off the girder line of `from` (m), azimuth difference |θ_to − θ_from| (deg). */
distanceM: number;
offsetM: number;
azimuthDiffDeg: number;
/** Direction from → to as an azimuth (deg), and its difference from each input azimuth (arc seconds). */
directionDeg: number;
fromCheckSec: number;
toCheckSec: number;
/** S = (z_to − z_from) / distance × 100 (%), sign along the structure order (− = lower towards the later structure). */
slopePct: number;
/** The structure on the higher end: the 오르막 쪽 where T1 sits for both bearings. */
uphill: string;
warnings: PairWarning[];
}
const DEG = Math.PI / 180;
const angleDiffDeg = (a: number, b: number) => Math.abs(((a - b + 540) % 360) - 180);
/**
* Girder pairs between neighbouring structures (Q-SH1, Q-SH4). For each bearing b on structure k the candidates on k + 1 lie
* ahead along b's azimuth, within 0.1° of its azimuth and 0.1 m of its girder line; the closest to the line is the pair
* (WS04 when there were two or more). WS01 when either input azimuth is more than 10″ from the pair direction.
*/
export function girderPairs(structures: PairStructure[]): BearingPair[] {
const pairs: BearingPair[] = [];
for (let k = 0; k + 1 < structures.length; k++) {
const here = structures[k], next = structures[k + 1];
for (const b of here.bearings) {
const u = { e: Math.sin(b.azimuthDeg * DEG), n: Math.cos(b.azimuthDeg * DEG) };
const candidates = next.bearings.map((c) => {
const d = { e: c.x - b.x, n: c.y - b.y };
return { c, along: d.e * u.e + d.n * u.n, offset: Math.abs(d.e * u.n - d.n * u.e), dAz: angleDiffDeg(c.azimuthDeg, b.azimuthDeg), d };
}).filter((t) => t.along > 0 && t.dAz <= PAIR_AZIMUTH_TOL_DEG && t.offset <= PAIR_OFFSET_TOL_M)
.sort((p, q) => p.offset - q.offset);
if (!candidates.length) continue;
const best = candidates[0];
const distanceM = Math.hypot(best.d.e, best.d.n);
const directionDeg = (Math.atan2(best.d.e, best.d.n) / DEG + 360) % 360;
const fromCheckSec = angleDiffDeg(directionDeg, b.azimuthDeg) * 3600;
const toCheckSec = angleDiffDeg(directionDeg, best.c.azimuthDeg) * 3600;
const slopePct = ((best.c.z - b.z) / distanceM) * 100;
const warnings: PairWarning[] = [];
if (fromCheckSec > AZIMUTH_CHECK_SEC || toCheckSec > AZIMUTH_CHECK_SEC) warnings.push('WS01');
if (candidates.length > 1) warnings.push('WS04');
pairs.push({
from: { structure: here.name, no: b.no }, to: { structure: next.name, no: best.c.no },
distanceM, offsetM: best.offset, azimuthDiffDeg: best.dAz, directionDeg, fromCheckSec, toCheckSec,
slopePct, uphill: best.c.z > b.z ? next.name : here.name, warnings,
});
}
}
return pairs;
}
/** The pair a bearing belongs to, on either end; undefined when it has none (then WS03 with a fallback S, not done here). */
export function pairOf(pairs: BearingPair[], structure: string, no: number): BearingPair | undefined {
return pairs.find((p) => (p.from.structure === structure && p.from.no === no) || (p.to.structure === structure && p.to.no === no));
}
/** Smallest allowed thin-end thickness T_min (mm, Q-SH3). */
export const SOLE_T_MIN_MM = 22;
/** Thickness one bearing needs: T_req = T_min + L·|S|/2 (L mm, S %). */
export function soleRequiredT(lengthMm: number, slopePct: number, tMinMm: number = SOLE_T_MIN_MM): number {
return tMinMm + (lengthMm * Math.abs(slopePct)) / 200;
}
/**
* Row thickness and end thicknesses (Q-SH8·9): T_row = max(T_min, ⌈max T_req⌉) over the row; per bearing
* T1 = round(T_row + L·|S|/2) on the 오르막 쪽 and T2 = round(T_row − L·|S|/2) (≥ T_min by construction).
*/
export function soleRowThickness(items: { lengthMm: number; slopePct: number }[], tMinMm: number = SOLE_T_MIN_MM) {
const T = Math.max(tMinMm, Math.ceil(Math.max(...items.map((i) => soleRequiredT(i.lengthMm, i.slopePct, tMinMm))) - 1e-9));
return {
T,
ends: items.map((i) => {
const half = (i.lengthMm * Math.abs(i.slopePct)) / 200;
return { T1: Math.round(T + half), T2: Math.round(T - half) };
}),
};
}