Skip to content

License requirement

The functionality described requires a MANUS Bodypack or a MANUS license key with the SDK feature enabled.

SDK Client example

Introduction

The SDK Client example functions as a demonstration of all major SDK functions available through Python. It is a menu-driven console application that shows how to connect to MANUS Core, how to set up the coordinate system, and how to handle multiple data streams including gloves, ergonomics, landscape (device inventory), raw skeleton data, and processed skeleton animations. The example client is written in Python and demonstrates best practices for working with the MANUS SDK in an interactive environment.

Location: examples/sdk_client.py

Features:

  • Interactive menu system for exploring data
  • Glove data stream visualization
  • Skeleton setup and management with proper node/chain hierarchies

SDK Callbacks

All the data streams MANUS Core provides are structured as callbacks. A callback is a function that you pass to a CoreSdk_RegisterCallback... function. These functions are called on another thread when data becomes available. This way, applications do not need to poll the SDK to determine if there is new data.

It is good practice to register all the callbacks you require after initializing the SDK and before connecting to MANUS Core. You only need to register the callbacks which you intend to use; if you do not plan to use certain data streams, this could reduce network traffic from MANUS Core.

Raw Skeleton Callback

OnRawSkeletonCallback
def on_raw_skeleton_callback(self, stream_info):
    """Raw skeleton data from streaming source"""
    try:
        skeletons_count = stream_info.skeletonsCount

        for i in range(skeletons_count):
            # Get skeleton info
            info = ffi.new("RawSkeletonInfo*")
            result = lib.CoreSdk_GetRawSkeletonInfo(i, info)
            if result != SDKReturnCode.Success:
                continue

            # Get skeleton nodes
            nodes_array = ffi.new("SkeletonNode[]", info[0].nodesCount)
            result = lib.CoreSdk_GetRawSkeletonData(i, nodes_array, info[0].nodesCount)
            if result != SDKReturnCode.Success:
                continue

            # Cache by glove ID (callbacks may return one glove at a time)
            glove_id = info[0].gloveId
            self.raw_skeleton_data[glove_id] = {
                'nodes_count': info[0].nodesCount,
                'nodes': [self._convert_skeleton_node(nodes_array[j]) 
                         for j in range(info[0].nodesCount)],
                'side': info[0].side
            }
    except Exception as e:
        print(f"Error in raw skeleton callback: {e}")

A best practice for data received on another thread is to save the data to a thread-safe location and process it on your own thread. This way, you do not block the callback thread, which could lead to delayed data transfer.

Ergonomics Callback

OnErgonomicsCallback
def on_ergonomics_callback(self, stream_info):
    """Finger joint angles and bend information"""
    try:
        for i in range(stream_info.dataCount):
            ergo_data = stream_info.data[i]
            if ergo_data.isUserID:
                continue  # Skip user IDs, use glove IDs only

            glove_id = ergo_data.id
            # Convert CFFI data to Python dict for persistence
            self.ergonomics_data[glove_id] = {
                'id': ergo_data.id,
                'isUserID': ergo_data.isUserID,
                'data': [ergo_data.data[j] for j in range(119)]  # Copy all values
            }
    except Exception as e:
        print(f"Error in ergonomics callback: {e}")

Landscape Callback

OnLandscapeCallback
def on_landscape_callback(self, landscape_info):
    """System overview (devices, users, configuration)"""
    try:
        # Deep copy entire landscape structure
        self.landscape_data = self._convert_landscape_to_dict(landscape_info)
    except Exception as e:
        print(f"Error in landscape callback: {e}")

Raw Device Data Callback

