HumansXDocumentation Center 日本語Website ↗
Knowledge Base › Wuji Glove › Manual

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

ItemRequirement
Wuji Glove1
ConnectionEthernet

This example sets an SDK-side parameter only. It writes no firmware, and the glove doesn’t move.

Software

ItemRequirement
Operating systemLinux or a Mac with Apple silicon, meeting the system requirements
Wuji SDKv2026.8.31 or later
Python3.10 or later
C toolchainCMake 3.16 or later, plus the extracted SDK package
URDF fileA readable hand model whose handedness matches the glove
  • Python

  • C

pip install wuji-sdk
LIB="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:

StepResult
1. Connect and read hand_model_pathRaises WujiException: Parameter not set: calibration.hand_model_path
2. Read back after set(path) in the same sessionReturns the path just set
3. disconnect_all(), then connect() againReads Parameter not set once more
4. Check the parameter file on diskThe 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:

ValueMeaningurdf_source_path
builtin_defaultBuilt-in generic modelNone
overrideA path was named explicitlyThat path
Calibration outputThe serial number was calibrated, using ~/.wuji/sdk/models/<serial number>_hand.urdfThat 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:

ComparisonResult
Peak joint-angle difference1.0140 rad = 58.10°
Mean joint-angle difference0.1395 rad = 7.99°
Peak skeleton joint position difference39.25 mm

Peak difference broken out per finger:

FingerPeak difference
Thumb58.10°
Index20.78°
Middle20.34°
Ring31.26°
Pinky24.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 pointBehaviorWhat to do
Currently on the default SDK userset fails with an error naming the fixCall switch_user() first
URDF path unreadableThe example checks before connecting and exits with code 2Use an absolute path, and expand ~ yourself
get before any setRaises Parameter not set: calibration.hand_model_pathWrap it in try/except
ReconnectThe setting is gone and the default source returnsCall set again after connecting
URDF handedness doesn’t match the gloveSolving proceeds with no error and a systematic offsetCheck whether robot name in the URDF reads left_hand or right_hand
Device already in useConnecting raises Session already exists on deviceWait 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.