Blender 4.0でPython APIを使用して文字を表示してレンダリングする方法

## はじめに


この記事では、Blender 4.0でPython APIを使用して文字を表示し、レンダリングする手順を説明します。このプロセスは、コマンドラインからBlenderを操作し、自動化タスクを実行する際に役立ちます。

---

こちらの記事もおすすめ

https://hamaruki.com/generate-3d-models-from-images-using-wonder3d/

https://hamaruki.com/how-to-create-luminous-object-animations-with-blender-4-0-even-for-beginners/



## 必要なツール
- Blender 4.0
- 基本的なPythonの知識

## クリプトの準備

まず、Blenderをバックグラウンドで実行し、テキストオブジェクト、カメラ、ライトを作成してレンダリングするスクリプトを準備します。以下のスクリプトをテキストエディタにコピーし、`background_job.py`という名前で保存します。

```python
# This script is an example of how you can run blender from the command line
# (in background mode with no interface) to automate tasks, in this example it
# creates a text object, camera and light, then renders and/or saves it.
# This example also shows how you can parse command line options to scripts.
#
# Example usage for this test.
#  blender --background --factory-startup --python $HOME/background_job.py -- \
#          --text="Hello World" \
#          --render="/tmp/hello" \
#          --save="/tmp/hello.blend"
#
# Notice:
# '--factory-startup' is used to avoid the user default settings from
#                     interfering with automated scene generation.
#
# '--' causes blender to ignore all following arguments so python can use them.
#
# See blender --help for details.


import bpy


def example_function(text, save_path, render_path):
    # Clear existing objects.
    bpy.ops.wm.read_factory_settings(use_empty=True)

    scene = bpy.context.scene
    
    # Set the render engine to Eevee
    bpy.context.scene.render.engine = 'BLENDER_EEVEE'
    bpy.context.scene.eevee.use_bloom = True

    # Text Object
    txt_data = bpy.data.curves.new(name="MyText", type='FONT')
    txt_ob = bpy.data.objects.new(name="MyText", object_data=txt_data)
    scene.collection.objects.link(txt_ob)
    txt_data.body = text
    txt_data.align_x = 'CENTER'

    # Text Material (Emission)
    mat = bpy.data.materials.new(name="EmissionMaterial")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    nodes.clear()  # clear all the nodes
    emission = nodes.new(type='ShaderNodeEmission')
    emission.inputs[0].default_value = (1, 1, 1, 1)  # white color
    emission.inputs[1].default_value = 5.0  # strength
    output = nodes.new(type='ShaderNodeOutputMaterial')
    links = mat.node_tree.links
    link = links.new(emission.outputs[0], output.inputs[0])
    txt_ob.data.materials.append(mat)

    # Camera
    cam_data = bpy.data.cameras.new("MyCam")
    cam_ob = bpy.data.objects.new(name="MyCam", object_data=cam_data)
    scene.collection.objects.link(cam_ob)
    scene.camera = cam_ob
    cam_ob.location = 0.0, 0.0, 10.0

    # Sun Light
    light_data = bpy.data.lights.new("MyLight", 'SUN')
    light_ob = bpy.data.objects.new(name="MyLight", object_data=light_data)
    scene.collection.objects.link(light_ob)
    light_ob.location = 2.0, 2.0, 5.0
    light_ob.data.energy = 5.0  # adjust the energy for desired brightness

    bpy.context.view_layer.update()

    if save_path:
        bpy.ops.wm.save_as_mainfile(filepath=save_path)

    if render_path:
        render = scene.render
        render.use_file_extension = True
        render.filepath = render_path
        bpy.ops.render.render(write_still=True)


def create_simple_scene(save_path, render_path):
    # Clear existing objects
    bpy.ops.wm.read_factory_settings(use_empty=True)

    scene = bpy.context.scene

    # Create a simple cube
    bpy.ops.mesh.primitive_cube_add(size=2)
    cube = bpy.context.object

    # Camera
    cam_data = bpy.data.cameras.new("Camera")
    cam_ob = bpy.data.objects.new("Camera", cam_data)
    scene.collection.objects.link(cam_ob)
    scene.camera = cam_ob
    cam_ob.location = (5, -5, 5)
    cam_ob.rotation_euler = (0.785398, 0, 0.785398)

    # Light
    light_data = bpy.data.lights.new(name="Light", type='POINT')
    light_ob = bpy.data.objects.new("Light", light_data)
    scene.collection.objects.link(light_ob)
    light_ob.location = (0, 0, 5)

    # Update the scene
    bpy.context.view_layer.update()

    # Save and Render
    if save_path:
        bpy.ops.wm.save_as_mainfile(filepath=save_path)
    if render_path:
        render = scene.render
        render.use_file_extension = True
        render.filepath = render_path
        bpy.ops.render.render(write_still=True)

def main():
    import sys       # to get command line args
    import argparse  # to parse options for us and print a nice help message

    # get the args passed to blender after "--", all of which are ignored by
    # blender so scripts may receive their own arguments
    argv = sys.argv

    if "--" not in argv:
        argv = []  # as if no args are passed
    else:
        argv = argv[argv.index("--") + 1:]  # get all args after "--"

    # When --help or no args are given, print this help
    usage_text = (
        "Run blender in background mode with this script:"
        "  blender --background --python " + __file__ + " -- [options]"
    )

    parser = argparse.ArgumentParser(description=usage_text)

    # Example utility, add some text and renders or saves it (with options)
    # Possible types are: string, int, long, choice, float and complex.
    parser.add_argument(
        "-t", "--text", dest="text", type=str, default="Hello World",
        help="This text will be used to render an image",
    )

    parser.add_argument(
        "-s", "--save", dest="save_path", metavar='FILE', default="/tmp/hello.blend",
        help="Save the generated file to the specified path",
    )
    parser.add_argument(
        "-r", "--render", dest="render_path", metavar='FILE', default="/tmp/hello2",
        help="Render an image to the specified path",
    )
    
    print(">>> args")
    args = parser.parse_args(argv)  # In this example we won't use the args
    
    print(args)

    if not args.text:
        print("Error: --text=\"some string\" argument not given, aborting.")
        parser.print_help()
        print("see you...")
        return

    # Run the example function
    example_function(args.text, args.save_path, args.render_path)
    #create_simple_scene(args.save_path, args.render_path)

    print("Batch job finished, exiting")
    print("batch job finished, exiting")


if __name__ == "__main__":
    main()

```


