ここのところ3DCGやPython関連が続きHoloやUnityに触れることがなかなかできていないですが、本日もPython系です。
先日Pythonを使用してGIF作成を行いました。
こちらでは任意のパスからシーケンス画像(連番画像)を読み取り、任意の長さでGIF画像を作成していました。
結構筆者としては気に入っているのですが、シーケンス画像はBlenderでレンダリングして作成していました。
ということで今回はBlenderでGIF書き出しを行いたいと思います。
〇Blenderのレンダリングエクスポート形式
BlenderではデフォルトではGIF形式のサポートをサポートしていません。

まずはBlenderのアドオンとしてレンダリングのエクスポート形式にGIFを選択できるようにしたかったのですが、Blenderの提供されているAPIではこの追加は不可能なようです。
そのため通常通り3Dビューポートに表示されるアドオンとして作成していきます。
基本コードを過去の記事から流用します。
#アドオンの定義
bl_info = {
"name": "GIFMaker",
"blender": (3, 5, 0),
"category": "Object",
}
import bpy
# Define a new operator (action or function)
class GIFExporterOperator(bpy.types.Operator):
bl_idname = "object.gif_maker"
bl_label = "Hello, World!"
def execute(self, context):
self.report({'INFO'}, "Hello, World!")
return {'FINISHED'}
# Define a new operator to print text
class PrintTextOperator(bpy.types.Operator):
bl_idname = "object.print_text"
bl_label = "Print Text"
text_input: bpy.props.StringProperty(name="Input Text")
def execute(self, context):
print("Input Text:", self.text_input)
return {'FINISHED'}
# Define a new UI panel
class GIFExporterPanel(bpy.types.Panel):
bl_label = "GIF Exporter"
bl_idname = "OBJECT_PT_hello_world"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'GIFExporter'
def draw(self, context):
layout = self.layout
# Add a text field to the panel
layout.prop(context.scene, "my_text_input")
# Add the print operator to the panel
layout.operator("object.print_text")
# Registration
def register():
bpy.utils.register_class(GIFExporterOperator)
bpy.utils.register_class(PrintTextOperator)
bpy.utils.register_class(GIFExporterPanel)
bpy.types.Scene.my_text_input = bpy.props.StringProperty(name="Text Input")
def unregister():
bpy.utils.unregister_class(GIFExporterOperator)
bpy.utils.unregister_class(PrintTextOperator)
bpy.utils.unregister_class(GIFExporterPanel)
del bpy.types.Scene.my_text_input
if __name__ == "__main__":
register()
この状態では3Dビューポート上にGIFExporterというタブが表示されます。

これをベースにして実装を行います。
〇レンダリング
今回のアドオンとしてBlenderでいきなりGIF画像の作成はできないので一度通常のレンダリングを行いシーケンス画像を作成し、仮フォルダに配置、そこからGIFを作成し仮フォルダのシーケンス画像を削除というプロセスを実装していきます。
BlenderでPyhonコードからレンダリングを実行するにはこちらの記事で以前取り組んでいましたのでこちらを流用します。
レンダリングを行うためには次のコードを実行します。
import bpy # レンダリングの実行 bpy.ops.render.render(write_still=True)
例えば上記処理に組み込みボタンを選択した際にレンダリングが実行されるという実装は次のようになります。
class PrintTextOperator(bpy.types.Operator):
bl_idname = "object.print_text"
bl_label = "Print Text"
text_input: bpy.props.StringProperty(name="Input Text")
def execute(self, context):
print("Input Text:", self.text_input)
bpy.ops.render.render(write_still=True) #追加
return {'FINISHED'}
これによって基本的にはC\temp内にレンダリング結果が出力されます。

この時にレンダリングされるのは現在選択されているキーフレームの1画像となります。つまり、現在選択されているキーフレームのシーケンス画像をレンダリングしたい場合は次のようにループ処理を行うとよさそうです。
今回は次のような新規クラスを作成しました。
class LoopRenderOperator(bpy.types.Operator):
bl_idname = "object.loop_render"
bl_label = "Loop Render"
start_frame: bpy.props.IntProperty(name="Start Frame", default=1)
end_frame: bpy.props.IntProperty(name="End Frame", default=20)
step: bpy.props.IntProperty(name="Step", default=1)
def execute(self, context):
for frame in range(self.start_frame, self.end_frame + 1, self.step):
bpy.context.scene.frame_set(frame)
bpy.ops.render.render(write_still=True)
return {'FINISHED'}
ここではfor文を用いてStart FrameからEnd Frameまでの間のレンダリングを実行します。
しかしこのままではレンダリングしたファイル名が同じのため上書きされてしまいます。
次のようにパスを明示して上書きされないようにします。
def execute(self, context):
for frame in range(self.start_frame, self.end_frame + 1, self.step):
bpy.context.scene.frame_set(frame)
bpy.context.scene.render.filepath = f"C/temp/render_{frame}"
bpy.ops.render.render(write_still=True)
return {'FINISHED'}

これを実行するとCドライブ直下のtmpフォルダに20個のレンダリング画像が生成されます。

以上でスクリプトを用いたシーケンス画像の生成できました。
次回はこの生成したファイルを用いてGIF画像を作成していきます。