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

変形・配置

  • 3dsMax
  • Python
  • pymxs
  • チートシート
3ds Max Python チートシート の章一覧

オブジェクトを動かす操作です。プロパティに代入する(絶対指定) 方法と、move / rotate / scale で相対的に動かす 方法の 2 系統があります。 pymxs には in coordsys が無いので、ローカル軸で動かすときは 行列で計算する のが Python 流です。

位置・回転・スケールを読む / 書く

b = rt.getNodeByName("Box001")

b.pos                                  # MXS: b.pos  -- Point3
b.rotation                             # MXS: b.rotation  -- Quat
b.scale                                # MXS: b.scale  -- Point3
b.transform                            # MXS: b.transform  -- Matrix3。位置+回転+スケール

b.pos = rt.Point3(10, 20, 0)           # MXS: b.pos = [10,20,0]
b.scale = rt.Point3(2, 2, 2)           # MXS: b.scale = [2,2,2]
  • ⚠️ b.pos.x = 10 は効きません(b.pos はコピーが返るため)。成分だけ変えるときは取り出して戻します。
b = rt.getNodeByName("Box001")
p = b.pos
p.z += 30              # MXS: b.pos.z += 30
b.pos = p

相対的に動かす(move / rotate / scale)

b = rt.getNodeByName("Box001")
rt.move(b, rt.Point3(10, 0, 0))                # MXS: move b [10,0,0]
rt.rotate(b, rt.EulerAngles(0, 0, 45))         # MXS: rotate b (eulerAngles 0 0 45)
rt.rotate(b, rt.Quat(90, rt.Point3(1, 0, 0)))  # MXS: rotate b (quat 90 [1,0,0])  -- 軸と角度で回す
rt.scale(b, rt.Point3(2, 2, 2))                # MXS: scale b [2,2,2]

rt.move(rt.objects, rt.Point3(0, 0, 5))        # MXS: move objects [0,0,5]
  • move / rotate / scale は 今の状態からの相対変形 です。絶対値で指定したいときはプロパティに代入します。
  • move / rotate / scale は mapped 関数なので、rt.objects や rt.selection、配列をそのまま渡すと中のオブジェクト全部に一括で適用されます(上の例は全オブジェクトを Z に 5 動かしています)。
  • 軸はワールド(in coordsys を付けない MaxScript の既定と同じ)、回転・スケールの中心は各オブジェクトのピボットです。

回転を角度(オイラー角)で読む

b = rt.getNodeByName("Box001")
rt.rotate(b, rt.EulerAngles(0, 0, 30))
z = rt.getProperty(b, "rotation.z_rotation")     # UI の [回転] Z と同じ値(度) → 30.0
# MXS: b.rotation.z_rotation
x = rt.getProperty(b, "rotation.x_rotation")     # X / Y も同じ書き方
  • rotation.x_rotation / y_rotation / z_rotation は、既定の回転コントローラ(オイラー XYZ)の値で、UI の [回転] 欄と同じ度数です。b.rotation.z_rotation とは書けないので(→「pymxs の基本」章のネストしたプロパティ)、rt.getProperty にパスを文字列で渡します。
  • ⚠️ b.rotation は Quat(四元数)なので、そこから角度を計算すると、UI の数字と符号や表し方が一致しないことがあります。画面と同じ値が欲しいときは上の書き方で読みます。

行列で配置する(Matrix3)

b = rt.getNodeByName("Box001")
tm = rt.rotateZMatrix(90) * rt.transMatrix(rt.Point3(0, 50, 0))   # Z 90 度回して (0,50,0) へ
b.transform = tm                                                   # 位置・回転・スケールがすべてこの行列の値で置き換わる

x_axis = b.transform.row1       # ローカル X 軸の向き(ワールド)
pos = b.transform.row4          # 位置(= b.pos)
  • MaxScript の行列は 「左から順に適用」 です。A * B は「A をしてから B をする」という意味になります(行ベクトルの流儀)。たとえば T * b.transform は「先に T、次に今の b.transform」という意味で、MaxScript の preTranslate(左から掛ける)に対応します(右から掛ける translate はその逆です)。
  • rt.transMatrix / rt.rotateXMatrix / rt.rotateYMatrix / rt.rotateZMatrix / rt.scaleMatrix で部品を作って掛け合わせます。
  • row1〜row3 がローカル X / Y / Z 軸、row4 が位置です。

ローカル軸で動かす(in coordsys local の代わり)

