fix(abut): 교대 받침 방위각을 읽어 받침블럭 회전에 반영

- 원인: 교대 파서가 받침 시트의 방위각 열(6번째)을 읽지 않아, 블럭이 항상 교대축에 맞춰 그려졌음
- 방위각을 교각과 같은 변환(swapXYAzimuth)으로 읽고, 교대축 직각 기준 블럭 회전각(rotDeg)을 계산해 블럭·몰탈패드를 블럭 중심에서 회전
- 운곡교 A1: 회전 약 4.42° (교각 규칙과 같은 값). 날개벽(skew) 선과의 차이는 0.63°로 시안 AW01 값과 같음
- ② 받침 ELEVATION TABLE에 방위각 CAD / 진북, 블럭 회전 열 추가. 읽을 수 없는 방위각은 AP08 경고 후 축 방향 유지
- 테스트 5건 추가, 전체 647건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-03 18:20:05 +09:00
1 parent 7e3ba306f3
commit 4e3950eb67
7 files changed
+106 -6

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import * as THREE from 'three';
import { describe, expect, it } from 'vitest';
import { bearingRotDeg, buildAbut } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
import { buildWings } from '../src/core/abut/wings';
import { buildAbutGroup } from '../src/view/abut-scene';
import { abutElevationRows, resolveBearingRule } from '../src/core/abut/bearing-table';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
const model = buildAbut(toAbutInput(rec));
describe('교대 받침 방위각 (엑셀 받침 시트 6번째 열)', () => {
it('reads the 방위각 of every bearing (75°56′55.9″ … 75°58′08.4″ in the workbook frame)', () => {
expect(rec.bearings.every((b) => b.azimuthDeg !== undefined && Number.isFinite(b.azimuthDeg))).toBe(true);
// model-xy = 90 − workbook value: 75.94886° → 14.05114°
expect(rec.bearings.find((b) => b.no === 1)!.azimuthDeg).toBeCloseTo(90 - (75 + 56 / 60 + 55.9 / 3600), 6);
});
it('turns each block like the pier rule (≈ 4.42° from the axis normal); against the wing (skew) line the rest is 0.63° (시안 AW01)', () => {
const front = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide };
// 날개벽 방향: CAD 각 lineDeg → 진북 = 90 − CAD (시안: 진북 194.68°, CAD 255.32°).
const wingRot = bearingRotDeg(90 - model.wing!.lineDeg, model.u, front);
for (const b of model.bearings) {
expect(Math.abs(b.rotDeg)).toBeGreaterThan(4.3);
expect(Math.abs(b.rotDeg)).toBeLessThan(4.5);
}
const first = model.bearings.find((b) => b.no === 1)!;
expect(Math.abs(first.rotDeg - wingRot)).toBeCloseTo(0.63, 1);
});
it('without the 방위각 the blocks stay on the axis (rotation 0)', () => {
const flat = buildAbut({ ...toAbutInput(rec), bearings: toAbutInput(rec).bearings.map((b) => ({ ...b, azimuthDeg: undefined })) });
expect(flat.bearings.every((b) => b.rotDeg === 0)).toBe(true);
});
it('the 3D block turns with the bearing: its footprint is no longer axis-aligned', () => {
const input = toAbutInput(rec);
const members = buildMembers(model, { bottomEl: input.foundationBottomEl, pileLengthMm: input.pileLengthMm });
const group = buildAbutGroup(model, members, buildWings(model, members));
const block = (group.getObjectByName('blocks') as THREE.Group).children[0] as THREE.Mesh;
const box = new THREE.Box3().setFromObject(block);
// a turned 1.2 m square is wider than 1.2 m along s
expect(box.max.x - box.min.x).toBeGreaterThan(1.2 + 1e-4);
});
it('the ELEVATION table shows the 방위각 (CAD = the workbook value, 진북) and the block turn', () => {
const [r] = abutElevationRows(model, resolveBearingRule()).filter((x) => x.no === 1);
expect(r.azimuthCadDeg).toBeCloseTo(75 + 56 / 60 + 55.9 / 3600, 6);
expect(r.azimuthNorthDeg).toBeCloseTo(90 - (75 + 56 / 60 + 55.9 / 3600), 6);
expect(r.rotDeg).toBeCloseTo(model.bearings.find((b) => b.no === 1)!.rotDeg, 9);
});
});