diff --git a/public/boardViewer.html b/public/boardViewer.html
index a9f83d8..2699078 100644
--- a/public/boardViewer.html
+++ b/public/boardViewer.html
@@ -251,7 +251,15 @@ const gCompareLines = new THREE.Group(); // Verbindungslinien Pos A↔Pos B
const gPositionC = new THREE.Group(); // Pos C Marker (nur fremd, cyan)
const gYAxis = new THREE.Group(); // Y-Achse Visualisierung (Kreismittelpunkte, Achse)
const gSkeleton = new THREE.Group(); // Roboter-Skeleton (FK, nur im Homing-Mode)
-scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines, gPositionC, gYAxis, gSkeleton);
+const gArmMarkers = new THREE.Group(); // Arm-Marker Modellpositionen + Fehlerlinien (FK)
+scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines, gPositionC, gYAxis, gSkeleton, gArmMarkers);
+
+const LINK_COLORS = {
+ Board: 0x8b6528, Base: 0x888888,
+ Arm1: 0x3355cc, Ellbow: 0xaaaaaa, Arm2: 0xddcc88,
+ Hand: 0xcc8833, Palm: 0xcc3333,
+ FingerA: 0x33aa33, FingerB: 0x33aa33,
+};
// ── Zustand für Positionen ────────────────────────────────────────────────────
let _primaryFremdMarkers = []; // Pos A – [{marker_id, position_mm, num_cameras}]
@@ -259,8 +267,9 @@ let _compareFremdMarkers = []; // Pos B – [{marker_id, position_mm, num_cam
let _positionCFremdMarkers = []; // Pos C – [{marker_id, position_mm, num_cameras}]
// ── Homing-Mode Zustand ───────────────────────────────────────────────────────
-let _currentRobot = null; // robot.json nach loadData()
-let _homingAngles = null; // { x, y, z, a, b, c, e } nach Homing-Run
+let _currentRobot = null; // robot.json nach loadData()
+let _homingAngles = null; // { x, y, z, a, b, c, e } nach Homing-Run
+let _measuredMarkers = null; // measuredMarkers aus letztem buildScene-Aufruf
// ── Roboter-Skeleton (Forward Kinematics) ─────────────────────────────────────
/**
@@ -270,6 +279,7 @@ let _homingAngles = null; // { x, y, z, a, b, c, e } nach Homing-Run
*/
function buildSkeletonFK(robot, angles) {
clearGroup(gSkeleton);
+ clearGroup(gArmMarkers);
if (!robot?.links) return;
const links = robot.links;
@@ -310,23 +320,54 @@ function buildSkeletonFK(robot, angles) {
// 3. Skeleton-Segment zeichnen
const skel = link.skeleton;
- if (!skel?.from || !skel?.to) continue;
+ if (skel?.from && skel?.to) {
+ const [fx, fy, fz] = skel.from;
+ const [tx, ty, tz] = skel.to;
+ const fromW = new THREE.Vector3(fx * S, fz * S, -fy * S).applyMatrix4(childFrame);
+ const toW = new THREE.Vector3(tx * S, tz * S, -ty * S).applyMatrix4(childFrame);
- const [fx, fy, fz] = skel.from;
- const [tx, ty, tz] = skel.to;
- const fromW = new THREE.Vector3(fx * S, fz * S, -fy * S).applyMatrix4(childFrame);
- const toW = new THREE.Vector3(tx * S, tz * S, -ty * S).applyMatrix4(childFrame);
+ const [cr, cg, cb] = skel.color ?? [0.8, 0.2, 0.2];
+ const color = new THREE.Color(cr, cg, cb);
+ const rad = Math.max((skel.radius ?? 4) * S, 0.004);
- const [cr, cg, cb] = skel.color ?? [0.8, 0.2, 0.2];
- const color = new THREE.Color(cr, cg, cb);
- const rad = Math.max((skel.radius ?? 4) * S, 0.004);
+ gSkeleton.add(makeLine(fromW, toW, color, 0.9));
+ gSkeleton.add(makeSphere(fromW, rad, color));
+ gSkeleton.add(makeSphere(toW, rad, color));
+ // Gelenk-Mittelpunkt (Welt-Ursprung des Link-Frames)
+ const jointW = new THREE.Vector3().applyMatrix4(childFrame);
+ gSkeleton.add(makeSphere(jointW, 0.004, 0xc8cdd8));
+ }
- gSkeleton.add(makeLine(fromW, toW, color, 0.9));
- gSkeleton.add(makeSphere(fromW, rad, color));
- gSkeleton.add(makeSphere(toW, rad, color));
- // Gelenk-Mittelpunkt (Welt-Ursprung des Link-Frames)
- const jointW = new THREE.Vector3().applyMatrix4(childFrame);
- gSkeleton.add(makeSphere(jointW, 0.004, 0xc8cdd8));
+ // 4. Arm-Marker zeichnen (Modellposition via FK, als orientiertes Quadrat)
+ if (link.markers?.length > 0) {
+ const col = LINK_COLORS[linkName] ?? 0xffffff;
+ for (const m of link.markers) {
+ if (!m.position) continue;
+ const [lx, ly, lz] = m.position;
+ const posWorld = new THREE.Vector3(lx * S, lz * S, -ly * S).applyMatrix4(childFrame);
+ const markerSizeM = (m.size ?? 25) * S;
+ const [nx, ny, nz] = m.normal ?? [0, 0, 1];
+ const normalWorld = new THREE.Vector3(nx, nz, -ny).transformDirection(childFrame);
+ gArmMarkers.add(makeMarkerSquareOriented(posWorld, normalWorld, markerSizeM, col));
+ gArmMarkers.add(makeSphere(posWorld, 0.003, col));
+ }
+ }
+ }
+
+ // 5. Fehlerlinien: Modell-Marker → gemessene Position (aus 3b)
+ if (_measuredMarkers?.markers?.length > 0) {
+ for (const obs of _measuredMarkers.markers) {
+ if (obs.link === 'Board' || !obs.link) continue;
+ const ldata = links[obs.link];
+ if (!ldata || !frames[obs.link]) continue;
+ const modelM = ldata.markers?.find(mm => mm.id === obs.marker_id);
+ if (!modelM?.position) continue;
+ const [lx, ly, lz] = modelM.position;
+ const modelPosW = new THREE.Vector3(lx * S, lz * S, -ly * S).applyMatrix4(frames[obs.link]);
+ const obsPosW = r2vArr(obs.position_mm);
+ gArmMarkers.add(makeLine(modelPosW, obsPosW, 0xff8800, 0.85));
+ gArmMarkers.add(makeSphere(obsPosW, 0.007, LINK_COLORS[obs.link] ?? 0x3b82f6));
+ }
}
}
@@ -362,6 +403,18 @@ function makeMarkerSquare(pos, size, color) {
return m;
}
+function makeMarkerSquareOriented(pos, normalVec, size, color) {
+ const geo = new THREE.PlaneGeometry(size, size);
+ const mat = new THREE.MeshPhongMaterial({ color, side: THREE.DoubleSide, transparent: true, opacity: 0.85 });
+ const mesh = new THREE.Mesh(geo, mat);
+ mesh.position.copy(pos);
+ const n = normalVec.clone().normalize();
+ if (n.lengthSq() > 1e-9) {
+ mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), n);
+ }
+ return mesh;
+}
+
function makeEdgeBorder(pos, size, color) {
const geo = new THREE.EdgesGeometry(new THREE.PlaneGeometry(size, size));
const mat = new THREE.LineBasicMaterial({ color, transparent: true, opacity: 0.75 });
@@ -404,6 +457,7 @@ function buildScene(data) {
clearGroup(gPaper); clearGroup(gMarkers); clearGroup(gMeasured); clearGroup(gCameras);
const { robot, detections, cameraPoses, measuredMarkers } = data;
+ _measuredMarkers = measuredMarkers ?? null;
// Alle erkannten Marker-IDs (über alle Kameras)
const detectedIds = new Set();
diff --git a/server/server.js b/server/server.js
index 745a7ce..547c7b3 100755
--- a/server/server.js
+++ b/server/server.js
@@ -446,6 +446,8 @@ const SCRIPT_4B = path.join(__dirname, '..', 'scripts', '4b_revolute_angle.p
*/
function runScript(args, send) {
return new Promise((resolve) => {
+ const cmd = [PYTHON_BIN, '-u', ...args].join(' ');
+ console.log(`[runScript] ${cmd}`);
const proc = spawn(PYTHON_BIN, ['-u', ...args]);
let outBuf = '';