Callibration endet bei Y-Axis
This commit is contained in:
381
doc/Homing_ROADMAP.md
Normal file
381
doc/Homing_ROADMAP.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Homing Roadmap – appRobotHoming
|
||||
|
||||
> Stand: 2026-06-13
|
||||
> Ziel: Aus einem einzigen Kamera-Snapshot die aktuellen Gelenkwinkel/-positionen
|
||||
> des Roboters bestimmen und an den Controller senden.
|
||||
|
||||
---
|
||||
|
||||
## Was ist Homing?
|
||||
|
||||
Homing = der Roboter weiss **nicht**, wo er ist.
|
||||
Die Kameras schauen auf das Board + die ArUco-Marker am Roboter und berechnen
|
||||
daraus die vollständige Pose aller Gelenke — ohne mechanische Endschalter.
|
||||
|
||||
Homing läuft bei **jedem** Einschalten ab, also muss es schnell und robust sein.
|
||||
Kalibrierung hingegen läuft nur nach mechanischen Änderungen (≈ einmalig).
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen (Kalibrierung muss abgeschlossen sein)
|
||||
|
||||
| Was | Wo gespeichert | Status |
|
||||
|-----|----------------|--------|
|
||||
| Kamera-Intrinsik (NPZ) | `data/calibration/camX_calibration.npz` | ✅ |
|
||||
| Board-Marker-Positionen | `robot.json → links.Board.markers[]` | ✅ |
|
||||
| X-Achsen-Richtung | `robot.json → links.*.position` (rotiert) | ✅ |
|
||||
| **Arm1-Gelenkursprung Y/Z** | `robot.json → links.Arm1.jointToParent.origin[1,2]` | 🔶 in Arbeit |
|
||||
| Arm-Marker-Zuordnung | `robot.json → links.Arm1/Ellbow/Arm2/Hand.markers[]` | ❌ offen |
|
||||
|
||||
> **Schlüssel-Erkenntnis:** Sobald `Arm1.jointToParent.origin` korrekt gesetzt ist (aus
|
||||
> der Y-Achsen-Kalibrierung), ist die gesamte Kinematikkette in `robot.json` geometrisch
|
||||
> definiert. Dann kann Homing starten.
|
||||
|
||||
---
|
||||
|
||||
## Kinematik-Kette (aus `robot.json`)
|
||||
|
||||
```
|
||||
Board (ROOT, fest)
|
||||
│
|
||||
├── Base linear variable=x axis=[1,0,0] origin=[0,0,16]
|
||||
│ → Slider-Position entlang Board-X
|
||||
│
|
||||
├── Arm1 revolute variable=y axis=[-1,0,0] origin=[110, 108*, 45*]
|
||||
│ → Schultergelenk (heben/senken) *aus Y-Achsen-Kalib.
|
||||
│
|
||||
├── Ellbow revolute variable=z axis=[-1,0,0] origin=[0,-250,0]
|
||||
│ → Ellbogen (relativ zu Arm1-Ende)
|
||||
│
|
||||
├── Arm2 revolute variable=a axis=[0,-1,0] origin=[90,0,0]
|
||||
│ → Unterarm-Drehung
|
||||
│
|
||||
├── Hand revolute variable=b axis=[1,0,0] origin=[0,-250,0]
|
||||
│ → Handgelenk
|
||||
│
|
||||
├── Palm revolute variable=c axis=[0,-1,0] origin=[0,0,0]
|
||||
│ → Handflächen-Rotation
|
||||
│
|
||||
└── FingerA/B linear variable=e axis=±[1,0,0] origin=[±4,-35,0]
|
||||
→ Greifer (symmetrisch)
|
||||
```
|
||||
|
||||
**Gelenkvariablen:** `x` (mm), `y z a b c` (Grad), `e` (mm)
|
||||
|
||||
---
|
||||
|
||||
## Homing-Ablauf (Schritt für Schritt)
|
||||
|
||||
### Schritt 1 — Snapshot aufnehmen
|
||||
|
||||
**Was:** Alle Kameras gleichzeitig ein Foto.
|
||||
|
||||
**Script:** — (direkt via Webcam-API)
|
||||
|
||||
**UI-Aktion:** Button `[Foto aufnehmen]`
|
||||
|
||||
**Ausgabe:**
|
||||
- `cam0.jpg`, `cam1.jpg`, `cam2.jpg` im aktuellen Run-Verzeichnis
|
||||
- **Snapshots-Sektion:** Kamerabilder erscheinen
|
||||
|
||||
---
|
||||
|
||||
### Schritt 2 — ArUco-Marker erkennen
|
||||
|
||||
**Was:** Pixel-Koordinaten aller sichtbaren Marker in jedem Kamerabild.
|
||||
|
||||
**Script:** `1_detect_aruco_observations.py`
|
||||
|
||||
```bash
|
||||
python 1_detect_aruco_observations.py \
|
||||
-i cam0.jpg cam1.jpg cam2.jpg \
|
||||
-robot robot.json \
|
||||
-outDir data/homing/TIMESTAMP/
|
||||
```
|
||||
|
||||
**Output-Dateien:**
|
||||
- `cam0_aruco_detection.json` — Marker-IDs + Pixel-Ecken + Kamera-Pose-Kandidaten
|
||||
|
||||
**Snapshot CSV:** Tabelle: MarkerID | Kamera | px-Koordinaten | confidence
|
||||
|
||||
**Fehlerfälle:**
|
||||
- Marker verdeckt → weniger Marker in CSV sichtbar
|
||||
- Schlechte Beleuchtung → confidence niedrig
|
||||
|
||||
---
|
||||
|
||||
### Schritt 3 — Kamera-Posen schätzen
|
||||
|
||||
**Was:** Aus den Board-Markern (fix im Raum) die extrinsische Lage jeder Kamera
|
||||
berechnen.
|
||||
|
||||
**Script:** `2_estimate_camera_from_observations.py`
|
||||
|
||||
```bash
|
||||
python 2_estimate_camera_from_observations.py \
|
||||
-i data/homing/TIMESTAMP/ \
|
||||
-robot robot.json \
|
||||
-outDir data/homing/TIMESTAMP/
|
||||
```
|
||||
|
||||
**Output-Dateien:**
|
||||
- `cam0_camera_pose.json` — 4×4 Transformationsmatrix Kamera→Welt
|
||||
|
||||
**Analysis & Reasoning:** Reprojektions-Fehler pro Kamera (sollte < 3 px)
|
||||
|
||||
**Fehlerfälle:**
|
||||
- Zu wenig Board-Marker sichtbar (< 3) → Kamera-Pose nicht berechenbar
|
||||
- Hoher Reprojektions-Fehler → Kalibrierung möglicherweise veraltet
|
||||
|
||||
---
|
||||
|
||||
### Schritt 3b — 3D-Marker-Positionen triangulieren
|
||||
|
||||
**Was:** Aus den Kamera-Posen und den Pixel-Koordinaten die echten 3D-Positionen
|
||||
aller Marker berechnen (inkl. Arm-Marker).
|
||||
|
||||
**Script:** `3b_corner_marker_poses.py`
|
||||
|
||||
```bash
|
||||
python 3b_corner_marker_poses.py \
|
||||
-i data/homing/TIMESTAMP/ \
|
||||
-robot robot.json \
|
||||
--outDir data/homing/TIMESTAMP/
|
||||
```
|
||||
|
||||
**Output-Dateien:**
|
||||
- `aruco_marker_poses.json` — für jeden Marker: `position_mm`, `normal`, `corners_m`, `link`
|
||||
|
||||
**Snapshot CSV:** Vollständige Marker-Tabelle mit triangulierten 3D-Positionen
|
||||
|
||||
**Fehlerfälle:**
|
||||
- Marker nur in 1 Kamera → keine Triangulation möglich (braucht ≥ 2)
|
||||
|
||||
---
|
||||
|
||||
### Schritt 4 — Gelenkwinkel bestimmen
|
||||
|
||||
Zwei Methoden — **4b** ist sequenziell und robuster:
|
||||
|
||||
#### Methode A — Vollständige State-Schätzung (schnell)
|
||||
|
||||
**Script:** `4_robotState_estimation_v6.py`
|
||||
|
||||
```bash
|
||||
python 4_robotState_estimation_v6.py \
|
||||
aruco_marker_poses.json \
|
||||
--robot robot.json \
|
||||
--output homing_state.json
|
||||
```
|
||||
|
||||
Schätzt alle Gelenke in einem Durchlauf mit geometrischen Gleichungen
|
||||
(Kabsch-Fit, Projektions-Residuen, 2D-Winkel).
|
||||
|
||||
#### Methode B — Sequenziell Gelenk für Gelenk (robuster)
|
||||
|
||||
**Script:** `4b_revolute_angle.py` — einmal pro Gelenk, von root nach tip:
|
||||
|
||||
```bash
|
||||
# Slider-Position (x) aus Board-Markern bekannt → manuell oder aus 4a
|
||||
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
|
||||
--link Arm1 --x-mm 180 --output state_arm1.json
|
||||
|
||||
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
|
||||
--link Ellbow --from-state state_arm1.json --output state_ellbow.json
|
||||
|
||||
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
|
||||
--link Arm2 --from-state state_ellbow.json --output state_arm2.json
|
||||
|
||||
python 4b_revolute_angle.py --robot robot.json --aruco aruco_marker_poses.json \
|
||||
--link Hand --from-state state_arm2.json --output state_hand.json
|
||||
```
|
||||
|
||||
Jedes `--from-state` enthält den akkumulierten Gelenk-State aus allen
|
||||
vorherigen Schritten.
|
||||
|
||||
**Warum sequenziell?** Arm2-Winkel kann nur korrekt berechnet werden wenn
|
||||
Arm1 und Ellbow bereits bekannt sind (der Weltachsenvektor des Arm2-Joints
|
||||
ändert sich mit den vorherigen Gelenkwinkeln).
|
||||
|
||||
**Output-JSON-Struktur (pro Schritt):**
|
||||
```json
|
||||
{
|
||||
"link": "Arm1",
|
||||
"joint": "y",
|
||||
"mean_angle_deg": 23.4,
|
||||
"circular_std_deg": 0.8,
|
||||
"num_pairs": 6,
|
||||
"joint_origin_world_mm": [290, 108, 45],
|
||||
"joint_axis_world": [-1, 0, 0],
|
||||
"accumulated_state": { "x": 180, "y": 23.4, "z": null, "a": null, ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Analysis & Reasoning:** Gelenk-für-Gelenk-Ergebnis als JSON-Baum
|
||||
|
||||
**Fehlerfälle:**
|
||||
- `circular_std_deg` > 5° → Marker-Konfiguration fragwürdig, Messung wiederholen
|
||||
- `num_pairs` < 2 → Arm-Marker nicht genug sichtbar
|
||||
|
||||
---
|
||||
|
||||
### Schritt 5 (optional) — Kamera-Z verfeinern
|
||||
|
||||
**Was:** Kamera-Höhe (Z) ist aus Board-Markern schlecht bestimmt (Board ist flach).
|
||||
Wenn Ellbow-Winkel bekannt → FK-Vorhersage als Z-Referenz nutzen.
|
||||
|
||||
**Script:** `5_camera_z_refine.py`
|
||||
|
||||
```bash
|
||||
python 5_camera_z_refine.py \
|
||||
--angle state_ellbow.json \
|
||||
--robot robot.json \
|
||||
--aruco aruco_marker_poses.json \
|
||||
-pose cam0_camera_pose.json \
|
||||
-pose cam1_camera_pose.json \
|
||||
--outDir data/homing/TIMESTAMP/
|
||||
```
|
||||
|
||||
**Output:** Korrigierte `*_camera_pose_v8.json` + `z_correction.json`
|
||||
|
||||
**Wann nötig:** Wenn Residuen in Schritt 4 systematisch in Z-Richtung driften.
|
||||
|
||||
---
|
||||
|
||||
### Schritt 6 — Ergebnis senden
|
||||
|
||||
**Was:** Vollständigen Joint-State an Roboter-Controller übermitteln.
|
||||
|
||||
**UI-Aktion:** Button `[An Roboter senden]`
|
||||
|
||||
**Body:** `{ x, y, z, a, b, c, e }`
|
||||
→ `POST ROBOT_URL/api/state` (oder Motion-Controller-Protokoll)
|
||||
|
||||
**Result – Raw JSON:**
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-06-13T19:00:00",
|
||||
"state": { "x": 180.0, "y": 23.4, "z": -12.1, "a": 5.0, "b": 0.0, "c": 0.0, "e": 0.0 },
|
||||
"confidence": { "y_std_deg": 0.8, "z_std_deg": 1.2 }
|
||||
}
|
||||
```
|
||||
|
||||
**Result – Tree View:** Gelenk-Werte als lesbare Tabelle
|
||||
|
||||
---
|
||||
|
||||
## UI-Seitenstruktur (analog index.html)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ AKTIONEN │
|
||||
│ [Foto aufnehmen] [Homing berechnen] │
|
||||
│ [An Roboter senden] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ AUSGABE / LOG │
|
||||
│ Schritt-für-Schritt Log aller Scripts │
|
||||
├─────────────────────────────────────────┤
|
||||
│ ANALYSIS & REASONING │
|
||||
│ Kamera-Posen, Reprojektionsfehler, │
|
||||
│ Gelenk-Schätzungen je Schritt als JSON │
|
||||
├──────────────────┬──────────────────────┤
|
||||
│ RESULT – RAW JSON │ RESULT – TREE │
|
||||
│ Vollst. State-JSON │ Gelenk-Tabelle │
|
||||
├─────────────────────────────────────────┤
|
||||
│ SNAPSHOT CSV │
|
||||
│ Tabelle aller Marker: ID, Position, │
|
||||
│ Link, Residual, num_cameras │
|
||||
├─────────────────────────────────────────┤
|
||||
│ SNAPSHOTS (Bilder) │
|
||||
│ Annotierte Kamerabilder mit Markern │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Offene Punkte (Voraussetzungen für Homing)
|
||||
|
||||
### 🔶 A — Arm-Marker in robot.json eintragen
|
||||
|
||||
Die Arm-Links haben noch **keine Marker** in `robot.json` (links.Arm1/Ellbow/Arm2/Hand.markers).
|
||||
|
||||
Mögliche Quellen:
|
||||
- Aus der Y-Achsen-Kalibrierung: Marker 197, 218, 219 wurden als rotierend erkannt
|
||||
→ diese können mit relativen Positionen in den jeweiligen Links eingetragen werden
|
||||
- Mit `4b_revolute_angle.py` lässt sich pro Link prüfen, welche Marker
|
||||
"zu diesem Link gehören" (paarweise Baseline-Methode)
|
||||
|
||||
**Aktion:** Für jedes Arm-Segment mind. 2 Marker bestimmen und in robot.json eintragen.
|
||||
|
||||
### 🔶 B — Arm1.jointToParent.origin[Y, Z] finalisieren
|
||||
|
||||
Aus der Arm1-Y-Achsen-Kalibrierung (calibration_arm.html):
|
||||
- Berechneter Wert: Y ≈ 115.6 mm, Z ≈ 50.3 mm
|
||||
- Aktueller Wert in robot.json: origin = [110, 108, 45]
|
||||
- **Aktion:** Button „Joint-Origin Y/Z übernehmen" klicken → schreibt in robot.json
|
||||
|
||||
### 🔶 C — X-Position (Slider) im Homing
|
||||
|
||||
Die Slider-Position `x` ist für `4b_revolute_angle.py --x-mm` nötig.
|
||||
Optionen:
|
||||
1. Mechanischer Anschlag / Encoder → immer bekannt
|
||||
2. Aus Board-Markern schätzen (funktioniert wenn A0-Marker sichtbar)
|
||||
3. Manuell eingeben (Fallback)
|
||||
|
||||
### 🔶 D — Homing-Seite implementieren
|
||||
|
||||
Eine neue `homing.html`-Seite (oder Tab in `calibration.html`) die:
|
||||
- Die Script-Kette 1 → 2 → 3b → 4b(je Link) → (5) als SSE-Stream orchestriert
|
||||
- Alle Ausgabe-Sektionen wie in index.html befüllt
|
||||
- „An Roboter senden" Button mit finalem State-JSON verdrahtet
|
||||
|
||||
---
|
||||
|
||||
## Script-Abhängigkeitsgraph
|
||||
|
||||
```
|
||||
Kamera-Snapshot
|
||||
│
|
||||
▼
|
||||
[1] detect_aruco → cam*_aruco_detection.json
|
||||
│
|
||||
▼
|
||||
[2] estimate_camera → cam*_camera_pose.json
|
||||
│ (aus Board-Markern)
|
||||
▼
|
||||
[3b] corner_marker_poses → aruco_marker_poses.json
|
||||
│ (alle Marker trianguliert)
|
||||
├──────────────────────────────────────┐
|
||||
▼ ▼
|
||||
[4b] revolute Arm1 [4] state_v6 (alternativ)
|
||||
│
|
||||
[4b] revolute Ellbow ──► [5] camera_z_refine (optional, nutzt Ellbow)
|
||||
│
|
||||
[4b] revolute Arm2
|
||||
│
|
||||
[4b] revolute Hand
|
||||
│
|
||||
▼
|
||||
State-JSON {x, y, z, a, b, c, e}
|
||||
│
|
||||
▼
|
||||
→ Roboter-Controller
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Status-Tabelle
|
||||
|
||||
| Schritt | Script | Status | Blockiert durch |
|
||||
|---------|--------|--------|-----------------|
|
||||
| 1 Snapshot | Webcam-API | ✅ vorhanden | — |
|
||||
| 2 Marker erkennen | `1_detect_aruco.py` | ✅ vorhanden | — |
|
||||
| 3 Kamera-Pose | `2_estimate_camera.py` | ✅ vorhanden | — |
|
||||
| 3b Triangulation | `3b_corner_marker_poses.py` | ✅ vorhanden | — |
|
||||
| 4 State-Schätzung | `4_robotState_estimation_v6.py` | ✅ vorhanden | Arm-Marker fehlen |
|
||||
| 4b Sequenziell | `4b_revolute_angle.py` | ✅ vorhanden | Arm-Marker fehlen |
|
||||
| 5 Z-Verfeinern | `5_camera_z_refine.py` | ✅ vorhanden | Ellbow-Marker fehlen |
|
||||
| **Arm1-Origin** | calibration_arm.html | 🔶 bereit | Joint-Origin Y/Z übernehmen |
|
||||
| **Arm-Marker** | robot.json | ❌ fehlen | manuelle Zuordnung |
|
||||
| **Homing-UI** | homing.html | ❌ fehlt | erst nach Markern sinnvoll |
|
||||
@@ -24,9 +24,6 @@
|
||||
<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="arm2" data-src="/calibration_arm2.html">Arm2 – Z</button>
|
||||
<button class="tab-btn" data-tab="elbow" data-src="/calibration_elbow.html">Elbow</button>
|
||||
<button class="tab-btn" data-tab="hand" data-src="/calibration_hand.html">Hand</button>
|
||||
</nav>
|
||||
|
||||
<!-- CONTENT (Panels werden lazy per fetch befüllt) -->
|
||||
@@ -35,9 +32,6 @@
|
||||
<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-arm2"></div>
|
||||
<div class="tab-panel" id="tab-elbow"></div>
|
||||
<div class="tab-panel" id="tab-hand"></div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.calib-body -->
|
||||
|
||||
@@ -22,9 +22,6 @@ 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 === 'arm2') initArm('arm2');
|
||||
else if (tab === 'elbow') initArm('elbow');
|
||||
else if (tab === 'hand') initArm('hand');
|
||||
|
||||
} catch (err) {
|
||||
document.getElementById('tab-' + tab).innerHTML =
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
<div class="sections">
|
||||
|
||||
<div class="section full">
|
||||
<h2>Arm2 – Z-Achse <span class="status-badge open">offen</span></h2>
|
||||
<div class="info-grid" style="margin-top:14px">
|
||||
<span class="info-label">Ziel</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Z-Rotationsachse von Arm2 bestimmen: drei Positionen aufnehmen, Umkreismittelpunkte berechnen.
|
||||
</span>
|
||||
<span class="info-label">Ablauf</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Board erkennen (Pos A) → Arm2 drehen → Board erkennen (Pos B) → Arm2 drehen → Board erkennen (Pos C)
|
||||
→ Viewer zeigt berechnete Rotationsachse (magenta)
|
||||
</span>
|
||||
<span class="info-label">Letzter Run</span>
|
||||
<span class="info-value" id="arm2-last-run">–</span>
|
||||
</div>
|
||||
<div class="controls" style="margin-top:16px">
|
||||
<button id="btn-arm2-run">Board erkennen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Aktionen ───────────────────────────────────────────────────────────── -->
|
||||
<div class="section full">
|
||||
<h2>Aktionen</h2>
|
||||
<div style="margin-top:14px;display:flex;align-items:center;gap:20px;flex-wrap:wrap">
|
||||
<button id="btn-arm2-ccw" style="font-size:18px;padding:6px 22px" title="Unterarm rauf">
|
||||
⤴ Rauf
|
||||
</button>
|
||||
<span style="color:var(--muted);font-size:11px">Roboter-Unterarm drehen (Schrittweite folgt)</span>
|
||||
<button id="btn-arm2-cw" style="font-size:18px;padding:6px 22px" title="Unterarm runter">
|
||||
Runter ⤵
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Ausgabe / Log</h2>
|
||||
<textarea id="log-arm2" readonly placeholder="(Ausgabe erscheint hier)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Board-Viewer</h2>
|
||||
<p style="font-size:12px;color:var(--muted);margin-bottom:10px">
|
||||
<strong>Pos A</strong> (Basis) · <strong>Pos B</strong> (orange) · <strong>Pos C</strong> (cyan) –
|
||||
alle drei Timestamps sind vorgewählt. Sobald Pos C gesetzt ist, wird die Rotationsachse berechnet.
|
||||
</p>
|
||||
<iframe
|
||||
id="arm2-viewer-frame"
|
||||
src="/boardViewer.html?defaults=abc"
|
||||
style="width:100%;height:740px;border:1px solid #334155;border-radius:6px;background:#0d0f13;display:block"
|
||||
title="Board-Viewer (Arm2)"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,42 +0,0 @@
|
||||
<div class="sections">
|
||||
|
||||
<div class="section full">
|
||||
<h2>Elbow – Rotation <span class="status-badge open">offen</span></h2>
|
||||
<div class="info-grid" style="margin-top:14px">
|
||||
<span class="info-label">Ziel</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Rotationsachse des Elbow-Gelenks bestimmen: drei Positionen aufnehmen, Umkreismittelpunkte berechnen.
|
||||
</span>
|
||||
<span class="info-label">Ablauf</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Board erkennen (Pos A) → Elbow drehen → Board erkennen (Pos B) → Elbow drehen → Board erkennen (Pos C)
|
||||
→ Viewer zeigt berechnete Rotationsachse (magenta)
|
||||
</span>
|
||||
<span class="info-label">Letzter Run</span>
|
||||
<span class="info-value" id="elbow-last-run">–</span>
|
||||
</div>
|
||||
<div class="controls" style="margin-top:16px">
|
||||
<button id="btn-elbow-run">Board erkennen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Ausgabe / Log</h2>
|
||||
<textarea id="log-elbow" readonly placeholder="(Ausgabe erscheint hier)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Board-Viewer</h2>
|
||||
<p style="font-size:12px;color:var(--muted);margin-bottom:10px">
|
||||
<strong>Pos A</strong> (Basis) · <strong>Pos B</strong> (orange) · <strong>Pos C</strong> (cyan) –
|
||||
alle drei Timestamps sind vorgewählt. Sobald Pos C gesetzt ist, wird die Rotationsachse berechnet.
|
||||
</p>
|
||||
<iframe
|
||||
id="elbow-viewer-frame"
|
||||
src="/boardViewer.html?defaults=abc"
|
||||
style="width:100%;height:740px;border:1px solid #334155;border-radius:6px;background:#0d0f13;display:block"
|
||||
title="Board-Viewer (Elbow)"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -1,42 +0,0 @@
|
||||
<div class="sections">
|
||||
|
||||
<div class="section full">
|
||||
<h2>Hand – Rotation <span class="status-badge open">offen</span></h2>
|
||||
<div class="info-grid" style="margin-top:14px">
|
||||
<span class="info-label">Ziel</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Rotationsachse des Hand-Gelenks bestimmen: drei Positionen aufnehmen, Umkreismittelpunkte berechnen.
|
||||
</span>
|
||||
<span class="info-label">Ablauf</span>
|
||||
<span class="info-value" style="font-family:inherit;font-size:13px;color:var(--muted)">
|
||||
Board erkennen (Pos A) → Hand drehen → Board erkennen (Pos B) → Hand drehen → Board erkennen (Pos C)
|
||||
→ Viewer zeigt berechnete Rotationsachse (magenta)
|
||||
</span>
|
||||
<span class="info-label">Letzter Run</span>
|
||||
<span class="info-value" id="hand-last-run">–</span>
|
||||
</div>
|
||||
<div class="controls" style="margin-top:16px">
|
||||
<button id="btn-hand-run">Board erkennen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Ausgabe / Log</h2>
|
||||
<textarea id="log-hand" readonly placeholder="(Ausgabe erscheint hier)"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="section full">
|
||||
<h2>Board-Viewer</h2>
|
||||
<p style="font-size:12px;color:var(--muted);margin-bottom:10px">
|
||||
<strong>Pos A</strong> (Basis) · <strong>Pos B</strong> (orange) · <strong>Pos C</strong> (cyan) –
|
||||
alle drei Timestamps sind vorgewählt. Sobald Pos C gesetzt ist, wird die Rotationsachse berechnet.
|
||||
</p>
|
||||
<iframe
|
||||
id="hand-viewer-frame"
|
||||
src="/boardViewer.html?defaults=abc"
|
||||
style="width:100%;height:740px;border:1px solid #334155;border-radius:6px;background:#0d0f13;display:block"
|
||||
title="Board-Viewer (Hand)"
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user