業務でアニメーションを作成していると
シーンのレンダリングを別マシンでやりたいことがでてきます
ある程度の規模のスタジオではレンダーファームソフトウェアで管理しているようですが
Blender関連でそういった情報はあまり見当たらないようです
自動的にシーンをレンダリングする環境をPowerShellで作成してみました
PowerShell環境の設定
PowerShell (パワーシェル) とは、PowerShell (パワーシェル) とは、Microsoftが開発したコマンドラインシェルとスクリプト言語の両方の機能を併せ持つ、タスク自動化と構成管理のためのフレームワークタスク自動化と構成管理のためのフレームワーク
とのことで 私自身 Windowsの環境でこれを使ってみるのが初めてなので
他にも多数のサイトで解説されている内容ですが設定から始めてみます
環境自体は最初から入っているようですが
セキュリティの都合で一度はコマンドを打つ必要があるようです

スタートメニューから検索する等して「管理者として実行する」でPowerShellを起動します
そこで表れるターミナル画面で
Set-ExecutionPolicy RemoteSigned
と入力してEnter

そして実行の確認を聞いてくるのでyと入力してEnterで
「自分で作ったスクリプトは実行できる」状態になるようです
基本的なスクリプト
今回は目的の挙動のものを作れればいいので 試しにXのGrokで
「特定のフォルダを監視して特定の3Dファイルが追加された場合にレンダリングを実行するという操作」
といった要件で生成させてみて動くよう微調整しました
# 監視するフォルダとファイル拡張子を設定
$folder = "C:\Path\To\Your\Folder" # 監視するフォルダのパス
$filter = "*.blend" # 監視するファイルの拡張子(例: .blend)
$blenderPath = "C:\Program Files\Blender Foundation\Blender\blender.exe" # Blenderの実行ファイルパス
# FileSystemWatcherの設定
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $folder
$watcher.Filter = $filter
$watcher.IncludeSubdirectories = $false # サブフォルダを監視する場合はtrue
$watcher.EnableRaisingEvents = $true # イベントを有効化
# ファイルが作成されたときのイベントハンドラ
$onCreated = Register-ObjectEvent $watcher "Created" -Action {
$filePath = $Event.SourceEventArgs.FullPath
Write-Host "新しいファイルが検出されました: $filePath"
# Blenderでレンダリングを実行
$blenderCommand = "$blenderPath -b $filePath -a"
Write-Host "レンダリングコマンドを実行: $blenderCommand"
Invoke-Expression $blenderCommand
}
# スクリプトを終了しないように待機
Write-Host "フォルダ $folder を監視中... Ctrl+C で終了"
while ($true) { Start-Sleep -Seconds 5 }
これの監視フォルダとBlenderの実行ファイルのパスをそれぞれの環境に合わせて変更して
テキストファイルのエンコードを「UTF-8(BOM付き)」で保存します

"BOM付き"でないと ターミナル画面で表示される文字列が文字化けしたり
実行そのものができないことがあるので注意が必要です
このファイルを エクスプローラー上で右クリックでPowerShellで実行等で実行して

