アニメーションスタジオでは、各ショットにどのアセットを登場させる必要があるかを追跡するために ブレイクダウンリスト が使われます。
想像してみてください。最新の制作で、VFXアーティストとしてBlenderの何もないビューポートを前にしているあなた。マネージャーが、アセット、ショット、タイミングの手がかりが詳細に書かれたリストを渡し、"これをBlenderのシーンにして。"と言ってきます。
最初に思いつくのは、アセットマネージャーにログインして全てのオブジェクトを手作業で配置することかもしれません。しかし、数百のアセットが含まれる複雑なシーンはどうでしょうか?
ここで役立つのが、シンプルな自動化です。PythonのBlenderスクリプトを使えば、Kitsuのブレイクダウンデータを読み取り、数分で初期シーンを自動生成できます。
この記事では、完全な例を順に説明します。Gazu のPython APIでブレイクダウンを取得し、新しいBlenderシーンを作成し、アセットをダウンロードして、Blenderにインポートします。最後には、レイアウトまたはアニメーションにすぐ使える状態で、シーンを自動構築する最小限のパイプラインが手に入ります。
このガイドで紹介している例の統合に関する完全なソースコードは、GitHubで確認できます:
🔗 https://github.com/cgwire/blender-kitsu-automated-scene-composition
1. ブレイクダウンを取得する
すべての3Dショットは何もない状態のキャンバスから始まりますが、そのキャンバスを埋めるための指示はKitsuにすでに存在します。 the breakdown が、ステージ上に必要なものを正確に指示します。これにより、アニメーターが作業を始める前に確認できます。
典型的なブレイクダウンは、スクリプトがシーンを組み立てるために必要な重要な物語的な文脈を提供します。ステージ(シーケンス情報に保存されている開始・終了フレーム、尺、およびその他の注記)と、キャスト(キャラクターモデル、小道具、環境アセットの実際のブレイクダウン)です。
コードを書く前に、Kitsuのダッシュボードでブレイクダウンを定義する必要があります。ここでは、3Dアセットのライブラリを、必要とされる特定のショットに手動でリンクします。ここで新しいモデルを作るわけではありません。既存の「俳優」(アセット)を、特定のショットに割り当てるだけです:
- 制作プロジェクトを開く - Kitsuでプロジェクトに移動し、Shots タブを開きます。
- キャスティングシートを探す - Breakdown タブを探します(通常は右側のパネル、またはバージョンに応じて専用タブにあります)。
- ショットを選択する - 詳細なキャスティング表示を開くために、入力したい特定のショット(例:
SH01)をクリックします。 - アセットを割り当てる - 右側のパネルで、+(プラス) ボタン、または「Add Asset」をクリックします。ここで各アセットに必要な数量を指定することもできます。

