LogoLogo
WebsiteSupportDownloadsForumsQuick LinksContact Us
v3.2
v3.2
  • OptiTrack Documentation
  • WHAT'S NEW
    • What's New in Motive 3.2
  • QUICK START GUIDES
    • Quick Start Guide: Getting Started
    • Quick Start Guide: Prime Color Camera Setup
    • Quick Start Guide: Precision Capture
    • Quick Start Guide: Tutorial Videos
    • Quick Start Guide: Active Marker Tracking
    • Quick Start Guide: Outdoor Tracking Setup
  • HARDWARE
    • Cameras
      • Ethernet Cameras
        • PrimeX 120
        • PrimeX 41
        • PrimeX 22
        • PrimeX 13
        • PrimeX 13W
        • SlimX 13
        • Prime Color
        • VersaX 22
        • VersaX 41
        • VersaX 120
      • USB Cameras
        • Slim 3U
        • Flex 13
        • Flex 3
        • V120:Duo
        • V120:Trio
        • Adjusting Global Origin for Tracking Bars
    • Prepare Setup Area
    • Camera Mount Structures
    • Camera Placement
    • Ethernet Camera Network Setup
      • General Overview and Specs
      • Windows 10 Network Settings
      • Cabling and Load Balancing
      • Switch Configuration for PrimeX 120
      • NETGEAR ProSafe GSM7228S: Disabling the Broadcast Storm Control
      • White/Blacklisting Cameras
    • USB Camera System Setup
      • USB Camera Network Overview and Specs
      • V120:Duo and Trio Setup
      • Tracking Bar Coordinate System
        • Transforming Coordinate System: Global to Local
    • Aiming and Focusing
    • Camera Status Indicators
  • MOTIVE
    • Installation and License Activation
    • Motive Basics
    • Calibration
      • .mcal XML Calibration Files
      • Continuous Calibration
      • Continuous Calibration (Info Pane)
      • Calibration Squares
    • Markers
    • Assets
      • Gizmo Tool: Translate, Rotate, and Scale
    • Rigid Body Tracking
      • Aligning Rigid Body Pivot Point with a Replicated 3D Model
    • Skeleton Tracking
    • Trained Markersets
    • IMU Sensor Fusion
    • Data Recording
      • Data Types
    • Labeling
    • Data Editing
    • Data Export
      • Data Export: BVH
      • Data Export: C3D
      • Data Export: CSV
      • Data Export: FBX
      • Data Export: TRC
    • Data Streaming
    • Camera Video Types
    • Audio Recording
    • Motive HotKeys
    • Measurement Probe Kit Guide
    • Motive Batch Processor
    • Reconstruction and 2D Mode
  • MOTIVE UI PANES
    • Settings
      • Settings: General
      • Settings: Assets
      • Settings: Live Pipeline
      • Settings: Streaming
      • Settings: Views
      • Settings: Mouse and Keyboard
      • Settings: Audio
    • Assets Pane
    • Builder Pane
    • Constraints Pane
      • Constraints XML Files
    • Calibration Pane
    • Data Pane
    • Devices Pane
    • Edit Tools Pane
    • Graph View Pane
    • Info Pane
    • Labels Pane
    • Log Pane
    • Probe Pane
    • Properties Pane
      • Properties Pane: Camera
      • Properties Pane: Force Plates
      • Properties Pane: NI-DAQ
      • Properties Pane: OptiHub2
      • Properties Pane: Rigid Body
      • Properties Pane: Skeleton
      • Properties Pane: Take
      • Properties Pane: Trained Markerset
      • Properties Pane: eSync2
    • Status Panel
    • Toolbar/Command Bar
    • Control Deck
    • Viewport
  • PLUGINS
    • OptiTrack Blender Plugin
      • OptiTrack Blender Plugin
    • OptiTrack Unreal Engine Plugin
      • Unreal Engine: OptiTrack Live Link Plugin
        • Quick Start Guide: Real-Time Retargeting in Unreal Engine with Live Link Content
        • Unreal Editor for Fortnite (UEFN): OptiTrack Plugin for Live Link Hub
        • Unreal Engine: Live Link Camera Stream Setup
        • Live Link Content: Active Puck Static Meshes
      • Unreal Engine: MotionBuilder Workflow
      • Unreal Engine: HMD Setup
      • Unreal Engine VCS Inputs
    • OptiTrack Unity Plugin
      • Unity: HMD Setup
    • OptiTrack OpenVR Driver
    • OptiTrack MATLAB Plugin
    • Autodesk Maya
      • Autodesk Maya: OptiTrack Insight VCS Plugin
    • Autodesk MotionBuilder
      • Autodesk MotionBuilder Plugin
      • Autodesk MotionBuilder: OptiTrack Skeleton Plugin
      • Autodesk MotionBuilder: OptiTrack Optical Plugin
      • Autodesk MotionBuilder: OptiTrack Insight VCS Plugin
      • Autodesk MotionBuilder: Timecode Data
    • OptiTrack Peripheral API
    • External Plugins
      • Houdini 19 Integration
  • ACTIVE COMPONENTS
    • Active Components Hardware
      • Active Puck
      • Wired AnchorPuck
      • CinePuck
      • Wired CinePuck
      • BaseStation
      • Information for Assembling the Active Tags
      • Manus Glove Setup
    • Configuration
      • Active Batch Programmer
      • Active Hardware Configuration: PuTTY
      • Active Component Firmware Compatibility
    • Active Marker Tracking
      • Active Finger Marker Set
  • SYNCHRONIZATION
    • Synchronization Hardware
      • External Device Sync Guide: eSync 2
      • External Device Sync Guide: OptiHub2
    • Synchronization Setup
    • OptiTrack Timecode
  • VIRTUAL PRODUCTION
    • Unreal Engine: OptiTrack InCamera VFX
    • Entertainment Marker Sets
    • PrimeX 41
  • MOVEMENT SCIENCES
    • Movement Sciences Hardware
      • General Motive Force Plate Setup
      • AMTI Force Plate Setup
      • Bertec Force Plate Setup
      • Kistler Force Plate Setup
      • Delsys EMG Setup
      • NI-DAQ Setup
      • Multiple Device Setup
    • Movement Sciences Marker Sets
      • Biomechanics Marker Sets
      • Biomech (57)
      • Rizzoli Marker Sets
    • For Visual3D Users
    • Prime Color Camera Setup
      • Prime Color Setup: Required Components
      • Prime Color Setup: Hardware Setup
      • Prime Color Camera Setup: Camera Settings
      • Prime Color Camera Setup: Prime Color FS Calibration
      • Prime Color Setup: Data Recording / Export
      • Prime Color Camera Setup: FAQ / Troubleshooting
      • Prime Color Camera Setup: Windows Network Settings
  • VIRTUAL REALITY
    • VR Plugins
      • VR Unreal Engine
        • OptiTrack Unreal Engine Plugin
        • Unreal Engine: OptiTrack Live Link Plugin
          • UE5.1 Live Link Retarget External Workaround
        • Unreal Engine VCS Inputs
      • VR Unity
        • OptiTrack Unity Plugin
      • VR OpenVR
        • OptiTrack OpenVR Driver
    • VR HMD Setup
      • Unreal Engine: HMD Setup
      • Unity: HMD Setup
      • Manually Calibrating the HMD Pivot Point
      • Sync Configuration with an HTC Vive System
    • SlimX 13
    • Active Marker Tracking
      • Active Finger Marker Set
    • Synchronization Hardware
      • External Device Sync Guide: eSync 2
      • External Device Sync Guide: OptiHub2
  • ANIMATION
    • Autodesk Maya
      • Autodesk Maya: OptiTrack Insight VCS Plugin
    • Autodesk MotionBuilder
      • Autodesk MotionBuilder Plugin
      • Autodesk MotionBuilder: OptiTrack Skeleton Plugin
      • Autodesk MotionBuilder: OptiTrack Optical Plugin
      • Autodesk MotionBuilder: OptiTrack Insight VCS Plugin
      • Autodesk MotionBuilder: Timecode Data
  • ROBOTICS
    • MoCap4ROS2 Setup
    • OptiTrack Robot Applications
    • Outdoor Tracking Setup
  • DEVELOPER TOOLS
    • Developer Tools Overview
    • Camera SDK
      • Class: cCameraModule
      • Class: cUID
    • Motive API
      • Motive API: Quick Start Guide
      • Motive API Overview
      • Motive API: Function Reference
      • Motive API Camera Calibration
    • NatNet SDK
      • NatNet 4.1
      • NatNet: Class/Function Reference
      • NatNet: Creating a Managed (C sharp) Client Application
      • NatNet: Creating a Native (C++) Client Application
      • NatNet: Data Types
      • NatNet: Matlab Wrapper
      • NatNet: Migration to NatNet 3.0 libraries
      • NatNet: Remote Requests/Commands
      • NatNet: Sample Projects
      • NatNet: Unicast Data Subscription Commands
      • Latency Measurements
    • VRPN Sample
    • Peripheral API: Glove Devices
  • SKELETON MARKER SETS
    • Full Body
      • Baseline (41)
      • Core (50)
      • Biomech (57)
      • Conventional (39)
    • Full Body + Fingers
      • Baseline + Passive Fingers (49)
      • Baseline + Active Fingers (57)
      • Core + Passive Fingers (54)
      • Core + Active Fingers (62)
    • Upper
      • Baseline Upper (25)
      • Conventional Upper (27)
    • Lower
      • Baseline Lower (20)
      • Helen Hayes Lower (19)
      • Conventional Lower (16)
    • Hand and Fingers
      • Left/Right Hand (4) Active
      • Left/Right Hand (10) Active + Passive
      • Active Finger Marker Set
    • Glove Device Setup
      • Manus Glove Setup
      • StretchSense Glove Setup
    • Rizzoli Marker Sets
    • Entertainment Marker Sets
    • Rigid Body Skeleton Marker Set
  • GENERAL TROUBLESHOOTING
    • Licensing Troubleshooting
    • Windows 11 Optimization for Realtime Applications
    • Network Troubleshooting
    • Troubleshooting Q&A
    • Running Motive on High DPI Displays
    • Firewall Settings
