Compare commits
2 Commits
3084324f4a
...
fa91a36da9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa91a36da9 | ||
|
|
145c14842a |
@@ -121,6 +121,21 @@
|
|||||||
table.dtbl td:nth-child(2) { text-align: left; }
|
table.dtbl td:nth-child(2) { text-align: left; }
|
||||||
table.dtbl tr:hover td { background: #1a1f2b; }
|
table.dtbl tr:hover td { background: #1a1f2b; }
|
||||||
.row-1cam td:first-child::before { content: ''; }
|
.row-1cam td:first-child::before { content: ''; }
|
||||||
|
/* ── Viewer-Log ── */
|
||||||
|
#viewer-log {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-height: 64px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
background: #090b0e;
|
||||||
|
padding: 2px 10px 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #4a5568;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
#viewer-log .vl-ok { color: #4ade80; }
|
||||||
|
#viewer-log .vl-warn{ color: #fbbf24; }
|
||||||
|
#viewer-log .vl-err { color: #f87171; }
|
||||||
.cell-hi { color: #fbbf24; } /* amber: trianguliert */
|
.cell-hi { color: #fbbf24; } /* amber: trianguliert */
|
||||||
.cell-lo { color: #dde3ec; } /* hell: nur 2D */
|
.cell-lo { color: #dde3ec; } /* hell: nur 2D */
|
||||||
.cell-unk { color: #3b82f6; } /* blau: fremd */
|
.cell-unk { color: #3b82f6; } /* blau: fremd */
|
||||||
@@ -171,6 +186,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="table-wrap"></div>
|
<div id="table-wrap"></div>
|
||||||
|
<div id="viewer-log"></div>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
@@ -213,8 +229,25 @@ const gPaper = new THREE.Group(); // weißes A0-Papier
|
|||||||
const gMarkers = new THREE.Group(); // Modell-Rechtecke
|
const gMarkers = new THREE.Group(); // Modell-Rechtecke
|
||||||
const gMeasured = new THREE.Group(); // gemessene Positionen (3b)
|
const gMeasured = new THREE.Group(); // gemessene Positionen (3b)
|
||||||
const gCameras = new THREE.Group(); // Kamera-Frusta
|
const gCameras = new THREE.Group(); // Kamera-Frusta
|
||||||
const gCompare = new THREE.Group(); // Vergleichs-Punkte (anderer Timestamp, nur fremd)
|
const gCompare = new THREE.Group(); // Vergleichs-Punkte (anderer Timestamp, nur fremd)
|
||||||
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare);
|
const gCompareLines = new THREE.Group(); // Verbindungslinien Basis↔Vergleich
|
||||||
|
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines);
|
||||||
|
|
||||||
|
// ── Zustand für Vergleichs-Linien ─────────────────────────────────────────────
|
||||||
|
let _primaryFremdMarkers = []; // [{marker_id, position_mm, num_cameras}]
|
||||||
|
let _compareFremdMarkers = []; // [{marker_id, position_mm, num_cameras}]
|
||||||
|
|
||||||
|
// ── Viewer-interner Logger ────────────────────────────────────────────────────
|
||||||
|
function vlog(msg, kind = '') {
|
||||||
|
const el = document.getElementById('viewer-log');
|
||||||
|
if (!el) return;
|
||||||
|
const d = document.createElement('div');
|
||||||
|
d.className = kind ? `vl-${kind}` : '';
|
||||||
|
d.textContent = `${new Date().toLocaleTimeString('de-CH')} ${msg}`;
|
||||||
|
el.appendChild(d);
|
||||||
|
while (el.children.length > 40) el.removeChild(el.firstChild);
|
||||||
|
el.scrollTop = el.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
function clearGroup(g) {
|
function clearGroup(g) {
|
||||||
while (g.children.length) {
|
while (g.children.length) {
|
||||||
@@ -573,6 +606,68 @@ function buildTable(data) {
|
|||||||
wrap.innerHTML = html;
|
wrap.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Vergleichs-Overlay: Transparenz + Linien ─────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt Board-Marker und Papier-Ebene auf geringere Deckkraft, wenn der
|
||||||
|
* Vergleichs-Modus aktiv ist (compareActive=true → 10 % Deckkraft).
|
||||||
|
* Ursprüngliche Deckkraft wird pro Material einmalig gespeichert.
|
||||||
|
*/
|
||||||
|
function setSceneOpacity(compareActive) {
|
||||||
|
for (const obj of [...gPaper.children, ...gMarkers.children]) {
|
||||||
|
const mats = obj.material
|
||||||
|
? (Array.isArray(obj.material) ? obj.material : [obj.material])
|
||||||
|
: [];
|
||||||
|
for (const mat of mats) {
|
||||||
|
if (!mat) continue;
|
||||||
|
if (mat._origOpacity === undefined) mat._origOpacity = mat.opacity ?? 1.0;
|
||||||
|
if (mat._origTransparent === undefined) mat._origTransparent = mat.transparent ?? false;
|
||||||
|
mat.transparent = compareActive || mat._origTransparent;
|
||||||
|
mat.opacity = compareActive ? mat._origOpacity * 0.10 : mat._origOpacity;
|
||||||
|
mat.needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeichnet Verbindungslinien von Basis-fremd-Markern zu gleich-ID-Vergleichs-Markern.
|
||||||
|
* Aktualisiert gleichzeitig die Board-Transparenz.
|
||||||
|
*/
|
||||||
|
function buildCompareLines() {
|
||||||
|
clearGroup(gCompareLines);
|
||||||
|
const compareActive = _compareFremdMarkers.length > 0;
|
||||||
|
setSceneOpacity(compareActive);
|
||||||
|
|
||||||
|
if (!compareActive) {
|
||||||
|
vlog(`Vergleich inaktiv – Board-Marker voll sichtbar`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_primaryFremdMarkers.length) {
|
||||||
|
vlog(`⚠ Basis hat 0 fremd-Marker – keine Linien möglich`, 'warn');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryMap = new Map(_primaryFremdMarkers.map(m => [m.marker_id, m]));
|
||||||
|
const matchedIds = [];
|
||||||
|
const onlyCompare = []; // in compare aber nicht in primary
|
||||||
|
const onlyPrimary = [...primaryMap.keys()]; // werden unten gefiltert
|
||||||
|
|
||||||
|
for (const cm of _compareFremdMarkers) {
|
||||||
|
const pm = primaryMap.get(cm.marker_id);
|
||||||
|
if (!pm) { onlyCompare.push(cm.marker_id); continue; }
|
||||||
|
matchedIds.push(cm.marker_id);
|
||||||
|
// Linie: Basis-Position (blau) → Vergleichs-Position (orange)
|
||||||
|
gCompareLines.add(makeLine(r2vArr(pm.position_mm), r2vArr(cm.position_mm), 0xfb923c, 0.85));
|
||||||
|
}
|
||||||
|
|
||||||
|
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(' ')}`);
|
||||||
|
vlog(parts.join(' | '), matchedIds.length ? 'ok' : 'warn');
|
||||||
|
}
|
||||||
|
|
||||||
// ── Daten laden ───────────────────────────────────────────────────────────────
|
// ── Daten laden ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Haupt-Run laden (Basis-Dropdown). Ohne Selektion → neuester Run. */
|
/** Haupt-Run laden (Basis-Dropdown). Ohne Selektion → neuester Run. */
|
||||||
@@ -595,6 +690,13 @@ async function loadData() {
|
|||||||
}
|
}
|
||||||
buildScene(data);
|
buildScene(data);
|
||||||
buildTable(data);
|
buildTable(data);
|
||||||
|
// Fremd-Marker für Verbindungslinien merken (Marker, die nicht in Board-Link stehen)
|
||||||
|
const bIds = new Set((data.robot?.links?.Board?.markers ?? []).map(m => m.id));
|
||||||
|
_primaryFremdMarkers = (data.measuredMarkers?.markers ?? []).filter(m => !bIds.has(m.marker_id));
|
||||||
|
const measTotal = data.measuredMarkers?.markers?.length ?? 0;
|
||||||
|
vlog(`Basis: run=${data.runDir} gesamt=${measTotal} fremd=${_primaryFremdMarkers.length} boardIDs=${bIds.size}` +
|
||||||
|
(_primaryFremdMarkers.length ? ` (${_primaryFremdMarkers.map(m => m.marker_id).join(' ')})` : ''));
|
||||||
|
buildCompareLines();
|
||||||
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
|
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
|
||||||
statusEl.textContent = `Run: ${data.runDir}${robotLabel} • ${new Date().toLocaleTimeString('de-CH')}`;
|
statusEl.textContent = `Run: ${data.runDir}${robotLabel} • ${new Date().toLocaleTimeString('de-CH')}`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -602,26 +704,34 @@ async function loadData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vergleichs-Run laden (Compare-Dropdown) – zeigt nur fremd-triangulierte Marker als orange Kugeln. */
|
/**
|
||||||
|
* Vergleichs-Run laden (Compare-Dropdown).
|
||||||
|
* Zeigt nur fremd-triangulierte Marker (nicht im Board-Link) als orange Kugeln.
|
||||||
|
* Zieht außerdem Verbindungslinien zu gleich-ID-Markern im Basis-Run.
|
||||||
|
*/
|
||||||
async function loadCompareData() {
|
async function loadCompareData() {
|
||||||
clearGroup(gCompare);
|
clearGroup(gCompare);
|
||||||
|
_compareFremdMarkers = [];
|
||||||
const selRun = document.getElementById('sel-run-compare')?.value ?? '';
|
const selRun = document.getElementById('sel-run-compare')?.value ?? '';
|
||||||
if (!selRun) return;
|
if (!selRun) { vlog('Vergleich: keiner gewählt'); buildCompareLines(); return; }
|
||||||
|
vlog(`Vergleich: lade ${selRun} …`);
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`/api/board/latest?run=${encodeURIComponent(selRun)}`);
|
const r = await fetch(`/api/board/latest?run=${encodeURIComponent(selRun)}`);
|
||||||
if (!r.ok) return;
|
if (!r.ok) { vlog(`Vergleich: HTTP ${r.status}`, 'err'); buildCompareLines(); return; }
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
const markers = data.measuredMarkers?.markers ?? [];
|
const markers = data.measuredMarkers?.markers ?? [];
|
||||||
if (!markers.length) return;
|
// Board-Marker-IDs aus Robot.json dieses Runs
|
||||||
// Board-Marker-IDs aus Robot.json (für diesen Run)
|
|
||||||
const boardIds = new Set((data.robot?.links?.Board?.markers ?? []).map(m => m.id));
|
const boardIds = new Set((data.robot?.links?.Board?.markers ?? []).map(m => m.id));
|
||||||
for (const m of markers) {
|
for (const m of markers) {
|
||||||
if (!boardIds.has(m.marker_id)) {
|
if (!boardIds.has(m.marker_id)) {
|
||||||
// Nicht zugeordnet → orange Kugel (Vergleich)
|
_compareFremdMarkers.push(m); // für Linien
|
||||||
gCompare.add(makeSphere(r2vArr(m.position_mm), 0.006, 0xf97316));
|
gCompare.add(makeSphere(r2vArr(m.position_mm), 0.006, 0xf97316)); // orange Kugel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch { /* kein 3b-Output für diesen Run */ }
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run-Listen laden und beide Dropdowns befüllen. */
|
/** Run-Listen laden und beide Dropdowns befüllen. */
|
||||||
@@ -643,22 +753,35 @@ async function initRunSelectors() {
|
|||||||
runs5.map(r => `<option value="${r}"${r === cur ? ' selected' : ''}>${r}</option>`).join('');
|
runs5.map(r => `<option value="${r}"${r === cur ? ' selected' : ''}>${r}</option>`).join('');
|
||||||
}
|
}
|
||||||
if (selC) {
|
if (selC) {
|
||||||
|
// Default: zweiten neuesten Run vorwählen (falls vorhanden und noch kein Wert gesetzt)
|
||||||
|
const prevCompare = selC.value;
|
||||||
selC.innerHTML = '<option value="">– keiner –</option>' +
|
selC.innerHTML = '<option value="">– keiner –</option>' +
|
||||||
runs10.map(r => `<option value="${r}">${r}</option>`).join('');
|
runs10.map(r => `<option value="${r}">${r}</option>`).join('');
|
||||||
|
if (prevCompare) {
|
||||||
|
selC.value = prevCompare; // bisher gewählten behalten
|
||||||
|
} else if (runs10.length >= 2) {
|
||||||
|
selC.value = runs10[1]; // zweiter neuester als Default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch { /* offline oder noch keine Runs */ }
|
} catch { /* offline oder noch keine Runs */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
initRunSelectors().then(() => loadData());
|
/** Vollständige Initialisierung: Selektoren → Basis → Vergleich (sequenziell!) */
|
||||||
document.getElementById('btnReload').addEventListener('click', () => {
|
async function initAll() {
|
||||||
initRunSelectors().then(() => { loadData(); loadCompareData(); });
|
await initRunSelectors();
|
||||||
|
await loadData(); // setzt _primaryFremdMarkers
|
||||||
|
await loadCompareData(); // setzt _compareFremdMarkers + baut Linien
|
||||||
|
}
|
||||||
|
|
||||||
|
initAll();
|
||||||
|
document.getElementById('btnReload').addEventListener('click', initAll);
|
||||||
|
document.getElementById('sel-run-primary')?.addEventListener('change', async () => {
|
||||||
|
await loadData();
|
||||||
|
await loadCompareData();
|
||||||
});
|
});
|
||||||
document.getElementById('sel-run-primary')?.addEventListener('change', loadData);
|
|
||||||
document.getElementById('sel-run-compare')?.addEventListener('change', loadCompareData);
|
document.getElementById('sel-run-compare')?.addEventListener('change', loadCompareData);
|
||||||
window.addEventListener('message', (e) => {
|
window.addEventListener('message', async (e) => {
|
||||||
if (e.data?.type === 'reload') {
|
if (e.data?.type === 'reload') await initAll();
|
||||||
initRunSelectors().then(() => { loadData(); loadCompareData(); });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Resize & Render-Loop ──────────────────────────────────────────────────────
|
// ── Resize & Render-Loop ──────────────────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user