Liste CSV
This commit is contained in:
@@ -19,8 +19,8 @@
|
||||
font: 11px/1.5 'IBM Plex Mono', 'Cascadia Code', 'Courier New', monospace;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
#topbar {
|
||||
display: flex;
|
||||
@@ -51,12 +51,63 @@
|
||||
font-size: 11px;
|
||||
}
|
||||
.btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
#canvas-wrap { flex: 1; position: relative; overflow: hidden; }
|
||||
#canvas-wrap { flex: 1; min-height: 360px; position: relative; overflow: hidden; }
|
||||
canvas { display: block; width: 100%; height: 100%; }
|
||||
#hint {
|
||||
position: absolute; bottom: 6px; right: 10px;
|
||||
font-size: 9px; color: var(--muted); pointer-events: none;
|
||||
}
|
||||
/* ── Daten-Tabelle ── */
|
||||
#table-wrap {
|
||||
flex-shrink: 0;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
}
|
||||
.tbl-head {
|
||||
padding: 4px 10px 2px;
|
||||
font-size: 9px;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .07em;
|
||||
background: var(--panel);
|
||||
border-bottom: 1px solid var(--border);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
table.dtbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10px;
|
||||
}
|
||||
table.dtbl th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--panel);
|
||||
color: var(--muted);
|
||||
font-weight: normal;
|
||||
text-align: right;
|
||||
padding: 2px 7px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dtbl th:first-child,
|
||||
table.dtbl th:nth-child(2) { text-align: left; }
|
||||
table.dtbl td {
|
||||
text-align: right;
|
||||
padding: 1px 7px;
|
||||
border-bottom: 1px solid #111418;
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dtbl td:first-child,
|
||||
table.dtbl td:nth-child(2) { text-align: left; }
|
||||
table.dtbl tr:hover td { background: #1a1f2b; }
|
||||
.row-1cam td:first-child::before { content: ''; }
|
||||
.cell-hi { color: #fbbf24; } /* amber: trianguliert */
|
||||
.cell-lo { color: #fde68a; } /* hell: nur 2D */
|
||||
.cell-unk { color: #3b82f6; } /* blau: fremd */
|
||||
.cell-mut { color: var(--muted); }
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
@@ -88,6 +139,8 @@
|
||||
<div id="hint">Orbit · Scroll · Rechte Taste = Pan</div>
|
||||
</div>
|
||||
|
||||
<div id="table-wrap"></div>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
@@ -344,6 +397,142 @@ function buildScene(data) {
|
||||
(camInfo ? ` │ RMS: ${camInfo}` : '');
|
||||
}
|
||||
|
||||
// ── Daten-Tabelle ─────────────────────────────────────────────────────────────
|
||||
function buildTable(data) {
|
||||
const wrap = document.getElementById('table-wrap');
|
||||
if (!wrap) return;
|
||||
|
||||
const { robot, detections, cameraPoses, measuredMarkers } = data;
|
||||
const boardMarkers = robot?.links?.Board?.markers ?? [];
|
||||
|
||||
// Marker → Liste der Kameras, die ihn gesehen haben
|
||||
const camerasByMkr = {};
|
||||
for (const det of (detections ?? [])) {
|
||||
for (const id of (det.detectedMarkerIds ?? [])) {
|
||||
(camerasByMkr[id] ??= []).push(det.cameraId);
|
||||
}
|
||||
}
|
||||
|
||||
// measuredMarkers indiziert nach marker_id
|
||||
const measuredMap = {};
|
||||
for (const m of (measuredMarkers?.markers ?? [])) measuredMap[m.marker_id] = m;
|
||||
|
||||
// Modell-Positionen indiziert
|
||||
const modelMap = {};
|
||||
for (const m of boardMarkers) modelMap[m.id] = m.position; // [x,y,z] mm
|
||||
|
||||
// Alle relevanten IDs (erkannt oder trianguliert)
|
||||
const allIds = new Set([
|
||||
...(detections ?? []).flatMap(d => d.detectedMarkerIds ?? []),
|
||||
...Object.keys(measuredMap).map(Number),
|
||||
]);
|
||||
|
||||
const f1 = v => (v == null ? '–' : Number(v).toFixed(1));
|
||||
const f2 = v => (v == null ? '–' : Number(v).toFixed(2));
|
||||
const f4 = v => (v == null ? '–' : Number(v).toFixed(4));
|
||||
|
||||
// Zeilen zusammenstellen
|
||||
const rows = [...allIds].map(id => {
|
||||
const meas = measuredMap[id];
|
||||
const model = modelMap[id]; // null wenn nicht in Board
|
||||
const link = meas?.link ?? (model ? 'Board' : 'unknown');
|
||||
const cameras = camerasByMkr[id] ?? [];
|
||||
const numCam = meas?.num_cameras ?? cameras.length;
|
||||
|
||||
let x = null, y = null, z = null;
|
||||
let nx = null, ny = null, nz = null;
|
||||
let dist = null, dz = null, edge = null;
|
||||
let state = 'none'; // 'tri', '1cam', 'unk'
|
||||
|
||||
if (meas) {
|
||||
[x, y, z] = meas.position_mm;
|
||||
[nx, ny, nz] = meas.normal;
|
||||
edge = meas.edge_length_mm;
|
||||
state = model ? 'tri' : 'unk';
|
||||
|
||||
if (model) {
|
||||
const [mx, my, mz] = model;
|
||||
const dx = x - mx, dy = y - my, ddz = z - mz;
|
||||
dist = Math.sqrt(dx*dx + dy*dy + ddz*ddz);
|
||||
dz = ddz;
|
||||
}
|
||||
} else if (cameras.length > 0) {
|
||||
state = '1cam';
|
||||
}
|
||||
|
||||
return { id, link, numCam, x, y, z, nx, ny, nz, dist, dz, edge, state };
|
||||
}).sort((a, b) => {
|
||||
// Reihenfolge: Board tri → Board 1cam → unknown → Rest; innerhalb nach ID
|
||||
const rank = r => r.state === 'tri' ? 0 : r.state === '1cam' ? 1 : r.state === 'unk' ? 2 : 3;
|
||||
return rank(a) - rank(b) || a.id - b.id;
|
||||
});
|
||||
|
||||
// ── Marker-Tabelle ──
|
||||
const stateStyle = { tri: 'cell-hi', '1cam': 'cell-lo', unk: 'cell-unk', none: 'cell-mut' };
|
||||
const stateLabel = { tri: '▲', '1cam': '●', unk: '◆', none: '–' };
|
||||
|
||||
let html = `
|
||||
<div class="tbl-head">Erkannte Marker (${rows.length})</div>
|
||||
<table class="dtbl">
|
||||
<thead><tr>
|
||||
<th>ID</th><th>Link</th><th>Kam.</th>
|
||||
<th>x mm</th><th>y mm</th><th>z mm</th>
|
||||
<th>nx</th><th>ny</th><th>nz</th>
|
||||
<th>dist mm</th><th>Δz mm</th><th>Kante mm</th>
|
||||
</tr></thead>
|
||||
<tbody>`;
|
||||
|
||||
for (const r of rows) {
|
||||
const cs = stateStyle[r.state] ?? '';
|
||||
html += `<tr>
|
||||
<td class="${cs}">${stateLabel[r.state]} ${r.id}</td>
|
||||
<td>${r.link}</td>
|
||||
<td>${r.numCam}</td>
|
||||
<td>${f1(r.x)}</td><td>${f1(r.y)}</td><td>${f1(r.z)}</td>
|
||||
<td>${f4(r.nx)}</td><td>${f4(r.ny)}</td><td>${f4(r.nz)}</td>
|
||||
<td>${f2(r.dist)}</td><td>${f2(r.dz)}</td><td>${f1(r.edge)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += `</tbody></table>`;
|
||||
|
||||
// ── Kamera-Tabelle ──
|
||||
const camRows = measuredMarkers?.cameras?.length
|
||||
? measuredMarkers.cameras.map(c => ({
|
||||
id: c.camera_id,
|
||||
pos: c.position_mm,
|
||||
dir: c.direction,
|
||||
}))
|
||||
: (cameraPoses ?? []).map(cp => ({
|
||||
id: cp.cameraId,
|
||||
pos: cp.position_mm,
|
||||
dir: cp.rotation_matrix?.[2] ?? null,
|
||||
}));
|
||||
|
||||
if (camRows.length > 0) {
|
||||
html += `
|
||||
<div class="tbl-head" style="margin-top:0">Kameras (${camRows.length})</div>
|
||||
<table class="dtbl">
|
||||
<thead><tr>
|
||||
<th>ID</th>
|
||||
<th>x mm</th><th>y mm</th><th>z mm</th>
|
||||
<th>dir_x</th><th>dir_y</th><th>dir_z</th>
|
||||
</tr></thead>
|
||||
<tbody>`;
|
||||
for (const c of camRows) {
|
||||
const [cx, cy, cz] = c.pos ?? [null, null, null];
|
||||
const [dx, dy, dz] = c.dir ?? [null, null, null];
|
||||
html += `<tr>
|
||||
<td style="color:var(--accent)">${c.id}</td>
|
||||
<td>${f1(cx)}</td><td>${f1(cy)}</td><td>${f1(cz)}</td>
|
||||
<td>${f4(dx)}</td><td>${f4(dy)}</td><td>${f4(dz)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
html += `</tbody></table>`;
|
||||
}
|
||||
|
||||
wrap.innerHTML = html;
|
||||
}
|
||||
|
||||
// ── Daten laden ───────────────────────────────────────────────────────────────
|
||||
async function loadData() {
|
||||
const statusEl = document.getElementById('status');
|
||||
@@ -359,6 +548,7 @@ async function loadData() {
|
||||
return;
|
||||
}
|
||||
buildScene(data);
|
||||
buildTable(data);
|
||||
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
|
||||
statusEl.textContent = `Run: ${data.runDir}${robotLabel} • ${new Date().toLocaleTimeString('de-CH')}`;
|
||||
} catch (err) {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<iframe
|
||||
id="board-viewer-frame"
|
||||
src="/boardViewer.html"
|
||||
style="width: 100%; height: 520px; border: 1px solid #334155; border-radius: 6px; background: #0d0f13; display: block;"
|
||||
style="width: 100%; height: 740px; border: 1px solid #334155; border-radius: 6px; background: #0d0f13; display: block;"
|
||||
title="Board-Viewer"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user