- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가 - 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경 - 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결 - 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경 - 테스트 614건 통과, 타입 검사 통과 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
158 lines
5.3 KiB
TypeScript
158 lines
5.3 KiB
TypeScript
/**
|
||
* 교량받침 표 (2026-11-03, 모델값). Pure: the ELEVATION TABLE and the 솔플레이트 TABLE of a pier, every value from the
|
||
* model. No drawing values are used (C안). Lengths in mm; EL.X in m.
|
||
*
|
||
* - EL.X: the mortar top = the Excel z (padTop, Q16).
|
||
* - h1: block height (block top − block bottom). h2: mortar (pad top − block top). h3: bearing body (solplate rule).
|
||
* - h4: solplate centre thickness T. E = h1 + h2 + h3 + h4.
|
||
* - C1: bearing centre to the outer block edge along 교축. C2: between the two bearings of a pair, along 교축 (on the
|
||
* front row). C3: block span of the pair along 교축 (front row). C4·C5: 교각축 margins (bearing-layout). C6: block depth
|
||
* along 교각축.
|
||
* - S1·S2: block rebar bars from the model (bearing-block): count and bar length. For 일체 the pair's bars sit on the front row.
|
||
*/
|
||
import type { PierModel } from './geometry';
|
||
import { bearingEdgeDistances, pairEccentricities } from './bearing-layout';
|
||
import { INTEGRATED_BLOCK } from './integrated-block';
|
||
import { bearingBlockRebar, type BarRow } from './rebar/bearing-block';
|
||
|
||
/** Depth of a 분리 bearing block along 교각축 (mm), the same value as the block rebar. */
|
||
export const BLOCK_DEPTH_MM = 1200;
|
||
|
||
export interface ElevationRow {
|
||
no: number;
|
||
row: '전열' | '후열';
|
||
elx: number;
|
||
/** 교좌면 EL (m): the block bottom. */
|
||
seatEl: number;
|
||
h1: number;
|
||
h2: number;
|
||
h3: number;
|
||
h4: number;
|
||
E: number;
|
||
C1: number;
|
||
/** Front row only; null on the back row. */
|
||
C2: number | null;
|
||
C3: number | null;
|
||
C4: number;
|
||
C5: number;
|
||
C6: number;
|
||
S1: BarSummary | null;
|
||
S2: BarSummary | null;
|
||
}
|
||
|
||
export interface BarSummary {
|
||
count: number;
|
||
length: number;
|
||
}
|
||
|
||
export interface SolplateRow {
|
||
no: number;
|
||
/** 기울기 S (%), negative as drawn. */
|
||
S: number;
|
||
L: number;
|
||
W: number;
|
||
T: number;
|
||
T1: number;
|
||
T2: number;
|
||
/** Check: T1 − T2 against L·|S|/100 (mm). */
|
||
checkLhs: number;
|
||
checkRhs: number;
|
||
ok: boolean;
|
||
}
|
||
|
||
function barSummary(bars: BarRow[], name: 'S1' | 'S2'): BarSummary | null {
|
||
const hit = bars.filter((x) => x.name === name);
|
||
if (hit.length === 0) return null;
|
||
return { count: hit.reduce((sum, x) => sum + x.count, 0), length: hit[0].length };
|
||
}
|
||
|
||
export function elevationRows(m: PierModel): ElevationRow[] {
|
||
const edges = bearingEdgeDistances(m);
|
||
const rebar = bearingBlockRebar(m);
|
||
const pairs = pairEccentricities(m.blocks);
|
||
return [...m.blocks]
|
||
.sort((a, b) => a.no - b.no)
|
||
.map((b) => {
|
||
const plan = b.plan;
|
||
const h1 = Math.round((b.top - b.bottom) * 1000);
|
||
const h2 = Math.round((b.padTop - b.top) * 1000);
|
||
const edge = edges.find((e) => e.no === b.no)!;
|
||
const ib = m.integratedBlocks.find((x) => (b.row === '전열' ? x.frontNo : x.backNo) === b.no);
|
||
// C1: bearing centre to the outer edge of its block along 교축 (1200 block centred on the bearing, or the 2500 block).
|
||
const C1 = ib ? Math.round(Math.abs(b.n - (b.n <= ib.n ? ib.n - INTEGRATED_BLOCK.length / 2000 : ib.n + INTEGRATED_BLOCK.length / 2000)) * 1000) : 600;
|
||
const pair = pairs.find((p) => (b.row === '전열' ? p.frontNo : p.backNo) === b.no);
|
||
const front = b.row === '전열';
|
||
const C2 = front && pair ? Math.round(Math.abs(m.blocks.find((x) => x.no === pair.backNo && x.row === '후열')!.n - b.n) * 1000) : null;
|
||
const C3 = front && pair ? (ib ? INTEGRATED_BLOCK.length : (C2 ?? 0) + BLOCK_DEPTH_MM) : null;
|
||
const bars = rebar.find((r) => r.no === b.no && r.row === b.row)?.bars ?? [];
|
||
return {
|
||
no: b.no,
|
||
row: b.row,
|
||
elx: b.padTop,
|
||
seatEl: b.bottom,
|
||
h1,
|
||
h2,
|
||
h3: plan.h3,
|
||
h4: plan.T,
|
||
E: h1 + h2 + plan.h3 + plan.T,
|
||
C1,
|
||
C2,
|
||
C3,
|
||
C4: Math.round(edge.leftMm),
|
||
C5: Math.round(edge.rightMm),
|
||
C6: BLOCK_DEPTH_MM,
|
||
S1: barSummary(bars, 'S1'),
|
||
S2: barSummary(bars, 'S2'),
|
||
};
|
||
});
|
||
}
|
||
|
||
export function solplateRows(m: PierModel): SolplateRow[] {
|
||
return [...m.blocks]
|
||
.sort((a, b) => a.no - b.no)
|
||
.map((b) => {
|
||
const p = b.plan;
|
||
const checkLhs = p.T1 - p.T2;
|
||
const checkRhs = (p.L * Math.abs(p.slopePct)) / 100;
|
||
return {
|
||
no: b.no,
|
||
S: p.slopePct,
|
||
L: p.L,
|
||
W: p.W,
|
||
T: p.T,
|
||
T1: p.T1,
|
||
T2: p.T2,
|
||
checkLhs,
|
||
checkRhs,
|
||
ok: Math.abs(checkLhs - checkRhs) < 1e-6,
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* S1·S2 of one pair along 교축 (mm, 2026-11-03): S1 = bearing centre to the coping edge on each outer side, S2 = between
|
||
* the two bearing centres. The coping is centred on the axis with width B (model copingB), so S1 + S2 + S1 = B.
|
||
*/
|
||
export interface PairCopingSpans {
|
||
S1Low: number;
|
||
S2: number;
|
||
S1High: number;
|
||
copingWidth: number;
|
||
}
|
||
|
||
export function pairCopingSpans(m: PierModel, pairIndex: number): PairCopingSpans | null {
|
||
const pair = pairEccentricities(m.blocks)[pairIndex];
|
||
if (!pair) return null;
|
||
const f = m.blocks.find((b) => b.no === pair.frontNo && b.row === '전열')!;
|
||
const k = m.blocks.find((b) => b.no === pair.backNo && b.row === '후열')!;
|
||
const lo = Math.min(f.n, k.n);
|
||
const hi = Math.max(f.n, k.n);
|
||
const half = m.copingB / 2;
|
||
return {
|
||
S1Low: Math.round((lo + half) * 1000),
|
||
S2: Math.round((hi - lo) * 1000),
|
||
S1High: Math.round((half - hi) * 1000),
|
||
copingWidth: Math.round(m.copingB * 1000),
|
||
};
|
||
}
|