Skip to content

Repository files navigation

Dexterity Lab

A local browser simulator for a five-finger robotic hand. Explore articulated motion, object contact, grasping, and camera-based hand control with Three.js, Rapier physics, and MediaPipe hand tracking.

The default robot uses the supplied original meshes, joint definitions, masses, and inertias: 27 links and 20 actuated joints. A simplified reference hand is also available. Camera inference and simulation run in the browser; the Python launcher only serves static files.

Dexterity Lab showing the original robotic hand in a thumb–index pinch pose, a 3D manipulation bench, and wrist and joint controls

The local simulator with the original robot and the Pinch preset selected in Manual mode.

Highlights

  • Articulated robotics: a 20-joint hand with URDF/SRDF import, joint limits, and six-degree-of-freedom wrist control.
  • Interactive physics: Rapier rigid-body simulation with object contact, friction, and hand self-collision filtering.
  • Camera-based control: local MediaPipe hand tracking, open-hand calibration, and palm-relative motion mapping.
  • Geometry-aware pinching: inverse kinematics coordinates the original robot's thumb and index fingertips.
  • Inspectable sessions: record and export joint targets, measured angles, wrist poses, and object trajectories as JSON.

Contents

Choose a setup

Run commands from the project root—the directory containing run.py and package.json.

Task Requirements Entry point
Use the included app Python 3 and a complete dist/ folder python3 run.py
Edit or rebuild the app Node.js 20.19+ in the 20.x line, or Node.js 22.12+; npm npm run dev
Run Python geometry audits Python 3.12+ and requirements.txt Project-local .venv

The Python tools have been checked with Python 3.13.3. Use a browser with WebGL2; browser verification has used Chromium/Chrome. Camera control additionally requires a webcam, camera permission, and a secure context such as localhost or HTTPS.

A complete bundled build needs no Python packages, Node.js installation, API key, or external inference service. Development dependency installation needs registry access; asset preparation may download the camera model if missing. Once assets are present, the app runs locally without a network connection.

Quick start

On macOS or Linux:

python3 run.py

On Windows:

py -3 run.py

Open http://localhost:8000/ if the browser does not open automatically. Keep the terminal running and press Ctrl+C to stop the server.

Start-Mac.command and Start-Windows.bat launch the same app using the system Python command. A virtual environment is optional for launching.

Launcher options

python3 run.py --port 8001
python3 run.py --no-browser
python3 run.py --help

Use py -3 instead of python3 on Windows. The server binds to 127.0.0.1, serves dist/, and disables caching to avoid stale files after rebuilding. Do not open index.html directly with a file:// URL.

Source edits do not update the bundled app automatically. Use the development server while editing, or rebuild dist/ before refreshing a page served by run.py.

Development setup

Install the locked JavaScript dependencies, prepare assets, and start Vite:

npm ci
npm run assets
npm run dev

Open the URL printed by Vite, normally http://127.0.0.1:5173/. Vite reloads source changes during development. A Python environment is not required for this workflow.

npm run assets:

  • Restores public/robot/ from bundled dist/robot/ when the source robot assets are missing.
  • Restores the camera model from dist/models/ when available, otherwise downloads it.
  • Copies MediaPipe's WASM runtime from node_modules/ into public/wasm/.

Some packaged copies keep large assets only in dist/, so prepare assets before the first development build. If both public/robot/ and dist/robot/ are missing, restore the supplied robot assets from the project package; the script cannot download them.

Build and preview

npm run build
npm run preview

Preview serves the generated build, normally at http://127.0.0.1:4173/; use the URL printed in the terminal. Alternatively, python3 run.py serves the same dist/ directory.

Both dev and build generate public/tracking-worker.js from src/tracking-worker.js. Edit the source worker, not the generated file. After changing worker code during development, restart npm run dev and reload the page to regenerate the worker bundle.

Command Purpose
npm ci Install dependencies from package-lock.json
npm run assets Prepare robot, camera-model, and WASM assets
npm run dev Build the camera worker and start the development server
npm run build Build the camera worker and production app into dist/
npm run preview Serve the existing production build
npm test Run the automated JavaScript tests
npm run format Format project JavaScript with Prettier
npm run format:check Check JavaScript formatting without changing files

Python environment

The optional audit and proxy-generation scripts use NumPy and SciPy. Their pinned versions in requirements.txt require Python 3.12 or later. Use an interpreter meeting that requirement when creating the environment.

macOS / Linux

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python -m pip check

Windows PowerShell

py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
python -m pip check

If PowerShell blocks activation, use the environment directly:

.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe scripts/audit-urdf.py public/robot

