pymxs の基本
3ds Max Python チートシート の章一覧
pymxs は MaxScript の世界を Python から触るための 薄いラッパー です。
MaxScript の関数・クラス・グローバル変数は、すべて rt.(= pymxs.runtime)の下にそのままの名前で並んでいます。
この章では、以降の章を読むのに必要な「翻訳ルール」をまとめます。
rt の基本
b = rt.Box(length=20, width=20, height=20) # MXS: b = box length:20 width:20 height:20
rt.classOf(b) # MXS: classOf b → Box
rt.superClassOf(b) # MXS: superClassOf b → GeometryClass
rt.redrawViews() # MXS: redrawViews()
- MaxScript のクラス・関数・グローバルは
rt.名前で呼べます。キーワード引数 は名前=値で渡します(MaxScript の名前:値)。 rtの名前は 大文字小文字を区別しません(rt.converttomeshもrt.convertToMeshも同じ)。Python としては不思議ですが、中身が MaxScript だからです。本書では MaxScript のヘルプと同じ表記にそろえます。
$ の代わり(ノードを名前で取る)
b = rt.getNodeByName("Box001") # MXS: $Box001
sel = rt.selection[0] # MXS: selection[1](1 つだけ選んでいるときは $ でも可)
boxes = list(rt.execute("$Box* as array")) # MXS: $Box* as array(ワイルドカード)
- Python には
$構文がありません。名前で 1 つ取るならgetNodeByName、ワイルドカードやパス指定を使いたいときはrt.executeで MaxScript の式として評価します。rt.readValue(rt.StringStream("$Box*"))のようにreadValueでストリームを読ませる書き方もヘルプで紹介されています。 - 見つからないとき
getNodeByNameはNoneを返します(MaxScript のundefined)。
Name 値(#world など)
world = rt.Name("world") # MXS: #world
rt.toolMode.coordsys(world) # MXS: toolMode.coordsys #world(ツールバーの参照座標系を実際に切り替える)
n = rt.Name("MyToolOption")
str(n) # 'MyToolOption'(# は付かない)
n == rt.Name("mytooloption") # True(大文字小文字を区別しない)
- 関数の「決まった選択肢」を表す MaxScript の Name 値(
#xxx)はrt.Name("xxx")で作ります。Name は大文字小文字を区別せず 1 つに統合されるので、既に使われている綴りの名前は 最初に登録されたときの大文字小文字で表示されます。 - Python の文字列を渡すと、型エラーになるか無視されることが多いので、Name を求める引数には必ず
rt.Nameを使います。 - よく使うものは定数にしておくと読みやすくなります(
NO_PROMPT = rt.Name("noPrompt")など)。
値の型(Point3 / Color / 行列など)
p = rt.Point3(10, 0, 5) # MXS: [10,0,5]
c = rt.Color(255, 128, 0) # MXS: color 255 128 0
q = rt.EulerAngles(0, 0, 45) # MXS: eulerAngles 0 0 45
m = rt.Matrix3(1) # MXS: matrix3 1(単位行列)
r = rt.Interval(0, 100) # MXS: interval 0 100
p2 = rt.Point2(640, 480) # MXS: [640,480]
p.x, p.y, p.z # 成分を読む
p + rt.Point3(1, 1, 1) # 演算子もそのまま使える
rt.length(p) # MXS: length p
- 数値・文字列・真偽値は Python の
int/float/str/boolに自動で変換されます。 Point3などの MaxScript の値はラッパー(MXSWrapperBase)のまま渡ってきます。p.xのように成分を読むことも、+や*で計算することもできます。- MaxScript の
undefinedは Python のNoneになります。
⚠️ ネストしたプロパティへの代入は効かない
b = rt.getNodeByName("Box001")
b.pos.x = 10 # ✗ 効かない。b.pos は「コピー」が返るので、コピーの x が変わるだけ
print(b.pos.x) # 0.0 のまま
# 正しい書き方 1: 取り出して、変えて、戻す
p = b.pos
p.x = 10
b.pos = p # MXS: b.pos.x = 10
# 正しい書き方 2: setmxsprop / getmxsprop でパスを文字列で渡す
b.setmxsprop("pos.y", 20)
b.getmxsprop("pos.y") # 20.0
rt.getProperty(b, "pos.y") # 20.0(MaxScript の getProperty でも読める)
- pymxs で一番ハマる所 です。MaxScript の
b.pos.x = 10は「位置の x を書き換える」特別扱いがありますが、Python ではb.posを取り出した時点でノードとのつながりが切れます。 - ネストしたプロパティの読み書きには 3 通りの書き方があります。
setmxsprop/getmxspropは pymxs のラッパー(MXSWrapperBase)自身が持つメソッドで、rt.setProperty/rt.getPropertyは MaxScript の関数です。どちらもパスを文字列で渡せば同じように動きます。
配列とインデックス(0 始まりと 1 始まり)
arr = rt.Array(10, 20, 30) # MXS: #(10, 20, 30)
arr[0] # 10(Python から添字で読むと 0 始まり)
len(arr) # 3
rt.append(arr, 40) # MXS: append arr 40
py_list = list(arr) # Python の list に変換 → [10, 20, 30, 40]
rt.meditMaterials[0] # マテリアルエディタの 1 番目のスロット(添字は 0 始まり)
rt.setMeditMaterial(1, rt.PhysicalMaterial()) # 関数の「番号」引数は 1 始まりのまま
- Python から添字でアクセスすると 0 始まり、MaxScript の関数に渡す番号(頂点番号・スロット番号など)は 1 始まり です。
- 関数が返す配列の 中身の値 が番号の場合も 1 始まりです(例:
polyop.getFaceVertsが返す頂点番号)。「入れ物の添字」と「中身の番号」を区別してください。 - Python の
listを MaxScript の関数に 引数として 渡すと、配列に自動で変換されます。ただしrt.名前 = リストでグローバル変数に入れたときは変換されないので、rt.Array(*リスト)にしてから入れます(→「MaxScript との連携」章)。
== で比べる(is は使わない)
a = rt.getNodeByName("Box001")
b = rt.selection[0]
a == b # True(同じノード)
a is b # False(ラッパーは毎回別の Python オブジェクト)
- pymxs のオブジェクトは取り出すたびに新しいラッパーに包まれます。同じノードかどうかは
==で比べます。 - ノードは Python の
dictのキーやsetの要素にもできます(3ds Max 2019 Update 1 以降)。
コンテキスト(undo / animate / at time / redraw)
b = rt.getNodeByName("Box001")
with pymxs.undo(True, "箱を動かす"): # MXS: undo "箱を動かす" on ( ... )
b.pos = rt.Point3(0, 0, 30)
with pymxs.animate(True): # MXS: with animate on ( ... )
with pymxs.attime(0): # MXS: at time 0 b.pos = [0,0,0]
b.pos = rt.Point3(0, 0, 0)
with pymxs.attime(50):
b.pos = rt.Point3(100, 0, 0)
with pymxs.redraw(False): # MXS: with redraw off ( ... )
for i in range(10):
rt.Sphere(radius=5, pos=rt.Point3(i * 15, 50, 0))
- MaxScript のコンテキスト式は、
pymxsの関数を Python のwith文で使います。使えるのはanimate/attime/atlevel/quiet/redraw/undoです。 - ⚠️
in coordsysとaboutは pymxs にありません。rt.setRefCoordSys(rt.Name("local"))のように参照座標系を切り替える方法もありますが、これはツールバーの参照座標系ドロップダウンを実際に切り替える UI 操作です。UI の設定まで変わるので、本書ではこの方法は使いません。代わりの書き方は「変形・配置」章でも扱います。 - ⚠️ pymxs の操作を 1 つの undo 項目にまとめ、元に戻せるようにするには
pymxs.undo(True, "ラベル")で囲みます(Python のヘルプは、囲まない操作は undo に記録されないと説明しています。MaxScript 自体のヘルプは「既定で undo 可能」と記述しており、両者の説明は食い違うため、ここでは断定しません)。 - ⚠️
pymxs.undo(True)の中で例外が起きると、それまでの変更が取り消され、例外は外に伝わりません(MaxScript とは違う動き)。
グローバル変数と Dictionary
rt.myToolCount = 3 # MXS: global myToolCount = 3
rt.execute("myToolCount + 1") # 4(MaxScript からも見える)
d = rt.Dictionary(("one", 1), ("two", 2)) # MXS: Dictionary #("one", 1) #("two", 2)
d["three"] = 3
py_dict = {str(k): d[k] for k in d.keys} # Python の dict に変換
rt.名前 = 値で MaxScript のグローバル変数を作れます。MaxScript のコードやrt.executeと値をやり取りするときに使います。- 3ds Max 2021 以降 は、このように
rt.名前 = 値と書くだけで新しいグローバル変数を暗黙に作成できます。2020 以前 ではrt.execute('global myToolCount = 3')のように MaxScript 側でglobal宣言してから値を代入します。 - グローバルはセッション中ずっと残るので、名前には
myTool...のような接頭辞を付けて衝突を避けます。
BitArray と as 変換
rt.execute("fn pyToArray v = (v as array)") # MaxScript で変換関数を 1 度だけ定義
ba = rt.execute("#{1..3, 7}") # MXS: #{1..3, 7}
list(rt.pyToArray(ba)) # [1, 2, 3, 7]
- MaxScript の
as変換(x as array/x as stringなど)は Python に同じ構文がありません。小さな MaxScript 関数をrt.executeで定義して呼ぶ のが定番です。 - 頂点や面の選択(BitArray)を Python の
listにするときによく使います。
時間の値
rt.sliderTime = 10 # MXS: sliderTime = 10(整数を渡すとフレーム数として扱われる)
rt.currentTime # 現在の時間(Time 値)
rt.currentTime.frame # 10.0(フレーム数。Python では float)
rt.animationRange = rt.Interval(0, 200) # MXS: animationRange = interval 0 200
rt.animationRange.end.frame # 200.0
- MaxScript の時間リテラル(
10f/1sなど)は Python にありません。整数を渡すと、基本的にフレーム数として扱われます。 .frameは Python では float で返りますが、MaxScript のリスナーでは整数のように表示されます。
落とし穴まとめ
b.pos.x = 10のようなネストした代入は効かない。取り出して戻すかsetmxspropを使う。- 添字は 0 始まり、関数に渡す番号は 1 始まり。
- 同じノードかどうかは
isではなく==で比べる。 - Name を求める引数には文字列ではなく
rt.Name("...")を渡す。 in coordsys/aboutは無い。pymxs の操作を 1 つの undo 項目にまとめ、元に戻せるようにするにはpymxs.undo(True, "ラベル")で囲む(Python のヘルプは、囲まない操作は undo に記録されないと説明している)。print/assertは pymxs からは呼べない。Python のprint()/assert文、またはpymxs.print_()を使う(→「モジュール管理・デバッグ」章)。
関連
- MaxScript と行き来する書き方: 「MaxScript との連携」章
- 位置と回転の扱い: 「変形・配置」章
コメント