Stitch & loop motions | Uthana API Docs
Preview API
This API is a preview and is unstable—it is expected to change in future releases. Reach out via support@uthana.com, Slack, or Discord to share your feedback.
Not yet in the official clients
The stitch & loop API is not yet covered by the official Python (uthana) or JavaScript (@uthana/client) clients. Use the raw GraphQL examples below.
Learn more about Stitching and Looping.
Stitch allows you to join two motion clips into a single animation. Using a leading prefix, a trailing suffix, and spatial data at the join points, the stitch model generates a smooth transition between the two motions.
Overview
You provide pose samples for each clip (pelvis/root position + rotation, plus forward-facing yaw) at the required time points. The stitch uses the prefix pose at at_upper_trim_time and the suffix pose at at_lower_trim_time as the connection points, aligns the motions in world space, and returns a new motion id synchronously.
You can use the Uthana web app Stitch UI to play around with stitch and better understand how to position characters for prefix/suffix alignment.
Key concepts and terminology
Before diving into the API, it helps to understand the core concepts and how they map to the stitch parameters.
Coordinate system and axes
Uthana uses a right-handed, Y-up coordinate system consistent with Three.js, GLM, and glTF:
| Axis | Direction | Meaning |
|---|---|---|
| X | Right | Positive X points to the character's right when facing +Z |
| Y | Up | Positive Y points upward; the floor is at y = 0 |
| Z | Forward | Positive Z is "forward" when the character has zero yaw |
Right-handed Y-up axes
Pose properties and how they map to the API
The stitch model needs to know where each character is and how it's oriented at key moments. It uses this pose data to align the prefix and suffix spatially and generate a smooth transition. Positions are {x, y, z} in meters—e.g. {x: 0.1, y: 0.95, z: 0.5} means 0.1m to the right, pelvis height 0.95m above the floor, 0.5m forward. These properties are required for both the leading (prefix) and trailing (suffix) motion—each motion gets its own StitchParamsInput with full pose data.
You should get these values from your motion loader by evaluating the motion at each required time: seek to the frame, update the animation, and read the world-space transforms from your 3D engine (e.g. getWorldPosition, getWorldQuaternion in Three.js). Pelvis position and rotation come directly from the motion; hips_forward_facing_world_yaw is computed from the left and right hip bone positions (see How to obtain these values).
Suppose you want to space two motions 1 meter apart in a straight line. You would set the prefix pelvis at the last frame (at_upper_trim_time) to {x: 0, y: 0.95, z: 0} and the suffix pelvis at the first frame (at_lower_trim_time) to {x: 0, y: 0.95, z: 1}. Without this spatial data, the stitch model has no way to know where each motion sits in the world, and the transition will look wrong.
What each property is
| Property | Type | What it represents |
|---|---|---|
root_node_world_pos |
{x, y, z} (meters) |
Character root position in world space. x and z are horizontal; y is height above the floor. |
root_node_world_rot |
{x, y, z, w} (quaternion) |
Character root orientation in world space. Identity (no rotation) is {x: 0, y: 0, z: 0, w: 1}. |
pelvis_world_pos |
{x, y, z} (meters) |
Pelvis bone position in world space. Used in each of at_zero_time, at_lower_trim_time, at_upper_trim_time. |
pelvis_world_rot |
{x, y, z, w} (quaternion) |
Pelvis bone orientation in world space. |
hips_forward_facing_world_yaw |
radians | Angle around the Y axis for hips facing. Computed as atan2(forward.x, forward.z). Examples: - 0 = facing +Z- π/2 = facing -X- π = facing -Z- -π/2 = facing +X |
Quaternions use {x, y, z, w} with w as the scalar component. This matches Three.js, GLM, and common game engines. See Three.js Quaternion and Quaternions and spatial rotation for more on this format. For the coordinate system and glTF reference, see above.
What they're used for
The stitch model uses root and pelvis transforms to:
- Align the two motions spatially so the character doesn't pop or drift when transitioning.
- Match facing direction so the blend looks natural.
- Preserve floor contact so the character doesn't float or sink.
Without accurate pose data, the transition can look jarring. For production, always sample from your motion loader.
Example: a single pelvis state
Each of at_zero_time, at_lower_trim_time, and at_upper_trim_time is a PelvisStateInput:
{
"pelvis_world_pos": { "x": 0.12, "y": 0.95, "z": 0.0 },
"pelvis_world_rot": { "x": 0, "y": 0, "z": 0, "w": 1 },
"hips_forward_facing_world_yaw": 0
}
Root node vs. pelvis
- Root node (
root_node_world_pos,root_node_world_rot): The character's root transform in world space—typically the top-level node that contains the whole skeleton. - Pelvis: A specific bone (the hip/pelvis joint) that defines the character's center of mass and facing direction. The stitch model uses pelvis data to align the two motions spatially.
The root is the hierarchy root; the pelvis is a child bone. Their positions and rotations can differ when the character is posed.
Trim times and the three sample points
Each motion is sampled at three time points:
| Sample | When | Purpose |
|---|---|---|
at_zero_time |
t = 0 |
Start of the motion clip |
at_lower_trim_time |
Start of the trimmed segment | Where the "useful" part of the clip begins |
at_upper_trim_time |
End of the trimmed segment | Where the "useful" part ends |
- Prefix motion: The stitch uses the pose at
at_upper_trim_time(the last frame of the prefix) to connect to the suffix. - Suffix motion: The stitch uses the pose at
at_lower_trim_time(the first frame of the suffix) as the connection point.
For the prefix, keep at_upper_trim_time slightly before the clip end (e.g. motion_duration - 0.051) to avoid sampling exactly at the boundary.
How to obtain these values
Motion loader required
You will need a motion loader (e.g. GLTF + Three.js, or your engine's equivalent) that can evaluate poses at specific times.
Lower and upper trim times. You first need to decide
lower_trimandupper_trim—the start and end of the segment you want to use. In the web app, the trim handles on each motion's timeline define these: drag the handles to mark the segment; the times at those positions are yourlower_trimandupper_trim.Pose data at each time. Once you have those times, sample poses at
0,lower_trim, andupper_trimfrom your motion loader. The pose data comes from pelvis and root rotation and positioning in world space—pelvis_world_pos,pelvis_world_rot, and the root transforms—plus the hips forward yaw. The web app lets you preview and adjust how the prefix and suffix characters are positioned and oriented relative to each other; the 3D view shows how pelvis rotation and positioning affect spatial alignment.Pelvis positioning and duration. The spatial gap between the prefix and suffix connection poses, combined with
stitch_duration, can make or break a stitch. If the two poses are far apart in world space but you use a short duration, the model has to compress a large motion into a small window—the result can look rushed or unnatural. Conversely, if the poses are very close and you use a long duration, the transition may feel sluggish. Match the duration to the distance: larger gaps generally need longer durations for a smooth, believable transition. For example:- Walking (~1.2 m/s): For a 1 m gap, use
stitch_durationin the 0.8–1.2 s range; for 2 m, use 1.5–2.0 s. - Jogging (~2.5 m/s): For a 1 m gap, use 0.4–0.6 s; for 2 m, use 0.8–1.2 s.
- Walking (~1.2 m/s): For a 1 m gap, use
Summary. For programmatic use, the sampling process is:
- Load the motion (e.g. GLTF with animation) and the character skeleton.
- Seek to each time (
0,lower_trim,upper_trim) and update the animation. - Read world-space transforms for the pelvis and root from your 3D engine (e.g.
getWorldPosition,getWorldQuaternionin Three.js). - Compute
hips_forward_facing_world_yaw: Get world positions of left and right hip bones. The "hips forward" direction is perpendicular to the hip line in the horizontal plane:forward = normalize(cross(left_hip - right_hip, up)). Thenyaw = atan2(forward.x, forward.z).
Prerequisites
Before calling the stitch API, you need:
- API key: For authentication (see GraphQL API)
- Character ID: The character both motions are retargeted to (e.g.
cXi2eAP19XwQfor Uthana's default character) - Prefix motion ID: The leading motion (plays first)
- Suffix motion ID: The trailing motion (plays second)
- Pose data: Pelvis and root-node transforms sampled at time 0, lower trim, and upper trim for each motion
See How to obtain these values for the sampling process. The Uthana web app performs this automatically when you use the Stitch UI.
Step-by-step tutorial
Step 1: Authenticate your request
API_KEY="your-api-key"
API_URL="https://uthana.com/graphql"
Step 2: Build the stitch input
The MotionStitchInput requires character_id, prefix, and suffix. Each of prefix and suffix is a StitchParamsInput with motion metadata and pose samples at three time points.
If you don't yet have a motion loader, you can use these placeholder values for the pose fields to satisfy the schema and test the API flow. They are structurally valid but will not yield good stitch quality—for production, always sample from your motion data.
| Property | Placeholder value | Notes |
|---|---|---|
root_node_world_pos |
{x: 0, y: 0, z: 0} |
Origin. Replace with sampled data for real stitches. |
root_node_world_rot |
{x: 0, y: 0, z: 0, w: 1} |
Identity (no rotation). |
pelvis_world_pos (in each sample) |
{x: 0, y: 0, z: 0} |
Origin. |
pelvis_world_rot (in each sample) |
{x: 0, y: 0, z: 0, w: 1} |
Identity. |
hips_forward_facing_world_yaw |
0 |
Facing +Z. |
Recommended defaults
For stitch_duration, stitch_loop, prompt, and other optional fields, see Recommended defaults.
Required fields (must be derived from your motion data):
motion_id,motion_duration,motion_lower_trim_time,motion_upper_trim_time,motion_lower_trim_fraction,motion_upper_trim_fractionroot_node_world_pos,root_node_world_rot(character root at trim points)at_zero_time,at_lower_trim_time,at_upper_trim_time(pelvis position, rotation, andhips_forward_facing_world_yawin radians)
For prefix motions, keep the upper trim time slightly before the clip end (e.g. duration - 0.051) to avoid sampling at the exact boundary, which improves stitch quality.
Step 3: Call the mutation
curl -X POST "$API_URL" \
-u "$API_KEY": \
-H "Content-Type: application/json" \
-d '{
"query": "mutation CreateEnhancedStitchedMotion($stitch_input: MotionStitchInput!) { create_enhanced_stitched_motion(stitch_input: $stitch_input) { motion { id } } }",
"variables": {
"stitch_input": {
"character_id": "'