From 6b3053dd767f5e09d2a8eb6dbf8f9dbae1e53711 Mon Sep 17 00:00:00 2001 From: chk <79915315+ChKendel@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:27:07 +0200 Subject: [PATCH] robot.json editieren - board --- public/calibration.js | 73 +++++++++++++++++ public/calibration_board.html | 56 +++++++++++++ server/editRobot.js | 144 ++++++++++++++++++++++++++++++++++ server/server.js | 49 ++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 server/editRobot.js diff --git a/public/calibration.js b/public/calibration.js index c6cbde3..57770a9 100644 --- a/public/calibration.js +++ b/public/calibration.js @@ -403,4 +403,77 @@ function initBoard() { btn.disabled = false; } }); + + // ── Aktion 1: Z-Bereich Zuordnung ─────────────────────────────────────────── + document.getElementById('btn-act-assign').addEventListener('click', async () => { + const zMin = document.getElementById('act-z-min').value; + const zMax = document.getElementById('act-z-max').value; + const set = document.getElementById('act-set').value.trim(); + const link = document.getElementById('act-link').value.trim(); + const result = document.getElementById('act-result'); + + if (zMin === '' || zMax === '') { + result.innerHTML = '⚠ Bitte z-Bereich eingeben.'; return; + } + if (!set && !link) { + result.innerHTML = '⚠ Bitte mindestens Set oder Link angeben.'; return; + } + + result.innerHTML = 'Zuordnung läuft …'; + try { + const r = await fetch('/api/robot/assign-by-z', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ zMin: parseFloat(zMin), zMax: parseFloat(zMax), set, link }), + }); + const data = await r.json(); + if (!r.ok || data.error) { + result.innerHTML = `❌ ${data.error ?? `HTTP ${r.status}`}`; return; + } + if (data.numChanged === 0) { + result.innerHTML = 'Keine Marker im Z-Bereich gefunden.'; return; + } + const moved = data.changes.filter(c => c.oldLink !== c.newLink).length; + result.innerHTML = `✅ ${data.numChanged} Marker geändert` + + (moved ? `, ${moved} verschoben` : '') + `.` + + ` IDs: ${data.changes.map(c => c.markerId).join(', ')}`; + loadBoardTable(); + } catch (err) { + result.innerHTML = `❌ ${err}`; + } + }); + + // ── Aktion 2: Zuordnung entfernen ─────────────────────────────────────────── + document.getElementById('btn-act-remove').addEventListener('click', async () => { + const markerId = document.getElementById('act-rm-id').value.trim(); + const removeFrom = document.querySelector('input[name="act-rm-from"]:checked')?.value; + const result = document.getElementById('act-result'); + + if (!markerId) { + result.innerHTML = '⚠ Bitte Marker-ID eingeben.'; return; + } + + result.innerHTML = 'Verarbeite …'; + try { + const r = await fetch('/api/robot/remove-marker', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ markerId: parseInt(markerId, 10), removeFrom }), + }); + const data = await r.json(); + if (!r.ok) { + result.innerHTML = `❌ ${data.error ?? `HTTP ${r.status}`}`; return; + } + if (!data.changed) { + result.innerHTML = `❌ ${data.error}`; return; + } + const info = data.action === 'set-removed' + ? `Set "${data.oldSet}" entfernt (bleibt in Link "${data.link}")` + : `Aus Link "${data.link}" entfernt`; + result.innerHTML = `✅ Marker ${markerId}: ${info}`; + loadBoardTable(); + } catch (err) { + result.innerHTML = `❌ ${err}`; + } + }); } diff --git a/public/calibration_board.html b/public/calibration_board.html index 9b41060..cdc9ceb 100644 --- a/public/calibration_board.html +++ b/public/calibration_board.html @@ -20,6 +20,62 @@ + +
+

Aktionen

+ + +
+

+ Z-Bereich Zuordnung +

