リファレンス / 3ds Max / 3ds Max Python チートシート

MaxScript との連携

  • 3dsMax
  • Python
  • pymxs
  • チートシート
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。戻り値がタプルになる。

関連

← 3ds Max Python チートシート へ