For an existing .venv, activate it and install requirements as needed; do not recreate it each time. Run deactivate to leave it. In your editor, select .venv/bin/python on macOS/Linux or .venv\Scripts\python.exe on Windows. The environment and Python cache files are ignored by Git.

Python tools

After activating the environment and preparing robot assets:

python scripts/audit-urdf.py public/robot

The audit prints a JSON report without modifying inputs. It checks URDF/SRDF structure, masses, inertias, limits, mesh references, and collision exclusions.

The STL audit expects the original export directory layout, containing printed_parts_hi_res/, sim_visual/, and sim_collision/:

python scripts/audit-stl.py /path/to/original/export

Replace the example path with your export directory. Passing the reorganized public/robot/ directory does not inspect its meshes because its directory names differ.

python scripts/build-supplied-proxy.py regenerates and overwrites the legacy public/models/supplied-hand.json. It is not needed for the default original-mesh robot. Archived generators in public/reference/ also require original CAD source modules that are not supplied by requirements.txt.

Using the simulator

First session

  1. Launch the app and wait for Original hand · supplied meshes to load.
  2. In Manual, try Open, Pinch, Power, Fist, and Spread.
  3. Expand a finger's joint controls to adjust it individually. Wrist position uses millimeters; wrist rotation and joint sliders use degrees.
  4. Select Run grasp demo to watch an approach, close, lift, and release sequence with a 40 mm, 25 g cube. This switches to Manual, resets the world, and replaces the scene's objects.
  5. Select Reset world to restore the current robot and default objects. Reset also clears camera calibration and stops an active recording.

The original robot has its own presets. Pinch brings the thumb/index pads together; it does not find an object or move the wrist toward one. Grasping depends on object placement, contact, and friction. Motors obey joint limits, and contacts can stop a finger before its commanded target.

Scene and physics controls

Control Action
Drag the background Orbit the view
Scroll Zoom
Right-drag Pan
Shift + drag an object Pull it with a spring force
Perspective / Top / Front Change the viewing angle
Cube / Sphere / Cylinder / Prism Spawn an object using the selected size and mass
Place cube under hand Add a cube beneath the current wrist
Gravity / Contact friction Adjust the physics environment
Hand self-collision Toggle hand-to-hand link contact; other collision types remain active
Show collision shapes Display the physics geometry

Self-collision starts on for the original hand and off for the approximate reference hand. The active pair count reflects self-contact; the scene's Hand contacts metric counts hand-to-object contact.

Keyboard shortcuts

Key Action
Space Pause or resume simulation
R Reset the world
H Recenter the wrist and clear camera calibration

Shortcuts are ignored while an input/select is focused or the quick guide is open.

Camera control and pinching

  1. Select Camera → Enable camera and grant camera access.
  2. Choose the hand being tracked. Left → right robot mirrors left-hand control onto the right robot; it does not load a left robot model.
  3. Hold your open palm toward the camera with the wrist and all fingertips visible. Extend your thumb and separate it from your index finger.
  4. Press Calibrate open hand and hold still while six stable samples are collected. Calibration times out after 30 seconds; movement, curled fingers, or a pinching thumb can restart or prevent sampling.
  5. Move and rotate your palm gently, then bend and spread your fingers. Bring thumb and index fingertips together to pinch; separate them to reopen.
  6. Use Clutch to hold the current commands while repositioning your real hand. Press it again with your hand visible to resume from the current wrist position. Physics continues while clutched; use Pause to freeze simulation time.

On the original robot, pinch assistance uses tracked fingertip separation and robot joint geometry to coordinate the thumb and index finger. Wrist movement positions this pinch workspace in the world. This compensates for different human/robot proportions; it does not attach objects or bypass collision physics. Explicit imported retarget maps do not receive built-in pinch assistance.

Camera tuning

Setting Default Effect
Motion gain 1.0× Scales wrist translation
Thumb closing gain 1.5× Scales thumb movement relative to the calibrated open pose
Smoothing 70 ms Higher values reduce jitter but add response delay

Increase thumb gain if the thumb bends correctly but stops short; decrease it if it closes too early. Near a pinch, fingertip alignment takes priority over independent thumb/index angles. Keep the camera fixed after calibration. Recalibrate after changing the tracked hand, resetting/recentering, switching robots, or updating the app.

Slow means frames arrive with noticeable delay; it differs from No hand. Use good lighting, keep fingertips visible, and move slowly. Tracking loss holds the last commands; reacquisition reanchors the wrist to avoid a jump. Stopping the camera, switching to Manual, or hiding the tab releases the camera and clears calibration. Camera frames and landmarks remain in the browser.

Recording and export

  1. Select Record, then interact with the hand.
  2. Select Stop when finished.
  3. Select Export JSON to download the session.

