Marker error +110
This commit is contained in:
@@ -1160,7 +1160,10 @@ window.addEventListener('message', async (e) => {
|
|||||||
await loadData(e.data.runDir);
|
await loadData(e.data.runDir);
|
||||||
}
|
}
|
||||||
if (e.data?.type === 'homing-state' && IS_HOMING) {
|
if (e.data?.type === 'homing-state' && IS_HOMING) {
|
||||||
_homingAngles = e.data.state;
|
// Gefundene Winkel über Default-Position mergen, damit noch nicht erkannte
|
||||||
|
// Gelenke nicht auf 0 zusammenfallen, sondern sinnvoll stehen bleiben.
|
||||||
|
const base = _currentRobot?.defaultPosition ?? {};
|
||||||
|
_homingAngles = { ...base, ...e.data.state };
|
||||||
if (_currentRobot) buildSkeletonFK(_currentRobot, _homingAngles);
|
if (_currentRobot) buildSkeletonFK(_currentRobot, _homingAngles);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,23 +10,80 @@ import path from 'path';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import fsPromises from 'fs/promises';
|
import fsPromises from 'fs/promises';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modell-Welt-x eines Markers bei slider=0, durch Aufsummieren der origin.x
|
||||||
|
* entlang der Kette Link→…→Base. Das Ergebnis ist winkel-unabhängig (und damit
|
||||||
|
* exakt) genau dann, wenn alle revolute-Gelenke der Kette um die x-Achse drehen
|
||||||
|
* (Rotation um x erhält die x-Koordinate). Andernfalls xSafe=false.
|
||||||
|
*
|
||||||
|
* @param {object} links robot.json links
|
||||||
|
* @param {string} linkName
|
||||||
|
* @param {number[]} localPos Marker-Position im lokalen Link-Frame [x,y,z]
|
||||||
|
* @returns {{ worldX: number, xSafe: boolean }}
|
||||||
|
*/
|
||||||
|
function modelWorldXAtSliderZero(links, linkName, localPos) {
|
||||||
|
let xOffset = 0;
|
||||||
|
let xSafe = true;
|
||||||
|
let cur = linkName;
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
while (cur && links[cur]?.jointToParent && !seen.has(cur)) {
|
||||||
|
seen.add(cur);
|
||||||
|
const jtp = links[cur].jointToParent;
|
||||||
|
xOffset += jtp.origin?.[0] ?? 0;
|
||||||
|
|
||||||
|
const axis = jtp.axis ?? [0, 0, 0];
|
||||||
|
const isXAxis = Math.abs(axis[0]) === 1 && axis[1] === 0 && axis[2] === 0;
|
||||||
|
if (jtp.type === 'revolute' && !isXAxis) xSafe = false;
|
||||||
|
|
||||||
|
cur = links[cur].parent;
|
||||||
|
}
|
||||||
|
return { worldX: xOffset + (localPos?.[0] ?? 0), xSafe };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schätzt die Slider-X-Position aus den triangulierten Marker-Positionen
|
* Schätzt die Slider-X-Position aus den triangulierten Marker-Positionen
|
||||||
* (aruco_marker_poses.json). Nutzt den Durchschnitt der x_mm aller
|
* (aruco_marker_poses.json).
|
||||||
* Nicht-Board-Marker. Fallback: 0.0 wenn keine Arm-Marker sichtbar.
|
*
|
||||||
|
* Für jeden beobachteten Arm-Marker wird der implizierte Slider-Wert berechnet:
|
||||||
|
* slider_i = beobachtetes_world_x − Modell_world_x(slider=0)
|
||||||
|
* und über alle x-zuverlässigen Marker gemittelt. So wird der Gelenk-Offset
|
||||||
|
* (z.B. Arm1.origin.x = 110 mm) korrekt herausgerechnet.
|
||||||
|
*
|
||||||
|
* Fallback (kein robot.json oder keine zuverlässigen Marker): alter Mittelwert
|
||||||
|
* der rohen world-x – nur als Notlösung.
|
||||||
*
|
*
|
||||||
* @param {string} arucoJsonPath
|
* @param {string} arucoJsonPath
|
||||||
|
* @param {string} [robotJsonPath]
|
||||||
* @returns {number} x_mm
|
* @returns {number} x_mm
|
||||||
*/
|
*/
|
||||||
export function estimateXFromMarkers(arucoJsonPath) {
|
export function estimateXFromMarkers(arucoJsonPath, robotJsonPath) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(fs.readFileSync(arucoJsonPath, 'utf8'));
|
const data = JSON.parse(fs.readFileSync(arucoJsonPath, 'utf8'));
|
||||||
const armMarkers = (data.markers ?? []).filter(
|
const links = robotJsonPath
|
||||||
m => m.link && m.link !== 'Board',
|
? (JSON.parse(fs.readFileSync(robotJsonPath, 'utf8')).links ?? {})
|
||||||
);
|
: {};
|
||||||
|
|
||||||
|
const samples = [];
|
||||||
|
for (const obs of (data.markers ?? [])) {
|
||||||
|
if (!obs.link || obs.link === 'Board') continue;
|
||||||
|
const modelMarker = links[obs.link]?.markers?.find(m => m.id === obs.marker_id);
|
||||||
|
if (!modelMarker?.position) continue;
|
||||||
|
const { worldX, xSafe } = modelWorldXAtSliderZero(links, obs.link, modelMarker.position);
|
||||||
|
if (!xSafe) continue;
|
||||||
|
const obsX = obs.position_mm?.[0];
|
||||||
|
if (obsX == null) continue;
|
||||||
|
samples.push(obsX - worldX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples.length > 0) {
|
||||||
|
return samples.reduce((a, b) => a + b, 0) / samples.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Fallback: alter, geometrisch ungenauer Mittelwert ──
|
||||||
|
const armMarkers = (data.markers ?? []).filter(m => m.link && m.link !== 'Board');
|
||||||
if (armMarkers.length === 0) return 0.0;
|
if (armMarkers.length === 0) return 0.0;
|
||||||
const sumX = armMarkers.reduce((s, m) => s + (m.position_mm?.[0] ?? 0), 0);
|
return armMarkers.reduce((s, m) => s + (m.position_mm?.[0] ?? 0), 0) / armMarkers.length;
|
||||||
return sumX / armMarkers.length;
|
|
||||||
} catch {
|
} catch {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
@@ -78,7 +135,7 @@ export async function runHoming({
|
|||||||
|
|
||||||
// ── Schritt 2: X-Position bestimmen ─────────────────────────────────────
|
// ── Schritt 2: X-Position bestimmen ─────────────────────────────────────
|
||||||
send({ type: 'step', step: 2, total: 6, text: 'X-Position bestimmen …' });
|
send({ type: 'step', step: 2, total: 6, text: 'X-Position bestimmen …' });
|
||||||
const xMm = estimateXFromMarkers(arucoJson);
|
const xMm = estimateXFromMarkers(arucoJson, robotJsonPath);
|
||||||
send({ type: 'log', text: `▶ Geschätzte X-Position: ${xMm.toFixed(1)} mm` });
|
send({ type: 'log', text: `▶ Geschätzte X-Position: ${xMm.toFixed(1)} mm` });
|
||||||
send({ type: 'analysis', key: 'x_mm', value: xMm });
|
send({ type: 'analysis', key: 'x_mm', value: xMm });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user