Setting a Custom Hand Model
SDK Practice
Overview
Online finger joint angles and skeleton solving both depend on a hand model URDF. By default that’s the built-in generic model, or the calibration output once you’ve calibrated. To swap in your own model temporarily, use hand_model_path.
Two prerequisites trip people up. You must switch to a named SDK user first, because the call fails outright under the default user. And the setting lives only inside the current connection, so it’s gone after a reconnect.
This page tests both to the end and quantifies what swapping models does to solver output.
Matching example: examples/c/wuji_glove/2_set_hand_model_path.c. Python has no standalone example, but the --hand-model-path argument in the subscription examples uses the same API.
For API and field definitions, see Custom URDF Override.
Prerequisites
Hardware
| Item | Requirement |
|---|---|
| Wuji Glove | 1 |
| Connection | Ethernet |
This example sets an SDK-side parameter only. It writes no firmware, and the glove doesn’t move.
Software
| Item | Requirement |
|---|---|
| Operating system | Linux or a Mac with Apple silicon, meeting the system requirements |
| Wuji SDK | v2026.8.31 or later |
| Python | 3.10 or later |
| C toolchain | CMake 3.16 or later, plus the extracted SDK package |
| URDF file | A readable hand model whose handedness matches the glove |
Python
C
pip install wuji-sdkLIB="libwuji_sdk_c.so"
if [ "$(uname -s)" = "Darwin" ]; then LIB="libwuji_sdk_c.dylib"; fi
cmake -S examples/c/wuji_glove -B build/glove \
-DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
-DWUJI_SDK_LIB="${SDK}/lib/${LIB}"
cmake --build build/glove --target 2_set_hand_model_path Before You Start
Switch to a named SDK user. See SDK User Management. This is a hard requirement, not a suggestion.
Data Flow
Two Hard Constraints
One: The Default User Fails Outright
Calling this under the default user returns an error that states both the cause and the fix:
wuji_glove_set_hand_model_path failed: Unsupported operation: the default SDK
user does not support a custom hand_model_path: online IK always uses the
built-in default URDF. Switch to a named SDK user (SdkManager::switch_user)
before setting hand_model_path Under the default user, online solving always uses the built-in generic model, with no exceptions.
Two: The Setting Doesn’t Survive a Reconnect
The full sequence, all under one named user:
| Step | Result |
|---|---|
1. Connect and read hand_model_path | Raises WujiException: Parameter not set: calibration.hand_model_path |
2. Read back after set(path) in the same session | Returns the path just set |
3. disconnect_all(), then connect() again | Reads Parameter not set once more |
| 4. Check the parameter file on disk | The modification time never changed |
So hand_model_path lives in session-scoped parameters and never reaches disk. Set it again on every connection, as a fixed step right after connecting.
When unset, get() raises rather than returning an empty string or None. Test for a custom hand model with try/except, not with a truthiness check.
Three Values for the URDF Source
The offline pipeline’s urdf_source property takes these values, and the online side follows the same rules:
| Value | Meaning | urdf_source_path |
|---|---|---|
builtin_default | Built-in generic model | None |
override | A path was named explicitly | That path |
| Calibration output | The serial number was calibrated, using ~/.wuji/sdk/models/<serial number>_hand.urdf | That file’s path |
How Much a Model Swap Changes
Taking the same 200 frames of real EMF pose data and solving twice, once on the built-in default model and once on a calibration output from a different glove, then comparing element by element:
| Comparison | Result |
|---|---|
| Peak joint-angle difference | 1.0140 rad = 58.10° |
| Mean joint-angle difference | 0.1395 rad = 7.99° |
| Peak skeleton joint position difference | 39.25 mm |
Peak difference broken out per finger:
| Finger | Peak difference |
|---|---|
| Thumb | 58.10° |
| Index | 20.78° |
| Middle | 20.34° |
| Ring | 31.26° |
| Pinky | 24.47° |
The thumb is the most sensitive. It has the most degrees of freedom, so geometry differences in the model show up there most clearly.
These numbers say the hand model isn’t an optional setting. With the wrong model, solver output looks normal and confidence stays respectable, yet the result sits half a joint away from the real gesture. Matching models matters most when you replay data someone else captured.
Two-Step Buffer Query on the C Side
wuji_glove_get_hand_model_path uses the familiar C two-step pattern, and the example wraps it cleanly enough to copy:
static int print_hand_model_path(struct WujiDevice *dev) {
/* Step one: pass NULL and 0 to ask how large a buffer you need.
* Success here returns ERR_BUFFER_TOO_SMALL, not OK */
size_t needed = 0;
WujiStatus st = wuji_glove_get_hand_model_path(dev, NULL, 0, &needed);
if (st != WUJI_STATUS_ERR_BUFFER_TOO_SMALL || needed == 0) {
fprintf(stderr, "size query failed: %s\n", wuji_last_error());
return -1;
}
/* Step two: allocate and fetch for real */
char *buf = (char *)malloc(needed);
if (!buf) return -1;
st = wuji_glove_get_hand_model_path(dev, buf, needed, &needed);
if (st != WUJI_STATUS_OK) {
fprintf(stderr, "get failed: %s\n", wuji_last_error());
free(buf);
return -1;
}
printf("Read back hand_model_path = %s\n", buf);
free(buf);
return 0;
} Step one succeeds with WUJI_STATUS_ERR_BUFFER_TOO_SMALL. Testing for == WUJI_STATUS_OK there sends you down the error path every time.
Integration Steps
Complete examples: Python callback example and C hand-model example. The following excerpts show the integration order.
Reuse imports, manager setup, C initialization, connection error handling, and callbacks from Python example and C example. Check the status of each C call. On failure, release existing resources and exit as shown in the examples.
1. Confirm the User and Check the URDF
A custom hand model requires a named SDK user. Before connecting, expand the path to an absolute path and confirm that the file is readable.
Python
C
from pathlib import Path
user = manager.current_user()
if user["is_default"]:
raise RuntimeError("Switch to a named SDK user first")
urdf = Path(path).expanduser().resolve()
if not urdf.is_file():
raise FileNotFoundError(urdf)WujiUserInfo user = {0};
wuji_current_user(&user);
bool is_default = user.is_default;
wuji_user_info_free(&user);
if (is_default) { wuji_shutdown(); return 2; }
if (access(urdf_path, R_OK) != 0) return 2; 2. Connect and Set the Hand Model
Set the path after each connection. After setting it, read it back to confirm the active path.
Python
C
glove = manager.connect(sn=serial_number, device_name="glove_ik")
glove.hand_model_path().set(str(urdf))
print(glove.hand_model_path().get())WujiStatus st = wuji_glove_set_hand_model_path(dev, urdf_path);
if (st != WUJI_STATUS_OK) {
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown();
return 1;
}
print_hand_model_path(dev); 3. Subscribe to Results From the New Model
Subscribe to joint angles after setting the model. Close the subscription and release the device in order on exit.
Python
C
sub = glove.hand_joint_angles().subscribe_with_callback(
lambda frame: print(len(frame.fingers)))
try:
time.sleep(10)
finally:
sub.close()
manager.disconnect_all()struct WujiSub *sub = NULL;
wuji_glove_subscribe_hand_joint_angles(dev, on_hand_joint_angles, &ctx, &sub);
/* Keep the main thread running for callbacks, then clean up below. */
wuji_sub_close(sub);
wuji_dev_disconnect(dev);
wuji_dev_release(dev);
wuji_shutdown(); Running It
./build/glove/2_set_hand_model_path /absolute/path/to/hand.urdf WG1KA00XXXXXXXXX The second argument is optional, and without it the example connects to the first glove it finds. On a shared network, pass the serial number explicitly.
Output under a named user:
SN=WG1KA00XXXXXXXXX Type=WujiGlove Address=192.168.1.100:50001
...
Connecting to WG1KA00XXXXXXXXX (192.168.1.100:50001)
Set hand_model_path = /path/to/custom_hand.urdf
Read back hand_model_path = /path/to/custom_hand.urdf
Subscribed to hand_joint_angles. Move the glove. Ctrl+C to stop.
seq=473345 frame_id=r_hand_emf_tx fingers=5 frame=1
finger[0] confidence=0.465 angles=[0.0571 -0.4224 -0.2648 -0.0216 0.1521]
finger[1] confidence=0.407 angles=[-0.1745 -0.1312 0.0087 0.0087 0.0000]
finger[2] confidence=0.398 angles=[-0.1745 0.3062 0.0087 0.0087 0.0000]
finger[3] confidence=0.440 angles=[-0.1745 0.5134 0.0087 0.0087 0.0000]
finger[4] confidence=0.470 angles=[-0.1745 0.5897 0.0087 0.0087 0.0000] Measured on a right-hand Wuji Glove resting on a desk, unworn, under a named SDK user.
The frame_id on hand_joint_angles is r_hand_emf_tx while hand_skeleton uses r_wrist, because the two streams reference different frames by design.
The fifth angle stays 0 on every finger but the thumb as alignment padding, unrelated to the hand model.
Notes
| Branch point | Behavior | What to do |
|---|---|---|
| Currently on the default SDK user | set fails with an error naming the fix | Call switch_user() first |
| URDF path unreadable | The example checks before connecting and exits with code 2 | Use an absolute path, and expand ~ yourself |
get before any set | Raises Parameter not set: calibration.hand_model_path | Wrap it in try/except |
| Reconnect | The setting is gone and the default source returns | Call set again after connecting |
| URDF handedness doesn’t match the glove | Solving proceeds with no error and a systematic offset | Check whether robot name in the URDF reads left_hand or right_hand |
| Device already in use | Connecting raises Session already exists on device | Wait for the previous session’s heartbeat to time out, then retry |
Mismatched handedness is the sneakiest of these. The SDK doesn’t validate it and still produces results with normal confidence.
Adapting It
Fold the Setting Into Your Connect Path
Because it doesn’t survive a reconnect, wrap connection and setting together:
def connect_with_model(manager, sn, urdf_path):
glove = manager.connect(sn=sn, device_name="glove_ik")
glove.hand_model_path().set(str(urdf_path))
return glove Route reconnect logic through the same function, or a reconnect quietly reverts to the default model.
Check Handedness
Glance at the URDF’s root node before setting it:
grep -o 'robot name="[^"]*"' hand.urdf | head -1 A right-hand glove needs right_hand. The SDK doesn’t validate this and won’t complain when it’s wrong.
Calibrate for a Model That Fits Your Hand
A custom path answers “which existing model” rather than “how accurate is it.” For a model that matches your own hand, run hand model calibration. Its output lands in ~/.wuji/sdk/models/<serial number>_hand.urdf and loads automatically afterward, with no setting needed.
Use urdf_path for Offline Replay
The offline pipeline offers the same capability as a constructor argument, free of session scope:
pipeline = WujiGlove.offline_pipeline(
sn="WG1KA00XXXXXXXXX", hand_side="right",
urdf_path="/path/to/hand.urdf",
) A missing path raises immediately rather than falling back to the default model.
Related
- SDK User Management: switch to a named user, the hard requirement for this page
- Hand Model Calibration: generate a URDF that matches your hand
- Replaying Data Offline: name a model when replaying, and the source of the comparison data above
- Subscribing With Callbacks: frame structures for
hand_joint_anglesandhand_skeleton
ドキュメント