OnRawDeviceDataCallback
def on_raw_device_data_callback(self, raw_device_info):
    """Raw sensor data from connected devices (accelerometers, gyros, etc.)"""
    try:
        raw_device_data_count = raw_device_info.rawDeviceDataCount

        for i in range(raw_device_data_count):
            raw_device = ffi.new("RawDeviceData*")
            result = lib.CoreSdk_GetRawDeviceData(i, raw_device)
            if result != SDKReturnCode.Success:
                continue

            device_id = raw_device.id
            sensor_count = raw_device.sensorCount

            # Extract sensor transforms (position, rotation, scale)
            sensors = []
            for sensor_idx in range(sensor_count):
                sensor_transform = raw_device.sensorData[sensor_idx]
                sensors.append({
                    'position': (sensor_transform.position.x, 
                                sensor_transform.position.y,
                                sensor_transform.position.z),
                    'rotation': (sensor_transform.rotation.w,
                                sensor_transform.rotation.x,
                                sensor_transform.rotation.y,
                                sensor_transform.rotation.z)
                })

            # Cache device data by device ID
            self.raw_device_data[device_id] = {
                'id': device_id,
                'sensor_count': sensor_count,
                'sensors': sensors,
                'device_rotation': (raw_device.rotation.w,
                                  raw_device.rotation.x,
                                  raw_device.rotation.y,
                                  raw_device.rotation.z)
            }
    except Exception as e:
        print(f"Error in raw device data callback: {e}")

This callback provides access to raw sensor data including accelerometers, gyroscopes, and other motion sensors from connected Manus devices. Each sensor's position and rotation is available for custom processing.

Skeleton Callback

OnSkeletonCallback
def on_skeleton_callback(self, stream_info):
    """Processed skeleton data with animation transforms"""
    try:
        skeletons_count = stream_info.skeletonsCount
        new_skeletons = []

        for i in range(skeletons_count):
            # Get skeleton info
            info = ffi.new("SkeletonInfo*")
            result = lib.CoreSdk_GetSkeletonInfo(i, info)
            if result != SDKReturnCode.Success:
                continue

            # Get skeleton nodes with transforms
            nodes_array = ffi.new("SkeletonNode[]", info.nodesCount)
            result = lib.CoreSdk_GetSkeletonData(i, nodes_array, info.nodesCount)
            if result != SDKReturnCode.Success:
                continue

            # Store skeleton data with metadata from setup
            skeleton_data = {
                'skeleton_id': info.id,
                'nodes_count': info.nodesCount,
                'nodes': [self._convert_skeleton_node(nodes_array[j]) 
                         for j in range(info.nodesCount)]
            }
            new_skeletons.append(skeleton_data)

        self.skeletons = new_skeletons
    except Exception as e:
        print(f"Error in skeleton callback: {e}")

Client Connection

There are three modes to interact with your glove devices: integrated, local, and remote. The integrated method talks directly to the gloves without needing a MANUS Core instance. The local and remote methods talk to a MANUS Core instance which then communicates with the gloves.

On startup, the choice is given between:

  • Core Integrated mode: The SDK is integrated into the client application.
  • Core Local mode: The SDK will connect to a MANUS Core running locally on this machine.
  • Core Remote mode: The SDK will search and connect to a MANUS Core instance on the network.

When using Remote, the network is scanned, and a list of available MANUS Core instances is returned.

After initialization and connection, the client displays an interactive main menu:

============================================================
MAIN MENU
============================================================
  [G] Gloves & Ergonomics Data
  [S] Skeleton Data
  [R] Raw Skeleton Data
  [D] Raw Device Data (Sensors)
  [L] Landscape Data
  [C] Glove Calibration
  [P] Pairing / Unpairing
  [K] Skeleton Management
  [Q] Quit
============================================================
Option Description Hotkey
[G] Display glove & ergonomics data G
[S] Display processed skeleton data S
[R] Display raw skeleton data R
[D] Display raw device sensor data D
[L] Display landscape (system overview) L
[C] Glove calibration menu C
[P] Pairing / Unpairing menu P
[K] Skeleton management (load/unload) K
[B] Back to main menu B
[H] Toggle left/right hand H
[D] Toggle compact/detailed view D
[U] Unload loaded skeleton U
[Q] Exit the application Q

