Files
appRobotHoming/test/yAxisComputeJs.test.js
2026-06-13 06:13:31 +02:00

196 lines
7.4 KiB
JavaScript
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.
/**
* yAxisComputeJs.test.js
* =======================
* Unit-Test für public/yAxisCompute.js (reine JS-Berechnungslogik).
*
* Dieselben drei Timestamps wie der Python-Test:
* test/y-axis-finder-examples/20260612_190019 (Pos A)
* test/y-axis-finder-examples/20260612_190104 (Pos B)
* test/y-axis-finder-examples/20260612_190241 (Pos C)
*
* Erwartete Werte (aus verifiziertem Python-Lauf und boardViewer-Logik):
* Marker 197, 218, 219 → rotieren (3 Marker genutzt)
* Marker 201, 204, 242 → gefiltert (Bewegung < 10 mm)
* axisDir ≈ [0.9995, 0.029, -0.015] (fast reine X-Achse des Roboters)
*
* Hinweis: Im Gegensatz zum Python-Skript verwendet computeYAxis() nur die
* Marker-Zentren (position_mm), nicht alle vier Ecken. Deshalb:
* numMarkers = 3 (nicht 5 wie Python)
* Residuen etwas größer (Python hat 4× mehr Punkte je Marker)
*/
const fs = require('fs');
const path = require('path');
const { computeYAxis, DEFAULT_MIN_MOVEMENT_MM } = require('../public/yAxisCompute');
// ── Hilfsfunktionen ───────────────────────────────────────────────────────────
const EXAMPLES = path.join(__dirname, 'y-axis-finder-examples');
/** Lädt aruco_marker_poses.json und gibt die fremd-Marker zurück (link !== 'Board'). */
function loadFremdMarkers(timestamp) {
const file = path.join(EXAMPLES, timestamp, 'aruco_marker_poses.json');
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
return (data.markers ?? []).filter(m => m.link !== 'Board');
}
const fremdA = loadFremdMarkers('20260612_190019');
const fremdB = loadFremdMarkers('20260612_190104');
const fremdC = loadFremdMarkers('20260612_190241');
// ── Haupt-Suite ───────────────────────────────────────────────────────────────
describe('computeYAxis Arm1-Testdaten (190019 / 190104 / 190241)', () => {
let r;
beforeAll(() => {
r = computeYAxis(fremdA, fremdB, fremdC);
});
// Grundstatus
test('Berechnung erfolgreich (ok: true)', () => {
expect(r.ok).toBe(true);
});
// Marker-Anzahlen
test('numMarkersCommon erfasst alle gemeinsamen fremd-Marker', () => {
// Alle drei Timestamps haben dieselben 6 fremd-Marker in common:
// 197, 201, 204, 218, 219, 242
expect(r.numMarkersCommon).toBeGreaterThanOrEqual(4);
});
test('3 Marker tatsächlich genutzt (197, 218, 219)', () => {
expect(r.numMarkers).toBe(3);
const usedIds = r.markerData.map(m => m.markerId).sort((a, b) => a - b);
expect(usedIds).toEqual([197, 218, 219]);
});
// Filterung
test('kaum-bewegende Marker werden gefiltert (201, 204 mindestens)', () => {
const skippedIds = r.skipped.map(s => s.id).sort((a, b) => a - b);
expect(skippedIds).toContain(201);
expect(skippedIds).toContain(204);
});
test('gefilterte Marker haben Bewegung < DEFAULT_MIN_MOVEMENT_MM', () => {
r.skipped.forEach(s => {
expect(s.maxMoveMm).toBeLessThan(DEFAULT_MIN_MOVEMENT_MM);
expect(s.reason).toMatch(/Bewegung zu gering/);
});
});
test('gefilterte Marker enthalten posA (für späteres Base-Zuordnen)', () => {
r.skipped.forEach(s => {
expect(Array.isArray(s.posA)).toBe(true);
expect(s.posA).toHaveLength(3);
});
});
// Achsenrichtung
test('axisDir ist Einheitsvektor (|axisDir| ≈ 1)', () => {
const [ax, ay, az] = r.axisDir;
const len = Math.sqrt(ax * ax + ay * ay + az * az);
expect(len).toBeCloseTo(1.0, 4);
});
test('Achse liegt fast auf X-Achse des Roboters (axisDir[0] > 0.99)', () => {
// Arm1 schwingt auf/ab → Drehachse = X-Richtung
expect(r.axisDir[0]).toBeGreaterThan(0.99);
});
test('axisDir hat 3 Komponenten', () => {
expect(r.axisDir).toHaveLength(3);
});
test('axisPoint hat 3 Komponenten', () => {
expect(r.axisPoint).toHaveLength(3);
});
// Kippwinkel
test('tiltXY und tiltYZ sind finite Zahlen', () => {
expect(Number.isFinite(r.tiltXY)).toBe(true);
expect(Number.isFinite(r.tiltYZ)).toBe(true);
});
// markerData-Struktur
test('jeder markerData-Eintrag hat posA/posB/posC/circumcenter/normal', () => {
r.markerData.forEach(md => {
expect(md.posA).toHaveLength(3);
expect(md.posB).toHaveLength(3);
expect(md.posC).toHaveLength(3);
expect(md.circumcenter).toHaveLength(3);
expect(md.normal).toHaveLength(3);
});
});
});
// ── Parametertest: minMovementMm ──────────────────────────────────────────────
describe('computeYAxis minMovementMm-Parameter', () => {
test('minMovementMm = 0 → alle gemeinsamen Marker werden genutzt, keine Skips', () => {
const r = computeYAxis(fremdA, fremdB, fremdC, { minMovementMm: 0 });
expect(r.ok).toBe(true);
// Kein Marker sollte wegen Bewegung gefiltert sein
const movementSkips = r.skipped.filter(s => s.reason.includes('Bewegung zu gering'));
expect(movementSkips).toHaveLength(0);
// Mehr Marker als im Standardfall
expect(r.numMarkers).toBeGreaterThanOrEqual(3);
});
test('minMovementMm = 9999 → alle Marker gefiltert, ok: false', () => {
const r = computeYAxis(fremdA, fremdB, fremdC, { minMovementMm: 9999 });
expect(r.ok).toBe(false);
expect(r.reason).toMatch(/gefiltert|gefunden/i);
expect(Array.isArray(r.skipped)).toBe(true);
});
test('DEFAULT_MIN_MOVEMENT_MM ist 10', () => {
expect(DEFAULT_MIN_MOVEMENT_MM).toBe(10.0);
});
});
// ── Edge-Cases ────────────────────────────────────────────────────────────────
describe('computeYAxis Edge Cases', () => {
test('leere Eingaben → ok: false', () => {
const r = computeYAxis([], [], []);
expect(r.ok).toBe(false);
});
test('keine gemeinsamen Marker → ok: false', () => {
const a = [{ marker_id: 1, position_mm: [0, 0, 0] }];
const b = [{ marker_id: 2, position_mm: [1, 0, 0] }];
const c = [{ marker_id: 3, position_mm: [2, 0, 0] }];
const r = computeYAxis(a, b, c);
expect(r.ok).toBe(false);
});
test('drei kollineare Punkte → degenerat, ok: false (einzelner Marker)', () => {
// Marker bewegt sich auf einer Linie → Kreisebene undefiniert
const a = [{ marker_id: 99, position_mm: [0, 0, 100] }];
const b = [{ marker_id: 99, position_mm: [0, 100, 100] }]; // nur Y ändert sich
const c = [{ marker_id: 99, position_mm: [0, 200, 100] }]; // weiterhin nur Y
// Bewegung ist 200 mm (> 10 mm Threshold), aber Punkte sind kollinear
const r = computeYAxis(a, b, c, { minMovementMm: 0 });
expect(r.ok).toBe(false);
});
test('ein einzelner gültiger Marker → Achse berechenbar', () => {
// Punkt bewegt sich auf einem Kreisbogen (einfache 2D-Rotation um Z-Achse)
const R = 100; // Radius 100 mm
const angle = (deg) => deg * Math.PI / 180;
const mk = (deg) => ({
marker_id: 42,
position_mm: [R * Math.cos(angle(deg)), R * Math.sin(angle(deg)), 50],
});
const r = computeYAxis([mk(0)], [mk(90)], [mk(180)]);
expect(r.ok).toBe(true);
expect(r.numMarkers).toBe(1);
// Achse sollte in Z-Richtung zeigen [0, 0, ±1]
expect(Math.abs(r.axisDir[2])).toBeGreaterThan(0.99);
});
});