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

アニメーション

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

キーを打つ基本は pymxs.animate(True) と pymxs.attime(フレーム) の 2 つの with です。 キーを直接いじるときはコントローラを取り出しますが、pymxs では b.pos.controller と書けない ので、getPropertyController を使います。

時間(タイムスライダ)を操作する

rt.animationRange = rt.Interval(0, 120)     # MXS: animationRange = interval 0 120
rt.frameRate = 30                           # MXS: frameRate = 30
rt.sliderTime = 24                          # MXS: sliderTime = 24

rt.currentTime.frame                        # 24.0
rt.animationRange.start.frame, rt.animationRange.end.frame   # (0.0, 120.0)
rt.ticksPerFrame                            # 1 フレームあたりのティック数(30fps なら 160)
  • 整数を渡すと フレーム数 として扱われます。
  • 3ds Max の内部の時間単位は「ティック」(1 秒 = 4800 ティック)です。

キーを打つ

b = rt.getNodeByName("Box001")

with pymxs.animate(True):                      # MXS: with animate on (
    with pymxs.attime(0):                      #   at time 0 b.pos = [0,0,0]
        b.pos = rt.Point3(0, 0, 0)
    with pymxs.attime(50):                     #   at time 50 b.pos = [100,0,0]
        b.pos = rt.Point3(100, 0, 0)
        rt.rotate(b, rt.EulerAngles(0, 0, 90))  #   at time 50 rotate b (eulerAngles 0 0 90)
                                               # )
  • animate(True) の中で値を変えると、attime で指定した時間にキーが打たれます。attime を付けないと 今のタイムスライダの時間 に打たれます。
  • attime だけなら値を読むだけでキーは打たれない。animate なしで書くと、キーがあるトラックでは全キーが一緒にずれるので注意。
  • ⚠️ キーの無いトラックに 0 以外の時間で最初のキーを打つと、フレーム 0 にも元の値のキーが自動で作られます(rt.preferences.autoKeyDefaultKeyOn)。

ある時間の値を読む

b = rt.getNodeByName("Box001")
with pymxs.animate(True):
    with pymxs.attime(100):
        b.pos = rt.Point3(0, 0, 100)

samples = []
for f in range(0, 101, 25):
    with pymxs.attime(f):                  # MXS: at time f b.pos
        samples.append(b.pos.z)
  • タイムスライダを動かさずに、任意の時間の値を読めます。
  • 読むだけなら animate は不要です。

コントローラを取り出す

b = rt.getNodeByName("Box001")
pos_ctrl = rt.getPropertyController(b.controller, "Position")   # MXS: b.pos.controller
x_ctrl = rt.getPropertyController(pos_ctrl, "X Position")        # MXS: b.pos.controller.x_position.controller
rot_ctrl = rt.getPropertyController(b.controller, "Rotation")

rt.classOf(pos_ctrl)       # Position_XYZ
rt.classOf(x_ctrl)         # bezier_float
  • ⚠️ b.pos は Point3 の値そのもので controller プロパティを持たないため、b.pos.controller は AttributeError になります。MaxScript では .pos.controller をパーサが特別扱いしているので書けます。
  • getPropertyController に渡す名前は、トラックビューに表示される名前("Position" / "X Position" など)です。

キーを読む / 追加する / 消す

b = rt.getNodeByName("Box001")
with pymxs.animate(True):
    for f, x in [(0, 0), (30, 50), (60, 0)]:
        with pymxs.attime(f):
            b.pos = rt.Point3(x, 0, 0)

x_ctrl = rt.getPropertyController(rt.getPropertyController(b.controller, "Position"), "X Position")

n = rt.numKeys(x_ctrl)                                   # MXS: numKeys x_ctrl → 3
times = [k.time.frame for k in x_ctrl.keys]              # [0.0, 30.0, 60.0]
values = [k.value for k in x_ctrl.keys]                  # [0.0, 50.0, 0.0]

k = rt.addNewKey(x_ctrl, 90)                             # MXS: addNewKey x_ctrl 90f
k.value = 25                                             # 追加したキーの値
rt.deleteKey(x_ctrl, 1)                                  # MXS: deleteKey x_ctrl 1(1 番目のキーを削除、1 始まり)
rt.moveKeys(x_ctrl, 10)                                  # MXS: moveKeys x_ctrl 10(全キーを 10 フレーム後ろへ)
  • ctrl.keys[0] のように 添字で読むと 0 始まり、deleteKey などの関数に渡す番号は 1 始まり です。
  • キーの値はキーオブジェクトの .value、時間は .time です。k.value = ... のような直接の代入はキーに反映されます(キーはノードのプロパティと違ってコピーではありません)。
  • ⚠️ キーを追加・削除・移動すると番号が振り直されるので、取っておいたキーの変数は使い回さないでください。

