UI verbessern
This commit is contained in:
@@ -204,6 +204,13 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
const S = 1 / 1000; // mm → m
|
||||
|
||||
// ── Modus-Erkennung ──────────────────────────────────────────────────────────
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
const IS_HOMING = _urlParams.get('mode') === 'homing';
|
||||
if (IS_HOMING) {
|
||||
document.getElementById('run-bar').style.display = 'none';
|
||||
}
|
||||
|
||||
// robot (x=right, y=backward, z=up) → Three.js (x=right, y=up, z=toward viewer)
|
||||
function r2v(rx, ry, rz) { return new THREE.Vector3(rx * S, rz * S, -ry * S); }
|
||||
function r2vArr([rx, ry, rz]) { return r2v(rx, ry, rz); }
|
||||
@@ -243,13 +250,86 @@ const gCompare = new THREE.Group(); // Pos B Marker (nur fremd, orange)
|
||||
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)
|
||||
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines, gPositionC, gYAxis);
|
||||
const gSkeleton = new THREE.Group(); // Roboter-Skeleton (FK, nur im Homing-Mode)
|
||||
scene.add(gPaper, gMarkers, gMeasured, gCameras, gCompare, gCompareLines, gPositionC, gYAxis, gSkeleton);
|
||||
|
||||
// ── Zustand für Positionen ────────────────────────────────────────────────────
|
||||
let _primaryFremdMarkers = []; // Pos A – [{marker_id, position_mm, num_cameras}]
|
||||
let _compareFremdMarkers = []; // Pos B – [{marker_id, position_mm, num_cameras}]
|
||||
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
|
||||
|
||||
// ── Roboter-Skeleton (Forward Kinematics) ─────────────────────────────────────
|
||||
/**
|
||||
* Zeichnet das Roboter-Skeleton mit vorwärts-kinematischer Berechnung.
|
||||
* Nutzt skeleton.from/to aus robot.json (in lokalem Link-Frame).
|
||||
* angles: { x_mm→x, y_deg→y, z_deg→z, a_deg→a, b_deg→b, c_deg→c, e_mm→e }
|
||||
*/
|
||||
function buildSkeletonFK(robot, angles) {
|
||||
clearGroup(gSkeleton);
|
||||
if (!robot?.links) return;
|
||||
|
||||
const links = robot.links;
|
||||
const order = ['Base', 'Arm1', 'Ellbow', 'Arm2', 'Hand', 'Palm', 'FingerA', 'FingerB'];
|
||||
// frames: Link-Name → Matrix4 (link-lokal → Welt)
|
||||
const frames = { Board: new THREE.Matrix4() }; // Board = Welt-Ursprung
|
||||
|
||||
for (const linkName of order) {
|
||||
const link = links[linkName];
|
||||
if (!link?.jointToParent) continue;
|
||||
|
||||
const parentName = link.parent ?? 'Board';
|
||||
const parentFrame = frames[parentName] ?? new THREE.Matrix4();
|
||||
const jtp = link.jointToParent;
|
||||
|
||||
// 1. Translation zum Gelenk-Ursprung (im Parent-Frame)
|
||||
const [ox, oy, oz] = jtp.origin ?? [0, 0, 0];
|
||||
const T_origin = new THREE.Matrix4().makeTranslation(ox * S, oz * S, -oy * S);
|
||||
|
||||
// 2. Gelenk-Transformation (Rotation/Translation je nach Typ)
|
||||
const varName = jtp.variable;
|
||||
const q = angles?.[varName] ?? 0;
|
||||
let T_joint = new THREE.Matrix4(); // Einheitsmatrix bei q=0
|
||||
|
||||
if (jtp.type === 'revolute') {
|
||||
const [ax, ay, az] = jtp.axis ?? [0, 1, 0];
|
||||
// robot (x,y,z) → Three.js (x, z, -y)
|
||||
const axisV = new THREE.Vector3(ax, az, -ay).normalize();
|
||||
T_joint.makeRotationAxis(axisV, q * Math.PI / 180);
|
||||
} else if (jtp.type === 'linear') {
|
||||
const [ax, ay, az] = jtp.axis ?? [1, 0, 0];
|
||||
T_joint.makeTranslation(ax * q * S, az * q * S, -ay * q * S);
|
||||
}
|
||||
|
||||
// Child-Frame = Parent-Frame × T_origin × T_joint
|
||||
const childFrame = parentFrame.clone().multiply(T_origin).multiply(T_joint);
|
||||
frames[linkName] = childFrame;
|
||||
|
||||
// 3. Skeleton-Segment zeichnen
|
||||
const skel = link.skeleton;
|
||||
if (!skel?.from || !skel?.to) continue;
|
||||
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Viewer-interner Logger ────────────────────────────────────────────────────
|
||||
function vlog(msg, kind = '') {
|
||||
const el = document.getElementById('viewer-log');
|
||||
@@ -810,14 +890,21 @@ function computeAndShowYAxis() {
|
||||
|
||||
// ── Daten laden ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** Haupt-Run laden (Basis-Dropdown). Ohne Selektion → neuester Run. */
|
||||
/** Haupt-Run laden. Im Homing-Mode: immer neuester Homing-Run, kein Dropdown. */
|
||||
async function loadData() {
|
||||
const statusEl = document.getElementById('status');
|
||||
statusEl.textContent = 'Laden …';
|
||||
const selRun = document.getElementById('sel-run-primary')?.value ?? '';
|
||||
const url = selRun
|
||||
? `/api/board/latest?run=${encodeURIComponent(selRun)}`
|
||||
: '/api/board/latest';
|
||||
|
||||
let url;
|
||||
if (IS_HOMING) {
|
||||
url = '/api/board/latest?from=homing';
|
||||
} else {
|
||||
const selRun = document.getElementById('sel-run-primary')?.value ?? '';
|
||||
url = selRun
|
||||
? `/api/board/latest?run=${encodeURIComponent(selRun)}`
|
||||
: '/api/board/latest';
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await fetch(url);
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
@@ -826,6 +913,7 @@ async function loadData() {
|
||||
statusEl.textContent = 'Kein Board-Run vorhanden.';
|
||||
document.getElementById('stats').textContent = '';
|
||||
clearGroup(gPaper); clearGroup(gMarkers); clearGroup(gMeasured); clearGroup(gCameras);
|
||||
if (IS_HOMING) clearGroup(gSkeleton);
|
||||
return;
|
||||
}
|
||||
buildScene(data);
|
||||
@@ -839,6 +927,13 @@ async function loadData() {
|
||||
buildCompareLines();
|
||||
const robotLabel = data.robotFile ? ` • Robot: ${data.robotFile}` : '';
|
||||
statusEl.textContent = `Run: ${data.runDir}${robotLabel} • ${new Date().toLocaleTimeString('de-CH')}`;
|
||||
|
||||
// Skeleton im Homing-Mode
|
||||
if (IS_HOMING && data.robot) {
|
||||
_currentRobot = data.robot;
|
||||
const angles = _homingAngles ?? data.robot.defaultPosition ?? {};
|
||||
buildSkeletonFK(data.robot, angles);
|
||||
}
|
||||
} catch (err) {
|
||||
statusEl.textContent = `Fehler: ${err.message ?? err}`;
|
||||
}
|
||||
@@ -965,6 +1060,11 @@ async function initRunSelectors() {
|
||||
|
||||
/** Vollständige Initialisierung: Selektoren → Pos A → Pos B → Pos C (sequenziell!) */
|
||||
async function initAll() {
|
||||
if (IS_HOMING) {
|
||||
// Homing-Mode: nur neuester Run laden, kein Dropdown, kein Vergleich
|
||||
await loadData();
|
||||
return;
|
||||
}
|
||||
await initRunSelectors();
|
||||
await loadData(); // setzt _primaryFremdMarkers
|
||||
await loadCompareData(); // setzt _compareFremdMarkers + baut Linien + Y-Achse (no-op)
|
||||
@@ -984,6 +1084,10 @@ document.getElementById('sel-run-compare')?.addEventListener('change', async ()
|
||||
document.getElementById('sel-run-c')?.addEventListener('change', loadPositionC);
|
||||
window.addEventListener('message', async (e) => {
|
||||
if (e.data?.type === 'reload') await initAll();
|
||||
if (e.data?.type === 'homing-state' && IS_HOMING) {
|
||||
_homingAngles = e.data.state;
|
||||
if (_currentRobot) buildSkeletonFK(_currentRobot, _homingAngles);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Resize & Render-Loop ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -484,10 +484,13 @@ async function runHoming() {
|
||||
setHomingStatus('✗ Fehler', 'open');
|
||||
setHomingProgress(6, 6, 'Fehler aufgetreten');
|
||||
}
|
||||
if (evt.runDir) {
|
||||
await loadHomingImages(evt.runDir);
|
||||
const frame = document.getElementById('board-viewer-frame');
|
||||
if (frame) { const s = frame.src; frame.src = ''; frame.src = s; }
|
||||
if (evt.runDir) await loadHomingImages(evt.runDir);
|
||||
const frame = document.getElementById('board-viewer-frame');
|
||||
if (frame?.contentWindow) {
|
||||
frame.contentWindow.postMessage({ type: 'reload' }, '*');
|
||||
if (evt.state) {
|
||||
frame.contentWindow.postMessage({ type: 'homing-state', state: evt.state }, '*');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<h2>Board-Viewer</h2>
|
||||
<iframe
|
||||
id="board-viewer-frame"
|
||||
src="/boardViewer.html?defaults=a"
|
||||
src="/boardViewer.html?mode=homing"
|
||||
style="width:100%;height:600px;border:1px solid #334155;border-radius:6px;background:#0d0f13;display:block;margin-top:12px"
|
||||
title="Board-Viewer"
|
||||
></iframe>
|
||||
|
||||
Reference in New Issue
Block a user