Compare commits

...

2 Commits

Author SHA1 Message Date
chk
fa91a36da9 x-axis justierung: lines 2 2026-06-10 18:49:26 +02:00
chk
145c14842a x-axis justierung: lines 2026-06-10 18:40:57 +02:00

View File

@@ -121,6 +121,21 @@
table.dtbl td:nth-child(2) { text-align: left; }
table.dtbl tr:hover td { background: #1a1f2b; }
.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-lo { color: #dde3ec; } /* hell: nur 2D */
.cell-unk { color: #3b82f6; } /* blau: fremd */
@@ -171,6 +186,7 @@
</div>
<div id="table-wrap"></div>
<div id="viewer-log"></div>
<script type="module">
import * as THREE from 'three';
@@ -214,7 +230,24 @@ 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)
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) {
while (g.children.length) {
@@ -573,6 +606,68 @@ function buildTable(data) {
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 ───────────────────────────────────────────────────────────────
/** Haupt-Run laden (Basis-Dropdown). Ohne Selektion → neuester Run. */
@@ -595,6 +690,13 @@ async function loadData() {
}
buildScene(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}` : '';
statusEl.textContent = `Run: ${data.runDir}${robotLabel}${new Date().toLocaleTimeString('de-CH')}`;
} 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() {
clearGroup(gCompare);
_compareFremdMarkers = [];
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 {
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 markers = data.measuredMarkers?.markers ?? [];
if (!markers.length) return;
// Board-Marker-IDs aus Robot.json (für diesen Run)
// Board-Marker-IDs aus Robot.json dieses Runs
const boardIds = new Set((data.robot?.links?.Board?.markers ?? []).map(m => m.id));
for (const m of markers) {
if (!boardIds.has(m.marker_id)) {
// Nicht zugeordnet → orange Kugel (Vergleich)
gCompare.add(makeSphere(r2vArr(m.position_mm), 0.006, 0xf97316));
_compareFremdMarkers.push(m); // für Linien
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. */
@@ -643,22 +753,35 @@ async function initRunSelectors() {
runs5.map(r => `<option value="${r}"${r === cur ? ' selected' : ''}>${r}</option>`).join('');
}
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>' +
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 */ }
}
initRunSelectors().then(() => loadData());
document.getElementById('btnReload').addEventListener('click', () => {
initRunSelectors().then(() => { loadData(); loadCompareData(); });
});
document.getElementById('sel-run-primary')?.addEventListener('change', loadData);
document.getElementById('sel-run-compare')?.addEventListener('change', loadCompareData);
window.addEventListener('message', (e) => {
if (e.data?.type === 'reload') {
initRunSelectors().then(() => { loadData(); loadCompareData(); });
/** Vollständige Initialisierung: Selektoren → Basis → Vergleich (sequenziell!) */
async function initAll() {
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-compare')?.addEventListener('change', loadCompareData);
window.addEventListener('message', async (e) => {
if (e.data?.type === 'reload') await initAll();
});
// ── Resize & Render-Loop ──────────────────────────────────────────────────────