b = rt.getNodeByName("Box001")
rt.rotate(b, rt.EulerAngles(0, 0, 90))                     # 準備: Z 90 度回しておく

b.transform = rt.transMatrix(rt.Point3(10, 0, 0)) * b.transform   # MXS: in coordsys local move b [10,0,0]  -- ローカル X に 10
b.transform = rt.rotateXMatrix(30) * b.transform                  # ローカル X 軸まわりに 30 度
  • 「移動や回転の行列」を左から掛けるとローカル軸、右から掛けるとワールド軸での変形になります(preTranslate / preRotate が左から、translate / rotate(行列版)が右からの掛け算に対応します)。
  • 回転していない Box ならローカル X とワールド X は同じ向きなので結果も同じですが、回したあとはローカル X がその向きに沿って動きます。この例では先に Z 90 度回転しているため、ローカル X に 10 動かすとワールドでは Y に 10 動きます。

MaxScript の書き方をそのまま使いたいときは、rt.execute にノードをグローバル変数で渡します。

rt.myToolNode = rt.getNodeByName("Box001")
rt.execute("in coordsys local move myToolNode [10,0,0]")
rt.execute("about [0,0,0] rotate myToolNode (eulerAngles 0 0 90)")   # 原点を中心に回す
  • 使い終わったら rt.myToolNode = None にして、グローバル変数を残さないようにします。

別オブジェクトに合わせる(位置合わせ)

a = rt.getNodeByName("Box001")
t = rt.getNodeByName("Sphere001")

a.pos = t.pos                  # MXS: a.pos = t.pos  -- 位置だけ合わせる
a.rotation = t.rotation        # 向きだけ合わせる
a.transform = t.transform      # 位置・向き・スケールを丸ごと一致させる

バウンディングボックスと接地

s = rt.getNodeByName("Sphere001")
s.min, s.max, s.center               # 値を見る(min / max は読み取り専用) -- ワールド空間の最小 / 最大 / 中心
size = s.max - s.min                 # 大きさ

p = s.pos
p.z -= s.min.z                       # MXS: s.pos.z -= s.min.z  -- 一番下を Z=0 に合わせる
s.pos = p

bb_min, bb_max = rt.nodeLocalBoundingBox(s)     # ローカル空間のバウンディングボックス
# bb = rt.nodeLocalBoundingBox(s, asBox3=True)  # 3ds Max 2022 以降は Box3 値としても受け取れる
  • min / max / center は ワールド空間 の値です。回転したオブジェクトの「自分の向きでの大きさ」は nodeLocalBoundingBox で取ります。
  • 配列を返す関数は、Python のアンパック(a, b = ...)で受け取れます。

ピボット(基点)を操作する

b = rt.getNodeByName("Box001")
b.pivot = b.center             # MXS: b.pivot = b.center  -- ピボットをバウンディングボックス中心へ
rt.CenterPivot(b)              # [中心オブジェクト] と同じ(結果は b.pivot = b.center と同じ)
rt.WorldAlignPivot(b)          # [ワールド座標に位置合わせ] と同じ
rt.ResetXForm(b)               # [X フォームをリセット](XForm モディファイヤが載る)
  • pivot を変えても メッシュは動きません(基点だけが動く)。UI の [基点にのみ影響] と同じです。
  • ResetXForm のあとはスタックに XForm が載るので、確定させたいときは rt.collapseStack(b) します(→「モディファイヤ」章)。

よく使う形

# 選択を X 方向に 30 ずつ並べる
for i, o in enumerate(rt.selection):
    o.pos = rt.Point3(i * 30, 0, 0)

# 位置を 10 単位のグリッドに丸める
for o in rt.objects:
    p = o.pos
    o.pos = rt.Point3(round(p.x / 10) * 10, round(p.y / 10) * 10, round(p.z / 10) * 10)

# ランダムに散らす
import random
for o in rt.objects:
    o.pos = rt.Point3(random.uniform(-100, 100), random.uniform(-100, 100), 0)
  • Python の enumerate / round / random(標準ライブラリ)がそのまま使えます。
  • ⚠️ Python 3 の round() は偶数への丸め(銀行丸め)です。round(0.5) は 1 ではなく 0 になります。

落とし穴まとめ

  • b.pos.x = ... は効かない。取り出して、変えて、戻す。
  • move / rotate / scale は相対、プロパティ代入は絶対。
  • in coordsys / about は pymxs に無い。行列を左から掛けるか、rt.execute で MaxScript を使う。
  • min / max / center はワールド空間。
  • pivot の変更はメッシュを動かさない。

関連

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