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

+43
View File
@@ -168,3 +168,46 @@ describe('교대만 (교각 0기): A1 ↔ A2 현 짝 (검토 2026-11-06)', () =>
expect(bridgePairs([p1], [a1, abuts[3]]).some((p) => p.kind === 'chord')).toBe(false);
});
});
describe('방위각 입력 · 계산 · 적용 (2026-11-06)', () => {
it('P1: the 계산 방위각 is the A1 girder direction (≤ 0.5″ from the input); the back row has none', async () => {
const { bridgePairs } = await import('../src/core/bridge-pairs');
const { buildPier } = await import('../src/core/geometry');
const m = buildPier(p1, { pairs: bridgePairs([p1], [a1]) });
for (const s of m.soles.filter((x) => x.row === '전열')) expect(Math.abs(s.azimuthCalcDeg! - s.azimuthInputDeg) * 3600).toBeLessThanOrEqual(0.5);
expect(m.soles.filter((x) => x.row === '후열').every((x) => x.azimuthCalcDeg === undefined)).toBe(true);
});
it('applying a value turns that block and keeps the workbook value as the input', async () => {
const { bridgePairs } = await import('../src/core/bridge-pairs');
const { buildPier } = await import('../src/core/geometry');
const pairs = bridgePairs([p1], [a1]);
const base = buildPier(p1, { pairs });
const turned = buildPier({ ...p1, azimuthApplied: { 3: p1.bearings[2].azimuthDeg + 1 } }, { pairs });
const skew = (m: typeof base, no: number) => m.blocks.find((b) => b.no === no)!.rowSkewDeg;
expect(skew(turned, 3) - skew(base, 3)).toBeCloseTo(1, 6);
expect(skew(turned, 2)).toBe(skew(base, 2));
expect(turned.soles.find((s) => s.no === 3)!.azimuthInputDeg).toBe(p1.bearings[2].azimuthDeg);
});
it('현 짝 (교대만): the 계산 방위각 is the arc tangent — equal to the input on a circular arc', async () => {
const { chordPairs } = await import('../src/core/bearing-pair');
const a2: PairStructure = { name: 'A2', bearings: A1.bearings.map((b) => {
const t = ((b.azimuthDeg - 4) * Math.PI) / 180;
return { ...b, x: b.x + 350 * Math.sin(t), y: b.y + 350 * Math.cos(t), azimuthDeg: b.azimuthDeg - 8, z: b.z - 6.65 };
}) };
const pairs = chordPairs([A1], [a2]);
expect(pairs).toHaveLength(5);
pairs.forEach((p, i) => {
expect(Math.abs(p.fromCalcDeg - A1.bearings[i].azimuthDeg) * 3600).toBeLessThan(0.5);
expect(Math.abs(p.toCalcDeg - a2.bearings[i].azimuthDeg) * 3600).toBeLessThan(0.5);
});
});
it('교대: an applied 계산 방위각 reaches the abutment model through toAbutInput', async () => {
const { toAbutInput } = await import('../src/core/abut/parse');
const input = toAbutInput({ ...a1, azimuthApplied: { 1: 10 } });
expect(input.bearings.find((b) => b.no === 1)!.azimuthDeg).toBe(10);
expect(input.bearings.find((b) => b.no === 2)!.azimuthDeg).toBe(a1.bearings[1].azimuthDeg);
});
});