MaxScript との連携
3ds Max Python チートシート の章一覧
pymxs で書けないことは、MaxScript の文字列を実行して補う のが定番です。逆に、MaxScript から Python を呼ぶこともできます。 既存の MaxScript 資産を活かしながら Python に移っていくときの書き方をまとめます。
MaxScript の式を実行する(rt.execute)
n = rt.execute("1 + 2") # MXS: execute "1 + 2" → 3
nodes = list(rt.execute("$*Box* as array")) # ワイルドカードでノードを集める
rt.execute("max select all") # max コマンドも実行できる
rt.execute(文字列)は MaxScript のコードを実行し、最後の式の値を返します。max ...形式の 3ds Max コマンド(max file open/max tool zoomextents allなど)は pymxs に直接の窓口がないので、rt.executeで呼びます。ただしmax undo/max redoだけは例外で、pymxs.run_undo()/pymxs.run_redo()として直接呼べます。
複数行の MaxScript を実行する
rt.execute("""
fn myToolCountVerts nodes = (
local total = 0
for n in nodes where canConvertTo n Editable_Mesh do
total += (getPolygonCount n)[2]
total
)
""")
total = rt.myToolCountVerts(rt.objects) # 定義した関数は rt から呼べる
rt.executeで定義した関数・struct・グローバル変数は、以降rt.名前で呼べます。- ループが重い処理は MaxScript 側で書いて 1 回だけ呼ぶ と速くなります。pymxs の呼び出しは 1 回ごとに Python と MaxScript の境界をまたぐためです(→「メッシュ・ポリゴン」章)。
Python の値を MaxScript に渡す
names = ["Box001", "Sphere001"]
rt.myToolNames = rt.Array(*names) # MaxScript の配列にしてからグローバル変数に入れる
rt.execute("for n in myToolNames do (getNodeByName n).wirecolor = red")
- ⚠️ 値を 文字列に埋め込んで
rt.executeするのは避けます。パスの\や引用符で壊れやすいからです。グローバル変数に入れて渡すか、MaxScript の関数を定義して引数で渡します。 rt.名前 = 値は、その名前がまだ無くても MaxScript のグローバル変数として作られます(3ds Max 2021 以降)。- 数値・文字列・真偽値はそのまま MaxScript の値になります。
- ⚠️ Python の
listをrt.名前 = リストで入れても、MaxScript の配列にはなりません(Python のリストのまま入り、MaxScript のfor ... inで回せない)。rt.Array(*リスト)で配列にしてから入れます。関数の 引数 に渡すときは自動で配列に変換されるので、この手間は要りません。辞書も同じで、rt.Dictionary(→「pymxs の基本」章)で作ってから入れます。
.ms ファイルを読み込む(fileIn)
import os
ms_path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_lib.ms")
with open(ms_path, "w", encoding="utf-8-sig") as f:
f.write('fn myToolHello who = ("Hello, " + who)\n')
rt.fileIn(ms_path) # MXS: fileIn @"...\my_tool_lib.ms"
rt.myToolHello("pymxs") # 'Hello, pymxs'
- 既存の MaxScript ライブラリは
rt.fileInで読み込めば、その関数や struct を Python から呼べます。 - MaxScript のファイルに日本語を含めるなら、UTF-8(BOM 付きが確実)で保存します。
MaxScript の struct を使う
rt.execute("""
struct MyToolLibStruct (
factor = 2.0,
fn double x = x * factor
)
global myToolLib = MyToolLibStruct()
""")
rt.myToolLib.double(21) # 42.0
rt.myToolLib.factor = 3.0 # メンバー変数も読み書きできる
rt.myToolLib.double(2) # 6.0
- struct のインスタンスは参照として渡されるため、
rt.myToolLib.factor = 3.0のようにメンバー変数へ代入すると、posのような値のコピーとは違い、struct 本体の状態がそのまま書き換わります。
MaxScript から Python を呼ぶ
MaxScript 側では python インタフェース(3ds Max 2015 以降。python.Import は 2017 以降)から Python を使います(ここは MaxScript のコードです)。
python.Execute "print('hello from python')" -- Python のコードを実行
python.ExecuteFile @"C:\tools\my_tool.py" -- .py ファイルを実行
os = python.Import "os" -- モジュールを読み込む
os.getcwd() -- そのまま関数を呼べる
Python で書いた関数を MaxScript から呼ぶには、rt.名前 = 関数 で MaxScript のグローバル変数に入れておきます。
def my_tool_add(a, b):
return a + b
rt.myToolAdd = my_tool_add # MaxScript のグローバル変数に Python 関数を入れる
rt.execute("myToolAdd 3 4") # 7(MaxScript からは普通の関数として呼べる)
- マクロスクリプト(
macros.new)・ロールアウトのボタン・DialogMonitorOPS.RegisterNotificationなど、「MaxScript の文字列しか受け付けない」場所から Python の処理を呼ぶときに使う方法です。
参照渡し(& 引数)を受け取る(byref)
b = rt.getNodeByName("Box001")
ok, new_nodes = rt.maxOps.cloneNodes(
b,
cloneType=rt.Name("instance"),
newNodes=pymxs.byref(None), # MXS: maxOps.cloneNodes b cloneType:#instance newNodes:&nn
)
new_nodes[0] # 複製されたノード
- MaxScript の
&変数で値を返す引数は、pymxs.byref(...)で渡します。関数の戻り値は タプル(本来の戻り値, 参照渡しの値…) になります。 - 出力専用の引数には
Noneを渡せます。入出力の引数には初期値を渡し、更新後の値はタプルで受け取ります(渡した変数そのものは書き換わりません)。 pymxs.byref()は 3ds Max 2021 で追加された関数です(2020 以前では使えません)。pymxs.mxsreference()は 3ds Max 2021 で非推奨になりました。byrefを使います。
カスタムアトリビュートを定義する
ca_def = rt.execute("""
attributes myToolData (
parameters main rollout:params (
weight type:#float ui:spnWeight default:1.0
)
rollout params "My Tool" (
spinner spnWeight "Weight" type:#float
)
)
""")
b = rt.getNodeByName("Box001")
rt.custAttributes.add(b.baseObject, ca_def) # MXS: custAttributes.add b.baseObject caDef
b.myToolData.weight = 0.5
- カスタムアトリビュートの 定義 は pymxs では書けないので、MaxScript の文字列で定義します。追加・値の読み書きは Python からできます。
custAttributes.addはノードを渡しても既定(baseObject:true)で基本オブジェクトに付きます。上のコードでb.baseObjectと明示的に書いているのは、それをはっきりさせるためです。
落とし穴まとめ
rt.executeに値を文字列で埋め込まない。グローバル変数か関数の引数で渡す。rt.executeやrt.名前 = ...で作ったグローバルは 3ds Max を閉じるまで残る。名前に接頭辞を付ける。showPropertiesなど「リスナーに書き出す」関数は、pymxs から呼ぶとリスナーに何も出ない(公式ヘルプの説明)。文字列で受け取るにはto=に StringStream を渡す(→「モジュール管理・デバッグ」章)。&引数はpymxs.byref。戻り値がタプルになる。
関連
- 値の翻訳ルール: 「pymxs の基本」章
- MaxScript の文字列で登録するコールバック: 「コールバック・イベント」章
コメント