Powered by GitBook
On this page
  • 1. NatNet Library
  • 2. Connect
  • 3. Get DataDescriptions
  • 4. Get Frame Data
  • 5. Disconnect

Was this helpful?

Export as PDF
  1. DEVELOPER TOOLS
  2. NatNet SDK

NatNet: Creating a Managed (C sharp) Client Application

PreviousNatNet: Class/Function ReferenceNextNatNet: Creating a Native (C++) Client Application

Was this helpful?

The following guide references SampleClientML.cs client application that is provided with the SDK. This sample demonstrates the use of .NET NatNet assembly for connecting to a NatNet server, receiving a data stream, and parsing and printing out the received data.

SDK/API Support Disclaimer

We provide developer tools to enable OptiTrack customers across a broad set of applications to utilize their systems in the ways that best suit them. Our Motive API through the NatNet SDK and Camera SDK is designed to enable experienced software developers to integrate data transfer and/or system operation with their preferred systems and pipelines. Sample projects are provided alongside each tool, and we strongly recommend the users to reference or use the samples as reliable starting points. The following list specifies the range of support that will be provided for the SDK tools:

  • Using the SDK tools requires background knowledge on software development; therefore, we do not provide support for basic project setup, compiling, and linking when using the SDK/API to create your own applications.

  • Although we ensure the SDK tools and their libraries work as intended, we do not provide support for custom developed applications that have been programmed or modified by users using the SDK tools.

  • Ticketed support will be provided for licensed Motive users using the Motive API and/or the NatNet SDK tools from the included libraries and sample source codes only.

  • The Camera SDK is a free product, and therefore we do not provide free ticketed support for it.

  • For other questions, please check out the . Very often, similar development issues get reported and solved there.

