前言
最近遇到了一个比较难搞的需求,好不容易解决了,在这里记录一下。
需求是这样的,公司有大佬在 motionbuilder 写了插件,利用 mobu API 做了一个自定义的节点并在里面通过 FBXStore API 存入了自定义数据。
我需要将这些操作通过 Python FBXSDK 来完成这些数据的写入。
主要原因是 motionbuilder 的稳定性不可靠,如果可以利用纯外部调用 FBXSDK 的形式解决问题,就不需要依赖 mobu 了。
用 FBXSDK 来还原自定义节点操作都好说。
主要蛋疼的地方在于需要解决 FBXStore API 调用背后怎么转换成二进制的问题。
motion builder C++ 插件编译
在 motion builder 的安装路径有
OpenRealitySDK
文件夹,里面的 samples 有很多开发 mobu 的参考代码。
其中比较具有代表性的脚本就是OpenRealitySDK\samples\devices\devicecamera\ordevicecamera_device.cxx
这个脚本就定义怎么将自定义数据存入 FBX 当中,并且利用FbxRetrieve
方法将功能读取回来。
我们可以把这个东西编译出来作为我们这次测试的内容。
默认 motionbuilder 的 samples 里面提供了 sln 工程,可以直接用 VS 打开。
打开之后需要将平台工具集升级到最新的 VS 版支持的工具集,默认是 2012 工具集太过古老了。
改完之后本想着愉快地编译,然而这样会报错。
这个问题只能归结为新的平台工具集已经去掉了支持,但是头文件依旧引入相应的文件,解决也很简单,将报错的那一行注释即可。
编译完成会默认去到
bin\x64\plugins
的目录,这样只要重启 motion builder 就能加载到这个 dll 了。
这样将这个图标拖拽到场景就可以创建一个 device.
将这场景以 ascii 的格式保存。
检查保存的 FBX 文件,可以看到 FBXStore 的写入逻辑,会将信息写入到节点的MoBuAttrBlindData
属性上
存储出来可以看到相应的信息。
这里官方的插件将信息转成了 KString 所以里面的信息也是以 FBX ASCII 的形式存在。
但如果将 FBX 存成 Binary 模式,然后再用 Python FBXSDK 来转存成 ASCII 的话,这些 FBXStore 的数据会转成 base64 的二进制数据。
如果用 base64 解码,可以看到里面存储的二进制数据。
1 | import base64 |
VScode 安装 Hex Editor 可以查看二进制数据。
而我这边需要想办法用 Python 写入二进制数据,从而摆脱 motion builder 的依赖。
FBX 二进制
FBX 数据格式
RenderDoc Python 开发 FBX 导出工具
之前写 renderdoc 导出 FBX 插件的时候,使用的时 FBX ASCII 格式,通过将数据写入到 FBX ASCII 对应的位置。FBX 就可以被读取到。
当时踩了的坑也可以从中窥探到 FBX 存储的结构。
Python 二进制处理
Maya 输出顶点动画到引擎
通过上面的文章,可以了解到 Python 写入二进制数据可以依赖内置的 struct 包。
写入数据需要了解 C++ 的数据类型的长度,按照长度和数据的写入顺序就可以用 Python 还原二进制数据。
通过 C++ 源码可以知道写入了这些数据
通过源码和二进制的对比,可以窥探到其中意思规则
比如利用 C++ 可以知道 Version 数据写入的时0x0351
的数据
1 | import struct |
使用 python 将
0x0351
转换为整形会返回Q\x03\x00\x00
正好和 二进制 数据是对应的,中间的 I 则表示是 Int 整形数据。
这个规律我经过我对二进制不少数据的解读总结出来的。但还有一些数据的含义是未知的。
后来在网上搜索了一下这个二进制规则,发现 Blender 官方提供了 FBX 二进制的解读。 链接
这个文章有非常完整的 FBX 二进制规则。
通过这个规则可以解读出整个 FBX 二进制数据的存储方式。
比如开头的
CommmType
前面有14位数据,除去开头第一个0x70
数据,后面的数据分别对应EndOffset
NumProperties
PropertyListLen
NameLen
完全和 Node Record Format 对应。
理解了数据的存储方式之后,就可以很顺利用 Python 写入同样的二进制数据。
FBXSDK 写入问题
只是我处理的时候发现 Python FBXSDK 无法直接写入 blob 二进制数据。
原因是FbxProperty.Set
不接受 bytes 数据。
这个部分是用 C++ 模板实现的,可能这个功能并没有映射给 Python FBXSDK,导致功能缺失。(也只能说这个功能少用得很)
为了保证数据的长度,我的处理方式是用 FbxString 写入相同长度的 字符串桩 ,比如一堆*
的字符串。
保存出去的 FBX 二进制文件再度用 Python 读取,然后将 字符串桩 替换为真实的 二进制 数据。
总结
这次深度挖掘了 FBX 二进制格式,对 FBX 的文件处理更加得心应手😄~