diff --git a/public/calibration.js b/public/calibration.js
index 57770a9..785cfc6 100644
--- a/public/calibration.js
+++ b/public/calibration.js
@@ -431,11 +431,16 @@ function initBoard() {
result.innerHTML = `❌ ${data.error ?? `HTTP ${r.status}`}`; return;
}
if (data.numChanged === 0) {
- result.innerHTML = 'Keine Marker im Z-Bereich gefunden.'; return;
+ result.innerHTML = 'Keine Marker im Z-Bereich gefunden (weder in robot.json noch im letzten 3b-Run).'; return;
}
- const moved = data.changes.filter(c => c.oldLink !== c.newLink).length;
- result.innerHTML = `✅ ${data.numChanged} Marker geändert` +
- (moved ? `, ${moved} verschoben` : '') + `.` +
+ const added = data.changes.filter(c => c.action === 'added').length;
+ const moved = data.changes.filter(c => c.action === 'updated' && c.oldLink !== c.newLink).length;
+ const updated = data.changes.filter(c => c.action === 'updated').length - moved;
+ const parts = [];
+ if (added) parts.push(`${added} neu hinzugefügt`);
+ if (moved) parts.push(`${moved} verschoben`);
+ if (updated) parts.push(`${updated} aktualisiert`);
+ result.innerHTML = `✅ ${data.numChanged} Marker: ${parts.join(', ')}.` +
` IDs: ${data.changes.map(c => c.markerId).join(', ')}`;
loadBoardTable();
} catch (err) {
diff --git a/server/editRobot.js b/server/editRobot.js
index 960bf89..4fe5588 100644
--- a/server/editRobot.js
+++ b/server/editRobot.js
@@ -23,31 +23,32 @@ async function writeRobot(robotPath, data) {
* 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).
+ * - set (optional): Setzt den set-Wert des Markers.
+ * - link (optional): Verschiebt den Marker in diesen Link (wird ggf. angelegt).
+ * - extraMarkers (optional): Triangulierte Marker aus aruco_marker_poses.json –
+ * werden als neue Einträge in robot.json hinzugefügt,
+ * wenn sie noch nicht vorhanden sind.
*
* Gibt { numChanged, changes[] } zurück.
- * changes[]: { markerId, oldLink, newLink, oldSet, newSet }
+ * changes[]: { markerId, action, oldLink, newLink, oldSet, newSet }
+ * action: 'updated' | 'added'
*/
-export async function assignByZRange(robotPath, { zMin, zMax, set, link }) {
+export async function assignByZRange(robotPath, { zMin, zMax, set, link, extraMarkers = [] }) {
const robot = await readRobot(robotPath);
const links = robot.links ?? {};
const changes = [];
- // Snapshot aller Marker (mit aktuellem Link-Name) – vor der Mutation
+ const zLo = Number(zMin);
+ const zHi = Number(zMax);
+
+ // ── Teil 1: Bestehende robot.json-Marker aktualisieren / verschieben ──────────
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 });
- }
+ 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);
@@ -58,6 +59,7 @@ export async function assignByZRange(robotPath, { zMin, zMax, set, link }) {
if (z < zLo || z > zHi) continue;
const change = {
+ action: 'updated',
markerId: marker.id,
oldLink: currentLink,
oldSet: marker.set ?? '',
@@ -65,20 +67,12 @@ export async function assignByZRange(robotPath, { zMin, zMax, set, link }) {
newSet: marker.set ?? '',
};
- // Set setzen
- if (set !== undefined && set !== '') {
- marker.set = set;
- change.newSet = set;
- }
+ 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 = [];
+ if (!links[link]) links[link] = { markers: [] };
+ if (!links[link].markers) links[link].markers = [];
links[link].markers.push(marker);
change.newLink = link;
}
@@ -86,6 +80,45 @@ export async function assignByZRange(robotPath, { zMin, zMax, set, link }) {
changes.push(change);
}
+ // ── Teil 2: Neue Marker aus 3b-Triangulation einfügen (noch nicht in robot.json) ──
+ // Diese haben keine Position in robot.json – wir übernehmen die gemessene Position.
+ if (link && extraMarkers.length > 0) {
+ const knownIds = new Set();
+ for (const ld of Object.values(links)) {
+ for (const m of (ld.markers ?? [])) knownIds.add(Number(m.id));
+ }
+
+ for (const em of extraMarkers) {
+ const emId = Number(em.marker_id);
+ const emPos = em.position_mm; // [x_mm, y_mm, z_mm] robot-Koordinaten
+ if (knownIds.has(emId)) continue; // bereits in robot.json (evtl. gerade hinzugefügt)
+ if (!Array.isArray(emPos) || emPos.length < 3) continue;
+
+ const z = Number(emPos[2]);
+ if (z < zLo || z > zHi) continue;
+
+ const newMarker = {
+ id: emId,
+ position: emPos.map(v => Math.round(Number(v) * 100) / 100), // 2 Dezimalstellen
+ };
+ if (set) newMarker.set = set;
+
+ if (!links[link]) links[link] = { markers: [] };
+ if (!links[link].markers) links[link].markers = [];
+ links[link].markers.push(newMarker);
+ knownIds.add(emId);
+
+ changes.push({
+ action: 'added',
+ markerId: emId,
+ oldLink: null,
+ oldSet: '',
+ newLink: link,
+ newSet: set ?? '',
+ });
+ }
+ }
+
if (changes.length > 0) {
robot.links = links;
await writeRobot(robotPath, robot);
diff --git a/server/server.js b/server/server.js
index ddc4931..6c1f070 100755
--- a/server/server.js
+++ b/server/server.js
@@ -706,8 +706,23 @@ app.post('/api/robot/assign-by-z', async (req, res) => {
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`);
+
+ // Triangulierte Marker aus dem letzten Board-Run als Zusatzquelle für
+ // Marker, die noch nicht in robot.json stehen (z.B. neu entdeckte Marker)
+ let extraMarkers = [];
+ try {
+ const latestRun = await findLatestBoardRun();
+ if (latestRun) {
+ const posesPath = path.join(boardDataDir, latestRun, 'aruco_marker_poses.json');
+ const poses = JSON.parse(await fsPromises.readFile(posesPath, 'utf8'));
+ extraMarkers = poses.markers ?? [];
+ }
+ } catch { /* kein 3b-Output vorhanden – nur bestehende robot.json-Marker bearbeiten */ }
+
+ const result = await assignByZRange(ROBOT_JSON, { zMin, zMax, set, link, extraMarkers });
+ const added = result.changes.filter(c => c.action === 'added').length;
+ const updated = result.changes.filter(c => c.action === 'updated').length;
+ console.log(`robot/assign-by-z z=[${zMin}..${zMax}] set="${set}" link="${link}" → ${updated} aktualisiert, ${added} neu (von ${extraMarkers.length} 3b-Markern)`);
return res.json(result);
} catch (err) {
console.error('robot/assign-by-z error:', err);