[Maya Python] コマンドからDeadlineにサブミットする

## はじめに
2022年8月に無償化されたAWS Thinkbox製品の「**Deadline**」ですが、ようやくじっくり触り始めたので備忘録を残しておきます。

コマンドからジョブをサブミットするには、Deadline Clientに付属している`deadlinecommand.exe`を使用します。実際は`deadlinecommand.exe`を叩くだけですのでMayaもPythonも関係なしに動くのですが、そのままでは公式ドキュメントのままなのでより実務で使えそうなMaya & Pythonの例で記載していきます。

### 公式ドキュメント
* [Manual Job Submission — Deadline 10.2.0.10 documentation](https://docs.thinkboxsoftware.com/products/deadline/10.2/1_User%20Manual/manual/manual-submission.html)
* [Command — Deadline 10.2.0.10 documentation](https://docs.thinkboxsoftware.com/products/deadline/10.2/1_User%20Manual/manual/command.html)

### 環境
* Windows 10 Pro
* Deadline 10.2.0.1
* Maya 2023 (Python 3.9.7)

## コマンドに直接フラグを付けるパターン
ドキュメントにもある通り`deadlinecommand`に`-SubmitCommandLineJob`フラグをつけてその後にレンダリングコマンドとその他オプションを付け加えます。subprocess[^1]にはコマンドをスペース区切りにしたリストで渡します。

[^1]:本記事ではPython3を使用していますが、Maya2020以前のPython2ではsubprocessに`run`がありませんので`call`などで置き換える必要があるかと思います。

```python
import os
import subprocess

deadlinecommand_exe = os.path.join(os.environ['DEADLINE_PATH'], 'deadlinecommand.exe')
# Result: 'C:\\Program Files\\Thinkbox\\Deadline10\\bin\\deadlinecommand.exe' # 

render_exe = os.path.normpath(os.path.join(os.environ['MAYA_LOCATION'], 'bin', 'Render.exe'))
# Result: 'C:\\Program Files\\Autodesk\\Maya2023\\bin\\Render.exe' # 

camera_name = 'カメラ名'
project_path = 'Mayaプロジェクトパス'
output_path = '出力パス'
scene_path = 'Mayaシーンパス'
s_frame = 1 #開始フレーム
e_frame = 10 #終了フレーム
chunksize = 1 #タスクごとのフレーム数
pool = 'pool_01' #プール名
group = 'group_01' #グループ名
priority = 50
job_name = 'ジョブ名'
department = '部門（任意）'
comment = 'コメント（任意）'

submit_cmd = [deadlinecommand_exe,
    '-SubmitCommandLineJob',
    '-executable', render_exe,
    '-arguments', '-r file -cam <QUOTE>{0}<QUOTE> -s <STARTFRAME> -e <ENDFRAME> -proj <QUOTE>{1}<QUOTE> -rd <QUOTE>{2}<QUOTE> <QUOTE>{3}<QUOTE>'.format(camera_name, project_path, output_path, scene_path),
    '-frames', str(s_frame) + '-' + str(e_frame),
    '-chunksize', str(chunksize),
    '-pool', pool,
    '-group', group,
    '-priority', str(priority),
    '-name', job_name,
    '-department', department,
    '-prop', 'Comment=' + comment]

subprocess.run(submit_cmd)
```

他にも細かいオプションを付け加えていきたい場合は、`-prop KEY=VALUE`の形式で追加していきます。
```python
submit_cmd.extend(['-prop', 'SecondaryPool=pool_02'])
submit_cmd.extend(['-prop', 'DeleteOnComplete=true'])
```


## jobファイルを作成するパターン
deadline関係の引数をまとめた`info.job`と、MayaBatch関連の引数をまとめた`plugin_info.job`を事前に用意し、`deadlinecommand.exe`に渡す方法もあります。前項の「直接フラグを付けるパターン」よりはこちらのほうが扱いやすいかと思います。Deadline Repository付属のSubmitterスクリプトもこちらの形式です。
※各`.job`のファイル名は何でもOKです。

```info.job
Plugin=MayaBatch
Name=ジョブ名
Department=部門（任意）
Comment=コメント（任意）
Pool=pool_01
SecondaryPool=
Group=group_01
Priority=50
Allowlist=
DeleteOnComplete=false
Frames=1-10
ChunkSize=1
```

引数は`<リポジトリパス>\plugins\MayaBatch\MayaBatch.py`に記述されている`MayaBatchPlugin`クラスの変数に対応しているようです。
```plugin_info.job
Renderer=arnold
MayaToArnoldVersion=5
Version=2023
Build=64bit
Animation=1
UsingRenderLayers=0
RenderLayer=
ImageWidth=1920
ImageHeight=1080
Camera=カメラ名
ProjectPath=Mayaプロジェクトパス
OutputFilePath=出力パス
OutputFilePrefix=出力ファイル名
SceneFile=Mayaシーンパス
```
ここでは仮にTempフォルダに保存しているものとします。
```python
import os
import subprocess

deadlinecommand_exe = os.path.join(os.environ['DEADLINE_PATH'], 'deadlinecommand.exe')
job_info = os.path.join(os.environ['TEMP'], 'info.job')
plugin_info = os.path.join(os.environ['TEMP'], 'plugin_info.job')
submit_cmd = [deadlinecommand_exe, job_info, plugin_info]

subprocess.run(submit_cmd)
```

### 引数をtxtファイルにまとめる
`.job`ファイルのパスを`.txt`にひとまとめにしてコマンドへ渡すこともできます。
```submission.txt
C:\Users\<USERNAME>\AppData\Local\Temp\info.job
C:\Users\<USERNAME>\AppData\Local\Temp\plugin_info.job
```
```python
subprocess.run([deadlinecommand_exe, 'submission.txt'])
```

### 複数ジョブを一括送信する
複数のジョブを一括で送信する場合は`SubmitMultipleJobs`フラグを使用します。この場合も適当な`.txt`に引数をひとまとめにしてコマンドへ渡すことができます。
```args.txt
SubmitMultipleJobs
-jobs
C:\Users\<USERNAME>\AppData\Local\Temp\job1_info.job
C:\Users\<USERNAME>\AppData\Local\Temp\job1_plugin_info.job
-jobs
C:\Users\<USERNAME>\AppData\Local\Temp\job2_info.job
C:\Users\<USERNAME>\AppData\Local\Temp\job2_plugin_info.job
```
```python
subprocess.run([deadlinecommand_exe, 'args.txt'])
```

### 送信先のリポジトリを指定する
特に何も指定しない場合、送信先のリポジトリはDeadline Launcherでアクティブになっているリポジトリになりますが、`RunCommandForRepository`フラグを付けることでリポジトリを指定してサブミットができます。
```args.txt
RunCommandForRepository
Direct
\\<リポジトリPC名>\DeadlineRepository10
SubmitMultipleJobs
-jobs
C:\Users\<USERNAME>\AppData\Local\Temp\job1_info.job
C:\Users\<USERNAME>\AppData\Local\Temp\job1_plugin_info.job
-jobs
C:\Users\<USERNAME>\AppData\Local\Temp\job2_info.job
C:\Users\<USERNAME>\AppData\Local\Temp\job2_plugin_info.job
-jobs
C:\Users\<USERNAME>\AppData\Local\Temp\job3_info.job
C:\Users\<USERNAME>\AppData\Local\Temp\job3_plugin_info.job
```

## 各種情報の取得
実際のツールに応用する場合は「Poolをプルダウンから選びたい」「Workerの一覧を取得したい」など出来るだけ使用者が手打ちする箇所を減らす必要があるかと思います。少し本題とはズレますが、同じく`deadlinecommand.exe`を使用して情報を取得する方法もいくつか記載していきます。

#### Deadlineバージョン
```python
subprocess.run([deadlinecommand_exe, 'GetVersion'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['v10.2.0.10 Release (3b87216c7)']
```
#### リポジトリパス
```python
subprocess.run([deadlinecommand_exe, 'GetRepositoryRoot'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['\\\\<リポジトリPC名>\\DeadlineRepository10']
```
#### Pool, Group
```python
subprocess.run([deadlinecommand_exe, 'GetPoolNames'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['pool_01', 'pool_02', 'pool_03']

subprocess.run([deadlinecommand_exe, 'GetGroupNames'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['group_01', 'group_02', 'group_03']
```

#### Worker
```python
subprocess.run([deadlinecommand_exe, 'GetSlaveNames'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['Worker_01', 'Worker_02', 'Worker_03', 'Worker_04', 'Worker_05']
```

#### Group/Poolに割り当てられているWorker
```python
subprocess.run([deadlinecommand_exe, 'GetSlaveNamesInGroup', 'group_01'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['Worker_01', 'Worker_02']

subprocess.run([deadlinecommand_exe, 'GetSlaveNamesInPool', 'pool_01'], capture_output=True, text=True, shell=True).stdout.splitlines()
# Result: ['Worker_03', 'Worker_04']
```

## 余談
ちなみに、Deadline Repositoryに付属しているSubmitter（`<リポジトリパス>\submission\Maya\Installers\Maya-submitter-windows-installer.exe`）をインストールすると、シェルフに以下のようなボタンが現れ「**Submit Job To Deadline**」というツールが起動できるようになります。
![SubmitJobToDeadline](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211093/cac7813f-4f9e-6804-826a-159c3543b20f.png)
シェルフのコマンドにはMELで`SubmitJobToDeadline`とだけ書かれていますが、こちらは`<リポジトリパス>\submission\Maya\Main\SubmitMayaToDeadline.mel`内で定義されている関数を呼び出しています。

`SubmitMayaToDeadline.mel`は`C:\ProgramData\Thinkbox\Deadline10\submitters\Maya\deadline.mod`[^2]経由でMaya起動時にロードされます。このMELスクリプトの内容を追っていくと最終的にやっていることは`.job`ファイルの作成と`deadlinecomamnd.exe`の実行になっています。（他にもいろいろやっていそうですがあまり深くは調べてません💦）

[^2]:SubmitterをAll usersでインストールするとこのパスが環境変数`MAYA_MODULE_PATH`に追加されます。