MaxScriptからPythonを呼び出す

自分へのメモも兼ねて、Qitaに記事を投稿します。

今回は、MaxScriptからPytyonを呼び出す方法にはまったので記事を書いてきます。


# 検証環境

3DS Max 2024

# MaxScriptとは

MAXScript は、3ds Max のビルトイン スクリプト言語です。
詳しくは[こちら](https://help.autodesk.com/view/3DSMAX/2025/JPN/?guid=GUID-FE40F021-5444-4709-BF08-DF1F1F0960C3)をご参照ください

# やりたかった事

やりたかった事としては、MaxScriptからPythonを呼び出すという事になります。
順を追って説明して行きます。

1. **『MaxScript』スクリプトと『Python』スクリプトは独立しており、それぞれ違う目的で作られたものである**
1. **『MaxScript』スクリプトは、機能が固まったツールとなっている**
1. **『Python』スクリプトは、追加で付ける機能が別途Pythonで作成されてしまった形となっている**

以上の前提条件により、MaxScriptスクリプトから機能として独立しているPythonスクリプトをキックした方が、早いように思えました。

:::note warn
呼び出すPythonスクリプトは、大きいスクリプトではない為、MaxScriptで書き直した方が、正直より品質が上がった成果物になったかもと思う一面もあります…
:::
# まずは公式

公式で参考になりそうなページは、以下になります。

* [インタフェース: python](https://help.autodesk.com/view/MAXDEV/2024/JPN/?guid=GUID-860B6BEB-AE3E-4F99-96E9-CCD70DF4B772)
* [MAXScript から Python を実行](https://help.autodesk.com/view/MAXDEV/2023/JPN/?guid=GUID-E9BA617D-50E5-447F-997B-4F640F1110BA)

いくつか関数が用意されていますが、今回関係があるのは、『**Execute**』と『**ExecuteFile**』となります。

# Execute

**Pythonのスクリプトを文字列で渡して実行**します。
Pythonのスクリプト構文にエラーがあった場合には正しく動作しません。
MaxScriptでの呼び出し方は以下になり、第一引数にPythonのスクリプト構文の文字列を渡します。
戻り値は、Executenの実行結果([※後述](https://qiita.com/_akoto_/items/f95b9ba19e5a2216d3d2#%E6%88%BB%E3%82%8A%E5%80%A4))を返します。

```
result = python.Execute "print('hello')"
```

# ExecuteFile

こちらは、**Pythonのスクリプトのファイルを渡して実行**します。
Pythonファイルにエラーがあった場合には正しく動作しません。
MaxScriptでの呼び出し方は以下になり、第一引数にPythonファイルのフルパスとなります。
戻り値は、ExecuteFileの実行結果([※後述](https://qiita.com/_akoto_/items/f95b9ba19e5a2216d3d2#%E6%88%BB%E3%82%8A%E5%80%A4))を返します。

```
python.ExecuteFile "demoBentCylinder.py"
```

# 戻り値

それぞれの戻り値は、 以下の結果が返り、『classOf』で型を見てみたところ『**Name型**』を返していました。


|ステータス       |説明                        |
|----------------|--------------------------- |
|#success        |成功                        |
|#pathError      |実行するファイルのパスが問題？|
|#initError      |Pythonの初期化に失敗？       |
|#scriptFileError|スクリプトエラー？           |
|#executeError   |実行エラー？                 |

:::note info
エラーは、『スクリプトエディタ』でエラーの再現を試してみたのですが、『存在しないファイルを読ませたり』『スペルミスや未定義の関数を呼ぶ』等を行ってみたのですが、『#success』しか返ってこず、エラーの再現が取れなかったので説明文は**予測**です。
『スクリプトエディタ』で上述したエラー文を実行した場合は、**失敗し例外がスロー**されています。
:::

# Pythonファイルの作り方の違い

今回の本題に入る前に今回ハマっていた要因の一部でもあるPythonファイルの作り方について説明します。
元々あったPythonファイルは、前述した通り、それ一つでウインドを持ち、ボタンを持った、ツール単体としても機能するスクリプトでした。
具体的には、以下のように、**QtWidgets.QDockWidget** を使用し、ウインドウを作成し表示する機能が組まれている内容でした。

```:Hoge.py
    # Pythonスクリプトです
    main_window = qtmax.GetQMaxMainWindow()
    w = PyMaxDockWidget(parent=main_window)
    w.setFloating(True)
    w.show()
```

# 呼び出しが上手く行った例

ここからが本題になります。
実際に、呼び出しが上手く行ったPythonファイルの作り方は、ライブラリやモジュールに近いのような作り方がされており、**単にPythonの関数を呼び出すだけのような作り**です。

```:Hoge.ms
    -- MaxScriptです
    result = python.ExecuteFile @"C:\Test\HogeModule.py" 
    exestring = "CallHoge()"　　　　　 // CallHogeは HogeModule.py で定義されている
    result = python.execute exestring // 実行
    print result                      // エラー結果の出力
```

# 勘違いしていた点 その①

まず最初に勘違いしていた点として、『**ExecuteはPythonの構文を実行する関数**』『**ExecuteFileはPythonファイルを実行する関数**』と思っていた点です。
もう少し嚙み砕いて説明すると、実行前にロードも行っている為、ExecuteFileは、『**Import**』と『**Execute**』を同時に行っているものだと考えてください。
その為、ExecuteFile実行後に、**Executeを使ってExecuteFileでロードしたPythonファイルの関数を呼び事が可能**です。

# 勘違いしていた点 その②

もう一点勘違いしていた点として、**QtWidgets.QDockWidgetで作成されたPythonファイルに引数を渡せると思っていた点**です。
もっと、探せば別の方法が存在するかも知れませんが、今回は時間切れでした。

試した事としては

* ExecuteFileにコマンドライン引数を付けてみる → <font color="Red">**失敗**</font>
* Executeで引数付きのPythonファイルを呼び出して、そのPythonから引数付きでキックする → <font color="Red">**失敗**</font>

他に考えられた方法としては以下の方法です。
* batファイルからPythonを実行する(MaxScriptからbatを実行する必要がある)
* 外部ファイルに書き出す(遠回りだけど現実的かも)


# まとめ

今回は、『ExecuteFile』でPythonのスクリプトをロードして『Execute』で、必要な関数をキックする方法を取りました。
ツールとして独立していると、私のように上手く行かなかったりするので、『**最初からPythonで必要な処理だけの実装を書いて**』という担当者に頼み方をする必要がありそうですね。