Files
Aislo/B07_DesignDetail/openwebcad/src/entities/ArcEntity.test.ts
T
eomsangdonandClaude Opus 5.5 edd51b66e9 fix(B07): 이동 · 크기 · 회전 · 복사 · 배열 때 색 · 층 유지 · 호 대칭 축 · 시계방향 호 내보내기(84-3)
- 복제 함수 cloneKeepingLook 한 곳(move-tool.helpers) — 옮기기 · 크기 · 회전은 id · 묶음까지 그대로 · 복사 · 배열은 새 id
- LineEntity · ArcEntity clone 이 색 · 층 · 선 꼴 유지(폴리선 자식 포함)
- 복사가 누를 때마다 원본을 한 벌씩 더 쌓던 흠 고침
- ArcEntity mirror 가 축 각도 따름(α → 2θ − α) · 그리기 끝각 = 시작각 ± 쓸어간 각(끝각 0 → 2π 온 원 흠)
- DXF 내보내기 시계방향 호는 시작 · 끝 바꿔 같은 작은 호
- 시험: ArcEntity.test.ts 10 · resources/tester/cad/clone-keeps-look.test.ts 7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
2026-10-08 12:08:03 +09:00

122 lines
4.3 KiB
TypeScript

import { type Arc, Point } from '@flatten-js/core';
import { describe, expect, it } from 'vitest';
import { EPSILON } from '../App.consts.ts';
import type { DrawController } from '../drawControllers/DrawController.ts';
import { mirrorPointOverAxis } from '../helpers/mirror-point-over-axis.ts';
import { ArcEntity } from './ArcEntity.ts';
import { LineEntity } from './LineEntity.ts';
describe('ArcEntity.distanceTo', () => {
/**
* ---X---
* ----- -----
* -- --
*/
it('distance to point on arc', () => {
const arc = new ArcEntity('layer1', new Point(0, 0), 1, Math.PI / 4, (3 * Math.PI) / 4, true);
const point = (arc.getShape() as Arc).pointAtLength(
(arc.getShape() as Arc).length / 2
) as Point;
const distanceInfo = arc.distanceTo(point);
expect(distanceInfo).toBeDefined();
if (!distanceInfo) return;
expect(distanceInfo[0]).to.equal(0);
});
/**
* ------- X
* ----- -----
* -- --
*/
it('distance to point outside arc', () => {
const arc = new ArcEntity('layer1', new Point(0, 0), 1, Math.PI / 4, (3 * Math.PI) / 4, true);
const point = new Point(2, 2);
const distanceInfo = arc.distanceTo(point);
expect(distanceInfo).toBeDefined();
if (!distanceInfo) return;
expect(distanceInfo[0]).to.be.closeTo(Math.sqrt(2 * 2 + 2 * 2) - 1, EPSILON);
});
/**
* -------
* ----- -----
* -- --
* - X -
*/
it('distance to point inside arc', () => {
const arc = new ArcEntity('layer1', new Point(0, 0), 1, Math.PI / 4, (3 * Math.PI) / 4, true);
const point = new Point(0.5, 0.5);
const distanceInfo = arc.distanceTo(point);
expect(distanceInfo).toBeDefined();
if (!distanceInfo) return;
expect(distanceInfo[0]).to.be.closeTo(1 - Math.sqrt(0.5 * 0.5 + 0.5 * 0.5), EPSILON);
});
it('should return distance from a point to an arc', () => {
const arc = new ArcEntity('layer1', new Point(20, 20), 20, 0, 2 * Math.PI * 0.75, true);
const distanceInfo = arc.distanceTo(new Point(20 - 14.14, 20 + 14.14));
expect(distanceInfo).toBeDefined();
if (!distanceInfo) return;
expect(distanceInfo[0]).toBeLessThan(1);
});
});
describe('ArcEntity 대칭 · 그리기 · 복제(84-3)', () => {
const middle = (entity: ArcEntity) => {
const arc = entity.getShape() as Arc;
return arc.pointAtLength(arc.length / 2) as Point;
};
const mirrorOver = (axisEnd: Point) => {
const arc = new ArcEntity('layer1', new Point(2, 1), 1, 0, Math.PI / 2, true);
const before = arc.getShape() as Arc;
const axis = new LineEntity('layer1', new Point(0, 0), axisEnd);
const expected = [before.start, middle(arc), before.end].map((point) =>
mirrorPointOverAxis(point, axis)
);
arc.mirror(axis);
const after = arc.getShape() as Arc;
return { expected, actual: [after.start, middle(arc), after.end], length: after.length };
};
for (const [name, axisEnd] of [
['세로축', new Point(0, 1)],
['가로축', new Point(1, 0)],
['45° 축', new Point(1, 1)],
['30° 축', new Point(Math.cos(Math.PI / 6), Math.sin(Math.PI / 6))],
] as const) {
it(`${name} 대칭 — 끝점 · 가운데 점이 대칭 자리 · 작은 호 그대로`, () => {
const { expected, actual, length } = mirrorOver(axisEnd);
for (let index = 0; index < 3; index++) {
expect(actual[index].x).toBeCloseTo(expected[index].x, 9);
expect(actual[index].y).toBeCloseTo(expected[index].y, 9);
}
expect(length).toBeCloseTo(Math.PI / 2, 9);
});
}
it('시작각이 음수 · 끝각 0 인 호 — 끝각을 2π 로 바꾸지 않음(온 원으로 안 그림)', () => {
const arc = new ArcEntity('layer1', new Point(0, 0), 1, -Math.PI / 2, 0, true);
const calls: number[][] = [];
const controller = {
setLineStyles: () => {},
drawArc: (_c: Point, _r: number, start: number, end: number) => calls.push([start, end]),
} as unknown as DrawController;
arc.draw(controller);
expect(calls[0][1] - calls[0][0]).toBeCloseTo(Math.PI / 2, 9);
});
it('복제 — 색 · 층 · 선 꼴 그대로', () => {
const arc = new ArcEntity('구조물', new Point(0, 0), 1, 0, 1, false);
arc.lineColor = '#ff0000';
arc.lineWidth = 3;
arc.lineDash = [2, 1];
const copy = arc.clone();
expect([copy.layerId, copy.lineColor, copy.lineWidth, copy.lineDash]).toEqual([
'구조물',
'#ff0000',
3,
[2, 1],
]);
});
});