Y-Axis checks

This commit is contained in:
chk
2026-06-13 06:13:31 +02:00
parent 1762a771cf
commit 7f17427e0a
7 changed files with 765 additions and 110 deletions

195
test/yAxisComputeJs.test.js Normal file
View File

@@ -0,0 +1,195 @@
/**
* 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);
});
});