すべてのキーを消す

b = rt.getNodeByName("Box001")
with pymxs.animate(True):
    with pymxs.attime(10):
        b.pos = rt.Point3(0, 0, 50)

rt.deleteKeys(b, rt.Name("allKeys"))          # MXS: deleteKeys b #allKeys(ノードの全キー)

補間(接線タイプ)を変える

b = rt.getNodeByName("Box001")
with pymxs.animate(True):
    for f in (0, 50):
        with pymxs.attime(f):
            b.pos = rt.Point3(f, 0, 0)

x_ctrl = rt.getPropertyController(rt.getPropertyController(b.controller, "Position"), "X Position")
for k in x_ctrl.keys:
    k.inTangentType = rt.Name("linear")       # MXS: k.inTangentType = #linear
    k.outTangentType = rt.Name("linear")
  • 種類は #smooth / #linear / #step / #fast / #slow / #custom / #auto を rt.Name で渡します。

コントローラを割り当てる

b = rt.getNodeByName("Box001")
rt.setPropertyController(b.controller, "Position", rt.Noise_Position())     # MXS: b.pos.controller = Noise_Position()(位置をノイズに)

s = rt.getNodeByName("Sphere001")
t = rt.getNodeByName("Teapot001")
pc = rt.Position_Constraint()
rt.setPropertyController(s.controller, "Position", pc)       # MXS: s.pos.controller = Position_Constraint()(位置コンストレイント)
pc.appendTarget(t, 50)                                       # MXS: pc.appendTarget t 50(ウェイト 50)
  • setPropertyController(親コントローラ, "名前", 新しいコントローラ) で差し替えます。
  • 変換(位置・回転・スケール)のように最初からコントローラが付いているプロパティ以外(例: Box の height)にコントローラを付けるときは、rt.getSubAnim で SubAnim をたどって controller プロパティに入れます(公式ヘルプ「Accessing Object Properties and Controllers」)。Box なら sub = rt.getSubAnim(rt.getSubAnim(b, 4), 3)(4 = Object、3 = Height)としてから rt.setProperty(sub, "controller", rt.bezier_float()) とします。

ベイクする(コンストレイントの動きを別ノードのキーに焼き付ける)

s = rt.getNodeByName("Sphere001")
t = rt.getNodeByName("Teapot001")
with pymxs.animate(True):                       # 準備: ティーポットを動かし、球をコンストレイントで追従させる
    with pymxs.attime(20):
        t.pos = rt.Point3(-50, 100, 0)
pc = rt.Position_Constraint()
rt.setPropertyController(s.controller, "Position", pc)
pc.appendTarget(t, 100)

start = int(rt.animationRange.start.frame)
end = int(rt.animationRange.end.frame)

samples = {}
for f in range(start, end + 1):                  # ① 毎フレームの変換を読む
    with pymxs.attime(f):
        samples[f] = s.transform

baked = rt.Point(name=s.name + "_baked")         # MXS: Point name:(s.name + "_baked")(② 別のノードにキーとして書く)
with pymxs.animate(True):
    for f, tm in samples.items():
        with pymxs.attime(f):
            baked.transform = tm
  • 「読む」と「書く」のループを分けると、途中で読む値が変わる心配がありません。
  • 毎フレーム書くのでキーが多くなります。キーを減らしたいときは、書き込み後に間引く処理を足します。
  • 同じノード(この例なら Sphere001)にベイクしたいときは、先に rt.setPropertyController(s.controller, "Position", rt.Position_XYZ()) でコンストレイントを外し、通常のコントローラに戻してから書き込みます。

再生する

rt.playAnimation(immediateReturn=True)   # 再生を始めてすぐ戻る(MXS: playAnimation immediateReturn:true)
rt.isAnimPlaying()                        # 再生中なら True
rt.stopAnimation()                        # 停止
  • リアルタイム再生がオン(realTimePlayback = true)のときは、immediateReturn=True を付けないと、ユーザが止めるまでスクリプトが先に進みません。オフのときはすぐ戻ります。

落とし穴まとめ

  • b.pos.controller は書けない。rt.getPropertyController(b.controller, "Position")。
  • キーの添字は 0 始まり、deleteKey の番号は 1 始まり。
  • attime を付けずに animate(True) で値を変えると、今のタイムスライダの時間にキーが打たれる。
  • 時間は整数ならフレーム数。内部の単位はティック(1 秒 = 4800)。

関連

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