アニメーション
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)。
関連
withのコンテキストの基本: 「pymxs の基本」章- 時間が変わったときに処理を走らせる: 「コールバック・イベント」章
コメント