Assets ページが、使用したいモデル(Characters、Propsなど)ですでに埋まっていることを確認してください。
保存を押すと、リンクが確立されます。これで、PythonスクリプトがGazuに「このショットに何が入っている?」と尋ねると、Kitsuは先ほど割り当てたアセットのリストを返します。あなたのPythonスクリプトはブリッジの役割を担い、そのキャスティング情報を解析して自動的にBlenderのビューポートへ反映します。
ローカルの開発環境が必要なら、 Custom DCC BridgeガイドでDockerからKitsuをインストールする方法 を確認してください。
Kitsuはデータを保持していますが、それを取得する手段が必要です。そこで登場するのが、KitsuのREST API向けのPython SDK Gazu です:
import gazugazu.set_host("<http://localhost/api>") user = gazu.log_in("admin@example.com", "mysecretpassword")
projects = gazu.project.all_projects() project = projects0
sequence = gazu.shot.get_sequence_by_name(project, "SQ01") shot = gazu.shot.get_shot_by_name(sequence, "SH01")
assets = gazu.casting.get_shot_casting(shot)
ローカルのKitsuインスタンスに接続し、次に最初の制作(名前で取得することもできます)と、キャスティングを取得したいショットを選びます。
このショットIDを使うことで、対応するアセットのキャスティング(ブレイクダウンリスト)を取得できます。
2. ブレイクダウンからアセットを取得する
いま、誰がショットに入るかは分かりました。次は どのように見えるか を把握する必要があります。
Kitsuでは、アセットに応じて利用できるプレビュー用のファイルが複数存在し、リビジョンによって使い分けられます。スクリプトは、各アセットの最新リビジョンを取得できるように、このデータを辿る必要があります:
local_paths = for asset in assets: tasks = gazu.task.all_tasks_for_asset(asset"asset_id") last_task = max(tasks, key=lambda x: x"updated_at")preview_files = gazu.files.get_all_preview_files_for_task(last_task) last_preview_file = max(preview_files, key=lambda x: x"updated_at")
download_dir = "./previews" os.makedirs(download_dir, exist_ok=True)
save_path = os.path.join( download_dir, last_preview_file"original_name" + "." + last_preview_file"extension", ) gazu.files.download_preview_file(last_preview_file, save_path)
local_paths.append(save_path)
各アセットについて、あらゆる種類('Modeling'、'Animation'など)や状態('done'、'todo'...)に該当するすべてのタスクのリストを取得します。このリストをフィルタして、最も更新が新しいタスクを取り出します。
このタスクIDを使うことで、最新の対応するプレビューファイルのリビジョンを取得し、ローカルフォルダ previews にダウンロードできます。これらのダウンロードパスは、インポート手順のためにメモリに保持します。
このループの最後には、データベースのエントリが、Blenderが取り込める実体のあるモデルファイルへと変換され、ハードドライブ上に用意できています。
3. 新しいBlenderシーンを作成する
アセットファイルは安全にダウンロード済みなので、次の作業は、それらを新しいキャストメンバーとして受け取るためのBlender環境の準備です。
BlenderのネイティブPython APIである bpy モジュールは、アプリケーションのあらゆる要素を操作するためのコマンドコンソールとして働きます。
Kitsuのアセットをインポートする前に、新しいBlenderシーンに付属しているデフォルトのオブジェクトを削除する必要があります。このシンプルなチュートリアルでは、デフォルトの Cube を対象にします。これは、デフォルトのCameraとLight以外で存在することが多い唯一のオブジェクトだからです:
bpy.data.objects.remove(bpy.data.objects.get("Cube"), do_unlink=True)do_unlink=True フラグは、他のどのオブジェクトからも使われていないなら、オブジェクトのデータブロック(メッシュデータなど)をBlenderに完全に削除させ、後に残る不要なもの(クラッター)を作らないようにします。
これで、インポートしたアセットがそれぞれの場所に配置される準備が整いました。
4. アセットファイルをインポートする
いよいよ成果です!Kitsuからダウンロードしたファイルは、ジオメトリと基本マテリアルを扱える標準化されたインターチェンジ形式 .glb です。そのため、Blenderの専用 gltf インポートオペレーターを使用します。
重要なポイントは、ダウンロードしたアセットへの正しい 絶対ファイルパス(glb_path)を渡すことです。幸いなことに、それらは先ほどのコードスニペットで保存されています:
for path in local_paths: if path.lower().endswith((".glb")): print(f"Importing: {path}") bpy.ops.import_scene.gltf(filepath=path)
print("All preview GLB files imported successfully!")
bpy.ops.import_scene.gltf() が実行されると、Blenderはファイルを読み取り、現在のシーンに対応する オブジェクト、メッシュ、および マテリアル を自動で作成します。
インポートされたアセットは、ワールド原点(0, 0, 0)に配置された、完成済みのBlenderオブジェクトになります。以降のパイプライン手順の準備ができました。
5. シーンを保存する
このパイプラインの最後のステップは、組み立てたレイアウトを恒久的で、バージョン管理可能なファイルとして保存することです。この手順をせずにBlenderを閉じると、すべての自動作業が失われます。そこで、bpy.ops.wm.save_as_mainfile オペレーターを使います。これは、Blenderのインターフェースで File > Save As をクリックするのと同等の、プログラム上の操作です:
scene_save_dir = "./" os.makedirs(scene_save_dir, exist_ok=True)blend_filename = "SH01.blend" blend_path = os.path.join(scene_save_dir, blend_filename)
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
結果として、SH01.blend という新しいBlenderファイルが作成されます。これはKitsuの ブレイクダウン要件 を正確に反映しており、次の部署が受け取る準備が整っています。

