Files
Pier_v1/tests/axis.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

100 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import { DIM, solveIp4 } from '../src/core/geometry';
import { buildAxis, copingSpan, fromAxis, kinkArc, nearKink, toAxis } from '../src/core/axis';
import type { PierInput } from '../src/core/types';
import { loadP1 } from './helpers';
const pt = (p: PierInput['ips'][number]) => ({ e: p.x, n: p.y });
const ip = (p: PierInput, name: string) => pt(p.ips.find((q) => q.name === name)!);
describe('straight axis (regression against spec §12)', () => {
const p1 = loadP1();
const axis = buildAxis(ip(p1, 'IP1'), ip(p1, 'IP3'), null);
const bp1 = copingSpan(axis, p1.copingLengthMm / 1000).sStart;
it('bearing s and n relative to BP1 match spec §12', () => {
const front = p1.bearings.filter((b) => b.row === '전열').sort((a, b) => a.no - b.no);
const back = p1.bearings.filter((b) => b.row === '후열').sort((a, b) => a.no - b.no);
const fs = front.map((b) => toAxis(axis, { e: b.x, n: b.y }));
const bs = back.map((b) => toAxis(axis, { e: b.x, n: b.y }));
expect(fs[0].s - bp1).toBeCloseTo(0.943, 3);
expect(fs[fs.length - 1].s - bp1).toBeCloseTo(10.965, 3);
expect(bs[0].s - bp1).toBeCloseTo(1.029, 3);
expect(bs[bs.length - 1].s - bp1).toBeCloseTo(11.05, 3);
expect(fs[0].n).toBeCloseTo(0.698, 3);
expect(bs[0].n).toBeCloseTo(-0.6, 3);
});
it('straight axis: every point is on stretch 0 and round-trips', () => {
for (const b of p1.bearings) {
const p = { e: b.x, n: b.y };
const q = toAxis(axis, p);
expect(q.k).toBe(0);
const back = fromAxis(axis, q);
expect(back.e).toBeCloseTo(p.e, 9);
expect(back.n).toBeCloseTo(p.n, 9);
}
});
});
// Synthetic bend: IP1 (0, 0), IP2 (1, 0), kink (7, 0), IP3 (9, −3.464), a 60° right turn with IP3 ahead on the turned line.
// The P1 sheet is straight (IP1, IP2, IP3 collinear), so P1 cannot exercise a bend.
describe('bent axis IP1 → IP4 → IP3', () => {
const p1 = loadP1();
const ip1 = { e: 0, n: 0 };
const ip2 = { e: 1, n: 0 };
const ip3 = { e: 9, n: -3.4641016 };
const kink = solveIp4(ip1, ip2, ip3, 60)!;
const axis = buildAxis(ip1, ip3, kink);
const a = kinkArc(axis)!;
it('kink lies on the IP1→IP2 line, at s = 7, and the arc length adds the two stretches', () => {
const cross = (kink.e - ip1.e) * (ip2.n - ip1.n) - (kink.n - ip1.n) * (ip2.e - ip1.e);
expect(cross).toBeCloseTo(0, 9);
expect(a).toBeCloseTo(7, 6);
const len2 = Math.hypot(ip3.e - kink.e, ip3.n - kink.n);
expect(axis.length).toBeCloseTo(a + len2, 9);
});
it('the kink is the continuity point: s = a on both stretches at n = 0', () => {
const from0 = fromAxis(axis, { k: 0, s: a, n: 0 });
const from1 = fromAxis(axis, { k: 1, s: a, n: 0 });
expect(from0.e).toBeCloseTo(kink.e, 9);
expect(from0.n).toBeCloseTo(kink.n, 9);
expect(from1.e).toBeCloseTo(kink.e, 9);
expect(from1.n).toBeCloseTo(kink.n, 9);
});
it('every bearing keeps its input world coordinates through toAxis → fromAxis', () => {
for (const b of p1.bearings) {
const p = { e: b.x, n: b.y };
const back = fromAxis(axis, toAxis(axis, p));
expect(back.e).toBeCloseTo(p.e, 9);
expect(back.n).toBeCloseTo(p.n, 9);
}
});
it('a point just before the mitre plane is on stretch 0, just after it on stretch 1', () => {
const u1 = axis.segments[0].u;
const u2 = axis.segments[1].u;
const before = { e: kink.e - 0.01 * u1.e, n: kink.n - 0.01 * u1.n };
const after = { e: kink.e + 0.01 * u2.e, n: kink.n + 0.01 * u2.n };
expect(toAxis(axis, before).k).toBe(0);
expect(toAxis(axis, after).k).toBe(1);
});
it('coping centred on the axis: span is L_IP/2 ± L/2 and the kink lies inside it', () => {
const L = 6;
const span = copingSpan(axis, L);
expect(span.sStart).toBeCloseTo(axis.length / 2 - L / 2, 9);
expect(span.sStart < a && a < span.sEnd).toBe(true);
});
it('columns within the Level half-width of the kink are reported', () => {
const half = DIM.levelWidth / 2;
const columns = [a - 0.5, a + 5];
expect(nearKink(axis, columns, half)).toEqual([a - 0.5]);
expect(nearKink(axis, [a + 5], half)).toEqual([]);
});
});