1. NatNet Library

When developing a managed client applications, you will need to link both native and managed DLL files(NatNetLib.dll and NatNetML.dll). The managed NatNet assembly is derived from the native library, so without the NatNetLib.dll, NatNetML.dll will not be imported properly. These library files can be found in the NatNetSDK\lib folder for 32-bit platform and in the NatNetSDK\lib\x64 folder for 64-bit platform. Make sure these DLL files are properly linked and placed alongside the executables.

Also, when using the NatNetML assembly, place the NatNetML.xml file alongside imported DLL file. This allows XML documentation to be included as a reference. These library files can be found in the NatNetSDK\lib folder for 32-bit platform and in the NatNetSDK\lib\x64 folder for 64-bit platform. Make sure these DLL files are properly linked and placed alongside the executables.

2. Connect

a. Create a NatNetClientML object

Tracking server and client network is established through an instance of NatNet client object (NatNetML.NatNetClientML). Also, this NatNetClientML object will be used for receiving tracking data and sending NatNet commands to and from the server application. When instantiating the NatNetClientML object, input an integer value for determining the desired type of UDP connection; whether it connects via multicast (0) or unicast (1).

[C#] SampleClientML.cs : Instantiating NatNetClientML

/*  [NatNet] Network connection configuration    */
private static NatNetML.NatNetClientML m_NatNet;    // The client instance
private static string strLocalIP = "127.0.0.1";     // Local IP address (string)
private static string strServerIP = "127.0.0.1";    // Server IP address (string)
private static int iConnectionType = 0;             // Connection Type
static void connectToServer()
{
    /*  [NatNet] Instantiate the client object  */
    m_NatNet = new NatNetML.NatNetClientML(iConnectionType);

    /*  [NatNet] Checking verions of the NatNet SDK library  */
    int[] verNatNet = new int[4];           // Saving NatNet SDK version number
    verNatNet = m_NatNet.NatNetVersion();
    Console.WriteLine("NatNet SDK Version: {0}.{1}.{2}.{3}", verNatNet[0], verNatNet[1], verNatNet[2], verNatNet[3]);
 
    /*  [NatNet] Connecting to the Server    */
    Console.WriteLine("\nConnecting...\n\tLocal IP address: {0}\n\tServer IP Address: {1}\n\n", strLocalIP, strServerIP);
    m_NatNet.Initialize(strLocalIP, strServerIP);
}

b. Connect to Server

[C#] SampleClientML.cs : Connect to Server. Initialize

/*  [NatNet] Network connection configuration    */
private static NatNetML.NatNetClientML m_NatNet;    // The client instance
private static string strLocalIP = "127.0.0.1";     // Local IP address (string)
private static string strServerIP = "127.0.0.1";    // Server IP address (string)
private static int iConnectionType = 0;             // Connection Type
static void connectToServer()
{
    /*  [NatNet] Instantiate the client object  */
    m_NatNet = new NatNetML.NatNetClientML(iConnectionType);

    /*  [NatNet] Checking verions of the NatNet SDK library  */
    int[] verNatNet = new int[4];           // Saving NatNet SDK version number
    verNatNet = m_NatNet.NatNetVersion();
    Console.WriteLine("NatNet SDK Version: {0}.{1}.{2}.{3}", verNatNet[0], verNatNet[1], verNatNet[2], verNatNet[3]);

    /*  [NatNet] Connecting to the Server    */
    Console.WriteLine("\nConnecting...\n\tLocal IP address: {0}\n\tServer IP Address: {1}\n\n", strLocalIP, strServerIP);
    m_NatNet.Initialize(strLocalIP, strServerIP);
}

Server Discovery

You can also use the NatNetServerDiscover class to auto-detect available servers to connect to. This is demonstrated in the WinFromSamplesApp**.**

c. Confirm connection

[C#] SampleClientML.cs : GetServerDescription

static bool fetchServerDescriptor()
{
    NatNetML.ServerDescription m_ServerDescriptor = new NatNetML.ServerDescription();
    int errorCode = m_NatNet.GetServerDescription(m_ServerDescriptor);

    if (errorCode == 0)
    {
        Console.WriteLine("Success: Connected to the server\n");
        parseSeverDescriptor(m_ServerDescriptor);
        return true;
    }
    else
    {
        Console.WriteLine("Error: Failed to connect. Check the connection settings.");
        Console.WriteLine("Program terminated (Enter ESC to exit)");
        return false;
    }
}

3. Get DataDescriptions

a. GetDataDescription

GetDataDescriptions method in the NatNetClientML class queries a list of DataDescriptors from the connected server and saves it in a declared list of NatNetML.DataDescriptions. In the SampleClientML sample, the following lines are executed to accomplish this:

[C#] SampleClientML.cs : GetDataDescription

/*  List for saving each of datadescriptors */
private static List<NatNetML.DataDescriptor> m_DataDescriptor = new List<NatNetML.DataDescriptor>();

static void fetchDataDescriptor()
{
    /*  [NatNet] Fetch Data Descriptions. Instantiate objects for saving data descriptions and frame data    */        
    bool result = m_NatNet.GetDataDescriptions(out m_DataDescriptor);

    if (result)
    {
        Console.WriteLine("Success: Data Descriptions obtained from the server.");
        parseDataDescriptor(m_DataDescriptor);
    }
    else
    {
        Console.WriteLine("Error: Could not get the Data Descriptions");
    }
        Console.WriteLine("\n");
}

b. ParseDataDescription

After obtaining a list of data descriptions, use the saved DataDescriptor objects to access and output data descriptions as needed. In many cases, it is better to re-organize and save the received descriptor objects into separate lists, or into hashtables, of corresponding data types, so that they can be referenced later in the program.

[C#] SampleClientML.cs : Parsing/Saving Data Descriptions

static void parseDataDescriptor(List<NatNetML.DataDescriptor> description)
{
    //  [NatNet] Request a description of the Active Model List from the server. 
    //  This sample will list only names of the data sets, but you can access 
    int numDataSet = description.Count;
    Console.WriteLine("Total {0} data sets in the capture:", numDataSet);

    for (int i = 0; i < numDataSet; ++i)
    {
        int dataSetType = description[i].type;
        // Parse Data Descriptions for each data sets and save them in the delcared lists and hashtables for later uses.
        switch (dataSetType){
            case ((int) NatNetML.DataDescriptorType.eMarkerSetData):
                  NatNetML.MarkerSet mkset = (NatNetML.MarkerSet)description[i];
                  Console.WriteLine("\tMarkerSet ({0})", mkset.Name);
                  break;

            case ((int) NatNetML.DataDescriptorType.eRigidbodyData):
                  NatNetML.RigidBody rb = (NatNetML.RigidBody)description[i];
                  Console.WriteLine("\tRigidBody ({0})", rb.Name);

                  // Saving Rigid Body Descriptions
                  mRigidBodies.Add(rb);
                  break;

            case ((int) NatNetML.DataDescriptorType.eSkeletonData):
                  NatNetML.Skeleton skl = (NatNetML.Skeleton)description[i];
                  Console.WriteLine("\tSkeleton ({0}), Bones:", skl.Name);
 
                  //Saving Skeleton Descriptions
                  mSkeletons.Add(skl);
 
                  // Saving Individual Bone Descriptions
                  for (int j = 0; j < skl.nRigidBodies; j++)
                  {
                      Console.WriteLine("\t\t{0}. {1}", j + 1, skl.RigidBodies[j].Name);
                      int uniqueID = skl.ID * 1000 + skl.RigidBodies[j].ID;
                      int key = uniqueID.GetHashCode();
                      htSkelRBs.Add(key, skl.RigidBodies[j]); //Saving the bone segments onto the hashtable
                  }
                  break;

            case ((int) NatNetML.DataDescriptorType.eForcePlateData):
                    NatNetML.ForcePlate fp = (NatNetML.ForcePlate)description[i];
                    Console.WriteLine("\tForcePlate ({0})", fp.Serial);

                    // Saving Force Plate Channel Names
                    mForcePlates.Add(fp);
         
                    for (int j = 0; j < fp.ChannelCount; j++)
                    {
                          Console.WriteLine("\t\tChannel {0}: {1}", j + 1, fp.ChannelNames[j]);
                    }
                    break;

            default:
                  // When a Data Set does not match any of the descriptions provided by the SDK.
                  Console.WriteLine("\tError: Invalid Data Set");
                  break;
    }
  }
}

4. Get Frame Data

a. Callback Handler (OnFrameReady)

The best way to receive tracking data without losing any of its frames is to create a callback handler function for processing the data. The OnFrameReady event type from the client object can be used to declare a callback event, and the linked function gets called each time a frame is received from the server. Setting up a frame handler function will ensure that every frame gets processed promptly. However, these handler functions should return as quickly as possible to prevent accumulation of frames due to processing latency within the handler.

m_NatNet.OnFrameReady += new NatNetML.FrameReadyEventHandler(frameHandlerFunction);
  • OnFrameReady2: Alternate function signatured frame ready callback handler for .NET applications/hosts that don't support the OnFrameReady event type defined above (e.g. MATLAB)

[C#] SampleClientML.cs : Frame Data Callback Handler

Console.WriteLine("============================= FRAME OF DATA ===================================\n");
Console.WriteLine("Now Fetching the Frame Data\n");

/*  [NatNet] Assigning a event handler function for fetching frame data each time a frame is received   */
m_NatNet.OnFrameReady += new NatNetML.FrameReadyEventHandler(parseFrameData);

Console.WriteLine("Success: Data Port Connected \n");

/// [NatNet] parseFrameData will be called when a frame of Mocap data has is received from the server application
static void parseFrameData(NatNetML.FrameOfMocapData data, NatNetML.NatNetClientML client)
{

    /*  Exception handler for cases where assets are added or removed.
    Data description is re-obtained in the main function so that contents
    in the frame handler function is kept minimal. */

    if (( data.bTrackingModelsChanged == true || data.nRigidBodies != mRigidBodies.Count ||
      data.nSkeletons != mSkeletons.Count || data.nForcePlates != mForcePlates.Count))
    {
              assetChanged = true;
    }

    /*  Processing and outputting frame data every 200th frame.
    This conditional statement is included in order to simplify the program output */

    if(data.iFrame % 200 == 0)
    {
        if (data.bRecording == false)
        Console.WriteLine("Frame #{0} Received:", data.iFrame);
    
    else if (data.bRecording == true)
        Console.WriteLine("[Recording] Frame #{0} Received:", data.iFrame);
        processFrameData(data);
    }
}

b. Single Frame (GetLastFrameOfData)

Calling the GetLastFrameOfData method returns a FrameOfMocapData of the most recent frame that was streamed out from the connected server application. This approach is should only be used for .NET applications/hosts that do not support the OnFrameReady callback handler function.

FrameOfMocapData data = m_NatNet.GetLastFrameOfData();

This function is supported in NatNetML only. Native implementations should always use the callback handlers.

5. Disconnect

When exiting the program, call Uninitialize method using the connected client object and disconnect the client application from the server.

[C#] SampleClientML.cs : Disconnect

    /*  [NatNet] Disabling data handling function   */
    m_NatNet.OnFrameReady -= parseFrameData;

    /*  Clearing Saved Descriptions */
    mRigidBodies.Clear();
    mSkeletons.Clear();
    htSkelRBs.Clear();
    mForcePlates.Clear();

    /*  Disconnecting from the Server  */
    m_NatNet.Uninitialize();
} // End. Main()

To connect to the server, use the Initialize method from the instantiated NatNetClientML object. When calling this method, input the proper Local IP address and the Server IP address. The local IP address must match the IP address of the host PC, and the server IP address must match the address that the server is streaming onto, which is defined in the panel in Motive.

To confirm whether the client has successfully connected to the server application, let's try querying for a packet using the GetServerDescription method. If the server is connected, the corresponding server descriptions will be obtained. This method returns an value, and when successfully operated it will return a value of 0.

As explained in the page, there are two kinds of data formats included in streamed NatNet packets; one of which is Data Descriptions. In managed NatNet assembly, data descriptions for each of the assets (Marker Sets, Rigid Bodies, Skeletons, and force plates) included in the capture session is stored in a DataDescriptor class. A single capture take (or live streaming) may contain more than one assets, and respectively, there may be more than one data descriptions. For this reason, data descriptions are stored in a list format.

Now, let's obtain the tracking data from the connected server. Tracking data for a captured frame is stored in an instance of NatNetML.FrameOfMocapData. As explained in the page, every FrameOfMocapData contains tracking data of the corresponding frame. There are two approaches for obtaining frame data using the client object; by calling the GetLastFrameOfData method or by linking a callback handler function using the OnFrameReady method. In general, creating a callback function is recommended because this approach ensures that every frame of tracking data gets received.

NaturalPoint forums
Data Streaming
NatNet: Data Types
Data Types
ServerDescriptor
ErrorCode