PythonでFBXファイルを ASCII <--> BINARY 変換


任意ディレクトリ内にある、
全てのfbxファイルを
ASCIIフォーマット、又はBINARYフォーマットに
一括で変換したい時があったので、
python fbx sdk でやってみました。


# 環境

- macOS Catalina
- python 3.7.0
- FBX Python SDK (2020.0.1)


# FBX Python Bindings

公式ドキュメントを参考に、FBX Python Bindings を環境に入れます。
[Installing Python FBX (2020.0.1)](http://help.autodesk.com/view/FBX/2020/ENU/?guid=FBX_Developer_Help_scripting_with_python_fbx_installing_python_fbx_html)

ちなみに、その時の最新版をみたい場合は、
下記のようにいくと取りにいけるようです。
https://www.autodesk.com/products/fbx/overview -> GET FBX SDK -> FBX Python Bindings

# 変換する

FBXファイルのフォーマット変換は、
こんな感じでできるようです。

[こちらを参考にさせてもらいました](https://github.com/segurvita/fbx_sdk_python_sample)

```python:任意fbxファイルのフォーマットを変換する
from fbx import FbxManager
from fbx import FbxScene
from fbx import FbxImporter
from fbx import FbxExporter

FBX_MANAGER = FbxManager.Create()

def _get_file_fomrat(format_name):
    io_plugin_registry = FBX_MANAGER.GetIOPluginRegistry()
    for format_id in range(io_plugin_registry.GetWriterFormatCount()):
        if io_plugin_registry.WriterIsFBX(format_id):
            desc = io_plugin_registry.GetWriterFormatDescription(format_id)
            if format_name in desc:
                return format_id
    # Default format is auto
    return -1

def _convert(path, file_format_id):
    scene = FbxScene.Create(FBX_MANAGER, "")
    importer = FbxImporter.Create(FBX_MANAGER, "")
    importer.Initialize(path, -1)
    importer.Import(scene)
    importer.Destroy()
    exporter = FbxExporter.Create(FBX_MANAGER, "")
    exporter.Initialize(path, file_format_id)
    exporter.Export(scene)
    exporter.Destroy()
    scene.Destroy()

# 対象のfbxファイルを、バイナリに変換する
_convert('target_fbx_file_path', _get_file_fomrat('binary'))
# 対象のfbxファイルを、アスキーに変換する
_convert('target_fbx_file_path', _get_file_fomrat('ascii'))
```

# 変換前に、対象がバイナリフォーマットか確認する

同じフォーマットに変換するのは無駄なので、
目的と同じフォーマットへの変換を除外するようにします。

[こちら](https://code.blender.org/2013/08/fbx-binary-file-format-specification/)に、非公式のようですがバイナリフォーマットの
ヘッダー情報が載っているので参考にしてみます。
※[こちらにもある](https://wiki.fileformat.com/3d/fbx/)
※公式の情報が見当たらない。。。

```python:任意fbxファイルがバイナリフォーマットであるか確認する
FBX_BINARY_SIGNATURE = b"Kaydara FBX Binary  \x00\x1A\x00"
FBX_BINARY_SIGNATURE_LENGTH = len(FBX_BINARY_SIGNATURE)

def _is_binary_fbx(path):
    with open(path, 'rb') as file:
        return file.read(FBX_BINARY_SIGNATURE_LENGTH) == FBX_BINARY_SIGNATURE
    return False
```

先頭23bytesは固定のバイナリデータなので、
必要な分だけファイル読み込みをして比較しています。

変換メソッドの最初に、この判定を仕込むと、
目的のフォーマットと違うファイルだけ変換されるようになります。

```python
def _convert(path, file_format_id, required_binary):
    if _is_binary_fbx(path) != required_binary:
        scene = FbxScene.Create(FBX_MANAGER, "")
        importer = FbxImporter.Create(FBX_MANAGER, "")
        importer.Initialize(path, -1)
        importer.Import(scene)
        importer.Destroy()
        exporter = FbxExporter.Create(FBX_MANAGER, "")
        exporter.Initialize(path, file_format_id)
        exporter.Export(scene)
        exporter.Destroy()
        scene.Destroy()
```

# マルチプロセスで高速化する

1fbxの変換を1jobとして、
ファイル単位での変換を並列化して
高速化をねらってみます。

concurrent.futures.ProcessPoolExecutorを使います。
python 3.x系が必要になります。

```python:並列化して高速化
import concurrent.futures

with concurrent.futures.ProcessPoolExecutor() as executor:
    executor.submit(_convert, 'target_fbx_file_path', _get_file_fomrat('ascii'), false)
```

# Walkするー

任意ディレクトリ内を全て対象にしたいので、
os.walk します。

```python
import os
import sys

with concurrent.futures.ProcessPoolExecutor() as executor:
    file_format_id = _get_file_fomrat('binary')
    for root, _dirs, files in os.walk('target_directory_path'):
        for filename in files:
            if filename.endswith('.fbx'):
                path = os.path.join(root, filename)
                executor.submit(_convert, path, file_format_id, true)
```

これで少しは早く、一括変換が可能になったと思います。

# 出力ファイルの FBX File Version を指定する

変換後のファイルは使っているFBX SDKのバージョンに依存しますが、
下記のように指定することができます。

```python
from fbx import FbxSceneRenamer

# FBXVersion: 7500 になる
exporter.SetFileExportVersion('FBX201600', FbxSceneRenamer.eNone)
# FBXVersion: 7200 になる
exporter.SetFileExportVersion('FBX201200', FbxSceneRenamer.eNone)
```

指定できるバージョンは
[fbxio.h](http://help.autodesk.com/view/FBX/2020/ENU/?guid=FBX_Developer_Help_cpp_ref_fbxio_8h_html) で定義されている
FBX_xxxx_xx_COMPATIBLE 系の文字列です。


# まとめ

まとめると、下記にアップしたもののようになります。
https://github.com/yassy0413/fbx-file-utility-python/blob/master/fbx_file_format_utility.py