Skeleton Management

The client includes skeleton setup and management functionality that allows you to create and load hand skeletons with proper node and chain hierarchies.

Loading a Test Skeleton

To load a hand skeleton:

  1. From the main menu, select [K] for Skeleton Management
  2. Select [L] to load a left-hand skeleton or [R] for right-hand
  3. The skeleton will be created with:
  • 1 root hand node
  • 20 finger joint nodes (5 fingers × 4 joints)
  • 6 chains (1 hand + 5 finger chains)

The skeleton will be fully animated with data from the connected gloves and you'll see the processed skeleton data in the [S] Skeleton Data view.

Unloading a Skeleton

From the Skeleton Management menu, select [U] to unload the currently loaded skeleton.

Helper Functions

_create_node_setup()

Create Node Setup
def _create_node_setup(self, node_id, parent_id, x, y, z, name=""):
    """Helper function to create and initialize a NodeSetup structure.

    Args:
        node_id: Unique identifier for this node
        parent_id: ID of the parent node in the hierarchy
        x, y, z: Position coordinates
        name: Optional name for the node

    Returns:
        NodeSetup cdata structure with all fields initialized
    """

Initializes a node with:

  • Position, rotation (identity), and scale (1.0)
  • Type set to Joint
  • No special settings (IK, Foot, etc.)
  • Name as UTF-8 string (up to 64 bytes)

_setup_hand_nodes()

Setup Hand Nodes
def _setup_hand_nodes(self, setup_index, side_enum):
    """Create nodes for a hand skeleton (1 root + 5 fingers × 4 joints).

    Args:
        setup_index: Skeleton setup index from CoreSdk_CreateSkeletonSetup
        side_enum: Side.Left or Side.Right

    Returns:
        True if successful, False otherwise

    Node Structure:
        Node 0: Hand (root)
        Nodes 1-4: Thumb metacarpal → distal
        Nodes 5-8: Index metacarpal → distal
        Nodes 9-12: Middle metacarpal → distal
        Nodes 13-16: Ring metacarpal → distal
        Nodes 17-20: Pinky metacarpal → distal
    """

Creates anatomically accurate node positions based on the C++ SDK sample with proper parent-child relationships.

_setup_hand_chains()

Setup Hand Chains
def _setup_hand_chains(self, setup_index, side_enum):
    """Create chains for a hand skeleton (1 hand + 5 finger chains).

    Args:
        setup_index: Skeleton setup index from CoreSdk_CreateSkeletonSetup
        side_enum: Side.Left or Side.Right

    Returns:
        True if successful, False otherwise

    Chain Structure:
        Chain 0: Hand (wrist) - contains all finger chain IDs
        Chain 1: Thumb - nodes 1-4
        Chain 2: Index - nodes 5-8
        Chain 3: Middle - nodes 9-12
        Chain 4: Ring - nodes 13-16
        Chain 5: Pinky - nodes 17-20

    Settings:
        - Hand motion: IMU (Inertial Measurement Unit)
        - All finger chains linked to hand chain
        - Leaf nodes disabled (extended to tips)
    """

Sets up proper chain hierarchies and links fingers to the hand chain.

Skeleton Data View

The Skeleton Data view displays processed skeleton information (animates with glove input after loading):

Compact View:

============================================================
SKELETON DATA (Processed)
============================================================
[D] Toggle detailed view  |  [B] Back to main menu
============================================================

Received 1 skeleton(s):

Skeleton #1:
  Skeleton ID: 1
  Node Count: 21
  Total Nodes: 21

Detailed View (toggle with [D]):

============================================================
SKELETON DATA (Processed)
============================================================
[D] Toggle detailed view  |  [B] Back to main menu
============================================================

Received 1 skeleton(s):

