Files
Pier_v1/tests/bearing-coordinate-frame.test.ts
T
ypshimandClaude Haiku 5.5 1d31d2f3a9 feat(abut): 교대 형상·입력 패널과 교각 받침·솔플레이트 개편
- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가
- 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경
- 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결
- 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경
- 테스트 614건 통과, 타입 검사 통과

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-03 14:51:34 +09:00

57 lines
2.9 KiB
TypeScript

import { readFileSync, readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import { describe, expect, it } from 'vitest';
import { swapXYAzimuth } from '../src/core/coordinate-frame';
import { parseWorkbook } from '../src/core/parseWorkbook';
import { buildPier } from '../src/core/geometry';
import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project';
import { loadP1 } from './helpers';
describe('bearing position / direction coordinate agreement', () => {
it('reflects the direction vector with the exchanged coordinate axes', () => {
for (const angle of [0, 45, 90, 153.200861111, 180, 270, 350, -30, 720]) {
const original = angle * Math.PI / 180;
const converted = swapXYAzimuth(angle) * Math.PI / 180;
expect(Math.sin(converted)).toBeCloseTo(Math.cos(original), 12);
expect(Math.cos(converted)).toBeCloseTo(Math.sin(original), 12);
}
expect(swapXYAzimuth(NaN)).toBeNaN();
});
it('keeps every real Sanwoe bearing approximately aligned with its coping', () => {
const dir = fileURLToPath(new URL('../pierzainer_ground/data/input/sanwoe/', import.meta.url));
const name = readdirSync(dir).find((name) => name.includes('산외교') && name.endsWith('.xlsx'))!;
const { piers, issues } = parseWorkbook(XLSX.read(readFileSync(`${dir}/${name}`), { type: 'buffer' }));
expect(issues).toEqual([]);
expect(piers).toHaveLength(7);
for (const input of piers) {
const model = buildPier(input);
expect(model.blocks).toHaveLength(4);
for (const block of model.blocks) expect(Math.abs(block.rowSkewDeg), `${input.name}/${block.no}`).toBeLessThan(0.03);
}
expect(piers[0].bearings[0].azimuthDeg).toBeCloseTo(296.799138889, 8);
});
it('migrates legacy input/defaults/draft once, including backup-style project round trips', () => {
const normalized = loadP1();
const legacy = structuredClone(normalized);
delete legacy.bearingAzimuthFrame;
legacy.bearings = legacy.bearings.map((b) => ({ ...b, azimuthDeg: swapXYAzimuth(b.azimuthDeg) }));
const file: ProjectFile = {
schemaVersion: SCHEMA_VERSION,
project: { name: '', bridge: '', client: '', author: '', crs: '', memo: '' },
source: { fileName: 'legacy.xlsx', sheets: [] },
piers: [{ id: 'P1', input: legacy, defaults: structuredClone(legacy), draft: structuredClone(legacy) }], history: [],
};
const opened = parseProject(JSON.stringify(file));
for (const input of [opened.piers[0].input, opened.piers[0].defaults!, opened.piers[0].draft!]) {
expect(input.bearingAzimuthFrame).toBe('model-xy');
input.bearings.forEach((b, i) => expect(b.azimuthDeg).toBeCloseTo(normalized.bearings[i].azimuthDeg, 10));
expect(input.ips).toEqual(normalized.ips);
}
expect(parseProject(serializeProject(opened))).toEqual(opened);
expect(legacy.bearingAzimuthFrame).toBeUndefined();
});
});