The export includes robot/joint metadata, collision policy, physics settings, joint targets and measured angles, wrist pose, object trajectories, and contact counts. Its schema is dexterity-lab/1; distances are meters, angles radians, masses kilograms, time seconds, and quaternions use [x, y, z, w] order.

Recording samples at most about 30 times per simulated second and stops at 18,000 samples. Rendering performance can reduce the sampling rate. A new recording replaces the previous in-memory recording; reloading loses unexported data. There is no built-in session replay/import interface.

Importing a robot

Select + Import URDF folder and choose a directory containing the URDF and every referenced mesh. The bundled layout is:

robot/
├── hand_right_printed.urdf
├── hand_right_printed.srdf
├── meshes/
│   ├── visual/       # Referenced STL or OBJ files
│   └── collision/    # Referenced collision meshes
├── retarget-map.json       # Optional
└── simulation-frame.json   # Optional

With multiple URDFs, the importer selects a uniquely named right-hand URDF; otherwise provide one URDF. Prefer public/robot/ (after asset preparation) or dist/robot/ over the archived raw export: bundled copies contain corrected paths, mesh units, and SRDF names.

Supported imports include STL/OBJ, box/sphere/cylinder shapes, fixed/revolute/continuous joints, origins and mesh scales, material colors, joint limits, mass, center of mass, and full inertia. Continuous controls are restricted to ±π. Collision meshes become convex hulls; concave geometry needs externally prepared convex decomposition.

A matching SRDF supplies self-collision exclusions. Without one, only directly connected links are excluded. Mismatched robot names, unknown links, conflicting matching SRDFs, missing/ambiguous meshes, and unsupported constructs cause import errors. Expand Xacro first. DAE, textures, mimic joints, prismatic/floating/planar joints, and tendon transmissions are unsupported.

Custom joint mapping

For different joint names or axis conventions, include retarget-map.json:

{
  "index_pip": { "joint": "actual_index_joint", "sign": 1, "offset": 0 },
  "thumb_cmc_abd": { "joint": "actual_thumb_joint", "sign": -1, "offset": 0 }
}

Keys are tracker joint names; joint names the destination URDF joint. The mapping applies sourceAngle * sign + offset, with offsets in radians. These signs are examples, not universal settings. Check joints in Manual before camera use.

Standard names include <finger>_mcp_abd, <finger>_mcp_flex, <finger>_pip, and <finger>_dip for index/middle/ring/pinky, plus thumb_cmc_abd, thumb_cmc_flex, thumb_mcp_flex, and thumb_ip_flex.

An optional simulation-frame.json sets the source-to-display Euler XYZ rotation in radians:

{ "rpy": [-1.57079632679, 0, -1.57079632679] }

The bundled hand20_printed_right has recognized display and control conventions. Other robots need their own mapping and pose validation; the grasp demo is not calibrated for arbitrary imports.

Tests and formatting

After development setup:

npm test
npm run format:check

Tests cover inertia, original/reference dynamics, collision filtering, joint limits, grasp/release, native Pinch contact, camera-pinch commands for both hands, palm-axis rotation, calibration, and tracking-loss recovery. Original-hand tests use real bundled URDF/SRDF and STL files from public/robot/, falling back to dist/robot/.

For per-joint idle-motion measurements:

node scripts/check-hand-stability.mjs

Format JavaScript with:

npm run format

Prettier uses .prettierrc.json. Source, scripts, tests, and JavaScript configuration are formatted; dependencies, the Python environment, dist/, copied WASM loaders, and the generated camera worker are excluded by .prettierignore.

Controller API

Once the app has loaded, window.dexterity is available in the browser console. Select Manual first so camera commands do not overwrite your commands.

const lab = window.dexterity;
lab.setPaused(true);
lab.setJointTargets({ index_pip: 0.9, index_dip: 0.5 }); // Radians
lab.setWrist([0, 0.16, 0]); // Meters; keep the current wrist orientation
lab.spawn('cube', [0, 0.2, 0.1], 0.04, 0.035); // Size in meters, mass in kg
const state = lab.advance(120); // One simulated second, only while paused
console.log(state.qMeasured);
lab.setPaused(false);

setWrist(position, quaternion) also accepts an optional [x, y, z, w] quaternion. advance() accepts 1–1200 whole steps. snapshot() reads the current state; reset() restores the scene. model, simulation, tracker, and scene expose current instances for development. Targets use the same physical motors as the UI; the API does not bypass contacts or limits.

Project structure

