robot.json editieren - board

This commit is contained in:
chk
2026-06-10 17:27:07 +02:00
parent 45513cf714
commit 6b3053dd76
4 changed files with 322 additions and 0 deletions

View File

@@ -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 = '<span style="color:#f87171">⚠ Bitte z-Bereich eingeben.</span>'; return;
}
if (!set && !link) {
result.innerHTML = '<span style="color:#f87171">⚠ Bitte mindestens Set oder Link angeben.</span>'; return;
}
result.innerHTML = '<span style="color:#555b6e">Zuordnung läuft …</span>';
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 = `<span style="color:#f87171">❌ ${data.error ?? `HTTP ${r.status}`}</span>`; return;
}
if (data.numChanged === 0) {
result.innerHTML = '<span style="color:#555b6e">Keine Marker im Z-Bereich gefunden.</span>'; return;
}
const moved = data.changes.filter(c => c.oldLink !== c.newLink).length;
result.innerHTML = `<span style="color:#22c55e">✅ ${data.numChanged} Marker geändert` +
(moved ? `, ${moved} verschoben` : '') + `.</span>` +
` IDs: ${data.changes.map(c => c.markerId).join(', ')}`;
loadBoardTable();
} catch (err) {
result.innerHTML = `<span style="color:#f87171">❌ ${err}</span>`;
}
});
// ── 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 = '<span style="color:#f87171">⚠ Bitte Marker-ID eingeben.</span>'; return;
}
result.innerHTML = '<span style="color:#555b6e">Verarbeite …</span>';
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 = `<span style="color:#f87171">❌ ${data.error ?? `HTTP ${r.status}`}</span>`; return;
}
if (!data.changed) {
result.innerHTML = `<span style="color:#f87171">❌ ${data.error}</span>`; return;
}
const info = data.action === 'set-removed'
? `Set "${data.oldSet}" entfernt (bleibt in Link "${data.link}")`
: `Aus Link "${data.link}" entfernt`;
result.innerHTML = `<span style="color:#22c55e">✅ Marker ${markerId}: ${info}</span>`;
loadBoardTable();
} catch (err) {
result.innerHTML = `<span style="color:#f87171">❌ ${err}</span>`;
}
});
}

View File

@@ -20,6 +20,62 @@
</div>
</div>
<!-- ── Aktionen ─────────────────────────────────────────────────────────── -->
<div class="section full">
<h2>Aktionen</h2>
<!-- Aktion 1: Z-Bereich Zuordnung -->
<div style="margin-top:14px">
<p style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">
Z-Bereich Zuordnung
</p>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:12px;color:var(--text)">
z&nbsp;von
<input id="act-z-min" type="number" step="0.5" placeholder="-30"
style="width:72px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px">
bis
<input id="act-z-max" type="number" step="0.5" placeholder="-24"
style="width:72px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px">
mm &ensp;&ensp; Set
<input id="act-set" type="text" placeholder="A0"
style="width:72px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px">
Link
<input id="act-link" type="text" placeholder="Board"
style="width:90px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px">
<button id="btn-act-assign"
style="background:#1e293b;color:#c8cdd8;border:1px solid #4a9eff;border-radius:3px;padding:4px 14px;cursor:pointer;font:inherit;font-size:12px">
Zuordnen
</button>
</div>
</div>
<!-- Aktion 2: Zuordnung entfernen -->
<div style="margin-top:14px">
<p style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">
Zuordnung entfernen
</p>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:12px;color:var(--text)">
ID
<input id="act-rm-id" type="number" placeholder="z.B. 46"
style="width:90px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 7px;font:inherit;font-size:12px">
aus
<label style="cursor:pointer;display:flex;align-items:center;gap:4px">
<input type="radio" name="act-rm-from" value="set" checked> Set
</label>
<label style="cursor:pointer;display:flex;align-items:center;gap:4px">
<input type="radio" name="act-rm-from" value="link"> Link&nbsp;<span style="color:var(--muted);font-size:10px">(nur falls Set leer)</span>
</label>
<button id="btn-act-remove"
style="background:#1e293b;color:#c8cdd8;border:1px solid #ef4444;border-radius:3px;padding:4px 14px;cursor:pointer;font:inherit;font-size:12px">
Entfernen
</button>
</div>
</div>
<!-- Ergebnis-Zeile -->
<div id="act-result" style="margin-top:10px;font-size:11px;min-height:18px;color:var(--muted)"></div>
</div>
<div class="section full">
<h2>Ausgabe / Log</h2>
<textarea id="log-board" readonly placeholder="(Ausgabe erscheint hier)"></textarea>

144
server/editRobot.js Normal file
View File

@@ -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.` };
}

View File

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