교각·교대 솔플레이트 표에 받침별 '방위각 입력 (CAD)' · '방위각 계산 (CAD)' · '차이 (″, 계산 − 입력)' · [적용] 열을 두고, 표 위에 '계산 방위각 모두 적용 / 모두 입력값으로' 버튼을 둔다 (사용자 요청 2026-11-06, Q-SH4 "입력값 유지"를 사용자가 고르게 바꿈). - 계산 방위각: 거더 짝이면 두 받침을 잇는 방향, 교대만의 현 짝이면 원곡선 접선(현 방향 ± 방위각 변화의 절반) - 적용값은 교각 input.azimuthApplied / 교대 record.azimuthApplied에 받침번호별로 저장, 엑셀 값은 그대로. 블럭 · 받침 · 솔플레이트 회전과 오르막 방향에 쓰고, 짝 찾기는 엑셀 입력값으로 - 차이 표시: 거더 짝 ≤ 10″ / WS01, 현 짝은 '현 · 참고'. 도·분·초 CAD 각(엑셀과 같은 표기) - 테스트: P1 전열 계산값 ≤ 0.5″ · 후열 없음, 적용한 받침만 블럭 회전 변화, 원곡선 현 접선 = 입력, 교대 toAbutInput 반영 - 브라우저: 운곡교AA.xlsx A1-광주 ① 적용 → 블럭 회전 4.39° → 4.36°, 모두 적용 5개 · 모두 해제, 예외 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
180 lines
6.7 KiB
TypeScript
180 lines
6.7 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 { SlopeSource } from './sole-plate';
|
||
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 W08: T1 − T2 against L·|S|/100 (mm), within 1 mm (T1 and T2 are rounded to whole mm). */
|
||
checkLhs: number;
|
||
checkRhs: number;
|
||
ok: boolean;
|
||
/** 짝 받침 (같은 거더), e.g. "A1-①"; absent when none (2026-11-06). */
|
||
pair?: string;
|
||
/** 방위각 검사 (″): input azimuth against the pair direction; absent without a pair. */
|
||
azimuthSec?: number;
|
||
pairKind?: 'girder' | 'chord';
|
||
/** 방위각 입력 (엑셀) and 계산 (짝 받침), deg model 진북; 계산 absent without a pair. */
|
||
azimuthInputDeg: number;
|
||
azimuthCalcDeg?: number;
|
||
/** Where S comes from: 계산 (짝) / 입력 (종단경사) / 가정 (다른 열) / 수정됨 / 없음. */
|
||
source: SlopeSource;
|
||
/** T typed by the user (수정됨) rather than automatic. */
|
||
tTyped: boolean;
|
||
/** 받침 평면 A × B (mm). */
|
||
A: number;
|
||
B: number;
|
||
warnings: string[];
|
||
}
|
||
|
||
const CIRCLED_NO = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⑪', '⑫', '⑬', '⑭', '⑮', '⑯', '⑰', '⑱', '⑲', '⑳'];
|
||
|
||
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 s = m.soles.find((x) => x.no === b.no)!;
|
||
const checkLhs = p.T1 - p.T2;
|
||
const checkRhs = (p.L * Math.abs(p.slopePct)) / 100;
|
||
return {
|
||
pair: s.pair ? `${s.pair.structure}-${CIRCLED_NO[s.pair.no - 1] ?? s.pair.no}${s.pairKind === 'chord' ? ' (현)' : ''}` : undefined, pairKind: s.pairKind,
|
||
azimuthSec: s.azimuthCheckSec, azimuthInputDeg: s.azimuthInputDeg, azimuthCalcDeg: s.azimuthCalcDeg, source: s.slopeSource, tTyped: s.tTyped, A: s.aMm, B: s.bMm, warnings: s.warnings,
|
||
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) <= 1 + 1e-9,
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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),
|
||
};
|
||
}
|