文件格式¶
两种 OTS 文件都是纯粹的 UTF-8 JSON,带缩进、可读。你可以用文本编辑器打开一个,在版本控制里对比两个,或者写脚本去读它。这是刻意的 — 一个你无法检视的交换格式,就是一个你无法调试的交换格式。
当前格式是 version 4。
共享的文件头¶
两种格式的开头是一样的:
{
"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"
}
}
| 键 | 含义 |
|---|---|
format |
octo_pvt 或 octo_anm |
version |
格式版本。必须精确匹配 — 见下文 |
source |
是哪个应用写的:harmony、blender 或 maya |
fps |
源场景的帧率 |
coordinate_system |
这些数值所处的空间 — 参见 坐标系 |
没有向后迁移
读取方 只 接受它自己的版本。把一个 v3 文件加载进 v4 构建版本会报告:
unsupported version 3 (this build writes/reads v4; no backward migration — re-export from the source DCC)
这是一个选择,不是疏忽。悄悄迁移一个旧文件,意味着去猜它那些数字当初是什么意思,而猜错会产生一个"细微地错着"的 rig,而不是一个明显坏掉的 rig。从源头重新导出只要几秒钟,而且永远是对的。
.pvt — pivot 文件¶
{
"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": []
}
| 键 | 含义 |
|---|---|
name |
这个 rig 的名字 |
scale_factor |
面向用户的 Scale 选项。Pivot 在写入时乘以它,读取时除以它 |
pivot_space |
local(相对于 parent)或 armature |
bones[].pivot |
静置位置 |
bones[].rotation |
静置旋转,形式为 quaternion [w, x, y, z] |
bones[].path |
该项在其源场景中的完整路径 — 用于匹配,以及让不同 rig 中的同名项保持区分 |
bones[].rig |
Rig 标签 |
path、rig 和 is_camera 在为空时会被省略,所以一个简单的文件依然很小。
.anm — 动画文件¶
{
"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": []
}
自给自足的那几个字段¶
parent、rest_pivot、rest_rotation 和 rig 就是折叠进每个 peg 里的 .pvt 内容。它们正是 Create Rig 能够只凭一个 .anm 就构建出可用骨架的原因。参见 动画文件 (.anm)。
Keyframe¶
| 键 | 含义 |
|---|---|
frame |
帧号 |
pos |
位置,规范的米 |
rot |
Euler XYZ,度 — 人类可读的那种形式 |
rot_quat |
同一个旋转的 quaternion 形式 [w, x, y, z],来自 world matrix bake |
scl |
缩放 |
interp |
BEZIER、LINEAR 或 CONSTANT |
handles |
绝对的 bezier handle,逐轴,按通道分组 |
rot_quat 胜出
当两者都存在时,rot_quat 是权威的,每个导入方都优先用它。它与顺序无关,因此彻底绕开了 Euler 顺序的歧义。rot 则作为可读的备选保留。
Maya 和 Blender 的 bake 不会在 rot_quat 旁边输出 handle,因为局部坐标系的 handle 无法映射到一条烘焙出的 world 曲线上。插值类型仍然会传递。
Handle 是 (frame, value) 空间中绝对的 [[left_x, left_y], [right_x, right_y]] 数对 — 每个轴一对,分组进 pos、rot 和 scl。之所以是绝对值而不是相对值,是为了在读入时不必再对着某个帧率重新推导一遍。
source_modes¶
一份关于这个 peg 在其源应用中是怎么创作的记录 — 位置是分离的还是基于 path、旋转是 Euler 还是 quaternion-path,以及 3D 有没有启用。
它是一条提示,而且仅仅是一条提示
source_modes 从不影响任何数值。它是给读文件的人看的诊断信息。导入方不会因为它而改变自己写曲线的方式。
摄像机¶
两种格式携带相同的摄像机块,通过名称与驱动它的那一项关联:
| 键 | 含义 |
|---|---|
name、peg、path |
身份,以及是哪一项在驱动它 |
is_default |
这是不是场景的默认摄像机 |
fov、fov_axis |
视野 — 垂直,与 Harmony 的惯例一致 |
override_fov |
Harmony 的逐摄像机 FOV 覆盖标记 |
near_plane、far_plane |
Clip plane,以规范距离表示 |
near_field、far_field |
Harmony 原本以 field 为单位的 clip 值,保留下来以便 Harmony 往返无损 |
angle |
Roll,绕 Z 的度数 |
offset_x/y/z、pivot_x/y |
相对于 peg 的摆放 |
rig |
Rig 标签 |
参见 摄像机。
实用说明¶
文件很小。 一个完整角色 rig 加一个 60 帧的镜头是几百 KB。它们完全可以用邮件发送、放进版本控制,或者作为记录与镜头存放在一起。
它们可以 diff。 同一个 rig 的两次导出会产生可比较的 JSON,这让"这两个版本之间改了什么"变成一次文本 diff,而不是靠猜。
命名随你。 OTS 不在乎文件叫什么;识别它的是里面的 format 键。保留 .pvt / .anm 扩展名只是为了让文件选择器表现正常。