Files
Aislo/resources/tester/cad/geometry.test.ts
T
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

143 lines
4.8 KiB
TypeScript

/**
* B07 CAD 기하 순수 함수 검증 (조사표 1·2절 명령의 계산 부분).
* 실행: cd B07_DesignDetail/openwebcad && npx vitest run --root ../.. tmp/tests/cad
*/
import { Point } from '@flatten-js/core';
import { describe, expect, it } from 'vitest';
import { hatchSpans, isPointInPolygon } from '../../../B07_DesignDetail/openwebcad/src/helpers/geometry/hatch-lines';
import {
dividePoints,
measurePoints,
pointAtDistance,
polylineLength,
} from '../../../B07_DesignDetail/openwebcad/src/helpers/geometry/sample-entity';
import {
arcThroughThreePoints,
ellipsePoints,
intersectLines,
offsetPolylinePoints,
regularPolygonPoints,
revisionCloudPoints,
splinePoints,
} from '../../../B07_DesignDetail/openwebcad/src/helpers/geometry/shape-points';
const p = (x: number, y: number) => new Point(x, y);
describe('3점 호 (ARC)', () => {
it('단위원 위의 세 점이면 중심은 원점, 반지름은 1이다', () => {
const arc = arcThroughThreePoints(p(1, 0), p(0, 1), p(-1, 0));
expect(arc).not.toBeNull();
expect(arc?.center.x).toBeCloseTo(0, 6);
expect(arc?.center.y).toBeCloseTo(0, 6);
expect(arc?.radius).toBeCloseTo(1, 6);
});
it('일직선이면 호가 성립하지 않는다', () => {
expect(arcThroughThreePoints(p(0, 0), p(1, 0), p(2, 0))).toBeNull();
});
});
describe('정다각형 (POLYGON)', () => {
it('변 수만큼 꼭짓점을 만들고 마지막에 닫는다', () => {
const points = regularPolygonPoints(p(0, 0), p(1, 0), 6);
expect(points).toHaveLength(7); // 꼭짓점 6 + 닫는 점
expect(points[6].x).toBeCloseTo(points[0].x, 9);
for (const point of points) {
expect(Math.hypot(point.x, point.y)).toBeCloseTo(1, 9);
}
});
});
describe('타원 (ELLIPSE)', () => {
it('장축·단축 반지름이 지정한 값과 같다', () => {
const points = ellipsePoints(p(0, 0), p(2, 0), 1, 4);
expect(points[0].x).toBeCloseTo(2, 9); // 장축 방향
expect(points[1].y).toBeCloseTo(1, 9); // 90°에서 단축 반지름
});
});
describe('간격띄우기 (OFFSET·MLINE)', () => {
it('수평선을 왼쪽으로 밀면 y가 커진다', () => {
const offset = offsetPolylinePoints([p(0, 0), p(10, 0)], 2);
expect(offset[0].y).toBeCloseTo(2, 9);
expect(offset[offset.length - 1].y).toBeCloseTo(2, 9);
});
it('꺾인 폴리선은 이음매에서 두 평행선의 교점을 쓴다', () => {
const offset = offsetPolylinePoints([p(0, 0), p(10, 0), p(10, 10)], 1);
expect(offset).toHaveLength(3);
expect(offset[1].x).toBeCloseTo(9, 9);
expect(offset[1].y).toBeCloseTo(1, 9);
});
});
describe('직선 교점 (FILLET·CHAMFER·EXTEND)', () => {
it('직교하는 두 직선의 교점을 찾는다', () => {
const crossing = intersectLines(p(0, 0), p(10, 0), p(4, -5), p(4, 5));
expect(crossing?.x).toBeCloseTo(4, 9);
expect(crossing?.y).toBeCloseTo(0, 9);
});
it('평행하면 교점이 없다', () => {
expect(intersectLines(p(0, 0), p(10, 0), p(0, 3), p(10, 3))).toBeNull();
});
});
describe('점렬 길이와 분할 (DIVIDE·MEASURE)', () => {
const line = [p(0, 0), p(10, 0)];
it('길이를 누적해서 잰다', () => {
expect(polylineLength([p(0, 0), p(3, 4), p(3, 4)])).toBeCloseTo(5, 9);
});
it('지정 거리 위치의 점을 찾는다', () => {
expect(pointAtDistance(line, 2.5)?.x).toBeCloseTo(2.5, 9);
expect(pointAtDistance(line, 20)).toBeNull();
});
it('등분은 내부 점만 만든다', () => {
const points = dividePoints(line, 4);
expect(points).toHaveLength(3);
expect(points.map((point) => point.x)).toEqual([2.5, 5, 7.5]);
});
it('길이분할은 지정 간격마다 점을 둔다', () => {
expect(measurePoints(line, 4).map((point) => point.x)).toEqual([4, 8]);
});
});
describe('해치 스캔선 (HATCH)', () => {
const square = [p(0, 0), p(10, 0), p(10, 10), p(0, 10)];
it('사각형 내부를 가로지르는 선분을 만든다', () => {
const spans = hatchSpans(square, 0, 2);
expect(spans.length).toBeGreaterThan(3);
for (const [start, end] of spans) {
expect(start.x).toBeCloseTo(0, 6);
expect(end.x).toBeCloseTo(10, 6);
expect(start.y).toBeGreaterThan(0);
expect(start.y).toBeLessThan(10);
}
});
it('점이 다각형 안에 있는지 가른다', () => {
expect(isPointInPolygon(square, p(5, 5))).toBe(true);
expect(isPointInPolygon(square, p(15, 5))).toBe(false);
});
});
describe('스플라인·구름형 (SPLINE·REVCLOUD)', () => {
it('스플라인은 시작·끝 조정점을 지난다', () => {
const curve = splinePoints([p(0, 0), p(5, 5), p(10, 0)], 8);
expect(curve[0].x).toBeCloseTo(0, 9);
expect(curve[curve.length - 1].x).toBeCloseTo(10, 9);
expect(curve.length).toBeGreaterThan(10);
});
it('구름형은 경로 길이에 맞춰 스캘럽을 채운다', () => {
const cloud = revisionCloudPoints([p(0, 0), p(10, 0)], 1);
expect(cloud.length).toBeGreaterThan(20);
expect(cloud[0].x).toBeCloseTo(0, 6);
});
});