Skip to content

File Format

Both OTS files are plain UTF-8 JSON, indented and readable. You can open one in a text editor, diff two of them in version control, or write a script that reads one. That's deliberate — an interchange format you can't inspect is an interchange format you can't debug.

The current format is version 4.

Shared header

Both formats start the same way:

{
  "format": "octo_anm",
  "version": 4,
  "source": "harmony",
  "fps": 24.0,
  "coordinate_system": {
    "up": "y",
    "hand": "right",
    "unit": "meters",
    "rot_order": "xyz",
    "rot_repr_anim": "euler_deg",
    "rot_repr_rest": "quat_wxyz"
  }
}
Key Meaning
format octo_pvt or octo_anm
version The format version. Must match exactly — see below
source Which application wrote it: harmony, blender, or maya
fps The source scene's frame rate
coordinate_system The space the values are in — see Coordinate Systems

No backward migration

A reader accepts only its own version. A v3 file loaded into a v4 build reports:

unsupported version 3 (this build writes/reads v4; no backward migration — re-export from the source DCC)

This is a choice, not an oversight. Silently migrating an old file means guessing what its numbers meant, and a wrong guess produces a rig that's subtly off rather than obviously broken. Re-exporting from the source takes seconds and is always correct.


.pvt — pivot file

{
  "format": "octo_pvt",
  "version": 4,
  "source": "harmony",
  "name": "spaceship",
  "scale_factor": 1.0,
  "pivot_space": "local",
  "fps": 24.0,
  "coordinate_system": { "…": "…" },
  "bones": [
    {
      "name": "hip",
      "parent": null,
      "pivot": [0.0, 0.0, 0.0],
      "rotation": [1.0, 0.0, 0.0, 0.0],
      "path": "Armature/hip",
      "rig": "spaceship",
      "is_camera": false
    }
  ],
  "cameras": []
}
Key Meaning
name The rig's name
scale_factor The user-facing Scale option. Pivots are multiplied by it on write, divided on read
pivot_space local (relative to parent) or armature
bones[].pivot Rest position
bones[].rotation Rest rotation, as a quaternion [w, x, y, z]
bones[].path The item's full path in its source scene — used for matching, and to keep same-named items in different rigs distinct
bones[].rig The rig tag

path, rig, and is_camera are omitted when empty, so a simple file stays small.


.anm — animation file

{
  "format": "octo_anm",
  "version": 4,
  "source": "maya",
  "frame_range": [1, 60],
  "fps": 24.0,
  "coordinate_system": { "…": "…" },
  "pegs": [
    {
      "name": "hip-P",
      "path": "Top/spaceship/hip-P",
      "parent": "root",
      "rest_pivot": [0.0, 0.0, 0.0],
      "rest_rotation": [1.0, 0.0, 0.0, 0.0],
      "rig": "spaceship",
      "is_camera": false,
      "source_modes": {
        "position": "separate",
        "rotation": "euler",
        "scale": "separate",
        "enable_3d": true
      },
      "keyframes": [
        {
          "frame": 1,
          "pos": [0.0, 0.0, 0.0],
          "rot": [0.0, 0.0, 0.0],
          "rot_quat": [1.0, 0.0, 0.0, 0.0],
          "scl": [1.0, 1.0, 1.0],
          "interp": "BEZIER",
          "handles": { "pos": [], "rot": [], "scl": [] }
        }
      ]
    }
  ],
  "cameras": []
}

The self-sufficiency fields

parent, rest_pivot, rest_rotation, and rig are the .pvt content, folded into each peg. They're the reason Create Rig can build a working skeleton from a .anm alone. See Animation Files (.anm).

Keyframes

Key Meaning
frame Frame number
pos Position, canonical metres
rot Euler XYZ, degrees — the human-readable form
rot_quat The same rotation as a quaternion [w, x, y, z], from the world-matrix bake
scl Scale
interp BEZIER, LINEAR, or CONSTANT
handles Absolute bezier handles, per axis, grouped by channel

rot_quat wins

When both are present, rot_quat is authoritative and every importer prefers it. It's order-independent, so it sidesteps Euler-order ambiguity entirely. rot is kept as a readable fallback.

Handles are not emitted alongside rot_quat by the Maya and Blender bakes, because local-frame handles don't map onto a baked world curve. The interpolation type still travels.

Handles are absolute [[left_x, left_y], [right_x, right_y]] pairs in (frame, value) space — one per axis, grouped into pos, rot, and scl. Absolute rather than relative, so they don't have to be re-derived against a frame rate on the way in.

source_modes

A record of how the peg was authored in its source application — separate or path-based position, Euler or quaternion-path rotation, and whether 3D was enabled.

It's a hint, and only a hint

source_modes never affects any value. It's diagnostic information for a human reading the file. An importer will not change how it writes a curve because of it.


Cameras

Both formats carry the same camera block, linked to its animated item by name:

Key Meaning
name, peg, path Identity, and which item animates it
is_default Whether this is the scene's default camera
fov, fov_axis Field of view — vertical, matching Harmony's convention
override_fov Harmony's per-camera FOV override flag
near_plane, far_plane Clip planes, as canonical distances
near_field, far_field Harmony's original clip values in fields, preserved so a Harmony round trip is lossless
angle Roll, degrees about Z
offset_x/y/z, pivot_x/y Placement relative to the peg
rig The rig tag

See Cameras.


Practical notes

Files are small. A full character rig with a 60-frame shot is a few hundred kilobytes. They're fine to email, put in version control, or keep alongside a shot as a record.

They're diffable. Two exports of the same rig produce comparable JSON, which makes "what changed between these two versions" a text diff rather than a guess.

Naming is up to you. OTS doesn't care what the file is called; the format key inside it is what identifies it. Keeping the .pvt / .anm extensions just makes the file pickers behave.