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

レンダリング・ビューポート・バッチ

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

画像を出す処理と、画面を持たない バッチ処理(3dsmaxbatch) の書き方です。 Python で書くと、出力ファイル名の組み立てやフォルダ内のシーンの一括処理がとても楽になります。

レンダリングしてファイルに保存する

import os

rt.renderSceneDialog.close()                           # 設定を変える前にレンダリング設定ダイアログを閉じる
rt.renderers.current = rt.Default_Scanline_Renderer()  # レンダラーを指定(ここでは軽いスキャンライン)

out = os.path.join(rt.getDir(rt.Name("renderoutput")), "my_tool_test.png")
bmp = rt.render(outputFile=out, outputSize=rt.Point2(320, 240), vfb=False)
# MXS: render outputFile:out outputSize:[320,240] vfb:false
rt.close(bmp)                                          # 使い終わったビットマップを閉じてメモリを返す
  • rt.render() は [レンダリング設定] の内容とは別に、引数で解像度・出力先・フレーム・カメラなどを指定できます。戻り値はレンダリング結果のビットマップです。
  • vfb=False にするとフレームバッファのウィンドウを出しません。バッチ処理ではこちらにします。
  • ⚠️ レンダリング設定ダイアログが開いていると設定変更が効かないことがあるので、先に閉じておきます。

カメラ・フレームを指定してレンダリングする

import os

rt.renderers.current = rt.Default_Scanline_Renderer()
cam = rt.Freecamera(pos=rt.Point3(0, -200, 50))
rt.rotate(cam, rt.EulerAngles(80, 0, 0))

out_dir = os.path.join(rt.getDir(rt.Name("renderoutput")), "my_tool_seq")
os.makedirs(out_dir, exist_ok=True)

for f in range(0, 3):
    path = os.path.join(out_dir, "shot_{:04d}.png".format(f))
    bmp = rt.render(camera=cam, frame=f, outputFile=path, outputSize=rt.Point2(160, 120), vfb=False)
    rt.close(bmp)
  • 1 フレームずつ render を呼ぶと、ファイル名の付け方(カメラ名・日付・連番の桁数など)を Python で自由に決められます。

レンダリング設定を読む / 変える

import os

rt.renderSceneDialog.close()
rt.renderWidth = 1920                          # MXS: renderWidth = 1920
rt.renderHeight = 1080
rt.rendTimeType = 2                            # 1=単一フレーム / 2=アクティブタイムセグメント / 3=範囲 / 4=フレーム指定
rt.rendOutputFilename = os.path.join(rt.getDir(rt.Name("renderoutput")), "final_.png")
rt.rendSaveFile = True                         # ファイルに保存する

renderer_name = str(rt.classOf(rt.renderers.current))   # 今のレンダラー
  • これらは [レンダリング設定] ダイアログの [共通] タブの項目です。rt.render() はこのダイアログの設定を使わず引数で指定した内容だけでレンダリングしますが、rt.execute("max quick render") はダイアログの設定どおりにレンダリングします。
  • rendTimeType = 2(アクティブタイムセグメント)のように複数フレームを出力する設定にすると、rendOutputFilename の末尾に 4 桁の連番が自動で付きます。
  • レンダラー固有の設定(Arnold のサンプル数など)は rt.renderers.current のプロパティにあります。名前は rt.getPropNames(rt.renderers.current) で調べます。

ビューポートを操作する

rt.viewport.activeViewport = 4                          # 4 番目のビューをアクティブに(番号は 1 始まり)
rt.viewport.setType(rt.Name("view_persp_user"))         # パースビューにする
rt.execute("max tool zoomextents all")                  # 全オブジェクトを全ビューで全体表示 (max コマンド)
rt.redrawViews()

cam = rt.Freecamera(pos=rt.Point3(0, -200, 50))
rt.viewport.setCamera(cam)                              # アクティブなビューをカメラビューに
  • ビューの番号は MaxScript の番号なので 1 始まりです。
  • ズームや最大化など、メニューにある操作の多くは max ... コマンド(rt.execute)で呼びます。
  • ⚠️ activeViewport = 4 は 4 分割レイアウトが前提です。ビューを 1 つに最大化しているときはビューが 1 つしかないので、この番号では選べません。

