spin Marker Callibration
This commit is contained in:
@@ -338,18 +338,34 @@ function buildSkeletonFK(robot, angles) {
|
||||
gSkeleton.add(makeSphere(jointW, 0.004, 0xc8cdd8));
|
||||
}
|
||||
|
||||
// 4. Arm-Marker zeichnen (Modellposition via FK, als orientiertes Quadrat)
|
||||
// 4. Arm-Marker zeichnen (Modellposition via FK, orientiertes Quadrat + spin)
|
||||
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 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));
|
||||
const normalW = new THREE.Vector3(nx, nz, -ny).transformDirection(childFrame).normalize();
|
||||
|
||||
// P1: Quadrat mit spin-Rotation (um die Marker-Normale in Welt-Koordinaten)
|
||||
const markerMesh = makeMarkerSquareOriented(posWorld, normalW, markerSizeM, col);
|
||||
const spinRad = ((m.spin ?? 0) * Math.PI) / 180;
|
||||
if (Math.abs(spinRad) > 1e-6) {
|
||||
markerMesh.quaternion.premultiply(
|
||||
new THREE.Quaternion().setFromAxisAngle(normalW, spinRad)
|
||||
);
|
||||
}
|
||||
gArmMarkers.add(markerMesh);
|
||||
gArmMarkers.add(makeSphere(posWorld, 0.0006, col));
|
||||
|
||||
// P3b (Modell-Seite): Orientierungszeiger zur Ecke 0 (top-left bei spin=0)
|
||||
// markerMesh.quaternion kodiert bereits Q_normal ∘ Q_spin
|
||||
const ptrDir = new THREE.Vector3(1, 1, 0).normalize().applyQuaternion(markerMesh.quaternion);
|
||||
const corner0W = posWorld.clone().add(ptrDir.multiplyScalar(markerSizeM * Math.SQRT1_2));
|
||||
gArmMarkers.add(makeLine(posWorld, corner0W, col, 0.9));
|
||||
gArmMarkers.add(makeSphere(corner0W, 0.0008, col));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<button class="tab-btn" data-tab="board" data-src="/calibration_board.html">Board</button>
|
||||
<button class="tab-btn" data-tab="robot-x-axis" data-src="/calibration_xaxis.html">Robot X Axis</button>
|
||||
<button class="tab-btn" data-tab="arm1" data-src="/calibration_arm.html">Arm1 – Y</button>
|
||||
<button class="tab-btn" data-tab="marker" data-src="/calibration_marker.html">Marker</button>
|
||||
</nav>
|
||||
|
||||
<!-- CONTENT (Panels werden lazy per fetch befüllt) -->
|
||||
@@ -32,6 +33,7 @@
|
||||
<div class="tab-panel" id="tab-board"></div>
|
||||
<div class="tab-panel" id="tab-robot-x-axis"></div>
|
||||
<div class="tab-panel" id="tab-arm1"></div>
|
||||
<div class="tab-panel" id="tab-marker"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.calib-body -->
|
||||
|
||||
@@ -22,6 +22,7 @@ async function loadPanel(tab, src) {
|
||||
else if (tab === 'board') initBoard();
|
||||
else if (tab === 'robot-x-axis') initXAxis();
|
||||
else if (tab === 'arm1') initArm('arm1');
|
||||
else if (tab === 'marker') initMarker();
|
||||
|
||||
} catch (err) {
|
||||
document.getElementById('tab-' + tab).innerHTML =
|
||||
@@ -950,3 +951,163 @@ function initBoard() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Tab: Marker ───────────────────────────────────────────────────────────────
|
||||
|
||||
function initMarker() {
|
||||
const logEl = document.getElementById('log-marker');
|
||||
const tableWrap = document.getElementById('marker-table-wrap');
|
||||
const linkSel = document.getElementById('marker-action-link');
|
||||
const idSel = document.getElementById('marker-action-id');
|
||||
const spinLabel = document.getElementById('marker-spin-current');
|
||||
const resultEl = document.getElementById('marker-action-result');
|
||||
const frameEl = document.getElementById('marker-viewer-frame');
|
||||
|
||||
const ARM_LINKS = ['Arm1', 'Ellbow', 'Arm2', 'Hand', 'Palm', 'FingerA', 'FingerB'];
|
||||
|
||||
let _robot = null;
|
||||
|
||||
function logM(msg) {
|
||||
const ts = new Date().toLocaleTimeString('de-CH');
|
||||
logEl.value += `[${ts}] ${msg}\n`;
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
}
|
||||
|
||||
// ── Marker-Tabelle rendern ────────────────────────────────────────────────
|
||||
function renderTable(robot) {
|
||||
if (!tableWrap) return;
|
||||
const links = robot?.links ?? {};
|
||||
const th = (a) => `style="text-align:${a};padding:3px 8px;border-bottom:1px solid #2a2d35;white-space:nowrap;background:#1e293b;color:#555b6e;font-weight:normal"`;
|
||||
const td = (a, x = '') => `style="padding:2px 8px;border-bottom:1px solid #111418;text-align:${a};white-space:nowrap;${x}"`;
|
||||
|
||||
let rows = '';
|
||||
let total = 0;
|
||||
for (const linkName of ARM_LINKS) {
|
||||
const markers = links[linkName]?.markers ?? [];
|
||||
for (const m of markers) {
|
||||
total++;
|
||||
const pos = m.position ? m.position.map(v => Number(v).toFixed(1)).join(', ') : '–';
|
||||
const norm = m.normal ? m.normal.map(v => Number(v).toFixed(2)).join(', ') : '–';
|
||||
rows += `<tr>
|
||||
<td ${td('left', 'color:#4a9eff')}>${linkName}</td>
|
||||
<td ${td('right')}>${m.id}</td>
|
||||
<td ${td('left', 'color:#888')}>${m.name ?? '–'}</td>
|
||||
<td ${td('right')}>${pos}</td>
|
||||
<td ${td('right', 'color:#aaa')}>${norm}</td>
|
||||
<td ${td('right')}>${m.size ?? '–'}</td>
|
||||
<td ${td('right', 'color:#f0a500;font-weight:bold')}>${m.spin ?? 0}°</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (total === 0) {
|
||||
tableWrap.innerHTML = '<p style="font-size:12px;color:var(--muted)">Keine Arm-Marker in robot.json eingetragen.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
tableWrap.innerHTML = `
|
||||
<p style="font-size:10px;color:#555b6e;margin-bottom:4px">${total} Marker in Arm-Links</p>
|
||||
<table style="border-collapse:collapse;font-size:11px;font-family:inherit;width:100%">
|
||||
<thead><tr>
|
||||
<th ${th('left')}>Link</th>
|
||||
<th ${th('right')}>ID</th>
|
||||
<th ${th('left')}>Name</th>
|
||||
<th ${th('right')}>Position [x,y,z] mm</th>
|
||||
<th ${th('right')}>Normal [nx,ny,nz]</th>
|
||||
<th ${th('right')}>Size mm</th>
|
||||
<th ${th('right')}>Spin</th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// ── Marker-Dropdown für gewählten Link befüllen ───────────────────────────
|
||||
function updateMarkerDropdown() {
|
||||
if (!idSel || !_robot) return;
|
||||
const linkName = linkSel?.value;
|
||||
const markers = _robot.links?.[linkName]?.markers ?? [];
|
||||
const prev = idSel.value;
|
||||
idSel.innerHTML = '<option value="">– wählen –</option>' +
|
||||
markers.map(m => `<option value="${m.id}">${m.id}${m.name ? ' – ' + m.name : ''}</option>`).join('');
|
||||
if (markers.some(m => String(m.id) === prev)) idSel.value = prev;
|
||||
updateSpinLabel();
|
||||
}
|
||||
|
||||
function updateSpinLabel() {
|
||||
if (!spinLabel || !_robot) { if (spinLabel) spinLabel.textContent = '–'; return; }
|
||||
const linkName = linkSel?.value;
|
||||
const markerId = idSel?.value;
|
||||
if (!markerId) { spinLabel.textContent = '–'; return; }
|
||||
const markers = _robot.links?.[linkName]?.markers ?? [];
|
||||
const m = markers.find(mm => String(mm.id) === String(markerId));
|
||||
spinLabel.textContent = m ? `Aktuell: spin = ${m.spin ?? 0}°` : '–';
|
||||
}
|
||||
|
||||
// ── Robot laden ───────────────────────────────────────────────────────────
|
||||
async function loadRobot() {
|
||||
try {
|
||||
const r = await fetch('/api/robot');
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
_robot = await r.json();
|
||||
renderTable(_robot);
|
||||
updateMarkerDropdown();
|
||||
} catch (err) {
|
||||
if (tableWrap) tableWrap.innerHTML = `<p style="color:#f87171;font-size:12px">Fehler: ${err}</p>`;
|
||||
logM(`❌ robot.json konnte nicht geladen werden: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Spin-Aktion ausführen ─────────────────────────────────────────────────
|
||||
async function applySpin(delta) {
|
||||
if (!resultEl) return;
|
||||
const linkName = linkSel?.value;
|
||||
const markerId = idSel?.value;
|
||||
if (!markerId) {
|
||||
resultEl.innerHTML = '<span style="color:#f87171">⚠ Bitte zuerst einen Marker wählen.</span>';
|
||||
return;
|
||||
}
|
||||
const markers = _robot?.links?.[linkName]?.markers ?? [];
|
||||
const current = markers.find(m => String(m.id) === String(markerId));
|
||||
const oldSpin = current?.spin ?? 0;
|
||||
const newSpin = ((oldSpin + delta) % 360 + 360) % 360;
|
||||
|
||||
resultEl.innerHTML = '<span style="color:#555b6e">Speichern …</span>';
|
||||
try {
|
||||
const r = await fetch('/api/robot/set-arm-marker-spin', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ linkName, markerId: Number(markerId), spin: newSpin }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || !data.changed) {
|
||||
resultEl.innerHTML = `<span style="color:#f87171">❌ ${data.error ?? `HTTP ${r.status}`}</span>`;
|
||||
return;
|
||||
}
|
||||
resultEl.innerHTML =
|
||||
`<span style="color:#22c55e">✅ ${linkName} #${markerId}: spin ${data.oldSpin}° → ${data.newSpin}°</span>`;
|
||||
logM(`Spin ${linkName}#${markerId}: ${data.oldSpin}° → ${data.newSpin}°`);
|
||||
|
||||
// Lokales Modell aktualisieren
|
||||
if (current) current.spin = data.newSpin;
|
||||
updateSpinLabel();
|
||||
renderTable(_robot);
|
||||
|
||||
// Viewer neu laden
|
||||
if (frameEl?.contentWindow) frameEl.contentWindow.postMessage({ type: 'reload' }, '*');
|
||||
} catch (err) {
|
||||
resultEl.innerHTML = `<span style="color:#f87171">❌ ${err}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Event-Listener ────────────────────────────────────────────────────────
|
||||
document.getElementById('btn-marker-reload')?.addEventListener('click', () => loadRobot());
|
||||
linkSel?.addEventListener('change', () => updateMarkerDropdown());
|
||||
idSel?.addEventListener('change', () => updateSpinLabel());
|
||||
|
||||
document.getElementById('btn-spin-minus90')?.addEventListener('click', () => applySpin(-90));
|
||||
document.getElementById('btn-spin-plus90')?.addEventListener('click', () => applySpin(+90));
|
||||
document.getElementById('btn-spin-180')?.addEventListener('click', () => applySpin(+180));
|
||||
|
||||
// Init
|
||||
loadRobot();
|
||||
}
|
||||
|
||||
96
public/calibration_marker.html
Normal file
96
public/calibration_marker.html
Normal file
@@ -0,0 +1,96 @@
|
||||
<div class="sections">
|
||||
|
||||
<!-- ── Aktuelle Marker ──────────────────────────────────────────────────── -->
|
||||
<div class="section full">
|
||||
<h2>Aktuelle Marker <span class="status-badge open">aus robot.json</span></h2>
|
||||
<p style="font-size:12px;color:var(--muted);margin-top:8px;margin-bottom:10px">
|
||||
Arm-Marker aller Links (Board-Marker ausgeblendet). Spin = Drehung um die Marker-Normale in Grad.
|
||||
</p>
|
||||
<div id="marker-table-wrap">
|
||||
<p style="font-size:12px;color:var(--muted)">(wird geladen …)</p>
|
||||
</div>
|
||||
<div class="controls" style="margin-top:12px">
|
||||
<button id="btn-marker-reload">Neu laden</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Aktionen ─────────────────────────────────────────────────────────── -->
|
||||
<div class="section full">
|
||||
<h2>Aktionen</h2>
|
||||
|
||||
<!-- Auswahl Link / Marker -->
|
||||
<div style="margin-top:14px;display:flex;flex-wrap:wrap;align-items:center;gap:10px;font-size:12px;color:var(--text)">
|
||||
<label>Link
|
||||
<select id="marker-action-link"
|
||||
style="margin-left:6px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 8px;font:inherit;font-size:12px;cursor:pointer">
|
||||
<option value="Arm1">Arm1</option>
|
||||
<option value="Ellbow">Ellbow</option>
|
||||
<option value="Arm2">Arm2</option>
|
||||
<option value="Hand">Hand</option>
|
||||
<option value="Palm">Palm</option>
|
||||
<option value="FingerA">FingerA</option>
|
||||
<option value="FingerB">FingerB</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>Marker-ID
|
||||
<select id="marker-action-id"
|
||||
style="margin-left:6px;background:#1e293b;border:1px solid #334155;color:#c8cdd8;border-radius:3px;padding:3px 8px;font:inherit;font-size:12px;cursor:pointer">
|
||||
<option value="">– wählen –</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<span id="marker-spin-current" style="color:var(--muted);font-size:11px">–</span>
|
||||
</div>
|
||||
|
||||
<!-- Spin-Aktionen -->
|
||||
<div style="margin-top:12px">
|
||||
<p style="font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">
|
||||
Spin (Drehung um Marker-Normale)
|
||||
</p>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center">
|
||||
<button id="btn-spin-minus90"
|
||||
style="background:#1e293b;color:#c8cdd8;border:1px solid #334155;border-radius:3px;padding:5px 14px;cursor:pointer;font:inherit;font-size:12px">
|
||||
−90°
|
||||
</button>
|
||||
<button id="btn-spin-plus90"
|
||||
style="background:#1e293b;color:#c8cdd8;border:1px solid #334155;border-radius:3px;padding:5px 14px;cursor:pointer;font:inherit;font-size:12px">
|
||||
+90°
|
||||
</button>
|
||||
<button id="btn-spin-180"
|
||||
style="background:#1e293b;color:#c8cdd8;border:1px solid #334155;border-radius:3px;padding:5px 14px;cursor:pointer;font:inherit;font-size:12px">
|
||||
180°
|
||||
</button>
|
||||
<span style="font-size:10px;color:var(--muted)">→ wird sofort in robot.json gespeichert und im Viewer aktualisiert</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ergebnis -->
|
||||
<div id="marker-action-result" style="margin-top:10px;font-size:11px;min-height:18px;color:var(--muted)"></div>
|
||||
</div>
|
||||
|
||||
<!-- ── Log ──────────────────────────────────────────────────────────────── -->
|
||||
<div class="section full">
|
||||
<h2>Log</h2>
|
||||
<textarea id="log-marker" readonly placeholder="(Aktionen erscheinen hier)"
|
||||
style="height:100px"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- ── Viewer ───────────────────────────────────────────────────────────── -->
|
||||
<div class="section full">
|
||||
<h2>Viewer</h2>
|
||||
<p style="font-size:12px;color:var(--muted);margin-bottom:10px">
|
||||
Zeigt das Roboter-Modell mit den Arm-Markern in der aktuellen robot.json-Konfiguration.
|
||||
Sind Homing-Messwerte vorhanden (aus letztem Homing-Run), werden auch die beobachteten Marker
|
||||
als Kugeln und die Abweichungs-Linien dargestellt.
|
||||
Nach einer Spin-Änderung wird der Viewer automatisch neu geladen.
|
||||
</p>
|
||||
<iframe
|
||||
id="marker-viewer-frame"
|
||||
src="/boardViewer.html?mode=homing"
|
||||
style="width:100%;height:740px;border:1px solid #334155;border-radius:6px;background:#0d0f13;display:block"
|
||||
title="Marker-Viewer"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user