> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Setting a Custom Hand Model

## 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 where hand models come from and how they're stored, see [Calibration and Configuration](/en/docs/wuji-glove/calibration). This page covers only how to run it and what the measurements show.

## 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 | Ubuntu 22.04 LTS or later |
| 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 |

<Tabs>
  <Tab title="Python">
    ```bash theme={null}
    pip install wuji-sdk
    ```
  </Tab>

  <Tab title="C">
    ```bash theme={null}
    cmake -S examples/c/wuji_glove -B build/glove \
      -DWUJI_SDK_INCLUDE_DIR="${SDK}/include" \
      -DWUJI_SDK_LIB="${SDK}/lib/libwuji_sdk_c.so"
    cmake --build build/glove --target 2_set_hand_model_path
    ```
  </Tab>
</Tabs>

### Before You Start

**Switch to a named SDK user.** See [SDK User Management](/en/docs/wuji-sdk/sdk-practice/user-management/). This is a hard requirement, not a suggestion.

## Data Flow

```mermaid theme={null}
flowchart TD
    USER["named SDK user<br/>the default user is rejected"]
    CONN["connect(sn=...)<br/>open a session"]
    SET["hand_model_path().set(path)<br/>write a session parameter"]
    GET["hand_model_path().get()<br/>read back to verify"]
    IK["online solver reloads the model"]
    ANG["hand_joint_angles"]
    SKE["hand_skeleton"]
    TIP["tip_poses"]
    GONE["disconnect<br/>the setting is gone"]

    USER --> CONN
    CONN --> SET
    SET --> GET
    SET --> IK
    IK --> ANG
    IK --> SKE
    IK --> TIP
    CONN --> GONE
```

## 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:

```text theme={null}
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.

<Warning>
  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.
</Warning>

## 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.

## Runtime Handling

| 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.

## 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:

```c theme={null}
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

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from pathlib import Path
    from wuji_sdk import SdkManager, DeviceType, WujiException

    manager = SdkManager.instance()

    # 1. Confirm you're not on the default user, or step 4 will fail
    user = manager.current_user()
    if user["is_default"]:
        raise RuntimeError("A custom hand model needs a named SDK user. Call switch_user() first")
    print(f"Current user: {user['display_name']}")

    # 2. Verify the URDF is readable. Use an absolute path
    urdf = Path("~/.wuji/sdk/models/my_hand.urdf").expanduser().resolve()
    if not urdf.is_file():
        raise FileNotFoundError(urdf)

    # 3. Connect
    devices = [d for d in manager.scan() if d.device_type == DeviceType.WujiGlove]
    glove = manager.connect(sn=devices[0].sn, device_name="glove_ik")

    # 4. Set it. Repeat after every connection, since it doesn't survive one
    glove.hand_model_path().set(str(urdf))

    # 5. Read back to verify
    print(f"Active hand model: {glove.hand_model_path().get()}")

    # 6. Joint angles from here on come from the new model
    sub = glove.hand_joint_angles().subscribe_with_callback(
        lambda f: print(f"conf={[round(x.confidence, 3) for x in f.fingers]}")
    )

    # get raises when unset, so test for a custom model this way
    try:
        current_model = glove.hand_model_path().get()
    except WujiException:
        current_model = None
    ```
  </Tab>

  <Tab title="C">
    ```c theme={null}
    #include "wuji_sdk.h"

    /* 1. Initialize */
    WujiInitOptions opts = { .log_level = 3 };
    wuji_init(&opts);

    /* 2. Confirm you're not on the default SDK user */
    WujiUserInfo current = {0};
    if (wuji_current_user(&current) == WUJI_STATUS_OK) {
        if (current.is_default) {
            fprintf(stderr, "A custom hand model needs a named SDK user\n");
        }
        wuji_user_info_free(&current);
    }

    /* 3. Verify the URDF is readable. The example checks before connecting,
          so it doesn't fail only after a session is open */
    if (access(urdf_path, R_OK) != 0) {
        fprintf(stderr, "URDF is not readable: %s (%s)\n", urdf_path, strerror(errno));
        return 2;
    }

    /* 4. Discover and connect */
    WujiDiscovered *list = NULL; size_t n = 0;
    wuji_scan(&list, &n);
    WujiConnectTarget tgt = {
        .kind = WUJI_CONNECT_TARGET_KIND_SN,
        .value = list[idx].serial_number,
    };
    struct WujiDevice *dev = NULL;
    wuji_connect(&tgt, "glove_ik", NULL, &dev);
    wuji_discovered_free(list, n);

    /* 5. Set it. Repeat after every connection */
    if (wuji_glove_set_hand_model_path(dev, urdf_path) != WUJI_STATUS_OK) {
        fprintf(stderr, "set failed: %s\n", wuji_last_error());
    }

    /* 6. Read back to verify, using the two-step helper above */
    print_hand_model_path(dev);

    /* 7. Subscribe. What arrives now comes from the new model */
    struct WujiSub *sub = NULL;
    ik_ctx_t ctx = {0};
    wuji_glove_subscribe_hand_joint_angles(dev, on_hand_joint_angles, &ctx, &sub);

    /* 8. Close the subscription first, then release the device */
    wuji_sub_close(sub);
    wuji_dev_disconnect(dev);
    wuji_dev_release(dev);
    wuji_shutdown();
    ```
  </Tab>
</Tabs>

## Running It

```bash theme={null}
./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:

```text theme={null}
  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.

## Adapting It

### Fold the Setting Into Your Connect Path

Because it doesn't survive a reconnect, wrap connection and setting together:

```python theme={null}
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:

```bash theme={null}
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:

```python theme={null}
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](/en/docs/wuji-sdk/sdk-practice/user-management/): switch to a named user, the hard requirement for this page
* [Hand Model Calibration](/en/docs/wuji-glove/sdk-practice/hand-model-calibration): generate a URDF that matches your hand
* [Replaying Data Offline](/en/docs/wuji-glove/sdk-practice/offline-replay): name a model when replaying, and the source of the comparison data above
* [Subscribing With Callbacks](/en/docs/wuji-glove/sdk-practice/subscribe-callback): frame structures for `hand_joint_angles` and `hand_skeleton`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.