⚠ **뿌리** — `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>
69 lines
2.8 KiB
JavaScript
69 lines
2.8 KiB
JavaScript
// 수확기 rect 변환(B07_DesignDetail_UI_Cad_Structures.rectPoints) 자체검증 — 로직 복제.
|
|
// 돌쌓기 돌·돌망태 칸은 rect + rotate(벽기울기) 로 그린다. 회전을 빠뜨리면 돌이 벽을
|
|
// 삐져나가 클립에 전부 잘려 나간다(2026-09-03 실측: rect 49개가 CAD 에 안 실렸다).
|
|
// 실행: node tmp/tests/test_cad_rect_harvest.mjs
|
|
import assert from "node:assert/strict";
|
|
|
|
const flip = (x, y) => [x, -y];
|
|
|
|
function rectPoints(attrs, transform) {
|
|
const { x, y, width, height } = attrs;
|
|
const corners = [
|
|
[x, y],
|
|
[x + width, y],
|
|
[x + width, y + height],
|
|
[x, y + height],
|
|
[x, y],
|
|
];
|
|
const rotate = /rotate\(\s*(-?[\d.]+)[\s,]+(-?[\d.]+)[\s,]+(-?[\d.]+)\s*\)/.exec(transform ?? "");
|
|
if (!rotate) return corners.map(([cx, cy]) => flip(cx, cy));
|
|
const angle = (Number(rotate[1]) * Math.PI) / 180;
|
|
const [ox, oy] = [Number(rotate[2]), Number(rotate[3])];
|
|
const cos = Math.cos(angle);
|
|
const sin = Math.sin(angle);
|
|
return corners.map(([cx, cy]) => {
|
|
const dx = cx - ox;
|
|
const dy = cy - oy;
|
|
return flip(ox + dx * cos - dy * sin, oy + dx * sin + dy * cos);
|
|
});
|
|
}
|
|
|
|
const near = (a, b) => Math.abs(a - b) < 1e-9;
|
|
|
|
// ① 회전이 없으면 네 모서리를 y만 뒤집어 닫힌 점열로 낸다.
|
|
{
|
|
const points = rectPoints({ x: 10, y: 20, width: 4, height: 6 }, null);
|
|
assert.equal(points.length, 5);
|
|
assert.deepEqual(points[0], [10, -20]);
|
|
assert.deepEqual(points[2], [14, -26]);
|
|
assert.deepEqual(points[0], points[4], "닫힌 점열이어야 한다");
|
|
}
|
|
|
|
// ② rotate(90 cx cy) — 중심 기준 90° 회전. 폭·높이가 맞바뀐다.
|
|
{
|
|
const points = rectPoints({ x: -1, y: -2, width: 2, height: 4 }, "rotate(90 0 0)");
|
|
const xs = points.map((p) => p[0]);
|
|
const ys = points.map((p) => p[1]);
|
|
assert.ok(near(Math.max(...xs) - Math.min(...xs), 4), "가로가 원래 세로가 된다");
|
|
assert.ok(near(Math.max(...ys) - Math.min(...ys), 2), "세로가 원래 가로가 된다");
|
|
}
|
|
|
|
// ③ 회전 중심은 제자리에 남는다 — 벽 기울기만큼 기울여도 돌 중심이 안 움직인다.
|
|
{
|
|
const attrs = { x: 8, y: 12, width: 4, height: 2 };
|
|
const center = [attrs.x + attrs.width / 2, attrs.y + attrs.height / 2];
|
|
const points = rectPoints(attrs, `rotate(-16.7 ${center[0]} ${center[1]})`);
|
|
const xs = points.map((p) => p[0]);
|
|
const ys = points.map((p) => p[1]);
|
|
assert.ok(near((Math.min(...xs) + Math.max(...xs)) / 2, center[0]));
|
|
assert.ok(near((Math.min(...ys) + Math.max(...ys)) / 2, -center[1]));
|
|
}
|
|
|
|
// ④ 소수·쉼표 구분자도 읽는다 — SVG가 `rotate(-16.7,120.5,88.25)` 로 쓰기도 한다.
|
|
{
|
|
const points = rectPoints({ x: 0, y: 0, width: 2, height: 2 }, "rotate(-16.7,1,1)");
|
|
assert.ok(!near(points[0][0], 0), "회전이 실제로 적용돼야 한다");
|
|
}
|
|
|
|
console.log("OK test_cad_rect_harvest.mjs — 4건");
|