Skeleton #1:
  Skeleton ID: 1
  Total Nodes: 21
  All Nodes:
    Node  0 (Node_0                      ): (  0.000,   0.000,   0.000)
    Node  1 (Node_1                      ): (  0.025,  -0.025,   0.000)
    Node  2 (Node_2                      ): (  0.058,  -0.025,   0.000)
    Node  3 (Node_3                      ): (  0.080,  -0.020,   0.000)
    Node  4 (Node_4                      ): (  0.090,   0.000,   0.000)
    ... [16 more finger joint nodes]
    Node 20 (Node_20                     ): (  0.090,   0.030,   0.000)

Data Stream Views

Raw Skeleton Data

The Raw Skeleton view displays joint positions and rotations for one or both hands:

============================================================
RAW SKELETON DATA - LEFT HAND
============================================================
[H] Toggle hand  |  [D] Toggle view  |  [B] Back
============================================================

Left Hand Skeleton:
  Glove ID: 1234567899 (0x499602D3)
  Node Count: 25
  Total Nodes: 25

Controls:

  • [H]: Switch between left (glove ID 1) and right (glove ID 2) hand display
  • [D]: Toggle between compact (hand structure summary) and detailed view (all nodes with transforms)
  • [B]: Return to main menu

Detailed View example:

Raw Skeleton - Detailed View:

Left Hand Skeleton:
  Glove ID: 1234567899 (0x499602D3)
  Node Count: 25
  Total Nodes: 25
  All Nodes:
    Node  0 (L_Hand): (  0.000,   0.000,   0.000)
    Node  1 (L_Thumb_Metacarpal): (  0.025,  -0.025,   0.000)
    Node  2 (L_Thumb_Proximal): (  0.058,  -0.025,   0.000)
    Node  3 (L_Thumb_Intermediate): (  0.080,  -0.020,   0.000)
    Node  4 (L_Thumb_Distal): (  0.090,   0.000,   0.000)
    ... [21 more nodes with positions and rotations]
    Node 24 (L_Pinky_Distal): (  0.090,   0.030,   0.000)

Ergonomics Data

The Ergonomics view displays finger joint bend and spread angles from connected gloves:

============================================================
GLOVES & ERGONOMICS DATA
============================================================
Haptics (HOLD): [1-5] Left (pinky-thumb) [6-0] Right (thumb-pinky)
============================================================

Left Glove:
  Glove ID: 2941338881 (0xAF514501)
  Joint Angles (degrees):
  Thumb : Spread=-36.68°  MCP= 44.20°  PIP=  0.86°  DIP= 30.91°
  Index : Spread= 50.52°  MCP=-76.32°  PIP= 53.92°  DIP=113.67°
  Middle: Spread= 49.25°  MCP=-49.85°  PIP= 63.82°  DIP= 89.94°
  Ring  : Spread= 10.89°  MCP=-30.96°  PIP=  1.79°  DIP= 60.08°
  Pinky : Spread= 10.44°  MCP=-25.79°  PIP=  0.62°  DIP= 15.35°

Right Glove:
  Glove ID: 2941338882 (0xAF514502)
  Joint Angles (degrees):
  Thumb : Spread= 35.12°  MCP= 42.85°  PIP=  1.23°  DIP= 32.15°
  Index : Spread= 48.67°  MCP=-75.45°  PIP= 55.33°  DIP=115.22°
  Middle: Spread= 47.89°  MCP=-48.92°  PIP= 64.15°  DIP= 91.28°
  Ring  : Spread= 12.34°  MCP=-32.11°  PIP=  2.56°  DIP= 61.45°
  Pinky : Spread=  9.78°  MCP=-26.34°  PIP=  0.89°  DIP= 16.72°

Data Explanation:

  • Glove ID: Hardware identifier (shown in decimal and hexadecimal)
  • Spread: Finger abduction/adduction angle from palm center (negative = towards palm)
  • MCP: Metacarpophalangeal joint bend (knuckle) in degrees
  • PIP: Proximal interphalangeal joint bend (middle joint) in degrees
  • DIP: Distal interphalangeal joint bend (tip joint) in degrees