ビューポートを画像として保存する

import os

rt.redrawViews()
bmp = rt.viewport.getViewportDib()                     # アクティブなビューをビットマップで取得
path = os.path.join(rt.getDir(rt.Name("temp")), "my_tool_viewport.png")
bmp.filename = path                                    # 保存先を決めて
rt.save(bmp)                                           # 保存する(MXS: save bmp)
rt.close(bmp)
  • プレビュー画像やレビュー用のスナップショットを作るのに便利です。captureAlpha=True(3ds Max 2015 以降の任意引数)を渡すとアルファ付きで取れます。

3dsmaxbatch でヘッドレス実行する

3dsmaxbatch.exe は、UI を出さずに 3ds Max を起動してスクリプトを実行するコマンドです。 .py を直接渡せるバージョンもありますが、ここでは MaxScript から python.ExecuteFile で呼ぶ小さな入口ファイル を用意する、どのバージョンでも使える形で示します。

入口の MaxScript ファイル(run_export.ms):

python.ExecuteFile @"C:\tools\my_tool\batch_export.py"

コマンドプロンプトから実行します(-mxsString で値を渡せます):

"C:\Program Files\Autodesk\3ds Max 2022\3dsmaxbatch.exe" ^
    C:\tools\my_tool\run_export.ms ^
    -mxsString src:"D:\scenes" ^
    -mxsString dst:"D:\fbx"

Python 側(batch_export.py)では、渡された値を maxOps.mxsCmdLineArgs から読みます。

import glob
import os

args = rt.maxOps.mxsCmdLineArgs                       # -mxsString / -mxsValue で渡された辞書
if args is None:
    raise SystemExit("3dsmaxbatch から実行してください")
src = args[rt.Name("src")]
dst = args[rt.Name("dst")]
os.makedirs(dst, exist_ok=True)

for max_path in sorted(glob.glob(os.path.join(src, "*.max"))):
    rt.loadMaxFile(max_path, quiet=True)               # quiet=True でダイアログを出さない
    fbx = os.path.join(dst, os.path.splitext(os.path.basename(max_path))[0] + ".fbx")
    ok = rt.exportFile(fbx, rt.Name("noPrompt"), using=rt.FBXEXP)
    print("{} -> {} ({})".format(max_path, fbx, "OK" if ok else "NG"))
    rt.resetMaxFile(rt.Name("noPrompt"))
  • maxOps.mxsCmdLineArgs の辞書のキーは Name 値です(rt.Name("src"))。3ds Max を普通に起動したとき(3dsmax.exe)は値がありません。
  • print() の出力は、バッチ実行時は起動したコマンドプロンプトに出ます。
  • バッチ処理では ダイアログが出ると止まってしまう ので、quiet=True / #noPrompt / vfb=False を付け忘れないようにします。
  • 処理結果を伝えたいときは、最後に rt.quitMax(quiet=True, exitCode=1) のようにします。exitCode を指定すると、終了時に終了コードをコンソールに出します。バッチ処理でステータス(正常終了は 0、失敗は 1 など)を受け取るのに使います。

落とし穴まとめ

  • レンダリング設定を変える前に rt.renderSceneDialog.close()。
  • render() で作ったビットマップは rt.close(bmp) で閉じる(ループで溜めない)。
  • ビューの番号は 1 始まり。
  • バッチではダイアログを出さない(quiet=True / #noPrompt / vfb=False)。

関連

  • シーンの保存・書き出し: 「ファイル・データ」章の「シーンを保存する / 開く / リセットする」「FBX / OBJ に書き出す・読み込む」
  • レンダリングの前後に処理を挟む(preRender / postRender): 「コールバック・イベント」章の「一般コールバック(ファイル・ノード作成・レンダリングなど)」

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