feat(bearing): 방위각 입력 · 계산 비교와 계산값 적용 버튼

교각·교대 솔플레이트 표에 받침별 '방위각 입력 (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>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-06 15:55:24 +09:00
1 parent 7c5f129712
commit c7acfccd51
9 files changed
+161 -22

No files matched your search

+7 -2
View File
@@ -51,6 +51,8 @@ export function resolveBearingRule(changes?: Partial<AbutBearingRule> & Record<s
export interface AbutSoleContext {
pairs?: BearingPair[];
slopeI?: number;
/** 엑셀 방위각 per bearing number (deg model 진북), when an applied 계산 방위각 is in the model. */
inputAzimuthDeg?: Record<string, number>;
}
/** Solplate of every bearing of the abutment (one row). */
@@ -58,7 +60,7 @@ export function abutSoles(model: AbutModel, rule: AbutBearingRule, heightMm: num
const plates = Object.fromEntries(model.bearings.map((b) => [String(b.no), { aMm: rule.aMm, bMm: rule.bMm }]));
return structureSoles(
model.name,
model.bearings.map((b) => ({ no: b.no, row: '1열', azimuthDeg: b.azimuthDeg ?? 0 })),
model.bearings.map((b) => ({ no: b.no, row: '1열', azimuthDeg: b.azimuthDeg ?? 0, inputAzimuthDeg: ctx.inputAzimuthDeg?.[String(b.no)] })),
{
h3Mm: rule.h3Mm ?? heightMm - rule.tRefMm, tRefMm: rule.tRefMm, tMinMm: rule.tMinMm, marginMm: rule.marginMm, plates,
tByRow: rule.tMm === undefined ? undefined : { '1열': rule.tMm },
@@ -132,6 +134,9 @@ export interface AbutSolplateRow {
/** 방위각 검사 (″): 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;
L: number;
W: number;
T: number;
@@ -146,7 +151,7 @@ export function abutSolplateRows(model: AbutModel, rule: AbutBearingRule, height
return abutSoles(model, rule, heightMm, ctx).map((p) => {
const rhs = (p.L * Math.abs(p.slopePct)) / 100;
return {
no: p.no, S: p.slopePct, source: p.slopeSource, azimuthSec: p.azimuthCheckSec,
no: p.no, S: p.slopePct, source: p.slopeSource, azimuthSec: p.azimuthCheckSec, azimuthInputDeg: p.azimuthInputDeg, azimuthCalcDeg: p.azimuthCalcDeg,
pair: p.pair ? `${p.pair.structure}-${CIRCLED[p.pair.no - 1] ?? p.pair.no}${p.pairKind === 'chord' ? ' (현)' : ''}` : undefined, pairKind: p.pairKind,
L: p.L, W: p.W, T: p.T, T1: p.T1, T2: p.T2,
check: `${p.T1 - p.T2} ≈ ${rhs.toFixed(1)} ${Math.abs(p.T1 - p.T2 - rhs) <= 1 + 1e-9 ? '일치' : '불일치 (W08)'}`,
+4 -1
View File
@@ -66,6 +66,8 @@ export interface AbutRecord {
shape?: Partial<AbutShape>;
/** 받침 규칙 (② 교량받침 카드: h3, 솔플레이트 T·여유, 받침 종류). Only the changed values. Absent = 교각 P1 가정값 (QA-15). */
bearingRule?: Partial<AbutBearingRule>;
/** 계산 방위각을 적용한 받침: 받침번호 → 방위각 (deg, model 진북). Absent = 엑셀 입력값 (2026-11-06). */
azimuthApplied?: Record<string, number>;
/** 날개벽 치수 (② 날개벽 카드, 측별, mm). Only the changed values. */
wingDims?: { left?: Partial<WingDims>; right?: Partial<WingDims> };
/** 접속슬래브 입력 (② 접속슬래브 카드: 형식, 경사, 치수 mm). Only the changed values. Absent = 매립형 · 하향 4 % 기본값. */
@@ -235,7 +237,8 @@ export function toAbutInput(rec: AbutRecord): AbutInput {
ip1: rec.ip1,
ip2: rec.ip2,
ip3: rec.ip3,
bearings: rec.bearings.map((b) => ({ no: b.no, pos: b.pos, z: b.z, seatEl: b.seatEl, azimuthDeg: b.azimuthDeg })),
// 계산 방위각을 적용한 받침 (2026-11-06) use that value; the record keeps the workbook value.
bearings: rec.bearings.map((b) => ({ no: b.no, pos: b.pos, z: b.z, seatEl: b.seatEl, azimuthDeg: rec.azimuthApplied?.[String(b.no)] ?? b.azimuthDeg })),
skewDeg: rec.skewDeg,
slopeI: rec.slopeI,
wings: rec.wings,
+9 -1
View File
@@ -45,6 +45,9 @@ export interface BearingPair {
slopePct: number;
/** The structure on the higher end: the 오르막 쪽 where T1 sits for both bearings. */
uphill: string;
/** 계산 방위각 (deg, model 진북) of each end from the pair: the girder direction, or on a 현 the arc tangent. */
fromCalcDeg: number;
toCalcDeg: number;
/** girder = the two ends of one girder; chord = A1 ↔ A2 of a bridge loaded without piers (현, chordPairs). */
kind: 'girder' | 'chord';
warnings: PairWarning[];
@@ -52,6 +55,8 @@ export interface BearingPair {
const DEG = Math.PI / 180;
const angleDiffDeg = (a: number, b: number) => Math.abs(((a - b + 540) % 360) - 180);
/** a − b wrapped to (−180, 180]. */
const signedDiff = (a: number, b: number) => ((a - b + 540) % 360) - 180;
/**
* Girder pairs over the bridge (Q-SH1, Q-SH4). For each bearing b the candidates on the other structures lie
@@ -89,7 +94,7 @@ export function girderPairs(structures: PairStructure[]): BearingPair[] {
pairs.push({
from: { structure: here.name, no: b.no }, to: { structure: best.c.structure, no: best.c.no },
distanceM, offsetM: best.offset, azimuthDiffDeg: best.dAz, directionDeg, fromCheckSec, toCheckSec,
slopePct, uphill: best.c.z > b.z ? best.c.structure : here.name, warnings, kind: 'girder',
slopePct, uphill: best.c.z > b.z ? best.c.structure : here.name, warnings, kind: 'girder', fromCalcDeg: directionDeg, toCalcDeg: directionDeg,
});
}
}
@@ -119,6 +124,9 @@ export function chordPairs(starts: PairStructure[], ends: PairStructure[]): Bear
distanceM, offsetM: 0, azimuthDiffDeg: angleDiffDeg(best.c.azimuthDeg, b.azimuthDeg), directionDeg: best.directionDeg,
fromCheckSec: best.dev * 3600, toCheckSec: best.dev * 3600, slopePct: ((best.c.z - b.z) / distanceM) * 100,
uphill: best.c.z > b.z ? best.structure : here.name, warnings: candidates.length > 1 ? ['WS04'] : [], kind: 'chord',
// Circular arc: the tangent at each end is the chord turned by half the azimuth change.
fromCalcDeg: (best.directionDeg + signedDiff(b.azimuthDeg, best.c.azimuthDeg) / 2 + 360) % 360,
toCalcDeg: (best.directionDeg + signedDiff(best.c.azimuthDeg, b.azimuthDeg) / 2 + 360) % 360,
});
}
return pairs;
+4 -1
View File
@@ -64,6 +64,9 @@ export interface SolplateRow {
/** 방위각 검사 (″): 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. */
@@ -133,7 +136,7 @@ export function solplateRows(m: PierModel): SolplateRow[] {
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, source: s.slopeSource, tTyped: s.tTyped, A: s.aMm, B: s.bMm, warnings: s.warnings,
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,
+4 -2
View File
@@ -459,11 +459,13 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel {
}
const blocks: BlockModel[] = [];
const bearingAz = new Map(input.bearings.map((r) => [r.no, r.azimuthDeg]));
// 계산 방위각을 적용한 받침 (2026-11-06): that value turns the block, bearing and solplate; the workbook value stays in input.
const azimuthOf = (r: { no: number; azimuthDeg: number }) => input.azimuthApplied?.[String(r.no)] ?? r.azimuthDeg;
const bearingAz = new Map(input.bearings.map((r) => [r.no, azimuthOf(r)]));
const skewOf = (no: number, k: number) => rowSkewDeg(bearingAz.get(no) ?? NaN, axis.segments[k].u);
// Solplate of every bearing (결정 2026-11-06): S from the girder pair (ctx.pairs over the whole bridge), else the
// 종단경사 input, else the other row; T per row; the girder underside stays, so the block top drops by T − T_ref.
const soles = new Map(structureSoles(input.name, input.bearings.map((b) => ({ no: b.no, row: b.row, azimuthDeg: b.azimuthDeg })),
const soles = new Map(structureSoles(input.name, input.bearings.map((b) => ({ no: b.no, row: b.row, azimuthDeg: azimuthOf(b), inputAzimuthDeg: b.azimuthDeg })),
input.sole, ctx.pairs ?? [], input.longitudinalSlope === undefined ? undefined : input.longitudinalSlope * 100).map((s) => [s.no, s]));
const planOf = (no: number, k: number, skewDeg: number): SolplatePlan => {
const s = soles.get(no)!;
+8
View File
@@ -38,6 +38,9 @@ export interface BearingSole {
pair?: { structure: string; no: number };
/** 방위각 검사 (″): this bearing's input azimuth against the direction to its pair (Q-SH4, WS01 > 10″). Absent without a pair. */
azimuthCheckSec?: number;
/** 입력 방위각 and the one computed from the pair (deg, model 진북); the second is absent without a pair. */
azimuthInputDeg: number;
azimuthCalcDeg?: number;
/** girder (같은 거더) or chord (교대만, A1 ↔ A2 현). */
pairKind?: 'girder' | 'chord';
slopePct: number;
@@ -65,7 +68,10 @@ export interface BearingSole {
export interface SoleBearing {
no: number;
row: string;
/** The azimuth in use (an applied 계산 방위각 when chosen) — for the 오르막 direction. */
azimuthDeg: number;
/** The workbook azimuth, when it differs from the one in use. */
inputAzimuthDeg?: number;
}
const DEG = Math.PI / 180;
@@ -121,6 +127,8 @@ export function structureSoles(structure: string, bearings: SoleBearing[], setti
return {
no: b.no, row: b.row, pair: pair ? (pair.from.structure === structure ? pair.to : pair.from) : undefined,
pairKind: pair?.kind,
azimuthInputDeg: b.inputAzimuthDeg ?? b.azimuthDeg,
azimuthCalcDeg: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCalcDeg : pair.toCalcDeg) : undefined,
azimuthCheckSec: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCheckSec : pair.toCheckSec) : undefined,
slopePct, slopeSource: source, aMm, bMm, L, W, T, tTyped: typed, T1, T2, tReq: req[i], h3: s.h3Mm,
dropMm: T - s.tRefMm, uphill: sign ? { e: sign * dir.e, n: sign * dir.n } : null, warnings,
+2
View File
@@ -67,6 +67,8 @@ export interface PierInput {
solplate?: SolplateRule;
/** 솔플레이트 입력 (결정 2026-11-06, 시안): h3, T_ref, T_min, 여유, 받침별 A × B, 열 T·받침 S 직접 수정. Absent = 기본값. */
sole?: SoleSettings;
/** 계산 방위각을 적용한 받침: 받침번호 → 방위각 (deg, model 진북). Absent = 엑셀 입력값 (2026-11-06). */
azimuthApplied?: Record<string, number>;
/** 다주형 기둥 위치 입력 (Q-M10 미결): 끝 ~ 기둥1 중심 e_c, 기둥 간격 s_c (mm). Absent = L/5 규칙. */
columnEndOffsetMm?: number;
columnSpacingMm?: number;