From 1032c53630d1dd13ac7c204d9bdfa9744a13799a Mon Sep 17 00:00:00 2001 From: chk <79915315+ChKendel@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:41:55 +0200 Subject: [PATCH] Viewer --- public/boardViewer.html | 344 ++++++++++++++++++++++++++++++++++ public/calibration.js | 9 +- public/calibration_board.html | 13 ++ server/server.js | 67 +++++++ 4 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 public/boardViewer.html diff --git a/public/boardViewer.html b/public/boardViewer.html new file mode 100644 index 0000000..a138a40 --- /dev/null +++ b/public/boardViewer.html @@ -0,0 +1,344 @@ + + + + +Board Viewer + + + + + + +
+
+ Erkannt + Nicht erkannt + Kamera +
+ + Laden … + +
+ +
+ +
Orbit · Scroll · Rechte Taste = Pan
+
+ + + + diff --git a/public/calibration.js b/public/calibration.js index 9297412..a852a62 100644 --- a/public/calibration.js +++ b/public/calibration.js @@ -250,7 +250,14 @@ function initBoard() { await readSseStream(response, logB, (evt) => { if (evt.exitCode === 0) { logB('✅ Board-Run abgeschlossen.'); - if (evt.runDir) document.getElementById('board-last-run').textContent = evt.runDir; + if (evt.runDir) { + document.getElementById('board-last-run').textContent = evt.runDir; + // Board-Viewer im iframe nach dem Run neu laden + const frame = document.getElementById('board-viewer-frame'); + if (frame?.contentWindow) { + frame.contentWindow.postMessage({ type: 'reload' }, '*'); + } + } } else { logB(`❌ Beendet mit Exit-Code ${evt.exitCode}`); } diff --git a/public/calibration_board.html b/public/calibration_board.html index d1d7d88..a72baa9 100644 --- a/public/calibration_board.html +++ b/public/calibration_board.html @@ -25,4 +25,17 @@ +
+

Board-Viewer

+

+ Wird nach jedem Board-Run automatisch aktualisiert. +

+ +
+ diff --git a/server/server.js b/server/server.js index 9215907..1f7396d 100755 --- a/server/server.js +++ b/server/server.js @@ -572,6 +572,73 @@ app.post('/api/board/run', async (req, res) => { } }); +/** Neuestes Board-Run-Verzeichnis (Timestamp-Name) oder null */ +async function findLatestBoardRun() { + try { + await fsPromises.access(boardDataDir); + const entries = await fsPromises.readdir(boardDataDir, { withFileTypes: true }); + const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort().reverse(); + return dirs[0] ?? null; + } catch { + return null; + } +} + +/** + * GET /api/board/latest + * Gibt Daten des letzten Board-Runs zurück: robot.json + Detection-Ergebnisse + Kamera-Posen. + * Wird vom Board-Viewer (boardViewer.html) abgefragt. + */ +app.get('/api/board/latest', async (req, res) => { + try { + const runName = await findLatestBoardRun(); + if (!runName) return res.json({ runDir: null, robot: null, detections: [], cameraPoses: [] }); + + const runDir = path.join(boardDataDir, runName); + + let robot = null; + try { robot = JSON.parse(await fsPromises.readFile(ROBOT_JSON, 'utf8')); } catch {} + + let files = []; + try { files = await fsPromises.readdir(runDir); } catch {} + + const detections = []; + const cameraPoses = []; + + for (const f of files.sort()) { + if (f.endsWith('_aruco_detection.json')) { + try { + const data = JSON.parse(await fsPromises.readFile(path.join(runDir, f), 'utf8')); + detections.push({ + file: f, + cameraId: data.camera?.camera_id ?? f.replace('_aruco_detection.json', ''), + detectedMarkerIds: (data.detections ?? []).map(d => d.marker_id), + numDetected: data.aruco?.num_detected_markers ?? 0, + numRejected: data.aruco?.num_rejected_candidates ?? 0, + }); + } catch {} + } else if (f.endsWith('_camera_pose.json')) { + try { + const data = JSON.parse(await fsPromises.readFile(path.join(runDir, f), 'utf8')); + const cp = data.camera_pose; + cameraPoses.push({ + file: f, + cameraId: data.camera?.camera_id ?? f.replace('_camera_pose.json', ''), + position_mm: cp?.camera_in_world?.position_mm ?? null, + rotation_matrix: cp?.world_to_camera?.rotation_matrix ?? null, + usedMarkerIds: data.estimation?.used_marker_ids ?? [], + rms_px: data.estimation?.residual_rms_px ?? null, + }); + } catch {} + } + } + + return res.json({ runDir: runName, robot, detections, cameraPoses }); + } catch (err) { + return res.status(500).json({ error: String(err) }); + } +}); + /** * POST /api/calibration/upload-npz * Liest {camera}_calibration.npz aus der aktuellen Session und