+
+ z von + + bis + + mm  →  Set + + Link + + +
+
+ + +
+

+ Zuordnung entfernen +

+
+ ID + + aus + + + +
+
+ + +
+
+

Ausgabe / Log

diff --git a/server/editRobot.js b/server/editRobot.js new file mode 100644 index 0000000..960bf89 --- /dev/null +++ b/server/editRobot.js @@ -0,0 +1,144 @@ +/** + * editRobot.js + * Hilfsfunktionen zum Bearbeiten von robot_xxx.json. + * Alle Schreibvorgänge machen ein Backup-Kommentar in der Datei (nein, aber + * atomisches Write per Temp-Datei ist hier nicht nötig – die Datei wird direkt + * überschrieben; bei Bedarf Backup-Strategie ergänzen). + */ +import fsPromises from 'fs/promises'; + +// ── I/O ─────────────────────────────────────────────────────────────────────── + +async function readRobot(robotPath) { + return JSON.parse(await fsPromises.readFile(robotPath, 'utf8')); +} + +async function writeRobot(robotPath, data) { + await fsPromises.writeFile(robotPath, JSON.stringify(data, null, 2), 'utf8'); +} + +// ── Aktion 1: Marker nach Z-Bereich zuordnen ───────────────────────────────── + +/** + * Weist allen Markern, deren z-Position (mm) zwischen zMin und zMax liegt, + * das angegebene Set und/oder den angegebenen Link zu. + * + * - set (optional): Setzt den set-Wert des Markers. + * - link (optional): Verschiebt den Marker in diesen Link (wird ggf. angelegt). + * + * Gibt { numChanged, changes[] } zurück. + * changes[]: { markerId, oldLink, newLink, oldSet, newSet } + */ +export async function assignByZRange(robotPath, { zMin, zMax, set, link }) { + const robot = await readRobot(robotPath); + const links = robot.links ?? {}; + const changes = []; + + // Snapshot aller Marker (mit aktuellem Link-Name) – vor der Mutation + const snapshot = []; + for (const [linkName, linkData] of Object.entries(links)) { + for (const marker of (linkData.markers ?? [])) { + if (Array.isArray(marker.position)) { + snapshot.push({ id: marker.id, currentLink: linkName }); + } + } + } + + // Nur Marker im Z-Fenster bearbeiten + const zLo = Number(zMin); + const zHi = Number(zMax); + + for (const { id, currentLink } of snapshot) { + const srcLinkData = links[currentLink]; + const idx = (srcLinkData?.markers ?? []).findIndex(m => Number(m.id) === id); + if (idx === -1) continue; + + const marker = srcLinkData.markers[idx]; + const z = Number(marker.position[2]); + if (z < zLo || z > zHi) continue; + + const change = { + markerId: marker.id, + oldLink: currentLink, + oldSet: marker.set ?? '', + newLink: currentLink, + newSet: marker.set ?? '', + }; + + // Set setzen + if (set !== undefined && set !== '') { + marker.set = set; + change.newSet = set; + } + + // Link wechseln + if (link && link !== currentLink) { + // Aus altem Link entfernen + srcLinkData.markers.splice(idx, 1); + + // Ziel-Link anlegen falls noch nicht vorhanden + if (!links[link]) links[link] = { markers: [] }; + if (!links[link].markers) links[link].markers = []; + links[link].markers.push(marker); + change.newLink = link; + } + + changes.push(change); + } + + if (changes.length > 0) { + robot.links = links; + await writeRobot(robotPath, robot); + } + + return { numChanged: changes.length, changes }; +} + +// ── Aktion 2: Set oder Link-Zuordnung entfernen ─────────────────────────────── + +/** + * Entfernt die Set- oder Link-Zuordnung eines Markers. + * + * removeFrom: 'set' → löscht nur den set-Wert (Marker bleibt im Link) + * removeFrom: 'link' → entfernt Marker komplett aus dem Link + * (nur wenn set bereits leer/nicht gesetzt) + * + * Gibt { changed, markerId, action, link, [oldSet], [error] } zurück. + */ +export async function removeMarkerAssignment(robotPath, { markerId, removeFrom }) { + const id = Number(markerId); + const robot = await readRobot(robotPath); + const links = robot.links ?? {}; + + for (const [linkName, linkData] of Object.entries(links)) { + const markers = linkData.markers ?? []; + const idx = markers.findIndex(m => Number(m.id) === id); + if (idx === -1) continue; + + const marker = markers[idx]; + + if (removeFrom === 'set') { + const oldSet = marker.set ?? ''; + delete marker.set; + await writeRobot(robotPath, robot); + return { changed: true, markerId: id, action: 'set-removed', link: linkName, oldSet }; + } + + if (removeFrom === 'link') { + if (marker.set) { + return { + changed: false, + markerId: id, + error: `Marker ${id} hat noch Set "${marker.set}". Bitte zuerst Set entfernen.`, + }; + } + markers.splice(idx, 1); + await writeRobot(robotPath, robot); + return { changed: true, markerId: id, action: 'link-removed', link: linkName }; + } + + return { changed: false, error: `Unbekannte Aktion: "${removeFrom}"` }; + } + + return { changed: false, error: `Marker-ID ${id} nicht gefunden.` }; +} diff --git a/server/server.js b/server/server.js index ec797f1..ddc4931 100755 --- a/server/server.js +++ b/server/server.js @@ -8,6 +8,7 @@ import { fileURLToPath } from 'url'; import process from 'process'; import { spawn } from 'child_process'; import { WebcamClient } from './webcamClient.js'; +import { assignByZRange, removeMarkerAssignment } from './editRobot.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -689,6 +690,54 @@ app.get('/api/board/latest', async (req, res) => { } }); +// ── Robot-JSON bearbeiten ───────────────────────────────────────────────────── + +/** + * POST /api/robot/assign-by-z + * Weist allen Markern in [zMin, zMax] mm das angegebene Set und/oder Link zu. + * Body: { zMin, zMax, set?, link? } + */ +app.post('/api/robot/assign-by-z', async (req, res) => { + try { + const { zMin, zMax, set, link } = req.body ?? {}; + if (zMin == null || zMax == null) { + return res.status(400).json({ error: 'zMin und zMax sind erforderlich' }); + } + if (!set && !link) { + return res.status(400).json({ error: 'Mindestens set oder link muss angegeben werden' }); + } + const result = await assignByZRange(ROBOT_JSON, { zMin, zMax, set, link }); + console.log(`robot/assign-by-z z=[${zMin}..${zMax}] set="${set}" link="${link}" → ${result.numChanged} geändert`); + return res.json(result); + } catch (err) { + console.error('robot/assign-by-z error:', err); + return res.status(500).json({ error: String(err) }); + } +}); + +/** + * POST /api/robot/remove-marker + * Entfernt Set oder Link-Zuordnung eines Markers. + * Body: { markerId, removeFrom } removeFrom: 'set' | 'link' + */ +app.post('/api/robot/remove-marker', async (req, res) => { + try { + const { markerId, removeFrom } = req.body ?? {}; + if (markerId == null) { + return res.status(400).json({ error: 'markerId ist erforderlich' }); + } + if (!['set', 'link'].includes(removeFrom)) { + return res.status(400).json({ error: 'removeFrom muss "set" oder "link" sein' }); + } + const result = await removeMarkerAssignment(ROBOT_JSON, { markerId, removeFrom }); + console.log(`robot/remove-marker id=${markerId} from=${removeFrom} → changed=${result.changed}`); + return res.json(result); + } catch (err) { + console.error('robot/remove-marker error:', err); + return res.status(500).json({ error: String(err) }); + } +}); + /** * POST /api/calibration/upload-npz * Liest {camera}_calibration.npz aus der aktuellen Session und