監視フォルダに.blendファイルを移動させることでバックグラウンドでレンダリング処理が動きます
とりあえずの動作確認ができました
スクリプトの改良
前述のスクリプトでは
- ファイルパスの指定を動作環境毎に調整する必要がある
- 複数のファイルに対応していない
- 処理の終了したファイルが分かりにくい
- レンダリング中にも「レンダリングコマンドを実行:(実行中のコマンド)」のメッセージが出るだけで正常動作しているのかわかりにくい
- 必要なファイルの移動が終わっていない場合にもコマンドの実行が始まってしまう
といった欠点があります
なので
- 監視フォルダをスクリプトと同じ場所ににする
- 追加されたファイルをレンダーキューとして登録して順に処理する
- 終了した対象を処理済みフォルダに移動する
そして
- レンダリング処理をするのに.blendファイルを移動するのではなく
スクリプトと同じ場所にショートカットファイルを置くことで指定
という処理に変更してみました
# スクリプトのあるフォルダ
$scriptFolder = $PSScriptRoot
# 処理済みフォルダを相対パスで設定
$processedFolder = Join-Path $PSScriptRoot "Processed" # スクリプトと同じフォルダ内のProcessedフォルダ
$filter = "*.lnk" # ショートカットファイルを監視
$blenderPath = "blender\blender.exe" # Blenderの実行ファイルパス
# 処理済みフォルダが存在しない場合は作成
if (-not (Test-Path $processedFolder)) {
New-Item -Path $processedFolder -ItemType Directory
Write-Host "処理済みフォルダを作成しました: $processedFolder"
}
# 処理キュー(スレッドセーフなConcurrentQueueを使用)
$queue = New-Object System.Collections.Concurrent.ConcurrentQueue[psobject]
# COMオブジェクトを使用してショートカットのターゲットパスを取得する関数
function Get-ShortcutTarget {
param ($shortcutPath)
try {
$shell = New-Object -ComObject WScript.Shell
$shortcut = $shell.CreateShortcut($shortcutPath)
return $shortcut.TargetPath
} catch {
Write-Host "ショートカットのターゲット取得に失敗しました: $_"
return $null
} finally {
if ($null -ne $shell) {
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell) | Out-Null
}
}
}
# ファイル処理関数
function Process-File {
param ($filePath)
# ショートカットのターゲットパスを取得
$targetPath = Get-ShortcutTarget -shortcutPath $filePath
if ($null -ne $targetPath -and $targetPath -like "*.blend") {
#Write-Host "ターゲットは.blendファイルです: $targetPath"
# レンダリング実行中のメッセージ
Write-Host "レンダリングを開始します: $targetPath [$(Get-Date)]"
# Blenderでレンダリングを実行(リアルタイム出力)
$blenderCommand = "& `"$blenderPath`" -b `"$targetPath`" -a"
try {
$output = Invoke-Expression $blenderCommand 2>&1 | ForEach-Object { Write-Host $_; $_ }
Write-Host "レンダリングが完了しました: $targetPath [$(Get-Date)]"
Write-Host "フォルダ $scriptFolder を監視中... Ctrl+C で終了します "
# ショートカットを処理済みフォルダに移動
try {
$shortcutName = Split-Path $filePath -Leaf
$destinationPath = Join-Path $processedFolder $shortcutName
if (Test-Path $destinationPath) {
# 同名ファイルが存在する場合、ユニークな名ーに変更
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($shortcutName)
$extension = [System.IO.Path]::GetExtension($shortcutName)
$counter = 1
while (Test-Path $destinationPath) {
$destinationPath = Join-Path $processedFolder "$baseName-$counter$extension"
$counter++
}
}
Move-Item -Path $filePath -Destination $destinationPath -Force
#Write-Host "ショートカットを移動しました: $filePath -> $destinationPath"
} catch {
Write-Host "ショートカットの移動中にエラーが発生しました: $_"
}
} catch {
Write-Host "レンダリング中にエラーが発生しました: $_"
}
} else {
Write-Host "ターゲットが.blendファイルではありません: $targetPath"
}
}
# FileSystemWatcherの設定
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $scriptFolder
$watcher.Filter = $filter
$watcher.IncludeSubdirectories = $false # サブフォルダを監視しない
$watcher.EnableRaisingEvents = $true # イベントを有効化
# ファイルが作成されたときのイベントハンドラ
$onCreated = Register-ObjectEvent $watcher "Created" -Action {
$shortcutPath = $Event.SourceEventArgs.FullPath
Write-Host "新しいショートカットが検出されました: $shortcutPath"
# キューに追加
$queue.Enqueue($shortcutPath)
}
# キュー処理ループ
$processing = $false
Write-Host "フォルダ $scriptFolder を監視中 "
Write-Host "レンダリングする.blendファイルのショートカットを監視フォルダに置いてください"
Write-Host "Ctrl+C で終了します"
while ($true) {
$filePath = $null # 変数を初期化
if (-not $processing -and $queue.TryDequeue([ref]$filePath)) {
$processing = $true
Process-File -filePath $filePath
$processing = $false
}
Start-Sleep -Milliseconds 600
}
Blenderもポータブル版(Zip版)を利用して同じフォルダ内に置くことで任意のバージョンで処理ができるようにしています

Zip版のBlenderを解凍した時の名称は「blender-4.5.0-windows-x64」といった感じですが
複製したフォルダを単純なblenderというフォルダ名にしてあります
スクリプトを実行した状態で
エクスプローラー上でAltを押しながら.blendファイルをドラッグする等でスクリプトのあるフォルダにショートカットを作成してください
必要なファイルを順次追加していっても 順に処理されるはずです
アニメーションレンダリングの出力先は.blendファイルの設定のままになります
(.blendファイルを開いて「レンダー>アニメーションレンダリング」を実行したのと同じ状態)
メモ
当初は 監視フォルダに必要なファイル全てをコピーする予定だったので
そのためのコードの部分をメモ程度に残しておきます
動作の確認はしていません
書き込み状態の監視
# ファイルの書き込み完了を待つ
$retryCount = 5
$retryDelay = 1 # 秒
$fileReady = $false
for ($i = 0; $i -lt $retryCount; $i++) {
try {
$file = [System.IO.File]::Open($targetPath, 'Open', 'Read', 'None')
$file.Close()
$fileReady = $true
break
} catch {
Write-Host "ファイルがまだ書き込み中です。$($retryCount - $i)回再試行..."
Start-Sleep -Seconds $retryDelay
}
}
親フォルダの移動
# 親フォルダを取得
$parentFolder = Split-Path $targetPath -Parent
$parentFolderName = Split-Path $parentFolder -Leaf
$destinationPath = Join-Path $processedFolder $parentFolderName
# 処理済みフォルダに親フォルダを移動
try {
if (Test-Path $destinationPath) {
# 同名フォルダが存在する場合、ユニークな名前に変更
$baseName = $parentFolderName
$counter = 1
while (Test-Path $destinationPath) {
$destinationPath = Join-Path $processedFolder "$baseName-$counter"
$counter++
}
}
Move-Item -Path $parentFolder -Destination $destinationPath -Force
Write-Host "親フォルダを移動しました: $parentFolder -> $destinationPath"
} catch {
Write-Host "フォルダの移動中にエラーが発生しました: $_"
}