Skip to content
Get SDK Access

Hand Solver Example (Python)

rgmp_hand_solver.py does the same job as the rkk-hand-solver sidecar shipped in the SDK, but in plain Python: it reads the driver’s RGMP stream, solves every connected glove into a 26-joint hand, and re-emits the result as RGMP on a port of its own. Any RGMP client can then read solved hands without carrying a solver.

flowchart LR
    Driver["SDK Driver<br/>rokoko-sdk"]
    Solver["rgmp_hand_solver.py<br/>(solve)"]
    App["Your application"]

    Driver -->|"TCP :12276 (RGMP)"| Solver
    Solver -->|"TCP :12277 (RGMP)"| App

The ports, flags, and wire format match the binary, so the two are interchangeable. Where the binary is the one you ship against, this example is the one you read and change: it is the whole solve — quaternion math, rest pose, finger aiming, hinge constraints, forward kinematics — in readable Python, with no third-party packages and no rebuild between edits.

Navigate to $ROKOKO_SDK_HOME/examples/python in a terminal. By default the solver starts the driver itself, so this is the whole setup:

Terminal window
python3 rgmp_hand_solver.py

If something else already manages the driver, point it at the running one instead:

Terminal window
python3 rgmp_hand_solver.py --no-driver

Then read the solved hands with any RGMP client — the same one you already use against the driver, aimed at the solver’s port:

Terminal window
python3 rgmp_stream.py --host 127.0.0.1 --port 12277
FlagDefaultPurpose
--listen127.0.0.1:12277Address to serve solved hands on.
--rgmp-addr127.0.0.1:12276The driver’s RGMP endpoint to consume.
--emitlocalWhat each hand carries: local, world, or both.
--hand-width0Knuckle span in meters, widening the rest pose’s finger fan. 0 uses the model default.
--no-hinges—Disable the hinge constraint, letting non-thumb fingers splay and twist off-axis.
--no-driver—Assume the driver is already running.
--driver-pathrokoko-sdkDriver executable to launch.
--driver-arg—Extra argument passed to the driver, after the defaults. Repeatable.
--driver-quiet—Discard the driver’s output instead of sharing the solver’s.
--driver-timeout15Seconds to wait for the driver to open its RGMP port.

The four --driver-* flags are rejected, not ignored, when combined with --no-driver — silently dropping them would hide a mistake.

The last two flags have no counterpart in the binary. They exist because this is the copy you tune: --hand-width and --no-hinges are the two knobs worth reaching for first when a resting pose or a finger’s motion doesn’t match your rig.

One synthetic device per solved hand, with a device_type of solved_hand, identical in shape to the binary’s output — including the rest pose sent once in static_data and the --emit group choice:

--emitGroupPer frame, per hand
local (default)joints_local444 bytes
worldjoints_world744 bytes
bothboth of the above1188 bytes

What the solver emits in the hand solving guide describes the skeleton, the group layouts, and the timestamp and disconnect behavior in full; all of it applies here.

Values arrive in the glove’s own right-handed sensor frame (+X right, +Y toward the wrist, +Z down). There is no conversion to Y-up, no handedness flip, and no yaw offset — rebasing onto your engine’s frame stays a single rotation you apply to the whole skeleton. See Two things that trip people up.

Same protocol, same defaults, but not the same coverage. Pick the binary for anything you ship:

  • No --emit-openxr. The joints_openxr group is the binary’s only. That makes this solver unusable as the source for the Isaac Lab glove device, which consumes exactly that group.
  • Pure Python performance. The solve is fast enough to keep up with two gloves on a normal machine, but it is interpreted math on a single thread — it has none of the binary’s headroom.
  • The binary’s early-release caveats apply here too: calibration is not applied, sensor validity is read but not acted on, and Coil Pro world anchoring is the least exercised path. See the caveats in the guide.

Unlike the other Python examples, this one is a small package tree rather than a single script, so it isn’t reproduced inline here — read it in $ROKOKO_SDK_HOME/examples/python. It splits into three layers, each usable on its own:

rgmp_hand_solver.py the CLI: arguments in, hand_solver.run() out
hand_solver/ solve -> emit -> fan out
solver_core/ the solve itself, standing apart from any transport
rgmp_core/ the wire protocol: framing, definitions, layouts, client

solver_core/ is the part to read if you are implementing the solve yourself in another language — it has no transport code in it at all:

ModuleRole
math.pyVector and quaternion primitives
transform.pyThe bone transform hierarchy
skeleton.pyThe 26-joint skeleton and its rest pose, loaded from data/hand-skeleton.json
finger_poser.pyOne frame of sensor quaternions in, a posed skeleton out
smartglove.pyRecognizing and decoding a glove’s sensors from an RGMP definition

hand_solver/ is everything around the solve — the parts you would replace to put the solver somewhere other than a TCP port:

ModuleRole
upstream.pyReads the driver’s stream, solves it, publishes the result
pipeline.pyRGMP events in, framed solved-hand messages out. No I/O
emit.pyDescribing and encoding a solved hand as an RGMP device
hub.pyFan-out: one solved frame in, every connected consumer out
server.pyThe listener and accept loop
driver.pyLaunching and supervising rokoko-sdk

A frame that cannot be decoded, or whose solve produces a non-finite joint, is dropped rather than emitted, and each cause is logged once per device rather than once per frame. The solve is stateless, so the next good frame recovers.