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
+
+
+
+
+
+
+
+
+
+
+
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