x-axis justierung: lines 3
This commit is contained in:
@@ -666,6 +666,54 @@ function buildCompareLines() {
|
|||||||
if (onlyCompare.length) parts.push(`nur Vergleich: ${onlyCompare.join(' ')}`);
|
if (onlyCompare.length) parts.push(`nur Vergleich: ${onlyCompare.join(' ')}`);
|
||||||
if (noMatch.length) parts.push(`nur Basis: ${noMatch.join(' ')}`);
|
if (noMatch.length) parts.push(`nur Basis: ${noMatch.join(' ')}`);
|
||||||
vlog(parts.join(' | '), matchedIds.length ? 'ok' : 'warn');
|
vlog(parts.join(' | '), matchedIds.length ? 'ok' : 'warn');
|
||||||
|
|
||||||
|
// ── Bewegungsanalyse: mittlerer Verschiebungsvektor → Abweichung von X-Achse ──
|
||||||
|
if (matchedIds.length > 0) {
|
||||||
|
// Summe aller Verschiebungsvektoren (in Roboter-Koordinaten mm)
|
||||||
|
let sx = 0, sy = 0, sz = 0;
|
||||||
|
for (const cm of _compareFremdMarkers) {
|
||||||
|
const pm = primaryMap.get(cm.marker_id);
|
||||||
|
if (!pm) continue;
|
||||||
|
const [pmx, pmy, pmz] = pm.position_mm.map(Number);
|
||||||
|
const [cmx, cmy, cmz] = cm.position_mm.map(Number);
|
||||||
|
sx += cmx - pmx; sy += cmy - pmy; sz += cmz - pmz;
|
||||||
|
}
|
||||||
|
const n = matchedIds.length;
|
||||||
|
const dx = sx / n, dy = sy / n, dz = sz / n;
|
||||||
|
const dist = Math.sqrt(dx*dx + dy*dy + dz*dz);
|
||||||
|
|
||||||
|
if (dist > 0.01) { // mindestens 0.01 mm Bewegung
|
||||||
|
// Einheitsvektor – immer in Richtung positives X zeigen (konsistente Vorzeichen)
|
||||||
|
let vx = dx/dist, vy = dy/dist, vz = dz/dist;
|
||||||
|
if (vx < 0) { vx = -vx; vy = -vy; vz = -vz; }
|
||||||
|
|
||||||
|
// Abweichungswinkel zur X-Achse [1,0,0] in Roboter-Koordinaten
|
||||||
|
// horizontal (XY-Ebene, Rotation um Z): positiv = nach Y (rückwärts) verschoben
|
||||||
|
// vertikal (XZ-Ebene, Rotation um Y): positiv = nach oben (+Z) verschoben
|
||||||
|
const degXY = Math.atan2(vy, vx) * 180 / Math.PI;
|
||||||
|
const degXZ = Math.atan2(vz, vx) * 180 / Math.PI;
|
||||||
|
|
||||||
|
const fmt = v => (v >= 0 ? '+' : '') + v.toFixed(3) + '°';
|
||||||
|
const good = Math.abs(degXY) < 0.5 && Math.abs(degXZ) < 0.5;
|
||||||
|
|
||||||
|
vlog(`Bewegung: ⌀${dist.toFixed(2)} mm dir=[${vx.toFixed(4)}, ${vy.toFixed(4)}, ${vz.toFixed(4)}] (${n} Marker)`);
|
||||||
|
vlog(`Abw. von X-Achse: horizontal(XY) ${fmt(degXY)} vertikal(XZ) ${fmt(degXZ)}`, good ? 'ok' : 'warn');
|
||||||
|
|
||||||
|
// Parent informieren → aktiviert "X-Achse übernehmen"-Button
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'xaxis-measurement',
|
||||||
|
direction: [vx, vy, vz],
|
||||||
|
angleXY: degXY,
|
||||||
|
angleXZ: degXZ,
|
||||||
|
numMarkers: n,
|
||||||
|
distMm: dist,
|
||||||
|
}, '*');
|
||||||
|
} else {
|
||||||
|
vlog(`Bewegung zu klein (${dist.toFixed(3)} mm) – Winkelberechnung übersprungen`, 'warn');
|
||||||
|
// Messung ungültig → Parent mitteilen
|
||||||
|
window.parent.postMessage({ type: 'xaxis-measurement', direction: null }, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Daten laden ───────────────────────────────────────────────────────────────
|
// ── Daten laden ───────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -420,6 +420,80 @@ function initXAxis() {
|
|||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── X-Achse übernehmen ────────────────────────────────────────────────────
|
||||||
|
// Empfängt postMessage aus dem eingebetteten boardViewer-iframe.
|
||||||
|
let _xaxisDirection = null; // zuletzt gemessene Richtung [vx,vy,vz]
|
||||||
|
|
||||||
|
const adoptBtn = document.getElementById('btn-xaxis-adopt');
|
||||||
|
|
||||||
|
function onXaxisMessage(e) {
|
||||||
|
// Nachricht muss vom boardViewer-iframe stammen
|
||||||
|
const frame = document.getElementById('xaxis-viewer-frame');
|
||||||
|
if (!frame || e.source !== frame.contentWindow) return;
|
||||||
|
const msg = e.data;
|
||||||
|
if (!msg || msg.type !== 'xaxis-measurement') return;
|
||||||
|
|
||||||
|
if (Array.isArray(msg.direction)) {
|
||||||
|
_xaxisDirection = msg.direction;
|
||||||
|
const fmt = v => (v >= 0 ? '+' : '') + v.toFixed(3) + '°';
|
||||||
|
logX(`📐 Messung empfangen: dir=[${msg.direction.map(v => v.toFixed(4)).join(', ')}]` +
|
||||||
|
` XY=${fmt(msg.angleXY)} XZ=${fmt(msg.angleXZ)}` +
|
||||||
|
` (${msg.numMarkers} Marker, Ø${msg.distMm.toFixed(1)} mm)`);
|
||||||
|
if (adoptBtn) {
|
||||||
|
adoptBtn.disabled = false;
|
||||||
|
adoptBtn.style.opacity = '1';
|
||||||
|
adoptBtn.style.cursor = 'pointer';
|
||||||
|
adoptBtn.title = `X-Achse übernehmen (dir=[${_xaxisDirection.map(v => v.toFixed(4)).join(', ')}])`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ungültige / zu kleine Bewegung → Button sperren
|
||||||
|
_xaxisDirection = null;
|
||||||
|
if (adoptBtn) {
|
||||||
|
adoptBtn.disabled = true;
|
||||||
|
adoptBtn.style.opacity = '.45';
|
||||||
|
adoptBtn.style.cursor = 'not-allowed';
|
||||||
|
adoptBtn.title = 'Noch keine gültige Messung verfügbar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', onXaxisMessage);
|
||||||
|
|
||||||
|
if (adoptBtn) {
|
||||||
|
adoptBtn.addEventListener('click', async () => {
|
||||||
|
if (!_xaxisDirection) return;
|
||||||
|
const fmt = v => (v >= 0 ? '+' : '') + v.toFixed(4);
|
||||||
|
logX(`🔄 Übernehme X-Achse: dir=[${_xaxisDirection.map(fmt).join(', ')}] …`);
|
||||||
|
adoptBtn.disabled = true;
|
||||||
|
adoptBtn.style.opacity = '.45';
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/robot/adopt-x-axis', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ direction: _xaxisDirection }),
|
||||||
|
});
|
||||||
|
const data = await r.json();
|
||||||
|
if (!r.ok) {
|
||||||
|
logX(`❌ Fehler: ${data.error ?? r.status}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logX(`✅ X-Achse gespeichert — ${data.numChanged} Marker rotiert`);
|
||||||
|
logX(` Ursprung (A0-Schwerpunkt): [${data.origin.join(', ')}] mm`);
|
||||||
|
logX(` Neue X-Achse: [${data.newXAxis.join(', ')}]` +
|
||||||
|
` Korr. XY=${(data.angleXYdeg >= 0 ? '+' : '') + data.angleXYdeg}°` +
|
||||||
|
` XZ=${(data.angleXZdeg >= 0 ? '+' : '') + data.angleXZdeg}°`);
|
||||||
|
// Viewer neu laden damit die aktualisierten Positionen sichtbar werden
|
||||||
|
const frame = document.getElementById('xaxis-viewer-frame');
|
||||||
|
if (frame?.contentWindow) frame.contentWindow.postMessage({ type: 'reload' }, '*');
|
||||||
|
} catch (err) {
|
||||||
|
logX(`❌ Netzwerkfehler: ${err}`);
|
||||||
|
} finally {
|
||||||
|
adoptBtn.disabled = false;
|
||||||
|
adoptBtn.style.opacity = '1';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tab: Board (shared helpers) ───────────────────────────────────────────────
|
// ── Tab: Board (shared helpers) ───────────────────────────────────────────────
|
||||||
|
|||||||
@@ -45,6 +45,16 @@
|
|||||||
Rechts ➡
|
Rechts ➡
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="margin-top:16px;display:flex;align-items:center;gap:14px;flex-wrap:wrap">
|
||||||
|
<button id="btn-xaxis-adopt" disabled
|
||||||
|
style="padding:5px 18px;opacity:.45;cursor:not-allowed;border:1px solid #4a9eff;border-radius:3px;background:#1e293b;color:#c8cdd8;font:inherit;font-size:12px"
|
||||||
|
title="Gemessene X-Achsen-Richtung in robot.json übernehmen – folgt">
|
||||||
|
X-Achse übernehmen
|
||||||
|
</button>
|
||||||
|
<span style="font-size:11px;color:var(--muted)">
|
||||||
|
Übernimmt die gemessene Richtung (aus Basis- und Vergleichs-Run) als X-Achse in robot.json
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Ausgabe / Log ──────────────────────────────────────────────────────── -->
|
<!-- ── Ausgabe / Log ──────────────────────────────────────────────────────── -->
|
||||||
|
|||||||
@@ -389,3 +389,102 @@ export async function assignMarkerId(robotPath, { markerId, set, link, extraMark
|
|||||||
change: { action: 'added', markerId: id, oldLink: null, oldSet: '', newLink: link, newSet: set ?? '' },
|
change: { action: 'added', markerId: id, oldLink: null, oldSet: '', newLink: link, newSet: set ?? '' },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Aktion 5: X-Achse übernehmen ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotiert alle Marker-Positionen in robot.json so, dass die übergebene Richtung
|
||||||
|
* zur neuen X-Achse [1,0,0] wird. Rotation um den Schwerpunkt aller A0-Marker
|
||||||
|
* (Origin bleibt erhalten).
|
||||||
|
*
|
||||||
|
* direction: [vx, vy, vz] – gemessene Ist-X-Richtung in Roboter-Koordinaten
|
||||||
|
*
|
||||||
|
* Algorithmus:
|
||||||
|
* 1. Normalisiere direction, ggf. Vorzeichen so dass vx > 0
|
||||||
|
* 2. Baue Orthonormalbasis: x_new = v,
|
||||||
|
* z_new = Gram-Schmidt([0,0,1] gegen v),
|
||||||
|
* y_new = cross(z_new, x_new)
|
||||||
|
* 3. Rotationsmatrix R (old→new): Zeilen = [x_new, y_new, z_new]
|
||||||
|
* 4. p_new = origin + R * (p_old − origin)
|
||||||
|
*/
|
||||||
|
export async function adoptXAxis(robotPath, { direction }) {
|
||||||
|
const [vx, vy, vz] = direction.map(Number);
|
||||||
|
const len = Math.sqrt(vx * vx + vy * vy + vz * vz);
|
||||||
|
if (len < 1e-9) throw new Error('Richtungsvektor zu klein (fast Null-Vektor).');
|
||||||
|
|
||||||
|
// Normalisieren, immer positive X-Komponente
|
||||||
|
let nx = vx / len, ny = vy / len, nz = vz / len;
|
||||||
|
if (nx < 0) { nx = -nx; ny = -ny; nz = -nz; }
|
||||||
|
|
||||||
|
// ── Orthonormalbasis ──────────────────────────────────────────────────────
|
||||||
|
// Z_new: Gram-Schmidt von [0,0,1] gegen x_new
|
||||||
|
const dotZ = nz; // dot([0,0,1], [nx,ny,nz])
|
||||||
|
let zx = -dotZ * nx, zy = -dotZ * ny, zz = 1 - dotZ * nz;
|
||||||
|
let zlen = Math.sqrt(zx * zx + zy * zy + zz * zz);
|
||||||
|
if (zlen < 1e-9) {
|
||||||
|
// Sonderfall: x_new fast parallel zu Z – Fallback auf [0,1,0]
|
||||||
|
const dotY = ny;
|
||||||
|
zx = -dotY * nx; zy = 1 - dotY * ny; zz = -dotY * nz;
|
||||||
|
zlen = Math.sqrt(zx * zx + zy * zy + zz * zz);
|
||||||
|
}
|
||||||
|
zx /= zlen; zy /= zlen; zz /= zlen;
|
||||||
|
|
||||||
|
// Y_new = cross(z_new, x_new) [rechte-Hand-Regel: ẑ × x̂ = ŷ]
|
||||||
|
const yx = zy * nz - zz * ny;
|
||||||
|
const yy = zz * nx - zx * nz;
|
||||||
|
const yz = zx * ny - zy * nx;
|
||||||
|
|
||||||
|
// Rotationsfunktion: p_rot = R * p (R hat Zeilen = neue Achsen in alten Koordinaten)
|
||||||
|
function rotVec(px, py, pz) {
|
||||||
|
return [
|
||||||
|
nx * px + ny * py + nz * pz, // neue X-Komponente
|
||||||
|
yx * px + yy * py + yz * pz, // neue Y-Komponente
|
||||||
|
zx * px + zy * py + zz * pz, // neue Z-Komponente
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const robot = await readRobot(robotPath);
|
||||||
|
const links = robot.links ?? {};
|
||||||
|
|
||||||
|
// ── Ursprung: Schwerpunkt aller A0-Marker ────────────────────────────────
|
||||||
|
const a0Pos = [];
|
||||||
|
for (const ld of Object.values(links)) {
|
||||||
|
for (const m of (ld.markers ?? [])) {
|
||||||
|
if (m.set === 'A0' && Array.isArray(m.position) && m.position.length >= 3) {
|
||||||
|
a0Pos.push(m.position.map(Number));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ox = 0, oy = 0, oz = 0;
|
||||||
|
if (a0Pos.length > 0) {
|
||||||
|
for (const [px, py, pz] of a0Pos) { ox += px; oy += py; oz += pz; }
|
||||||
|
ox /= a0Pos.length; oy /= a0Pos.length; oz /= a0Pos.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Alle Marker rotieren ──────────────────────────────────────────────────
|
||||||
|
let numChanged = 0;
|
||||||
|
for (const ld of Object.values(links)) {
|
||||||
|
for (const m of (ld.markers ?? [])) {
|
||||||
|
if (!Array.isArray(m.position) || m.position.length < 3) continue;
|
||||||
|
const [px, py, pz] = m.position.map(Number);
|
||||||
|
const [rx, ry, rz] = rotVec(px - ox, py - oy, pz - oz);
|
||||||
|
m.position = [
|
||||||
|
Math.round((ox + rx) * 100) / 100,
|
||||||
|
Math.round((oy + ry) * 100) / 100,
|
||||||
|
Math.round((oz + rz) * 100) / 100,
|
||||||
|
];
|
||||||
|
numChanged++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
robot.links = links;
|
||||||
|
await writeRobot(robotPath, robot);
|
||||||
|
|
||||||
|
return {
|
||||||
|
numChanged,
|
||||||
|
origin: [ox, oy, oz].map(v => Math.round(v * 10) / 10),
|
||||||
|
newXAxis: [nx, ny, nz].map(v => Math.round(v * 10000) / 10000),
|
||||||
|
angleXYdeg: Math.round(Math.atan2(ny, nx) * 18000 / Math.PI) / 100,
|
||||||
|
angleXZdeg: Math.round(Math.atan2(nz, nx) * 18000 / Math.PI) / 100,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'url';
|
|||||||
import process from 'process';
|
import process from 'process';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { WebcamClient } from './webcamClient.js';
|
import { WebcamClient } from './webcamClient.js';
|
||||||
import { assignByZRange, removeMarkerAssignment, alignSetToMeasured, assignMarkerId } from './editRobot.js';
|
import { assignByZRange, removeMarkerAssignment, alignSetToMeasured, assignMarkerId, adoptXAxis } from './editRobot.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -864,6 +864,31 @@ app.post('/api/robot/assign-id', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/robot/adopt-x-axis
|
||||||
|
* Dreht alle Marker-Positionen in robot.json so, dass die gemessene Richtung
|
||||||
|
* zur neuen X-Achse [1,0,0] wird. Rotation um den A0-Schwerpunkt.
|
||||||
|
* Body: { direction: [vx, vy, vz] }
|
||||||
|
*/
|
||||||
|
app.post('/api/robot/adopt-x-axis', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { direction } = req.body ?? {};
|
||||||
|
if (!Array.isArray(direction) || direction.length < 3) {
|
||||||
|
return res.status(400).json({ error: '"direction" muss ein Array [vx,vy,vz] sein.' });
|
||||||
|
}
|
||||||
|
const result = await adoptXAxis(ROBOT_JSON, { direction });
|
||||||
|
console.log(
|
||||||
|
`robot/adopt-x-axis dir=[${direction.map(v => Number(v).toFixed(4)).join(', ')}]` +
|
||||||
|
` → ${result.numChanged} Marker, Ursprung=[${result.origin.join(', ')}]` +
|
||||||
|
` XY=${result.angleXYdeg}° XZ=${result.angleXZdeg}°`,
|
||||||
|
);
|
||||||
|
return res.json(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('robot/adopt-x-axis error:', err);
|
||||||
|
return res.status(500).json({ error: String(err) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/calibration/upload-npz
|
* POST /api/calibration/upload-npz
|
||||||
* Liest {camera}_calibration.npz aus der aktuellen Session und
|
* Liest {camera}_calibration.npz aus der aktuellen Session und
|
||||||
|
|||||||
Reference in New Issue
Block a user