All angles are in degrees with real-time updates at ~100 Hz from connected gloves.

Controls:

  • [B]: Return to main menu

Landscape Data

The Landscape view displays the system overview showing all connected devices:

============================================================
LANDSCAPE DATA - System Overview (COMPACT)
============================================================
[D] Toggle detailed view  |  [B] Back to main menu
============================================================

╔═══ Landscape ═══
╠═ Devices
║  ├─ Dongles: 2
║  │  • ID:0x39BFB011 FW:5.16.0
║  │  • ID:0x39BFBFFF FW:5.16.0
║  ├─ Gloves: 2
║  │  • ID:1 (Left) - Dongle:0
║  │  • ID:2 (Right) - Dongle:1
╠─ Users: 1
║  • WiredUser_0x0000000 (ID:0) Gloves:[L-R]
╠─ Skeletons: 1
║  • Skeleton 1 (Hand) User:0
╠═ Trackers: 0
└─ Settings
   • Manus Core: v3.1.1
   • Mode: Live
   • Max Glove Pairs: 2
   • Features: SDK, Recording, Exporting

Compact View provides:

  • Dongle count and firmware versions
  • Connected glove list with IDs and assignments
  • Tracker inventory
  • User count
  • Skeleton count

Detailed View expands to show all device fields and properties for deep inspection.

Controls:

  • [D]: Toggle to detailed view (shows all fields)
  • [B]: Return to main menu

Raw Device Data

The Raw Device Data view displays sensor information from connected Manus devices (IMUs, accelerometers, gyroscopes, etc.):

============================================================
RAW DEVICE DATA - Sensor Information
============================================================
[B] Back to main menu
============================================================

Connected Devices: 2

Device ID: 0x39BFB011
  Sensor Count: 2
  Sensors:
    Sensor 1:
      Position: (  0.000,   0.000,   0.000)
      Rotation: (W: 1.000, X: 0.000, Y: 0.000, Z: 0.000)
    Sensor 2:
      Position: (  0.050,   0.000,   0.000)
      Rotation: (W: 0.999, X: 0.045, Y: 0.000, Z: 0.000)
  Device Rotation: (W: 0.998, X:-0.063, Y: 0.000, Z: 0.000)

Device ID: 0x39BFBFFF
  Sensor Count: 2
  Sensors:
    Sensor 1:
      Position: (  0.000,   0.000,   0.000)
      Rotation: (W: 1.000, X: 0.000, Y: 0.000, Z: 0.000)
    Sensor 2:
      Position: (  0.050,   0.000,   0.000)
      Rotation: (W: 0.998, X:-0.063, Y: 0.000, Z: 0.000)
  Device Rotation: (W: 0.999, X: 0.031, Y: 0.000, Z: 0.000)

Data Displayed:

  • Device ID: Hardware identifier for each device
  • Sensor Count: Number of motion sensors in the device
  • Sensor Position/Rotation: Transform of each sensor relative to device origin
  • Device Rotation: Overall device orientation

Use Cases:

  • Direct access to raw IMU data for custom motion processing
  • Sensor fusion and filtering
  • Advanced hand tracking algorithms
  • Motion capture and animation

Controls:

  • [B]: Return to main menu

Architecture

Skeleton Setup Flow

The skeleton loading process follows this sequence:

  1. Create Skeleton SetupCoreSdk_CreateSkeletonSetup() returns setup index
  2. Add Hand Nodes_setup_hand_nodes() adds 21 nodes with positions and hierarchy
  3. Add Hand Chains_setup_hand_chains() creates chain topology and links
  4. Load SkeletonCoreSdk_LoadSkeleton() returns skeleton ID for activation
  5. Build Metadata → Extract chains to build node→chain_type mapping
  6. Start Streaming → Skeleton is now animated and generates callbacks

Data Caching

All callback data is cached by glove ID or converted to Python dictionaries to prevent CFFI memory reuse issues:

Data Caching
# Raw skeleton cached by glove ID
self.raw_skeleton_data = {
    1: {'nodes_count': 43, 'nodes': [...]},  # Left glove
    2: {'nodes_count': 43, 'nodes': [...]}   # Right glove
}

# Ergonomics cached by glove ID
self.ergonomics_data = {
    1: {'id': 1, 'data': [...]},
    2: {'id': 2, 'data': [...]}
}

# Landscape is a deep copy of entire structure
self.landscape_data = {
    'dongles': [...],
    'gloves': [...],
    'users': [...],
    ...
}

This caching strategy ensures data persists even when CFFI structures are reused by the SDK.

Thread Safety

All callback data is protected by thread-safe primitives:

Thread-Safe Callbacks
import threading

self.skeleton_mutex = threading.Lock()
self.ergonomics_mutex = threading.Lock()
self.landscape_mutex = threading.Lock()

# In callback:
with self.skeleton_mutex:
    self.raw_skeleton_data[glove_id] = converted_data

# In main thread:
with self.skeleton_mutex:
    display_data = self.raw_skeleton_data.copy()

Performance Considerations

Aspect Implementation Notes
Frame Rate 10 FPS limit Reduces terminal flicker glove data rate is much higher
Data Copying Deep CFFI→dict Prevents memory reuse issues, uses more memory
Thread Safety Mutex per data stream Callbacks run in SDK threads
Memory Dictionary caching Requires more memory than direct CFFI use

Data Structures

ClientRawSkeleton

Container for skeleton callback data:

skeleton_data = {
    'nodes_count': 43,
    'side': 1,  # 1=Left, 2=Right
    'nodes': [
        {
            'transform': {
                'position': {'x': 0.0, 'y': 0.05, 'z': -0.005},
                'rotation': {'x': 0.0, 'y': 0.087, 'z': -0.005, 'w': 0.996}
            }
        },
        # ... more nodes
    ]
}

Ergonomics Data

ergonomics_data = {
    'id': 1,
    'isUserID': False,
    'data': [12, 45, 32, 28, ...]
}

The data array contains: - Spread, MCP, PIP, DIP for each finger (5 fingers × 4 values = 20 values) - Additional sensor data and metrics (remaining ~99 values)

Landscape Data

landscape_data = {
    'dongles': [
        {'id': 0x39BFB011, 'firmware': '5.16.0', ...}
    ],
    'gloves': [
        {'id': 1, 'side': 1, 'dongle_id': 0, ...},
        {'id': 2, 'side': 2, 'dongle_id': 1, ...}
    ],
    'users': [],
    'skeletons': [
        {'id': 1, ...},
        {'id': 2, ...}
    ],
    'settings': {...}
}

Customization Examples

Change Frame Rate

# In __init__:
self.min_display_interval = 0.05  # 20 FPS
# or
self.min_display_interval = 0.033  # 30 FPS

Export Data to File

import json

def export_snapshot(self):
    """Export current data snapshot"""
    snapshot = {
        'timestamp': time.time(),
        'skeleton': self.raw_skeleton_data,
        'ergonomics': self.ergonomics_data,
        'landscape': self.landscape_data
    }

    with open('sdk_data.json', 'w') as f:
        json.dump(snapshot, f, indent=2)

    print("Data exported to sdk_data.json")

Connection Flow

The client initializes in the following order:

  1. Prompt user for connection mode (Integrated/Local/Remote)
  2. Initialize SDK (CoreSdk_InitializeIntegrated or CoreSdk_InitializeCore)
  3. Register all callbacks
  4. Set coordinate system
  5. Search for hosts and establish connection
  6. Enter main menu

If connection fails in Local or Remote modes, the client retries every second until successful.

Troubleshooting

"Could not connect" Loop

  • Local mode: Ensure MANUS Core is running (localhost)
  • Remote mode: Check network connectivity and firewall
  • Integrated mode: Should always work; check console for errors

