Y-Axis checks

This commit is contained in:
chk
2026-06-13 06:13:31 +02:00
parent 1762a771cf
commit 7f17427e0a
7 changed files with 765 additions and 110 deletions

View File

@@ -488,3 +488,124 @@ export async function adoptXAxis(robotPath, { direction }) {
angleXZdeg: Math.round(Math.atan2(nz, nx) * 18000 / Math.PI) / 100,
};
}
// ── Aktion 6: Fixe Marker einem Link zuordnen ─────────────────────────────────
/**
* Ordnet Marker, die sich bei einer Gelenk-Rotation kaum bewegen, dem angegebenen
* Link zu. Typischer Anwendungsfall: Marker auf dem Basis-Körper werden als
* "Base"-Marker in robot.json registriert.
*
* - markerIds: Array von Marker-IDs (number[])
* - targetLink: Ziel-Link-Name (z. B. 'Base')
* - measuredPositions: Array von { id, position_mm:[x,y,z] } aus Pos A
*
* Gibt { numAdded, numAlreadyPresent, changes[] } zurück.
* Bestehende Einträge werden NICHT verschoben (überschreiben wäre destruktiv).
*/
export async function assignFixedMarkersToLink(robotPath, { markerIds, targetLink, measuredPositions = [] }) {
if (!targetLink) throw new Error('targetLink muss angegeben werden.');
const robot = await readRobot(robotPath);
const links = robot.links ?? {};
// Index: markerId → aktueller Link
const existingMap = new Map();
for (const [linkName, linkData] of Object.entries(links)) {
for (const m of (linkData.markers ?? [])) {
existingMap.set(Number(m.id), linkName);
}
}
// Pos-A-Positionen indizieren
const posMap = new Map();
for (const p of measuredPositions) {
posMap.set(Number(p.id), p.position_mm);
}
if (!links[targetLink]) links[targetLink] = { markers: [] };
if (!links[targetLink].markers) links[targetLink].markers = [];
const changes = [];
let numAdded = 0, numAlreadyPresent = 0;
for (const rawId of markerIds) {
const id = Number(rawId);
if (existingMap.has(id)) {
numAlreadyPresent++;
changes.push({ action: 'already-present', markerId: id, existingLink: existingMap.get(id) });
continue;
}
const pos = posMap.get(id);
if (!pos || pos.length < 3) {
changes.push({ action: 'skipped-no-position', markerId: id });
continue;
}
links[targetLink].markers.push({
id,
position: pos.map(v => Math.round(Number(v) * 10) / 10), // 1 Dezimalstelle (mm)
positionSource: 'calibration-fixed-detection',
});
numAdded++;
changes.push({ action: 'added', markerId: id, targetLink });
}
if (numAdded > 0) {
robot.links = links;
await writeRobot(robotPath, robot);
}
return { numAdded, numAlreadyPresent, changes };
}
// ── Aktion 7: Joint-Origin Y/Z aus Drehachse übernehmen ──────────────────────
/**
* Setzt die Y- und Z-Koordinaten des jointToParent.origin eines Links.
*
* Der Ursprung der Drehachse (axisPoint_mm) liegt im Board-Koordinatensystem.
* Y und Z sind davon direkt auf den Joint-Origin übertragbar (X wird vom
* Slider bestimmt und bleibt unverändert).
*
* - linkName: Name des Links, dessen Joint aktualisiert wird (z. B. 'Arm1')
* - y: Neuer Y-Wert des Joint-Origins (mm)
* - z: Neuer Z-Wert des Joint-Origins (mm)
*
* Gibt { changed, linkName, oldOrigin, newOrigin } zurück.
*/
export async function setJointOriginYZ(robotPath, { linkName, y, z }) {
if (!linkName) throw new Error('linkName muss angegeben werden.');
const robot = await readRobot(robotPath);
const link = (robot.links ?? {})[linkName];
if (!link) return { changed: false, error: `Link '${linkName}' nicht in robot.json gefunden.` };
const joint = link.jointToParent;
if (!joint) return { changed: false, error: `Link '${linkName}' hat kein jointToParent.` };
const oldOrigin = Array.isArray(joint.origin) ? [...joint.origin] : [null, null, null];
if (!Array.isArray(joint.origin) || joint.origin.length < 3) {
joint.origin = [oldOrigin[0] ?? 0, 0, 0];
}
joint.origin[1] = Math.round(Number(y) * 10) / 10; // Y (1 Dezimalstelle)
joint.origin[2] = Math.round(Number(z) * 10) / 10; // Z
// Quelle dokumentieren
if (!joint.originSource) joint.originSource = [null, null, null];
while (joint.originSource.length < 3) joint.originSource.push(null);
joint.originSource[1] = 'calibration-yaxis';
joint.originSource[2] = 'calibration-yaxis';
await writeRobot(robotPath, robot);
return {
changed: true,
linkName,
oldOrigin,
newOrigin: [...joint.origin],
};
}