6. ユーザーフレンドリーなアドオン
スクリプトは期待どおりに動きますが、アーティストはどうでしょう?スクリプトの実行方法を知っている人ばかりではありません。
コードを少しだけ変更して、 Blenderのアドオンに変える ことにしましょう:
bl_info = { "name": "Kitsu Shot Auto-Importer", "description": "Pick a project and shot and auto-import the latest preview assets", "author": "cgwire", "version": (1, 0, 0), "blender": (3, 0, 0), "location": "Viewport > N-Panel > Kitsu", "category": "Import-Export", }import os import sys
sys.path.append("~/.local/lib/python3.11/site-packages")
import bpy import gazu from bpy.props import EnumProperty, StringProperty
def get_projects(): try: projects = gazu.project.all_projects() return (p"id", p"name", "") for p in projects except: return
def get_sequences(project_id): if not project_id: return try: seqs = gazu.shot.all_sequences_for_project(project_id) return (s"id", s"name", "") for s in seqs except: return
def get_shots(sequence_id): if not sequence_id: return try: shots = gazu.shot.all_shots_for_sequence(sequence_id) return (s"id", s"name", "") for s in shots except: return
class KITSU_Props(bpy.types.PropertyGroup): project: EnumProperty(name="Project", items=lambda self, context: get_projects())
sequence: EnumProperty( name="Sequence", items=lambda self, context: get_sequences(self.project) )
shot: EnumProperty( name="Shot", items=lambda self, context: get_shots(self.sequence) )
class KITSU_OT_import_shot(bpy.types.Operator): bl_idname = "kitsu.import_shot_assets" bl_label = "Import Shot Assets" bl_description = ( "Download and import latest preview GLB/GLTF files for selected shot" )
def execute(self, context): props = context.scene.kitsu_props
# Fetch shot data shot = gazu.shot.get_shot(props.shot) assets = gazu.casting.get_shot_casting(shot)
download_dir = os.path.join(bpy.app.tempdir, "kitsu_previews") os.makedirs(download_dir, exist_ok=True)
local_paths =
for asset in assets: tasks = gazu.task.all_tasks_for_asset(asset"asset_id") if not tasks: continue
last_task = max(tasks, key=lambda x: x"updated_at") preview_files = gazu.files.get_all_preview_files_for_task(last_task) if not preview_files: continue
last_preview = max(preview_files, key=lambda x: x"updated_at")
save_path = os.path.join( download_dir, last_preview"original_name" + "." + last_preview"extension", )
gazu.files.download_preview_file(last_preview, save_path) local_paths.append(save_path)
# Clean default cube obj = bpy.data.objects.get("Cube") if obj: bpy.data.objects.remove(obj, do_unlink=True)
# Import GLB/GLTF assets for path in local_paths: if path.lower().endswith((".glb", ".gltf")): bpy.ops.import_scene.gltf(filepath=path)
# Auto-save blend file save_dir = os.path.join(os.path.expanduser("~"), "kitsu_scenes") os.makedirs(save_dir, exist_ok=True)
blend_path = os.path.join(save_dir, f"{shot'name'}.blend") bpy.ops.wm.save_as_mainfile(filepath=blend_path)
self.report({"INFO"}, f"Imported assets and saved: {blend_path}") return {"FINISHED"}
class KITSU_PT_panel(bpy.types.Panel): bl_label = "Kitsu Auto-Importer" bl_idname = "KITSU_PT_auto_importer" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Kitsu"
def draw(self, context): props = context.scene.kitsu_props layout = self.layout
layout.separator() layout.prop(props, "project") layout.prop(props, "sequence") layout.prop(props, "shot")
layout.separator() layout.operator("kitsu.import_shot_assets", icon="IMPORT")
classes = ( KITSU_Props, KITSU_OT_import_shot, KITSU_PT_panel, )
def register(): for c in classes: bpy.utils.register_class(c) bpy.types.Scene.kitsu_props = bpy.props.PointerProperty(type=KITSU_Props)
def unregister(): for c in classes: bpy.utils.unregister_class(c) del bpy.types.Scene.kitsu_props
if name == "main": register()
これで、制作プロジェクト、シーケンス、ショットを手動で選択して、ブレイクダウンデータを取得し、対応するキャスティングを現在のBlenderビューポートにインポートできます。

ロジックはシンプルです。gazu の同じコードを使ってドロップダウンメニューを埋め込み、それらをビューポート内のパネルにまとめます。import ボタンを押すと、対応するブレイクダウンアセットがすべてダウンロードされ、現在のワークスペースにインポートされます。
sys.path.append("~/.local/lib/python3.11/site-packages") を追加することで、BlenderがあなたのシステムのPythonインストールを使って gazu のような外部ライブラリを読み込めるようになります。Blenderには独自に隔離されたPython環境が同梱されているため、パッケージのインストール管理は不便になりがちです。パスを拡張することで、Blenderにローカルのモジュールも確認させるだけで済みます。自分の環境に合わせて、このパスを調整してください。
まとめ
Kitsuからブレイクダウンリストを直接取得し、Blenderをスクリプトで制御してシーンを組み立てることで、繰り返し発生する手作業をなくし、すべてのショット間でアセットの整合性を保てます。これは単に時間を節約するだけでなく、人為的なミスを減らし、プロデューサーが必要とする正しいアセットのバージョンとシーン構成から、すべてのアーティストが作業を開始できるようにします。こうすれば、10ショットでも10,000ショットでも、同じ信頼性で対応できます。
とはいえ、私たちの言葉だけを信じる必要はありません。 Githubリポジトリをクローンして実際に試してみてください!
このワークフローは、アニメーション中に作成される新しいリビジョンから自動プレビューやレポートを生成したり、アセット情報を更新したりすることで拡張できます。


