rotation vorbereitung
This commit is contained in:
@@ -159,7 +159,9 @@
|
||||
<span><span class="dot circle" style="background:#dde3ec"></span>Erkannt (nur 2D)</span>
|
||||
<span><span class="dot circle" style="background:#fbbf24"></span>Gemessen (3b)</span>
|
||||
<span><span class="dot circle" style="background:#3b82f6"></span>Fremd (3b)</span>
|
||||
<span><span class="dot circle" style="background:#f97316"></span>Vergleich</span>
|
||||
<span><span class="dot circle" style="background:#f97316"></span>Pos B</span>
|
||||
<span><span class="dot circle" style="background:#22d3ee"></span>Pos C</span>
|
||||
<span><span class="dot circle" style="background:#fb7185"></span>Kreismittelpkt.</span>
|
||||
<span><span class="dot" style="background:#9b7bff"></span>Kamera</span>
|
||||
</div>
|
||||
<span id="stats"></span>
|
||||
@@ -172,12 +174,18 @@
|
||||
<select id="sel-run-primary" class="btn" style="min-width:158px">
|
||||
<option value="">⟳ aktuellster</option>
|
||||
</select>
|
||||
<span class="run-lbl" style="margin-left:10px">Vergleich
|
||||
<span class="run-lbl" style="margin-left:10px">Pos B
|
||||
<span style="text-transform:none;opacity:.7;font-size:9px">(nur fremd)</span>
|
||||
</span>
|
||||
<select id="sel-run-compare" class="btn" style="min-width:158px">
|
||||
<option value="">– keiner –</option>
|
||||
</select>
|
||||
<span class="run-lbl" style="margin-left:10px">Pos C
|
||||
<span style="text-transform:none;opacity:.7;font-size:9px">(Y-Achse)</span>
|
||||
</span>
|
||||
<select id="sel-run-c" class="btn" style="min-width:158px">
|
||||
<option value="">– keiner –</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="canvas-wrap">
|
||||
@@ -229,13 +237,16 @@ const gPaper = new THREE.Group(); // weißes A0-Papier
|
||||
const gMarkers = new THREE.Group(); // Modell-Rechtecke
|
||||
const gMeasured = new THREE.Group(); // gemessene Positionen (3b)
|
||||
const gCameras = new THREE.Group(); // Kamera-Frusta
|
||||
const gCompare = new THREE.Group(); // Vergleichs-Punkte (anderer Timestamp, nur fremd)
|
||||
const gCompareLines = new THREE.Group(); // Verbindungslinien Basis↔Vergleich
|
||||
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines);
|
||||
const gCompare = new THREE.Group(); // Pos B Marker (nur fremd, orange)
|
||||
const gCompareLines = new THREE.Group(); // Verbindungslinien Pos A↔Pos B
|
||||
const gPositionC = new THREE.Group(); // Pos C Marker (nur fremd, cyan)
|
||||
const gYAxis = new THREE.Group(); // Y-Achse Visualisierung (Kreismittelpunkte, Achse)
|
||||
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines, gPositionC, gYAxis);
|
||||
|
||||
// ── Zustand für Vergleichs-Linien ─────────────────────────────────────────────
|
||||
let _primaryFremdMarkers = []; // [{marker_id, position_mm, num_cameras}]
|
||||
let _compareFremdMarkers = []; // [{marker_id, position_mm, num_cameras}]
|
||||
// ── Zustand für Positionen ────────────────────────────────────────────────────
|
||||
let _primaryFremdMarkers = []; // Pos A – [{marker_id, position_mm, num_cameras}]
|
||||
let _compareFremdMarkers = []; // Pos B – [{marker_id, position_mm, num_cameras}]
|
||||
let _positionCFremdMarkers = []; // Pos C – [{marker_id, position_mm, num_cameras}]
|
||||
|
||||
// ── Viewer-interner Logger ────────────────────────────────────────────────────
|
||||
function vlog(msg, kind = '') {
|
||||
@@ -663,13 +674,19 @@ function buildCompareLines() {
|
||||
const noMatch = onlyPrimary.filter(id => !matchedIds.includes(id));
|
||||
const parts = [`${matchedIds.length} Linien`];
|
||||
if (matchedIds.length) parts.push(`IDs: ${matchedIds.join(' ')}`);
|
||||
if (onlyCompare.length) parts.push(`nur Vergleich: ${onlyCompare.join(' ')}`);
|
||||
if (noMatch.length) parts.push(`nur Basis: ${noMatch.join(' ')}`);
|
||||
if (onlyCompare.length) parts.push(`nur Pos B: ${onlyCompare.join(' ')}`);
|
||||
if (noMatch.length) parts.push(`nur Pos A: ${noMatch.join(' ')}`);
|
||||
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)
|
||||
// ── Bewegungsanalyse ──────────────────────────────────────────────────────────
|
||||
// Pos C aktiv → Bogen-Modus (Y-Achse), keine lineare X-Achsen-Analyse
|
||||
const posC_active = !!document.getElementById('sel-run-c')?.value;
|
||||
|
||||
if (posC_active) {
|
||||
vlog('Pos C aktiv → Bogen-Modus, X-Achsen-Analyse übersprungen');
|
||||
window.parent.postMessage({ type: 'xaxis-measurement', direction: null }, '*');
|
||||
} else if (matchedIds.length > 0) {
|
||||
// ── Lineare X-Achsen-Analyse (mittlerer Verschiebungsvektor) ──────────────
|
||||
let sx = 0, sy = 0, sz = 0;
|
||||
for (const cm of _compareFremdMarkers) {
|
||||
const pm = primaryMap.get(cm.marker_id);
|
||||
@@ -699,7 +716,6 @@ function buildCompareLines() {
|
||||
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],
|
||||
@@ -710,10 +726,129 @@ function buildCompareLines() {
|
||||
}, '*');
|
||||
} 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 }, '*');
|
||||
}
|
||||
}
|
||||
|
||||
// Y-Achse neu berechnen (nutzt _positionCFremdMarkers – kein-op wenn leer)
|
||||
computeAndShowYAxis();
|
||||
}
|
||||
|
||||
// ── Y-Achsen-Berechnung aus drei Positionen ───────────────────────────────────
|
||||
|
||||
function computeAndShowYAxis() {
|
||||
clearGroup(gYAxis);
|
||||
|
||||
// Nur aktiv wenn alle drei Positionen vorliegen
|
||||
if (_positionCFremdMarkers.length === 0) return;
|
||||
if (_primaryFremdMarkers.length === 0) return;
|
||||
if (_compareFremdMarkers.length === 0) {
|
||||
vlog('Y-Achse: Pos B nicht gewählt – kein Vergleich aktiv', 'warn');
|
||||
return;
|
||||
}
|
||||
|
||||
const mapA = new Map(_primaryFremdMarkers .map(m => [m.marker_id, m]));
|
||||
const mapB = new Map(_compareFremdMarkers .map(m => [m.marker_id, m]));
|
||||
const mapC = new Map(_positionCFremdMarkers.map(m => [m.marker_id, m]));
|
||||
|
||||
function dist2(P, Q) { return (P[0]-Q[0])**2 + (P[1]-Q[1])**2 + (P[2]-Q[2])**2; }
|
||||
|
||||
const circumcenters = []; // [{id, C:[x,y,z]}] in mm
|
||||
const normals = []; // [[nx,ny,nz]] – Achsenrichtung je Marker
|
||||
|
||||
for (const [id, ma] of mapA) {
|
||||
const mb = mapB.get(id);
|
||||
const mc = mapC.get(id);
|
||||
if (!mb || !mc) continue;
|
||||
|
||||
const P1 = ma.position_mm.map(Number);
|
||||
const P2 = mb.position_mm.map(Number);
|
||||
const P3 = mc.position_mm.map(Number);
|
||||
|
||||
// Normalenvektor der Kreisebene = Achsenrichtung
|
||||
const v1 = [P2[0]-P1[0], P2[1]-P1[1], P2[2]-P1[2]];
|
||||
const v2 = [P3[0]-P1[0], P3[1]-P1[1], P3[2]-P1[2]];
|
||||
const cross = [
|
||||
v1[1]*v2[2] - v1[2]*v2[1],
|
||||
v1[2]*v2[0] - v1[0]*v2[2],
|
||||
v1[0]*v2[1] - v1[1]*v2[0],
|
||||
];
|
||||
const crossLen = Math.sqrt(cross[0]**2 + cross[1]**2 + cross[2]**2);
|
||||
if (crossLen < 1e-3) {
|
||||
vlog(`Y-Achse: Marker ${id} degenerat (Punkte zu nahe / kollinear)`, 'warn');
|
||||
continue;
|
||||
}
|
||||
const n = cross.map(c => c / crossLen);
|
||||
normals.push(n);
|
||||
|
||||
// Umkreismittelpunkt (baryzentrischer Ansatz)
|
||||
const a2 = dist2(P2, P3), b2 = dist2(P1, P3), c2 = dist2(P1, P2);
|
||||
const w1 = a2*(b2+c2-a2), w2 = b2*(a2+c2-b2), w3 = c2*(a2+b2-c2);
|
||||
const wSum = w1 + w2 + w3;
|
||||
if (Math.abs(wSum) < 1e-6) {
|
||||
vlog(`Y-Achse: Marker ${id} – Umkreis undefiniert`, 'warn');
|
||||
continue;
|
||||
}
|
||||
const C = [
|
||||
(w1*P1[0] + w2*P2[0] + w3*P3[0]) / wSum,
|
||||
(w1*P1[1] + w2*P2[1] + w3*P3[1]) / wSum,
|
||||
(w1*P1[2] + w2*P2[2] + w3*P3[2]) / wSum,
|
||||
];
|
||||
circumcenters.push({ id, C });
|
||||
|
||||
// Kreismittelpunkt (rose)
|
||||
gYAxis.add(makeSphere(r2vArr(C), 0.007, 0xfb7185));
|
||||
// Bogen-Linie B→C (cyan)
|
||||
gYAxis.add(makeLine(r2vArr(P2), r2vArr(P3), 0x22d3ee, 0.6));
|
||||
}
|
||||
|
||||
if (circumcenters.length === 0) {
|
||||
vlog('Y-Achse: Keine gemeinsamen fremd-Marker in Pos A+B+C gefunden', 'warn');
|
||||
window.parent.postMessage({ type: 'yaxis-measurement', axisDir: null }, '*');
|
||||
return;
|
||||
}
|
||||
|
||||
// Achsenrichtung: Mittlere Normale (Vorzeichen angleichen)
|
||||
const ref = normals[0];
|
||||
const aligned = normals.map(n => {
|
||||
const dot = n[0]*ref[0] + n[1]*ref[1] + n[2]*ref[2];
|
||||
return dot >= 0 ? n : n.map(c => -c);
|
||||
});
|
||||
const meanN = [0, 1, 2].map(i => aligned.reduce((s, n) => s + n[i], 0) / aligned.length);
|
||||
const meanNLen = Math.sqrt(meanN[0]**2 + meanN[1]**2 + meanN[2]**2);
|
||||
const axisDir = meanN.map(c => c / meanNLen); // Roboter-Koordinaten
|
||||
|
||||
// Referenzpunkt: Schwerpunkt der Umkreismittelpunkte
|
||||
const axisPoint = [0, 1, 2].map(i =>
|
||||
circumcenters.reduce((s, c) => s + c.C[i], 0) / circumcenters.length
|
||||
);
|
||||
|
||||
// Achse als Linie ±500 mm visualisieren (magenta)
|
||||
const L = 500;
|
||||
const p1mm = axisPoint.map((v, i) => v - L * axisDir[i]);
|
||||
const p2mm = axisPoint.map((v, i) => v + L * axisDir[i]);
|
||||
gYAxis.add(makeLine(r2vArr(p1mm), r2vArr(p2mm), 0xe879f9, 0.9));
|
||||
gYAxis.add(makeSphere(r2vArr(axisPoint), 0.011, 0xe879f9));
|
||||
|
||||
// Abweichung von der idealen Y-Achse [0,1,0] in Roboter-Koordinaten
|
||||
const [ax, ay, az] = axisDir;
|
||||
const tiltXY = Math.atan2(ax, ay) * 180 / Math.PI; // Kippung in XY-Ebene
|
||||
const tiltYZ = Math.atan2(az, ay) * 180 / Math.PI; // Kippung in YZ-Ebene
|
||||
const fmt = v => (v >= 0 ? '+' : '') + v.toFixed(3) + '°';
|
||||
const good = Math.abs(tiltXY) < 0.5 && Math.abs(tiltYZ) < 0.5;
|
||||
|
||||
vlog(`Y-Achse (${circumcenters.length} Marker): dir=[${axisDir.map(v => v.toFixed(4)).join(', ')}]`);
|
||||
vlog(` Referenzpunkt: [${axisPoint.map(v => v.toFixed(1)).join(', ')}] mm`);
|
||||
vlog(` Abw. von Y-Achse: XY ${fmt(tiltXY)} YZ ${fmt(tiltYZ)}`, good ? 'ok' : 'warn');
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'yaxis-measurement',
|
||||
axisDir,
|
||||
axisPoint,
|
||||
tiltXY,
|
||||
tiltYZ,
|
||||
numMarkers: circumcenters.length,
|
||||
}, '*');
|
||||
}
|
||||
|
||||
// ── Daten laden ───────────────────────────────────────────────────────────────
|
||||
@@ -778,11 +913,44 @@ async function loadCompareData() {
|
||||
}
|
||||
vlog(`Vergleich: ${markers.length} gesamt fremd=${_compareFremdMarkers.length} boardIDs=${boardIds.size}` +
|
||||
(_compareFremdMarkers.length ? ` (${_compareFremdMarkers.map(m => m.marker_id).join(' ')})` : ''));
|
||||
} catch (err) { vlog(`Vergleich Fehler: ${err}`, 'err'); }
|
||||
buildCompareLines(); // Linien + Transparenz aktualisieren
|
||||
} catch (err) { vlog(`Pos B Fehler: ${err}`, 'err'); }
|
||||
buildCompareLines(); // Linien + Transparenz + Y-Achse aktualisieren
|
||||
}
|
||||
|
||||
/** Run-Listen laden und beide Dropdowns befüllen. */
|
||||
/**
|
||||
* Pos C laden.
|
||||
* Zeigt nur fremd-triangulierte Marker als cyan Kugeln.
|
||||
* Danach wird Y-Achse neu berechnet (computeAndShowYAxis).
|
||||
*/
|
||||
async function loadPositionC() {
|
||||
clearGroup(gPositionC);
|
||||
_positionCFremdMarkers = [];
|
||||
const selRun = document.getElementById('sel-run-c')?.value ?? '';
|
||||
if (!selRun) {
|
||||
vlog('Pos C: nicht gewählt');
|
||||
computeAndShowYAxis(); // räumt gYAxis auf
|
||||
return;
|
||||
}
|
||||
vlog(`Pos C: lade ${selRun} …`);
|
||||
try {
|
||||
const r = await fetch(`/api/board/latest?run=${encodeURIComponent(selRun)}`);
|
||||
if (!r.ok) { vlog(`Pos C: HTTP ${r.status}`, 'err'); computeAndShowYAxis(); return; }
|
||||
const data = await r.json();
|
||||
const markers = data.measuredMarkers?.markers ?? [];
|
||||
const boardIds = new Set((data.robot?.links?.Board?.markers ?? []).map(m => m.id));
|
||||
for (const m of markers) {
|
||||
if (!boardIds.has(m.marker_id)) {
|
||||
_positionCFremdMarkers.push(m);
|
||||
gPositionC.add(makeSphere(r2vArr(m.position_mm), 0.006, 0x22d3ee)); // cyan
|
||||
}
|
||||
}
|
||||
vlog(`Pos C: ${markers.length} gesamt fremd=${_positionCFremdMarkers.length}` +
|
||||
(_positionCFremdMarkers.length ? ` (${_positionCFremdMarkers.map(m => m.marker_id).join(' ')})` : ''));
|
||||
} catch (err) { vlog(`Pos C Fehler: ${err}`, 'err'); }
|
||||
computeAndShowYAxis();
|
||||
}
|
||||
|
||||
/** Run-Listen laden und alle Dropdowns befüllen. */
|
||||
async function initRunSelectors() {
|
||||
try {
|
||||
const [r5, r10] = await Promise.all([
|
||||
@@ -800,28 +968,50 @@ async function initRunSelectors() {
|
||||
selP.innerHTML = '<option value="">⟳ aktuellster</option>' +
|
||||
runs5.map(r => `<option value="${r}"${r === cur ? ' selected' : ''}>${r}</option>`).join('');
|
||||
}
|
||||
// ── Default-Verhalten per URL-Param steuern ──────────────────────────────
|
||||
// ?defaults=a → nur Pos A bekommt Default (Board-Tab, Y-Achse-Tab)
|
||||
// ?defaults=ab → Pos A + Pos B (X-Achsen-Tab)
|
||||
// ?defaults=abc → alle drei (Arm-Tab)
|
||||
// kein Param → wie "ab" (Rückwärtskompatibilität)
|
||||
// ?compare=none → wie "a" (Rückwärtskompatibilität)
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const rawDef = params.get('defaults')
|
||||
?? (params.get('compare') === 'none' ? 'a' : 'ab');
|
||||
const defB = rawDef.includes('b');
|
||||
const defC = rawDef.includes('c');
|
||||
|
||||
if (selC) {
|
||||
// URL-Param ?compare=none → kein Default-Vergleich (Board-Tab)
|
||||
// Ohne den Param → zweiten neuesten Run vorwählen (X-Achsen-Tab)
|
||||
const noCompare = new URLSearchParams(window.location.search).get('compare') === 'none';
|
||||
const prevCompare = selC.value;
|
||||
selC.innerHTML = '<option value="">– keiner –</option>' +
|
||||
runs10.map(r => `<option value="${r}">${r}</option>`).join('');
|
||||
if (prevCompare) {
|
||||
selC.value = prevCompare; // bisher gewählten behalten
|
||||
} else if (!noCompare && runs10.length >= 2) {
|
||||
selC.value = runs10[1]; // zweiter neuester als Default (X-Achse)
|
||||
selC.value = prevCompare; // bisher gewählten behalten
|
||||
} else if (defB && runs10.length >= 2) {
|
||||
selC.value = runs10[1]; // zweiter neuester als Default
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pos C ──
|
||||
const selRunC = document.getElementById('sel-run-c');
|
||||
if (selRunC) {
|
||||
const prevC = selRunC.value;
|
||||
selRunC.innerHTML = '<option value="">– keiner –</option>' +
|
||||
runs10.map(r => `<option value="${r}">${r}</option>`).join('');
|
||||
if (prevC) {
|
||||
selRunC.value = prevC; // bisher gewählten behalten
|
||||
} else if (defC && runs10.length >= 3) {
|
||||
selRunC.value = runs10[2]; // dritter neuester als Default
|
||||
}
|
||||
// Andernfalls bleibt "– keiner –" aktiv (Board-Tab mit ?compare=none)
|
||||
}
|
||||
} catch { /* offline oder noch keine Runs */ }
|
||||
}
|
||||
|
||||
/** Vollständige Initialisierung: Selektoren → Basis → Vergleich (sequenziell!) */
|
||||
/** Vollständige Initialisierung: Selektoren → Pos A → Pos B → Pos C (sequenziell!) */
|
||||
async function initAll() {
|
||||
await initRunSelectors();
|
||||
await loadData(); // setzt _primaryFremdMarkers
|
||||
await loadCompareData(); // setzt _compareFremdMarkers + baut Linien
|
||||
await loadData(); // setzt _primaryFremdMarkers
|
||||
await loadCompareData(); // setzt _compareFremdMarkers + baut Linien + Y-Achse (no-op)
|
||||
await loadPositionC(); // setzt _positionCFremdMarkers + berechnet Y-Achse
|
||||
}
|
||||
|
||||
initAll();
|
||||
@@ -829,8 +1019,12 @@ document.getElementById('btnReload').addEventListener('click', initAll);
|
||||
document.getElementById('sel-run-primary')?.addEventListener('change', async () => {
|
||||
await loadData();
|
||||
await loadCompareData();
|
||||
await loadPositionC();
|
||||
});
|
||||
document.getElementById('sel-run-compare')?.addEventListener('change', loadCompareData);
|
||||
document.getElementById('sel-run-compare')?.addEventListener('change', async () => {
|
||||
await loadCompareData(); // baut Linien + ruft computeAndShowYAxis auf
|
||||
});
|
||||
document.getElementById('sel-run-c')?.addEventListener('change', loadPositionC);
|
||||
window.addEventListener('message', async (e) => {
|
||||
if (e.data?.type === 'reload') await initAll();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user