View File

@@ -8,7 +8,7 @@ import { fileURLToPath } from 'url';
import process from 'process';
import { spawn } from 'child_process';
import { WebcamClient } from './webcamClient.js';
import { assignByZRange, removeMarkerAssignment, alignSetToMeasured, assignMarkerId, adoptXAxis } from './editRobot.js';
import { assignByZRange, removeMarkerAssignment, alignSetToMeasured, assignMarkerId, adoptXAxis, assignFixedMarkersToLink, setJointOriginYZ } from './editRobot.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -889,6 +889,61 @@ app.post('/api/robot/adopt-x-axis', async (req, res) => {
}
});
/**
* POST /api/robot/assign-fixed-markers
* Ordnet Marker, die sich bei einer Gelenk-Rotation kaum bewegen, dem
* angegebenen Link zu (typisch: 'Base').
* Body: { markerIds: number[], targetLink: string, measuredPositions: [{id, position_mm}] }
*/
app.post('/api/robot/assign-fixed-markers', async (req, res) => {
try {
const { markerIds, targetLink, measuredPositions = [] } = req.body ?? {};
if (!Array.isArray(markerIds) || markerIds.length === 0) {
return res.status(400).json({ error: '"markerIds" muss ein nicht-leeres Array sein.' });
}
if (!targetLink) {
return res.status(400).json({ error: '"targetLink" muss angegeben werden.' });
}
const result = await assignFixedMarkersToLink(ROBOT_JSON, { markerIds, targetLink, measuredPositions });
console.log(
`robot/assign-fixed-markers [${markerIds.join(',')}] → ${targetLink}` +
` added=${result.numAdded} alreadyPresent=${result.numAlreadyPresent}`,
);
return res.json(result);
} catch (err) {
console.error('robot/assign-fixed-markers error:', err);
return res.status(500).json({ error: String(err) });
}
});
/**
* POST /api/robot/set-joint-origin
* Setzt Y und Z des jointToParent.origin eines Links aus der berechneten
* Drehachsen-Position.
* Body: { linkName: string, y: number, z: number }
*/
app.post('/api/robot/set-joint-origin', async (req, res) => {
try {
const { linkName, y, z } = req.body ?? {};
if (!linkName) return res.status(400).json({ error: '"linkName" muss angegeben werden.' });
if (!Number.isFinite(Number(y)) || !Number.isFinite(Number(z))) {
return res.status(400).json({ error: '"y" und "z" müssen Zahlen sein.' });
}
const result = await setJointOriginYZ(ROBOT_JSON, { linkName, y: Number(y), z: Number(z) });
if (!result.changed) {
return res.status(400).json({ error: result.error });
}
console.log(
`robot/set-joint-origin ${linkName}: ` +
`[${result.oldOrigin.join(', ')}] → [${result.newOrigin.join(', ')}]`,
);
return res.json(result);
} catch (err) {
console.error('robot/set-joint-origin error:', err);
return res.status(500).json({ error: String(err) });
}
});
/**
* POST /api/calibration/upload-npz
* Liest {camera}_calibration.npz aus der aktuellen Session und