Path Responsibility
run.py, Start-* Serve and launch the prebuilt app
index.html, src/style.css, src/main.js UI, rendering, controls, and app lifecycle
src/model.js, src/urdf.js Reference model, native presets, and URDF/SRDF import
src/physics.js, src/inertia.js, src/collision-policy.js Dynamics, inertia transforms, and collision filtering
src/tracking.js, src/tracking-worker.js, src/tracking-quality.js Camera lifecycle, inference, and calibration quality
src/retarget.js, src/robot-control.js, src/kinematics.js Palm-relative tracking, robot mappings, and pinch alignment
public/robot/, public/models/, public/wasm/ Local runtime assets
public/reference/ Supplied documents, renders, and archived generators
scripts/ Asset preparation, worker build, audits, and stability checks
tests/ Automated tests and fixtures
reports/ Geometry audits and verification records
dist/ Generated app served by run.py; included in packaged builds
vite.config.js, package.json, package-lock.json Build configuration and JavaScript dependencies
requirements.txt, .venv/ Optional Python dependencies and local environment

Troubleshooting

Symptom What to check
dist/ is missing Restore a complete bundled build, or run npm ci, npm run assets, and npm run build. Robot assets must exist in public/robot/ or bundled dist/robot/.
Source changes are not visible run.py serves dist/. Use npm run dev, or rebuild and refresh.
/assets/index-….js returns 404 Restart run.py if the launcher changed, then hard-refresh: Cmd+Shift+R on macOS or Ctrl+Shift+R on Windows/Linux. Keep HTML and assets from the same build together.
Port 8000 is busy Run python3 run.py --port 8001 (Windows: py -3 run.py --port 8001).
python3 is not found Install Python or use your platform's Python command; Windows examples use py -3.
NumPy/SciPy cannot install Confirm Python 3.12+, activate .venv, and install requirements.txt with python -m pip.
Camera startup fails Use localhost/HTTPS, allow browser/OS camera access, and close other applications using the camera. Confirm model and WASM assets are available.
Calibration never completes Select the correct tracked hand, show all fingertips, separate thumb/index, straighten fingers, and hold still in good light.
Camera stops after changing tabs This is intentional. Enable it again and recalibrate.
Thumb/pinch alignment is wrong Load the latest build, select the original robot and correct tracked hand, recalibrate open, and compare with Manual → Pinch. Built-in assistance requires the recognized original model without a custom retarget map. Position the pinch over the object using the wrist.
Tracking says Slow or lags Check inference/frame delay. Try better lighting, slower movements, and browser hardware acceleration. Use the reference hand to compare rendering load. More smoothing adds delay.
Loading fails or meshes are missing Inspect the browser console, prepare assets before rebuilding, and confirm the complete robot folder is present.
Robot import fails or is misoriented Check mesh paths/scales, supported joint types, SRDF robot names, and simulation-frame.json. Expand Xacro before import.
A finger misses its slider target Contacts, joint limits, and motor speed limits can prevent or delay reaching targets. Inspect collision shapes and active pairs.

Physics and tracking limits

  • Physics: Rapier 0.19.3, fixed 120 Hz stepping, a kinematic wrist, dynamic links, and continuous collision detection. Solver iterations are 32 plus 8 additional hand iterations for the original robot, and 12 plus 4 for the reference.
  • Motion: wrist targets are rate-limited to 0.65 m/s and 4 rad/s. Joint target speed is the smaller of 3.5 rad/s and the imported velocity limit. Contact impulses can produce higher instantaneous body velocities.
  • Motors: ideal force-based position servos. URDF effort limits are metadata, not hard torque caps. Tendons, gearbox dynamics, saturation, and compliance are not modeled.
  • Geometry: supplied visual detail and inertia are retained; collision meshes use convex hulls. The original hand filters 31 SRDF exclusions plus directly connected pairs. Other hand contacts remain active.
  • Tracking: MediaPipe Tasks Vision 0.10.32 uses the bundled Hand Landmarker float16 model in a classic Web Worker, with GPU inference and CPU fallback. It provides 21 landmarks, not a dense surface or reliable absolute depth. Wrist depth is inferred from image scale.
  • Accuracy: occlusion, foreshortening, finite solver tolerances, and differing human/robot proportions limit alignment. The full configuration space and arbitrary grasps are not validated. On slow machines, simulated time can progress more slowly than real time.

Automated and browser checks do not establish performance with your actual camera or physical robot. This is an interactive simulation workspace, not hardware validation or an exact digital twin.

References and notices

Reports describe the versions and fixtures under which they were produced. The stability report supersedes earlier grasp/collision measurements; historical reports do not replace current tests. Supplied robot/reference assets are not relicensed by this README.

About

Browser-based robotic hand simulator featuring camera-based hand tracking, physics-driven grasping, and inverse-kinematics pinch control. Built with Three.js, Rapier, and MediaPipe.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages