前言

  最近遇到了一个比较难搞的需求,好不容易解决了,在这里记录一下。
  需求是这样的,公司有大佬在 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 打开。

image

  打开之后需要将平台工具集升级到最新的 VS 版支持的工具集,默认是 2012 工具集太过古老了。
  改完之后本想着愉快地编译,然而这样会报错。

image

  这个问题只能归结为新的平台工具集已经去掉了支持,但是头文件依旧引入相应的文件,解决也很简单,将报错的那一行注释即可。

image

  编译完成会默认去到 bin\x64\plugins 的目录,这样只要重启 motion builder 就能加载到这个 dll 了。

image

  这样将这个图标拖拽到场景就可以创建一个 device.
  将这场景以 ascii 的格式保存。
  检查保存的 FBX 文件,可以看到 FBXStore 的写入逻辑,会将信息写入到节点的 MoBuAttrBlindData 属性上

image

  存储出来可以看到相应的信息。
  这里官方的插件将信息转成了 KString 所以里面的信息也是以 FBX ASCII 的形式存在。
  但如果将 FBX 存成 Binary 模式,然后再用 Python FBXSDK 来转存成 ASCII 的话,这些 FBXStore 的数据会转成 base64 的二进制数据。

image

  如果用 base64 解码,可以看到里面存储的二进制数据。

1
2
3
4
5
6
7
8
import base64

in_data = r"cBsAAAABAAAABQAAAAhDb21tVHlwZUkQAAAANAAAAAEAAAAFAAAAB1ZlcnNpb25JUQMAAGIAAAADAAAAGwAAAAZTZXJpYWxJAQAAAEkAlgAAUwwAAABTY2VuZQABTW9kZWycAAAABAAAACQAAAAJU2ltdWxhdG9yRAAAAAAAAPA/RAAAAAAAAPA/RAAAAAAAAAAARAAAAAAAAAAAwwAAAAIAAAATAAAAB05ldHdvcmtTCQAAADEyNy4wLjAuMUm5CwAA9gAAAAIAAAAaAAAADFNoYXJlZE1lbW9yeVMKAAAASE1DX1NITV9WMVMGAAAAMDAwMDAwFAEAAAEAAAAJAAAACFNldHRpbmdzRAAAAAAAAAAAMgEAAAEAAAAFAAAADFNhbXBsaW5nTW9kZUkAAAAAVAEAAAEAAAAFAAAAEEluc3RydW1lbnRBY3RpdmVJAQAAAG0BAAABAAAABQAAAAdWZXJzaW9uSVEDAACOAQAAAQAAAAUAAAAPTGVuc1RhYmxlTG9hZGVkSQAAAADbAQAABgAAAC4AAAASTWFudWFsTW9kZVNldHRpbmdzSQAAAABEBzDzdETpTEBEAAAAAACARkBJAAAAAEQAAAAAAAAAAEQAAAAAAAAAAP4BAAACAAAACgAAAAxJbnZlcnRWYWx1ZXNJAAAAAEkAAAAAHwIAAAEAAAAJAAAAC0FzcGVjdFJhdGlvRFVVVVVVVfU/QwIAAAEAAAAJAAAADlpvb21NdWx0aXBsaWVyRAAAAAAAAPA/aAIAAAEAAAAJAAAAD05vZGFsTXVsdGlwbGllckQAAAAAAABZQIgCAAABAAAABQAAAA5BbmdsZUluRGVncmVlc0kAAAAAywIAAAQAAAAkAAAAEkVuY29kZXJDYWxpYnJhdGlvbkQAAADgzxJjQUQAAADgzxJjwUQAAADgzxJjQUQAAADgzxJjwRsDAAAGAAAANgAAAA1TdHVkaW9PZmZzZXRzRAAAAAAAAAAARAAAAAAAAAAARAAAAAAAAAAARAAAAAAAAAAARAAAAAAAAAAARAAAAAAAAAAANAMAAAEAAAAFAAAAB1ZlcnNpb25JUQMAAE8DAAABAAAABQAAAAlTeW5jRGVsYXlJBAAAAAAAAAAAAAAAAAAAAAA="

output_path = r"G:\_TEMP\2022-11-1\test_device.bin"

with open(output_path,'wb')as wf:
wf.write(base64.b64decode(in_data))

image

  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 还原二进制数据。


image

  通过 C++ 源码可以知道写入了这些数据

image

  通过源码和二进制的对比,可以窥探到其中意思规则
  比如利用 C++ 可以知道 Version 数据写入的时 0x0351 的数据

1
2
3
>>> import struct
>>> struct.pack("i",0x0351)
b'Q\x03\x00\x00'

  使用 python 将 0x0351 转换为整形会返回 Q\x03\x00\x00

image

  正好和 二进制 数据是对应的,中间的 I 则表示是 Int 整形数据。
  这个规律我经过我对二进制不少数据的解读总结出来的。但还有一些数据的含义是未知的。

  后来在网上搜索了一下这个二进制规则,发现 Blender 官方提供了 FBX 二进制的解读。 链接
  这个文章有非常完整的 FBX 二进制规则。
  通过这个规则可以解读出整个 FBX 二进制数据的存储方式。

  比如开头的 CommmType 前面有14位数据,除去开头第一个 0x70 数据,后面的数据分别对应 EndOffset NumProperties PropertyListLen NameLen
  完全和 Node Record Format 对应。

  理解了数据的存储方式之后,就可以很顺利用 Python 写入同样的二进制数据。

FBXSDK 写入问题

  只是我处理的时候发现 Python FBXSDK 无法直接写入 blob 二进制数据。
  原因是 FbxProperty.Set 不接受 bytes 数据。

image

  这个部分是用 C++ 模板实现的,可能这个功能并没有映射给 Python FBXSDK,导致功能缺失。(也只能说这个功能少用得很)
  为了保证数据的长度,我的处理方式是用 FbxString 写入相同长度的 字符串桩 ,比如一堆 * 的字符串。
  保存出去的 FBX 二进制文件再度用 Python 读取,然后将 字符串桩 替换为真实的 二进制 数据。

总结

  这次深度挖掘了 FBX 二进制格式,对 FBX 的文件处理更加得心应手😄~