Coordinate Systems¶
Three applications, three ideas of which way is up and how big a unit is. This page explains what OTS does about that — and the one mistake it exists to avoid.
You don't have to read this to use OTS. Read it when something arrives rotated oddly, or when you want to know why the tool trusts the file rather than the sender.
The canonical space¶
Everything on disk is stored in one space:
| Up | Y |
| Handedness | Right |
| Unit | Metres |
| Rotation (animation) | Euler XYZ, degrees |
| Rotation (rest) | Quaternion, w x y z |
That's Harmony's OGL convention, which makes Harmony the identity case. Every other application declares its own space and converts at the edge.
| Application | Native space | Conversion to canonical |
|---|---|---|
| Harmony | Y-up right-handed, metres (OGL) | Identity, plus a runtime field ↔ OGL scale |
| Blender | Z-up right-handed, metres | Swizzle (x, z, −y) |
| Maya | Y-up right-handed, centimetres | ×0.01, no axis swap (Z-up scenes swizzle too) |
The header¶
Every file carries a coordinate_system block describing the space it was written in. Every reader looks at that block and converts accordingly.
This is why all six routes work without per-route code. A reader doesn't know or care which application wrote the file — only what space it declares. Adding an application is adding a space.
It's also why on-disk values never change meaning between versions: the numbers are always canonical, and the header is always there to say what they are relative to.
Four channels, four conversions¶
The interesting part is that the four kinds of data convert differently.
Signed axis permutation, then the unit scale.
A Blender position (x, y, z) becomes canonical (x, z, −y). A Maya position is multiplied by 0.01.
The w component is preserved; the vector part is permuted like a position.
No unit scale — a rotation has no length.
An unsigned permutation, and no unit scale.
Scale is a magnitude. Negating it would mirror the object, which is never what an axis-convention change means.
Not a permutation. This is the one that catches people.
See below.
The Euler trap¶
Here is the mistake, stated plainly, because it's the reason OTS exists in the shape it does:
A rotation's change of basis is not a permutation of its Euler angles.
It looks like it should be. If a position swizzles (x, y, z) → (x, z, −y), surely the rotation does too? And for the identity basis it happens to work, which makes the bug survive testing.
But permuting Euler angles for a Z-up ↔ Y-up change produces a rotation about the wrong axes. The object turns — so nothing looks obviously broken — but it turns wrong, and the error compounds through a chain. This is the classic symptom of a hand-rolled 3D bridge: "the rotation came through, but it's not right."
The correct transform is a conjugation of the rotation itself:
- Convert the Euler angles to a quaternion.
- Rotate that quaternion's axis by the signed permutation.
- Convert back to Euler XYZ.
OTS does that, everywhere, with a fast path that skips the work when the permutation is the identity (which is why Harmony ↔ Maya in a Y-up scene costs nothing).
Rotation travels as a quaternion where it can
Alongside the Euler angles, each animation key also carries a real quaternion computed from the baked world matrix. It's order-independent, so it sidesteps Euler-order ambiguity entirely — and every importer prefers it when it's present. The Euler values remain as a human-readable fallback.
Units¶
| Harmony | Metres, via a runtime field ↔ OGL scale that OTS asks the live scene for. It isn't a constant — it depends on resolution, field of view, and aspect ratio |
| Blender | Metres. No conversion |
| Maya | Centimetres. ×0.01 out, ×100 in |
The .pvt Scale option is a separate user-facing multiplier for rigs authored at an odd working size. It is not the inter-application unit conversion, which is automatic and invisible.
Harmony's "fake quaternion"¶
Harmony has a column type named QUATERNIONPATH which is not a quaternion. It's a subclass of the 3D-path column that stores three Euler angle components plus a velocity.
So a Harmony peg's rotation is always read and written as Euler XYZ degrees, in every peg mode, regardless of what the column is called. Reading it as a (w, x, y, z) quaternion produces garbage.
See In Harmony.
Camera aim¶
Cameras get one extra correction, because "which way does a camera point" is a per-application convention:
| Application | Camera looks down | On import |
|---|---|---|
| Harmony | Its own −Z — already canonical forward | Roll only |
| Maya | Its own −Z — already canonical forward | Roll only |
| Blender | −Z in a Z-up world | +90° in X, plus roll |
Blender's tilt is removed again on export, so a forward-looking camera round-trips to an identity peg rather than accumulating a 90° drift each time.
FOV is carried as a vertical angle, matching Harmony's scene settings, and each application is explicitly set to a vertical sensor fit. See Cameras.
Known limit¶
Z-up Maya scenes emit interpolation-only handles. Keyframe values convert correctly, but per-key bezier tangents are simplified. Work in Y-up in Maya if exact tangent shapes matter.