Data Not Updating

  • Ensure gloves are powered on and connected
  • Verify MANUS Core Dashboard shows glove status
  • Check that callbacks are registered before connecting
  • Verify gloves are actively sending data

Terminal Display Issues

  • Adjust min_display_interval if display updates too frequently/slowly
  • If terminal appears garbled, use full detailed view instead of compact
  • Try reducing other terminal output during operation

Remote Mode Takes Long Time

  • Network discovery takes ~3 seconds by design
  • If no hosts found after 3 seconds, check:
  • MANUS Core running on remote machine
  • Network connectivity between machines

Skeleton Not Appearing in Skeleton Data

  • Ensure you've loaded a skeleton using [K][L] or [R]
  • Wait a moment for the first skeleton callback to arrive
  • Verify raw skeleton data is available first (indicates glove connectivity)

API Reference

load_test_skeleton(side)

Loads a complete hand skeleton with proper node and chain hierarchies.

Load Test Skeleton
def load_test_skeleton(self, side):
    """Load a test skeleton for the specified side (left or right).

    This function creates a fully configured hand skeleton matching the 
    C++ SDK sample implementation:
    - 1 root hand node
    - 20 finger joint nodes (5 fingers × 4 joints)
    - 6 chains (1 hand chain + 5 finger chains)

    Args:
        side: 'left' or 'right' to specify which hand to load

    Raises:
        Various SDK return code errors if setup fails

    After loading, the skeleton will:
    - Generate skeleton callbacks with animation data
    - Be visible in the Skeleton Data view
    - Be animated by connected glove data
    - Continue streaming until unload_test_skeleton() is called

    Example:
        client.load_test_skeleton('left')   # Load left hand
        # ... wait for callbacks ...
        client.unload_test_skeleton()       # Unload it
    """

Return Values:

  • Logs "Loaded [Side] hand skeleton (ID: X)" on success
  • Logs error message on failure with specific SDK return code

Common Issues:

  • Failed to create skeleton setup: SDK initialization problem
  • Failed to add [object] to skeleton setup: Corrupted node/chain data
  • Failed to load skeleton: Setup data invalid or SDK issue
  • Failed to assign skeleton ID: Serious SDK state issue

unload_test_skeleton()

Unloads the currently loaded test skeleton.

Unload Test Skeleton
def unload_test_skeleton(self):
    """Unload the first loaded skeleton from the system.

    Removes the skeleton from the animation pipeline and cleans up:
    - Skeleton data streaming
    - Internal metadata cache
    - Resources allocated by the SDK

    After calling, no more skeleton callbacks will be received 
    for this skeleton and the Skeleton Data view will be empty.

    Example:
        client.unload_test_skeleton()  # Remove currently loaded skeleton
    """

Return Values:

  • Logs "Unloaded skeleton (ID: X)" on success
  • Logs "No loaded skeleton to unload" if none are loaded
  • Logs error message on SDK failure

Internal Helper Functions

These are used internally by load_test_skeleton and are documented for reference:

_create_node_setup(node_id, parent_id, x, y, z, name="")

Creates a properly initialized NodeSetup structure with anatomically correct defaults:

  • Position at (x, y, z)
  • Identity rotation (w=1.0, x/y/z=0.0)
  • Unit scale (1.0, 1.0, 1.0)
  • Type: Joint
  • No special settings (IK, Foot, Leaf, etc.)

_setup_hand_nodes(setup_index, side_enum)

Adds 21 nodes to a skeleton setup:

  • 1 root hand node at origin
  • 5 fingers with 4 joints each
  • Positions based on average hand dimensions from C++ sample
  • Proper parent-child hierarchy

_setup_hand_chains(setup_index, side_enum)

Creates 6 chains linking the nodes:

  • Hand chain (wrist) containing all finger chain IDs
  • 5 finger chains with proper joint sequencing
  • Hand motion set to IMU (Inertial Measurement Unit)
  • All fingers linked to hand chain

See Also