## スクリプトの解説

スクリプトは大きく分けて3つの部分から構成されます。
### `example_function`

この関数は、テキストオブジェクトを作成し、EeveeレンダーエンジンでBloom効果を有効にして、シーンをレンダリングします。
- テキストオブジェクトの作成
- エミッションマテリアルの追加
- カメラとサンライトの設定
- レンダリングと保存の設定
###  `main`

この関数は、コマンドライン引数を解析し、上記の`example_function`を実行します。
- 引数解析 
- `example_function`の呼び出し

### スクリプトの実行部

最後に、スクリプトが直接実行された場合に`main`関数を呼び出します。

## コマンドラインからの実行

Blenderをインストールしたディレクトリに移動し、以下のコマンドを実行します。

```bash
blender --background --factory-startup --python /path/to/background_job.py -- --text="Your Text" --render="/path/to/render.png" --save="/path/to/save.blend"
```

 
- `--background` はBlenderをバックグラウンドモードで起動します。 
- `--factory-startup` はユーザー設定を無視して、デフォルト設定でBlenderを開始します。 
- `--python` はPythonスクリプトを指定します。 
- `--text`, `--render`, `--save` はスクリプトに渡す引数です。

## 結果の確認

コマンドを実行した後、指定したレンダーパスに画像ファイルが、保存パスにBlenderファイルが生成されていることを確認します。


## まとめ

この記事では、BlenderのPython APIを使用して、テキストを含むシンプルな3Dシーンを自動的に作成し、レンダリングする方法を紹介しました。この技術は、3Dグラフィックスの自動化やバッチ処理に非常に有効です。今後は、Twitter用に作成した動画をインスタグラムやTiktok用に変換するスクリプトを作成していきます。



## リポジトリ

https://github.com/Sunwood-ai-labs/VideoPortraitMaker/tree/main

## 参考サイト

https://bluebirdofoz.hatenablog.com/entry/2020/06/19/002435

https://note.com/piano_music/n/n9dce46416492